参照元に参照先の通し行番号を書き込む(2)
たとえば、

みたいなドキュメントがあるとする。
参照指示性を高めるために、全ての行に通しで行番号を振っている。
これは、芦田宏直氏のアイディアで、詳しいことは氏の著書『シラバス論』(2019 晶文社)をご覧ください。
問題は、文中に「〇〇行目を参照」などと書いた場合。
「相互参照」機能を使えば、段落番号とかページ番号なんかは設定できるんだが、通しの行番号というのがない。
参照先の位置などというものは、編集の都合で揺れ動くものなので、「相互参照」的に設定できないのは非常につらい。
そこで、
このとき、ブックマークを利用して参照先の通しの行番号を割り出し、参照元の行番号の部分を書き換える方法を編み出した。
あとは、前回発覚した
ブックマーク部分の文字を書き換えたらブックマークが消滅する問題
を解消すればよろしい。
消滅するブックマークを復活させる考え方
まず、次のように考えた
- 参照元の
[Range].Textを書き換える前に、参照元のRangeオブジェクトを取得しておく - 参照元の
[Bookmark].Range.Textを書き換える Document.Bookmarks.Addメソッドを、参照元のブックマーク名、1.で取得したRangeオブジェクトを渡して実行する
これでうまくいく、と思った。
しかし、これではうまくいかない。
2.で参照元の[Bookmark].Range.Textを書き換えた時点で、1.で取得したRangeオブジェクトが潰れてしまっているのだ。
たとえば、「○行目」の「○」の部分(1文字選択の状態)だったはずのRangeオブジェクトが、2.で「48行目」というふうに書き換えたとすると、「48」の手前のカンチャン(0文字選択の状態)になってしまうのだ。
これではまずい。
そこで、次のような手順を踏むことにした。
- 参照元の
[Range].Textを書き換える前に、参照元のRangeオブジェクトを取得しておく - 参照元の
[Bookmark].Range.Textを書き換える - 1.で取得した
RangeオブジェクトについてSelectメソッドを実行する。(書き換えた行番号の手前にカーソルが移動する。) Selection.MoveRightメソッドを用いて、行番号の文字数分だけカーソルを右に動かす。(右にドラッグする。)Document.Bookmarks.Addメソッドを、参照元のブックマーク名、4.で取得したSelection.Rangeオブジェクトを渡して実行する
こんなふうにした。
参照先の行番号に応じて参照元を書き換えるメソッド
上記の考えに基づいて作成したメソッドがコチラ。
リスト1
Public Sub refreshLineNumberReference( _
ByVal TargetDocument As Document, _
ByVal ReferrerName As String, _
ByVal ReferenceName As String)
'### 参照先ブックマークのある行番号を取得して、参照元 ###'
'### ブックマークの箇所の行番号を書き換える ###'
'///ReferrerName :参照元ブックマーク名'
'///ReferenceName :参照先ブックマーク名'
Dim Doc As Document
Set Doc = TargetDocument
'参照先、参照元ブックマークが存在しなかったらExit'
If Not bookmarkExists(Doc, ReferenceName) Then Exit Sub
If Not bookmarkExists(Doc, ReferrerName) Then Exit Sub
'メインの処理'
'参照元ブックマークを取得'
Dim bmFrom As Bookmark
Set bmFrom = Doc.Bookmarks(ReferrerName)
'参照先の行番号を取得'
Dim lineNum As Long
lineNum = getLineNumber(Doc.Bookmarks(ReferenceName).Range)
'参照元のRangeオブジェクトを取得'
Dim tgtRange As Range
Set tgtRange = bmFrom.Range
'参照元の行番号を書き換える'
Dim tmp As String
tmp = CStr(lineNum)
bmFrom.Range.Text = tmp
'参照元ブックマークが消滅しているので、復元する'
'Rangeオブジェクトが潰されてしまっているので、行番号を表す'
'文字数分右に広げてRagneオブジェクトを取得し直す'
Call tgtRange.Select
Call Selection.MoveRight(wdCharacter, Len(tmp), wdExtend)
Set tgtRange = Selection.Range
'再度ブックマークを設定する'
Call Doc.Bookmarks.Add(ReferrerName, tgtRange)
End Sub
Private Function bookmarkExists( _
ByVal tgtDoc As Document, _
ByVal tgtName As String) As Boolean
'///ブックマーク名の存否を確認'
bookmarkExists = True
Dim i As Long
For i = 1 To tgtDoc.Bookmarks.Count
If tgtDoc.Bookmarks(i).Name = tgtName Then
Exit Function
End If
Next
bookmarkExists = False
End Function
Public Function getLineNumber( _
ByVal tgtRange As Range) As Long
Dim ret As Long
'///tgtRangeのあるページ番号を取得'
Dim currPage As Long
currPage = tgtRange.Information(wdActiveEndPageNumber)
'tgtRangeのあるページ内での行番号を取得'
Dim currLine As Long
currLine = tgtRange.Information(wdFirstCharacterLineNumber)
'tgtRangeが1ページ目にあるときは、その行番号を返す'
If currPage = 1 Then
ret = currLine
GoTo Finalizer:
End If
'2ページ以上ある時は、手前のページまでの累計を足さなければいけない'
Dim Doc As Document
Set Doc = tgtRange.Parent
'カーソル位置を記録'
Dim orgRange As Range
Set orgRange = Selection.Range
'文書の先頭にカーソルを置く'
Call Doc.Range(0, 0).Select
'1ページ目の最終位置を取得'
Dim pageEnd As Long
'1ページ目の最終位置を選択'
Dim i As Long
For i = 1 To currPage - 1
pageEnd = Doc.Bookmarks("\Page").End
Call Doc.Range(pageEnd - 1, pageEnd - 1).Select
ret = ret + Selection.Range.Information(wdFirstCharacterLineNumber)
'次のページの先頭へ'
Call Selection.MoveRight(wdCharacter, 1, wdMove)
Next
ret = ret + currLine
'カーソル位置を戻す'
Call orgRange.Select
Finalizer:
getLineNumber = ret
End Function
Rangeオブジェクトのある通し行番号を返すメソッドのコードを再掲したので、異様にタテ長になってしまったが、気にしないでくだされ。
今回もかなり細かくコメントを入れたので、説明は省略。
使ってみる


このように、参照先と参照元にそれぞれ「参照先01」、「参照元01」という名前のブックマークを設定し、次のコードで使ってみる。
リスト2
Private Sub test00()
Dim Doc As Document
Set Doc = Application.ActiveDocument
Call LineNumUtil.refreshLineNumberReference( _
Doc, _
"参照元01", _
"参照先01")
End Sub

ほれ。このように、ちゃんと参照先の行番号に置き換わっておる。

さらに、参照先をテキトーに動かしてから実行しても、ちゃんと参照先の行番号に置き換わっておる。
おわりに
やはりWordのRangeオブジェクトは癖が強い。まだまだ理解が足りないな……。
もっとわかってきたら、洗練されてくると思う。