首页
/ Docling Document(DoclingDocument)深度解析:Docling 统一文档表示的字段结构、层级模型与源码实践

Docling Document(DoclingDocument)深度解析:Docling 统一文档表示的字段结构、层级模型与源码实践

2026-09-04 23:56:57作者:房伟宁

Docling v2 引入的统一文档表示 DoclingDocument,是 Docling 把 PDF、DOCX、HTML、Markdown 等数十种格式收敛到同一数据模型的核心。本文围绕仓库文档 DoclingDocument 概念说明 展开:先讲清它的字段体系与树状层级模型,再基于仓库中真实的转换样例逐行解读序列化结构,最后结合 DocumentConverter 与各 Backend 源码,说明如何获取、构建与复用这种统一表示,读完即可在 RAG、检索、下游处理等场景中对文档结构做精确操作。

DoclingDocument 的文档层级结构示例:YAML 片段与 MS Word 原文对照

1. 什么是 DoclingDocument,它解决了什么问题

DoclingDocument 是一个用 Pydantic 定义的文档数据类型,可表达各类文档的公共特征(引自概念文档):

  • 文本(Text)、表格(Tables)、图片(Pictures)等内容项;
  • 以章节和分组组织的文档层级(document hierarchy with sections and groups);
  • 正文与页眉/页脚等"版面装饰"(furniture)之间的区分;
  • 每个内容项的版面信息(bounding boxes,如果可用);
  • 溯源信息(provenance information)。

需要注意一个实现边界:Pydantic 类型定义本身位于独立仓库 docling-coredocling_core.types.doc 模块中,本仓库通过依赖 docling-core>=2.91.0 引入(见 pyproject.tomldependencies 段),因此 DoclingDocumentTextItemTableItem 等类不在本仓库目录内,而由 docling_core.types.doc 提供;本仓库的所有 Backend、Pipeline 和 CLI 都围绕这套类型构建。

它的价值在于:上游几十种输入格式(见 DocumentConverter._get_default_option 中的 _get_default_option,覆盖 DOCX、PDF、HTML、Markdown、CSV、EPUB、LaTeX、邮件、专利 XML、音频等)都被转换成同一个结构,下游无论做 Markdown 导出、JSON 持久化、分块检索还是版面分析,都只需面对一种数据模型。

DoclingDocument 分组(Grouping)示例:"Let's swim" 标题下的子项与列表分组

2. 顶层字段:内容项与内容结构两大类

概念文档把 DoclingDocument 的顶层字段分为两类,这是理解整个模型的关键。

2.1 内容项(content items)

这些字段存放"有内容"的节点:

  • texts:所有具有文本表示的项(段落、章节标题、公式……),基类为 TextItem
  • tables:所有表格,类型 TableItem,可携带结构标注;
  • pictures:所有图片,类型 PictureItem,可携带结构标注;
  • key_value_items:键值项。

以上字段都是列表,其中存放继承自 DocItem 类型的实例,通过 JSON pointer(如 #/texts/4)互相引用父节点和子节点。

2.2 内容结构(content structure)

这些字段不存放内容本身,只组织内容:

  • body:主文档正文的树结构根节点;
  • furniture:所有不属于正文的项(页眉、页脚等)的树结构根节点;
  • groups:不代表内容、而作为其他内容项容器的项(例如一个列表、一个章节)。

body/furniture/groups 中只存放 NodeItem 实例,同样通过 JSON pointer 引用子节点与父节点。

阅读顺序的载体:文档的阅读顺序封装在 body 树中,具体是树中每个节点的 children 列表顺序。也就是说,"先读什么、后读什么"不是隐式的,而是显式地写在树结构里——这正是它相对于纯线性文本流的本质区别。

3. 实例剖析:word_sample.docx 的转换结果

概念文档以 tests/data/word_sample.docx 的转换结果为例,左侧展示 YAML 片段、右侧对照 MS Word 原文。本仓库中该样例及其 ground truth 真实存在:

以下按 JSON 序列化逐段拆解(字段名与真实文件一致)。

3.1 文档头:schema、版本与 origin(溯源)

{
  "schema_name": "DoclingDocument",
  "version": "1.10.0",
  "name": "word_sample",
  "origin": {
    "mimetype": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "binary_hash": 5964280909995938039,
    "filename": "word_sample.docx"
  },
  ...
}

origin 就是概念文档所说 provenance 在文档层面的体现:格式 MIME 类型、内容哈希、文件名。这使一份 DoclingDocument 序列化后可以自我描述"我从哪里来",对审计和去重都很实用。

3.2 body 与 furniture:两个树根

"body": {
  "self_ref": "#/body",
  "children": [ { "$ref": "#/texts/0" }, { "$ref": "#/texts/1" } ],
  "content_layer": "body",
  "name": "_root_",
  "label": "unspecified"
},
"furniture": {
  "self_ref": "#/furniture",
  "children": [],
  "content_layer": "furniture",
  "name": "_root_",
  "label": "unspecified"
}

bodyfurniture 各自是独立的树根(self_ref 指向自身),每个节点带 content_layer 字段标明属于哪一层。这份 DOCX 样例没有页眉页脚,所以 furniture.children 为空——但在 PDF 等版式文档的转换中,页眉页脚项会被挂到这棵树上,从而在导出时可以被选择性地排除。children 按 JSON pointer 引用 texts 列表中的项,顺序即阅读顺序。

3.3 标题树:正文项嵌套在 title 之下

概念文档指出:示例文档第一页的所有项都嵌套在 title 项(#/texts/1)之下。JSON 中可以看到:

"texts": [
  {
    "self_ref": "#/texts/0",
    "parent": { "$ref": "#/body" },
    "children": [],
    "content_layer": "body",
    "label": "text",
    "prov": [],
    "orig": "Summer activities",
    "text": "Summer activities",
    "formatting": {
      "bold": false, "italic": false, "underline": false,
      "strikethrough": false, "script": "baseline"
    }
  },
  {
    "self_ref": "#/texts/1",
    "parent": { "$ref": "#/body" },
    "children": [
      { "$ref": "#/texts/2" },
      { "$ref": "#/pictures/0" },
      { "$ref": "#/texts/3" },
      { "$ref": "#/texts/4" }
    ],
    "content_layer": "body",
    "label": "title",
    "prov": [],
    "orig": "Swimming in the lake",
    "text": "Swimming in the lake"
  },
  ...
]

从中可以读出三层信息:

  1. label 区分角色#/texts/0 是普通 text#/texts/1titletitlechildren 里混排了文本项(#/texts/2 "Duck")、图片项(#/pictures/0)和图注文本(#/texts/3 "Figure 1: This is a cute duckling")——正文、图片和图注的层级归属一目了然。
  2. provformatting 并存:每个文本项都有 prov(provenance 列表)和 formatting(粗体、斜体、下划线、删除线、上下标基线)。formatting 保留了源文档的文字样式,prov 则记录项在原始页面中的位置。
  3. origtext 双字段orig 是源文档中的原始文本,text 是规范化后的文本,便于对照原始内容与处理结果。

关于 prov 的一个值得注意的细节:这份 DOCX 样例的 prov 均为空列表。从仓库代码结构看,DOCX 等结构化格式本身没有版面坐标,溯源信息自然缺位;而在 csv_backend.py 这类 Backend 中,表格单元格会显式传入 prov=[ProvenanceItem(page_no=1)] 等位置信息。这说明 provenance 是"有则填、无则空"的可选字段,不同输入格式下信息密度不同,属于概念文档中"layout information, if available"的准确含义。

3.4 分组(Grouping):groups 如何组织列表

概念文档的第二张图展示:标题 "Let's swim"(#/texts/5)之下的所有项都作为其子节点,其中既包含文本项,也包含装列表元素的分组。JSON 中对应的 groups 字段是:

"groups": [
  {
    "self_ref": "#/groups/0",
    "parent": { "$ref": "#/texts/4" },
    "children": [
      { "$ref": "#/texts/6" },
      { "$ref": "#/texts/7" },
      { "$ref": "#/texts/8" }
    ],
    "content_layer": "body",
    "name": "list",
    "label": "list"
  },
  {
    "self_ref": "#/groups/1",
    "parent": { "$ref": "#/texts/4" },
    "children": [
      { "$ref": "#/texts/10" },
      { "$ref": "#/texts/11" },
      { "$ref": "#/texts/12" }
    ],
    "content_layer": "body",
    "name": "list",
    "label": "list"
  },
  ...
]

可以看到 groups/0parent#/texts/4,其三个列表项(#/texts/6~8)被打包成一个 label: "list" 的容器;父节点 #/texts/4(即标题 "Let's swim" 一级的节点)的 children 则同时引用了直接子文本和这个 group。这就是概念文档所说的"children of 'Let's swim' are both text items and groups"的具体形态:

  • 列表项不直接挂在标题下,而是先聚合成 list 组,再挂在标题下;
  • 组本身不是内容,它的作用是保持"哪些项属于同一个列表/章节"这一结构语义,供导出与分块使用;
  • 每个 group 通过 parent 字段指回树中位置,通过 children 列出成员,双向引用均使用 JSON pointer,任何节点都可以独立定位。

4. 文档如何产生:从 DocumentConverter 到 DoclingDocument

理解字段之后,再看 DoclingDocument 在实际转换链路中的位置。

DocumentConverter 是主入口。其 convert / convert_all 方法返回 ConversionResult,其中 document 属性就是一份 DoclingDocument;转换流程为:按 InputFormat 查表选出 Backend 与 Pipeline(见 _get_default_option),Pipeline 调用对应 Backend 的 convert() 构建出 DoclingDocument。DOCX 这类结构化格式走 SimplePipeline + MsWordDocumentBackend,因此不依赖版面模型即可保留标题、列表等结构,这也是上面 JSON 中 title/list 标签能准确出现的原因。

最小使用示例可参考 minimal.py

from docling.document_converter import DocumentConverter

source = "https://arxiv.org/pdf/2408.09869"

converter = DocumentConverter()
result = converter.convert(source)

# result.document 即 DoclingDocument
print(result.document.export_to_markdown())

(注意:转换 PDF 需要安装含版面模型的依赖;转换 DOCX/HTML 等结构化格式只需结构化格式对应的 extras。运行前提以 pyproject.toml 中定义的 optional-dependencies 为准。)

5. 文档如何被消费与复用

5.1 JSON 往返:model_validate_json

DoclingDocument 作为 Pydantic 模型,序列化/反序列化是其原生能力。仓库中的 JSON Backend docling_json_backend.py 展示了消费侧的权威实现:读取文件(兼容 UTF-8 BOM)后直接 DoclingDocument.model_validate_json(json_data) 还原对象,convert() 再原样返回该文档。这意味着上面第 3 节展示的 JSON 文件本身就是一种一等输入格式:DocumentConverter 可直接把 JSON_DOCLING 格式转回来(见 document_converter.py 中的 InputFormat.JSON_DOCLING 注册),实现"转换一次、到处复用"的管线模式。

5.2 导出:export 方法与序列化器

DoclingDocument 自带导出方法(如 export_to_markdown()),它们是便捷封装,内部委托给序列化器。各导出格式对表格跨行跨列(rowspan/colspan)的处理差异,完整记录在 序列化概念文档 中:

格式 跨行跨列处理
JSON 无损保留,完整序列化 TableData(含全部 span 字段)
Doclang 保留,OTSL 用显式续表标记(colspan 为 LCEL、rowspan 为 UCEL、两者兼有为 XCEL
DocTags 保留,OTSL 原生编码 span 结构
HTML 保留,输出原生 rowspan / colspan 属性
Markdown 扁平化:单元格文本只写在起点位置,其余覆盖格渲染为空
LaTeX 扁平化:tabular 环境暂不输出 \multirow / \multicolumn
WebVTT 不适用,表格不序列化

因此若下游依赖精确表格结构,优先选 export_to_html()export_to_dict() 而不是 export_to_markdown();序列化器体系(BaseDocSerializer / BaseTableSerializer 等)还允许子类化并注入自定义表格序列化器,示例见 serialization.ipynb

5.3 构建 API:从空文档搭建 DoclingDocument

概念文档还提到,DoclingDocument 附带一套"从零构建"的构造 API。本仓库各 Backend 就是这套 API 的用户,源码中可以确认以下方法签名:

  • doc.add_text(...):追加文本项;asciidoc_backend.py 中用它写入各级标题,ebcdic_backend.py 中用于写入字段描述;
  • doc.add_table(data=..., parent=...):追加表格项并可指定父节点;csv_backend.py 用它把整张 CSV 表格挂入文档树,boxnote_backend.py_add_table 同理;
  • doc.add_group(...) / doc.add_inline_group(...):创建分组容器;asciidoc_backend.py 用它把列表项聚合成 group,boxnote_backend.py 同时展示了嵌套分组(行内组与块级组)的用法。

一个与 add_text / add_group 源码一致的构建示意(对应 AsciiDoc/列表处理中的实际模式):

from docling_core.types.doc import DoclingDocument, DocItemLabel

doc = DoclingDocument(name="my_doc")
title = doc.add_text(label=DocItemLabel.TITLE, text="Let's swim")
group = doc.add_group(label="list", name="list", parent=title)
for item_text in ("backstroke", "breaststroke", "butterfly"):
    doc.add_text(label=DocItemLabel.ITEM, text=item_text, parent=group)

以上参数名与调用方式与 asciidoc_backend.pyboxnote_backend.py 中的真实调用保持一致(labeltextparent 为通用参数);完整的构造 API 清单以 docling-core 的类型定义为准。

6. 小结:一份表示,三种用法

对照概念文档的要点,DoclingDocument 的能力在本仓库中都有对应的代码落点,可以归纳为三种用法:

  1. 作为转换目标DocumentConverter.convert() 返回 ConversionResult.document,数十种输入格式汇入同一模型;
  2. 作为持久化与交换格式model_dump_json / model_validate_json 无损往返,JSON Backend 让已转换结果可以重新进入转换管线;
  3. 作为可编程的数据结构body/furniture/groups 树 + JSON pointer 引用,使"标题下有哪些列表""哪些项属于页眉"这类问题变成可直接遍历的结构化查询,而非文本正则;构造 API 则允许在自定义 Backend 中从零搭建同构文档。

进一步阅读建议:word_sample.docx.json(真实序列化全文)、serialization.md(导出格式与 span 行为)、chunking.md(基于该表示的 RAG 分块)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384