Edited at

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

More than 1 year has 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までいけるらしい。もし業務でルビ振りをする必要がある人はご活用ください。


参照記事