• ベストアンサー
※ ChatGPTを利用し、要約された質問です(原文:ヤマ括弧でくくられたテキストを抽出(入れ子))

VBA学習者のためのヤマ括弧抽出方法

このQ&Aのポイント
  • VBA学習者のために、ヤマ括弧でくくられたテキストを抽出する方法についてご説明します。
  • ユニットが入れ子になっている場合も対応するためのコードを用意しました。
  • しかし、内側のユニットを取り出す方法についてはまだうまくいっていません。どのようにすればよいでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • nda23
  • ベストアンサー率54% (777/1416)
回答No.2

完全に内側の部分のタグを抜き出すなら 正規表現を組み合わせて処理できます。 Dim 原始テキスト As String Dim 行位置 As Long Dim カッコ内を抜き出す As Object Dim 左カッコを数える As Object Dim 左カッコを置換する As Object Dim 一致集団 As Object Dim 部分一致 As Object Dim 左カッコ集団 As Object Dim 左カッコ数 As Long Dim カウンタ As Long Dim 一致文字列 As String Dim 正規表現 As String 原始テキスト = "Test <if([control]~" '★略 行位置 = 0 '★カッコ内を抜き出す正規表現を作る Set カッコ内を抜き出す = CreateObject("VBScript.RegExp") カッコ内を抜き出す.Global = True カッコ内を抜き出す.Pattern = "(<.*?>)" '★左カッコを数える正規表現を作る Set 左カッコを数える = CreateObject("VBScript.RegExp") 左カッコを数える.Global = True 左カッコを数える.Pattern = "<" '★左カッコを置換する正規表現を作る Set 左カッコを置換する = CreateObject("VBScript.RegExp") 左カッコを置換する.Global = False '★カッコ内を抜き出して一致集合を作る Set 一致集団 = カッコ内を抜き出す.Execute(原始テキスト) '★集合の一つずつを順に処理する For Each 部分一致 In 一致集団     '★一致した文字列を取得する     一致文字列 = 部分一致.Value     '★一致した文字列の中の左カッコの数を調べる     Set 左カッコ集団 = 左カッコを数える.Execute(一致文字列)     If 左カッコ集団.Count > 1 Then         '★左カッコが2個以上の場合は直前の左カッコまで除去する         正規表現 = ""         '★左カッコの数-1だけ下記パターンを繋げる         For カウンタ = 2 To 左カッコ集団.Count             正規表現 = 正規表現 & "<.*?"         Next         '★最後の左カッコを追加する         左カッコを置換する.Pattern = 正規表現 & "<"         '★最初の左カッコから最後の左カッコまでを一つの左カッコにする         一致文字列 = 左カッコを置換する.Replace(一致文字列, "<")     End If     '★行位置を更新して結果をセルに記録する     行位置 = 行位置 + 1     Cells(行位置, 1) = 一致文字列 Next 特定のパターンを抜き出したり置換するのに正規表現は便利です。 文字を数えるのも楽ですしね。 正規表現がどんな働きをするかデバッグ実行で1ステップずつ 実行してみてください。

Kazu_creator
質問者

お礼

早速のご回答ありがとうございます! 実は、私も当初は正規表現での処理を試みていたのですが、nda23が活用されていた正規表現でヒットした文字列のコレクション(?)の存在や、正規表現のパターンを動的に変化させる手法を知らなかったために挫折したのでした。 私が書いたコードで外殻の「ユニット」を取り出した上で、nda23さんにご教授いただいたプロシージャをCallで呼べば「結果B」が得られそうです。 意図した結果が得られただけなく、今後の正規表現を使用した処理の可能性も広がりました。 ありがとうございました。

その他の回答 (2)

  • nda23
  • ベストアンサー率54% (777/1416)
回答No.3

#2です。 VBAという範疇から少しズレますが、JavaScriptの Stringオブジェクトを使う方法を思い出したので、 参考までに記載します。これで期待する結果になる のではないでしょうか。 JavaScriptの/~/は正規表現です。 Function 解析(ByVal 文字列 As String) As String() Dim JS As ScriptControl Dim X As Long Dim I As Long Dim 文 As String '★オブジェクトの生成 Set JS = CreateObject("ScriptControl") JS.Language = "JavaScript" '★共通変数の定義 JS.ExecuteStatement "var 配列 = new Array();" JS.ExecuteStatement "var 多重度 = 0;" JS.ExecuteStatement "var カッコ数 = 0;" JS.ExecuteStatement "var カッコ位置 = 0;" JS.ExecuteStatement "var 文字列 = '';" '★解析関数の定義 文 = "function 解析(文字,位置) {" '●左カッコで一致した場合 文 = 文 & vbNewLine & "if ( 文字 == '<' ) {" '●最初のカッコの場合は位置を記録する 文 = 文 & vbNewLine & " if ( カッコ数 == 0 ) カッコ位置 = 位置;" '●カッコのネストレベルを増加させる 文 = 文 & vbNewLine & " カッコ数++;" 文 = 文 & vbNewLine & " return;" 文 = 文 & vbNewLine & "}" '■右カッコで一致したらネストレベルを減少させる 文 = 文 & vbNewLine & "カッコ数--;" '■ネストレベルが0に戻らない場合は何もしない 文 = 文 & vbNewLine & "if ( カッコ数 != 0 ) return;" '■右カッコの数が揃ったので、部分列を取得する 文 = 文 & vbNewLine & "var 部分文字列 = 文字列.substring(カッコ位置,位置 + 1);" '■多重度文字列の字数を得る 文 = 文 & vbNewLine & "var 字数 = (多重度 + '').length;" '■現在の多重度以下をスキップする 文 = 文 & vbNewLine & "var i = 0;" 文 = 文 & vbNewLine & "for ( ; i < 配列.length ; i++ ) {" 文 = 文 & vbNewLine & " if ( 多重度 < 配列[i].substr(0,字数).valueOf() ) break;" 文 = 文 & vbNewLine & "}" '■配列の要素数を増やす 文 = 文 & vbNewLine & "配列.length++;" '■配列をズラせる 文 = 文 & vbNewLine & "for ( var j = 配列.length - 1 ; j > i ; j-- ) {" 文 = 文 & vbNewLine & " 配列[j] = 配列[j - 1];" 文 = 文 & vbNewLine & "}" '■レベルと一緒に部分列を格納する 文 = 文 & vbNewLine & "配列[i] = 多重度 + 部分文字列;" '■部分列内部の左カッコ数が1なら終了する 文 = 文 & vbNewLine & "if ( 部分文字列.match(/</g).length == 1 ) return;" '■現在の情報を記録する 文 = 文 & vbNewLine & "var 保存数 = カッコ数;" 文 = 文 & vbNewLine & "var 保存位置 = カッコ位置;" 文 = 文 & vbNewLine & "var 保存文字列 = 文字列;" '■共通変数を初期化する 文 = 文 & vbNewLine & "カッコ数 = 0;" 文 = 文 & vbNewLine & "カッコ位置 = 0;" '■多重度を増加させる 文 = 文 & vbNewLine & "多重度++;" '■先頭の左カッコを除いた文字列を解析する 文 = 文 & vbNewLine & "文字列 = 部分文字列.substr(1);" 文 = 文 & vbNewLine & "文字列.replace(/<|>/g,解析);" '■多重度を減少させる 文 = 文 & vbNewLine & "多重度--;" '■共通変数を戻す 文 = 文 & vbNewLine & "カッコ数 = 保存数;" 文 = 文 & vbNewLine & "カッコ位置 = 保存位置;" 文 = 文 & vbNewLine & "文字列 = 保存文字列;" '★解析関数の終端 文 = 文 & vbNewLine & "}" JS.ExecuteStatement 文 文 = "" '◎文字列を代入 JS.ExecuteStatement "文字列 = '" & ThisWorkbook.Worksheets(1).Cells(1, 1) & "';" '◎文字列を解析 JS.ExecuteStatement "文字列.replace(/<|>/g,解析);" '◎配列の要素数を取得 X = JS.Eval("配列.length - 1;") '◎戻り値用配列を定義する ReDim 配列(X) As String '◎結果を設定する For I = 0 To X 配列(I) = JS.Eval("配列[" & CStr(I) & "].replace(/\d+/,'')") Next '★オブジェクトを解放する JS.Reset Set JS = Nothing '◎戻り値を返す 解析 = 配列 End Function JavaScriptのStringオブジェクトにはreplaceメソッドがありますが、 このメソッドの第2パラメータに関数を指定すると、正規表現が 一致した文字と一致した位置をパラメータにして呼び出します。 正規表現は"<"または">"としています。 <>が揃ったところで、内側の解析を順次、実行しています。 配列は外側の<>から内側の<>と、ネストレベルが下がるほど 後方になるように並べ替えています。

Kazu_creator
質問者

お礼

お礼が遅くなってしまいました。申し訳ありません。 やっぱり、VBAだけじゃなくて他の言語もしっていると便利ですね。 ちょうど、PHPとかJAVAのようなWave系で使われる言語もちょっと知っときたいなぁ、と思っていたところです。 お陰さまで、モチベーションがちょっとあがりました。 でも、プログラミング言語だけじゃなくて外国語にも手を出しているので、JAVAの勉強はいつになることやら。。。 とにかく、ご教示ありがとうございました。 勉強になりました。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.1

こういうのは、一つのsubやfunctionで全部やろうとしないで、1部だけ分解するsub/functionを何度も適用するのがいいと思います。 例えば > Sub テスト() → sub 分解(testLine as string) 等として、 分解 サンプルテキストB で分解して、結果Aが得られたら、 分解 Cells(1,1).Value で 結果Aの1つめをさらに分解して....と分解できなくなるまで繰り返す、というものです。 そのままだと、セルが上書きされるとか、分解できない場合の判定とかができないので、いろいろと工夫が必要ですが。 functionにして、結果のunitArray()をそのまま返すとか、終了時の最後のセルの場所を返すとか。

Kazu_creator
質問者

補足

早速のご回答ありがとうございます。 実は、私も実際に文字列を抽出する処理はサブルーチンにして、繰り返し呼び出せばいいじゃないかと思っていたのですが、「入れ子がなくなるまで」の判定をどうしようとか、サブルーチンの戻り値をどう配列に入れたらいいだろう、というところで良くわからなくなり挫折したのでした。 >functionにして、結果のunitArray()をそのまま返すとか、 Functionプロシージャって配列を戻り値と出来るんですか? 手元の書籍にはやり方が書いてないのですが、よろしかったらちょっと教えていただけませんか?

関連するQ&A