- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
导读
本文以 pandoc 仓库中的命令行测试用例 test/command/256.md 为核心线索,深入讲解 pandoc 的缩写(abbreviation)识别机制:读者将掌握--abbreviations选项的完整用法、内置缩写表与自定义缩写文件的区别、缩写如何影响 Markdown 阅读器在句间空格上的处理(非断行空格\160),以及如何在 LaTeX/PDF 输出中避免因句号被误判为句尾而产生的多余间距问题。文中所有结论均可通过仓库源码与测试用例逐一验证。
一、测试用例全景:一个段落,两种输出
test/command/256.md用同一个输入句子,对比了启用自定义缩写文件与使用默认内置缩写表两种场景下 Markdown 阅读器的原生 AST 输出:
输入文本:
Foo. bar baz h.k. and e.g. and Mr. Brown.场景一:显式指定自定义缩写文件
% pandoc --abbreviations=command/abbrevs -t native其输出为:
[ Para [ Str "Foo.\160bar" , Space , Str "baz" , Space , Str "h.k.\160and" , Space , Str "e.g." , Space , Str "and" , Space , Str "Mr." , Space , Str "Brown." ] ]场景二:不指定--abbreviations,使用默认行为
% pandoc -t native其输出为:
[ Para [ Str "Foo." , Space , Str "bar" , Space , Str "baz" , Space , Str "h.k." , Space , Str "and" , Space , Str "e.g.\160and" , Space , Str "Mr.\160Brown." ] ]对比两段 AST 可以得出一个清晰的结论:
- 在场景一中,
Foo.与h.k.被识别为缩写,其后紧跟的空格被替换为非断行空格(AST 中表现为Str "Foo.\160bar"这类字符串内含\160字符);而e.g.、Mr.未被识别,保留普通Space; - 在场景二中正好相反:
e.g.与Mr.被识别为缩写,Foo.与h.k.则被当作普通单词加句号处理。
也就是说,测试文件模拟的自定义command/abbrevs文件包含Foo.和h.k.而不包含e.g.与Mr.,而 pandoc 内置的默认缩写表中恰好包含e.g.、Mr.却不包含Foo.、h.k.。两次测试从正反两个方向验证了缩写识别完全由缩写表驱动。
二、缩写识别的实现原理:Markdown 阅读器中的源码依据
2.1 缩写表从哪里来:readAbbreviations
--abbreviations选项的作用链在源码中清晰可见。命令行的--abbreviations FILE选项在 src/Text/Pandoc/App/CommandLineOptions.hs#L719-L725 中解析:
, option "" ["abbreviations"] (ReqArg (\arg opt -> return opt { optAbbreviations = Just $ normalizePath arg }) "FILE") Files (T.pack "File with abbreviations")而缩写表的实际加载由 src/Text/Pandoc/App.hs#L376-L384 中的readAbbreviations完成:
-- | Retrieves the set of abbreviations to be used by pandoc. These currently -- only affect the Markdown reader. readAbbreviations :: PandocMonad m => Maybe FilePath -> m (Set.Set Text) readAbbreviations mbfilepath = (case mbfilepath of Nothing -> readDataFile "abbreviations" Just f -> readFileStrict f) >>= fmap (Set.fromList . filter (not . T.null) . T.lines) . toTextM (fromMaybe mempty mbfilepath)这段实现揭示了两个关键事实:
- 未指定
--abbreviations时,pandoc 会读取随程序分发的数据文件data/abbreviations(仓库中的 data/abbreviations); - 指定时,则严格读取用户提供的文件,两者互斥——这也是测试 256.md 中两次输出行为迥异的根本原因。
2.2 缩写如何影响空格:str解析器
缩写识别的核心逻辑位于 Markdown 阅读器的str解析器,src/Text/Pandoc/Readers/Markdown.hs#L1799-L1821:
str :: PandocMonad m => MarkdownParser m (F Inlines) str = do !result <- mconcat <$> many1 ( takeWhile1P isAlphaNum <|> "." <$ try (char '.' <* notFollowedBy (char '.')) ) updateLastStrPos (do guardEnabled Ext_smart abbrevs <- getOption readerAbbreviations if result `Set.member` abbrevs then try (do ils <- whitespace notFollowedBy (() <$ cite <|> () <$ note) -- ?? lookAhead alphaNum -- replace space after with nonbreaking space -- if softbreak, move before abbrev if possible (#4635) return $ do ils' <- ils case B.toList ils' of [Space] -> return $! (B.str result <> B.str "\160") _ -> return $! (B.str result <> ils')) <|> return (return $! B.str result) else return (return $! B.str result)) <|> return (return $! B.str result)从中可以提炼出完整的判定流程:
- 解析器先累积一个由字母数字与单个句点组成的“词”(注意
notFollowedBy (char '.')保证...省略号不会被拆散); - 仅在启用
smart扩展(Ext_smart)时才会进入缩写判定分支; - 将该词与缩写表
readerAbbreviations(类型为Set.Set Text,见 src/Text/Pandoc/Options.hs#L72)做成员匹配; - 命中缩写后,再查看其后的空白:若后面恰好是一个普通空格(
Space),则把该空格替换为\160(非断行空格,NBSP),从而得到Str "Foo.\160bar"这样的输出;若后面是换行等其他内容,则保持原样; - 同时使用
notFollowedBy避免在引用(cite)或脚注(note)紧随其后的场景下误替换。
这正是测试 256.md 中 AST 字符串里出现\160的来源——\160即 Unicode 字符 U+00A0(NO-BREAK SPACE)在 Haskell 转义中的写法。
三、默认缩写表:data/abbreviations的内容与用途
当用户不提供--abbreviations时,pandoc 从数据目录读取 data/abbreviations。该文件每行一个缩写,共 80 条左右,覆盖了英文写作中常见的缩写形式,例如:
aet. aetat. al. Apr. Aug. bk. Bros. c. Capt. cf. ch. chap. chs. Co. col. Corp. cp. d. Dec. Dr. e.g. ed. eds. esp. f. fasc. Feb. ff. fig. fl. fol. fols. Fr. Gen. Gov. Hon. i.e. ill. Inc. incl. Jan. Jr. Jul. Jun. Ltd. M.A. M.D. Mar. Mr. Mrs. Ms. n. n.b. nn. No. Nov. Oct. p. Ph.D. pp. Pres. Prof. pt. q.v. Rep. Rev. s.v. s.vv. saec. sec. Sen. Sep. Sept. Sgt. Sr. St. univ. viz. vol. vs.这也解释了测试 256.md 场景二的行为:e.g.与Mr.都位于上表之中,因此被识别;而Foo.(大写开头且是普通专名)与h.k.不在表中,因此保持为“词 + 句号”的普通结构。
四、为什么要在意非断行空格
缩写识别并不仅仅是一个 AST 层面的细节,它对最终排版输出有实际影响。相关测试 test/command/md-abbrevs.md 明确说明了动机:
Pandoc recognizes an abbreviation and inserts a nonbreaking space (among other things, this prevents a sentence-ending space from being inserted in LaTeX output).
也就是说,当把Mr. Brown这样的文本转换为 LaTeX/PDF 时,如果不做处理,Mr.末尾的句号容易被 LaTeX 误判为句子结束符,从而在Mr.与Brown之间插入过宽的句间间距。pandoc 将句号后的空格替换为非断行空格后,这一误判即可避免,同时也能防止缩写在行尾被换行拆散。
同类的非断行空格处理在其他阅读器中也有对应实现,例如 Muse 阅读器中的nbsp(src/Text/Pandoc/Readers/Muse.hs#L923-L924)将~~解析为\160,可以对照理解 pandoc 对空白语义的统一处理方式。
五、实操指南:自定义缩写文件
基于测试 256.md 的模式,自定义缩写文件的用法可以概括如下:
- 准备文本文件:每行一个缩写,含末尾句点,例如:
e.g. i.e. Mr. Mrs. Dr. h.k.- 传入选项:
pandoc --abbreviations=my-abbrevs.txt input.md -o output.pdf- 验证效果(使用与测试相同的 native 输出):
pandoc --abbreviations=my-abbrevs.txt -t nativereadAbbreviations的实现细节还提示了几个注意事项:
- 空行会被过滤(
filter (not . T.null)),因此文件中出现空行不会报错; - 文件内容会整体替换内置默认表,而不是合并——如果业务文档中大量使用默认表之外的缩写,需要把默认 data/abbreviations 中的条目一并抄入自定义文件;
- 该表“currently only affect the Markdown reader”(源码注释原文),对 HTML、LaTeX 等其他格式的阅读器没有影响;
- 缩写识别依赖
smart扩展(Ext_smart),若通过-f markdown-smart关闭 smart 扩展,则缩写判定分支不会进入,空格不会被替换。
六、相关配置与测试验证路径
除了--abbreviations,pandoc 还提供语义相近但用途不同的--citation-abbreviations选项(解析见 src/Text/Pandoc/App/CommandLineOptions.hs#L1100-L1108),它服务于citeproc文献处理流程(见 src/Text/Pandoc/Citeproc.hs#L125-L159),用于文献目录中期刊名的缩写,与本文讨论的 Markdown 阅读器空格机制相互独立,使用时注意不要混淆。
如需验证本文描述的行为,仓库提供了多条可复现的测试路径:
- test/command/256.md:
--abbreviations自定义文件 vs 默认表的对照测试(本文主线); - test/command/md-abbrevs.md:缩写识别的正向用例,以及用
Mr\.转义句点以关闭缩写的反例; - data/abbreviations:随程序分发的默认缩写表;
- src/Text/Pandoc/App.hs#L376-L384:缩写表加载实现;
- src/Text/Pandoc/Readers/Markdown.hs#L1799-L1821:空格替换核心逻辑。
七、小结
通过test/command/256.md这一组对照测试,可以完整理解 pandoc 缩写识别机制的三个层面:命令行选项(--abbreviations决定缩写表来源)、数据文件(data/abbreviations 提供默认集合)、解析器行为(Markdown.hs 的str在 smart 扩展开启时把缩写后的空格替换为非断行空格\160)。这一机制虽然只影响 Markdown 阅读器,却直接关系到 LaTeX/PDF 等输出格式的排版质量,是处理英文文本时值得了解和掌握的一个细节。
- 文档
- 开发工具
- CLI
【免费下载链接】pandoc
Universal markup converter
相关推荐
Pandoc 命令行黄金测试实战:从 test/command/5857.md 看空列表项的 Markdown 往返输出
Pandoc 命令行黄金测试实战:从 test/command/5857.md 看空列表项的 Markdown 往返输出 本文以 test/command/58
文档开发工具CLI为什么选择VUnit?探索这款革命性HDL单元测试框架的核心优势
为什么选择VUnit?探索这款革命性HDL单元测试框架的核心优势 VUnit是一款专为VHDL/SystemVerilog设计的开源单元测试框架,它彻底改变了硬
文档开发工具CLIPandoc AsciiDoc 写入器的字符转义机制:从 `test/command/10385.md` 看加号与反引号的转义处理
Pandoc AsciiDoc 写入器的字符转义机制:从 test/command/10385.md 看加号与反引号的转义处理 导读 本文以 pandoc 命令
文档开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考