You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java实现PDF转Excel:如何保留原PDF表格的行列结构?

解决PDF表格转Excel保留行列结构的问题

我明白你的困扰——用iText读取PDF表格后转成Excel,结果所有内容都变成了纯文本,完全没保留原有的表格行列结构对吧?这是因为PDF里的表格并非原生的数据结构,本质上是靠文本的坐标定位+线条绘制拼出来的,默认的文本提取只会按顺序读取内容,不会识别文本的位置关系来还原表格。

下面给你一套可行的解决方案,通过捕获文本位置信息来还原表格结构:

核心思路

  1. 自定义iText文本提取策略,记录每个文本块的**坐标位置(x、y)**和内容;
  2. 根据文本块的y坐标分组(y值相近的属于同一行);
  3. 每行内的文本块按x坐标排序(从左到右对应表格的列);
  4. 把整理好的行列数据写入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

火山引擎 最新活动