RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

HTML でルビを描画する

はじめに

こんにちは、DevOps チームの中島です。
突然ですが、みなさんは HTML でルビが描画できることをご存知ですか?

私はこれまで知らなかったのですが、アジア圏のみの要望なのに標準化されているのは大変恐れ多いことですね。 ちなみにルビを描画する HTML は以下のようになります。エディタで普通に書こうとすると結構大変です。

<ruby>
  <rb>
    ルビを振る漢字
  </rb>
  <rp>
    ルビ非対応ブラウザのルビ開始記号左
  </rp>
  <rt>
    ここにルビ
  </rt>
  <rp>
    ルビ非対応ブラウザのルビ開始記号右
  </rp>
</ruby>

さて弊社では先日、日本語学習者のための日本語スピーキング力アセスメント (測定) サービス PROGOS Japanese をリリースしました。

アセスメントはブラウザ上に問題文が表示され、それに対する回答をマイクで録音する形式となっています。 日本語学習者の中には漢字を読むことが難しい方もいるため、問題文にルビが振ってあるのですが、 ルビ付きの HTML を作成するのが苦痛なのでツールを作成しました。今回はそのツールの仕組みを説明していきます。

技術要素の選定

以下のような考慮事項をもとに技術選定をしました。

  • A1. あまり手間をかけずに作れる、保守が大変にならない
    ツールを作る上でコスパを考慮するのは当然ですね。

  • A2. 外部のサーバにリクエストしてはいけない
    入力が試験問題であるという性質上、閉じた環境で動作する必要があります。

  • A3. 出力されたものが完全でなくても良い
    漢字の読み方は一意に特定することはできないので (ラノベのタイトルなどを想像してください) 、ある程度正しく推測されかつ自分で読み方を指定できれば十分です。

以上より、既に保守対象である GitLab サーバの Pages に HTML + JavaScript でホストし (A1) 、 ルビを振る外部 API を呼べないため (A2) 日本語辞書付きの形態素解析*1ライブラリ kuromoji.js を利用する (A1, A3) ことにしました。

ロジックの説明

ツールの動作としては、以下の通りとなります。

  • B1. 漢字かな交じりの文章を入力としてルビをカッコ付きで付与した形式に変換する
    日本語で話してください→日本語(にっぽんご)で話(はな)してください
  • B2. 次に変換された内容に対して必要であればユーザがルビの細かい修正を行う
    →日本語(にほんご)で話(はな)してください
  • B3. 最後にそれを HTML に変換する
    <ruby>...(省略)...</ruby>

ここではメインのロジックとなる B1 の部分について説明します。
B1 の部分で行いたい処理は、漢字かな交じり文に出現する各漢字に対して、(概ね) 適切な読み方を与える処理です。

辞書から読み方を取得する

まずは日本語辞書に登録された読み方を各漢字に割り当てることを試みます。
以下のように kuromoji.js を利用すると、形態素解析が行われた上で辞書に登録されている読み方を取得できます。

var kuromoji = require("kuromoji");
kuromoji.builder({ dicPath: "./node_modules/kuromoji/dict" }).build(function (err, tokenizer) {
    var path = tokenizer.tokenize("美味しそうな焼肉を指し示した");
    path.map((token) => {
        console.log(token.surface_form + ": " + kana2hira(token.reading))
    });
});

※kana2hira (カタカナをひらがなに変換する関数) の実装は省略

出力

$ node index.js
美味し: おいし
そう: そう
な: な
焼肉: やきにく
を: を
指し示し: さししめし
た: た

なるほど、形態素は以下のパターンに分けられることが分かります。

  • C1. 全て漢字
    焼肉
  • C2. 全てひらがな
    そう
  • C3. 漢字が複数続いたあとひらがなで終わる
    美味し
  • C4. 3.が複数繰り返される
    指し示し

これらのパターンに対して、漢字に対する読み方の割り当てを考えていきます。

C1 は熟語と考え、構成する全ての漢字に対して辞書が振った読み方を割り当てれば問題なさそうです。
焼肉 / やきにく
→焼肉(やきにく)

C2 は漢字がないので割り当てる必要ありません。
そう
→そう

C3 は後方からひらがなをマッチングしていき、残った部分が漢字の読み方になります。
美味|し /
おい|し
→美味(おい)し

C4 は特に今回の例の場合は、機械的に決められないためひと工夫必要そうです。
指し示し / さししめし
→指(さ)し示(しめ)し ?
→指(さし)し示(め)し ?

常用漢字一覧から読み方を取得する

C4. を一意にするために必要な情報は、各漢字の単体での読み方です。
Wikipedia常用漢字一覧 から、漢字の読み方リストを作成します。

読み方の推測

あとは C3 と同様に元の文章と kuromoji の読みを後方からマッチングしていき、漢字が出てきたら (図の「示」の部分) その漢字の読み方リストから同じ読み方があるかを探します。 同じ読み方があれば (「しめ」 が該当) その文字数分消費して前方に向かって同様の処理を繰り返します。ない場合その形態素の読み方を推測するのは諦めます。

以上が読み方を推測する処理になります。

終わりに

手前味噌ですが、今回は自分の持っている手札をうまく組み合わせて、最低限の努力で最大の効果を出せた気がしています。 これからもどんどん出来ることを増やして、価値を生み出していきたいですね。
最後までご覧頂きありがとうございました。

We're hiring!

rarejob-tech.co.jp

*1:形態素解析についての説明は他所に譲ります。