Java实现PDF转Excel:如何保留原PDF表格的行列结构?
解决PDF表格转Excel保留行列结构的问题
我明白你的困扰——用iText读取PDF表格后转成Excel,结果所有内容都变成了纯文本,完全没保留原有的表格行列结构对吧?这是因为PDF里的表格并非原生的数据结构,本质上是靠文本的坐标定位+线条绘制拼出来的,默认的文本提取只会按顺序读取内容,不会识别文本的位置关系来还原表格。
下面给你一套可行的解决方案,通过捕获文本位置信息来还原表格结构:
核心思路
- 自定义iText文本提取策略,记录每个文本块的**坐标位置(x、y)**和内容;
- 根据文本块的y坐标分组(y值相近的属于同一行);
- 每行内的文本块按x坐标排序(从左到右对应表格的列);
- 把整理好的行列数据写入Excel。
具体代码实现
1. 自定义文本提取策略类(适配iText 5)
这个类会帮我们捕获每个文本片段的位置和内容,方便后续分组:
import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.parser.ImageRenderInfo; import com.itextpdf.text.pdf.parser.RenderListener; import com.itextpdf.text.pdf.parser.TextRenderInfo; import java.util.ArrayList; import java.util.List; public class TableTextExtractionStrategy implements RenderListener { private List<TextBlock> textBlocks = new ArrayList<>(); // 坐标容错值,可根据你的PDF表格行高调整 private static final float COORDINATE_TOLERANCE = 2f; @Override public void beginTextBlock() {} @Override public void renderText(TextRenderInfo renderInfo) { Rectangle rect = renderInfo.getBaseline().getBoundingRectange(); String text = renderInfo.getText().trim(); if (!text.isEmpty()) { textBlocks.add(new TextBlock(rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop(), text)); } } @Override public void endTextBlock() {} @Override public void renderImage(ImageRenderInfo renderInfo) {} // 整理成按行分组、按列排序的表格数据 public List<List<String>> getTableData() { if (textBlocks.isEmpty()) return new ArrayList<>(); // PDF坐标原点在左下角,所以按y值倒序排序(越靠上的行越先处理) textBlocks.sort((tb1, tb2) -> Float.compare(tb2.getTop(), tb1.getTop())); List<List<String>> tableData = new ArrayList<>(); List<String> currentRow = new ArrayList<>(); currentRow.add(textBlocks.get(0).getText()); float currentRowTop = textBlocks.get(0).getTop(); for (int i = 1; i < textBlocks.size(); i++) { TextBlock tb = textBlocks.get(i); // 判断是否属于同一行(y坐标差在容错范围内) if (Math.abs(tb.getTop() - currentRowTop) <= COORDINATE_TOLERANCE) { currentRow.add(tb.getText()); } else { // 当前行按x坐标排序(从左到右) currentRow.sort((t1, t2) -> { float x1 = textBlocks.stream().filter(tb2 -> tb2.getText().equals(t1)).findFirst().get().getLeft(); float x2 = textBlocks.stream().filter(tb2 -> tb2.getText().equals(t2)).findFirst().get().getLeft(); return Float.compare(x1, x2); }); tableData.add(currentRow); // 开启新行 currentRow = new ArrayList<>(); currentRow.add(tb.getText()); currentRowTop = tb.getTop(); } } // 添加最后一行数据 currentRow.sort((t1, t2) -> { float x1 = textBlocks.stream().filter(tb2 -> tb2.getText().equals(t1)).findFirst().get().getLeft(); float x2 = textBlocks.stream().filter(tb2 -> tb2.getText().equals(t2)).findFirst().get().getLeft(); return Float.compare(x1, x2); }); tableData.add(currentRow); return tableData; } // 内部类存储单个文本块的位置和内容 private static class TextBlock { private float left; private float bottom; private float right; private float top; private String text; public TextBlock(float left, float bottom, float right, float top, String text) { this.left = left; this.bottom = bottom; this.right = right; this.top = top; this.text = text; } public float getLeft() { return left; } public float getTop() { return top; } public String getText() { return text; } } }
2. 主逻辑:读取PDF并写入Excel
结合Apache POI完成最终的Excel生成:
import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.parser.PdfReaderContentParser; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; public class PdfTableToExcel { public static void main(String[] args) { String pdfPath = "D:/JDEV_WORK/MANOJ/ItemPriceReport.pdf"; String excelPath = "D:/JDEV_WORK/MANOJ/ItemPriceReport.xlsx"; try (PdfReader reader = new PdfReader(pdfPath); Workbook workbook = new XSSFWorkbook(); FileOutputStream fos = new FileOutputStream(excelPath)) { Sheet sheet = workbook.createSheet("PDF表格数据"); PdfReaderContentParser parser = new PdfReaderContentParser(reader); // 遍历PDF的每一页 for (int pageNum = 1; pageNum <= reader.getNumberOfPages(); pageNum++) { TableTextExtractionStrategy strategy = parser.processContent(pageNum, new TableTextExtractionStrategy()); List<List<String>> tableData = strategy.getTableData(); // 将数据写入Excel int rowIndex = sheet.getLastRowNum(); for (List<String> rowData : tableData) { Row row = sheet.createRow(++rowIndex); int colIndex = 0; for (String cellData : rowData) { Cell cell = row.createCell(colIndex++); cell.setCellValue(cellData); } } } // 自动调整列宽,优化显示 for (int i = 0; i < sheet.getRow(0).getLastCellNum(); i++) { sheet.autoSizeColumn(i); } workbook.write(fos); System.out.println("Excel文件生成成功!"); } catch (IOException e) { e.printStackTrace(); } } }
注意事项
- 坐标容错值调整:如果你的PDF表格行与行间距较大,可以适当增大
COORDINATE_TOLERANCE的值,避免把同一行的文本拆分成多行; - 复杂表格处理:如果PDF里有合并单元格、不规则表格,这个基础版本可能无法完全适配,需要额外判断文本块的宽度和覆盖范围;
- 依赖版本:确保项目中引入了正确的依赖包,比如:
<!-- iText 5 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.3</version> </dependency> <!-- Apache POI --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.5</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.5</version> </dependency>
内容的提问来源于stack exchange,提问作者Raj




