「ネオ写経」のすすめ

「ネオ写経」のすすめ

新型コロナウイルス対応で外出の自粛が求められる中、みなさまいかがお過ごしでしょうか。

ろくにテレワーク環境も整っていないのに、「とにかくテレワークだ!」的に導入されてしまった事業所も、それなりにあると思います。

個人的には、この期間は〈終息後のダメージ回復〉を効果的に行うために個人が力量を高める機会ととらえるのが良いと思います。それができる余裕のある業界に限られますが……。

「ネオ写経」とは

私が勝手に考えました。

もともと、プログラミング界隈で「写経」といえば、サンプル・コードの類を写すこと。(ですよね?)

ただ、私は「写経」というものをしたことがほぼない。写しているうちに、「あら? じゃあ、ここはこうした方がおもろいやんけ。」とか、「ついでにこうしといたれ。」みたいなのが出てくるから、「般若心経」を写していたはずなのに、なぜか「あほだら経」が出来上がっていた、ということが起こる。

そんな私が考えた「ネオ写経」とは!?

既存クラスをラップしたクラスを作る。

これ。

ニセFileSystemObjectを作る

FileSystemObjectは便利だ。

しかも、プロパティ名とかメソッド名は、さすがプロが作っただけあって、実に良くできている。コードが実に読みやすくなる。

ところが、デフォルトでは使えない。

いちいち参照設定をせねばならん。

もちろんCreateObjectを使えば参照設定せずにすむ。

しかし、これだとObject型変数に突っ込んで使用することになり、コーディング時に入力補完の恩恵が得られずイマイチ。

もちろん、コーディング時に参照設定をしておき、完成したら参照設定を切る、という方法もあるにはあるが、それはそれでメンドクサイ。

だったら、FileSystemObjectクラスをラップしたFileSystemObjectクラスを自作しちまえばいいんでねえの!

コンストラク

これは簡単。Class_InitializeScripting.FileSystemObjectクラスのインスタンスを得ればよい。

まず、プロジェクトにクラスモジュールを挿入し、オブジェクト名をFileSystemObjectにする。そして、クラスモジュールに次のコードを書く。

クラスモジュール FileSystemObject
'Declarations Section'
'Module Level Variables'
Private fsObj As Object

'Constructor'
Private Sub Class_Initialize()
  Set fsObj = CreateObject("Scripting.FileSystemObject")
End Sub

これだけ。

これで、プロパティとかメソッドをこの変数fsObjを経由して呼び出すようにすればいい。

プロパティとメソッドの実装(笑)

大袈裟な見出しだが、プロパティをメソッドを実装(笑)するときの教科書が、おれたちの「オブジェクト ブラウザー」様だ!

ブラウザー」と延ばしているところがボスキャラ感があっていいよね。

[F2]キーを押すか何かして、「オブジェクト ブラウザー」を開き、FileSystemObjectを指定すると、

f:id:akashi_keirin:20200425075449j:plain

こんなふうにメンバを確認することができるし、

f:id:akashi_keirin:20200425075453j:plain

こんなふうに各メンバの実装方法を確認することもできる。

あとはコーディングあるのみ!

……である!

プロパティの実装(笑)

……といっても、FileSystemObjectにはプロパティは一つしかない。これは意外だった。なんと、Drivesコレクションを返すDrivesプロパティしかないのだ。

実装方法は、オブブラ(略すなw)によると、

Property Drives As Drives

とのこと。

もちろん、このまま打ち込んでもコンパイルエラーになるので、脳内でコードを補完して

Public Prooerty Get Drives() As Drives

とする。

もちろん、Microsoft Scripting Runtimeを参照設定しないのだから、As Drivesではまずい。で、

Public Prooerty Get Drives() As Object

と、返り値をObject型に改めておく。

あとは中身。

Dim ret As Object
Set ret = fsObj.Drives    '……(1)'
Set Drives = ret    '……(2)'

これでいい。変数fsObjはモノホンのFileSystemObjectクラスのインスタンスを指しているから、そのDrivesプロパティの返り値はもちろんモノホンのDrivesコレクション。

だから、まずは(1)の

Set ret = fsObj.Drives

で返り値用の変数retにモノホンのDrivesコレクションを突っ込んでおき、(3)の

Set Drives = ret

でニセのDrivesプロパティの返り値としてやる。

プロシージャ全体は

Public Prooerty Get Drives() As Object
  Dim ret As Object
  Set ret = fsObj.Drives
  Set Drives = ret
End Property

こう。

メソッドの実装(笑)

メソッドも基本的にはこの方法。

f:id:akashi_keirin:20200425075456j:plain

このように、オブブラ(笑)で、

  • SubFunctionの別
  • 引数リスト
  • 引数がOptionalかどうか
  • 返り値の型

を確認し、それに応じてコーディングすればいい。

たとえば先の画像のCreateTextFileメソッドならば、

  • 種別はFunction
  • 引数はFileNameOverWriteUnicodeの三つ。
  • 引数OverWriteUnicodeOptional
  • 返り値はTextStream

なので、それに応じてコーディングする。

Public Function CreateTextFile( _
	    ByVal FileName As String, _
   Optional ByVal OverWrite As Boolean = True, _
   Optional ByVal Unicode As Boolean = False) As TextStream

となる。

しかし、TextStreamクラスもScripting.FileSystemObjectクラスのメンバなので、Objectにする。

Public Function CreateTextFile( _
	    ByVal FileName As String, _
   Optional ByVal OverWrite As Boolean = True, _
   Optional ByVal Unicode As Boolean = False) As Object

こうなる。

プロシージャ全体は

Public Function CreateTextFile( _
	    ByVal FileName As String, _
   Optional ByVal OverWrite As Boolean = True, _
   Optional ByVal Unicode As Boolean = False) As Object
  Dim ret As Object
  Set ret = fsObj.CreateTextFile(FileName, OverWrite, Unicode)
  Set CreateTextFile = ret
End Function

こう。

引数が列挙体型のやつがある

あと、メソッドの中には引数がScripting.FileSystemObjectの中で定義された列挙体であるものがある。

たとえば、

f:id:akashi_keirin:20200425075458j:plain

このGetStandardStreamメソッドの場合、

Function GetStandardStream(StandardStreamType As StandardStreamTypes, [Unicode As Boolean = False]) As TextStream

とあるように、第1引数StandardStreamTypeStandardStreamTypesという見慣れない型である。

こいつは、オブブラ(笑)で見ると

f:id:akashi_keirin:20200425075501j:plain

となっているように、Scriptingクラスで定義された列挙体なんである。

当然、Microsoft Scripting Runtimeを参照設定していないと使えない。

そういうときはどうするか。

このニセFileSystemObjectクラスモジュール内でPublic Enumにしてしまえばいいのである!

'Declarations Section'
'Constants'
Public Enum StandardStreamTypes
  StdIn = 0
  StdOut = 1
  StdErr = 2
End Enum

列挙体のメンバがそれぞれどの数値を表しているのか、というのもオブブラ(笑)先輩を見ればわかる。

f:id:akashi_keirin:20200425075504j:plain

ほれ。こんなふうに。

Scripting.__MIDL___MIDL_itf_scrrun_0001_0001_0003ちゅうのは何のことやらわからんがw

おわりに

上記のようにして、ひたすらプロパティ・メソッドを実装(笑)し続けることを、「ネオ写経」と呼んでおります。

オブジェクトの仕組みがよくわかって実に勉強になります。

「新型コロナ自粛で勉強ぐらいしかすることがない」という人は、一度やってみてはいかがでしょうか?

ちなみに、FileSystemObjectは、配下にDrivesDriveFoldersFolderFilesFileTextStreamというScripting内のオブジェクトを抱えているので、本気でFileSystemObjectクラスを丸ごとラップしようと思ったら、全部で八つもクラスモジュールを作ることになりますw

f:id:akashi_keirin:20200425075507j:plain

こんなふうに。

特に、Folders-Folderのような階層構造を持つクラスを表現するのにめちゃくちゃ頭を使いましたw

FormattedTextプロパティの怪

FormattedTextプロパティの怪

f:id:akashi_keirin:20200328155256j:plain

Writing Word Macros』という本を買った。

FormattedTextプロパティ

Rangeオブジェクトのところを読んでいたら、FormattedTextというプロパティについて書いてあった。

へえ。そんなものがあったのか。

で、『Word 2013 developer docs』(オフラインヘルプ)で調べてみた。

すると、

f:id:akashi_keirin:20200328154520j:plain

Returns or sets a Range object that includes the formatted text in the specified range or selection. Read/write.

こんなふうに書いてある。

FormattedText」という名前だが、Range型らしい。

まあ、書式情報を含んでいないといけないわけだから、String型のわけがないのだが。

実験

んで、ちょいと実験。

おなじみ、

f:id:akashi_keirin:20200328154522j:plain

このようなDocument(笑)を準備。

この中で、ゴシック体になっている「月面宙返り」の部分を、このDocument(笑)のケツに挿入するコードを書いてみる。

リスト1
Private Sub testFormattedTextProperty()
  Dim rng As Range
  Set rng = Selection.FormattedText  '……(1)'
  With ActiveDocument
    Call .Range(.Range.End - 1, .Range.End - 1).Select  '……(2)'
    Set Selection.FormattedText = rng  '……(3)'
  End With
End Sub

(1)の

Set rng = Selection.FormattedText

で変数rngSelectionオブジェクトのFormattedTextプロパティの返り値(Range型)を突っ込む。

あとは

With ActiveDocument
  Call .Range(.Range.End - 1, .Range.End - 1).Select  '……(2)'
  Set Selection.FormattedText = rng  '……(3)'
End With

の(2)でDocument(笑)の末尾位置を選択し([Range].Endプロパティは、末尾の改段落記号まで含めた数値を返すので、1を引いておかないとカーソル移動ができず、エラーになるので注意。)、(3)でそのFormattedTextプロパティに(1)で取得したRangeオブジェクト(rng)をセットする。

これで盤石である。時は来た。それだけだ!

先のDocument(笑)の「月面宙返り」の部分を

f:id:akashi_keirin:20200328154525j:plain

このように選択して、リスト1を実行。

f:id:akashi_keirin:20200328154529j:plain

な・・・・なんだってーーー!?

なんと、コンパイルエラー……。

「不正」呼ばわりである。

冗談半分で、リスト1の(3)の部分を

Selection.FormattedText = rng

にして実行してみる。

すると、

f:id:akashi_keirin:20200328154533j:plain

えっ?! なんで???

やりたかったことが実現できたとはいえ、わけがわからん。

よくわからないこと

オブジェクト ブラウザーによると、

f:id:akashi_keirin:20200328154535j:plain

Word.Rangeオブジェクトの既定メンバはTextプロパティである。

ということは、

Selection.FormattedText = rng

というのは

Selection.FormattedText.Text = rng.Text

ということにならないのだろうか。

FormattedTextプロパティがRange型であり、「Read/write」である以上、

Set Selection.FormattedText = rng

でないとおかしいように思うのだが……。

おわりに

ちなみに、たとえばリスト1の(3)のところを

Selection.FormattedText.Text = rng

にして実行すると、

f:id:akashi_keirin:20200328154539j:plain

このようにわけのわからない結果になる。

また、そもそもはコーディング・ミスだったのだが、リスト1の(3)を

Selection.FormattedText = rng.FormattedText

としても、

f:id:akashi_keirin:20200328154542j:plain

このように正しい(?)結果になる。

また、リスト1の(3)を

Selection.FormattedText = rng.Text

にすると、

f:id:akashi_keirin:20200328154545j:plain

「型が一致しません」というコンパイルエラーになる。

左辺と右辺をあれこれ変えてみた結果をまとめたのが

f:id:akashi_keirin:20200328154549j:plain

これ。

わからぬ……。

Tableオブジェクトの怪(Word)

Tableオブジェクトの怪(Word)

実に気色悪い現象に出くわしたので報告。

表の余分な行を削除する

たとえば、Wordでドキュメント内の表にデータを差し込むようなとき、

f:id:akashi_keirin:20200325081704j:plain

このように、使用しない行が生ずることがある。

宛先によってデータの数が異なるとき、テキトーな上司なら「ま、別にええんちゃう?」で済むのだが、神経質な上司だったりすると、「空白行は消さんかい!」などということに。

そこで、マクロで空白行を削除することを企てるのである。

マクロで空白行を削除する

次のようなコードで、空白行の削除を試みる。

リスト1
Private Sub test03()
  Dim tbl As Table
  Set tbl = ActiveDocument.Tables(1)  '……(1)'
  Dim i As Long
  With tbl    '……(2)'
    For i = .Rows.Count To 2 Step -1  '……(3)'
      If .Cell(i, 1).Range.Text = Chr(13) & Chr(7) Then  '……(4)'
        Call .Rows(i).Delete
      Else
        Exit For
      End If
    Next
  End With
End Sub

まず、(1)の

Set tbl = ActiveDocument.Tables(1)

で対象の表(Tableオブジェクト)を変数tblにぶち込む。

(2)の

With tbl

で記述をまとめておいて、(3)の

For i = .Rows.Count To 2 Step -1
  If .Cell(i, 1).Range.Text = Chr(13) & Chr(7) Then  '……(4)'
    Call .Rows(i).Delete
  Else
    Exit For
  End If
Next

Forループ。

「削除するときはケツから!」の原則に基づいて、TableオブジェクトのRowsコレクションのCountプロパティの値、すなわち表の行数からスタートして、2行目まで繰り返すことにする。

ループ内では、(4)の

If .Cell(i, 1).Range.Text = Chr(13) & Chr(7) Then

で、1列目に文字が入っているかどうかを判定。

Wordの表では、文字の入っていないセルにはChr(13)Chr(13)が入っている。

akashi-keirin.hatenablog.com

セル内に文字が入っていなければ

Call .Rows(i).Delete

で行ごと削除。

セル内に文字が入っていれば、(上の行から順にデータを入れている以上)これ以上削除する行はないと言うことだからElseブロックに進んで

Exit For

でループを抜ける。

実行

これで基本的にはうまいこと行くはずである。

しかし、ループに突入し、一つ目(つまり、5行目)を削除した途端、

f:id:akashi_keirin:20200325081707j:plain

工工工エエエエエエェェェェェェ(゚Д゚)ェェェェェェエエエエエエ工工工

何故、何故なんだ~?!

突然表の横幅がビニョーーーーンと伸びてしもたやないか……。

[Table].ColumnsコレクションからColumnオブジェクトを取得してWidthプロパティを調べてみる。

f:id:akashi_keirin:20200325081711j:plain

上が無残にも横に引き延ばされてしまったTables(1)、下があらかじめ同じものをコピッペしておいたTables(2)である。

f:id:akashi_keirin:20200325081714j:plain

このように、全然違うサイズに変わり果ててしまっていることがわかる。

おわりに

さっぱりわけがわからん。

何故、何故なんだ~?!(2回目。)

改行マークの怪(Word)

改行マークの怪

前回

akashi-keirin.hatenablog.com

の続き。

改行マークの正体とは?

まず、

f:id:akashi_keirin:20200313075748j:plain

このようなドキュメント(笑)を用意し、画像のように改行マークを選択状態にしておく。

そして、イミディエイトに

?Asc(Selection.Range.Text)

と入力して[Enter]を押す。

f:id:akashi_keirin:20200313075751j:plain

このように、「11」を得た。

charset.7jp.net

コチラの文字コード表によると、「11」は、

f:id:akashi_keirin:20200313075754j:plain

なんと、「VT」というよくわからないものだった。

てっきり「10」の「LF」だと思っていたのだが。

検証

では、選択部分を文字コード10LF)」にするとどうなるのか。

f:id:akashi_keirin:20200313075758j:plain

このように改行マークを選択した状態でイミディエイトに

Selection.Range.Text = Chr(10)

と入力して[Enter]を押す。すると、

f:id:akashi_keirin:20200313075801j:plain

工工工エエエエエエェェェェェェ(゚Д゚)ェェェェェェエエエエエエ工工工

これ、改段落マークとちゃいますのん???

さらに検証

では、この「改段落マーク」は何ものなのだろうか。

f:id:akashi_keirin:20200313075804j:plain

先ほど出現した「改段落マーク」(「Chr(10)」のはず。)を選択状態にして、イミディエイトに

?(Selection.Range.Text = vbCr)

と入力して[Enter]を押す。すると、

f:id:akashi_keirin:20200313075806j:plain

(゚Д゚)ハァ?(゚Д゚)ハァ?(゚Д゚)ハァ?

えっ……なんで……??? そうなの???

さらに、イミディエイトに

?(Selection.Range.Text = Chr(13))

と入力して[Enter]を押す。すると、

f:id:akashi_keirin:20200313075809j:plain

(゚Д゚)ハァ?(゚Д゚)ハァ?(゚Д゚)ハァ?

えっ……なんで……??? Selection.Range.TextChr(10)にしたやんか……。

さらに、イミディエイトに

?(Selection.Range.Text = Chr(10))

と入力して[Enter]を押す。すると、

f:id:akashi_keirin:20200313075813j:plain

(゚Д゚)ハァ?(゚Д゚)ハァ?(゚Д゚)ハァ?

そもそもSelection.Range.TextChr(10)にしたはずなのに、一周回ってChr(10)じゃなくなっとる……。

おわりに

さっぱりわけがわかりまへん。

改行・改段落の怪(Word)

改行・改段落の怪

前回

akashi-keirin.hatenablog.com

の続き。

前回のリスト1を再掲する。

前回のリスト1
'テキストの置換'
Private Sub replaceText(ByVal str1 As String, _
  With Selection.Find
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
  
  With Selection.Find
    Call .Execute(FindText:=str1, _
                  replacewith:=str2, _
                  Replace:=wdReplaceAll)
  End With
  
  With Selection.Find
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
End Sub

Private Sub removeUnsightlyCR()
  '連続するCarriageReturnを一つにする'
  Call replaceText(vbCr & vbCr, vbCr)  '……(*)'
End Sub

これの(*)の部分、replaceTextメソッドの第2引数をvbLfに変えたらどうなるのだろうか。やってみた。

vbCrをvbLfに置き換える

前掲リストの(*)部分を次のように書き換える。

Call replaceText(vbCr & vbCr, vbLf) 

そして、

f:id:akashi_keirin:20200313071553j:plain

おなじみ、このドキュメント(笑)を用意して、上掲コードを実行する。

f:id:akashi_keirin:20200313071556j:plain

な・・・・なんだってーーー!?

なかなか衝撃的な結果ではあるまいか。

てっきり

f:id:akashi_keirin:20200313071600j:plain

こうなるものと思っていたのだが。

おわりに

ますますわけがわからなくなってきたぞ。

VBAによる置換の怪(Word)

VBAによる置換の怪

ちょっと変な現象に出くわしたので報告。

無駄な改段落マークを削除する

最近、Webページ上で公開されている議事録の類をWordドキュメント化する作業にハマっている。今すぐ役に立つわけではないけれど、後で利用するときに楽かな、と思って。

Webページ上からWordドキュメントにテキスト部分をコピッペして、後は主にマクロを使って整形する。

そのときにやたら遭遇するのが

f:id:akashi_keirin:20200312074238j:plain

のようなパターン。

行と行の間に無駄な改段落マークがあるやつ。

まずはこいつを一掃したかった。

要は、二つ連なっている改段落マークを一つにすればよいのだから、次のようなコードでやった。

リスト1
'テキストの置換'
Private Sub replaceText(ByVal str1 As String, _
                        ByVal str2 As String)
  With Selection.Find    '……(1)'
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
  
  With Selection.Find    '……(2)'
    Call .Execute(FindText:=str1, _
                  replacewith:=str2, _
                  Replace:=wdReplaceAll)
  End With
  
  With Selection.Find    '……(3)'
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
End Sub

Private Sub removeUnsightlyCR()    '……(4)'
  '連続するCarriageReturnを一つにする'
  Call replaceText(vbCr & vbCr, vbCr)
End Sub

まずは、replaceTextメソッド。

str1str2の二つの引数を受け取って、ドキュメント中のstr1str2に置換する。それだけ。

(1)の

With Selection.Find
  Call .ClearFormatting
  Call .Replacement.ClearFormatting
End With

Findオブジェクトの設定をリセットする。

次に(2)の

With Selection.Find    '……(2)'
  Call .Execute(FindText:=str1, _
                ReplaceWith:=str2, _
                Replace:=wdReplaceAll)
End With

Find.Executeメソッドを実行する。

str1str2に置換したいので、引数FindTextstr1を、引数ReplaceWithstr2を渡す。

また、全て置換するために引数ReplaceにはwdReplaceAllを渡す。

後は、(3)の

With Selection.Find
  Call .ClearFormatting
  Call .Replacement.ClearFormatting
End With

で再度Findオブジェクトをリセットしておしまい。

このreplaceTextメソッドを、(4)の

Private Sub removeUnsightlyCR()
  '連続するCarriageReturnを一つにする'
  Call replaceText(vbCr & vbCr, vbCr)
End Sub

のように、str1vbCr & vbCr(二つ連なった改段落マーク)、str2vbCrを指定して実行することによって、行のカンチャンの目障りな改行マークを一掃するのである!

実行

さて、

f:id:akashi_keirin:20200312074240j:plain

この状態で、上記リスト1のremoveUnsightlyCRを実行すると、当然

f:id:akashi_keirin:20200312074243j:plain

こうなる。

しかし!

この状態で、イミディエイトに

?ActiveDocument.Paragraphs.Count

と打ち込んで[Enter]を押すと、

f:id:akashi_keirin:20200312074246j:plain

な、なんだってーーー!?

なんと、段落数は1なのである。

どう見ても5なのに。

標準機能で置換する

ちなみに、

f:id:akashi_keirin:20200312074249j:plain

f:id:akashi_keirin:20200312074251j:plain

このように、標準機能を用いて置換した場合、

f:id:akashi_keirin:20200312074255j:plain

段落数は5になる。VBAでやった場合と標準機能を用いた場合とで結果が異なるのである。まさにち~んw珍現象!

おわりに

テキストドキュメントを整形する機会のある人は、注意しましょう。

段落冒頭の半角スペースを除去する(Word)

各段落冒頭の半角スペースを取り除く

Webで公開されている議事録の類をWordドキュメント化することが割と増えた。

しばらく待っているとPDFで正式な議事録が出される場合もあるが、割と時間がかかる上、PDFだと記載内容をコピッペする際に割とめんどくさい。

そこで、Webページに掲載されている議事録のテキストをWordドキュメントにコピッペして整形する、という方法をとった。

段落冒頭にことごとく半角スペースがある問題

Webページから直接コピッペすると、改行位置もちゃんと反映されるので、整形するにあたっては実に楽。

ただし、今回私が取り扱った物件は、

行頭にことごとく半角スペースが入っている

という実にうっとうしいものであった。

何せ、Wordドキュメントで約30ページ、4万字超の議事録が五つも六つもあるのである。手作業で取り除くのはナンセンス。

ただ半角スペースの全てを取り除けば良いわけではないから、置換も使えない。

そこで、マクロでやることにした。

考え方

次のように考えた。

  • 取り除きたいのは段落冒頭の半角スペースに限る。
  • したがって、まずは改段落マークの場所(Rangeオブジェクト)を取得する。
  • 改段落マークの場所を取得したら、その次の文字の場所(Rangeオブジェクト)を取得する。
  • 次の文字の場所を表すRangeオブジェクトのTextプロパティの値を調べ、そいつが半角スペースだったら""で置きかえる。
  • 検索で改段落マークがヒットしなくなるまでループ

うむ、万全である!

指定した文字列の場所(Range)を取得するメソッド

getNextTextRangeメソッド
Private Function getNextTextRange( _
             ByVal tgtText As String) As Range
  Dim ret As Range
  With Selection.Find
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
  With Selection.Find
    .Text = tgtText
    .Replacement.Text = ""
    .Wrap = wdFindStop
    .Format = False
    .Highlight = False
    .MatchCase = False
    .MatchWholeWord = False
    .MatchByte = False
    .MatchAllWordForms = False
    .MatchSoundsLike = False
    .MatchWildcards = False
    .MatchFuzzy = False
  End With
  Call Selection.Find.Execute
  If Not Selection.Find.Found Then GoTo Finalizer
  Set ret = Selection.Range
  Call Selection.Collapse(wdCollapseEnd)
Finalizer:
  With Selection.Find
    Call .ClearFormatting
    Call .Replacement.ClearFormatting
  End With
  Set getNextTextRange = ret
End Function

Findオブジェクトを使う際の宿命、どうしてもタテ長になってしまう。しかし、やっていることは非常に簡単。引数tgtTextで受け取った文字列が見つかったら、その場所を取得してRangeオブジェクトを返すだけ。

段落冒頭の半角スペースを除去する

上記getNextTextRangeを用いて、同じく上記「考え方」を元に次のリスト1を作成。

リスト1
Private Sub removeSBSpaceAtTheTopOfParagraph()
  Dim tmpRange As Range
  Do
    Set tmpRange = getNextTextRange(vbCr)  '……(1)'
    If tmpRange Is Nothing Then Exit Do
    Call tmpRange.Select  '……(2)'
    Call Selection.Collapse(wdCollapseEnd)
    Call Selection.MoveRight(wdCharacter, 1, wdExtend)  '……(3)'
    If Selection.Range.Text = " " Then  '……(4)'
      Selection.Range.Text = ""
    End If
    DoEvents
  Loop
  Call ActiveDocument.Range(0, 0).Select
End Sub

いきなりDoループに突入!

(1)からの2行、

Set tmpRange = getNextTextRange(vbCr)
If tmpRange Is Nothing Then Exit Do

で先のgetNextTextRangeメソッドを用いて直近の改行改行マークの場所(Rangeオブジェクト)を取得。

tmpRangeNothingだったらループを抜ける。

次に、(2)からの2行、

Call tmpRange.Select
Call Selection.Collapse(wdCollapseEnd)

で、先ほど取得したRangeオブジェクトを選択状態にし、

f:id:akashi_keirin:20200308091738j:plain

さらに選択範囲を後方に向かって潰しておく。

f:id:akashi_keirin:20200308091741j:plain

そして、(3)の

Call Selection.MoveRight(wdCharacter, 1, wdExtend)

で、右に向かって1文字分だけ選択範囲を広げる。

これで、改行マークの次の1文字を選択した状態になる。

f:id:akashi_keirin:20200308091745j:plain

あとは、(4)からの3行、

If Selection.Range.Text = " " Then
  Selection.Range.Text = ""
End If

で、選択されている箇所(次の段落の冒頭)が半角スペースだったらそいつを""に置きかえる。

f:id:akashi_keirin:20200308091747j:plain

この繰り返し。

ちなみに、getNextTextRangeがドキュメント(笑)最後の改行マークの場所を取得したときは、

Call tmpRange.Select

を実行すると、

f:id:akashi_keirin:20200308091932j:plain

こうなって、

Call Selection.Collapse(wdCollapseEnd)

を実行して、

f:id:akashi_keirin:20200308091936j:plain

こうなって、

Call Selection.MoveRight(wdCharacter, 1, wdExtend)

を実行して、

f:id:akashi_keirin:20200308091939j:plain

こうなる。んで、この状態で次のループに突入してgetNextTextRangeメソッドを実行すると、

f:id:akashi_keirin:20200308091943j:plain

このように、なぜか改行マークが検索でヒットせず(Find.FoundプロパティがFalseを返す)、getNextTextRangeメソッドがNothingを返すので、無事にDoループから抜け出すことができる。

最後に動作の様子をお目にかけよう。

f:id:akashi_keirin:20200308091946g:plain



うむ、バッチリである!!!!!!!!!!!!!!!!

おわりに

んで、ここまで書いておいてアレなんですが……。

これ、

[Ctrl]+[ H ]で置換ダイアログ呼んで、「検索する文字列」に「^p 」(「 ^p」と半角スペース)、「置換後の文字列」に「^p 」と入力して置換したら一発

ということに気づきましたよ。とほほ……。