参照元に参照先の通し行番号を書き込む(2)

参照元に参照先の通し行番号を書き込む(2)

たとえば、

f:id:akashi_keirin:20200607183745j:plain

みたいなドキュメントがあるとする。

参照指示性を高めるために、全ての行に通しで行番号を振っている。

これは、芦田宏直氏のアイディアで、詳しいことは氏の著書『シラバス論』(2019 晶文社)をご覧ください。

問題は、文中に「〇〇行目を参照」などと書いた場合。

「相互参照」機能を使えば、段落番号とかページ番号なんかは設定できるんだが、通しの行番号というのがない。

参照先の位置などというものは、編集の都合で揺れ動くものなので、「相互参照」的に設定できないのは非常につらい。

そこで、

akashi-keirin.hatenablog.com

このとき、ブックマークを利用して参照先の通しの行番号を割り出し、参照元の行番号の部分を書き換える方法を編み出した。

あとは、前回発覚した

ブックマーク部分の文字を書き換えたらブックマークが消滅する問題

を解消すればよろしい。

消滅するブックマークを復活させる考え方

まず、次のように考えた

  1. 参照元[Range].Textを書き換える前に、参照元Rangeオブジェクトを取得しておく
  2. 参照元[Bookmark].Range.Textを書き換える
  3. Document.Bookmarks.Addメソッドを、参照元のブックマーク名、1.で取得したRangeオブジェクトを渡して実行する

これでうまくいく、と思った。

しかし、これではうまくいかない。

2.で参照元[Bookmark].Range.Textを書き換えた時点で、1.で取得したRangeオブジェクトが潰れてしまっているのだ。

たとえば、「○行目」の「○」の部分(1文字選択の状態)だったはずのRangeオブジェクトが、2.で「48行目」というふうに書き換えたとすると、「48」の手前のカンチャン(0文字選択の状態)になってしまうのだ。

これではまずい。

そこで、次のような手順を踏むことにした。

  1. 参照元[Range].Textを書き換える前に、参照元Rangeオブジェクトを取得しておく
  2. 参照元[Bookmark].Range.Textを書き換える
  3. 1.で取得したRangeオブジェクトについてSelectメソッドを実行する。(書き換えた行番号の手前にカーソルが移動する。)
  4. Selection.MoveRightメソッドを用いて、行番号の文字数分だけカーソルを右に動かす。(右にドラッグする。)
  5. 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オブジェクトのある通し行番号を返すメソッドのコードを再掲したので、異様にタテ長になってしまったが、気にしないでくだされ。

今回もかなり細かくコメントを入れたので、説明は省略。

使ってみる

f:id:akashi_keirin:20200607183748j:plain

f:id:akashi_keirin:20200607183751j:plain

このように、参照先と参照元にそれぞれ「参照先01」、「参照元01」という名前のブックマークを設定し、次のコードで使ってみる。

スト2
Private Sub test00()
  Dim Doc As Document
  Set Doc = Application.ActiveDocument
  Call LineNumUtil.refreshLineNumberReference( _
                     Doc, _
                     "参照元01", _
                     "参照先01")
End Sub

f:id:akashi_keirin:20200607183801g:plain

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

f:id:akashi_keirin:20200607183818g:plain

さらに、参照先をテキトーに動かしてから実行しても、ちゃんと参照先の行番号に置き換わっておる。

おわりに

やはりWordのRangeオブジェクトは癖が強い。まだまだ理解が足りないな……。

もっとわかってきたら、洗練されてくると思う。