Java高效提取PDF文件指定坐标的文本内容实战代码
目录
- 前言
- 解决办法
- 1. 研究下PDF文档,找出解决方案
- 2. 找个能操作PDF的第三方库pdfbox。
- 3. maven加载包
- 4. 先验证下第三方库是否可行
- 5. 确认文本在PDF文档中的坐标位置。
- 1. 代码校验(最精准)
- 2. 最直接粗暴的方法。
- 6. 整个demo先验证第三方库是否可行。
- 7. 整活上代码。
- 8. 验证代码可行性
- 结论
- 总结
前言
临时接到一个紧急需要处理的事项。业务侧一个同事有几千个PDF文件需要整理:需要从文件中的指定位置获取对应的编号和地址。
要的急,工作量大。所以就问到技术部有没有好的解决方案。问技术的话就只能写个demo跑下了。
解决办法
1. 研究下PDF文档,找出解决方案
PDF的文档看起来比较简单,因为只是需要读取两个坐标位置的文本内容,而且位置相对固定。所以就直接用Java的第三方库pdfbox来操作PDF文档。
2. 找个能操作PDF的第三方库pdfbox。
- 先下载pdfbox的jar包。官网介绍
- pdfbox能干啥:
pdfbox是Apache软件基金会的一个开源项目,它提供API和工具来处理PDF文档。
pdfbox是Apache PDFBox的Java版本,它提供了一个类库,用于读取,写入,转换和创建PDF文档。
pdfbox支持处理各种PDF特性,如文本,字体,图像,表单字段,注释,书签,页面布局等。
pdfbox还提供了对加密和数字签名PDF文档的支持,以及对PDF文档的提取和合并。
pdfbox还提供了对PDF文档的验证,签名验证,加密验证和数字签名的支持。
PDFBox是一个用于处理PDF文档的Java库。它提供了一组功能强大的API,可以用于创建、修改和提取PDF文档的内容。PDFBox可以用于各种用途,包括生成PDF文档、提取文本和图像、合并和拆分PDF文件、添加水印和书签等。
PDFBox支持处理各种PDF特性,如文本、字体、图像、表单字段、注释、书签、页面布局等。它还提供了对加密和数字签名PDF文档的支持,以及对PDF文档的高级操作,如提取文本位置信息、提取图像和字体等。
3. maven加载包
pdfbox有三个大的版本,每个版本差异较大,这个时候如果要引入的时候,要注意对应的版本了,否则demo就有可能跑不起来。
pdfbox最新的大版本是3.0。作为新时代的青年,肯定要与时俱进。3.0肯定是要用上的。
4. 先验证下第三方库是否可行
下载jar包后,直接用java代码跑下demo。 demo读取pdf文档内容并输出文本数据到控制台
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.lo编程客栈ad(new File("D:\\pdf\\test.pdf")); PDFTextStripper stripper = new PDFTextStripper(); String text = stripper.getText(document); System.out.println(text); document.close(); } }
发现demo跑起来后,报错。
原因是因为demo是2.0的版本,而当前的jar包是3.0的版本。PDDocument.load这个修改为Loader.load就OK了。接下来,就是如何获取到指定坐标位置的文本内容。
5. 确认文本在PDF文档中的坐标位置。
确认PDF文本坐标一般有两种方案。
1. 代码校验(最精准)
先用demo跑下,看下是否可以读取到指定坐标位置的文本内容。
/** * 获取文档坐标 * @param file PDF文件对象 * @param sourceTex 匹配的字符 * @return 坐标 */ public static Point getPoint(File file,String sourceTex) { Point point = new Point(); //获取文档坐标 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后,发现可以读取到指定坐标位置的文本内容。
这里会有个小问题,就是返回的坐标点有的会有小数。因为当前返回类型float,所以需要转换成int。2. 最直接粗暴的方法。
1. 福昕PDF文档工具。
2. 直接用福昕PDF文档定位工具定位坐标。 说实话,开发比较少用这种方式,因为感觉有点lower(其实是自己不太会用)
6. 整个demo先验证第三方库是否可行。
拿1个文件试试水
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(jsdocument); document.close(); }catch (IOException e) { e.printStackTrace(); } }
结果能够正常输出对应的文本内容。
7. 整活上代码。
奉上全部demo代码
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: 验证pdfbox的可行性 * * @author admin * @date since 2023/8/8 18:44 */ public class PdfDemo { //要匹配的位置内容点 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); // 递归调用,处理子文件夹 } else { String fileName = file.getName(); if (fileName.toLowerCase().endsWith(".pdf")) { mps.add(getLineData(file)); } } } } return mps; } /** * 行数据 * * @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指定坐标点文本值 * * @param file 文件 * @param rectangles 矩形坐标 * @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); // 获取区域的text String text = textStripper.getTextForRegion(regionName); text = text.replace("\u0000","-").replace(" ",""); System.out.println(">>text"+text); textArr[i]=text; } textStripper.setSortByPosition(true); textStrippejavascriptr.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; } }
8. 验证代码可行性
整理出来的excel,检查里面有些空格没有处理,就让业务自己批量替换一下。
因为代码只是一次性用的,就没有怎么进行封装了。总体来讲业务同事比较满意。
结论
- 第三方库pdfbox可以操作PDF文档。3.0版本之后和历史版本相差比较大,最好先阅读下源码。
- 坐标定位的话,可以用第三方也可以代码定位
- 如果代码后续想复用的话,最好抽离出公共方法
- 文件比较多的情况下,建议增加多线程处理。
总结
到此这篇关于Java高效提取PDF文件指定坐标的文本内容的文章就介绍到这javascript了,更多相关Java提取PDF文件指定坐标文本内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论