見出し画像

個サ作 #12 カレンダーLv.5 後編

こんにちは。

前回は正規表現を扱うカレンダーLv.5を途中までやりました。ほとんどが正規表現の学習でしたね。

過去のプログラムから流用する箇所は実装できており、使用する正規表現パターンもはっきりしています。全体としては80%ほどできているので残り20%をやっつけちゃいましょう。

今回もよろしくお願いします。


今回のゴール

今回はカレンダーLv.5が完成します。

今回のゴールだどん

エラーが発生する様子と正常動作する様子です。


カレンダーLv.5 後編

今、ソースコードがこんな状態ですね(書いてなかったらこのようにしてね)。

'正規表現の学習
Sub H_カレンダ5()

    Dim year As Integer
    Dim month As Integer
    Dim day As Integer: day = 1
    
    'エラー通知
    
    
    Range("F2:F32").Clear
    
    For i = 2 To 32
        If isDayWritable(year, month, day) Then
            Cells(i, 6).Value = day & "日"
            Application.Wait [Now() + "0:00:00.05"]
            Cells(i, 6).Select
        Else
            Exit For
        End If
        day = day + 1
    Next
    
End Sub
モジュール「Study3」の現在の実装状態

このマクロの中に少しソースコードを追加するのと、関数をひとつ追加します。早速、関数いっちゃいましょう。


getRegexpFirstHit関数の実装

前回やった正規表現パターンを用いて、ユーザの入力値から一部の値を抽出する関数を実装します(「正規表現パターン」のこと、記事中では「正規表現」とも「パターン」とも略して言うことがあります)。

  • 年情報の抽出は「^\d{4}(?=年)」←この正規表現

  • 月情報の抽出は「([1-9]|1[0-2])(?=月)」←この正規表現

この関数では引数を2つ受け取ります。

  • ユーザの入力値

  • 正規表現パターン

の2つです。では、側を作ります。

'#説明:指定された値のうち正規表現パターンに一致する最初の値を返す
'#引数:checkText:検査文字列、pattern:正規表現パターン
'#戻値:最初にヒットした値。ヒットがなければ空文字
'#注意:参照設定「Microsoft VBScript Regular Expressions 5.5」が必要
Function getRegexpFirstHit(checkText As String, pattern As String)

End Function
赤枠のソースコードを反映してね

今回はしっかりめに関数コメントを付けました。どんなコメントをどういう形式で付与するか?は自身の好みだったり、指針によって決まるものなので特にこれという決まりはありません。

後で見たときのことも考慮してわかりやすいものにできていればOKです。今回はだいたいよく触れている要素を挙げました。関数の概要(説明)と引数、戻り値はどのプログラミング言語でもよくコメントで言及しています。

コメントが十分についていればこれから実装を進める身としてもどんな中身を書けばいいのか、なんとなくイメージがつきますね。

「注意」に挙げている参照設定のところは後で触れます。

中の処理に進んでいきましょう。


オブジェクトとは

まず、次のソースコードをかいてください。

    Dim regexpObj As Object
    Set regexpObj = CreateObject("VBScript.RegExp")
赤枠のソースコードを反映してね

はい、変数の宣言・定義をしていることはこれを見ただけでお分かりいただけるかと思います。

ただ、これまではString型、Integer型、Boolean型でしたが、そのどれにも該当しないObjectオブジェクト型というものが出てきました。また「Setセット」も初出でしょう。


ここで#5でやったプロパティを思い出してください。「ある対象」に対して、そいつがどんなやつなのかを表す情報、と伝えました。

人間だったら名前、年齢、性別、身長、体重、髪の色などがプロパティです。

自動車だったらブランド、車体の色、乗車許容人数、車高などがプロパティです。

エクセルのセルだったら、背景色、文字色、文字の太さ、結合されているか、入力されている値、といった情報がプロパティです。

で、ですね。この「人間」や「自動車」のことをプログラミングの世界ではオブジェクトと呼んでいます。

エクセルのセル(Cells)はプロパティを設定するのみなのですが、オブジェクトのような振る舞いをすることも実はできるんです(補足の章で触れます)。


人間や自動車といった実在するモノを今は例として挙げましたが、オブジェクトとしてプログラムで表現できるものは有形無形ゆうけいむけいを問わず無限にあり、例えば「国」や「学校」「契約」「天気」などもオブジェクトとして扱うことができます。

そして、オブジェクトにはプロパティがつきものです。先ほどの人間や自動車の例でそれはお分かりいただけましたね。

さらにもうひとつ、オブジェクトがもつことのできる要素があります。


メソッドとは

オブジェクトはプロパティのほかにメソッドと呼ばれるものを持つことができます。また新しい言葉が出てきた!と身構えるかもしれませんが、ほぼ同じような要素をここまでに扱ってきております。

それは関数かんすうです。

カレンダーLv.4で関数を自作しましたが、あれは引数として受け取った値を用いて処理をし、その結果を返す、というものでしたね。

メソッドはまず、オブジェクトに属している、オブジェクトの持ち物という点で関数と前提が異なります。オブジェクトが持っているプロパティの値を書き換えたり、何か操作・処理をしたりするものです。

ただ、実際に実装するとなると両者はほぼ同じで見分けがつかないくらい似通っています。その証拠に・・・

「関数 メソッド」で検索したときの様子

「関数 メソッド」まで入れるとサジェスト(検索候補)には3つ目の言葉として「違い」が出てきています。それくらい違いが分かっていない人が多いんです。

なので、関数の作り方をもう既にやっているのでメソッドは新たに会得する必要もなく、もう習得しているものと捉えてもらっても大丈夫です。

また、ここまで長々と説明してきておいてなんですが、今回はメソッドは作らず、既に用意されているものを使うだけです。

ただ、ここまでの説明がなければピンとこない、そういう内容になっています。


さて、オブジェクトとは?メソッドとは?の説明をざっくりとしました。まだピンと来ていない方もいらっしゃると思います。大丈夫。次はソースコードの解説に戻って実際のオブジェクトの使い方を見てみましょう。


オブジェクト型変数の宣言・定義

    Dim regexpObj As Object
    Set regexpObj = CreateObject("VBScript.RegExp")

現在、上記のソースコードを書いていただいた状態です。

改めて、ソースを解説します。まず1行目はこれまで通り変数を宣言しており、そこで指定するデータ型がオブジェクト型だ、というだけのものです。

ポイントはこの段階ではなんのオブジェクトなのかがわかっていない、という点です。

そこで次にするのが2行目。どんなオブジェクトとしてこの変数に働いてもらうか?を定義するステップです。前項で言うなら人間だったり自動車だったり、そういうものをここで変数regexpObjに代入します。

先頭にSetというキーワードがついていますが、これはオブジェクトの場合はこうするものだ、と覚えておいてください。VBA特有のものです。

CreateObject("VBScript.RegExp")

このCreateObjectはただの関数です。

Set regexpObj = CreateObject("VBScript.RegExp")

引数に文字列を受け取り戻り値をオブジェクト型の変数にセットしている所から見て、指定されたオブジェクトを返してくれるのだと推測できますね。その通りです。

では今回、どんなオブジェクトを使うのか?見ていきましょう。


Regular Expression オブジェクト

RegularレギュラーExpressionエクスプレッションオブジェクトです。慣れない言葉が出てきたと思われるかもしれませんが・・・

はい、和訳すると「正規表現」です。なので

Set regexpObj = CreateObject("VBScript.RegExp")

この1行は正規表現オブジェクトを変数regexpObjに代入しています。もうお分かりいただけたと思いますが、変数名のregexpObjはRegular Expression Objectの略です。

オブジェクトをこうして使える状態にすることを実体化する、なんて言ったりもします。

この項の冒頭に貼った公式リファレンスページでは

Regular Expression オブジェクトの公式リファレンスから一部キャプチャしたもの

このように書いてありますが、今回はこのうちのReplaceメソッド以外を利用します。

ソースコードの実装と併せてみていきましょう。


With ステートメント

次のソースコードを下図のように追記してください。

    With regexpObj
    
    End With
赤枠のソースコードを反映してね

はい、正規表現の各プロパティ、メソッドを説明していくよ、と言いつつまったく関係ない要素が出てきました。すみません。

でもこれを使用するとソースコードが綺麗になり、コーディングが効率的になり、可読性も向上すると、いいこと尽くしなんです。

Withウィズステートメントというのですが、まず説明の前段としてオブジェクトのプロパティやメソッドを使うときのソースコードの書き方を案内させてください。

ここまでの実装の続きには以下のようなソースコードが続きます(まだ書かなくていい。あとでちゃんと説明します)。

'検索する正規表現条件
regexpObj.pattern = pattern
'大文字小文字の区別(True:しない、False:する)
regexpObj.IgnoreCase = False
'文字列の最後まで検索(True:する、False:しない)
regexpObj.Global = True
'戻り値
If regexpObj.test(checkText) Then
	Set getRegexpFirstHit = regexpObj.Execute(text)(0)
End If

先ほどのメソッドの説明の時に

メソッドはまず、オブジェクトに属している、オブジェクトの持ち物という点で関数と前提が異なります。

と言いました。メソッドは必ずオブジェクトに属しています。つまり持ち主がいるんです。持ち主なしでは存在できないんです。もっと言うと持ち主の存在も一緒に示さないとコーディングできないんです。

ですので、オブジェクト.メソッドという書き方をしています。プロパティも同様でオブジェクト.プロパティという書き方をします。

#5で「.ドット」は「の」や「に所属する」と呼んでください、と説明しました。その意味がここでわかると思います。

先ほどのソースコードからコメントを排してみると・・・

regexpObj.pattern = pattern
regexpObj.IgnoreCase = False
regexpObj.Global = True
If regexpObj.test(checkText) Then
	Set getRegexpFirstHit = regexpObj.Execute(text)(0)
End If

ほら、どれも「regexpObj.」から始まっているのがわかります。

これを読んでこう思ってほしいんです。

「くどい!」と。

何回も「正規表現オブジェクトの」って言われなくてもここでやるのは正規表現オブジェクトを使った処理なのだからいちいち言わなくてもいいと。

それを解消してくれるのがWithステートメントです。これを使うと

With regexpObj
    .pattern = pattern
    .IgnoreCase = False
    .Global = True
    If .test(checkText) Then
    	Set getRegexpFirstHit = .Execute(text)(0)
    End If
End With

こんな書き方ができます。「regexpObj」をWithの横に一度しか書いていません。他の箇所では省略していきなりドットから書いています。

これがWithステートメントの効力なんですね。ちなみにこれもSetと同じでVBA特有のものです。

さて、正規表現オブジェクトの本題に参りましょう。各メソッド、プロパティの使い方を解説していきます。


Pattern プロパティ

Withステートメントの中に下記のソースを下図のように追記ください。

        '検索する正規表現パターン
        .pattern = pattern
赤枠のソースコードを反映してね

これは第二引数で呼び出し元から受け取った正規表現パターンを正規表現オブジェクトのパターンプロパティに代入(設定)している処理です。

なので今回で言うと実際に使うときは

  • ^\d{4}(?=年)

  • ([1-9]|1[0-2])(?=月)

この2つが渡される、ということですね(2回に分けて呼びだします)。

公式リファレンスも貼っておきます。

公式リファレンスから一部キャプチャ。赤下線のところを抑えておけばOKです。

正規表現の説明が事細かに丁寧にされてるページですね。


IgnoreCase プロパティ

前項のソースの続きに以下を追記してください。

        '大文字小文字の区別(True:しない、False:する)
        .IgnoreCase = False
赤枠のソースコードを反映してね

これはコメントの通り、チェックする文字列を正規表現パターンに照合するとき英字の大文字小文字の違いを考慮するかどうか?を指定するプロパティです。

今回は年月を取るので英字は意識しませんが、例えば「School」と「school」どちらも正規表現にマッチしているとみなすのかみなさないのか、その厳密さを指定したいシーンもシステムによってはあります。

Ignoreイグノア自体には「無視する」という意味があり、Caseケースは大文字小文字を示しています。

        '大文字小文字の区別(True:しない、False:する)
        .IgnoreCase = False

ですので、「大文字小文字を無視する?」に対してFalseを指定すると無視しない、すなわち区別する、ということになります。

以下、公式リファレンスです。

赤下線のところを抑えておけばOK。

ちなみに設定しなかったらどうなるのか?ですが、True/Falseで指定するものは必ずデフォルト値があります。

公式リファレンスから一部キャプチャ。赤下線のところを見てください。

IgnoreCaseプロパティはFalseと書いてますね。


Global プロパティ

前項のソースの続きに以下を追記してください。

        '文字列の最後まで検索(True:する、False:しない)
        .Global = True
赤枠のソースコードを反映してね

これはですね、前回やった正規表現の説明で、文字列に対して何度もマッチする、というケースがあったと思います。

検証文字列に数値が含まれるかを確認している様子。5回ヒットしている

例えばこれですね。文字列に対して数字の正規表現パターンで検証しています。結果、5つの値がマッチしています。

で、今回の関数において、この正規表現パターンによる検証を最初のマッチでやめにするのか、最後まで検証するのか、をプロパティに指定しています。

まあ今回は年月だけなので最後までも何もないのですが、一応関数の振る舞いとして用意しています。あとは学習的な意味でも。

赤下線のところを抑えておけばOK。

こちらもデフォルトの記載が公式リファレンスにあります。

デフォルト値はFalseです。


ここまででプロパティに値を設定しました。

プロパティの設定値はメソッドで使用されることがあるので、メソッドの使用前に設定しておくことでメソッドに所定の働きをさせることができます。

メソッドを使う準備が整った、というわけですね。


Test メソッド

はい、いよいよメソッドです。次のソースコードを追記してください。

        '対象文字列を検証する
        If .Test(checkText) Then
            
        End If
赤枠のソースコードを反映してね

はい、If文が出てきましたね。もう慣れたものかと思います。

If と Thenの間にかけるのはTrueかFalseの結果を返す変数や数式、関数のみですが、これはメソッドも対象です。ですので、このTestメソッドはBoolean値を返してくれます。

「Test」という言葉からは「試験」を想像するかもしれませんが、「検証」と捉えてください。評価する、と捉えても差し支えありません。

公式リファレンスも確認しましょう。Testメソッドは引数として渡した文字列がpatternプロパティに設定した正規表現パターンにマッチするかどうか、その結果を返してくれます。

はい、赤下線の箇所、ご覧の通りですね。


Execute メソッド

いよいよ関数内の記述としては最後です。

次のソースコードをIf文の中に書いてください。

'最初にヒットした要素を返す
Set getRegexpFirstHit = .Execute(checkText)(0)
赤枠のソースコードを反映してね

これで関数としては

関数の実装が出来上がった状態

このような仕上がりになっています。途中の空行からぎょうは好みに合わせて削っていただいても問題ありません。

Executeエグゼキュートメソッドは事前にpatternプロパティに指定した正規表現パターンを使って引数に渡された文字列からマッチする文字列を探す働きをします。正規表現チェッカーみたいなことです。

Execute自体には「実行する」という意味があり、正規表現で行う処理として代表的なもの(マッチング検証)を実行してくれるわけです。

今回のポイントは

.Execute(checkText)(0)

この最後の「(0)」ですね。実はこのパターンはもう過去に出てきています。

#10で配列をやりましたね。そのとき

        If CInt(targetArray(i)) = checkValue Then

こんなソースが出てきました。これは配列targetArrayターゲットアレイの要素にアクセスするとき整数値をもっている変数 i によってN番目の要素を取り出している、というものでした。

と、いうことは?

.Execute(checkText)

この処理が返してくれる結果は配列なんですね。

公式リファレンスを見てみます。

コレクションを返す、という言葉がありますね。コレクションとは配列のことです。そして、一致するものが見つからない場合、空のコレクションを返す、とあります。

しかし今回は

        '対象文字列を検証する
        If .Test(checkText) Then
            '最初にヒットした要素を返す
            Set getRegexpFirstHit = .Execute(checkText)(0)
        End If

このようにTestメソッドで事前に要素があるかどうかを確認しているので、.Executeが実行されるときは必ず最低でも1つは要素がある、ということになります。

必ず最低でも1つは要素があるから戻り値であるコレクションに対して「(0)」を付与しています。

今実装したこの関数の名称はgetRegexpFirstHitとしています。正規表現による検索によって最初にヒットした要素を返す、という意味です。

            '最初にヒットした要素を返す
            Set getRegexpFirstHit = .Execute(checkText)(0)

このソースはそれをしているわけですね。

さて!getRegexpFirstHit関数が完成しました。残りの実装に行きましょう!


参照設定のやり方

・・・の前に、ひとつやらねばならないことがあります。

私、getRegexpFirstHit関数を実装し始めたところでこの↓ように言いました。

「注意」に挙げた参照設定のところは後で触れます。

getRegexpFirstHit関数の実装の項

関数のコメント中に

'#注意:参照設定「Microsoft VBScript Regular Expressions 5.5」が必要

というのがありますね。「参照設定」という言葉自体は#6でも出てきました。組込関数は一体どこで定義されているんだ?という文脈の中で少しだけ触れたのでしたね。

参照設定をやりましょう。下図を真似してください。

参照設定をする様子

上部メニューの「ツール」を選択し「参照設定」を開きます。その中に「Microsoft VBScript Regular Expressions 5.5」というのがあるので、その左端にあるチェックボックスをONにし、OKボタンで閉じます。

これはなにをしてるかというと、正規表現関連の処理に必要なモジュールを参照しているんですね。ライブラリを利用不可の状態から利用可に変更しました。

頻繁に使うわけでもないモジュールを最初から読み込んでおくと無駄に処理が重くなったり、不具合の原因になったりするので、必要な時にだけ参照設定をする、ということです。

※ちょっとミスがあって・・・補足で謝罪します。


getRegexpFirstHit関数を呼び出す

それでは、これで本当にいよいよです。先ほど作った関数を呼び出してみましょう。

今、「H_カレンダー5」マクロ冒頭の変数宣言は

    Dim year As Integer
    Dim month As Integer
    Dim day As Integer: day = 1

このようになっているかと思います。これをですね

    Dim year As Integer: year = getRegexpFirstHit(Cells(1, 6).Value, "^\d{4}(?=年)")
    Dim month As Integer: month = getRegexpFirstHit(Cells(1, 6).Value, "([1-9]|1[0-2])(?=月)")
    Dim day As Integer: day = 1

このようにしてください。変わった箇所は変数yearと変数monthの初期値設定です(変数dayはそのまま変わらず)。

year = getRegexpFirstHit(Cells(1, 6).Value, "^\d{4}(?=年)")
month = getRegexpFirstHit(Cells(1, 6).Value, "([1-9]|1[0-2])(?=月)")

ここね!

どちらも第一引数はF6セルに設定された値、第二引数は正規表現を渡しています。

では試しにそれぞれ本当に正しく動くのか、見てみましょう。

動作確認のため、System.Debugを仕込んでから実行する様子

はい、変数yearと変数monthの値を確かめるだけのつもりでいましたが、後続処理がガッツリ用意してあったので、もう普通に動いてしまいましたね。

でもイミディエイトウィンドウにもしっかりとそれぞれの値が出力されました。getRegexpFirstHit関数が正しく動いてくれていることがこれでわかります。

ここでひとつ注意なのですが、年月を入力するときね、頭に「'」を入力してください。先ほどのGif画像ではさり気なくそうしています。

これをしないと・・・

最初に「'」をつけずに年月情報を入力する様子

分かりますか?「2024年5月」まで入力するとエクセルによって「あーはいはい日付を打ちたいんだね!こっちでよしなに変換しちゃるわ!」ということで入力値が勝手に「2024/5/1」になってしまうんです。

でもそれだとせっかく用意してる正規表現とアンマッチしますからね。余計な変換をしないでこちらの入力値をそのまま受け取ってほしい場合は先頭にシングルクォーテーションを打ちましょう。

その後打つ文字列をそのままの文字として受け取ってくれる効果があります。ジングルクォーテーションは[Shift] + [7]キーで打てます。


エラー処理

カレンダーLv.5の処理としてはこれが最後になります。

先ほどは正しく年月が取得できたパターンで処理を行いました。では取得できないときはどうなるでしょうか。やってみます。

20244年5a月で実行する様子

はい、「20244年5a月」という値にしたところ、「0年0月」となりました。イミディエイトウィンドウに表示されていますね。

これにより、正規表現にヒットしなかった場合、「0」が返ってくることがわかります。これはつまり正常な値が入力されなかったことを意味します。

'エラー通知の箇所に次のソースを書いてください。

    If year = 0 Or month = 0 Then
        MsgBox "年月の値が不正です。処理を終了します。"
        Exit Sub
    End If
赤枠のソースコードを反映してね

変数yearもしくは変数monthの値が「0」であれば処理を終了します。

これでカレンダーLv.5は完成です(動作確認は省略)。

[Ctrl] + [S]キーで保存してください。


今回のふりかえり

今回は

  • オブジェクトとはなんぞや

  • メソッドとはなんぞや

  • 正規表現オブジェクトの使い方

  • 参照設定

を丁寧にやりましたね。グッジョブ!


今回の補足

Cellsプロパティがオブジェクトなわけ

今回、「オブジェクト」の説明をしましたね。その中でCellsプロパティについては以下のように言いました。

エクセルのセル(Cells)はプロパティを設定するのみなのですが、オブジェクトのような振る舞いをすることも実はできるんです(補足の章で触れます)。

Cellsの使い方について、これまでは

  • Cells(n, n).Value

  • Cells(n, n).Select

  • Cells(n, n).Font

このようにプロパティを設定する、という使い方をしてきました。これはどこのセルか、つまりセルのアドレスを必ず指定していますね。

ただ、Cellsそれ自体は他の使い方もあり例えば・・・

Cells.clear

と書くと全セルの値をクリアすることができます。これはセルに対する操作です。今回メソッドの説明で

メソッドはまず、オブジェクトに属している、オブジェクトの持ち物という点で関数と前提が異なります。オブジェクトが持っているプロパティの値を書き換えたり、何か操作・処理をしたりするものです。

出典は私

このように言いました。なので公式リファレンスもCellsプロパティという書き方をしてはいますが、オブジェクトという使い方もできるんです。

で実はよく見ると

ちゃんと書いてあるんですね。なので厳密に言うとCells(x, y)はプロパティでしかないんだけど、Cells.~と書くとRangeオブジェクトとして使うことができる、です。Cells.clearとRange.clearは同義です。

「オブジェクト修飾子」は文脈からして「Cells(x, y)」の「(x, y)」のことでしょう。これ、結構込み入ったツッコんだ話なので、よく分からなくても大丈夫です。


patternプロパティが先頭小文字のわけ

getRegexpFirstHit関数の中で

        '検索する正規表現パターン
        .pattern = pattern

こういう記載がありますね。これ、他の正規表現のプロパティは先頭が大文字なのにこれだけ小文字になってますよね。「P」じゃなくて「p」。左辺の方ね。

これ、どうやらVBAのこのエディタの仕様?のようなのですが、本来なら「Pattern」と表示されるプロパティ名でも他で「pattern」という名前で宣言している変数名があるとそっちに引っ張られてしまうようです。

なので今回

Function getRegexpFirstHit(checkText As String, pattern As String)

この第二引数で「pattern」としているため、プロパティ名も「Pattern」ではなく「pattern」になっちゃっているようです。

どうしても直したい場合のやり方、お伝えしておきますね。

まず、引数の「pattern」を「argPattern」に変更します。頭に付けた「arg」は「Argumentアーギュメント」の略で「引数」という意味です。

patternをargPatternに変更した様子

で、この次にpatternプロパティを「Pattern」に書き換えても駄目なんです。宣言している名前に引っ張られる、というところがポイントなので・・・

気になる方はこれを真似してみて!

このようにしてください。

    Dim Pattern As String

を宣言し、カーソルを移送するとそれと同時に「.pattern」が「.Pattern」になります。で、仮で書いた上記の変数宣言を削除します。


関数は何に属しているのか

今回の本編の説明中にメソッドはオブジェクトに属しているものだ、という旨の説明をしています。

メソッドの説明をするとき、実は関数と似てるんですわ、と言ったので、

じゃあ関数はなにに属してるんだよ

と思われた方もいるかもしれません。解説します。

えー、ゴホン。。関数はね、なににも属してない、という答えになります。関数が初めて出てきた#6ひとつひとつの命令行(処理)の集合体が関数だという話をしました。関数自体はそれ以上でもそれ以下でもないです。

今回扱った正規表現オブジェクトは事前にpatternプロパティに正規表現パターンを設定したことでTestメソッドやExecuteメソッドが使えました。

ただの関数にはそういったプロパティがまず存在しません。引数さえ適切にもらえればきちんと処理をしてくれます。ですので、何かに属しているのか?という観点では単独で存在している、と解釈ください。

余談ですが、関数とメソッドは文脈やプログラミング言語によっては同じものを指す場合もあります。ですので、あまり厳密に区分けせず、ふわっとした理解で置いておいてください。

私がたまに見るサイトも参考にどうぞ。


getRegexpFirstHit関数にて戻り値のSet

すみません。第1章をすべて書き終え、公開前に再度各記事を巡回している段階で気付いたのですが、今回のソースコード中の・・・

        '対象文字列を検証する
        If .Test(checkText) Then
            '最初にヒットした要素を返す
            Set getRegexpFirstHit = .Execute(checkText)(0)
        End If

ここの戻り値を返すときの「Set」要るか?と思って削除しても正常に動作しちゃいました。だからここの「Set」は要りません。

今回の本編中にも

先頭にSetというキーワードがついていますが、これはオブジェクトの場合はこうするものだ、と覚えておいてください。VBA特有のものです。

出典は私

と、言っていて設定対象がオブジェクトの時にだけ使うものです。

一応公式リファレンスも覗いたんです。ここではExecuteメソッドが返してくる値を戻り値としていますから、Executeメソッドのリファレンスです。

するとね・・・

Executeメソッドの公式リファレンスページから一部をキャプチャしたもの

ここでは「Set」って使っているんですよ。でも実際に値を出す箇所では「.Value」としてる!

・・・なので、これを直接戻り値として返すことを考えたらやはりオブジェクトではなくただの文字列なので、「Set」はいらないという結論になります。

これらを踏まえると下記の書き方が適切でしょう。

        '対象文字列を検証する
        If .Test(checkText) Then
            '最初にヒットした要素を返す
            getRegexpFirstHit = .Execute(checkText)(0).Value
        End If

本編中で実装したものとこちら、どっちでもいいです!


For Eachループのこと

前項で示したこの↓コードね、たぶんよくわからないと思うんですよ。

Executeメソッドの公式リファレンスページから一部をキャプチャしたもの

インデントが揃っていないのと、まだ習っていないループ構文があるからです。これ、解説しておきます。動かしてみるぞ!という方は次のソースコードを追記してください。動かしてみない方は眺めててください。

Sub H_ForEachの説明()
    '変数宣言!
    Dim regexpObj As Object
    '正規表現オブジェクトを作成する
    Set regexpObj = New RegExp
    '正規表現パターンを指定する。数値のみマッチするもの
    regexpObj.pattern = "\d"
    '文字列の最後まで検証する
    regexpObj.Global = True
    '検証結果の受け皿を宣言する
    Dim matches As Variant
    'パターンマッチングの結果を受け取る
    Set matches = regexpObj.Execute("1あ2い3う4え5お")
    'マッチした値をすべて出力する
    For Each match In matches
        Debug.Print match.Value
    Next
End Sub
おためし実装の様子

ほぼすべてのソースコードにコメントを付けたので、今回の学びと併せて基本はそこで理解していただきたいと思います。お伝えしたいのは・・・

    'マッチした値をすべて出力する
    For Each match In matches
        Debug.Print match.Value
    Next

ここ!これ、ループ処理なんですけど、これまで出てきたのとはちょっと書き方が違いますよね。これはですね、コレクションの全要素に対して処理をしたい場合に使うタイプのループです。

For Each [周回中の要素を格納する変数] In [コレクション変数]

↑こんな構文になってて、今回だったら変数matchesマッチーズにパターンマッチングの結果が格納されています。"1あ2い3う4え5お"から数値だけを抜いてきたので1,2,3,4,5を配列でもっています。

で、これを1要素ずつ処理したいのですが、そのとき周回ごとに要素を格納するのが変数matchです。動きをご覧いただきましょう。

ほら、5周していますね。ローカルウィンドウで見てみても、配列の各要素がValueプロパティをもっています。

本項で案内したソースコードはコメントアウトしておいてください。


参照設定の謝罪

これが今回の最後です。

実はですね・・・!本編で参照設定をしていただいたのですが・・・

はい、実行には参照設定が必要、みたいな口ぶりでしたが、実際はこの参照設定がなくても動いてしまいました・・・。すみません。

でですね、この参照設定が活きるためには正規表現オブジェクトのオブジェクト定義を適切にする必要があるのだということに今気づきました・・・!

getRegexpFirstHit関数の中に

    Dim regexpObj As Object
    Set regexpObj = CreateObject("VBScript.RegExp")

こういう2行がありますね。これの2行目を・・・

Set regexpObj = New RegExp

これと置き換えてください。

これで実行すると・・・(今、正規表現の参照設定は外れています)

参照設定なしではエラーになること、参照設定ありだと正常動作することを確認する様子

エラーになりましたね。そのあとにもう一度参照設定をしてから実行すると正常動作しました。

    Set regexpObj = CreateObject("VBScript.RegExp")

こっち↑のソースコードだとCreateObject関数がよしなにやってくれるようですが、

Set regexpObj = New RegExp

こっち↑だと、「RegExp?そんなオブジェクト知らないよ!」と判定されるようです。その証拠に

ここででたエラーはコンパイルエラーです。コンパイル#4で初めて出てきた言葉で、ソースコードが実行に足る内容かどうかを診てくれる、という話でしたね。

ですので、実行前に「こんなのじゃ実行できないよ」と言われた感じです。

動けばOKなので、最終的にはどちらのソースコードでも良いです。


おわりに

今回でカレンダーLv.5が完成しました。お疲れ様です。

正規表現は結構難しかったと思います。使わないと忘れてしまうものですし、実際に「システムエンジニア」だとか「プログラマ」として働いている人たちでもよくわかっていない人、たくさんいます。

それくらいちょっとニッチな分野というか、モノ自体は有名なんだけどちゃんと理解して使いこなしている人は多くない、というものです。

ですので、今回実際に目的をもって使う、という形で取り組めたのはとても良かったなと思います。実は私個人としてもたくさん勉強になりました。

次回は演習問題としてあみだくじを作ります。

これ、おもしろそうですよね~。エクセルってこんなこともできるんだ!っていう。私、正直今からワクワクしてますもん、次回を。

みなさんここまでありがとうございました。確実に一歩一歩プログラミングという山を登っています。次回も頑張りましょう。

ゆっくり休んで鋭気を養ってくださいませ。

今回もありがとうございました。


この記事が気に入ったらサポートをしてみませんか?