如何优雅地升级ruby标签:正则表达式指南

在支持HTML的环境(包括不少现代的Markdown编辑器1)使用<ruby>标签为汉字等表意文字添加注音(如拼音振假名),是一个非常实用的功能。然而,一个常见的痛点是,在不支持该标签的纯文本渲染环境下,内容的可读性会大大降低。

例如,一个标准的<ruby>标签:

<ruby>大学<rt>だいがく</rt></ruby>

在纯文本的渲染环境中会显示为大学だいがく,这显然不够理想。2

优雅降级的方案:<rp>标签

HTML标准为此提供了一个优雅的解决方案:<rp>(ruby parenthesis)标签。它的作用是包裹“备用字符”(通常是括号),这些字符只在不支持<ruby>的环境中显示。

理想的格式应该是这样:

<ruby>大学<rp>(</rp><rt>だいがく</rt><rp>)</rp></ruby>

这样可以实现两全其美的效果:

  • 在支持的环境3:正常显示注音样式,括号被隐藏。
  • 在不支持的环境(纯文本):显示为大学(だいがく),未尝有损可读性。

那么,如何将手上的文字(譬如说电子笔记库)中所有旧格式的<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>

到目前为止,一切似乎都很顺利。但一个更隐蔽的“定时炸弹”埋藏在这个方案中。

第二版方案:解决重复执行的bug

问题在于,如果我们不小心再次运行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),写作(?<!...),是解决这个问题的完美工具。它允许我们匹配一个模式,但前提是它的前面是某个特定的字符串。

最终的查找与替换规则

  • 打开VS Code的“在文件中查找和替换”面板 (Ctrl+Shift+HCmd+Shift+H)。
  • 确保启用正则表达式模式 (点击 .* 图标)。
  • 在“查找”(Find)输入框中,粘贴以下表达式(?<!<rp>\(<\/rp>)<rt>(.*?)<\/rt>
  • 在“替换”(Replace)输入框中,粘贴以下内容<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>标签也就包含在内了。 ↩︎
  • 偏偏市面上大把网页都是这个样子,吾辈任重道远呐。 ↩︎
  • 大到现代浏览器,小到Obsidian之类支援HTML的笔记软件的预览界面。 ↩︎
  • 甚至还有种情况,就是——虽然是头一回尝试批量执行,但是待执行的文档里面已经包含有写好了<rp><ruby>标签了。 ↩︎
  • 幂等性(idempotence)指的是在一次或多次执行相同操作后,系统的状态保持不变,即无论执行多少次,结果都是相同的。 ↩︎
  • 对“幂等性”概念的解释参见前文。 ↩︎
  • #HTML #XML

    汉语拼音 - 维基百科,自由的百科全书

    If the world is going to suck one solace will continue to be #HTML and #CSS and making fun nonsense on ye new olde Web Platform. https://lab.artlung.com/popover-retro/ Feel free to steal this or not and go make a web page.
    Popover à la Windows 95 / ArtLung Lab

    Where to Put Focus When Opening a Modal Dialog

    TL;DR: blanket statements about where to put focus when opening a modal dialog are wrong, including this one. This post is meant to help you, an intelligent and thoughtful and empathetic reader, figure out where you should set focus. The scenarios are non-exhaustive. Messages I’m artificially breaking these into three…

    Adrian Roselli

    Roll into a Refresh: Your Simple Dice Roller Game for Instant Brain Breaks!

    Today, we're diving into a tiny project: a Simple Dice Roller Game. It's built with just a few lines of HTML, styled cleanly with CSS (using Tailwind for a modern look), and brought to life with a dash of JavaScript.

    No complex setups, no installs - just an instant, smile-inducing distraction

    https://codemodd.blogspot.com/2025/06/roll-into-refresh-your-simple-dice.html

    #Programming #Coding #Computers #JavaScript #HTML #CSS
    #GameDev #Game

    The Ambient Code: How JavaScript Listens Without Direct Contact

    We all know JavaScript is the engine of interaction on our web pages.

    But what if JavaScript could be more like a subtle observer? What if it could "hear" actions happening on your page without directly having a personal connection to every single element?

    https://codemodd.blogspot.com/2025/06/the-ambient-code-how-javascript-listens.html

    #programming #Coding #Computers #JavaScript #HTML

    The first major component of the @makehypertext movement is HyperTemplates – a pure-HTML templating engine and blazing fast static site generator. 🚀

    https://hypertemplates.net/blog/introducing-hypertemplates/

    You can build incredible modern websites with pure #HTML, #CSS, and #Javascript! No frameworks, no build tools, no sacrifices, and no nonsense (unless you're into those sorts of things). 😊

    We hope you will give HyperTemplates a try, and we can't wait to see what you make with it! 📐

    #MakeHyperText

    Introducing HyperTemplates

    The pure-HTML templating system for the modern web.

    XRSH devlog 2024-03-22 AFRAME in/vs HTML, Meta browser Overlay

    https://makertube.net/w/iiqAKunyfn652HTY2LksJG

    XRSH devlog 2024-03-22 AFRAME in/vs HTML, Meta browser Overlay

    PeerTube

    <picture>

    Contains zero or more source elements and one img element to offer alternative versions of an image for different display/device scenarios.

    https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture

    #HTML #WebDev #DailyHTML

    <picture>: The Picture element - HTML: HyperText Markup Language | MDN

    The <picture> HTML element contains zero or more <source> elements and one <img> element to offer alternative versions of an image for different display/device scenarios.

    MDN Web Docs

    What I Learnt About Making Websites by Reading Two Thousand Web Pages, by @alex:

    https://alexwlchan.net/2025/learning-how-to-make-websites/

    #html #css #conditionalcomments #lessons

    What I learnt about making websites by reading two thousand web pages

    How to write thoughtful HTML, new-to-me features of CSS, and some quirks and relics I found while building my personal web archive.

    More work on my website today! Added auto-generation of all kinds of meta and opengraph tags to the Hugo template that I'm creating. Learning a lot of html and tailwind in the process. Also added RSS feeds if you want to follow the posts or a certain tag. Here you can see the end result:

    https://thinkpractice.nl/post/

    #tailwindcss #html #Buildinpublic

    Posts

    Smart apps for Apple devices