在 Lua 中创建自定义 Pandoc 写入器
引言
如果你需要渲染 Pandoc
尚不支持的格式,或者想要改变 Pandoc
渲染某种格式的方式,你可以使用 Lua 语言创建一个自定义的写入器。Pandoc
内置了 Lua
解释器,因此无需安装任何额外软件来实现这一点。
自定义写入器是一个 Lua
文件,它定义了如何渲染文档。写入器必须定义一个名为 Writer
或 ByteStringWriter
的单一函数,该函数会接收文档和写入器选项,然后处理文档转换,将其渲染成字符串。这种接口在 Pandoc 2.17.2
中引入,而 ByteString
写入器则在 Pandoc 3.0
中可用 。
Pandoc
还支持“经典”风格的自定义写入器,其中必须为每种 AST
元素类型定义一个 Lua
函数。经典风格的写入器已不推荐使用,如果可能的话,应替换为新风格的写入器。
写入器
使用新风格的自定义写入器必须包含一个名为 Writer
或 ByteStringWriter
的全局函数。Pandoc
调用此函数并以文档和写入器选项作为参数,并期望函数返回一个 UTF-8
编码的字符串。
function Writer (doc, opts)
-- ...
end
不返回文本而是返回二进制数据的写入器应该定义一个名为 ByteStringWriter
的函数。该函数仍然必须返回一个字符串,但它不需要是 UTF-8
编码的,可以包含任意二进制数据。
如果同时定义了 Writer
和 ByteStringWriter
函数,那么只会使用 Writer
函数 。
格式扩展
写入器可以通过格式扩展进行定制,如 smart
、citations
或 hard_line_breaks
。全局的 Extensions
表通过键指示支持的扩展。默认启用的扩展被赋予 true
值,而那些支持但默认禁用的扩展被赋予 false
值。
示例:具有以下全局表的写入器支持 smart
、citations
和 foobar
扩展,其中 smart
启用,其余默认禁用:
Extensions = {
smart = true,
citations = false,
foobar = false
}
用户如常控制扩展,例如,pandoc -t my-writer.lua+citations
。扩展可通过写入器选项的 extensions
字段访问,例如:
function Writer (doc, opts)
print(
'The citations extension is',
opts.extensions:includes 'citations' and 'enabled' or 'disabled'
)
-- ...
end
默认模板
自定义写入器的默认模板由全局函数 Template
的返回值定义。当用户未指定模板但以 -s
/ --standalone
标志调用 Pandoc 时,Pandoc 会使用默认模板进行渲染。
Template
全局可以留空,此时 Pandoc 在原本会使用默认模板的情况下会抛出错误。
示例:修改过的 Markdown 写入器
写入器可以访问 Lua 过滤器文档 中描述的所有模块。这包括 pandoc.write
,它可以用来以 Pandoc 已支持的格式渲染文档。在转换之前可以修改文档,如下例所示。它将文档渲染为 GitHub 风味的 Markdown,但总是使用围栏代码块,从不使用缩进代码块。
function Writer (doc, opts)
local filter = {
CodeBlock = function (cb)
-- 仅在代码块没有属性时修改
if cb.attr == pandoc.Attr() then
local delimited = '```\n' .. cb.text .. '\n```'
return pandoc.RawBlock('markdown', delimited)
end
end
}
return pandoc.write(doc:walk(filter), 'gfm', opts)
end
Template = pandoc.template.default 'gfm'
使用 pandoc.scaffolding.Writer
减少样板代码
pandoc.scaffolding.Writer
结构是一个自定义写入器的脚手架,旨在定义自定义写入器时避免常见的样板代码。该对象可用作函数,并允许跳过元数据和模板处理等细节,只需要为每种 AST 元素类型添加渲染函数。
pandoc.scaffolding.Writer
的值是一个通常应分配给全局 Writer
的函数:
Writer = pandoc.scaffolding.Writer
对于 Block
和 Inline
值的渲染函数可以分别添加到 Writer.Block
和 Writer.Inline
中。这些函数会接收元素和 WriterOptions。
Writer.Inline.Str = function (str)
return str.text
end
Writer.Inline.SoftBreak = function (_, opts)
return opts.wrap_text == "wrap-preserve"
and cr
or space
end
Writer.Inline.LineBreak = cr
Writer.Block.Para = function (para)
return {Writer.Inlines(para.content), pandoc.layout.blankline}
end
渲染函数必须返回一个字符串、一个 pandoc.layout *Doc*
元素,或这类元素的列表。在后者的情况下,这些值会被连接起来,就像它们被传递给 pandoc.layout.concat
一样。如果值不依赖于输入,也可以使用常量。
表 Writer.Block
和 Writer.Inline
可以用作函数;它们会为相应类型的元素应用正确的渲染函数。例如,Writer.Block(pandoc.Para 'x')
会委托给 Writer.Para
渲染函数,并返回该调用的结果。
类似地,函数 Writer.Blocks
和 Writer.Inlines
可用于渲染元素列表,而 Writer.Pandoc
则用于渲染文档的块。函数 Writer.Blocks
可以接受一个可选的第二个参数作为分隔符,例如,Writer.Blocks(blks, pandoc.layout.cr)
;默认的块分隔符是 pandoc.layout.blankline
。
所有预定义的函数都可以根据需要进行覆盖。
生成的 Writer 使用渲染函数来处理元数据值,并将它们转换为模板变量。如果有提供模板,则会自动应用。
经典样式
使用经典样式的 writer
为 pandoc AST
的每个元素定义了渲染函数。请注意,此风格已被弃用,可能会在以后的版本中移除。
例如,
function Para(s)
return "<paragraph>" .. s .. "</paragraph>"
end
模板变量
可以通过从 Doc
函数返回第二个值来添加新的模板变量或修改现有变量。
例如,以下代码将在变量 date
中添加当前日期,除非 date
已经被定义为元数据值或变量:
function Doc (body, meta, vars)
vars.date = vars.date or meta.data or os.date '%B %e, %Y'
return body, vars
end
pandoc 3.0 中的变化
自 pandoc 3.0
起,自定义 writer
进行了重做。由于技术原因,全局变量 PANDOC_DOCUMENT
和 PANDOC_WRITER_OPTIONS
被设置为空文档和默认值,分别。要恢复旧的行为,可以添加以下代码片段,这将把一个经典的 writer
转换为新式 writer
。
function Writer (doc, opts)
PANDOC_DOCUMENT = doc
PANDOC_WRITER_OPTIONS = opts
loadfile(PANDOC_SCRIPT_FILE)()
return pandoc.write_classic(doc, opts)
end