---
title: "PDF表單分析"
description: "EMILY.RPA PDF 表單分析模組：分析 PDF 檔案並擷取文字資訊，建議使用符合 PDF/A 標準的檔案。"
source: https://docs.emily.tips/wap/pdf2txt
---

# PDF表單分析

分析指定 PDF 格式檔案並擷取文字資訊。請注意指定的 PDF 檔案應符合 [PDF/A 標準](https://zh.wikipedia.org/zh-tw/PDF/A)。

模組上方可選擇「**VIEW**」「**TRAIN**」兩種模式。在「**TRAIN**」模式中可以進行程式碼編寫，在「**VIEW**」模式中則可對該文件進行預覽，文件換頁則可透過左邊下拉頁碼選單操作。

![](https://docs.emily.tips/img/zh/wap-121.png)

![](https://docs.emily.tips/img/zh/wap-122.png)

## 參數

**`PDF`** - PDF 格式輸入檔，支援ISO標準的PDF/A格式，點擊「**PICK**」選取檔案、鍵盤輸入工作資料夾中的 PDF 檔名，或使用 **%FILENAME%** 變數。

**`PASSWORD`** - PDF 格式輸入檔密碼，可使用 **%FILENAME%** 變數。

### 座標系統

模組中使用 Page Normalized Coordinate(PNC) 座標系統。原點位於文件第一頁的左上角 (0,0)，每一頁的座標範圍正規化至相鄰的整數。座標以 (x,y) 表示，其中 x 或 y 都可以帶小數，第一頁的座標範圍是 (0,0) 至 (1,1)，第二頁則是 (0,1) 至 (1,2) 依此類推，例如 (0.5,1.5) 代表座標在第二頁的正中間。

### 文字物件

模組解析指定檔案後會產出文件中所有的文字物件，物件中除了文字內容外，也會包含文字的座標。

![](https://docs.emily.tips/img/zh/wap-123.png)

## No-Code 輔助編輯器 (VIEW)

「**VIEW**」輔助編輯器可以在文件預覽畫面上以滑鼠鍵盤的組合操作來產生 Low-Code 需要的程式碼。在輔助編輯器中將滑鼠移到頁面文字上可以標示文字物件與座標，另外也可以透過滑鼠與鍵盤的組合操作來自動產生程式碼，使用者在完成操作後產生的程式碼會被存在剪貼簿中，可以切換到 Low-Code 編輯器中直接貼上，或進一步修改。

### 抓取文字物件

![滑鼠點擊文字物件](https://docs.emily.tips/img/zh/wap-124.png)

### 產生邊界

![滑鼠於想選取的區域拖拉出選取框](https://docs.emily.tips/img/zh/wap-125.png)

### 方向解析

![游標置於鍵值物件上，按下Shift+方向鍵，即可解析該方向第一個遇到的物件](https://docs.emily.tips/img/zh/wap-126.png)

### 範圍解析

![游標置於起始鍵值物件上按下Shift，游標移至結束鍵值物件上點擊，即可選取兩者間的物件](https://docs.emily.tips/img/zh/wap-127.png)

### 相對區域解析

![游標至於起始物件上按下Shift，游標移至想解析之區域拖拉出選取框](https://docs.emily.tips/img/zh/wap-128.png)

## Low-Code 編輯器 (TRAIN)

### input 輸入物件

輸入物件是將PDF檔案轉換後的資料物件，其中包含了統一坐標系後的所有文字物件，線條物件...，以及解析所需的函式。

```javascript
Doc{
    totalPages,    // 文件的總頁數
    pageInfo[],    // 頁面資訊，如頁面的畫素寬度/高度
    textData[],    //文字物件陣列
    hLines[],      // 橫線陣列
    vLines[],      // 縱線陣列
    blocks[],      // 方格(由縱橫線組成)陣列
    tables[],      // 表格陣列(由方格組成)陣列
}
```

文字物件是解析文件所使用的最小單位物件，每個文字物件都帶有以下的屬性:

```javascript
textObject{
    text,            // 文字物件的內文
    LT{},            // 文字物件左上角座標
    LB{},            // 文字物件左下角座標
    RT{},            // 文字物件右上角座標 
    RB{},            // 文字物件右下角座標
    C{},             // 文字物件中央點座標
    w,               // 文字物件寬度
    h,               // 文字物件高度
    block{},         // 紀錄該文字物件與文件表格的關係
    isDeleted(),     // 檢測文字物件是否被橫線劃過
    detectLine(dir), // 檢測並回傳最近的線條, dir可為"up","down","left","right"
    detectGroup(),   // 偵測並回傳鄰近線條相同的所有的文字物件,可用於偵測同一格內的所有文字物件。    
}
```

### 解析函式

![PDF文件的解析邏輯](https://docs.emily.tips/img/zh/wap-129.png)

輸入物件提供了數個解析函式與工具函式能幫助使用者在空間中找出目標物件。整體的解析邏輯在於使用空間(spatial)或字詞(textual)上的條件縮小文字物件的集合，接著用相對關係找到目標物件。

```javascript
textObj = input.getKeyObj( keyName, keyBounds, options )

// keyName: 文字物件的內文字串
// keyBounds: 找尋範圍的物件，格式為 {"top":num,"bottom":num,"left":num,"right":num,“page”:num}
// options: 選項物件，可提供下列選項
// “regExp”: 使用正規表達式過濾搜尋範圍
// “ignoreHeader”: 忽略每一頁上方範圍的資料，範圍由小數定義(頁高百分比)
// “ignoreFooter”: 忽略每一頁下方範圍的資料，範圍由小數定義(頁高百分比)
// “all”: 布林值，若開啟則函式回傳所有找到的文字物件(陣列形式)
```

```javascript
textObj = input.resolve( target, options )

// target: 目標物件，包含鍵值物件(keyObj)或是(keyName/keyBounds)的組合，解析方向(valPos)，解析範圍(valBounds)或是相對解析範圍(relValBounds)
// options: 選項物件，可提供下列選項
// “regExp”: 使用正規表達式過濾搜尋範圍
// “ignoreHeader”: 忽略每一頁上方範圍的資料，範圍由小數定義(頁高百分比)
// “ignoreFooter”: 忽略每一頁下方範圍的資料，範圍由小數定義(頁高百分比)
```

```javascript
textObjs[] = input.resolveRange( target, options )
    
// target: 目標物件，包含起始鍵值物件(startKeyObj)或是(startKeyName/startKeyBounds)的組合，結尾鍵值物件(endKeyObj)或是(endKeyName/endKeyBounds)的組合，解析方向(valPos)，解析範圍(valBounds)或是相對解析範圍(relValBounds)\
// options: 選項物件，可提供下列選項
// “regExp”: 使用正規表達式過濾搜尋範圍
// “ignoreHeader”: 忽略每一頁上方範圍的資料，範圍由小數定義(頁高百分比)
// “ignoreFooter”: 忽略每一頁下方範圍的資料，範圍由小數定義(頁高百分比)
```

```javascript
[實驗性功能] textObjs[] = input.resolveTable( target, options )
	
// target: 目標物件，包含鍵值物件(keyObj)或是(keyName/keyBounds)的組合，解析方向(valPos)，解析範圍(valBounds)或是相對解析範圍(relValBounds)
// valType: 解析模式，可以為 ROW，ROW(Num)，COL，COL(Num)，REPEAT。
// ROW/COL模式會解析整行/整列的資料
// ROW(Num)/COL(Num)模式會解析鍵值右方/下方的Num個值資料。
// REPEAT模式會解析所有相似表格中的位於鍵值位置資料。
// options: 選項物件，可提供下列選項
// “regExp”: 使用正規表達式過濾搜尋範圍
// “includeKey”: 布林值，如果為真，解行列解析的回傳值會包含target中的鍵值。
// “tableRefs”: 一個物件陣列，格式如下{“refText”:string, “R”:num, “C”:num}。此物件陣列用以定義"相似表格"需要具有那些相同內容。 
```

### 工具函式

```javascript
strings[] = input.textGrouping( textObjs , option)
    
// textObjs: 需要被重整的文字物件陣列
// Option: 排列方式選項，可以是 LINES, TDLR, LRTD(default)
// Note: 排序邏輯會將行高低於1.5倍/間距小於2個字寬的文字物件群組。接著將每個群組使用TDLR，LRTD的順序排序。並依序輸出每個群組的字串
// Note2: LINES模式則會以高度位於同一行高的方式分群，並以逐行的方式回傳字串。
```

```javascript
textObjs[] = input.textSort( textObjs, mode = 0)

// textObjs: 需要被重整的文字物件陣列
// Mode: 排序模式，目前只有左至右，上至下的物件排序。
// Note:回傳新的排序後陣列。
```

```javascript
num = input.checkBounds( keyBounds, textObj )
// (0 為無重疊, 1 為部分重疊, 2 為物件包含於範圍內)

// keyBounds: 欲測試的範圍。格式如{top:num, bottom:num, left:num, right:num}
// textObj: 欲測試的文字物件
```

### output 輸出物件

加入 output 物件中的每個 key 將被輸出成工作資料夾中的一個 TXT 檔案，檔案名稱即為 key，文字內容為該 key 相對應的 value。

## 範例

```javascript
// 1.Resolve single value by Name:
let target = {keyName:"PRODUCTION ORDER", keyBounds = "page":1, valPos:"RIGHT" } 
let productOrderNum = input.resolve( target );
// result: productOrderNum.text = “423022”

// 2.Resolve single value by object:
let orderObj = getKeyObj("PRODUCTION ORDER", {page:1}) 
let target = {keyObj: orderObj, valPos:"RIGHT"}
let productOrderNum = input.resolve( target );
// result: productOrderNum.text = “423022”

// 3.Resolve single value with regExp Filter:
let target = { keyName:"Coord #", keyBounds = {page:1, valPos:"RIGHT"}
let options = {“regExp”:/\d{2}\/\d{2}\/\d{2}/ } 
let issueDate = input.resolve( target, options );
// result: issueDate .text = “06/24/20”

// 4.Resolve multiples values by start/endKeyObjects:
let startObj = input.getKeyObj( "Ship Type", {page:1} )
let endObj = input.getKeyObj( "Ship Terms", {page:1} )
let target = {startKeyObj:startObj, endKeyObj:endObj, valPos:"RIGHT"}
let interData = input.resolveRange(target)
//Result:  interData[0].text = “AW”
//Result:  interData[1].text = “Ship Via"
//Result:  interData[2].text = “SEA”

// 5.Resolve multiples values by valBounds:
let sellerObj = getKeyObj("To (Seller):", {page:1})
let codeObj = getKeyObj({"Code:", {page:1})
let accObj = getKeyObj("Acc Sup:", {page:1})
let target = {valBounds:{top:sellerObj.LB.y, right:accObj.LB.x, bottom:codeObj.LT.y, page:1 } }
let address = input.resolveRange(target)
//Result:  address[0].text = “TAIEASY INTERNATIONAL. CO., LTD”
//Result:  address[1].text = “11F., NO. 1, JIHU RD., NEIHU DIST.,"
//Result:  address[2].text = “TAIPEI CITY 114,”
//Result:  address[3].text = “TAIWAN (R.O.C.)”
//Result:  address[4].text = “TAIPEI CITY, TAIWAN”

// 6.Resolve multiples values by relValBounds
let facObj = getKeyObj("Mfrag Fac:", {page:1})
let target = {relValBounds:{top: -0.01, right:0.25, bottom:0.09, left:0.0} }
let location= input.resolveRange(target)
//Result:  location[0].text = “FORMOSA VIET NAM TEXTILE INDUSTRY”
//Result:  location[1].text = “CO.,LTD"
//Result:  location[2].text = “MY XUAN A2 INDUSTRIAL ZONE, MY XUAN”
//Result:  location[3].text = “WARD,”
//Result:  location[4].text = “PHU MY TOWN, BA RIA - VUNG YAU”

// 7.Resolve value with ignore options
let buyerObj = input.getKeyObj("By Buyer",{page:1})
let target = {keyObj:buyerObj, valPos:"DOWN"}
let options = {ignoreHeader:0.22, ignoreFooter:0.85}
let downVal= input.resolveRange(target, options)
// downVal.text = ”To (Seller)”

// 8.Resolve block values in Table.
let target = {keyName:"DESC", keyBounds:{page:1}, valPos:"DOWN"}
let blockVal= input.resolve(target)
// blockVal.text = “TRAINER PANT”
let allBlockVal = input.resolveTable({keyObj:blockVal})
// allBlockVal.text = “TRAINER PANT
//                               YOUTH”

// 9.Resolve row blocks values in Table.
let target = {keyName:"Ship Via", keyBounds:{page:1}, valType:"ROW"}
let blockVals = input.resolveTable(target)
// blockVals[0].text = “Ship Type”
// blockVals[1].text = “AW”
// blockVals[2].text = “Ship Via”
// blockVals[3].text = “SEA”
// blockVals[4].text = “Ship Terms”
// blockVals[5].text = “FOB”
target.valType = "ROW"
blockVals = input.resolveTable(target);
// blockVals[0].text = “SEA”
// blockVals[1].text = “Ship Terms”
// blockVals[2].text = “FOB”
target.valType = "ROW2"
blockVals = input.resolveTable(target);
// blockVals[0].text = “SEA”
// blockVals[1].text = “Ship Terms”

// 10.Resolve Column/Row blocks values in Table.
let target = {keyName:"Coord #", keyBounds:{page:1}, valType:"COL5"}
let options = {includeKey:true}
// get 6 headers in first column
let colVals = input.resolveTable(target, options)
// get row values for each header
colVals.forEach( head => {
	let rowVals = input.resolveTable({keyObj:head, valType:"ROW0"})
})
// for “Coord #” row
// 	rowVals[0].text = “TRAINING” 
// 	rowVals[1].text = “PO Issue Date” 
// 	rowVals[2].text = “06/24/20” 
// for “Season” row
// 	rowVals[0].text = “204HO” 
// 	rowVals[1].text = “Last Revised Date” 
// 	rowVals[2].text = “06/24/20” 
// for “Payment Terms” row
// 	rowVals[0].text = “75 DAY TERMS THROUGH GT NEXUS” 
// for “Ship Type” row
// 	rowVals[0].text = “AW” 
// 	rowVals[1].text = “Ship Via” 
// 	rowVals[2].text = “SEA” 
// 	rowVals[3].text = “Ship Terms” 
// 	rowVals[4].text = “FOB” 
// for “Ref#” row
// 	rowVals[0].text = “204HO NTM056 YRR PNP A” 
// for “Bene” rows
// 	rowVals[0].text = “NTM064 TAIEASY INTERNATIONAL CO.,LTD” 

// 11.Resolve repeat blocks values in Table.
// find first origin
let target = {keyName:"Country of Origin", keyBounds:{“page":1}, “valPos”:”RIGHT”}
let originVal = input.resolve(target)
//  originVal.text = “Vietnam”
// Resolve origins in other tables
let options = { tableRefs: [ 
                {refText:"Item:", R:0, C:0},
                {refText:"Country of Origin", R:1, C:0}
                ]}
let blockVals = this.resolveTable({keyObj:originVal, valType:"RIGHT"})
// blockVals[0].text = “VietNam”
// blockVals[1].text = “Taiwan”
// blockVals[2].text = “China”
```
