ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Pandoc JSON 过滤器(Filters)完全指南:AST 原理、Haskell/Python 实现与 CLI 技术细节

Pandoc JSON 过滤器(Filters)完全指南:AST 原理、Haskell/Python 实现与 CLI 技术细节 文档开发工具CLI【免费下载链接】pandocUniversal markup converter项目地址https://gitcode.com/gh_mirrors/pa/pandoc点击查看免费下载本指南基于 Pandoc 官方文档 doc/filters.md 编写并结合本仓库源码如 src/Text/Pandoc/Filter.hs、src/Text/Pandoc/Filter/JSON.hs、src/Text/Pandoc/App.hs进行深度扩充。阅读完本文你将掌握 Pandoc 过滤器Filter的工作原理、AST抽象语法树的基本结构能够用 Haskell 或 Python 编写可投入实战的 JSON 过滤器并理解--filter选项的底层调用链、环境变量与解释器推断规则。Summary过滤器在 Pandoc 转换链路中的位置Pandoc 是一个通用文档格式转换器由一组reader读取器和writer写入器构成。转换文档时文本先由 reader 解析为 Pandoc 的中间表示——一个抽象语法树ASTAbstract Syntax Tree再交给 writer 输出为目标格式。AST 的数据结构由pandoc-types包中的Text.Pandoc.Definition模块定义本仓库中 Pandoc 主类型入口可见于 src/Text/Pandoc.hs。过滤器filter就是一段在 reader 与 writer 之间修改 AST 的程序转换流程可以抽象为INPUT --reader-- AST --filter-- AST --writer-- OUTPUTPandoc 支持两类过滤器Lua 过滤器使用 Lua 语言定义对 AST 的变换其解释器内嵌于 pandoc 二进制中无需安装任何外部软件且通常比 JSON 过滤器更快。详见仓库文档 doc/lua-filters.md。JSON 过滤器本文的主角。它是从标准输入读取、向标准输出写入的管道程序消费和产出的都是 pandoc AST 的 JSON 表示source format ↓ (pandoc) ↓ JSON-formatted AST ↓ (JSON filter) ↓ JSON-formatted AST ↓ (pandoc) ↓ target formatLua 过滤器拥有无需外部依赖、速度更快的优势但 JSON 过滤器可以用任意编程语言编写如果你想用 Lua 之外的语言实现变换逻辑JSON 过滤器是首选方案。JSON 过滤器可以直接放进 shell 管道中使用pandoc -s input.txt -t json | \ pandoc-citeproc | \ pandoc -s -f json -o output.html但更便捷的方式是使用--filter选项由 pandoc 自动完成管道铺设pandoc -s input.txt --filter pandoc-citeproc -o output.html从源码看--filter选项的处理集中在 src/Text/Pandoc/App.hs读取输入后applyFilters会被串联进转换管道readInput applyFilters ...见第 304-316 行即在 reader 解析完成后、writer 输出之前依次执行所有过滤器。而 src/Text/Pandoc/Filter.hs 定义了三种过滤器类型LuaFilter、JSONFilter和内建的CiteprocFilter其中applyFilters通过foldM让多个过滤器按顺序依次作用于同一份文档。A simple example为什么要用过滤器而不是正则表达式假设你想把 markdown 文档中所有二级及以上标题替换为普通段落并将文字改为斜体。第一反应是使用正则表达式perl -pe s/^## (.*)$/\*\1\*/ source.txt但这种方式在大多数场景下可行却存在大量边界问题ATX 标题可能以不属于标题文本的#序列结尾## My heading ##HTML 注释或围栏代码块中的行也可能以##开头它们不应被修改!-- ## This is just a comment -- ~~~~ ### A third level heading in standard markdown ~~~~Setext 风格的二级标题也需要处理A heading ---------无法可靠地判断添加星号是否会产生斜体如果字符串本身已被星号包围会意外变成粗体如果包含未转义的星号结果也不可控。要让正则表达式覆盖所有这些情况复杂度将急剧上升。更优的思路是让 pandoc 负责解析然后在文档写出前修改 AST——这正是过滤器擅长的事。用 native 输出观察 AST要查看 pandoc 解析文本后生成的 AST可以使用native输出格式% cat test.txt ## my heading text with *italics* % pandoc -s -t native test.txt Pandoc (Meta {unMeta fromList []}) [Header 2 (my-heading,[],[]) [Str My,Space,Str heading] , Para [Str text,Space,Str with,Space,Emph [Str italics]] ]可以看到一份Pandoc文档由Meta块存放标题、作者、日期等元数据和一组Block元素构成。上例中有两个BlockHeader标题和Para段落每个元素的内容又是一组Inline元素。Header 2的第一个参数2即标题层级第二个参数(my-heading,[],[])是标识符、类、键值对三元组第三个参数是标题内的行内内容。用 Haskell 编写第一个 JSON 过滤器利用Text.Pandoc.JSON模块可以用 Haskell 写一个把level 2的Header替换为包含Emph行内元素的Para的过滤器#!/usr/bin/env runhaskell -- behead.hs import Text.Pandoc.JSON main :: IO () main toJSONFilter behead behead :: Block - Block behead (Header n _ xs) | n 2 Para [Emph xs] behead x xtoJSONFilter做了两件事将behead类型为Block - Block提升为对整个PandocAST 的变换自动遍历 AST 并逐个变换每个 block用必要的 JSON 序列化/反序列化包装这个Pandoc - Pandoc变换生成一个从 stdin 消费 JSON、向 stdout 产出 JSON 的可执行程序。使用方法先赋予可执行权限再通过--filter调用chmod x behead.hspandoc -f SOURCEFORMAT -t TARGETFORMAT --filter ./behead.hs前提条件本机包仓库中需安装pandoc-types。使用 cabal-install 可通过cabal v2-update cabal v2-install --lib pandoc-types --package-env .完成。也可以把过滤器编译为二进制ghc -package-envdefault --make behead.hs pandoc -f SOURCEFORMAT -t TARGETFORMAT --filter ./behead注意如果过滤器已放入系统 PATH则无需前面的./命令行中可以出现多个--filter实例过滤器会按顺序依次应用——这一点与 src/Text/Pandoc/Filter.hs 中applyFilters使用foldM按序折叠的实现完全一致。LaTeX for WordPress针对目标格式的数学公式改写WordPress 博客要求特殊的 LaTeX 数学公式格式不是$emc^2$而是$LaTeX emc^2$。如何转换 markdown 文档用正则同样不可靠——$可能是货币符号也可能出现在注释、代码块或行内代码中。但我们只关心开启 LaTeX 数学的那对$。幸运的是pandoc 已经帮你把 LaTeX 数学提取成了Math元素因此过滤器可以这样写#!/usr/bin/env runhaskell -- wordpressify.hs import Text.Pandoc.JSON main toJSONFilter wordpressify where wordpressify (Math x y) Math x (LaTeX y) wordpressify x x这里省略了类型签名意在展示 Haskell 编写过滤器可以非常简洁。But I dont want to learn Haskell!用 Python 编写过滤器用 Haskell 写过滤器最正统但使用 Python 的pandocfilters包同样非常容易。该包发布在 PyPI 上安装方式pip install pandocfilters或easy_install pandocfilters斩首过滤器的 Python 版本#!/usr/bin/env python Pandoc filter to convert all level 2 headings to paragraphs with emphasized text. from pandocfilters import toJSONFilter, Emph, Para def behead(key, value, format, meta): if key Header and value[0] 2: return Para([Emph(value[2])]) if __name__ __main__: toJSONFilter(behead)toJSONFilter(behead)会遍历 AST 并对每个元素应用behead动作若behead返回空None节点保持不变若返回一个对象节点被替换若返回一个列表新列表会被拼接进原位置。本示例虽未用到format和meta参数但它们分别提供目标格式与文档元数据的访问能力是编写格式感知过滤器的重要入口。pandocfilters仓库中有大量 Python 过滤器示例追求更Pythonic的替代方案可以关注panflute库。pandocfilters的思想也已被移植到多种语言PHP、Perl、TypeScript/JavaScriptNode.js 生态中的pandoc-filter、node-pandoc-filter、Groovy、Rubyparu等你完全可以选择自己熟悉的语言。另外从 pandoc 2.0 起pandoc 内建了对 Lua 过滤器的支持——Lua 解释器内置于 pandoc因此运行 Lua 过滤器不需要任何额外软件。相关教程见仓库文档 doc/lua-filters.md以及 pandoc-lua-engine/src/Text/Pandoc/Lua/Engine.hs 中的引擎实现。Include files带 IO 的过滤器读取文件内容此前的变换都不涉及 IO。下面这个脚本会读取 markdown 文档找到带include属性的行内代码块并用指定文件的内容替换其内容#!/usr/bin/env runhaskell -- includes.hs import Text.Pandoc.JSON import qualified Data.Text.IO as TIO import qualified Data.Text as T doInclude :: Block - IO Block doInclude cb(CodeBlock (id, classes, namevals) contents) case lookup (T.pack include) namevals of Just f - CodeBlock (id, classes, namevals) $ TIO.readFile (T.unpack f) Nothing - return cb doInclude x return x main :: IO () main toJSONFilter doInclude用下面的输入测试Heres the pandoc README: ~~~~ {includeREADME} this will be replaced by contents of README ~~~~toJSONFilter也支持类型为Block - IO Block的变换函数——这正是它在 src/Text/Pandoc/Filter/JSON.hs 中被设计为在MonadIO上下文中执行apply :: MonadIO m ...的原因过滤器进程本身就运行在具备 IO 能力的外部管道中。Removing links返回列表的变换函数如果想移除文档中的所有链接但保留链接文本#!/usr/bin/env runhaskell -- delink.hs import Text.Pandoc.JSON main toJSONFilter delink delink :: Inline - [Inline] delink (Link _ txt _) txt delink x [x]注意delink不能是Inline - Inline类型因为用来替换链接的不是一个Inline元素而是一列Inline。因此我们让它成为Inline到Inline列表的函数。toJSONFilter依然能将其提升为Pandoc - Pandoc的变换——这与 Python 版返回列表即拼接的语义是对应的。A filter for ruby text一个真实的实战案例这是来自 pandoc-discuss 邮件列表的真实案例。用户 Qubyte 希望把日语笔记转换为排版良好的 HTML 和 (Xe)LaTeX在 HTML5 中ruby为汉字注音将假名标在汉字上方或侧边已是标准特性WebKit 系浏览器完整支持不支持它的浏览器如当时的 Firefox也会优雅降级——把注音放在字符侧边的括号里这对其他输出格式也适用而 (Xe)LaTeX 则完全不受 ruby 影响。此前他靠内联 HTML 实现非常繁琐例如rubyごrt/rt飯rp/rprtはん/rtrp/rp/ruby这段代码把ご飯gohan中的はんhan标注在第二个字符上方浏览器不支持时则以括号形式显示在右侧。他想要更省键的写法比如rはん。社区的解决方案是采用约定URL 以连字符-开头的 markdown 链接被解释为 ruby 注音はん对应的 Haskell 过滤器{-# LANGUAGE OverloadedStrings #-} -- handleruby.hs import Text.Pandoc.JSON import System.Environment (getArgs) import qualified Data.Text as T handleRuby :: Maybe Format - Inline - Inline handleRuby (Just format) x(Link attr [Str ruby] (src,_)) case T.uncons src of Just (-,kanji) | format Format html - RawInline format $ ruby kanji rp(/rprt ruby /rtrp)/rp/ruby | format Format latex - RawInline format $ \\ruby{ kanji }{ ruby } | otherwise - Str ruby _ - x handleRuby _ x x main :: IO () main toJSONFilter handleRuby这里体现了Maybe Format的妙用当脚本通过--filter调用时pandoc 会把目标格式作为第一个参数传给脚本若函数第一个参数类型是Maybe FormattoJSONFilter会自动将其赋值为Just目标格式或Nothing。编译并运行# first, make sure pandoc-types is installed: cabal install --lib pandoc-types --package-env . ghc --make handleRuby% pandoc -F ./handleRuby -t html はん ^D pruby飯rp(/rprtはん/rtrp)/rp/ruby/p % pandoc -F ./handleRuby -t latex はん ^D \ruby{飯}{はん}若要通过 LaTeX 生成 PDF需使用--pdf-enginexelatex指定包含日文字符的mainfont例如 Noto Sans CJK JP并在模板或 header-includes 中加入\usepackage{ruby}。Exercises巩固练习将 markdown 文档中所有常规文本改为全大写不得改动 URL 或链接标题中的文本。移除文档中所有的水平分割线horizontal rules。用罗马数字重新编号所有枚举列表。把每个dot类的围栏代码块替换为对代码块内容运行dot -Tpnggraphviz生成的图片。找出所有python类的代码块用 python 解释器运行它们并把结果打印到控制台。这些练习覆盖了过滤器的核心能力行内元素变换1、3、块级元素增删2、跨进程调用与资源生成4、5是检验你是否真正掌握 AST 变换思维的好方法。Technical details of JSON filtersJSON 过滤器的技术细节JSON 过滤器本质上是任何能够消费并产出合法 pandoc JSON 文档表示的程序。本节描述调用过滤器的具体技术约定均与 src/Text/Pandoc/Filter/JSON.hs 中externalFilter的实现一一对应。Arguments唯一参数是目标格式程序被调用时总是以目标格式作为唯一参数。例如pandoc --filter demo --tohtml会让 pandoc 以参数html调用程序demo。在源码中这一参数来自applyFilters ... [T.unpack format]见 src/Text/Pandoc/App.hs 第 308 行最终传入externalFilter的args。Environment variables调用前注入的环境变量Pandoc 在调用过滤器前会设置额外环境变量PANDOC_VERSION: 处理文档所用 pandoc 二进制的版本号。例如2.11.1。源码中取自pandocVersionText见 src/Text/Pandoc/Filter/JSON.hs 第 69 行。PANDOC_READER_OPTIONS: 传入输入解析器的选项的 JSON 对象表示源码中由envReaderOptions编码而来。对象字段如下abbreviations : 已知缩写集合字符串数组。 columns : 终端列数整数。 default-image-extension : 图片的默认扩展名字符串。 extensions : 语法扩展位域的整数表示。 indented-code-classes : 缩进代码块的默认类字符串数组。 standalone : 输入是否为带文档头header的独立文档true 或 false。 strip-comments : 是否剥离 HTML 注释而非将其解析为原始 HTMLtrue 或 false。 tab-stop : tab 停止位的宽度等价空格数整数。 track-changes : docx 的修订跟踪设置取值为 accept-changes、reject-changes、all-changes 之一。读者可能注意到PANDOC_READER_OPTIONS的字段与--columns、--tab-stop、--indented-code-classes、--default-image-extension、--strip-comments、--track-changes等 CLI 选项一一对应——过滤器可通过它感知解析阶段的上下文这在编写依赖解析选项的过滤器时非常有用。Supported interpreters非可执行文件时的解释器推断传给--filter/-F的文件预期是可执行文件。但若可执行位未设置pandoc 会尝试根据文件扩展名猜测合适的解释器。源码中的映射如下见 src/Text/Pandoc/Filter/JSON.hs 第 50-61 行文件扩展名解释器.pypython.hsrunhaskell.plperl.rbruby.phpphp.jsnode.rRscript实现细节上如果文件存在且可执行pandoc 会以./前缀直接运行它否则按上表把解释器作为程序名、把过滤文件路径作为其第一个参数来执行。若最终找不到可执行程序会抛出PandocFilterError错误信息形如Could not find executable ...。此外pandoc 通过pipeProcess将编码后的 JSON 文档写入过滤器进程的标准输入并从其标准输出读取结果后以eitherDecode反序列化回Pandoc若过滤器以非零状态退出同样会抛出PandocFilterError提示Filter returned error status。这一整套机制保证了过滤器可以平滑地集成到 pandoc 的转换主流程中。延伸阅读本仓库 Lua 过滤器完整教程doc/lua-filters.md过滤器调度核心实现src/Text/Pandoc/Filter.hsJSON 过滤器外部进程实现解释器推断、环境变量、管道通信src/Text/Pandoc/Filter/JSON.hs过滤器运行环境reader/writer 选项封装src/Text/Pandoc/Filter/Environment.hs过滤器在转换管道中的调用位置src/Text/Pandoc/App.hs命令行补全中--filter的声明test/command/completion.mdPandoc 主类型与 API 入口src/Text/Pandoc.hs系统级手册页含--filter说明pandoc-cli/man/pandoc.1赞分享文档开发工具CLI【免费下载链接】pandocUniversal markup converter项目地址https://gitcode.com/gh_mirrors/pa/pandoc点击查看免费下载相关推荐如何用Alpaca-LoRA实现高效LLM微调完整指南与实践案例如何用Alpaca LoRA实现高效LLM微调完整指南与实践案例 Alpaca LoRA是一个基于低秩适应LoRA技术的LLaMA模型微调项目能够在消费微调LoRA大模型Hubot机器人实现原理与技术细节解析Hubot机器人实现原理与技术细节解析 引言 Hubot作为一款优秀的聊天机器人框架其内部实现机制值得深入探讨。本文将系统性地剖析Hubot的核心实现原理包后端交互助手RoundedImageView核心实现原理与技术细节RoundedImageView核心实现原理与技术细节 RoundedImageView是一个专业的Android圆角图片处理库其核心实现基于RoundedD移动开发UI组件上一篇Qbot量化交易框架AI驱动的本地化投资研究平台技术解析与部署实践下一篇如何实现OBS Studio智能追踪开发者的完整插件开发指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表