ちまちま小説書いてるサイトでPHP製のMarkdownパーサーを使用しているがMarkdownにはルビ記法ないのなんでなんだ……という苦しみからクライアントサイドで実装(多分サーバーサイドでやるほうが良いと思う、多分)。

実装方法も考え方もほぼほぼ次の参考記事の通りです。感謝しかない。自分はほんのちょっと変更して、jQuery使わず他にpixiv記法を追加した。

参考:各種小説投稿サイトのルビ記法をJavaScriptで実現する - Qiita

実装例

script.js

const rubyTemplate = "<ruby>$1<rt>$2</rt></ruby>";

const rubyRegexList = [
  { pattern: /[\||](.+?)《(.+?)》/g, replacement: rubyTemplate },
  { pattern: /[\||](.+?)((.+?))/g, replacement: rubyTemplate },
  { pattern: /[\||](.+?)\((.+?)\)/g, replacement: rubyTemplate },
  { pattern: /\[\[rb:(.+?) &gt; (.+?)\]\]/g, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)《(.+?)》/gu, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)(([\p{sc=Hiragana}\p{sc=Katakana}ー~]+?))/gu, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)\(([\p{sc=Hiragana}\p{sc=Katakana}ー~]+?)\)/gu, replacement: rubyTemplate },
  { pattern: /[\||]《(.+?)》/g, replacement: "《$1》" },
  { pattern: /[\||]((.+?))/g, replacement: "($1)" },
  { pattern: /[\||]\((.+?)\)/g, replacement: "($1)" }
];

const rubyRegexMapList = rubyRegexList.map(({ pattern, replacement }) => ({
  pattern: new RegExp(pattern),
  replacement
}));

const replaceRubyTags = (str) => {
  return rubyRegexMapList.reduce((acc, { pattern, replacement }) => {
    return acc.replace(pattern, replacement);
  }, str);
}

const updateArticle = (el) => {
  const replacedHtml = replaceRubyTags(el.innerHTML);
  el.innerHTML = replacedHtml;
}

export default updateArticle;

index.html

<script type="module">
  import updateArticle from './script.js';

  const articleBody = document.getElementById("article-body");
  updateArticle(articleBody);
</script>

ESModuleとかでimportして使えるようにしたかったのでこんな感じでどうだろう。

正規表現について

正規表現のマッチングには Unicode プロパティエスケープ - JavaScript | MDN を使うようにした。

カタカナがややこしいことになってるんですが、まあ自分用なので多少妥協しました。

[:Script_Extensions=Hira,Kana:]
[:Script_Extensions=Hani,Hira,Kana:]
[:Script_Extensions=Bopo,Hang,Hani,Hira,Kana:]
[:Script_Extensions=Bopo,Hang,Hani,Hira,Kana,Yiii:]

置換処理について

Array.prototype.reduce() - JavaScript | MDN

なかなか配列でのreduceメソッドを理解できてないんだけどこんなこともできるんだね……っていうのをジピって知りました。有難うOpenAI。

解説 by chatGPT 3.5

💬 Answer

このコードは、RubyタグをHTMLに追加するための正規表現と置換ルールを定義し、記事本文の要素に適用するJavaScript関数を提供しています。

まず、rubyTemplateは、Rubyタグを含むHTMLテンプレート文字列を定義しています。$1$2は、後でマッチしたテキストの置換に使用されます。

const rubyTemplate = "<ruby>$1<rt>$2</rt></ruby>";

次に、rubyRegexListは、テキスト中でRubyタグを置換するために使用される正規表現と置換ルールのリストを定義します。各エントリは、patternreplacementフィールドを持ちます。patternは正規表現、replacementは置換するHTMLテンプレートです。

const rubyRegexList = [
  { pattern: /[\||](.+?)《(.+?)》/g, replacement: rubyTemplate },
  { pattern: /[\||](.+?)((.+?))/g, replacement: rubyTemplate },
  { pattern: /[\||](.+?)\((.+?)\)/g, replacement: rubyTemplate },
  { pattern: /\[\[rb:(.+?) &gt; (.+?)\]\]/g, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)《(.+?)》/gu, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)(([\p{sc=Hiragana}\p{sc=Katakana}ー~]+?))/gu, replacement: rubyTemplate },
  { pattern: /([\p{sc=Han}]+)\(([\p{sc=Hiragana}\p{sc=Katakana}ー~]+?)\)/gu, replacement: rubyTemplate },
  { pattern: /[\||]《(.+?)》/g, replacement: "《$1》" },
  { pattern: /[\||]((.+?))/g, replacement: "($1)" },
  { pattern: /[\||]\((.+?)\)/g, replacement: "($1)" }
];

このリストは、以下のようなテキストのパターンに一致するものを含みます。

  • |text《ruby》|text《ruby》|text(ruby)|text(ruby)|text(ruby)|text(ruby)に一致するもの
  • [[rb:ruby &gt; text]]に一致するもの
  • 漢字《ruby》漢字(kana)漢字(kana)に一致するもの
  • |《ruby》|《ruby》|(ruby)|(ruby)|(ruby)|(ruby)に一致するもの

次に、rubyRegexMapListは、rubyRegexListの各エントリを正規表現オブジェクトに変換するための配列です。map関数を使用して、オブジェクトを変換し、new RegExp()を使用して、各パターンを正規表現オブジェクトに変換しています。

const rubyRegexMapList = rubyRegexList.map(({ pattern, replacement }) => ({
  pattern: new RegExp(pattern),
  replacement
}));

replaceRubyTags関数は、記事の本文の要素を受け取り、各Rubyタグを置換するために、rubyRegexMapListの各正規表現をループで処理し、replace()メソッドを使用して、テキストの置換を実行します。

const replaceRubyTags = (str) => {
  return rubyRegexMapList.reduce((acc, { pattern, replacement }) => {
    return acc.replace(pattern, replacement);
  }, str);
};

最後に、updateArticle関数は、記事の本文の要素を受け取り、replaceRubyTags関数を使用して、その要素内のテキストを置換し、結果を要素のinnerHTMLプロパティに設定します。

const updateArticle = (el) => {
  const replacedHtml = replaceRubyTags(el.innerHTML);
  el.innerHTML = replacedHtml;
};

const articleBody = document.getElementById("article-body");
updateArticle(articleBody);

このコードは、Rubyタグを含むテキストをHTMLに変換するために必要な正規表現と置換ルールを提供し、それを実際に記事本文の要素に適用するJavaScript関数を提供しています。