好好学java --itext PDF内容解析 -- 将PDF页面数据转换为表格字典

最近楼主在做一个解析PDF内容的项目,遇到的比较棘手的地方是:使用itext解析PDF文件中的文字很不好用呀!网上的介绍都比较简单根本不好用呀!生成一堆的无序String很恶心呀,根本没法使用呀有木有……
正所谓有吐槽的地方就有代码,工作这两年时间做了那么久的伸手党,也该轮到我提供代码给大家了,吼吼吼……

主要实现的功能:
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的内容成功转换为行列数据,亲们动动发财的小手,自己将代码改为自己想要的功能就好了,谢谢大家,欢迎评论哟!

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
iTextPDF-7 框架中,可以使用 `layout` 包中的 `Cell` 类来设置表格单元格的样式,包括单元格的边框、背景色、字体、字号等。要实现单元格内容的自动换行,可以通过设置单元格的最大高度和宽度来实现。 首先,需要创建一个表格对象,并设置表格的列数和宽度: ``` Table table = new Table(new float[]{1, 1}); // 创建一个有两列的表格 // 设置每一列的宽度 table.setWidth(UnitValue.createPercentValue(100)); // 表格宽度为100% ``` 然后,创建一个 `Cell` 对象并设置单元格的样式和内容: ``` Cell cell = new Cell(); cell.setHeight(UnitValue.createPointValue(50)); // 设置单元格最大高度 cell.add(new Paragraph("这是一个很长很长很长的单元格内容")); // 设置单元格样式 cell.setBorder(Border.NO_BORDER); cell.setPadding(5); cell.setFontSize(12); cell.setTextAlignment(TextAlignment.CENTER); cell.setVerticalAlignment(VerticalAlignment.MIDDLE); cell.setKeepTogether(true); ``` 通过设置 `setHeight()` 方法,可以设置单元格的最大高度。如果单元格内容超出了最大高度,iText 会自动将内容换行并调整单元格高度以适应内容。同时,可以通过设置 `setKeepTogether(true)` 方法,让单元格内容保持在同一页上。 最后,将单元格添加到表格中并将表格添加到 PDF 文档中: ``` table.addCell(cell); // 将单元格添加到表格中 document.add(table); // 将表格添加到 PDF 文档中 ``` 这样,就可以实现表格单元格内容的自动换行,并且设置单元格的样式了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值