TrapRange:一种提取PDF文件中的表内容的方法


介绍 表数据结构是文档中最重要的数据结构之一,尤其是从企业系统导出数据时,数据通常采用表格式。

有几种数据文件格式,通常用于存储表格内容,例如CSV,文本和PDF。对于前两种格式,只需打开文件,循环浏览并使用适当的分隔符拆分单元格,就可以非常简单地进行操作。执行此操作的库很多。

对于PDF文件,故事完全不同,因为它没有针对表格内容的专用数据定义,如HTML中的表格,tr,td标签。PDF是一种复杂的格式,具有文本数据,字体,样式以及图像,音频和视频,可以将它们混合在一起。以下是我针对高密度表格内容中的数据提出的解决方案。

如何检测表格 经过一番调查,我意识到:

  • Column:同一列单元格中的文本内容位于一个矩形空间,该矩形空间不与另一列的其他矩形空间重叠。例如,在下图中,红色矩形和蓝色矩形是分隔的空格
  • Row:相同水平对齐的单词在同一行中。但这仅是足够的条件,因为行中的单元可能是多行单元。例如,黄色矩形中的第四个单元格有两行,短语“ FK到此客户的记录所在”和“ Ledgers表”不在同一水平排列中,但仍被视为在同一行中。在我的解决方案中,我仅假设单元格中的内容仅是单行内容。单元中的不同行被认为属于不同的行。因此,黄色矩形中的内容包含两行:1. {"Ledger_ID", "|", "Sales Ledger Account", "FK to this customer's record to"} 2. {NULL, NULL, NULL, "Ledgers table"}

PDFBox API 我在traprange后面的库是PDFBox到目前为止我所知道的最好的PDF库。要从PDF文件提取文本,PDFBoxAPI提供了4个类:

  • PDDocument:包含整个PDF文件的信息。为了加载PDF文件,我们使用方法PDDocument.load(stream: InputStream)
  • PDPage:表示PDF文档中的每一页。我们可以通过使用以下方法传递页面的索引来存档特定的页面内容:document.getDocumentCatalog().getAllPages().get(pageIdx: int)
  • TextPosition:表示文档中的单个单词或字符。我们可以通过class中的重写方法来获取a的所有TextPosition对象。一个对象有方法,,,返回其在网页和方法结合来获得它的内容。PDPageprocessTextPosition(text: TextPosition)PDTextStripperTextPositiongetX()getY()getWidth()getHeight()getCharacter() 在我的工作中,我直接通过使用TextPosition对象来处理文本块。对于PDF文件中的每个文本块,它将返回具有以下属性的文本元素:

  • x:距页面左侧的水平距离

  • y:距页面顶部边框的垂直距离
  • maxX:等于x +文本块的宽度
  • maxY:等于y +文本块的高度

Trap Ranges 最重要的是确定每个行和列的边界,因为如果我们知道行/列的边界,我们可以从中检索该行/列中的所有文本,从而可以轻松提取表中的所有内容并将其放入结构化模型。我们将这些边界命名为trap-ranges。TrapRange有两个属性:

lowerBound:包含此范围的下限端点 upperBound:包含此范围的上端点要计算的值trap-ranges,我们循环浏览页面的所有文本,并将每个文本的范围投影到水平和垂直轴上,获取结果并将它们连接在一起。遍历页面的所有文本后,我们将计算陷阱范围,并使用它们来识别表格的单元格数据。 加入样品

Algorithm 1:计算每个PDF页面的陷印范围:

columnTrapRanges <-- []
rowTrapRanges <-- []
for each text in page
begin
     columnTrapRanges <-- join(columnTrapRanges, {text.x, text.x + text.width} )
     rowTrapRanges <-- join(rowTrapRanges, {text.y, text.y + text.height} )
end

trap-ranges为表格计算之后,我们再次遍历所有文本并将它们分类为表格的正确单元格。

Algorithm 2:将文本块分类为正确的单元格:

table <-- new Table()
for each text in page
begin
     rowIdx <-- in rowTrapRanges, get index of the range that containts this text
     columnIdx <-- in columnTrapRanges, get index of the range that contains this text
     table.addText(text, rowIdx, columnIdx)
end
  • TrapRangeBuilder:build()计算和返回范围
  • Table,TableRow和TableCell:对于表的数据结构
  • PDFTableExtractor是最重要的一类。它包含用于初始化和从PDF文件提取表数据的方法。这里使用了构建器模式。以下是该类中的一些突出显示的方法:
  • setSource:设置PDF文件的来源。有3个重载setSource(InputStream)setSource(File)并且setSource(String)
  • addPage:确定要处理的页面。默认为所有页面
  • exceptPage:跳过页面
  • exceptLine:跳过嘈杂的数据。这些行中的所有文本都将被避免。
  • extract:处理并返回结果

例子

PDFTableExtractor extractor = new PDFTableExtractor();
List<Table> tables = extractor.setSource(“table.PDF”)
    .addPage(0)
    .addPage(1)
    .exceptLine(0) //the first line in each page
    .exceptLine(1) //the second line in each page
    .exceptLine(-1)//the last line in each page
    .extract();
String html = tables.get(0).toHtml();//table in html format
String csv = tables.get(0).toString();//table in csv format using semicolon as a delimiter

评估 在实验中,我使用了具有高表内容密度的PDF文件。结果表明,我的实现检测表格的含量比其他开放式源更好:pdftotext,pdftohtml,pdf2table。对于具有多个表或太多嘈杂数据的文档,我的方法效果不佳。如果行的单元格重叠,则这些单元格的列将合并。

结论 TrapRange该方法对表数据密度高的PDF文件效果最佳。带有文档的多表或嘈杂的数据太多,TrapRange不是一个很好的选择。通过替换PDFBox为相应的PDF库或使用命令行工具pdftohtml提取文本块并将这些数据用作的输入数据,我的方法也可以用其他编程语言实现algorithm 1, 2。


原文链接:http://codingdict.com