ルビを振るべき親文字の箇所を取得する
前回
紹介したのは、「青空文庫」からダウンロードしたテキストファイルから、ルビ情報(「《 》」で括られた文字列)を削除する、という対応だった。
しかし、いくら読みやすさのためといえども、ルビ情報全削除はあんまりである。
当然、次に目指すべきは、
ちゃんとルビを振ろうぜ
ということになる。
目次
こんなことをします
ルビの親文字に相当する部分をRange
オブジェクトとして取得するために必要なFunctionを作る。
通常であれば、これは人間にしか(完全には)なし得ない作業だが、幸い「青空文庫」では明確なルールに基づいてテキストデータを作ってくれているので、なんとかなる。
詳細は後述。
とにかく、たとえば、
この画像の中でいえば、
左の手で小次郎の鼻息《びそく》をそっと触れてみた。
の中から、
このように、親文字となるべき「鼻息
」の部分をRange
オブジェクトとして取得するのである。
考え方
先述の通り、「青空文庫」では、次のようなルールでルビ情報をテキストデータ上で表現している。
- 親文字の直後に「
《 》
」で括ってルビを示す
【例】:小次郎の鼻息《びそく》→「鼻息」が親文字 - ルビを振らない漢字と隣接しているときは、区切りの部分に「|」(全角バーティカルバー)が入っている
【例】:柳生|石舟斎《せきしゅうさい》→「石舟斎」が親文字
実に明解。
つまり、「《
」の手前から遡り、非漢字にぶつかるか、「|」にぶつかるかしたら、その直後の位置までが親文字ということだ。
処理の手順は次の通り。すなわち、
- 「
《 》
」で括られた部分のRange
オブジェクトを取得 - 取得した
Range
オブジェクトを一旦開始方向に向けて潰し、その位置(*1)を取得する - そこから1文字づつ遡って、漢字かどうか、または「|」かどうかを調べる
- 非漢字または「|」にぶつかったら、その直後の位置を取得する(*2)
- (*1)、(*2)で取得した位置を元に
Range
オブジェクトを作成する
このような手順である。
ルビの親文字の箇所を取得する
指定した文字の位置を取得する
これは、前回も使用したgetNextPosition
メソッドを使う。
コードを再掲する。
リスト1 標準モジュールFormatStrings
Private Function getNextPosition( _ ByVal FindText As String) As Long Dim ret As Long ret = -1 With Selection.Find .Text = FindText .Replacement.Text = "" .Wrap = wdFindStop .Format = False .Highlight = False .MatchCase = False .MatchFuzzy = False .MatchWholeWord = False .MatchByte = False .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False End With Call Selection.Find.Execute If Not Selection.Find.Found Then GoTo ReturnValue ret = Selection.Range.Start Call Selection.Collapse(wdCollapseEnd) ReturnValue: getNextPosition = ret End Function
漢字かどうかを判定するFunction
これまた、ずいぶん前に書いたコードを再利用する。
リスト2 標準モジュールFormatStrings
Private Function isKanji( _ ByVal tgtChar As String) As Boolean isKanji = False Dim char As String * 1 char = tgtChar If CInt(Asc(tgtChar)) > 0 Then Exit Function If CInt(Asc(char)) < CInt(&H889F) Then Exit Function isKanji = True End Function
Private
メソッドで、1文字だけ渡して漢字かどうかを判定するだけの用途に使うので、ややこしい引数チェック等はなし。
親文字の箇所を取得するFunction
ここまでで準備はオッケー。後はコーディングあるのみ!
リスト3 標準モジュールFormatStrings
Private Function getRubiedCharPosition( _ ByVal BasePos As Long, _ Optional ByVal Delimiter As String = "|") As Long '……(1)' Dim ret As Long '……(2)' ret = 0 Dim tgtDoc As Document Set tgtDoc = ActiveDocument Call tgtDoc.Range(BasePos, BasePos).Select '……(3)' Dim tmp As String '……(4)' Do tmp = tgtDoc.Range(BasePos - 1, BasePos).Text '……(5)' If Not isKanji(tmp) Or tmp = Delimiter Then '……(6)' ret = BasePos '……(7)' Exit Do Else BasePos = BasePos - 1 '……(8)' If BasePos < 0 Then Exit Do End If Loop getRubiedCharPosition = ret End Function
まず、(1)の
Private Function getRubiedCharPosition( _ ByVal BasePos As Long, _ Optional ByVal Delimiter As String = "|") As Long
で引数と返り値の設定。
引数BasePos
は探索の開始位置。「青空文庫」でいえば、「《
」の直前の位置ということになる。
引数Delimiter
は、親文字開始の目印となる文字。
「青空文庫」の場合、「|
」という非漢字文字が区切りの役割を果たしているので、デフォルト値を「|
」にする意味はない。
とにかく、「《
」の直前の位置から遡り、非漢字文字にぶつかったらその直後の位置を整数値で返すので、返り値はLong
型。
(2)の
Dim ret As Long ret = 0
で返り値用変数と初期値を設定。
文書の先頭から親文字が始まる可能性があるので、初期値は0
。
先頭まで行っても非漢字文字にぶつからないということは、(「青空文庫」のボランティアスタッフがミスったのでない限り、)文書の先頭が親文字の開始位置だということだ。
探索を繰り返して、非漢字文字にぶつからないまま文書先頭に至ったときには「0
」を返すようにするために、こうしておく。
(3)の
Call tgtDoc.Range(BasePos, BasePos).Select
で、探索開始位置にカーソルをセット。
(4)からの11行
Dim tmp As String Do tmp = tgtDoc.Range(BasePos - 1, BasePos).Text '……(5)' If Not isKanji(tmp) Or tmp = Delimiter Then '……(6)' ret = BasePos '……(7)' Exit Do Else BasePos = BasePos - 1 '……(8)' If BasePos < 0 Then Exit Do End If Loop
が探索過程。
まず、(5)の
tmp = tgtDoc.Range(BasePos - 1, BasePos).Text
で、先頭方向に1文字分の文字を取得。
(6)の
If Not isKanji(tmp) Or tmp = Delimiter Then
で、その文字が〝非漢字または区切り文字〟であるかどうかをを判定し、True
ならば、(7)の
ret = BasePos Exit Do
で位置を返す。
非漢字文字にぶつかった時点で、BasePos
の値は非漢字文字の直後、すなわち親文字の開始位置を表すので、これでよい。
(6)の判定結果がFalse
だったら、(8)の
BasePos = BasePos - 1 If BasePos < 0 Then Exit Do
でBasePos
の値を1
減らす。
また、1
減らした段階でBasePos
の値が負の数になっていたら、それ以上探索しても無駄なのでループを抜ける。(0
が返ることになる。)
ルビの親文字の箇所を取得する
ここまでで準備はできた。
まさに、「時は来た、それだけだ!」状態である。
上掲getNextPosition
で、「《
」の位置を取得すれば、それが〝ルビの親文字の箇所〟の終端となり、getRubiedCharPosition
で、文書前方の直近の非漢字文字の直後の位置を取得すれば、それが〝ルビの親文字の箇所〟の始端となるのである!
つまり、たとえば、
このようにカーソルを置いて、次のコードを実行すれば、親文字の部分が選択されることになる。
リスト4 標準モジュール
Private Sub test00() Dim rng As Range Dim startPos As Long Dim endPos As Long endPos = getNextPosition("《") startPos = getRubiedCharPosition(endPos) Set rng = ActiveDocument.Range(startPos, endPos) Call rng.Select End Sub
ほれ、この通り。
この状態で実行したら、
当然こうなる。
おわりに
あとは、親文字にルビを振り、「《 》」で括られた部分を削除するだけ。
ここまで来たら、あとは楽勝でしょう。