5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.jsとYahooテキスト解析APIで振り仮名ジェネレーターを作ってみた

Last updated at Posted at 2017-09-03

まとまった量のWebコンテンツに振り仮名をつける必要があるので、良い機会だと思ってrubyタグを自動生成できるアプリケーションを作ってみた。Herokuにアップしているけど、業務での使用が終わったら非公開にするかも。コードはGitHubに公開しています。

振り仮名ジェネレーター
[GitHub > furigana-generator] (https://github.com/takuya0206/furigana-generator)

開発環境はこんな感じ。

以下、ハマった & 工夫したところをメモ。

node-yjがインストールできない

YahooデベロッパーネットワークにWebAPIライブラリ集というページがあって、そこで node-yj が紹介されていた。「これを使えば簡単に実装できそう」なんて思っていたのだけど、そもそもインストールができなくてハマった。どうやら求められているpythonのバージョン差異が原因だったようだけど(ubuntu16.04.3はpython3系がデフォルト)、ubuntuにpython2系を入れてもインストールできなかった。node-yjは2013年が最後のcommitになっていたから、他の部分でもバージョンの差異があったのかもしれない。
結局、直接YahooAPIを叩くことにした。nodeのrequestをインストールして、超シンプルに実装した。

index.js
router.post('/generate', (req, res, next) => {
  // 送信するデータを生成
  let options = {
    url: config.API_URL,
    headers: {
      'User-Agent': 'Yahoo AppID: ' + config.appid,
    },
    form: {
      sentence : req.body.sentence
    }
  };
  // apiにリクエストを送り、callbackを処理
  request.post(options, (error, res, body) => {

カタカナや送り仮名にはルビをつけない

YahooAPIから返ってくるデータはこんな感じ。

<?xml version="1.0" encoding="UTF-8"?>
<ResultSet xmlns="urn:yahoo:jp:jlp:FuriganaService" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:yahoo:jp:jlp:FuriganaService https://jlp.yahooapis.jp/FuriganaService/V1/furigana.xsd">
  <Result>
    <WordList>
      <Word>
        <Surface>漢字</Surface>
        <Furigana>かんじ</Furigana>
        <Roman>kanzi</Roman>
      </Word>
      <Word>
        <Surface>かな交じり</Surface>
        <Furigana>かなまじり</Furigana>
        <Roman>kanamaziri</Roman>
        <SubWordList>
          <SubWord>
            <Surface>かな</Surface>
            <Furigana>かな</Furigana>
            <Roman>kana</Roman>
          </SubWord>
          <SubWord>
            <Surface>交</Surface>
            <Furigana>ま</Furigana>
            <Roman>ma</Roman>
          </SubWord>
          <SubWord>
            <Surface>じり</Surface>
            <Furigana>じり</Furigana>
            <Roman>ziri</Roman>
          </SubWord>
        </SubWordList>
      </Word>
    </WordList>
  </Result>
</ResultSet>

カタカナや送り仮名にまでルビがつくし、xml形式なので直接触れない。そこで、まずnodeのxml2jsというライブラリを使ってJavaScriptのオブジェクトに変換し、漢字のみ・送り仮名あり・カタカナなどを判定しながらHTML用にマークアップする実装にした。

index.js
const ruby = ['<ruby>','<rt>','</rt></ruby>'];
const data = {rubySentence : ''};

  // apiにリクエストを送り、callbackを処理
  request.post(options, (error, res, body) => {
    let word, subword, katakana = '';
    xml2js.parseString(body, (err, callback) => {
      //エラー(入力が空白)
      if (callback.hasOwnProperty('Error')) {
        data.rubySentence += "入力が適切ではありません";
      } else {
      for (let i = 0, len = callback.ResultSet.Result[0].WordList[0].Word.length; i < len; i++) {
       word = callback.ResultSet.Result[0].WordList[0].Word[i];
       katakana = word.Surface.toString();
       // 数字やローマ字の場合
       if (!word.hasOwnProperty('Furigana')) {
        data.rubySentence += word.Surface;
      }
       // ひらがなをスキップ
       else if (word.hasOwnProperty('Furigana') && word.Furigana[0] == word.Surface[0]) {
        data.rubySentence += word.Surface;
      }
       // SubWord(=送り仮名)がある場合
       else if (word.hasOwnProperty('SubWordList')) {
        for (let j = 0, len2 = word.SubWordList[0].SubWord.length; j < len2; j++) {
          subword = word.SubWordList[0].SubWord[j];
          if (subword.Furigana[0] == subword.Surface[0]) {
            data.rubySentence += subword.Surface;
          } else {
           data.rubySentence += ruby[0] + subword.Surface + ruby[1] + subword.Furigana + ruby[2];
         };
       };
     }
   // カタカナの場合
   else if (katakana.match(/^[\u30A0-\u30FF]+$/)) {
    data.rubySentence += word.Surface;
  }
  else {
   data.rubySentence += ruby[0] + word.Surface + ruby[1] + word.Furigana + ruby[2];
 };
};
};
});

もう少し綺麗に書けた気もするが、意図通りに動くので良しとしよう。尚、Qiitaにあげているコードは抜粋なので、詳しく見たい人はGitHubをどうぞ。

Node.jsの非同期処理への対応

一番ハマったのはここ。POSTが完了する前に、次の処理が走ってしまいバグが発生していた。「Javascriptは上から順番にプログラムが動いていく」という安易な覚え方がよくなかった。setIntervalで動作の完了を確認するように実装。

index.js
wait = setInterval (() => {
    if (data.rubySentence !== '') {
      clearInterval(wait);
      res.redirect('/');
    }
  }, 250);

終わり

気になる振り仮名の精度だけど、固有名詞の珍しい単語でたまに間違いがあるぐらいで、通常の文章ではほぼ問題がなかった。業務で使用する関係で、念のため小まめに目視確認をしてもらいたかったので、文字数の制限をかけているが、1リクエストの上限は100KBまでいけるらしい。もし業務でルビ振りをする必要がある人はご活用ください。

参照記事

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?