需求
目前工作的開發環境有用 CI/CD,且會執行 Fortify 掃描網站弱點,PM 要手動將 Fortify 產出的 PDF 表格數據整理到 Word 文件,他希望這種重覆性質的工作可以做成自動化,本文主要是記錄如何將 PDF 文件內表格數據解析出來的過程。
概念
- 使用 IFilterTextReader (NuGet 套件) 讀取 PDF 文件
- 使用正規表達式將表格數據解析成資料集合的物件
- 將資料集合填入到 Word 文件 (本文不會實作)
實作
PDF 文件內容
紅框是這次要取出的表格數據
安裝 NuGet 套件
安裝 IFilterTextReader
使用 IFilterTextReader 讀取 PDF 文件
1 2 3 4 5 6 7 8
| using IFilterTextReader;
string filePath = @"X:\SomeWhere\Fortify.pdf"; string source = new IFilterTextReader.FilterReader(filePath).ReadToEnd();
Console.WriteLine(source);
|
PDF 讀取出來的內容被轉換成純文字了,這次需要的表格數據部份如下
使用正規表達式解析內容
將關鍵片段手動排版一下,以便思考該怎麼寫正規表達式的 pattern
1 2 3 4 5 6 7 8 9 10
| A1 Injection 0 21 0 1 22 1.2 A2 Broken Authentication and Session Management 0 1 0 0 1 0.2 A3 Cross-Site Scripting (XSS) 0 8 0 0 8 0.2 A4 Insecure Direct Object References 0 40 0 12 52 3.2 A5 Security Misconfiguration 1 0 3 0 4 0.5 A6 Sensitive Data Exposure 3 19 0 0 22 1.8 A7 Missing Function Level Access Control 0 0 0 0 0 0.0 A8 Cross-Site Request Forgery (CSRF) 0 0 0 0 0 0.0 A9 Using Components with Known Vulnerabilities 0 0 0 0 0 0.0 A10 Unvalidated Redirects and Forwards 0 0 0 0 0 0.0
|
找出文字的固定規律
A3 的標題帶有特殊符號,為相對複雜的情況,故以 A3 為例。
這裡先訂出需要用到的幾個欄位名稱,並針對每個欄位會出現的內容做分析,決定出每個欄位要使用的正規表達式 pattern。
- IssueName: A3 Cross-Site Scripting (XSS)
字串開頭固定為 A流水號,內容可能會有多組英、數組成的單字,每個單字間用空白分隔,且可能會有 -, (, ) 等符號
給正規表達式用的 Pattern: \bA\d+\b [a-zA-Z0-9 \-\(\)]+
- Critical: 0
正整數
給正規表達式用的 Pattern: \b\d+\b
- High: 8
正整數
給正規表達式用的 Pattern: \b\d+\b
- Medium: 0
正整數
給正規表達式用的 Pattern: \b\d+\b
- Low: 0
正整數
給正規表達式用的 Pattern: \b\d+\b
- TotalIssues: 8
正整數
給正規表達式用的 Pattern: \b\d+\b
- Effort: 0.2
浮點數,且即使為 0 也會顯示為 0.0
給正規表達式用的 Pattern: \b\d+\.\d+\b
對正規表達式 pattern 特殊字元不熟的人可以參考本文結尾的參考資料
建立用來存放解析結果的資料類別
1 2 3 4 5 6 7 8 9 10 11
| class DataColumn { public string IssueName { get; set; } public string Critical { get; set; } public string High { get; set; } public string Medium { get; set; } public string Low { get; set; } public string TotalIssues { get; set; } public string Effort { get; set; } }
|
進行解析並存入結果資料集合物件
用前面準備好的 Pattern 加上正規表達式 群組命名 的寫法,以便後面將取出的資料以 群組名稱 的方式對應到資料物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| using IFilterTextReader;
List<DataColumn> dataList = new List<DataColumn>();
string filePath = @"X:\SomeWhere\Fortify.pdf"; string source = new IFilterTextReader.FilterReader(filePath).ReadToEnd();
string pattern = @"(?'IssueName'\bA\d+\b [a-zA-Z0-9 \-\(\)]+) (?'Critical'\b\d+\b) (?'High'\b\d+\b) (?'Medium'\b\d+\b) (?'Low'\b\d+\b) (?'TotalIssues'\b\d+\b) (?'Effort'\b\d+\.\d+\b)";
MatchCollection matches = Regex.Matches(source, pattern); if (matches.Count > 0) { foreach (Match match in matches) { dataList.Add( new DataColumn { IssueName = match.Groups["IssueName"].ToString(), Critical = match.Groups["Critical"].ToString(), High = match.Groups["High"].ToString(), Medium = match.Groups["Medium"].ToString(), Low = match.Groups["Low"].ToString(), TotalIssues = match.Groups["TotalIssues"].ToString(), Effort = match.Groups["Effort"].ToString(), } ); }
dataList.Dump(); } Console.WriteLine("執行結束");
|
用 LINQPad 檢視解析結果資料集合
感謝正規表達式讓我們快速完成需求,收工!
參考資料