多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

pandoc RST 阅读器 csv-table 指令深度解析:基于 3533-rst-csv-tables 测试用例的源码级实践指南

pandoc RST 阅读器 csv-table 指令深度解析:基于 3533-rst-csv-tables 测试用例的源码级实践指南 pandoc RST 阅读器 csv-table 指令深度解析基于 3533-rst-csv-tables 测试用例的源码级实践指南【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc本文以 pandoc 仓库中的命令测试文件 test/command/3533-rst-csv-tables.md 为骨架结合 RST 阅读器与 CSV 解析器的真实实现源码系统讲解 reStructuredTextcsv-table指令在 pandoc 中的解析规则、全部选项语义与底层实现原理。读完本文你将能准确掌握:file:、:header:、:widths:、:delim:、:quote:、:escape:、:header-rows:等指令选项的用法与边界行为并理解 CSV 文本如何一步步被转换为 pandoc 内部的 Table AST。csv-table是 reStructuredText 文档体系中一种用逗号分隔数据生成表格的指令非常适合维护变更日志、价格表、结构化数据等场景。pandoc 在 RST 阅读器-f rst中完整支持该指令并通过三个精心设计的命令测试用例锁定了它的解析行为外部 CSV 文件加载与列宽归一化、自定义分隔符/引号字符、自定义转义字符。本文所有结论均可在 src/Text/Pandoc/Readers/RST.hs 与 src/Text/Pandoc/CSV.hs 中得到验证。一、测试文件概述一次命令测试如何锁定解析行为pandoc 的命令测试command test约定俗成地使用如下格式代码块内以%开头的是要执行的 pandoc 命令^D之前是标准输入内容^D之后是期望的标准输出。测试文件 test/command/3533-rst-csv-tables.md 一共包含三个测试块分别对应csv-table指令的三类典型配置测试块指令选项验证要点第一块:widths:、:header:、:file:外部 CSV 文件加载、显式表头、列宽归一化、多行字段第二块:header-rows:、:quote:、:delim:首行作表头、自定义引号与分隔符、引号内转义第三块:escape:自定义转义字符测试数据文件 test/command/3533-rst-csv-tables.csv 与测试文档同目录存放内容如下注意第二条记录是一个跨两行的多行字段Albatross, 2.99, On a stick! Crunchy Frog, 1.49, If we took the bones out, it wouldnt be crunchy, now would it?二、第一个用例:file:外部加载 :header:显式表头 :widths:列宽2.1 测试输入与期望输出第一个测试块完整内容如下% pandoc -f rst -t native .. csv-table:: Test :widths: 10, 5, 10 :header: Flavor,Price,Slogan :file: command/3533-rst-csv-tables.csv ^D [ Table ( , [] , [] ) (Caption Nothing [ Plain [ Str Test ] ]) [ ( AlignDefault , ColWidth 0.4 ) , ( AlignDefault , ColWidth 0.2 ) , ( AlignDefault , ColWidth 0.4 ) ] ...期望输出为 pandoc 的 native内部 AST 文本表示格式。将其与测试数据文件对照可以读出三个关键行为行为一ColWidth 0.4 / 0.2 / 0.4是10, 5, 10归一化的结果。:widths:的值是列宽整数序列pandoc 在解析时先将它们求和1051025再逐列除以总和得到 0.4、0.2、0.4 三个 0~1 之间的比例系数。源码中这一逻辑位于 csvTableDirectivelet strictPos w | w 0 ColWidth w | otherwise ColWidthDefault let normWidths ws strictPos . (/ max 1 (sum ws)) $ ws let widths case trim $ lookup widths fields of Just auto - replicate numOfCols ColWidthDefault Just specs - normWidths $ map (fromMaybe (0 :: Double) . safeRead) $ splitTextBy (elem ( , :: String)) specs _ - replicate numOfCols ColWidthDefault可见:widths:支持三种取值字面量auto所有列退化为ColWidthDefault由后续输出格式自行决定宽度、逗号/空格分隔的数字序列归一化为比例、缺省同样全部使用ColWidthDefault。行为二表头由:header:显式提供。Flavor,Price,Slogan三个字段被解析后成为TableHead中的三个单元格而不是把 CSV 文件的第一行当作表头。源码中显式表头与数据行是两个独立解析过程最终被拼接在一起见 csvTableDirectivelet header case explicitHeader of Just h - parseCSV defaultCSVOptions h Nothing - Right [] let res parseCSV opts rawcsv case () $ header * res of注意:header:行是用defaultCSVOptions即默认逗号分隔、双引号引用解析的不受:delim:、:quote:等选项影响而数据部分才使用用户自定义选项opts。行为三多行字段被保留为单元格内的SoftBreak。CSV 文件第二条记录的 Slogan 字段跨了两行If we took the bones out, it wouldnt be crunchy, now would it?在期望输出中该单元格成为[ Plain [ Str On , Space , Str a , Space , Str stick! ] ]以及第二条记录中的SoftBreak。这是因为底层 CSV 解析器允许未加引号的换行被吸收进带引号字段内部详见本文第五节而 parseCell 会把这个多行文本当作 RST 块重新解析parseCell :: PandocMonad m Text - RSTParser m Blocks parseCell t parseFromString parseBlocks (trim t \n\n)单元格内容因此保留内嵌换行语义pandoc 据此生成SoftBreak软换行而不是LineBreak或断开为多个段落。三、第二个用例:header-rows:首行作表头 自定义:delim:与:quote:3.1 测试输入与期望输出% pandoc -f rst -t native .. csv-table:: Test :header-rows: 1 :quote: :delim: space a b cats 3 4 dogs 2 3 ^D期望输出中TableHead第一列是空单元格[]后面是a、b两个表头单元格表体两行分别是cats 3 4与dogs 2 3。这里隐藏了三个值得注意的解析细节细节一:header-rows: 1把第一条数据记录提升为表头。源码 csvTableDirective 中headerRowsNum的默认值取决于是否显式提供了:header:——有则默认 1无则默认 0let headerRowsNum fromMaybe (case explicitHeader of Just _ - 1 :: Int Nothing - 0 :: Int) $ lookup header-rows fields safeRead随后按这个数值拆分首行见 csvTableDirectivelet (headerRow,bodyRows,numOfCols) case rows of x:xs - if headerRowsNum 0 then (x, xs, length x) else ([], rows, length x) _ - ([],[],0)结合 listTableDirective 的注释Only the first row becomes the header even if header-rows: 1, since Pandoc doesnt support a table with multiple header rows可以确认由于 pandoc 的Table类型只有一个TableHead即使指定:header-rows: 2也仍然只有首行进入表头——这是 pandoc 对 RST 语义的有意裁剪使用时应避免依赖多行表头。细节二:quote: 与:delim: space共同改变 CSV 语法。数据记录cats 3 4被解析为三列cats、3、4。其中是引号字符翻倍转义单引号内部遇到两个单引号表示一个字面单引号。选项到解析器的映射位于 csvTableDirectivecsvDelim case trim $ lookup delim fields of Just tab - \t Just space - Just (T.unpack - [c]) - c _ - , , csvQuote case trim $ lookup quote fields of Just (T.unpack - [c]) - Just c _ - Just , csvEscape case trim $ lookup escape fields of Just (T.unpack - [c]) - Just c _ - Nothing , csvKeepSpace case trim $ lookup keepspace fields of Just true - True _ - False:delim:的关键字只有tab和space两个传入其它单字符则直接作为分隔符例如 test/command/7064.md 中的:delim: $。:quote:与:escape:同样只接受单字符且:escape:缺省为Nothing——此时转义退化为引号翻倍规则。细节三空表头单元格被保留。第一行 a b的首列是空字符串期望输出中它成为一个空Cell[]块列表说明空字段不会被丢弃而是原样保留为单元格。四、第三个用例:escape:自定义转义字符% pandoc -f rst -t native .. csv-table:: Test :escape: \ 1,\ ^D输入只有一行两列1和\一个转义后的双引号字符。期望输出中第二个单元格为[ Plain [ Str \ ] ]即字面双引号。这个用例验证的是 CSV 解析器中转义字符的两种工作模式。相关实现位于 src/Text/Pandoc/CSV.hsescaped :: CSVOptions - Parser Char escaped opts case csvEscape opts of Nothing - case csvQuote opts of Nothing - mzero Just q - try $ char q char q Just c - try $ char c noneOf \r\n未指定:escape:默认表示一个字面双引号即引号翻倍这正是第二个用例中产生单引号的机制。指定:escape: \反斜杠后跟任意非换行字符即为一个转义序列\解析为字面。由于:escape:只接受单个字符\中的反斜杠不会出现在结果里。这一模式与 Python 标准库csv模块的escapechar参数行为一致方便从其它工具链迁移数据。五、底层实现Text.Pandoc.CSV 与 csvTableDirective 的完整调用链5.1 可配置的 CSVOptions 解析器pandoc 将 CSV 解析器独立为模块 src/Text/Pandoc/CSV.hs核心是一个基于 Parsec 的纯函数式解析器data CSVOptions CSVOptions{ csvDelim :: Char , csvQuote :: Maybe Char , csvKeepSpace :: Bool -- treat whitespace following delim as significant , csvEscape :: Maybe Char -- default is to double up quote } deriving (Read, Show) defaultCSVOptions :: CSVOptions defaultCSVOptions CSVOptions{ csvDelim , , csvQuote Just , csvKeepSpace False , csvEscape Nothing } parseCSV :: CSVOptions - Text - Either ParseError [[Text]] parseCSV opts t parse (pCSV opts) csv t解析规则pCSV依次为行由sepEndBy组合、单元格分为带引号与不带引号两种形态、分隔符之后默认吞掉多余空白csvKeepSpace为True时保留、行结束符兼容\r\n、\r、\n三种。带引号单元格内部允许出现未加引号的换行——这正是第一节多行 Slogan 字段能够成立的原因。5.2 指令分发与 Table 构造在 RST 阅读器中指令名到处理函数的映射位于 指令分发表table - tableDirective top fields body list-table - listTableDirective top fields body csv-table - csvTableDirective top fields body其中:file:与:url:选项通过fetchItem从磁盘或网络获取数据csvTableDirectiverawcsv - case trim $ lookup file fields mplus lookup url fields of Just u - do (bs, _) - fetchItem u return $ UTF8.toText bs Nothing - return rawcsv这也解释了测试用例中:file: command/3533-rst-csv-tables.csv为何使用相对于测试运行目录的路径——该路径会被当作资源 URI 交给fetchItem解析。解析完成的[[Text]]经过逐单元格 RST 块解析parseCell后最终由B.table与compactifyTable组装为 pandoc 的TableASTcsvTableDirectivereturn $ compactifyTable $ B.table (B.simpleCaption $ B.plain title) (zip (replicate numOfCols AlignDefault) widths) (TableHead nullAttr $ toHeaderRow headerRow) [TableBody nullAttr 0 [] $ map toRow bodyRows] (TableFoot nullAttr [])所有单元格的对齐方式固定为AlignDefault列宽来自:widths:的归一化结果标题文本则来自指令参数行.. csv-table:: Test中的Test并作为表格的Caption。六、横向印证仓库中的其它 csv-table 测试除 3533 外仓库还有多个 csv-table 命令测试可以从不同角度印证上述解析规则test/command/6549.md.. csv-table:: Test table配合:file: command/01.csv、:delim: ;、:header-rows: 1期望输出为 HTML。其中第二个单元格data1\ndata2被渲染成ul列表直观展示了parseCell单元格内再次按 RST 块解析的行为——单元格里可以容纳多段落、列表等复杂块结构。test/command/7064.md.. csv-table:: Changes配合:header:、:widths: 15, 15, 70、:delim: $输出 HTML 的colgroup中列宽为 15%/15%/70%验证了数值序列 → 归一化比例 → 百分比宽度的完整链路同时也给出了一种用非逗号分隔符维护变更日志的实用模板。test/command/7112.md.. csv-table::无标题、无表头单元格echo PATHpath中的反引号被解析为行内代码说明指令参数标题和单元格文本都会走正常的 RST 行内标记解析而不是纯文本透传。七、实践要点与注意事项汇总结合上述测试与源码在 pandoc 中使用csv-table指令时建议注意以下几点表头二选一要么用:header:显式声明要么用:header-rows: 1取首行两者同时出现时:header:在前、headerRowsNum的默认值为 1但源码实现中:header:指定的行总是先被拼接实际使用时以明确指定其一为宜。列宽是比例而非像素:widths: 10, 5, 10最终表现为 0.4/0.2/0.4 的比例系数auto与缺省均表示交给输出格式决定ColWidthDefault。file/url是资源 URI相对路径按运行目录解析文件内容按 UTF-8 读取。引号与转义默认用双引号引用、引号翻倍转义:quote:与:escape:都只接受单字符escape一旦设置就取代翻倍规则。分隔符后的空白默认会被跳过csvKeepSpaceFalse若数据中分隔符后的空格有意义可设置:keepspace: true。单元格是小块文档单元格文本会再次经过 RST 块级与行内解析因此可以安全包含行内标记、列表乃至多段落但多行表头不被支持。通过对 3533-rst-csv-tables.md 及其配套数据文件、RST 阅读器 与 CSV 解析器 源码的逐行对照可以看到 pandoc 对 RSTcsv-table指令的支持是标准语法 明确取舍的工程实现完整覆盖了 reStructuredText 规范中delim、quote、escape、keepspace、header、header-rows、widths、file等核心选项同时依据自身 Table AST 的约束将多行表头收敛为首行表头、将列宽统一为归一化比例。理解这些规则你在编写 RST 文档、维护 CSV 数据或调试转换结果时就能准确预判 pandoc 的输出。【免费下载链接】pandocUniversal markup converter项目地址: https://gitcode.com/gh_mirrors/pa/pandoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表