今回の記事では、VBAでXMLデータを読み込んで解析する場合のサンプルプログラムと、読み込んだXMLデータの解析時に必要になる操作方法の知識を紹介していきます。
尚、当ブログでは、以前に「VBAでXMLを作成して出力する場合の実装例とXML操作の基礎知識まとめ」といった記事を公開しましたが、この記事ではあくまでVBAからXMLデータを作成し、それを出力するまでの処理を解説した内容であり、XMLを読み込んで解析する場合は、必要となる技術的な知識が異なります。
上記のリンク先にある記事と今回の記事の内容を組み合わせることで、XMLの読み込み、書き込みのどちらも問題無く習得できた状態になる想定です。
XMLフォーマットの基礎知識とVBAでXMLを扱う際の設定
「XML」とは、ウェブページなどでも使われる「HTML」にもよく似た書式のデータフォーマットです。
主にアプリケーション間でのデータの受け渡しで使用されます。
HTMLでは文章を”タグ”で囲むことで、そのHTMLページをブラウザが解析し、特定の文字列に装飾をしたり、罫線で囲むなどの表示に変換します。
XMLでも同様にタグでデータを囲みますが、そのタグはブラウザに装飾させるために使われるものではなく、データを階層構造で表現するために使用されます。
一般的に、異なるアプリケーション間でデータを受け渡す場合はCSVファイルなどがよく利用されますが、CSV形式のデータでは、データを単純な表形式でしか表現できません。
XMLではデータを階層構造で表現できるため、複雑なデータ構造を一つのファイルで扱うことが可能です。
詳しくは、前述した以前の記事で紹介しておりますため、興味があれば以下の記事もご一読ください。
このように複雑なデータ構造も表現できることで、却ってXMLの読み込みや書き込みでは処理が複雑になりがちです。
また、XMLをVBAで扱う場合は、VBE側で個別に参照設定を追加しておくと便利です。
参照設定を追加するライブラリ名は、Windows8.1以降のOSでは「Microsoft XML. v6.0」です。
参照設定を入れずに、プログラム内で明示的にライブラリを参照してインスタンスを作成する「遅延バインディング」で使用することもできますが、新しいOSと古いOSが混在している環境で使用するケースを除き、参照設定を追加して実装することをおススメします。
当記事でも、参照設定に追加されていることを前提としてサンプルプログラムを紹介します。
今回の記事で使用するサンプルXML
今回の記事では、以下のXMLファイルを用意して、そのXMLファイルをVBAから読み込みながら実装例を紹介していきます。
<?xml version="1.0" encoding="utf-8"?>
<customer>
<customer_no>123456</customer_no>
<name>
<last_name>山田</last_name>
<first_name>太郎</first_name>
</name>
<address>東京都〇〇区なんとかなんとか</address>
<telephone>
<number type="1">03-1234-5678</number>
<number type="2">080-1234-5678</number>
</telephone>
<email1></email1>
<email2/>
</customer>
簡単なXMLデータですが、冒頭に宣言部分があり、要素で階層化され属性も使用されているため、このXMLから自由に値が取れるようになれば、実務でのXMLの読み込み処理の実装に困ることはないかと思います。
あと、要素の「email1」と「email2」はともに要素の値が空です。
要素の値が無い状態はこのように二種類の表現方法があり、どちらを使用しても問題はありません。
こちらも覚えておいてください。
VBAでXMLを読み込みする際によく使うDOMオブジェクト
当項では、VBAでXMLファイルを読み込んで解析する場合に、よく使用することになるDOM(Document Object Model)のオブジェクトを抜粋して紹介していきます。
オブジェクト名 | オブジェクト型名 | 解説 |
---|---|---|
Document | MSXML2.DOMDocument60 | XMLデータ全体を格納するオブジェクト |
Node | MSXML2.IXMLDOMNode | XMLを構成する各データのベースとなるオブジェクト このオブジェクトと次のリストで大半の処理は賄える |
NodeList | MSXML2.IXMLDOMNodeList | ノードのコレクションを格納するオブジェクト メソッドやプロパティではこの型が使用されていることが多い |
Element | MSXML2.IXMLDOMElement | 要素を表すオブジェクト |
Attribute | MSXML2.IXMLDOMAttribute | 属性を表すオブジェクト |
他にもオブジェクトは色々ありますが、XMLの解析で必ず使用する、又は使用頻度が高いオブジェクトとしてはこれぐらいかと思います。
まずDocumentオブジェクトを生成して、解析するXMLファイルを読み込んだ後、NodeListを作り、ループ処理などでNodeオブジェクトやElementオブジェクトに対してメソッドを実行したり、プロパティを取得するといった操作をするイメージです。
VBAでXMLを読み込み解析する場合の基本操作
当項では、VBAでXMLファイルの読み込みをして、中身を解析する場合に必要となる、各メソッドやプロパティの基本的な操作方法を、サンプルコードを交えて紹介していきます。
要素名を指定してその要素の値を取得する
XML内の特定の要素名を指定してその値を取得したい場合は、DOMDocumentオブジェクトの「getElementsByTagName」を使用します。
この「getElementsByTagName」では、XMLデータの階層に関わらず要素名を指定するだけで要素を取得できるため、予め取得したい要素が決まっている場合は、このメソッドが便利です。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
getElementsByTagName | MSXML2.DOMDocument60 | 読み込んだXMLの検索したい要素名 アスタリスク(*)で全要素 |
指定された要素名のIXMLDOMNodeListオブジェクト |
このメソッドでは、DOMDocumentオブジェクト内のXMLを走査し、引数で指定した要素を取得します。
引数に要素名を指定せず、アスタリスクを指定すると、対象のXML内のすべての要素を取得してきます。
戻り値は「IXMLDOMNodeList」オブジェクトです。
戻り値を変数に入れてプロパティやメソッドをVBEで自動表示させたければ、予め戻り値を格納するオブジェクト変数をIXMLDOMNodeList型で宣言しておきます。
サンプルコード getElementsByTagName使用例
以下のサンプルコードでは、DOMDocumentオブジェクト内の「getElementsByTagName」を使用して、指定した要素名の要素を取得し、その要素名と要素の値をdebug.printで取り出します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test1()
Dim DOMDoc As MSXML2.DOMDocument60
Dim nodeList As IXMLDOMNodeList
Dim node As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素リストを取得します。
Set nodeList = DOMDoc.getElementsByTagName("number")
'引数にアスタリスクを指定した場合はすべての要素を取得
'Set elmlist = DOMDoc.getElementsByTagName("*")
'リストをループします。
For Each node In nodeList
'要素の名前を取り出します。
Debug.Print node.nodeName
'要素の値を取り出します。
Debug.Print node.Text
Next
Set node = Nothing
Set nodeList = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
number 03-1234-5678 number 080-1234-5678
要素名と属性名を指定してその要素内の属性の値を取得する
どの要素にどの属性が予め把握できているケースなどで、要素名と属性名をともに指定してその属性の値を取りたい場合は、IXMLDOMElementオブジェクトの「getAttribute」メソッドを使用できます。
※他にも属性の値を取得する方法はあります。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
getAttribute | IXMLDOMElement | IXMLDOMElement内の要素の検索したい属性名 | 属性の値(Text) |
サンプルコード getAttributeメソッド使用例
以下のサンプルコードでは、DOMDocumentオブジェクト内の「getElementsByTagName」を使用して、指定した要素名の要素を取得し、その要素のなかの属性名をgetAttributeメソッドで指定して属性の値をdebug.printで取り出します。
「getAttribute」メソッドはIXMLDOMNodeオブジェクトには存在しないのでご注意ください。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test2()
Dim DOMDoc As MSXML2.DOMDocument60
Dim nodeList As IXMLDOMNodeList
Dim elm As IXMLDOMElement
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素のリストオブジェクトを取得します。
Set nodeList = DOMDoc.getElementsByTagName("number")
'リストをループします。
For Each elm In nodeList
'属性名を指定して値を取り出します。
Debug.Print elm.getAttribute("type")
Next
Set elm = Nothing
Set nodeList = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
1 2
サンプルコード Attributes.getNamedItem(“属性名”)使用例
以下のサンプルコードでは、IXMLDOMNodeオブジェクトから属性名を指定して、属性名と値を取り出します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test2()
Dim DOMDoc As MSXML2.DOMDocument60
Dim nodeList As IXMLDOMNodeList
Dim node As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素のリストオブジェクトを取得します。
Set nodeList = DOMDoc.getElementsByTagName("number")
'リストをループします。
For Each node In nodeList
Debug.Print node.Attributes.getNamedItem("type").nodeName
Debug.Print node.Attributes.getNamedItem("type").NodeValue
Next
Set node = Nothing
Set nodeList = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
type 1 type 2
サンプルコード Attributes.Item(index)使用例
以下のサンプルコードでは、IXMLDOMNodeオブジェクトから属性を配列のインデックス番号を指定して、属性名と値を取り出します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test2()
Dim DOMDoc As MSXML2.DOMDocument60
Dim nodeList As IXMLDOMNodeList
Dim node As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素のリストオブジェクトを取得します。
Set nodeList = DOMDoc.getElementsByTagName("number")
'リストをループします。
For Each node In nodeList
Debug.Print node.Attributes.Item(0).nodeName
Debug.Print node.Attributes.Item(0).NodeValue
Next
Set node = Nothing
Set nodeList = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
type 1 type 2
指定した要素に子要素があるかを取得する
指定した要素に子要素があるかを判別するには、IXMLDOMNodeオブジェクトの「HasChildNodes」メソッドを使用します。
尚、Microsoftのドキュメントでは、ノードに子があるかを判別すると解説されていますが、ここで言う子要素は、階層構造の子ノードだけではなく、対象の要素に値が入っている状態でもTrueになります。
また、対象の要素には子要素が無く、値も無いけど属性が入っている場合はFalseです。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
HasChildNodes | IXMLDOMNode | 真偽※子要素があればTrue |
サンプルコード HasChildNodesの使用例
以下のサンプルコードでは、すべての要素の子要素有無を判定します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test3()
Dim DOMDoc As MSXML2.DOMDocument60
Dim elm As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'すべての要素を指定してリストオブジェクトを取得します。
Set elmlist = DOMDoc.getElementsByTagName("*")
'リストをループします。
For Each elm In elmlist
Debug.Print elm.nodeName
If elm.HasChildNodes Then
'※参考 以下の書き方でも同じ判定
'If elm.ChildNodes.Length > 0 Then
Debug.Print "子ノードあり"
Else
Debug.Print "子ノードなし"
End If
Next
Set elm1 = Nothing
Set elmlist = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
customer 子ノードあり customer_no 子ノードあり name 子ノードあり last_name 子ノードあり first_name 子ノードあり address 子ノードあり telephone 子ノードあり number 子ノードあり number 子ノードあり email1 子ノードなし email2 子ノードなし
指定した要素の数や属性の数を取得する
指定した要素の数を取得する場合は、IXMLDOMNodeListオブジェクトの「length」プロパティから個数を取得できます。
また、要素ごとの属性の数は、IXMLDOMNodeオブジェクト内の要素の「Attributes」プロパティ内の「length」プロパティから取得できます。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
lengthプロパティ | IXMLDOMNodeList | 個数(Long型) |
サンプルコード Nodeオブジェクトに対するlength取得例
以下のサンプルコードでは、ルート要素(サンプルXMLでは「customer」)配下のすべての要素数を取得します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test4()
Dim DOMDoc As MSXML2.DOMDocument60
Dim elmlist As IXMLDOMNodeList
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素のリストオブジェクトを取得します。
Set elmlist = DOMDoc.getElementsByTagName("*")
'要素の数を取得します。
Debug.Print elmlist.Length
Set elmlist = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
11
サンプルコード NodeオブジェクトのAttributesに対するlength取得例
以下のサンプルコードでは、要素名を指定して、その要素ごとに含まれる属性の個数を取得します。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test4()
Dim DOMDoc As MSXML2.DOMDocument60
Dim elmlist As IXMLDOMNodeList
Dim elm As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
'要素名を指定して要素のリストオブジェクトを取得します。
Set elmlist = DOMDoc.getElementsByTagName("number")
'リストをループします。
For Each elm In elmlist
'属性の数を取得します。
Debug.Print elm.Attributes.Length
Next
Set elmlist = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
1 1
階層ごとにループを入れ子して要素の名前と値を取得する
ループを入れ子しながら、浅い階層から深い階層へ順に要素の名前と値を取得する場合は、IXMLDOMNodeオブジェクトの「ChildNodes」プロパティを使用します。
このプロパティでは子ノードのコレクションを保持します。
そのコレクションからIXMLDOMNodeオブジェクトを生成して、更に「ChildNodes」プロパティを参照していくことで、階層ごとに要素を取ることが可能です。
※他にも色々方法はあります。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
childNodesプロパティ | IXMLDOMNode | childNodesプロパティ (コレクション) |
サンプルコード ChildNodes使用例
下記のサンプルコードでは、浅い階層から順にループを入れ子して要素の名前と値を取得しています。
ループの状態によっては、コレクション内に「Text」が無いのに値を取ろうとしている結果、debug.printの出力ではエラーになっていますが、一応動きます。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test5()
Dim DOMDoc As MSXML2.DOMDocument60
Dim elmlist As IXMLDOMNodeList
Dim elm As IXMLDOMNode
Dim elm1 As IXMLDOMNode
Dim elm2 As IXMLDOMNode
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
Set elmlist = DOMDoc.ChildNodes
For Each elm In elmlist
Debug.Print elm.nodeName
For Each elm1 In elm.ChildNodes
Debug.Print elm1.nodeName
For Each elm2 In elm1.ChildNodes
Debug.Print elm2.nodeName
Debug.Print elm2.Text
Next
Next
Next
Set elm2 = Nothing
Set elm1 = Nothing
Set elm = Nothing
Set elmlist = Nothing
Set DOMDoc = Nothing
End Sub
上記の実行結果
xml customer customer_no #text 123456 name last_name 山田 first_name 太郎 address #text 東京都〇〇区なんとかなんとか telephone number 03-1234-5678 number 080-1234-5678 email1 email2
再帰ですべての要素とその値を取得する
XMLフォーマットは階層構造でデータを表現することができます。
階層構造が予め固定されているデータであれば、条件分岐をしながらデータを解析して要素ごとの値を取り出すことができますが、階層が深い場合や、階層構造が予め固定されていないデータを網羅的に読み込むためには、再帰関数を作成し、再帰で動的に読み込みをします。
再帰でXMLを解析する場合はIXMLDOMNodeオブジェクトの「HasChildNodes」メソッドでその要素が空か否かを判定しつつ、IXMLDOMNodeオブジェクトの「ChildNodes」プロパティを再帰関数に再帰的に渡します。
また、ノードのコレクション内の種類を判定するために、IXMLDOMNodeオブジェクトの「NodeType」プロパティも取得します。
メソッド/プロパティ名 | メンバ | パラメータ | 戻り値 |
---|---|---|---|
HasChildNodes | IXMLDOMNode | 真偽※子がある場合はTrue | |
nodeTypeプロパティ | IXMLDOMNode | ノードの種類(数値) |
尚、nodeTypeプロパティで保持しているノードの種類は、以下のリンク先でご確認ください。
サンプルコード 再帰実装例
以下のサンプルコードでは、すべての要素名とその値を再帰で取得します。
このサンプルでは要素の名前と値のみ取得していますが、更に属性の有無の判定や、属性の個数も取得することで、属性の名前や値も含めて再帰で取得可能です。
Option Explicit
Const XMLFILE As String = "C:\sample.xml"
Sub test6()
Dim DOMDoc As MSXML2.DOMDocument60
'DOMDocumentのインスタンスを作成します。
Set DOMDoc = New MSXML2.DOMDocument60
'解析対象のXMLファイルを読み込みます。
DOMDoc.Load (XMLFILE)
Call xmlReAnalysis(DOMDoc.ChildNodes)
Set DOMDoc = Nothing
End Sub
'XML解析用再帰関数
Sub xmlReAnalysis(objNode As IXMLDOMNodeList)
Dim elm As IXMLDOMNode
For Each elm In objNode
'ノードの種類で処理を分岐します。
Select Case elm.NodeType
'要素の場合
Case 1
Debug.Print elm.nodeName
'テキスト(値)の場合
Case 3
Debug.Print elm.Text
End Select
'対象の要素に子があるか判定します。
If elm.HasChildNodes Then
'子があれば再帰関数を呼びます。
Call xmlReAnalysis(elm.ChildNodes)
End If
Next
Set elm = Nothing
End Sub
上記の実行結果
customer customer_no 123456 name last_name 山田 first_name 太郎 address 東京都〇〇区なんとかなんとか telephone number 03-1234-5678 number 080-1234-5678 email1 email2
最後に
今回の記事では、VBAでXMLを読み込んで解析する場合の必要になる知識と実際のサンプルコードを紹介しました。
最近のデータ交換用フォーマットでは、JSON形式が採用されることも多いのですが、XML形式もまだまだ頻繁に目にします。
VBAでXMLを扱えるようにしておくと、VBAを利用した他システム連携なども可能になるため、そのようなケースで非常に重宝します。
慣れるまでは難しく感じますが、是非挑戦してみてください。
今回も長々と読んでいただきましてありがとうございます。
それでは皆さん、ごきげんよう!