Pandoc 表格转换实战:Native 格式到 OpenDocument 的完整映射解析(test/command/10002 深度解读) Pandoc 表格转换实战Native 格式到 OpenDocument 的完整映射解析test/command/10002 深度解读【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc导读本文以 pandoc 仓库中的命令测试文件 test/command/10002.md 为核心载体逐行剖析一个包含双行表头、双行表体、双行表尾及跨列单元格的表格是如何从 pandoc 内部 AST 的native文本表示被转换为 LibreOffice/OpenOffice 可识别的 OpenDocumentODTXML 表格结构的。读完本文你将掌握 pandoc 表格 AST 的核心数据结构Table/TableHead/TableBody/TableFoot/Cell、OpenDocument 写入器src/Text/Pandoc/Writers/OpenDocument.hs中表格渲染的完整调用链以及 pandoc 命令测试golden test机制的编写与运行方式。一、这个测试文件在测试什么test/command/10002.md是 pandoc 回归测试套件command tests中的一个用例验证的是pandoc -f native -t opendocument这条转换链路。整个文件是一个输入 期望输出的 golden 测试stdin 输入一份native格式的 Pandoc AST^D表示输入结束其后的所有行是命令应当产生的标准输出OpenDocument XML。该测试的格式由 test/Tests/Command.hs 定义规则如下组成部分含义首行% pandoc ...要执行的 shell 命令%之后的部分命令之后的行作为 stdin 传入命令的文本^D单独一行stdin 输入结束标记^D之后的行期望在 stdout 上看到的输出 退出码可选期望的非零退出状态2 ...可选期望的 stderr 输出行Tests.Command.hs中的runCommandTesttest/Tests/Command.hs#L101-L120会解析每个以.md结尾的文件把%后的命令交给execTest执行再通过goldenTest将实际输出与^D后的期望输出做逐字节比较。因此本文档不仅是一份转换示例更是一份可自动验证的规格说明——任何改动如果破坏了表格到 OpenDocument 的输出这个测试就会失败。二、Native 输入表格 AST 的完整解剖测试的输入部分是一棵完整的Table块。先看它的整体骨架[ Table ( , [] , [] ) ... ]是 pandoc 文档的块列表[ Table ( , [] , [] ) -- Attr表格属性id/class/键值对 (Caption Nothing []) -- Caption无标题文字 [ ( AlignDefault , ColWidthDefault ) -- 3 列 ColSpec对齐、列宽 , ( AlignDefault , ColWidthDefault ) , ( AlignDefault , ColWidthDefault ) ] (TableHead ... ) -- 表头两行 [ TableBody ... ] -- 表体两个 Row (TableFoot ... ) -- 表尾两行 ]对照源码 src/Text/Pandoc/Writers/AnnotatedTable.hs#L60-L77Pandoc 表格的类型结构完全一致Table B.Attr B.Caption [B.ColSpec] TableHead [TableBody] TableFootTableHead B.Attr [HeaderRow]TableBody B.Attr B.RowHeadColumns [HeaderRow] [BodyRow]TableFoot B.Attr [HeaderRow]其中TableBody的第二个参数(RowHeadColumns 0)表示该表体有 0 个行头列stub column即表体行没有左侧的th头列区。2.1 列规格ColSpec三个列规格均为( AlignDefault , ColWidthDefault )AlignDefault默认对齐方式在 OpenDocument 写入器里不会产生fo:text-align属性ColWidthDefault不指定列宽。写入器源码 src/Text/Pandoc/Writers/Shared.hs#L575 中getColWidth (_, ColWidthDefault) 0即该列宽度按 0 处理最终不会生成相对宽度百分比。2.2 表头TableHead跨列单元格的表示表头由两行组成。第一行只有一个单元格ColSpan 3[ Cell ( , [] , [] ) AlignDefault (RowSpan 1) (ColSpan 3) [ Plain [ Str First , Space , Str Header , Space , Str Row ] ] ]RowSpan 1/ColSpan 3跨越 1 行、3 列——即该单元格横跨整个表格宽度Plain [ Str First , Space , Str Header , Space , Str Row ]单元格内容是一个Plain块由三个词Str与两个空格Space构成渲染为 First Header Row。第二行是三个独立单元格分别为 Second、Header、Row各自ColSpan 1。这两行叠加就形成了大表头与细分表头两层结构是对真实文档如跨列的大标题 列子标题的典型建模。2.3 表体TableBody与表尾TableFoot表体包含两个Row第 1 行单元格内容 Header - Table、Header - Body、Header - Row第 2 行单元格内容 Table、Body、Row。表尾与表头对称第一行只有一个ColSpan 3的单元格First Footer Row第二行是 Second、Footer、Row 三个独立单元格。这里可以观察到 pandoc AST 的一个重要设计表头行、表体行、表尾行共享同一种Row/Cell结构区分它们的是所在的区域TableHead/TableBody/TableFoot这一区分最终决定了输出时采用不同的段落样式见下文第四节。三、OpenDocument 输出一行一行读懂 XML测试期望的输出是一段完整的table:tableXML。我们先看它的顶层结构table:table table:nameTable1 table:style-nameTable1 table:table-column table:style-nameTable1.A / table:table-column table:style-nameTable1.B / table:table-column table:style-nameTable1.C / table:table-header-rows ... /table:table-header-rows table:table-row ... /table:table-row ... /table:table3.1 表格与列定义table:nameTable1与table:style-nameTable1表格名与样式名。源码 src/Text/Pandoc/Writers/OpenDocument.hs#L553-L556 中name Table tshow (tn 1)tn是文档中已生成的表格样式数量因此第一个表格命名为Table1这也解释了为什么测试输出是 Table1 而非随机值三个table:table-column分别对应三列样式名为Table1.A、Table1.B、Table1.C。列标识来自源码 src/Text/Pandoc/Writers/OpenDocument.hs#L555 的genIds map chr [65..]——即大写字母A、B、C……依次编号。3.2 表头区域跨列属性的落地table:table-header-rows中有两个table:table-row第一行的单元格带有table:number-columns-spanned3table:table-cell table:style-nameTableHeaderRowCell office:value-typestring table:number-columns-spanned3 text:p text:style-nameTable_20_HeadingFirst Header Row/text:p /table:table-cell这正是 AST 中ColSpan 3的 XML 落地。第二行的三个单元格则不带number-columns-spannedColSpan 1不输出该属性。注意输出中的换行First Header Row 在text:p内被输出为First Header\nRow。这是因为 OpenDocument 写入器对Plain块的文字使用了特定的排版换行策略text:p内部的软换行由渲染器处理属于格式化的正常表现不影响文本语义。3.3 表体与表尾样式切换表体两行的单元格样式为TableRowCell但段落样式有讲究表体第 1 行三个单元格Header - Table 等使用段落样式Table_20_Heading表体第 2 行Table、Body、Row使用段落样式Table_20_Contents表尾第 1 行ColSpan 3单元格使用Table_20_ContentsFirst Footer Row表尾第 2 行三个单元格同样使用Table_20_Contents。这一规律与源码完全对应表体的行头row headers沿用表头样式、表体其余行用内容样式src/Text/Pandoc/Writers/OpenDocument.hs#L658-L665表尾统一走tableHeaderRowsToOpenDocument o ns TableRowCell rsrc/Text/Pandoc/Writers/OpenDocument.hs#L667-L671并传入表体内容段落样式。四、源码级印证OpenDocument 写入器的表格渲染管线4.1 主入口table函数写入器对每个Table块调用table opts (Ann.Table (ident, _, _) (Caption _ c) colspecs thead tbodies tfoot)src/Text/Pandoc/Writers/OpenDocument.hs#L550-L586执行顺序为addTableStyle $ tableStyle tn textWidth columnIds注册表格样式列样式、表头/表体单元格样式addParaStyle注册Heading系列与Contents系列的段落样式paraTableStylescolHeadsToOpenDocument渲染table:table-header-rowsmapM tableBodyToOpenDocument渲染每个table:table-body测试中表体没有包裹元素直接输出table:table-row因为TableBody区域在 ODT 中不生成独立容器tableFootToOpenDocument渲染表尾行按writerTableCaptionPosition决定表格标题Caption在表格上方还是下方。4.2 单元格属性跨列、跨行与对齐tableItemToOpenDocumentsrc/Text/Pandoc/Writers/OpenDocument.hs#L703-L715组装每个table:table-cell的属性基础属性table:style-nameTableHeaderRowCell或TableRowCell与office:value-typestringcolspanAttribsrc/Text/Pandoc/Writers/OpenDocument.hs#L683-L687ColSpan 1时不输出任何属性ColSpan n时输出table:number-columns-spannednrowspanAttribsrc/Text/Pandoc/Writers/OpenDocument.hs#L689-L693RowSpan n时输出table:number-rows-spannedn本测试中所有RowSpan 1均不输出符合预期alignAttribsrc/Text/Pandoc/Writers/OpenDocument.hs#L695-L701AlignRight产生fo:text-alignend、AlignCenter产生fo:text-aligncenter、AlignDefault不产生对齐属性——与测试输入中三个AlignDefault无任何对齐属性输出的事实吻合。4.3 样式表的生成规则tableStylesrc/Text/Pandoc/Writers/OpenDocument.hs#L887-L922揭示了三个关键细节表格本身table:aligncenter居中且当总列宽为(0,1]之间的相对宽度时输出style:rel-width百分比列宽非 0 时列样式输出style:rel-column-width数值为floor (w * 65535)后跟*OpenDocument 的相对列宽单位TableHeaderRowCell与TableRowCell两个单元格样式fo:bordernone即无边框只在文档中第一个表格num 0时生成src/Text/Pandoc/Writers/OpenDocument.hs#L918-L920后续表格复用避免样式冗余。而段落样式Table_20_Heading与Table_20_Contents并非写入器动态生成它们来自参考文档 data/odt/styles.xmlTable_20_Contents是基础段落样式Table_20_Heading以它为父样式style:parent-style-nameTable_20_Contents派生。这正是测试输出中表头单元格带text:style-nameTable_20_Heading、表体内容带text:style-nameTable_20_Contents的原因。五、亲手复现把测试跑起来无需修改仓库任何文件你可以用本地编译的 pandoc 直接复现该测试的输入输出# 方式一直接以文件为输入native 文本被当作文档处理 pandoc -f native -t opendocument test/command/10002.md # 方式二手动把 AST 段从 [ Table ... 到 ^D 之前的部分保存为 input.native # 然后执行 pandoc -f native -t opendocument input.native运行后得到的输出应当与 test/command/10002.md 中^D之后的期望 XML 完全一致。也可以将该 XML 保存为table.fragment再手工包进一个 ODT 模板或在命令行中加入--standalone观察它如何被嵌入完整的 OpenDocument 文档pandoc -f native -t opendocument --standalone input.native -o table.odt生成的.odt可以直接用 LibreOffice 打开查看表格效果。此外native格式本身也支持反向验证用pandoc -f opendocument -t native table.odt可观察 OpenDocument 读取器如何把 XML 还原回 AST见 src/Text/Pandoc/Readers/OpenDocument.hs形成AST → ODT → AST的闭环。六、扩展把这份样例改造成更多场景基于上述 AST 语法可以轻松扩展出更多测试形态验证写入器的行为改动点语法示例预期输出变化跨行单元格(RowSpan 2) (ColSpan 1)输出table:number-rows-spanned2右对齐列( AlignRight , ColWidthDefault )单元格输出fo:text-alignend指定相对列宽( AlignDefault , ColWidth 0.5 )表格输出style:rel-width50%列输出style:rel-column-width行头列(RowHeadColumns 1)表体行首列使用表头样式输出表格标题(Caption Nothing [Plain [Str Cap]])在writerTableCaptionPosition指定位置输出TableCaption段落这些扩展点均可在 src/Text/Pandoc/Writers/OpenDocument.hs 与 data/odt/styles.xml 中找到直接对应的实现依据是理解 pandoc 表格子系统的最佳练习路径。结语test/command/10002.md虽然只是一个 200 行的测试文件却浓缩了 pandoc 表格处理的完整链路从 AST 的数据结构AnnotatedTable到写入器的样式注册与属性映射OpenDocument.hs再到参考样式表的父样式继承styles.xml最后以 golden test 的形式锁定行为。读懂它你就同时掌握了 pandoc 表格 AST、OpenDocument 表格 XML 与命令测试机制三块关键知识可以以此为模板为 pandoc 编写你自己的表格转换验证用例。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考