我有一个可行的解决方案,但感觉很脆弱。我想让它变得更强大。
我想使用 pandoc 将 Markdown 文件转换为 HTML。我想在我的 Markdown 文件中使用
$title$
和 $content$
等变量,并让 pandoc 用它们的值替换这些占位符字符串。
使用 HTML 模板文件时,这是开箱即用的。您的模板文件将包含您需要的所有样板文件,但不包含实际内容。您可以在 template.html 文件的顶部创建一个 YAML 块,如下所示:
---
title: Page Title
...
您的 HTML 可能包含如下标签:
<title>$title$</title>
当 pandoc 将 Markdown 渲染为 HTML 时,输出的 HTML 文件将包含:
<title>Page Title</title>
但是,在包含 HTML 页面实际内容的 Markdown 文档中,有两个问题需要解决:
$text surrounded by dollar signs$
解释为数学。我对第一个问题的解决方案是使用
\$
来逃避 Markdown 中的美元符号。这是一个示例。
输入.md
---
header: Section Header
content: This is content.
...
# \$header\$
\$content\$
我对第二个问题的解决方案是使用filter。这是一个 JavaScript 文件,我可以用它来替换像
$header$
这样的变量,并使用顶部 YAML 块中为其指定的值(包括用于调试的代码):
替换.js
#!/usr/bin/env node
const { createWriteStream } = require('fs')
const logger = new console.Console(createWriteStream("./output.log"));
// Pandoc filter to convert all text to uppercase
var pandoc = require("pandoc-filter");
var Str = pandoc.Str;
function action({ t: type, c: value }, format, meta) {
log(arguments)
const keys = Object.keys(meta).reduce(keyMap, {})
if (type === "Str"){
const key = keys[value]
if (key) {
const replace = meta[key].c.reduce(replacer, "")
logKeys(keys, key, replace)
return Str(replace)
}
}
}
function keyMap( map, key ){
const cue = `$${key}$`
map[cue] = key
return map
}
function replacer(result, { t, c }) {
switch (t) {
case "Space":
return result + " "
case "Emph":
return result + `<em>${c[0].c}</em>`
default:
return result + c
}
}
pandoc.stdio(action);
// Useful for debugging
function log(args) {
const [{t: type, c: content}, format, meta] = args
logger.log(`type: "${type}"`)
if (content) {
logger.log(`typeof content: ${typeof content}
content: ${JSON.stringify(content)}`)
}
logger.log(`format: ${format}`)
logger.log(`meta: ${JSON.stringify(meta)}`)
logger.log("")
}
function logKeys(keys, key, replace) {
logger.log("keys:", keys)
logger.log(`${key} ==> ${replace}`)
logger.log("---")
}
这需要安装 pandoc-filter 模块。
npm install -g pandoc-filter
我的代码创建了一个名为
output.log
的文件,以便我可以看到 replace.js
中发生的情况。
当我跑步时...
pandoc input.md --filter replace.js -o output.html
...我在
output.html
文件中得到了我期望的 HTML:
<h1 id="header">Section Header</h1>
<p>This is content.</p>
但是,如果我将
content
的值更改为 This is *content*.
(“内容”用星号括起来以表示斜体),output.log
显示我期望的值...
content ==> This is <em>content</em>.
...但是在
output.html
中,尖括号已被字符实体取代。
<p>This is <em>content</em>.</p>
不仅如此,我的整个
replacer()
switch{}
声明也很笨拙。它只处理我特别选择的案例。
如何使我的
replacer.js
脚本更加通用和健壮?
发送到
value
函数的action
是一个“抽象语法树”(AST),而不是字符串。 action
函数不应返回字符串,而应返回:
undefined
,意思是“保持 AST 不变”pandoc 将使用 AST 的修改版本来生成输出。
这是
action
函数的更正版本。
function action({ t: type, c: value }, format, meta) {
// log(arguments)
const substitutes = Object.keys(meta).reduce(keyMap, {})
if (type === "Str"){
// value may be a key in substitutes like "$title$"
return substitutes[value] // undefined if no substitute
}
function keyMap( map, key ){
const cue = `$${key}$`
map[cue] = meta[key].c
return map
}
}
每次调用我的
input.md
,meta
的值为:
{
"content": {
"t": "MetaInlines",
"c": [
{
"t": "Str",
"c": "This"
},
{
"t": "Space"
},
{
"t": "Str",
"c": "is"
},
{
"t": "Space"
},
{
"t": "Emph",
"c": [
{
"t": "Str",
"c": "content"
}
]
},
{
"t": "Str",
"c": "."
}
]
},
"header": {
"t": "MetaInlines",
"c": [
{
"t": "Str",
"c": "Section"
},
{
"t": "Space"
},
{
"t": "Str",
"c": "Header"
}
]
}
}
substitutes
对象将$content$
映射到meta.content.c
处的数组
当使用
action()
等于 value
调用 $content$
时,action()
返回此 AST 数组。