Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

振り仮名ジェネレーター
GitHub > 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までいけるらしい。もし業務でルビ振りをする必要がある人はご活用ください。

参照記事

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした