主要实现的功能: 1.读取PDF内容,将PDF转换成为(行,列,数据内容)的结构,方便后续使用。 2.已经很方便了好吗?对于那些抠字段信息的具体实现大家可以自己写,我是觉得对于一般的固定格式的pdf文件),笨笨的找行和列号就能够找到数据了。(手动狗头保命)
示例代码使用的jar
<!--java解析PDF --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13</version> </dependency>实例代码工具类:
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.itextpdf.awt.geom.Rectangle2D; import com.itextpdf.text.pdf.parser.ImageRenderInfo; import com.itextpdf.text.pdf.parser.RenderListener; import com.itextpdf.text.pdf.parser.TextRenderInfo; public class PDFTextListener implements RenderListener{ Logger log = LoggerFactory.getLogger(PDFTextListener.class); //页码 int pageNumber; //保存(y,数据行) 的 map private Map<Integer,TextRow> rowMap = new TreeMap<Integer,TextRow>(); //由于字体大小不一定相等,因此设置在上下 1pix 认定为同一行数据,该参数可调 private final int MAX_ROW_ERROR_PIX = 1 ; //解析结果行 List<TextRow> allRows = new ArrayList<>(); /** * 读取一页pdf文件并且将这一页pdf文件转换为一行行的文字,最终放在rowMap中 * @param renderInfo 文字信息 * * */ @Override public void renderText(TextRenderInfo renderInfo) { //读取PDF时,有些肉眼看上去是一行的字,可能会被解析为多个,导致找不到满足条件的关键字,这里做了简单的处理 //即如果一些词是连续的,前后没有空白字符串,即认为是一个词 String content = renderInfo.getText().trim(); //获取文字的边框 Rectangle2D.Float textRectangle = renderInfo.getDescentLine().getBoundingRectange(); log.info("content:{},(x,y,w,h)=({}, {}, {}, {})",content,textRectangle.getX(),textRectangle.getY(),textRectangle.getWidth(),textRectangle.getHeight()); double y = textRectangle.getY(); double x = textRectangle.getX(); TextRow row = null; int keyY = (int)y; boolean containsKey = false; //允许行的高度误差,从小往大找 for(int findY=keyY-MAX_ROW_ERROR_PIX;findY<=keyY+MAX_ROW_ERROR_PIX;findY++){ if(rowMap.containsKey(findY)){ keyY = findY; containsKey = true; break; } } if(!containsKey){ //创建一行新的记录 row = new TextRow(); row.setY(y); rowMap.put(keyY, row); } row = rowMap.get(keyY); if(content!=null&&content.length()>0){ //处理一行数据左右拼接问题(字体大小不一致) TextColumn column; if(row.getColumnsMap().containsKey(x)){ column = row.getColumnsMap().get(x); content = column.getContent()+content; column.setContent(content); }else{ column = new TextColumn(); column.setContent(content); column.setX(x); column.setY(y); row.getColumnsMap().put(x, column); } } } /** * 获取一个 (行号,列号,字符串)的字典,方便查找使用 * * */ public Map<Integer,Map<Integer,String>> getPageContentMap(){ Map<Integer,Map<Integer,String>> result = new HashMap<>(); for(Integer i=0;i<allRows.size();i++){ Map<Integer,String> tempRow = new HashMap<Integer,String>(); List<TextColumn> list = allRows.get(i).getColumns(); for(Integer j=0;j<list.size();j++){ tempRow.put(j, list.get(j).getContent()); } result.put(i, tempRow); } return result; } /** * 重要,由于解析的时候,rowMap中的y值是乱序的(因为itext解析也是有点乱序的),因此需要对rowMap进行逆序排序之后, * 才能得到正确的行,才能将排序好的数据放进allRows中。 * * */ public void sortAndAddToAllRows(){ allRows.clear(); rowMap.entrySet().stream(). sorted(Map.Entry.<Integer,TextRow>comparingByKey().reversed()).forEachOrdered(entry->{ allRows.add(entry.getValue()); TextRow row = entry.getValue(); row.setRowIndex(allRows.size()-1); //同理,每行的列也是乱序的,因此需要按照x从小到大排序。 row.sortAndAddColumns(); log.info("key:{} , value:{},size:{}",entry.getKey(),entry.getValue().getRowContent(),row.getColumnsMap().size()); }); } @Override public void endTextBlock() { // TODO Auto-generated method stub } @Override public void renderImage(ImageRenderInfo renderInfo) { // TODO Auto-generated method stub } @Override public void beginTextBlock() { // TODO Auto-generated method stub } public class TextRow { double y; int rowIndex; Map<Double,TextColumn> columnsMap = new HashMap<Double,TextColumn>(); List<TextColumn> columns = new ArrayList<>(); String rowContent=""; /** * 重要,由于解析的时候,columnsMap中的x值是乱序的(因为itext解析也是有点乱序的),因此需要对columnsMap进行顺序排序之后, * 才能得到正确的列,才能将排序好的数据放进columns中。 * * */ public void sortAndAddColumns(){ this.rowContent = ""; columns.clear(); columnsMap.entrySet().stream(). sorted(Map.Entry.<Double,TextColumn>comparingByKey()).forEachOrdered(entry->{ entry.getValue().setColumnIndex(columns.size()); columns.add(entry.getValue()); rowContent = rowContent +" "+entry.getValue().getContent(); }); } public double getY() { return y; } public void setY(double y) { this.y = y; } public int getRowIndex() { return rowIndex; } public void setRowIndex(int rowIndex) { this.rowIndex = rowIndex; } public String getRowContent() { return rowContent; } public void addRowContent(String str){ this.rowContent = rowContent.concat(" ").concat(str); } public int getColumnsNumber(){ if(this.columns!=null){ return this.columns.size(); }else{ return 0; } } public Map<Double, TextColumn> getColumnsMap() { return columnsMap; } public List<TextColumn> getColumns() { return columns; } public void setColumns(List<TextColumn> columns) { this.columns = columns; } } public class TextColumn{ double x; double y; int columnIndex; String content; public double getX() { return x; } public void setX(double x) { this.x = x; } public int getColumnIndex() { return columnIndex; } public void setColumnIndex(int columnIndex) { this.columnIndex = columnIndex; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public double getY() { return y; } public void setY(double y) { this.x = y; } } public int getPageNumber() { return pageNumber; } public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; } public List<TextRow> getAllRows() { return allRows; } public void setAllRows(List<TextRow> allRows) { this.allRows = allRows; } }具体测试代码:
import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import com.alibaba.fastjson.JSON; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.parser.PdfReaderContentParser; import com.shgsec.opms.yygl.service.impl.*; import com.shgsec.opms.yygl.service.impl.PDFTextListener.TextRow; public class PDFTest { static Logger log = LoggerFactory.getLogger(PDFTest.class); public static void main(String[] args) { String filePath = "E:/项目文件/运营交易划款指令生成项目/CBT20191008200903-现券.pdf"; Map<Integer,List<TextRow>> pages = readPDF(new File(filePath)); List<TextRow> page1 = pages.get(1); if(page1!=null){ for(TextRow row:page1){ log.info("第{}行 有列数:{} y:{} 数据:{}",row.getRowIndex(),row.getColumnsNumber(),row.getY(),row.getRowContent()); } } Map<Integer,Map<Integer,Map<Integer,String>>> pages2 = readPDF2(new File(filePath)); Map<Integer,Map<Integer,String>> pages21 = pages2.get(1); pages21.forEach((key,value)->{ log.info("第{}行 有列数:{} 数据:{}",key,value.size(),JSON.toJSONString(value)); }); } /** * PDF文件解析,返回行数据列表 * @param file 文件 * */ private static Map<Integer,List<TextRow>> readPDF(File file){ PdfReader reader; Map<Integer,List<TextRow>> pageMap = new HashMap<>(); try { reader = new PdfReader(file.getAbsolutePath()); for(int page=1; page<=reader.getNumberOfPages(); page++){ PDFTextListener renderListener = new PDFTextListener(); renderListener.setPageNumber(page); PdfReaderContentParser parse = new PdfReaderContentParser(reader); parse.processContent(page, renderListener); Rectangle rectangle = reader.getPageSize(page); log.info("第{}页:宽:{},高:{},左:{},右:{}",page,rectangle.getWidth(),rectangle.getHeight(),rectangle.getLeft(), rectangle.getRight()); renderListener.sortAndAddToAllRows(); pageMap.put(page, renderListener.getAllRows()); } reader.close(); } catch (IOException e) { e.printStackTrace(); } return pageMap; } /** * PDF文件解析,返回(行,列,内容字典) * @param file 文件 * */ private static Map<Integer,Map<Integer,Map<Integer,String>>> readPDF2(File file){ PdfReader reader; Map<Integer,Map<Integer,Map<Integer,String>>> pageMap = new HashMap<>(); try { reader = new PdfReader(file.getAbsolutePath()); for(int page=1; page<=reader.getNumberOfPages(); page++){ PDFTextListener renderListener = new PDFTextListener(); renderListener.setPageNumber(page); PdfReaderContentParser parse = new PdfReaderContentParser(reader); parse.processContent(page, renderListener); Rectangle rectangle = reader.getPageSize(page); log.info("第{}页:宽:{},高:{},左:{},右:{}",page,rectangle.getWidth(),rectangle.getHeight(),rectangle.getLeft(), rectangle.getRight()); renderListener.sortAndAddToAllRows(); pageMap.put(page, renderListener.getPageContentMap()); } reader.close(); } catch (IOException e) { e.printStackTrace(); } return pageMap; } }测试原文件PDF数据: 解析之后生成的数据:
[PDFTest.java:46] [INFO] 第20行 有列数:1 y:347.5828857421875 数据: 卖方信息 [PDFTest.java:46] [INFO] 第21行 有列数:2 y:326.77716064453125 数据: 机构 浙商银行股份有限公司 [PDFTest.java:46] [INFO] 第22行 有列数:2 y:306.77716064453125 数据: 法定代表人/地址 -/- [PDFTest.java:46] [INFO] 第23行 有列数:2 y:286.77716064453125 数据: 交易员/电话 吕浩/021-50290505 [PDFTest.java:46] [INFO] 第24行 有列数:2 y:266.77716064453125 数据: 资金账户户名 浙商银行 [PDFTest.java:46] [INFO] 第25行 有列数:2 y:246.7771759033203 数据: 资金开户行 浙商银行(316331000018) [PDFTest.java:46] [INFO] 第26行 有列数:2 y:226.7771759033203 数据: 资金账号/支付系统行号 316331000018/316331000018 [PDFTest.java:46] [INFO] 第27行 有列数:2 y:206.7771759033203 数据: 托管账户户名 浙商银行股份有限公司 [PDFTest.java:46] [INFO] 第28行 有列数:2 y:186.7771759033203 数据: 托管账号/托管机构 B0000199/上海清算所解析之后生成的字典数据:
[PDFTest.java:54] [INFO] 第20行 有列数:1 数据:{0:"卖方信息"} [PDFTest.java:54] [INFO] 第21行 有列数:2 数据:{0:"机构",1:"浙商银行股份有限公司"} [PDFTest.java:54] [INFO] 第22行 有列数:2 数据:{0:"法定代表人/地址",1:"-/-"} [PDFTest.java:54] [INFO] 第23行 有列数:2 数据:{0:"交易员/电话",1:"吕浩/021-50290505"} [PDFTest.java:54] [INFO] 第24行 有列数:2 数据:{0:"资金账户户名",1:"浙商银行"} [PDFTest.java:54] [INFO] 第25行 有列数:2 数据:{0:"资金开户行",1:"浙商银行(316331000018)"} [PDFTest.java:54] [INFO] 第26行 有列数:2 数据:{0:"资金账号/支付系统行号",1:"316331000018/316331000018"} [PDFTest.java:54] [INFO] 第27行 有列数:2 数据:{0:"托管账户户名",1:"浙商银行股份有限公司"} [PDFTest.java:54] [INFO] 第28行 有列数:2 数据:{0:"托管账号/托管机构",1:"B0000199/上海清算所"} [PDFTest.java:54] [INFO] 第29行 有列数:2 数据:{0:"1/",1:"1"}可以看见,程序已经把PDF的内容成功转换为行列数据,亲们动动发财的小手,自己将代码改为自己想要的功能就好了,谢谢大家,欢迎评论哟!
