🦔

ルビ付けの連鎖正規表現による送り仮名の落とし方

2022/12/06に公開

2022/12/08 更新:mixed.replace(/\p{sc=Han}+/gu, '(\\p{sc=Hiragana}+)'), 'u') 最初の \p{sc=Han}++ が抜けてたので修正した。

ということがあって、例えば漢字仮名交じり表記「逢うさ離るさ」とその発音のかな表記「おうさきるさ」しか持っていない場合、どういう処理をしたら「逢(お)うさ離(き)るさ」というように振仮名のルビを付けることができるのだろうかという問いに対し、去年の私は天才的な方法、正規表現で正規表現を作って正規表現でマッチするとかいうもの、具体的な試行錯誤の経緯は当該ツイートのリンク先の Qiita の文章に書いてある。ここでは、TypeScript での実装を試みる。

function parsePron(mixed: string, kana: string): ([kanji: string, kana: string] | [kana: string])[] {
   // ...
}

このように引数 mixed が混じり表記(「逢うさ離るさ」)で、kana が仮名表記(「おうさきるさ」)で、戻り値に返してほしいのが [["逢", "お"], ["うさ"], ["離", "き"], ["るさ"]] である。

方針としては簡単で、まず混じり表記の漢字を仮名を表す正規表現に変えて、さらにその正規表現で仮名表記をマッチすれば、対称なパーツが出てくるので、対応するパーツを取り出せば完成。

  function parsePron(mixed: string, kana: string): ([string] | [string, string])[] {
    // 正規表現文 /(\p{sc=Hiragana}+)うさ(\p{sc=Hiragana}+)るさ/u のようなものを作る
    const pattern = new RegExp(mixed.replace(/\p{sc=Han}+/gu, '(\\p{sc=Hiragana}+)'), 'u');

    // 作った正規表現文で仮名表記をマッチさせて、「お」「き」を取得する
    const groups = kana.match(pattern);
    if (!groups) return [[mixed, kana]];
    const [, ...rest] = groups;

    // 混じり表記の文を漢字部分とそうでない部分を分ける [ '逢', 'うさ', '離', 'るさ' ]
    const sections = mixed.split(/(\p{sc=Han}+)/u).filter(Boolean);

    // 最後に分けた部分に必要な場合だけルビを振る
    return sections.map((section) => {
      if (section.match(/\p{sc=Han}+/u)) {
        return [section, rest.shift()!];
      } else {
        return [section];
      }
    });
  }

以上で [ [ '逢', 'お' ], [ 'うさ' ], [ '離', 'き' ], [ 'るさ' ] ] を取得できた。

これを例えば以下のように使うことができる。

  const parsed = parsePron(mixed, kana)
  const html = parsed.map(([rb, rt]) => rt ? `<ruby>${rb}<rt>${rt}</rt></ruby>` : rb).join('') // <ruby>逢<rt>お</rt></ruby>うさ<ruby>離<rt>き</rt></ruby>るさ
  const paren = parsed.map(([rb, rt]) => rt ? `${rb}${rt}` : rb).join('') // 逢(お)うさ離(き)るさ

以上

Discussion