使用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会使用默认模板进行渲染。
Template
全局变量可以保持未定义,在这种情况下,当Pandoc本应使用默认模板时,它将抛出一个错误。
示例:修改后的Markdown写入器
写入器可以访问Lua过滤器文档中描述的所有模块。这包括pandoc.write
,它可用于将文档渲染成Pandoc已支持的格式。在此转换之前可以修改文档,如下面的简短示例所示。它将文档渲染为GitHub风格的Markdown,但总是使用围栏式代码块,从不使用缩进式代码。
function Writer (doc, opts)
local filter = {
CodeBlock = function (cb)
-- only modify if code block has no attributes
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
。
所有预定义函数都可以在需要时被覆盖。
生成的写入器使用渲染函数来处理元数据值,并将它们转换为模板变量。如果提供了模板,它会自动应用。
经典风格
使用经典风格的写入器为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中进行了重做。出于技术原因,全局变量PANDOC_DOCUMENT
和PANDOC_WRITER_OPTIONS
分别被设置为空文档和默认值。通过添加以下代码片段可以恢复旧行为,该片段将经典风格写入器转换为新风格写入器。
function Writer (doc, opts)
PANDOC_DOCUMENT = doc
PANDOC_WRITER_OPTIONS = opts
loadfile(PANDOC_SCRIPT_FILE)()
return pandoc.write_classic(doc, opts)
end