1. 程式人生 > >C#解析PDF:客戶化iTextSharp

C#解析PDF:客戶化iTextSharp

PDF相關測試一直都是手動進行,自動化測試介入的很少。我們專案,PDF是很重要的一塊,客戶經常需要將報表匯出到PDF。匯出的可能是表格,也可能是餅圖,條圖,線圖。表格的話,有flat grid,有tree grid。圖的話,花樣就更多了,圖例,座標軸…

最近的迭代有個story,是想要自動化測試能夠判斷表格匯出到PDF格式是否正確,看看現在專案用的iTextSharp能否實現這個需求。

iTextSharp是C#的PDF庫,人們經常用它來生成PDF,這個庫裡面有一小塊是解析PDF,這一小塊就是我需要用到的。一番探索之後發現,其實PDF沒有任何結構資訊,純粹就是畫圖一般畫出來的。我們專案的PDF,就是先獲取資料,然後根據資料節點,層級結構,生成相應的html檔案,最後從html生成PDF。PDF裡面的表格看起來是有結構的,就是一張表,實際上解析之後讀出來的資訊毫無結構可言。現有的庫最多可以一行一行把PDF裡面的文字讀出來,離我的預期還很遠。

比如我有如下兩個PDF報表:
這裡寫圖片描述

用現有的iTextSharp讀出來結果如下:
Tree Grid
Fund Asset Class NPV VaR
Fund_A 1234 (2345)
Fixed Income 1234 (2345)
Fund_B 3456 (1234)
Equity 3456 (1234)

Flat Grid
Fund Asset Class NPV VaR
Fund_A 1234 (2345)
Fund_A Fixed Income 1234 (2345)
Fund_B 3456 (1234)
Fund_B Equity 3456 (1234)

每一行的文字,它預設用空格分隔,這樣的話我無從判斷這一行到底匯出來幾個單元格,因為單元格自己的內容就可能有空格。

一開始我認為這個應該有個string引數,使用者可以設定,然後解析PDF的時候用這個string作為分隔。google了很久都沒有找到,去stackoverflow上提問了,也沒有人回答。

還好這個工具是開源的,我在github上找到了原始碼:github iTextSharp

原來根本沒有這麼個引數,程式寫死了用空格分隔,詳見SimpleTextExtractionStrategy.cs 的line 145和LocationTextExtractionStrategy.cs的line 193

接下來,把原始碼下下來,在類SimpleTextExtractionStrategy和LocationTextExtractionStrategy裡面新增屬性欄位:string separator,賦預設值為空格符,使用者可以在新建類例項時傳入任何他想要的string來為它賦值。讀PDF內容的方法增加一個引數,就是類的separator欄位。

比如我在新建SimpleTextExtractionStrategy類例項時,傳入引數將separator設定為++,那麼最後我解析出來的PDF內容如下:

Tree Grid
Fund++Asset Class++NPV++VaR
Fund_A++1234++(2345)
Fixed Income++1234++(2345)
Fund_B++3456++(1234)
Equity++3456++(1234)

Flat Grid
Fund++Asset Class++NPV++VaR
Fund_A++1234++(2345)
Fund_A++Fixed Income++1234++(2345)
Fund_B++3456++(1234)
Fund_B++Equity++3456++(1234)

這樣一來,我就可以以++為splitter來split每一行內容,繼而判斷PDF格式是否正確。

客戶化開源工具確實能解決自己的問題,但是,後續如果該工具更新了,那麼merge起來也是蠻困難的…只能後續不用這個工具的新版本了…

後話:問題解決了,我去stackoverflow上回答了自己的提問,社群管理員看到之後,也覺得這個不應該寫死,應該expose出來,讓使用者自己設定值,他@了iTextSharp工具的作者。但是不知道作者是否會採納我的建議,並在下一次更新時涵蓋我的改動

相關推薦

C#解析PDF客戶iTextSharp

PDF相關測試一直都是手動進行,自動化測試介入的很少。我們專案,PDF是很重要的一塊,客戶經常需要將報表匯出到PDF。匯出的可能是表格,也可能是餅圖,條圖,線圖。表格的話,有flat grid,有tree grid。圖的話,花樣就更多了,圖例,座標軸… 最近的

C++解析(12)初始列表與物件構造順序

0.目錄 1.類成員的初始化 2.類中的const成員 3.物件的構造順序 3.1 區域性物件的構造順序 3.2 堆物件的構造順序 3.3 全域性物件的構造順序 4.小結 1.類成員的初始化 類中是否可以定義const成員? 下面的類定義是否合法?如果合法,ci的值是什麼,儲

C++解析(1)CC++的升級

0.目錄 1.C與C++的關係 2.C到C++的升級 2.1 語言的實用性 2.2 register關鍵字 2.3 同名的全域性變數 2.4 struct關鍵字 2.5 int f() 與 int f(void) 有區別嗎? 3.小結 1.C與C++的關係 C++繼

C++解析(2)進化後的 const 分析

0.目錄 1.C語言中的const 2.C++中的const 3.對比 3.1 C語言與C++中的const 3.2 C++中的const與巨集定義 4.小結 1.C語言中的const const修飾的變數是只讀的,本質還是變數 const修飾的區域性變數在棧上分配空間

C++解析(6)函式引數的擴充套件

0.目錄 1.函式引數的預設值 2.函式預設引數的規則 3.函式佔位引數 4.小結 1.函式引數的預設值 C++可以在函式宣告時為引數提供一個預設值 當函式呼叫時沒有提供引數的值,則使用預設值 引數的預設值必須在函式宣告中指定: 執行以下程式: #include <

C++解析(8)C++中的新成員

0.目錄 1.動態記憶體分配 1.1 C++中的動態記憶體分配 1.2 new關鍵字與malloc函式的區別 1.3 new關鍵字的初始化 2.名稱空間 21 作用域與名稱空間 2.2 名稱空間的定義和使用 3.強制型別轉換 3.1 C方式的強制型別轉換

C++解析(9)關於const和引用的疑問

0.目錄 1.關於const的疑問 2.關於引用的疑問 2.1 引用與指標 2.2 從C++語言與C++編譯器角度看引用 2.3 從工程專案開發看引用 3.小結 1.關於const的疑問 const什麼時候為只讀變數?什麼時候是常量? const常量的判別準則: 只有用

C++解析(15)二階構造模式

0.目錄 1.建構函式與半成品物件 2.二階構造 3.小結 1.建構函式與半成品物件 關於建構函式: 類的建構函式用於物件的初始化 建構函式與類同名並且沒有返回值 建構函式在物件定義時自動被呼叫 問題: 如何判斷建構函式的執行結果? 在建構函式中執行return語句

C++解析(20)智慧指標與型別轉換函式

0.目錄 1.智慧指標 2.轉換建構函式 3.型別轉換函式 4.小結 1.智慧指標 記憶體洩漏(臭名昭著的Bug): 動態申請堆空間,用完後不歸還 C++語言中沒有垃圾回收機制 指標無法控制所指堆空間的生命週期 我們需要什麼: 需要一個特殊的指標 指標生命週期結束

C++解析(21)四個操作符

0.目錄 1.邏輯操作符的陷阱 2.逗號操作符的分析 3.前置操作符和後置操作符 4.小結 1.邏輯操作符的陷阱 邏輯運算子的原生語義: 運算元只有兩種值(true和false) 邏輯表示式不用完全計算就能確定最終值 最終結果只能是true或者false 示例——短路法則: #include

C++解析(20)智能指針與類型轉換函數

font 類類型 有一個 安全 ont 運行 root 工作 sign 0.目錄 1.智能指針 2.轉換構造函數 3.類型轉換函數 4.小結 1.智能指針 內存泄漏(臭名昭著的Bug): 動態申請堆空間,用完後不歸還 C++語言中沒有垃圾回收機制 指針無法控制所指堆空間的

C++解析(22)父子間的衝突

0.目錄 1.同名覆蓋 2.賦值相容 3.函式重寫遇上賦值相容 4.小結 1.同名覆蓋 子類中是否可以定義父類中的同名成員?如果可以,如何區分?如果不可以,為什麼? 父子間的衝突: 子類可以定義父類中的同名成員 子類中的成員將隱藏父類中的同名成員 父類中的同名成員依然存在於子類中

C++解析(22)父子間的沖突

return 父類引用 本質 ostream spa 進行 mes 編譯期 ++ 0.目錄 1.同名覆蓋 2.賦值兼容 3.函數重寫遇上賦值兼容 4.小結 1.同名覆蓋 子類中是否可以定義父類中的同名成員?如果可以,如何區分?如果不可以,為什麽? 父子間的沖突: 子類可以

C++解析(23)多型與C++物件模型

0.目錄 1.多型 2.C++物件模型 3.繼承物件模型 4.多型物件模型 5.小結 1.多型 面向物件中期望的行為: 根據實際的物件型別判斷如何呼叫重寫函式 父類指標(引用)指向 父類物件則呼叫父類中定義的函式 子類物件則呼叫子類中定義的重寫函式

C++解析(24)抽象類和介面、多重繼承

0.目錄 1.抽象類和介面 1.1 抽象類 1.2 純虛擬函式 1.3 介面 2.被遺棄的多重繼承 2.1 C++中的多重繼承 2.2 多重繼承的問題一 2.3 多重繼承的問題二 2.4 多重繼承的問題三 2.5 正確的使用多重繼承 3.小結 1.抽象類

C++解析(25)關於動態內存分配、虛函數和繼承中強制類型轉換的疑問

cas ror src 一個 聲明 eof struct 定義 namespace 0.目錄 1.動態內存分配 1.1 new和malloc的區別 1.2 delete和free的區別 2.虛函數 2.1 構造函數與析構函數是否可以成為虛函數? 2.2 構造函數與析構

C++解析(26)函式模板與類模板

0.目錄 1.函式模板 1.1 函式模板與泛型程式設計 1.2 多引數函式模板 1.3 函式過載遇上函式模板 2.類模板 2.1 類模板 2.2 多引數類模板與特化 2.3 特化的深度分析 3.小結 1.函式模板 1.1 函式模板與泛型程式設計 C++中有幾

C++解析(26)函數模板與類模板

null 並且 http 外部 asi 分類 工程 顯示調用 完全 0.目錄 1.函數模板 1.1 函數模板與泛型編程 1.2 多參數函數模板 1.3 函數重載遇上函數模板 2.類模板 2.1 類模板 2.2 多參數類模板與特化 2.3 特化的深度分析 3.小結 1

C++解析(29)型別識別

0.目錄 1.型別識別 2.動態型別識別 3.型別識別關鍵字 4.小結 1.型別識別 在面向物件中可能出現下面的情況: 基類指標指向子類物件 基類引用成為子類物件的別名 靜態型別——變數(物件)自身的型別 動態型別——指標(引用)所指向物件的實際型別 2.

C#基礎系列序列效率比拼

      前言:作為開發人員,物件的序列化恐怕難以避免。樓主也是很早以前就接觸過序列化,可是理解都不太深刻,對於用哪種方式去做序列化更是隨波逐流——專案中原來用的什麼方式照著用就好了。可是這麼多年自己對於這東西還是挺模糊的,今天正好有時間,就將原來用過的幾種方式總結了下,也算是做一個記錄,順便做了下效能測