Duck Observations
【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
| Number of freshwater ducks per year | | - |
| Year | Freshwater Ducks | | - | - | | 2019 | 120 | | 2020 | 135 | | 2021 | 150 | | 2022 | 170 | | 2023 | 160 | | 2024 | 180 |
可以看出:工作表名被序列化为一个二级标题,而正文内容**不是一整张大表**,而是被切分成了“一个 1×1 的标题表格”和“一个 7×2 的数据表格”两个独立的 `TableItem`。这正是 Docling Excel 后端区域探测算法的直接体现。 ## 二、为什么标题没有变成正文文本,而是成为一张 1×1 表格 很多读者第一眼会疑惑:`Number of freshwater ducks per year` 明显是个标题,为什么在默认输出里它表现为一张孤零零的 1×1 表,而不是一段文本? 答案藏在 Excel 后端的两个行为中: 1. **断连区域会被解析成不同表格**。在 [msexcel_backend.py](https://link.gitcode.com/i/4df4451c4f2f781193a9cef8aec7168c) 的类文档字符串中写明:“Cell contents, parsed as tables. If two groups of cells are disconnected between each other, they will be parsed as two different tables.” 结合 JSON 真值中两张表的 `prov.bbox` 可以发现:标题簇占据 `t=1 → b=2`,数据表簇占据 `t=3 → b=10`,两者之间存在一整行的空隙。因此基于 Flood Fill(BFS)的连通域探测把该工作表识别为**两个互不连通的单元格簇**,各生成一张 `TableItem`。 2. **`treat_singleton_as_text` 默认是 `False`**。在 [MsExcelBackendOptions](https://link.gitcode.com/i/e1b6476442423a2c4ae7f1f65672804b) 中,该选项被定义为: ```python treat_singleton_as_text: bool = Field( False, description=( "Whether to treat singleton cells (1x1 tables with empty neighboring " "cells) as TextItem instead of TableItem." ), )因为后端默认并不启用该选项,所以被孤立出来的 1×1 标题簇不会升级为TextItem,而是保持为一张 1×1 的TableItem。对应逻辑位于 msexcel_backend.py:只有当treat_singleton_as_text=True且该表恰好只有一个数据单元格时,才会通过doc.add_text(...)把它输出为正文文本。
这也解释了为什么 Markdown 真值第一行会出现单列表格:
| Number of freshwater ducks per year | | - |这是一张“只有表头、没有数据行”的紧凑型单列表:它的唯一单元格位于第 0 行(column_header: true),序列化后生成表头行,再用| - |作为分隔行。
三、从 JSON 真值看懂单元格语义标注
阅读 xlsx_05_table_with_title.xlsx.json 可以从内部结构上验证上述行为:
- 文档
body下只有一个groups/0,其name为Duck Observations、label为sheet,即整个工作表被建模为一个 sheet 分组(GroupItem),这正是 Markdown 中## Duck Observations标题的来源; texts数组为空——说明在默认选项下没有产生任何正文文本;tables数组包含两张表:tables/0:1 行 1 列,单元格文本为Number of freshwater ducks per year,语义标志为"column_header": true(因为该单元格位于第 0 行),没有合并跨行跨列(row_span/col_span均为 1);tables/1:7 行 2 列。首行Year、Freshwater Ducks均被标注为column_header: true,其后 2019–2024 共 6 行数值单元格的column_header均为false。
单元格层面的column_header/row_header/row_section布尔标志,正是 Docling 在文档中保留的表格语义元数据。它来自后端的建表逻辑:在 msexcel_backend.py 中,每个 Excel 单元格被转换为TableCell时执行column_header=excel_cell.row == 0——即只要单元格处于第 0 行就被视为列头。这让数据表的表头行与普通数据行在结构化层面得到明确区分,为后续面向 RAG 的切片、表格导出乃至 HTML 渲染提供了可靠依据。
四、从 itxt 真值看文档层级
xlsx_05_table_with_title.xlsx.itxt 用缩进文本直观呈现了同一份文档的树形结构:
item-0 at level 0: unspecified: group _root_ item-1 at level 1: sheet: group Duck Observations item-2 at level 2: table with [1x1] item-3 at level 2: table with [7x2]它验证了三个关键信息:整个工作表内容都挂在一个sheet分组下;该分组直接包含两张表格子节点;两张表的尺寸分别是[1x1]与[7x2],与 Markdown/JSON 真值完全一致。对于希望调试“为什么我的表被拆开了”的读者,itxt是比 JSON 更快的定位工具。
五、真值文件如何在测试中被使用
tests/data/xlsx/groundtruth/下的三份产物会被 test_backend_msexcel.py 中的两处测试消费:
5.1 端到端回归测试(默认选项)
test_e2e_excel_conversions(见 test_backend_msexcel.py)用默认DocumentConverter依次转换tests/data/xlsx/sources/下所有工作簿,然后:
- 使用 docling_core 的
MsExcelMarkdownDocSerializer,以MarkdownParams(compact_tables=True, layers=DEFAULT_CONTENT_LAYERS)序列化 Markdown,并与.md真值比对; - 使用
doc._export_to_indented_text(...)与.itxt真值比对; - 使用
verify_document(..., fuzzy=True)与.json真值比对。
因为默认转换没有启用treat_singleton_as_text,标题单元格被保留成 1×1 表格,所以.md真值中出现两张表——这与默认 e2e 的比对设定是自洽的。
5.2 选项行为测试(开启 treat_singleton_as_text)
test_table_with_title(见 test_backend_msexcel.py)则专门验证反向行为:显式构造MsExcelBackendOptions(treat_singleton_as_text=True)并注入ExcelFormatOption后再转换同一文件,此时断言结果变为:
assert len(doc.texts) == 1 # 标题变成了一个 TextItem assert len(doc.tables) == 1 # 只剩 7x2 的数据表 assert doc.texts[0].text == "Number of freshwater ducks per year" assert table.data.num_rows == 7 and table.data.num_cols == 2对照读这两处测试,就能完整理解同一份源文件在不同选项下“标题究竟是正文还是表”的输出差异,也说明了真值文件.md(默认选项)与test_table_with_title(开启选项)并不矛盾,而是覆盖了两种合法配置。
六、自己动手复现与验证
在仓库环境中可用以下方式复现该真值(默认选项下应得到与.md文件一致的输出):
from docling.document_converter import DocumentConverter from docling.datamodel.base_models import InputFormat converter = DocumentConverter(allowed_formats=[InputFormat.XLSX]) result = converter.convert( "tests/data/xlsx/sources/xlsx_05_table_with_title.xlsx" ) doc = result.document print(doc.export_to_markdown()) print(doc._export_to_indented_text(max_text_len=70, explicit_tables=False))如需复现“标题升级为正文文本”的路径,把MsExcelBackendOptions通过ExcelFormatOption传入即可:
from docling.document_converter import DocumentConverter, ExcelFormatOption from docling.datamodel.base_models import InputFormat from docling.datamodel.backend_options import MsExcelBackendOptions options = MsExcelBackendOptions(treat_singleton_as_text=True) converter = DocumentConverter( allowed_formats=[InputFormat.XLSX], format_options={InputFormat.XLSX: ExcelFormatOption(backend_options=options)}, ) doc = converter.convert( "tests/data/xlsx/sources/xlsx_05_table_with_title.xlsx" ).document print(list(doc.texts)[0].text) # Number of freshwater ducks per year print(list(doc.tables)[0].data.num_rows, "x", list(doc.tables)[0].data.num_cols)【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考