前言
臨時(shí)接到一個(gè)緊急需要處理的事項(xiàng)。業(yè)務(wù)側(cè)一個(gè)同事有幾千個(gè)PDF文件需要整理:需要從文件中的指定位置獲取對(duì)應(yīng)的編號(hào)和地址。
要的急,工作量大。所以就問到技術(shù)部有沒有好的解決方案。
問技術(shù)的話就只能寫個(gè)demo跑下了。
解決辦法
1. 研究下PDF文檔,找出解決方案
PDF的文檔看起來比較簡(jiǎn)單,因?yàn)橹皇切枰x取兩個(gè)坐標(biāo)位置的文本內(nèi)容,而且位置相對(duì)固定。所以就直接用java的第三方庫(kù)pdfbox來操作PDF文檔。
2. 找個(gè)能操作PDF的第三方庫(kù)pdfbox。
- 先下載pdfbox的jar包。
官網(wǎng)介紹 - pdfbox能干啥:
-
pdfbox是Apache軟件基金會(huì)的一個(gè)開源項(xiàng)目,它提供API和工具來處理PDF文檔。
-
pdfbox是Apache PDFBox的Java版本,它提供了一個(gè)類庫(kù),用于讀取,寫入,轉(zhuǎn)換和創(chuàng)建PDF文檔。
-
pdfbox支持處理各種PDF特性,如文本,字體,圖像,表單字段,注釋,書簽,頁(yè)面布局等。
-
pdfbox還提供了對(duì)加密和數(shù)字簽名PDF文檔的支持,以及對(duì)PDF文檔的提取和合并。
-
pdfbox還提供了對(duì)PDF文檔的驗(yàn)證,簽名驗(yàn)證,加密驗(yàn)證和數(shù)字簽名的支持。
-
PDFBox是一個(gè)用于處理PDF文檔的Java庫(kù)。它提供了一組功能強(qiáng)大的API,可以用于創(chuàng)建、修改和提取PDF文檔的內(nèi)容。PDFBox可以用于各種用途,包括生成PDF文檔、提取文本和圖像、合并和拆分PDF文件、添加水印和書簽等。
-
PDFBox支持處理各種PDF特性,如文本、字體、圖像、表單字段、注釋、書簽、頁(yè)面布局等。它還提供了對(duì)加密和數(shù)字簽名PDF文檔的支持,以及對(duì)PDF文檔的高級(jí)操作,如提取文本位置信息、提取圖像和字體等。
-
3. maven加載包
pdfbox有三個(gè)大的版本,每個(gè)版本差異較大,這個(gè)時(shí)候如果要引入的時(shí)候,要注意對(duì)應(yīng)的版本了,否則demo就有可能跑不起來。
pdfbox最新的大版本是3.0。作為新時(shí)代的青年,肯定要與時(shí)俱進(jìn)。3.0肯定是要用上的。
3. 先驗(yàn)證下第三方庫(kù)是否可行
下載jar包后,直接用java代碼跑下demo。 demo讀取pdf文檔內(nèi)容并輸出文本數(shù)據(jù)到控制臺(tái)
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.File;
import java.io.IOException;
public class PDFBoxDemo {
public static void main(String[] args) throws IOException {
PDDocument document = PDDocument.load(new File("D:\\pdf\\test.pdf"));
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
System.out.println(text);
document.close();
}
}
發(fā)現(xiàn)demo跑起來后,報(bào)錯(cuò)。
原因是因?yàn)閐emo是2.0的版本,而當(dāng)前的jar包是3.0的版本。PDDocument.load這個(gè)修改為L(zhǎng)oader.load就OK了。
接下來,就是如何獲取到指定坐標(biāo)位置的文本內(nèi)容。
4. 確認(rèn)文本在PDF文檔中的坐標(biāo)位置。
確認(rèn)PDF文本坐標(biāo)一般有兩種方案。
1. 代碼校驗(yàn)(最精準(zhǔn))
先用demo跑下,看下是否可以讀取到指定坐標(biāo)位置的文本內(nèi)容。
/**
* 獲取文檔坐標(biāo)
* @param file PDF文件對(duì)象
* @param sourceTex 匹配的字符
* @return 坐標(biāo)
*/
public static Point getPoint(File file,String sourceTex) {
Point point = new Point();
//獲取文檔坐標(biāo)
try {
PDDocument document = Loader.loadPDF(file);
PDFTextStripper textStripper = new PDFTextStripper() {
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
if (text.contains(targetText)) {
TextPosition textPositionStart = textPositions.get(0);
TextPosition textPositionEnd = textPositions.get(textPositions.size()-1);
point.setX(textPositionStart.getX());
point.setY(textPositionStart.getY());
}
}
};
textStripper.setSortByPosition(true);
textStripper.setStartPage(1);
textStripper.setEndPage(document.getNumberOfPages());
textStripper.getText(document);
document.close();
} catch (IOException e) {
e.printStackTrace();
}
return point;
}
跑完demo后,發(fā)現(xiàn)可以讀取到指定坐標(biāo)位置的文本內(nèi)容。
這里會(huì)有個(gè)小問題,就是返回的坐標(biāo)點(diǎn)有的會(huì)有小數(shù)。因?yàn)楫?dāng)前返回類型float,所以需要轉(zhuǎn)換成int。
2. 最直接粗暴的方法。
1. 福昕PDF文檔工具。
2. 直接用福昕PDF文檔定位工具定位坐標(biāo)。
說實(shí)話,開發(fā)比較少用這種方式,因?yàn)楦杏X有點(diǎn)lower(其實(shí)是自己不太會(huì)用)
5. 整個(gè)demo先驗(yàn)證第三方庫(kù)是否可行。
拿1個(gè)文件試試水
public static void main(String[] args) {
String filePath = "D:\\test\\test.pdf";
try {
PDDocument document = Loader.loadPDF(file);
PDFTextStripperByArea textStripper = new PDFTextStripperByArea ();
Rectangle rectangle = new Rectangle(80,120, 250,10);
String regionName = "regionName";
textStripper.addRegion(regionName, rectangle);
PDPage page = document.getPage(0);
textStripper.extractRegions(page);
String text = textStripper.getTextForRegion(regionName);
System.out.println(text);
textStripper.setSortByPosition(true);
textStripper.setStartPage(1);
textStripper.setEndPage(document.getNumberOfPages());
textStripper.getText(document);
document.close();
}catch (IOException e) {
e.printStackTrace();
}
}
結(jié)果能夠正常輸出對(duì)應(yīng)的文本內(nèi)容。
6. 整活上代碼。
奉上全部demo代碼文章來源:http://www.zghlxwxcb.cn/news/detail-641369.html
package com.example.demo;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson2.JSON;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.PDFTextStripperByArea;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Desc: 驗(yàn)證pdfbox的可行性
*
* @author admin
* @date since 2023/8/8 18:44
*/
public class PdfDemo {
//要匹配的位置內(nèi)容點(diǎn)
private static final String[] target= {"name", "address"};
public static void main(String[] args) {
ExcelWriter excelWriter= ExcelUtil.getWriter("D:\\test\\pdf\\test.xls");
String folderPath = "D:\\test\\pdf";
File folder = new File(folderPath);
if (folder.exists() && folder.isDirectory()) {
List<Map<String,Object>> mps = listPdfFiles(folder);
excelWriter.write(mps, true);
} else {
System.out.println("Invalid folder path.");
}
excelWriter.close();
}
/**
* 獲取pdf文件列表
*
* @param folder 文件夾
* @return {@code List<Map<String,Object>>}
*/
private static List<Map<String,Object>> listPdfFiles(File folder) {
List<Map<String,Object>> mps = new ArrayList<>();
File[] files = folder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
listPdfFiles(file); // 遞歸調(diào)用,處理子文件夾
} else {
String fileName = file.getName();
if (fileName.toLowerCase().endsWith(".pdf")) {
mps.add(getLineData(file));
}
}
}
}
return mps;
}
/**
* 行數(shù)據(jù)
*
* @param file 文件
* @return {@code Map<String,Object>}
*/
public static Map<String,Object> getLineData(File file){
Map<String,Object> lineData = new HashMap<>(target.length+2);
List<Point> pointList = getPoint(file);
String[] arr= getPointValue(file, pointList.stream().map(s -> new Rectangle(s.getX(), s.getY(), 260, 10)).toArray(Rectangle[]::new));
if(arr.length>=target.length) {
for(int i=0;i<target.length;i++)
{
lineData.put(target[i], arr[i]);
}
lineData.put("fileName", file.getName().toLowerCase().replace(".pdf", ""));
}
return lineData;
}
/**
* 獲得PDF指定坐標(biāo)點(diǎn)文本值
*
* @param file 文件
* @param rectangles 矩形坐標(biāo)
* @return {@code String[]}
*/
public static String[] getPointValue( File file,Rectangle... rectangles){
String[] textArr = new String[rectangles.length];
// String text="";
try {
PDDocument document = Loader.loadPDF(file);
PDFTextStripperByArea textStripper = new PDFTextStripperByArea ();
for(int i = 0; i < rectangles.length;i++ ) {
Rectangle rectangle =rectangles[i];
String regionName = "regionName"+rectangle.getX()+rectangle.getY();
textStripper.addRegion(regionName, rectangle);
PDPage page = document.getPage(0);
textStripper.extractRegions(page);
// 獲取區(qū)域的text
String text = textStripper.getTextForRegion(regionName);
text = text.replace("\u0000","-").replace(" ","");
System.out.println(">>text"+text);
textArr[i]=text;
}
textStripper.setSortByPosition(true);
textStripper.setStartPage(1);
textStripper.setEndPage(document.getNumberOfPages());
textStripper.getText(document);
document.close();
}catch (IOException e) {
e.printStackTrace();
}
return textArr;
}
public static List<Point> getPoint( File file){
List<Point> pointList=new ArrayList<>();
try {
PDDocument document = Loader.loadPDF(file);
PDFTextStripper textStripper = new PDFTextStripper() {
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
for(String target:target){
if (text.contains(target)) {
Point point = new Point();
TextPosition textPositionEnd = textPositions.get(textPositions.size() - 1);
point.setX((int) textPositionEnd.getEndX());
point.setY((int) textPositionEnd.getY());
pointList.add(point);
}
}
}
};
textStripper.setSortByPosition(true);
textStripper.setStartPage(1);
textStripper.setEndPage(document.getNumberOfPages());
textStripper.getText(document);
document.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(">>>>>pointList" + JSON.toJSONString(pointList));
return pointList;
}
}
7. 驗(yàn)證代碼可行性
整理出來的excel,檢查里面有些空格沒有處理,就讓業(yè)務(wù)自己批量替換一下。
因?yàn)榇a只是一次性用的,就沒有怎么進(jìn)行封裝了??傮w來講業(yè)務(wù)同事比較滿意。文章來源地址http://www.zghlxwxcb.cn/news/detail-641369.html
結(jié)論
- 第三方庫(kù)pdfbox可以操作PDF文檔。3.0版本之后和歷史版本相差比較大,最好先閱讀下源碼。
- 坐標(biāo)定位的話,可以用第三方也可以代碼定位
- 如果代碼后續(xù)想復(fù)用的話,最好抽離出公共方法
- 文件比較多的情況下,建議增加多線程處理。
到了這里,關(guān)于Java實(shí)戰(zhàn):高效提取PDF文件指定坐標(biāo)的文本內(nèi)容的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!