概要
Wordで教材を作るために、大量のテキストにルビを振りたい(ふりがなを設定したい)。しかし、Wordのルビ機能は貧弱なためほぼ手作業になり時間がかかる。そこで、Yahoo!のルビ振りAPIを使い、精度の高い高速なルビ振りの実現を目指した。
ざっくり言うと
- ChatGPTを利用したふりがな設定は自然だが、問題点があった
- Yahoo!のルビ振りAPIを使うことで、手作業を大きく減らすことができた
- Wordのルビ設定に近い使用感のプログラムができた
前回までのあらすじ
Wordのルビ振り機能には次のような問題点があった。
ChatGPTを活用してそこそこの成果を得たが、課題が残った。
ChatGPTを使った方法の課題
- まれに本文が改変されることがある
- カタカナにルビが振られることがある
- 生成AIを利用している(私の所属では使用が面倒)
特に本文が改変されるのは、私にとって大きな欠点だった。そんな中、私はYahoo!デベロッパーネットワークの提供するWeb API「ルビ振り(V2)」を発見した。このAPIを活用するため、我々調査隊はアマゾンの奥地へと向かった――。
やったこと
ルビ振りAPIを使用する準備
Yahoo! JAPAN テキスト解析API ってなに?
Yahoo! JAPAN テキスト解析APIは、LINEヤフーが提供するWebサービスの一つで、次のような種類がある。
- 日本語形態素解析
- かな漢字変換
- ルビ振り
- 校正支援
- 日本語係り受け解析
- キーフレーズ抽出
- 自然言語理解
- 固有表現抽出
「Yahoo! JAPAN ID」と「アプリケーションID」を取得する必要があるが、無料で使うことができる。すごい!
Yahoo! JAPAN IDの取得
テキスト解析APIを使うには、Yahoo! JAPAN IDが必要である。私はすでに持っていたのでそれを使った。
アプリケーションIDの取得
テキスト解析APIを使うには、アプリケーション登録を行い、アプリケーションIDを取得する必要がある。私は次のように入力して登録した。
項目 | 必須 | 内容 |
---|---|---|
ID連携利用有無 | * | ID連携を利用しない |
利用者情報 | * | 個人 |
個人情報提供先 ... | * | 同意しない |
契約者住所の国または地域 | 日本 | |
アプリケーション名 | * | ルビルビ星人の侵略 |
利用規約、... | * | 同意する |
登録するとClient ID(アプリケーションID)が発行されるので、これを控えておく。
VBA-JSONの導入
APIとのやり取りはJSON形式
今回使うAPIは、ルビを振ってほしい文章を渡すと、ルビ情報を返してくれる。やり取りは行きも帰りもJSON形式で行われる。JSONとは、次のような { } で構造をあらわす書き方である。
{
"id": "1234-1",
"jsonrpc": "2.0",
"method": "jlp.furiganaservice.furigana",
"params": {
"q": "漢字かな交じり文にふりがなを振ること。",
"grade": 1
}
}
この、戻ってきたJSON形式のルビ情報を単純な文字列の検索で解釈するのはとても大変なので、JSON形式のデータを解析して、構造にアクセスしやすくするライブラリ「VBA-JSON」を導入する。
VBA-JSONではなく、ScriptControllを使おうとしてやめた話は後述。
VBA-JSONの導入手順
1. VBA-JSONをダウンロードする
上記のGitからファイルをダウンロードした。
2. モジュールをエクスポートする
解凍したファイルのspecsフォルダ内にある「VBA-JSON – Specs.xlsm」というファイルを開き、VBEを開いて次の2ファイルをエクスポートした。
- JsonConverter
- Dictionary
3. モジュールをインポートする
エクスポートした2ファイルを、自分の環境でインポートした。私は、Wordでどのファイルを開いたときにも今回のプログラムを使いたかったので、Normalにインポートした。
これでVBA-JSONの導入は完了。
参考サイト
今回のVBA-JSONの導入手順は、こちらの記事を参考にした。
APIでルビを取得して設定する
APIにリクエストを投げてルビ情報を取得するコードを、Word VBAで書いた。
コード
Const APP_ID As String = "YOUR_API_KEY_HERE"
Const API_GRADE As Integer = 1 ' gradeパラメータの値
Sub ApplyRubyToSelection()
Dim selectedText As String
Dim jsonResponse As String
Dim rubyData As Collection
' 選択されていないときは終了
If Selection.Range.text = vbNullString Then
MsgBox "テキストが選択されていません。", vbExclamation
Exit Sub
End If
' 選択されたテキストを取得
selectedText = Selection.Range.text
' テキストのバイトサイズをチェック
' If LenB(EncodeUTF8(selectedText)) > 4000 - 400 Then ' 4KBに少し余裕を持たせる
' MsgBox "選択されたテキストのサイズが大きすぎます。" & vbCr & vbCr & _
' "選択サイズ: " & LenB(EncodeUTF8(selectedText)) & " B" & vbCr & _
' "上限サイズ: 3600 B", vbExclamation
' Exit Sub
' End If
' APIを呼び出してレスポンスを取得
jsonResponse = GetRubyFromAPI(selectedText)
' レスポンスからルビ情報を解析
Set rubyData = ParseAPIResponse(jsonResponse)
' ルビを適用
ApplyRuby Selection.Range, rubyData
End Sub
Private Function GetRubyFromAPI(text As String) As String
Dim httpRequest As Object
Set httpRequest = CreateObject("MSXML2.XMLHTTP")
Dim apiUrl As String
apiUrl = "https://jlp.yahooapis.jp/FuriganaService/V2/furigana"
' APIリクエストのボディを構築
Dim requestBody As String
requestBody = "{""id"":""1234-1"",""jsonrpc"":""2.0"",""method"":""jlp.furiganaservice.furigana""," & _
"""params"":{""q"":""" & text & """,""grade"":" & API_GRADE & "}}"
' HTTPリクエストを送信
With httpRequest
.Open "POST", apiUrl, False
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "User-Agent", "Yahoo AppID: " & APP_ID
.Send requestBody
GetRubyFromAPI = .ResponseText
End With
End Function
' JSONデータを解析し、ふりがな情報を抽出する関数
Private Function ParseAPIResponse(jsonResponse As String) As Collection
Dim rubyData As New Collection
Dim json As Object
Dim word As Object
Dim subword As Object
Dim surfaceText As String
Dim furiganaText As String
Dim isKanjiFlag As Boolean
' JSON文字列を解析してオブジェクトに変換
Set json = JsonConverter.ParseJson(jsonResponse)
' "word"プロパティから単語のリストを取得
For Each word In json("result")("word")
surfaceText = word("surface")
' "furigana"プロパティが存在するかチェック
If word.Exists("furigana") Then
furiganaText = word("furigana")
' 漢字かどうかを判断
isKanjiFlag = IsKanji(surfaceText)
' "subword"プロパティが存在する場合
If word.Exists("subword") Then
For Each subword In word("subword")
surfaceText = subword("surface")
If IsKanji(surfaceText) Then
furiganaText = subword("furigana")
rubyData.Add Array(surfaceText, furiganaText)
End If
Next subword
Else
' ルビ情報をコレクションに追加
rubyData.Add Array(surfaceText, furiganaText)
End If
End If
Next word
' 解析結果を返す
Set ParseAPIResponse = rubyData
End Function
' 文字が漢字かどうかを判断する関数
Private Function IsKanji(text As String) As Boolean
Dim charCode As Long
charCode = AscW(text) ' 文字列の最初の文字の Unicode コードポイントを取得
If charCode < 0 Then
charCode = charCode + 65536
End If
IsKanji = (charCode >= 19968 And charCode <= 40959) ' Unicodeの範囲で漢字かどうかを判定(&H4E00-&H9FFF)
End Function
Private Sub ApplyRuby(rng As Range, rubyData As Collection)
Dim Item As Variant
Application.DisplayStatusBar = True
' 上向きに検索しながらルビを設定
' (ルビ設定で挿入されたフィールドが新たに検索対象になり検索位置がずれてしまうため、上向きにする必要がある)
cnt = 0
n = rubyData.Count
For i = n To 1 Step -1
prog = Int(cnt / n * 100 / 10)
Application.StatusBar = "Processing...: " & String(prog, "■") & String(10 - prog, "□") & " (" & cnt & "/" & n & ")"
Item = rubyData(i)
With rng.Find
.Execute FindText:=Item(0), Forward:=False, Wrap:=wdFindContinue
If .Found Then
rng.PhoneticGuide text:=Item(1) ', Alignment:=wdPhoneticGuideAlignmentCenter, Raise:=10, FontSize:=5 ' ルビ部分
End If
End With
cnt = cnt + 1
Next i
Application.StatusBar = False
End Sub
'Private Function EncodeUTF8(text As String) As String
' Dim i As Long
' For i = 1 To Len(text)
' EncodeUTF8 = EncodeUTF8 & "%" & Hex(AscW(Mid(text, i, 1)))
' Next i
'End Function
' Web Services by Yahoo! JAPAN (https://developer.yahoo.co.jp/sitemap/)
解説
定数部分
Const APP_ID As String = "YOUR_API_KEY_HERE"
Const API_GRADE As Integer = 1 ' gradeパラメータの値
APP_IDには、事前に取得したアプリケーションIDを設定する。
API_GRADEは定数として書き出したが、後述する「余談」の理由により、今回は基本1に固定して使うことにした。
プロシージャ部分
大まかな構成は次の通り。
- ApplyRubyToSelection
- GetRubyFromAPI
- ParseAPIResponse
- IsKanji
- ApplyRuby
- EncodeUTF8
ApplyRubyToSelection
ApplyRubyToSelectionがメインのプロシージャである。今回は選択したテキストに対して処理を行うようにしたので、はじめに選択のチェックを行う。APIの仕様で「1リクエストの最大サイズを 4KB に制限」されているので、サイズをチェックしてからリクエストすべきだと思っていたが、1万字くらいで試しても大丈夫だったので、やっぱり気にしないことにした。
GetRubyFromAPI
実際にAPIを使ってリクエストしている関数です。GETじゃなくてPOST。
ParseAPIResponse
GetRubyFromAPIで返ってきたJSONデータを、VBA-JSONを使ってパースし、必要なルビ情報を取り出す。ルビ情報にはカタカナ部分や送り仮名部分も含まれるが、今回は漢字部分だけにルビを振りたいので、漢字部分の情報だけを取り出して返す。
ApplyRuby
ApplyRubyでルビ設定するときには、Findメソッドで対象の漢字を検索しながら進めていく。初めは下向きで検索していたが、同じ漢字が2連続で出てくるとPhoneticGuideで失敗してしまった。ルビの設定は内部ではフィールドとして扱われるが、Findメソッドで検索される対象にはこのフィールドも含まれてしまう。検索途中でフィールドが挿入されることで対象テキストの文字列が増え、保持していた検索位置がフィールドによってずれてしまい、2つ目の同じ漢字の検索でフィールド内の文字が検出されてしまうことで失敗していることが分かった。
そこで、挿入したフィールドの分検索位置をずらすのも大変なので、面倒なので上向きの検索にして末尾から処理することにした。
また、ルビの数が増えたときに一番時間がかかる処理なので、画面下部のステータスバーに進捗を表示するようにした。
APIの仕様
クレジット表示
個人用ではあるが、念のためクレジット表示もしておく。
エラー処理
してない。リクエストによってはエラーが返ることもあると思うが、自分用のプログラムなので丁寧なエラー処理は省略した。
リボンに登録
実行しやすくするために、組んだマクロをボタンとして登録して実行しやすくした。Wordのもともとのルビ機能と、前回組んだ青空文庫形式のルビを変換するマクロも一緒にまとめた。
実行
実験1
あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモリーオ市、郊外のぎらぎらひかる草の波。
カタカナはちゃんとスルーして、漢字のみに正しいルビを設定することができた。うつくしい。
実験2
今日は一月一日の日曜日で日本では祝日、日向市は五日ぶりの小春日和です。
「ついたち」、「いつか」はうまく設定できなかった。あれ、よく見たら漢数字はスルーされている? 見なかったことにしよう。ルビの正確な解釈はかなり難しく、文脈によって変わる読み方や固有名詞はどうせ自分で設定せざるを得ない。また、どうせ自分で読んで点検するので、細かいことは気にしない。
余談
1. ScriptControlにハマった話
ChatGPTに今回のコードを書いてもらったときに、外部ライブラリの「VBA-JSON」を使うコードを提示された。できればそういうのなしで解決したかったが、VBA-JSONを使わない方法としてよくヒットするScriptControlを使う方法がうまくいかなかったため、最終的にVBA-JSONを使うことにした。
「VBA JSON」でググると、ScriptControlを使ってパースする方法がたくさんヒットする。しかし、どれを試しても「クラスが登録されていません」というエラーが出る。参照設定をしたり、レジストリをいじったりといろいろ試すが、どれもうまくいかない。調べていくと、64bit環境では動かないとのことだった。
私と同じハマり方をした方の記事があったので、大変助かった。大いに参考にさせていただいた。
2. 学年別漢字配当表に基づくルビ振り
実はこのAPIにはもう一つすごい機能があって、学習指導要領の学年別漢字配当表に基づくルビ振りができる。リクエストのparams/gradeに定数を指定することで、例えば「小学校の1~3年生で習う漢字にはふりがなをつけない」といったことができる。
学年(注1)を指定します。
1: 小学1年生向け。漢字(注2)にふりがなを付けます。
2: 小学2年生向け。1年生で習う漢字にはふりがなを付けません。
3: 小学3年生向け。1~2年生で習う漢字にはふりがを付けません。
4: 小学4年生向け。1~3年生で習う漢字にはふりがなを付けません。
5: 小学5年生向け。1~4年生で習う漢字にはふりがなを付けません。
6: 小学6年生向け。1~5年生で習う漢字にはふりがなを付けません。
7: 中学生以上向け。小学校で習う漢字にはふりがなを付けません。
8: 一般向け。常用漢字にはふりがなを付けません。
無指定の場合、ひらがなを含むテキストにふりがなを付けます。注1:学年は「小学校学習指導要領」の付録「学年別漢字配当表」(1989年3月15日文部科学省告示。1992年4月施行)を参考に設定されています。
注2:JIS X 0208が定める漢字
ただし、注記を見るとわかるが、これは1989年の配当表に基づいている。小学校で習う学習漢字は2010年の常用漢字の改定に伴って追加されているので、2024年現在の最新の配当表とは違うことになる。したがって、この機能を丁寧に使用するような組み方をしても信頼に欠けると考えた。
また、私の用途ではあまりこういう使い方はしないので、今回は1に固定し、すべての漢字にふりがなをつける使い方にした。
最後に
成果
- そこそこ実用的なレベルで、ルビの設定を一括で自動処理することができた
AIを使ってさらに精度の高い自然なふりがなを得るとか、プログラムをアドイン化するとかまだできることはありそうだが、私の場合の用途と使用頻度では労力が見合わないので、この辺にしておこうと思う。もし精度の高い自然なルビ振りができるツールがあったら、ぜひコメントください。
参考サイト
記事中で紹介しなかったが参考にしたサイト。