





















在支持HTML的环境(包括许多Markdown编辑器1),可以使用<ruby>标签为汉字等表意文字添加注音(如拼音或振假名)。不过,在不支持该标签的纯文本环境中,内容的可读性会因此降低。
例如,一个标准的<ruby>标签:
<ruby>大学<rt>だいがく</rt></ruby>
在纯文本的渲染环境中会显示为大学だいがく,这显然不够理想。2
<rp>标签HTML标准提供了<rp>(ruby parenthesis)标签来处理这种兼容性问题。它用于包裹括号一类的“备用字符”,这些字符只在不支持<ruby>标签的环境中显示。
添加<rp>标签后的理想格式如下:
<ruby>大学<rp>(</rp><rt>だいがく</rt><rp>)</rp></ruby>
这样,可以两全其美:
大学(だいがく),未尝有损可读性。那么,如何将手上的文字(譬如说电子笔记库)中所有旧格式的<ruby>标签批量升级到这种新格式呢?自然会想到的是全局搜索与正则表达式。
(警告:请不要急着一行行跟着以下方案批量替换,建议先看看下文再做决定。另外,任何时候都请记得做好备份,再批量修改。)
这里用可以直接管理包括.markdown文件在内的笔记文档的VS Code来举例。我们最直接的想法是找到所有的注音标签,然后给它们加上括号。
<rt>(.*?)<\/rt><rp>(</rp><rt>$1</rt><rp>)</rp>这个方案可以处理最简单的情况。但很快我们就会发现第一个问题。
当遇到像日语“地震”这样的词,其源代码可能是:
<ruby>地<rt>じ</rt>震<rt>しん</rt></ruby>
上面的简单方案能正确工作吗?是的。它会独立地将<rt>じ</rt>替换为<rp>(</rp><rt>じ</rt><rp>)</rp>,并将<rt>しん</rt>替换为<rp>(</rp><rt>しん</rt><rp>)</rp>,最终得到正确的结果:
<ruby>地<rp>(</rp><rt>じ</rt><rp>)</rp>震<rp>(</rp><rt>しん</rt><rp>)</rp></ruby>
到目前为止,一切似乎都很顺利。但一个更隐蔽的“定时炸弹”埋藏在这个方案中。
问题在于,如果我们不小心再次运行4这个替换脚本,会发生什么?
它会找到已经修复好的<rp>(</rp><rt>じ</rt><rp>)</rp>中的<rt>じ</rt>,然后再次为它套上括号,导致灾难性的结果:
<rp>(</rp><rp>(</rp><rt>じ</rt><rp>)</rp><rp>)</rp>
这破坏了幂等性5原则。一个健壮的脚本必须能够安全地重复执行而不产生副作用。
因此,查找规则需要改进,目标是:只查找那些尚未被<rp>(</rp>和<rp>)</rp>包裹的<rt>标签。
正则表达式的“反向否定查找”(negative lookbehind),写作(?<!...),是解决这个问题的完美工具。它允许我们匹配一个模式,但前提是它的前面不是某个特定的字符串。
Ctrl+Shift+H 或 Cmd+Shift+H)。.* 图标)。(?<!<rp>\(<\/rp>)<rt>(.*?)<\/rt><rp>(</rp><rt>$1</rt><rp>)</rp>(?<!<rp>\(<\/rp>)<rt>(.*?)<\/rt>
(?<!<rp>\(<\/rp>):这是关键。它是一个反向否定查找,意思是:“从当前位置往前看,前面的文本不能是 <rp>(</rp>”。注意,为了匹配字面上的括号 (,我们用 \( 对其进行了转义。<rt>(.*?)<\/rt>:这部分和之前一样,用于匹配 <rt> 标签及其内容。这样,查找表达式就能准确地识别目标:
<ruby>地<rt>じ</rt>...中的<rt>じ</rt>,因为它的前面是字符地,不是<rp>(</rp>。<ruby>地<rp>(</rp><rt>じ</rt>...中的<rt>じ</rt>,因为它前面的文本正好是我们排除了的<rp>(</rp>。如此一来,无论对文件执行多少次这个替换操作,它都只会影响那些尚未被处理的旧标签,而对已经符合新格式的标签没有影响,保证了操作的安全性和幂等性6。
<ruby>标签并不是标准的Markdown语法——从“<”与“>”就能看出它的XML色彩,但是不少现代的Markdown编辑器——例如Obsidian之类——都会尽量支持HTML语法,<ruby>标签也就包含在内了。 ↩︎<rp>的<ruby>标签了。 ↩︎此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。