【VBA】ノード名を指定せずにXMLをパースする

CSVはExcelと相性が良いからか、CSVをExcelに取り込むというのはかなり情報が見つかります。

一方でVBAからXMLをパースしてExcelに取り込むというのも割とよくありそうなんですが、結構情報が少ないです。

また情報が見つかっても、ノード名をハードコーディングしている物が多いです。

取り込むXMLの形式なんて決まっていることが大半なんで当たり前か。

ただ、個人的にはレイアウト不明なXMLをVBAでパースして、内容だけ見やすくしたいということがあるので、コードを考えてみました。

MSXML2.DOMDocumentを使用してXMLをパース

テストに使用するXMLは以下です。

<?xml version="1.0" encoding="UTF-8" ?>
<fruits>
  <item>
    <name>みかん</name>
    <color>オレンジ</color>
    <value>10</value>
  </item>
  <item>
    <name>ぶどう</name>
    <color>紫</color>
    <value>20</value>
  </item>
</fruits>

上記をパースしてExcelシート上に書き出しを行います。

パースをするには、VBAからMSXML2.DOMDocumentを呼び出して利用することにします。

    'XMLをロードする
    Set XMLDoc = CreateObject("MSXML2.DOMDocument.6.0")
    XMLDoc.Load ("C:\test.xml")

これでUTF-8もSIJSも両方読めるようなので超便利ですね。

ちなみに、CreateObject(“MSXML2.DOMDocument”)となっている情報があって、これだと動かなかったためCreateObject(“MSXML2.DOMDocument.6.0”)にしました。

これで時間を滅茶苦茶無駄にした…。

どうやらWindows8やWIndows10では6.0をつけないといけないようですね。

子ノードを再帰呼び出ししてExcelシートに書き出す

作ったソースが以下のとおりです。

Private sh As Object
Private row As Long
Private col As Integer

Public Sub main()
    Dim path As String
    Dim XMLDoc As Object
    
    'ファイル選択ダイアログでパスを指定
    path = getFilePath(ThisWorkbook.path)
    If path = "" Then Exit Sub
    
    'XMLをロードする
    Set XMLDoc = CreateObject("MSXML2.DOMDocument.6.0")
    XMLDoc.Load (path)
    If (XMLDoc.parseError.ErrorCode <> 0) Then
        'ロード失敗時
        MsgBox XMLDoc.parseError.reason, vbCritical
        GoTo endProc
    End If
    
    '出力シートを定義
    Set sh = ThisWorkbook.Worksheets("Sheet1") 'Sheet1に出力
    sh.Cells.Clear
    row = 1
    col = 1
    
    '子ノードを書き出す
    Call getchildren(XMLDoc)
    
'後処理(開放)
endProc:
    Set sh = Nothing
    Set XMLDoc = Nothing

End Sub

'ファイル選択ダイアログでパスを指定
Private Function getFilePath(default As String)
    Dim path As Variant
    
    ChDir default
    path = Application.GetOpenFilename("XMLファイル(*.xml),*.xml")

    If VarType(path) = vbBoolean Then
        getFilePath = ""
    Else
        getFilePath = path
    End If
End Function

'子ノードを書き出す
Private Sub getchildren(XMLParent As Variant)
    If Not XMLParent Is Nothing Then
        For Each XMLChilld In XMLParent.ChildNodes
            If XMLChilld.ChildNodes.Length = 0 Then
                If Not XMLParent.BaseName = "" Then
                    sh.Cells(row, col).Value = XMLParent.BaseName
                    col = col + 1
                    sh.Cells(row, col).Value = XMLChilld.Text
                End If
            Else
                Call getchildren(XMLChilld)
            End If
        Next
        If col <> 1 Then
            row = row + 1
            col = 1
        End If
    End If
End Sub

実行結果

ChildNodesで子ノードの一覧を取得して、それをループさせています。

そして子ノードのChildNodes.Lengthが1以上なら孫ノードもあるはずなので、関数を再帰呼び出しして下の階層を見に行くようにしています。

ChildNodes.Lengthが0なら最下層なので、Excelシートに値を書き込みます。


課題

「XMLをパースしたんだから、階層で表示しろよ」と言われればそのとおりなのです。

ただ今回「レイアウト不明なXMLをExcelで見やすく表示する」ということがやりたかったので、NameとValueのペアで書き出しを行うのは必須でした。

階層で表示したい場合、同じ階層のものは同じ行に表示するのが良いかと思いますが、NameとValueのペアを保ったまま、表示する良い方法が思いつきませんでした。

Jsonをパースした時とかもそうなのですが、Excelは2次元しか無いので、階層が深くなると見やすく表示するのがお手上げなんですよねえ。


8 件のコメント

  • For Each XMLchild in XMLparent.ChildNodesのところでオブジェクト変数またはwithブロック変数が設定されていませんになってしまいます…

    • 情報ありがとうございます。
      こちらでは再現できず、わかりそうになかったです。
      とりあえずXMLParentが取れない場合もあるのかもしれないので、Nothingでハンドルしてみました。

      • 新規に書いていただいたコードを実行したところ、該当箇所で一気にend subまで飛ぶので、XMLparentが取れてない可能性があります。名前空間がついているのが原因でしょうかね…

    • <〇〇>と</〇〇>の間に値がなかったら書き出さないって感じですかね。
      それなら上に記載したコードの「子ノードを書き出す」の部分が以下のようになると思います。
      「If Not XMLParent.BaseName = “” And Not XMLChilld.Text = “” Then」行が変更点です。

      ‘子ノードを書き出す
      Private Sub getchildren(XMLParent As Variant)
      If Not XMLParent Is Nothing Then
      For Each XMLChilld In XMLParent.ChildNodes
      If XMLChilld.ChildNodes.Length = 0 Then
      If Not XMLParent.BaseName = “” And Not XMLChilld.Text = “” Then
      sh.Cells(row, col).Value = XMLParent.BaseName
      col = col + 1
      sh.Cells(row, col).Value = XMLChilld.Text
      End If
      Else
      Call getchildren(XMLChilld)
      End If
      Next
      If col <> 1 Then
      row = row + 1
      col = 1
      End If
      End If
      End Sub

  • どなたか、ご教示ください。
    現在、こちらのサイトを参考に、XMLファイルを開いてデータベースに内容を保存するところまでできました。
    フォルダー内に複数のファイルがあるため、フォルダー指定をして一括でデータベースに保存したいのです。
    どのように、書けば良いでしょうか。いろいろ思考錯誤しながらやっておりますが、どれも上手くいきません。

    • フォルダ内のファイルをループさせる処理ですが以下でいかがでしょうか。
      Debug.Print fileの行の時点でfile変数にファイルのパスが格納されていますので、これを使って処理をすればできるかと思います。
      ※ブログのコメントがスペースを許可しない設定なので見にくくてすみません

      Sub foldertest()
      Const INIT_FOLDER = “C:\”

      Dim folder As String
      ChDir INIT_FOLDER
      With Application.FileDialog(msoFileDialogFolderPicker)
      If .Show = True Then
      folder = .SelectedItems(1)
      Else
      ‘キャンセルが押された場合
      Exit Sub
      End If
      End With

      Dim FSO As Object
      Set FSO = CreateObject(“Scripting.FileSystemObject”)

      Dim file As Object

      ‘フォルダのファイルをループして処理する
      For Each file In FSO.GetFolder(folder).Files
      ‘拡張子がxmlの場合
      If (FSO.GetExtensionName(file) = “xml”) Then
      ‘ここまでの処理でfileという変数にファイルパスが入っているので、この変数の値を使ってメインの処理を行う
      Debug.Print file
      End If
      Next
      End Sub

  • ヒロユキ様
    大変ありがとうございます。
    答えを頂いているのに四苦八苦いたしましたが、無事に思い通りの動きをすることができました。

  • コメントを残す

    メールアドレスが公開されることはありません。

    日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)