主要实现的功能:
1.读取PDF内容,将PDF转换成为(行,列,数据内容)的结构,方便后续使用。
2.已经很方便了好吗?对于那些抠字段信息的具体实现大家可以自己写,我是觉得对于一般的固定格式的pdf文件),笨笨的找行和列号就能够找到数据了。(手动狗头保命)
示例代码使用的jar
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itextpdfartifactId>
<version>5.5.13version>
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;
}
}
[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的内容成功转换为行列数据,亲们动动发财的小手,自己将代码改为自己想要的功能就好了,谢谢大家,欢迎评论哟!