
降价表令人毛骨悚然:
- 您在单元格中写的文本不能超过几个单词,甚至不能少于一个列表。
- 如果方言允许使用第1款,则格式不便。
- 如果单元格未对齐,则无法读取表格。
- 不支持相同类型和自动化的表,例如行号。
现在该为Pandoc编写过滤器了,该过滤器从结构化的YAML中绘制表,并具有行编号,水平方向,图形模式,同时弄清楚如何编写Lua过滤器。
我通常使用Markdown编写文本,然后使用Pandoc转换为目标格式。 这是一个可以在格式之间转换文档的程序,例如,您可以从Markdown中获取HTML,MD,DOCX和PDF的另一种方言(超过30种输入和超过50种输出格式)。 Pandoc Markdown具有许多方便的扩展,用于链接,脚注,签名,公式。
Pandoc是功能的组合(可以用Haskell编写):特定的输入格式→文档的抽象表示→特定的输出格式。 可以使用Lua编写的过滤器来修改抽象表示。 过滤器不需要了解输出格式,但是可以将其考虑在内。
我们的过滤器将以抽象表示形式在条件语言table
中查找代码的抽象块,在其中读取YAML并生成Pandoc自身将以目标格式生成的表的抽象表示形式。
pandoc --lua-filter table.lua input.md -o output.html
有哪些替代方案,为什么会更糟?
- HTML表仅在Markdown中工作,并且只能转换为HTML; 仅解决了单元格中格式丰富的问题。
- 表生成器需要从文本编辑器切换;不方便编辑其中的单元格内容( 示例 )。
- 编辑器插件 ( Emacs Org-Mode , VIM插件 )不是通用的,并非始终可用。
相反, pandoc-crossref
和所有Pandoc包都与汇总表过滤器一起使用。 通过指定适当的输出格式,该过滤器还可用于生成标准的Markdown表。 缺点:
- 单元无法合并; Pandoc不支持此功能(尚未)。
- 对于水平表,必须使用输出格式(例如,通过CSS)进行样式化。
该表的描述包括三个部分:
表结构
图(列)的有序列表:
- 该列至少应有一个标题。
- 为了能够在不触摸数据的情况下重新排列列,必须指定列(
id
)中显示的记录属性。 - 特殊列没有ID,但是有关于如何填充它们的描述。 首先,您需要一个序列号(
special: number
)。 - 列对齐(
align
)。
而且,桌子可以是垂直的或水平的( orientation
)。 在后一种情况下,图形将是行。
表格属性:链接的ID( id
)和签名( caption
)。 Pandoc允许您签署表,但不能签署代码块。
YAML字典数组形式的数据 。
该结构对于多个表可能是通用的,因此您既可以直接与表一起描述它,也可以在元数据中(一次)描述它,然后引用命名的模板。
实施计划:
根据文档的元数据,我们形成模板字典。
对于带有类table
每个代码块:
- 我们解析YAML表。
- 如果指定了模板,则从字典中获取模板,否则从YAML填写模板。
- 我们从YAML填写表格的各个属性。
- 我们从YAML形成表格条目(记录是常规表格中的一行或水平表格中的一列)。
- 我们根据模板,属性和记录“绘制”表格。
上层是以书面形式实现的(所有代码都可以在文章结尾的链接上找到):
function Pandoc(doc) local meta_templates = doc.meta['table-templates'] if meta_templates then for name, value in pairs(meta_templates) do templates[name] = parse_template(value) end end local blocks = pandoc.walk_block(pandoc.Div(doc.blocks), { CodeBlock = create_table }) return pandoc.Pandoc(blocks, doc.meta) end
parse_template()
函数会稍微转换元数据格式。 Pandoc将它们的值表示为MetaBlock
和MetaInline
。 pandoc.utils.stringify()
函数(例如,方向)或视觉元素(例如,列标题中的一段文本)来组成简单的行。
关于调试。 Pandoc文档中有很多示例,但是类型不是很详细。 对于调试过滤器,使用变量转储功能很方便。 严肃的库打印了太多的细节,我更喜欢简单的选项之一 。
将元数据转换为文档元素的功能 local function to_inlines(content) if content == nil then return {} elseif type(content) == 'string' then return {pandoc.Str(content)} elseif type(content) == 'number' then return to_inlines(tostring(content)) elseif content.t == 'MetaInlines' then inlines = {} for i, item in ipairs(content) do inlines[i] = item end return inlines end end local function to_blocks(content) if (type(content) == 'table') and content.t == 'MetaBlocks' then return content else return {pandoc.Plain(to_inlines(content))} end end
在三元backtics中为每个代码块调用create_table()
函数。
我们只对table
“以语言显示”的代码块感兴趣:
if not contains('table', block.classes) then return block end
为了在代码块中解析YAML,我们创建一个仅包含YAML元数据的文档,使用Pandoc对其进行解析,仅保留元数据:
local meta = pandoc.read('---\n' .. block.text .. '\n---').meta
接下来,从meta
读取到模板或表结构和特定表的属性的链接。
fill_table()
函数从meta
数据中读取图形说明中指定的属性。 在同一阶段,如果将该列标记为特殊,则会生成其内容:
local data = {} for i, serie in ipairs(template.series) do if serie.special == 'number' then data[i] = to_blocks(#datum + 1) else data[i] = to_blocks(item[serie.id]) end end
format_table()
函数根据表的方向形成单元格的结果数组,并创建一个抽象表对象。 应该注意的是,如果应该为所有列设置宽度或标题,或者为所有列设置宽度或标题,否则Pandoc不会简单地创建表。
可以将完成的脚本放在~/.local/share/pandoc
( ~/.local/share/pandoc
数据目录 )中,以从任何位置按名称访问它。
聚苯乙烯
关于考虑输出格式过滤器。 例如,我在Pandoc中编写扰流器,如下所示:
::: {.spoiler title=""} . :::
Pandoc文档模型中没有破坏者,因此过滤器应大致按照以下方式生成原始块。 当然,实际代码( spoiler.lua
)应该通过FORMAT
变量考虑输出格式,而不是机械地考虑:尽管输出格式是markdown,但下面的片段会生成HTML原始块。
function Div(el) if not el.attr or not contains('spoiler', el.attr.classes) then return el end local title = el.attr.attributes['title'] or '' table.insert(el.content, 1, pandoc.RawBlock('html', '<' .. 'spoiler title="' .. title .. '">', 'RawBlock')) table.insert(el.content, pandoc.RawBlock('html', '<' .. '/spoiler>', 'RawBlock')) return el.content end
参考文献