请帮助乌克兰!
赞助商
Pandoc   一个通用文档转换器

使用Lua创建自定义Pandoc写入器

引言

如果你需要渲染Pandoc尚未处理的格式,或者想要改变Pandoc渲染某种格式的方式,你可以使用Lua语言创建自定义写入器。Pandoc内置了Lua解释器,因此你无需安装任何额外软件即可完成此操作。

自定义写入器是一个Lua文件,它定义了如何渲染文档。写入器必须只定义一个函数,名为WriterByteStringWriter,该函数会传入文档和写入器选项,然后处理文档的转换,将其渲染成字符串。此接口在Pandoc 2.17.2中引入,而ByteString写入器在Pandoc 3.0中可用。

Pandoc也支持“经典”自定义写入器,其中必须为每种AST(抽象语法树)元素类型定义一个Lua函数。经典风格的写入器已弃用,如果可能,应替换为新风格写入器。

写入器

使用新风格的自定义写入器必须包含一个名为WriterByteStringWriter的全局函数。Pandoc会以文档和写入器选项作为参数调用此函数,并期望该函数返回一个UTF-8编码的字符串。

function Writer (doc, opts)
  -- ...
end

不返回文本而是二进制数据的写入器应该改为定义一个名为ByteStringWriter的函数。该函数仍然必须返回一个字符串,但它不必是UTF-8编码的,并且可以包含任意二进制数据。

如果同时定义了WriterByteStringWriter函数,则只使用Writer函数。

格式扩展

写入器可以通过格式扩展进行定制,例如smartcitationshard_line_breaks。全局Extensions表通过键指示支持的扩展。默认启用的扩展被赋值为true,而那些受支持但被禁用的则被赋值为false。

示例:具有以下全局表的写入器支持扩展smartcitationsfoobar,其中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.BlockWriter.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.BlockWriter.Inline表可以作为函数使用;它们会为相应类型的元素应用正确的渲染函数。例如,Writer.Block(pandoc.Para 'x')将委托给Writer.Para渲染函数,并返回该调用的结果。

类似地,函数Writer.BlocksWriter.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_DOCUMENTPANDOC_WRITER_OPTIONS分别被设置为空文档和默认值。通过添加以下代码片段可以恢复旧行为,该片段将经典风格写入器转换为新风格写入器。

function Writer (doc, opts)
  PANDOC_DOCUMENT = doc
  PANDOC_WRITER_OPTIONS = opts
  loadfile(PANDOC_SCRIPT_FILE)()
  return pandoc.write_classic(doc, opts)
end