pdf2docx开发概要:解析表格¶
发布于:2020-08-15 | 分类:process automation
PDF中没有语义上的表格的概念,所以需要根据外观上的表格,即边框线(Stroke
类型的形状)围成的区域,来识别语义上的表格。另一方面,不借助文本框的话Word
不支持浮动布局,表现为一段文字默认占据一整行,所以需要表格来进行辅助布局,例如多列文本。于是,pdf2docx
考虑两类表格:
- 显式表格(
Lattice Table
):有边线,组织结构化的内容 - 隐式表格(
Stream Table
):无边线,既可能用于组织结构化的内容(隐藏边框的表格),也可能是为了页面布局的需要
解析显式表格¶
从边线出发,确定表格结构->单元格属性->单元格内容:
-
以是否相交为依据分组
Stroke
元素,每一组即为潜在的表格区域内的边框线(table border),根据潜在表格区域获取Fill
单元格背景(cell shading)。 -
基于边框线确定表格结构
-
判断是否存在外边框线,没有则加上假想的外边框线(白色、宽度0)。
-
将边框线按横、纵分组得到行间隔线和列间隔线,横纵间隔线的交点组成了初始的未考虑合并单元格的表格结构。
-
对于每一行,检测一条假想水平线与列边线的交点:不存在交点的位置即发生了行方向的合并单元格;同理检测列方向的合并单元格。
-
-
基于表格结构确定单元格属性
-
以左上角单元格表示合并单元格区域,被合并的单元格初始化为空
Cell()
。 -
根据每一个单元格的区域(合并单元格则考虑所有合并区域)确定边框的四个矩形:从而直接得到各个边框的宽度和颜色;并根据单元格区域检测
Fill
类型的矩形,存在则得到单元格背景色。
-
-
基于表格结构确定单元格包含的文本
-
将包含于单元格区域的文本/图片加入到相应单元格。有时文本块的划分与单元格区域并不严格匹配,需要深入到
block
>line
>span
>char
级别拆分原始文本块。 -
如果两个文本块物理上处于同一行,则合并为一个大文本块。目的是创建Word文档时保证正确的位置关系,因为每一个文本块将被作为独立的段落。
-
得到表格的位置及属性后,需要考虑能否在python-docx
中重建。好在或者直接使用API或者借助openxml
,以下操作都被python-docx
支持:
解析隐式表格¶
实际上很难完美重建语义上的无边线表格,pdf2docx
退而求其次,只是为了保证位置关系,从“看起来一样”的角度解析隐式表格。基本思路:
从文本块出发,确定分隔线即边线,接下来与显式表格的处理步骤一致。
-
基于文本块位置关系检测潜在的隐式表格区域
-
如果同一文本块内
line
级别元素竖直方向有重叠但物理上不是严格处于同一行,它将被视为表格的行。因为docx重建时,普通段落无法保证这样的位置关系,所以必须加入潜在表格区域,后续进一步划分为不同列。 -
如果同一文本块内
line
级别元素出现多次且相邻之间间隔一定距离,各个line
将被视为潜在的单元格,因此整个文本块被加入潜在表格区域。 -
同理,如果相邻两个文本块物理上处于同一行(竖直方向有重叠),它们也将被视为表格区域。
-
-
基于以上文本块的
line
级别的元素递归检测边界线-
按列分组,相邻两组的中间线即为列边界。因为列的角度有利于docx重建,行方向会因为不同文本块另起一段而破坏位置关系。
-
每一组内按行分组,相邻两组的中间线即为行边界。
-
重复以上步骤,直到每组只有单一文本块。
-
如上得到隐式表格区域的假想边框线,接下来按照显式表格流程处理即可。
以上将相邻两组的中间线作为列边界的处理方式简单快捷,但不足之处是不同行中的各列边界参差不齐,使表格结构复杂化。另外,还有一类半显式半隐式表格,即在隐式表格的基础上,又显式给出了部分表格线,例如常见的三线表。这些都需要在上述隐式表格解析的基础上进行表格线的优化,具体参考下文:
数据结构¶
表格块TableBlock
继承了块元素Block
的结构(详见PDF提取),同时新增了表征表格结构的Row
和Cell
。其中Cell
是布局级别的对象(Layout
),嵌套了下一级的块元素和形状元素,如此递归,直到块元素中不再包含表格,即只有表征文本和图片的TextBlock
。
{
'type': int, # 3-explicit table; 4-implicit table
'bbox': (float, float, float, float),
..., # some spacing properties same with Block
"rows": [
{
'bbox': (float, float, float, float),
'height': float,
'cells': [
{
'bbox': (float, float, float, float),
'bg-color': int,
'border-color': (int, int, int, int), # top, right, bootom, left
'border-width': (float, float, float, float),
'merged-cells': (int, int),
'blocks': [
{ # text/image blocks contained in current cell },
{ ... }
],
'shapes': [
{ # shapes contained in current cell }
{ ... }
]
},
... # more cells
]
},
... # more rows
]
}