【WinActor】変数値保存したテキストファイルから変数値を読み取るやつ作ってみた

この記事はどんな人向け?

・「変数値保存テキストファイル」と聞いて「ああアレね」と分かる人
・何でもいいから文字を読みたい人
・とりあえず開いた記事は最後まで読んでみようと思う人

「デバッグ:変数値保存」というライブラリ

そのライブラリに到達した時点の、シナリオに設定された全変数名と現在値を指定テキストファイルに出力する。
そんな、使わない人はまったく使わないし使う人はちょくちょく使う、「デバッグ」の名を冠する通り、具体的な処理ではなく主に何かあったときの解析に活用するためのライブラリ。
私が使うときはご多分に漏れず、エラー発生時の変数値を確認するために例外処理に配置しています。

そんな折、昨日(2022/6/24)のWebイベントでこんなことを聞きました。

WinActorにおける「部分実行テクニック」として
「例えばエラーが発生したところから実行したいときに、Excelファイルのシートに変数名の列と変数値の列を用意しておき、それを読み取るライブラリを部分実行したい部分の頭に配置することで、エラーが出る瞬間までは確定していた値が入った状態で部分実行ができる」
というもの。
(記憶から引っ張り出して書いているので表現等曖昧です…すいません)

これ自体はすごく便利なテクニックで、私も外部ファイル(Excel)で設定した変数値を読み取るライブラリ作って実際に運用してたりします。

ただ、こうも思いました。
「エラーが出る瞬間までは確定していた値が入った状態を読み取りたい」なら。

「デバッグ:変数値保存」で吐き出したテキストファイルから直接読むライブラリあったら良くない?

そんなわけで、書いてみることにしました。

変数値保存テキストファイルってどんなの?

手元に使用できるWinActorがないので、ググった情報と記憶を頼りにしています。なので超曖昧。間違えてたらしれっと直します。

---------------------------- Current variable
変数1=WinActor
変数2=12345
変数3=2022/6/25 14:50:10
----------------------------
---------------------------- Current variable
変数1=NTTDATA
変数2=456789
変数3=2022/6/25 15:21:32
----------------------------

特徴は以下のポイント。

  1. 連続する半角「-」+「Current variable」が開始点

  2. 変数名と変数値は半角「=」で左右に区切られている

  3. 半角「-」が続いて終わる

  4. その後にまた別のときの変数値が追記されていることもある

変数値保存テキストファイルを読み取る準備をする

前述のとおりWinActorがないので、今回はExcelを使用します。
WinActorライブラリは基本的にVBScriptで記述されているので、ExcelVBAの記述をそのまま使うことはできません。
しかしポイントを抑えておけば書くのも便利ですし、それから今回はWinActorの内部関数の代用を用意する必要があったので、Excelのテーブル(ListObject)で再現することにしました。

WinActorシナリオ変数の代わりに使用するテーブル

「シナリオに変数1~9という変数が設定されている」という前提です。
シート名は「WinActor変数」としました。
代用の関数は以下のように記述しました。

'***********************************************************
'WinActor代理関数//ライブラリには貼らない
'***********************************************************
Private Sub SetUMSVariable(VarName, VarData)
    With ThisWorkbook.Worksheets("WinActor変数").ListObjects(1)
        Dim rw As Long
        On Error Resume Next
        rw = WorksheetFunction.Match(VarName, .ListColumns("変数名").DataBodyRange, 0)
        If Err.Number > 0 Then Exit Sub
        On Error GoTo 0
        .ListColumns("値").DataBodyRange(rw) = VarData
    End With
End Sub

Private Function GetUMSVariable(VarName)
    GetUMSVariable = ""
    With ThisWorkbook.Worksheets("WinActor変数").ListObjects(1)
        Dim rw As Long
        On Error Resume Next
        rw = WorksheetFunction.Match(VarName, .ListColumns("変数名").DataBodyRange, 0)
        If Err.Number > 0 Then Exit Function
        On Error GoTo 0
        GetUMSVariable = .ListColumns("値").DataBodyRange(rw)
    End With
End Function

WinActorないので動作確認できないんですが、確かどちらも引数の変数名がシナリオ上存在しなくてもエラーにはならなかったはずなので、このように記述しました。もしエラー起こる仕様なら書き換えです…色んな所が…。
ここでは、先ほどのテーブルを参照して、変数名があれば値を書き込む、または読み出す、という動作をします。

読み取るテキストファイルには、フォーマットを参考に以下の内容を記入しました。

---------------------------- Current variable
変数1=aiueo
変数2=12345
変数3=ここで
改行する
変数4==SUM(A1:A15)
変数5=以下の処理をしてください。
⇒対象セル:D4
⇒数式:=COUNTIF(A:A,"りんご")
変数6=
変数7=test.xlsx
変数8=C:\RPA=data\data.csv
変数9=

ここまで
----------------------------
---------------------------- Current variable
変数1=kakikukeko
変数2=67890
変数3=ここで
改行しました
変数4==SUM(B1:B15)
変数5=以下の処理をしてください。
⇒対象セル:D5
⇒数式:=COUNTIF(A:A,"みかん")
変数6=
変数7=test.xlsx
変数8=C:\RPA=data\data.csv
変数9=

ここまで
----------------------------

ファイルパス:C:\WinActor\変数値保存.txt

気になるポイントは以下。

  1. 改行コードが含まれる場合、正しく変数値を取得できるか?

  2. 途中にブランクがあっても改行コードで表現できるか?

  3. 変数値に半角「=」が含まれる場合、正しく変数値を取得できるか?

  4. 変数値の保存が2回行われている場合、それぞれの時点の変数値を取得できるか?

書いたコードを実行してみた

コードの内容は後述するとして、実際に実行してみました。
用意したテーブルに、テキストファイルの値が入れば正解です。
まずは、最初に書き出した方の変数値を読み取ります。
結果は?

1回目に書き出した変数値を読み取ってみた結果

改行や半角「=」を含む内容も取得できてる!
では、2回目に書き出した変数値は?

2回目に書き出した変数値を読み取ってみた結果

できてる!
というわけで、ライブラリとしての検証はまだできていないものの、ExcelVBA上では変数値を読み取ることができました!たぶん!

実行する上での前提・注意点

どうしても解決できず、制約となってしまった部分です。

  1. シナリオ変数名に半角の「=」を含まない

    • 「テスト=01=2021/1/1」と書いてあった場合、
      変数名「テスト」変数値「01=2021/1/1」となります。

  2. 改行された部分に「---------------------------- Current variable」のみの行があると正確に値が取れません。

    • 「変数10=ここから
      ---------------------------- Current variable
      ここまで」と書いてある場合など

書いたコードを貼る(飛ばしても可)

こんなコードを書きました。
もちろん改行なども含んでですが、全部で250行あります
※さっき貼った代理関数も含みます。

Option Explicit
''***********************************************************
''Excelでのテスト用//ライブラリには貼らない
''***********************************************************
Const BOFWORD = "current variable"
Dim BOF
Dim EOF
'実行
Sub TestRun()
    BOF = "BOF/" & GetRandom(20)
    EOF = "EOF/" & GetRandom(20)
    Dim myTextFilePath
    Dim myTargetIndex
    Dim mySelectFormat
    myTextFilePath = "C:\WinActor\変数値保存.txt"
    myTargetIndex = 2
    mySelectFormat = "Shift-JIS"
    If myTargetIndex = "" Then myTargetIndex = 1
    '実行
    Call MainProcess(myTextFilePath, myTargetIndex, mySelectFormat)
    Debug.Print "fin"
End Sub

'***********************************************************
'WinActor代理関数//ライブラリには貼らない
'***********************************************************
Private Sub SetUMSVariable(VarName, VarData)
    With ThisWorkbook.Worksheets("WinActor変数").ListObjects(1)
        Dim rw As Long
        On Error Resume Next
        rw = WorksheetFunction.Match(VarName, .ListColumns("変数名").DataBodyRange, 0)
        If Err.Number > 0 Then Exit Sub
        On Error GoTo 0
        .ListColumns("値").DataBodyRange(rw) = VarData
    End With
End Sub
Private Function GetUMSVariable(VarName)
    GetUMSVariable = ""
    With ThisWorkbook.Worksheets("WinActor変数").ListObjects(1)
        Dim rw As Long
        On Error Resume Next
        rw = WorksheetFunction.Match(VarName, .ListColumns("変数名").DataBodyRange, 0)
        If Err.Number > 0 Then Exit Function
        On Error GoTo 0
        GetUMSVariable = .ListColumns("値").DataBodyRange(rw)
    End With
End Function

''***********************************************************
''ライブラリに貼るときはここから
''***********************************************************
'Option Explicit
''定数
'Const BOFWORD = "current variable"
''ランダム値付与後、定数扱い
'Dim BOF: BOF = "BOF/" & GetRandom(20)
'Dim EOF: EOF = "EOF/" & GetRandom(20)
''ライブラリプロパティ
'Dim myTextFilePath
'Dim myTargetIndex
'Dim mySelectFormat
'myTextFilePath = !変数値保存ファイル名!
'myTargetIndex = !インデックス!
'mySelectFormat = !テキスト形式|shift-jis,UTF-8!
'If myTargetIndex = "" Then myTargetIndex = 1
''実行
'Call MainProcess(myTextFilePath,myTargetIndex,mySelectFormat)

'***********************************************************
'プロセス実行//変数値テキストファイルの変数名を元に変数値を取得する
'引数:textFilePath>変数保存値テキストファイル
'引数:TargetIndex>テキストファイルの中の何番目に保存された変数値を取得するか
'***********************************************************
Public Sub MainProcess(TextFilePath, TargetIndex, SelectTextFormat)

    '変数宣言
    Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
    Dim arTextLine: arTextLine = "" 'テキスト1行区切りの1次配列
   
    '存在確認
    If FSO.FileExists(TextFilePath) = False Then
        Err.Raise 1, "", "テキストファイルが存在しません。" _
                                & vbCrLf & "パス:" & TextFilePath
        WScript.Quit
    End If
    '拡張子確認
    Select Case LCase(FSO.GetExtensionName(TextFilePath))
        Case "txt", "csv" '対象
        Case Else
            Err.Raise 1, "", "読み込めるのはテキストファイル(.txt/.csv)のみです。" _
                                    & vbCrLf & "パス:" & TextFilePath
            WScript.Quit
    End Select
    'インデックス確認
    If IsNumeric(TargetIndex) = False Or TargetIndex = "0" Then
        Err.Raise 1, "", "インデックスには数字(1~)を指定してください。" _
                                & vbCrLf & "インデックス:" & TargetIndex
        WScript.Quit
    End If
    
    'テキストファイル操作//テキストデータを1行ずつ配列に追加/1:読み込み専用
    With CreateObject("ADODB.Stream")
        .Charset = SelectTextFormat 'shift-jis,UTF-8
        .Open
        .LoadFromFile TextFilePath
        Do Until .EOS
            Call AddItemOfArray(arTextLine, .ReadText(-2)) ' -2:1行ずつ読み取り
        Loop
        .Close
    End With
    If Not IsArray(arTextLine) Then Exit Sub 'テキストデータがなければ終了
    
    '開始位置(BOF)と終了位置(EOF)を決める
    Call SetBOFandEOF(arTextLine, TargetIndex)
    
    '変数取り込み
    Call SetVariable(arTextLine)

    '解放
    Set FSO = Nothing

End Sub

'***********************************************************
'1次配列に要素を追加
'引数:ar>1次配列
'引数:Item>追加要素
'***********************************************************
Private Sub AddItemOfArray(ar, Item)

    If Not IsArray(ar) Then
        ar = Array(Item)
    Else
        ReDim Preserve ar(UBound(ar) + 1)
        ar(UBound(ar)) = Item
    End If
    
End Sub

'***********************************************************
'開始位置と終了位置の指定//何番目に保存された変数かのインデックスと指定インデックスが一致する位置を決める
'引数:arTextLine>テキストファイルから取得した1次配列
'引数:TargetIndex>指定された変数保存位置インデックス
'***********************************************************
Private Sub SetBOFandEOF(arTextLine, TargetIndex)

    Dim CurrentIndex: CurrentIndex = 0 '現在のインデックス
    Dim IsTarget: IsTarget = False '開始判定があるまでFalse
    Dim buf
    Dim i
    For i = 0 To UBound(arTextLine)
        'テキストから「-」を除き、小文字に統一して前後の空白を除く
        buf = Trim(LCase(Replace(arTextLine(i), "-", "")))
        Select Case buf
            Case BOFWORD '開始位置
                '開始未判定のときのみインデックス加算
                If IsTarget = False Then CurrentIndex = CurrentIndex + 1
                'インデックスが一致している+開始未判定ときに開始判定
                If CurrentIndex = TargetIndex And IsTarget = False Then
                    arTextLine(i) = BOF
                    IsTarget = True
                End If
            Case "" '終了位置か判定
                'インデックスが一致しているときのみ
                If CurrentIndex = TargetIndex Then
                    If i = UBound(arTextLine) Then
                        '最終行ならEOF確定
                        arTextLine(i) = EOF
                        Exit For
                    Else
                        '次の行が開始位置ならその位置インデックスではEOF
                        If Trim(LCase(Replace(arTextLine(i + 1), "-", ""))) = BOFWORD Then
                            arTextLine(i) = EOF
                            Exit For
                        End If
                    End If
                End If
        End Select
    Next
    
End Sub

'***********************************************************
'変数値の取得//BOFとEOFの間にある変数名を元に変数値を更新する
'引数:arTextLine>テキストファイルから取得した1次配列+BOF/EOF追加処理済み
'***********************************************************
Private Sub SetVariable(arTextLine)

    Dim CurrentVarName '現在変数名
    Dim VarName '仮の変数名
    Dim VarData '変数値
    Dim IsTarget: IsTarget = False  'BOF行になるまでFalse
    Dim intSplit 'テキストを[=]で区切った最初の位置
    Dim Text '配列の要素
    For Each Text In arTextLine
        Select Case Text
            Case BOF: IsTarget = True '開始//対象フラグ
            Case EOF: Exit For '終了//ループを抜ける
            '文字列
            Case Else
                '対象フラグ時
                If IsTarget = True Then
                    '[=]で区切る
                    intSplit = InStr(Text, "=")
                    
                    If intSplit <= 1 Then
                        '変数名がない>>前の変数名の値に続けて改行でテキストを繋げて上書き
                        VarData = GetUMSVariable(CurrentVarName) & vbCrLf & Text
                        Call SetUMSVariable(CurrentVarName, VarData)
                    Else
                        '仮の変数名で変数値を更新してみる
                        VarName = Left(Text, intSplit - 1)
                        VarData = Right(Text, Len(Text) - intSplit)
                        Call SetUMSVariable(VarName, VarData)
                        If GetUMSVariable(VarName) <> "" Or VarData = "" Then
                             '更新できている>>正しい変数名
                            CurrentVarName = VarName
                        Else
                            '更新できていない>>前の変数名の値に続けて改行でテキストを繋げて上書き
                            VarData = GetUMSVariable(CurrentVarName) & vbCrLf & Text
                            Call SetUMSVariable(CurrentVarName, VarData)
                        End If
                    End If
                End If
        End Select
    Next
    If IsTarget = False Then
        Err.Raise 1, "", "指定したインデックスに一致する変数値データがありませんでした。"
        WScript.Quit
    End If
    
End Sub

'***********************************************************
'ランダムな英数の文字列を生成する
'引数:numDigits>文字列の桁数
'***********************************************************
Function GetRandom(numDigits)

    Dim Ret: Ret = ""
    Dim num, buf
    Dim i
    For i = i To numDigits
        num = Round(Rnd() * 27, 0) + Round(Rnd() * 10, 0)
        If num <= 26 Then buf = Chr(64 + num) Else buf = num - 26
        Ret = Ret & buf
    Next
    GetRandom = Ret
    
End Function

…うーん!Note向きじゃない!

さらに、VBScript互換用に「WScript」モジュールを追加してこれだけ記述してます。

Sub Quit()
   End
End Sub

「標準モジュール」で適当なモジュール追加してもらって、上の長いコードをベタっと貼り、もう一つモジュールを追加してもらってモジュール名を「WScript」とし、短いコードを貼ってもらえば動く。
はず。
(「WinActor変数」シートにテーブルを準備いただくのも忘れずに)

実際のVBE画面

あとは、これが実際のWinActorライブラリとして動くかどうかですね。

終わりに

一部制約はありますし、動作確認は不十分なものの、実際に動かしてみて楽しかったです。
よければ、こういうパターンだと読み取れなくない?とか、こういう機能が欲しい、とか、ここもっとこうできるよ!みたいなのがあれば、教えてもらえるととっても嬉しいです。

最後までご覧いただきありがとうございました!