VBAで配列の初期値をファイルから読み込もう!

複雑なアプリケーションでは、ExcelやOutlookなどを起動した際に、過去登録した情報や環境による違いを吸収するために、初期値を設定したくなります。

初期値をコードに記述するのか?

初期値を定数(Public const)でコードの中に記述してもいいのですが、変更の都度コードを書き換えなくてはならず、ましてやユーザ自身に変えてもらう必要がある場合は決してやってはもらえないでしょう。

そのため、ファイルに初期値を一定のフォーマットで記述した上で、ディクショナリを生成したり、配列を生成したり、単一の変数を読み込んだりできたほうが何かと便利です。その場合、生成する型やファイルフォーマット毎にFunctionを用意しておくと便利です。

ちなみに、ディクショナリ・配列・変数の設定を1つのファイルにしてまとめて書く方が一見シンプルに見えますが、ファイル内の記述の構文解析が必要になること、またユーザが構文を完全に理解しないとエラーが起きますので、別々のファイルに記述して、エラー範囲を抑えることが良いと考えます。

以下は型ごとにFunctionを設け、個々の定義毎にファイルから値を読み込む場合の例になります。

配列の値を読み込もう

ここでは、ファイルから配列の値を読み込む関数名を[setArrayFromeFile]としており、ファイル名を指定することで「配列」を返します。

呼び出す側では

Dim 配列名() As Variant

で配列を定義し、

配列名 = setArrayFromeFile(ファイル名)

で配列の値を取得します。()が変数についたり型についたりややこしいですが、決まり事なので知らんぷりしてそのまま使いますw

配列を読み込む関数例

できるだけ汎用的に作りたいですよね。lineDatas()はファイルの各行を読み込む配列になります。

注)当初でdir()でファイルの存在有無を確認していましたが、サーバファイルなどではSystemFileObjectを利用しないとエラーが出るようです。ファイル有無の判定をdir()からFile.Existsプロパティへ修正しました。

'==================================================================
Function setArrayFromFile(fileName As String) As Variant()
'==================================================================
'Input: fileName 指定フォルダに入れるファイル名
'Output: ファイルあり:初期化された配列、ファイルなし:配列は初期化されず
'-----------------------------------------------------
   Debug_Printb " Function setArrayFromFile(():"
   
   Dim lineData As String
   Dim lineDatas() As Variant
   
   'ファイル名に指定フォルダ名を加えて書き出しファイル名を作成
   Dim OpenFileName As String
       OpenFileName = (フォルダを書きます) & fileName

       'ファイル有無判定
           Dim fileExist As Boolean
           Dim fsobj As FileSystemObject
           Set fsobj = New FileSystemObject
           fileExist = fsobj.FileExists(MyBushoNameFile)  ' ファイルの存在確認
           Set fsobj = Nothing

   If fileName <> "" And fileExist Then
       
       Open OpenFileName For Input As #1
       
           Do While Not EOF(1)
               Line Input #1, lineData
               
               '配列にデータを入れる場所を特定
               Dim InputPointOfArray AS Long
               If Not IsInitArray(lineDatas) then
                  InputPointOfArray = 0
               Else
                  InputPointOfArray = Ubound(lineDatas) + 1
               End if
               
               '配列を確保しデータを追加
               ReDim Preserve lineDatas(InputPointOfArray)
               lineDatas(InputPointOfArray) = lineData
           Loop 
       Close #1
   Else
       Exit Function
   End If
   
   setArrayFromFile = lineDatas()
   
End Function

IsInitArrayは、配列が初期化されているか、定義されたまま初期化されずそのままでは使えない状況かを判定します。初期化判定はSwiftなどの最近の言語では標準的に記述できますが、VBAは古すぎるのでそんなものはありませんw くわしくは「VBAで配列が使えるか(初期化済)を判定しよう!」を参照ください。

ところがどっこい

さて、上のコードで問題なくファイルを読み込めているように見えます。実際動きますし、たいていの場合は読み込めます。

しかーし、ですね。このコードではUTF-8のファイル(これが一般的のようです)を読み込むときに、書かれてもいないゴミデータを読み込むことがありました。

ではどうすればよいかというと、Line Inputをつかっちゃだめ、ということらしい。これで半日つぶれましたよ・・かんべんしてほしいわ。

では、ファイルからの読み込み部分をどのように書くかというと

   Dim lineDatas() As String
   Dim buf As String
   
   With CreateObject("ADODB.Stream")
       .Charset = "UTF-8"
       .Open
       .LoadFromFile OpenFileName
       buf = .ReadText
       .Close
       lineDatas = Split(buf, vbCrLf)
   End With

とするようです。CharSetを指定しているので、本来はコードを分析してS-JISだとかそこらへんもCase分で書くのかもしれませんが、まだわかっていません。

とりあえすUTF-8を指定して、bufにすべてのテキストを読み込み、Splitで行分割したものを配列に読み込む、というのが正解のようです。

なお、書き込み方法にも問題があり、UTF-8で書き込むというのではNGだそうです。UTF-8にはBOM付きという先頭に「UTF-8ですよ」という識別子がついているものがありますが、これはもう古いらしい。

なので、BOM付きではないUTF-8を書き込むというのもあるようで、この場合の書き込ませ方は別途記事にしようと思います(いいねがないのでやめちゃうかもですが・・)。

ちなみにここでlineDatas()は、Split関数を使うのでVariantからString型に変更しています。そのため、下の初期値判定は、IsInitArrayString()(別の記事見てね)を使っています。

空行を処理しよう

一見上記で正しいようですが、途中に空行が入った場合に配列に値が入らない場合があります。ファイルは人間が作るのでその程度のエラーはあるあるですw

なので、空行が生じないように空行を取り除きます。空行のみのファイルがある場合も戻り値となる配列が初期化されないため、エラーであることを呼び出し元に知らせることができます。

   Debug_Printb " Function setArrayFromFile(():空行排除"
   'Input lineDatas()
   'Outoput retArr()
   
   Dim data As Variant
   Dim retArr() As Variant

   If IsInitArrayString(lineDatas) Then
   
       For Each data In lineDatas
           If data <> "" Then
           
               '配列にデータを入れる場所を特定
               'Dim InputPointOfArray AS Long 重複定義
               If Not IsInitArray(retArr) then
                  InputPointOfArray = 0
               Else
                  InputPointOfArray = Ubound(retArr) + 1
               End if
               
               ReDim Preserve retArr(InputPointOfArray)
               retArr(InputPointOfArray) = data    

           Else
           End If
       Next
   Else
       Exit Function
   End If
   

Functionへ返すのはlineDatasではなくなりますので、関数の最後の記述は   setArrayFromFile = retArr() となります。

配列ではなく、変数をファイルから読み込もう

この場合はデータが1つだけファイルに書かれていることになりますが、仮にファイル内に値が1行だけに書かれる場合でも、配列と同じ処理を使います。値は1つだけのはずですので、取得した配列の(0)を使います。

なぜ配列の読み込み処理を流用するかというと、空行のみといったヒューマンエラーや、ファイルがないことなどを判定するため、読み込んだデータの”配列の初期化”有無を使うためです。

DIm 配列名 As Variant
配列名 = setArrayFromeFile(ファイル名)

のあと

Dim 変数名 As String
if IsInitArray(配列名) then
    変数名 = 配列名(0)
Else
    Msgbox "ファイル" & ファイル名 & "がありません"       
    Exit sub
End if

などの処理を書いて、ファイルがない、ファイルが空行のみなどのヒューマンエラーに対応することになります。

ディクショナリではどのように書くかも書こうと思いましたが、時間もないので今回はこの辺で。

ではでは。

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