今回の記事では、VBAを使って、xml形式のデータを生成してxmlファイルに出力する一連の処理の実装例を紹介します。
また、xmlに関する簡単な解説と、VBAでxmlを操作する場合に知っておいたほうが良い知識もまとめておきます。
ぜひ参考にしてください。
↓XMLの読み込みと解析をする場合はこちら。
「XML」ってそもそもなに?
当ブログは多くの記事が初心者向けとして書いており、今回の記事も、
という部分から紹介していきます。
まずは、Wikipediaの記事を引用してみましょう。
Extensible Markup Language(エクステンシブル マークアップ ランゲージ)は、基本的な構文規則を共通とすることで、任意の用途向けの言語に拡張することを容易としたことが特徴のマークアップ言語の総称である。一般的にXML(エックスエムエル)と略称で呼ばれる。
〜中略〜
XML の仕様は、World Wide Web Consortium (W3C) により策定・勧告されている。
〜中略〜
XMLの最も重要な目的は、異なる情報システムの間で、特にインターネットを介して、構造化された文書や構造化されたデータの共有を、容易にすることである。
まったくXMLを知らない人が、上記の文面を読んでもよくわからないかもしれません。
XMLの特徴を簡単に以下でまとめます。
- 仕様がWSCにより標準化された文章フォーマット
- システム間のデータ受け渡しやウェブ技術でよく利用される
- データや文章をタグで囲みテキスト形式のまま構造化して表現できる
「W3C」とは、インターネットやウェブ関連の技術の標準化を担っている団体である「World Wide Web Consortium」です。
XMLはこの団体で標準化された文章(データ)形式に関する規格の一つです。
また、XMLは様々なシステムや技術で広く利用されており、例えば異なるシステム間のデータ連携をする場合に、CSVファイルを受け渡してやり取りするケースも多いのですが、CSVでは表現できないような複雑なデータで受け渡ししないといけない場合などで利用されます。
また、ウェブアプリケーションが受け渡しをするデータにおいても広く利用されています。
また、XMLの大きな特徴としては、テキスト形式のファイルでありながら、親子関係などの構造化したデータや文章を表現できるところです。
XMLでは、データや文章をタグで囲みます。
HTMLとも非常に似ているのですが、HTMLの場合、使用できるタグはHTMLの仕様で決まっており、その囲ったタグによりブラウザでの見え方が変わるなど、個々のタグには決められた用途があります。
XMLもタグで囲みますが、個々のタグは任意に好きなタグ名を使用することができます。
HTMLの場合はブラウザに装飾を解釈させるためにタグで囲いますが、XMLの場合は文章やデータの構造を表現するためだけに使用します。
これらの説明を読んでも、いまいちイメージが湧かないかと思うので、XMLの記述例のサンプルを紹介します。
xmlサンプルと解説
まずはXMLフォーマットの基本的な記述ルールをまとめます。
- 文字コードは「UTF-8(BOM無し)」
- 改行コードは「LF」
- 一行目はXML宣言
- ルート要素は一つ
尚、文字コードや改行コードは上記で紹介したもの以外でも使用はできます。
例えば文字コードであれば「シフトJIS」でも良いですし、改行コードは「LFCF」でも結構です。
※xmlで使用する文字コードは、xmlフォーマットの一行目の宣言部のなかで指定し、その指定した文字コードと同じであれば、他の文字コードを使用しても問題はありません。
但し、前述した「W3C」で推奨しているのは「UTF-8」+「LF」であり、システム連携などで、相手側のシステムの仕様上、異なる形式のxmlデータを求められない限りは、「W3C」で推奨する「UTF-8」+「LF」を基本としてxmlを作成することをオススメします。
実際の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>
<email/>
</customer>
XMLの各要素などの呼び方
XMLで使われている要素などの呼び方も紹介していきます。
基本的には以下で紹介している項目が理解できれば十分です。
上記画像のなかで「要素」と呼んでいるのは「ノード」でも結構です。
厳密には、「要素」は「開始タグ」と「終了タグ」で値を囲んだひと塊を指しております。
「ノード」はもうすこし広義な名称で、この名称のなかに「要素ノード」や、
<!– コメント文字列 –>
のタグで囲む「コメントノード」などもあるのですが、一般的にXMLにおいて「ノード」と言えば「要素ノード」を指していることが多いため、当記事では「要素ノード」=「ノード」として呼称していきます。
XMLを生成するサンプルコードと基礎知識の解説
当項では、上記のXMLファイルを出力する場合の実装例を紹介します。
Sub CreateXML() 'IXMLDOMNodeオブジェクトの数が多い場合は配列として宣言してください。 '当サンプルコードでは説明の都合上配列を使いません。 Dim DOMDoc As MSXML2.DOMDocument60 Dim DeclareNode As MSXML2.IXMLDOMNode Dim RootNode As MSXML2.IXMLDOMNode Dim ParentNode01 As MSXML2.IXMLDOMNode Dim ParentNode02 As MSXML2.IXMLDOMNode Dim ParentNode03 As MSXML2.IXMLDOMNode Dim ParentNode04 As MSXML2.IXMLDOMNode Dim ParentNode05 As MSXML2.IXMLDOMNode Dim ChildNode01 As MSXML2.IXMLDOMNode Dim ChildNode02 As MSXML2.IXMLDOMNode Dim ChildNode03 As MSXML2.IXMLDOMNode Dim ChildNode04 As MSXML2.IXMLDOMNode Dim attr01 As MSXML2.IXMLDOMAttribute Dim attr02 As MSXML2.IXMLDOMAttribute 'XMLDOMオブジェクトを作成します。 Set DOMDoc = New MSXML2.DOMDocument60 DOMDoc.async = False '宣言部ノードを作成してXMLDOMオブジェクトの子ノードとして追加します。 Set DeclareNode = DOMDoc.appendChild(DOMDoc.createProcessingInstruction("xml", "version=""1.0"" encoding=""UTF-8""")) 'ルートノードを作成してXMLDOMオブジェクトの子ノードとして追加します。 Set RootNode = DOMDoc.appendChild(DOMDoc.createNode(NODE_ELEMENT, "customer", "")) 'ノード「customer_no」を作成してルートノードの子ノードとして追加します。 Set ParentNode01 = RootNode.appendChild(DOMDoc.createNode(NODE_ELEMENT, "customer_no", "")) 'ノード「customer_no」の値を指定します。 ParentNode01.Text = "123456" 'ノード「name」を作成してルートノードの子ノードとして追加します。 Set ParentNode02 = RootNode.appendChild(DOMDoc.createNode(NODE_ELEMENT, "name", "")) 'ノード「last_name」を作成して「name」ノードの子ノードとして追加します。 Set ChildNode01 = ParentNode02.appendChild(DOMDoc.createNode(NODE_ELEMENT, "last_name", "")) 'ノード「last_name」の値を指定します。 ChildNode01.Text = "山田" 'ノード「first_name」を作成して「name」ノードの子ノードとして追加します。 Set ChildNode02 = ParentNode02.appendChild(DOMDoc.createNode(NODE_ELEMENT, "first_name", "")) 'ノード「first_name」の値を指定します。 ChildNode02.Text = "太郎" 'ノード「address」を作成してルートノードの子ノードとして追加します。 Set ParentNode03 = RootNode.appendChild(DOMDoc.createNode(NODE_ELEMENT, "address", "")) 'ノード「address」の値を指定します。 ParentNode03.Text = "東京都〇〇区なんとかなんとか" 'ノード「telephone」を作成してルートノードの子ノードとして追加します。 Set ParentNode04 = RootNode.appendChild(DOMDoc.createNode(NODE_ELEMENT, "telephone", "")) 'ノード「number」を作成して「telephone」ノードの子ノードとして追加します。 Set ChildNode03 = ParentNode04.appendChild(DOMDoc.createNode(NODE_ELEMENT, "number", "")) '属性ノード「type」を作成して「telephone」ノードの属性に追加します。 Set attr01 = ChildNode03.Attributes.setNamedItem(DOMDoc.createNode(NODE_ATTRIBUTE, "type", "")) '属性ノード「type」の値を指定します。 attr01.NodeValue = "1" 'ノード「number」の値を指定します。 ChildNode03.Text = "03-1234-5678" 'ノード「number」を作成して「telephone」ノードの子ノードとして追加します。 Set ChildNode04 = ParentNode04.appendChild(DOMDoc.createNode(NODE_ELEMENT, "number", "")) '属性ノード「type」を作成して「telephone」ノードの属性に追加します。 Set attr02 = ChildNode04.Attributes.setNamedItem(DOMDoc.createNode(NODE_ATTRIBUTE, "type", "")) 'ノード「number」の値を指定します。 ChildNode04.Text = "080-1234-5678" '属性ノード「type」の値を指定します。 attr02.NodeValue = "2" 'ノード「email」を作成してルートノードの子ノードとして追加します。※ノードの値を指定しないケース Set ParentNode05 = RootNode.appendChild(DOMDoc.createNode(NODE_ELEMENT, "email", "")) 'XMLDOMオブジェクトのSaveメソッドに出力先ファイルパスを渡してXMLファイルを出力します。 DOMDoc.Save "C:\tmp\test.xml" 'XMLDOMオブジェクトを破棄します。 Set DOMDoc = Nothing End Sub
上記のサンプルコードの冒頭のコメントでも記載していますが、使用する予定の要素の数分の「IXMLDOMNode」オブジェクト用変数を宣言していますが、正直これだと不便なので、実際に実務で実装する場合は、以下のようにオブジェクト変数を配列として使用する実装をおススメします。
Dim Node(50) As MSXML2.IXMLDOMNode
また、次項では、上記のコードを実装するうえで必用な知識の解説や、個々の処理で使用しているメソッドの解説もしていきます。
XMLを扱う場合の参照設定
VBAでXMLフォーマットを扱う場合、一般的には「MSXML」というライブラリを使用します。
VBEの上部メニューにある「ツール」→「参照設定」を表示し、Windows 8.1以降の場合は「Microsoft XML. v6.0」を選択します。
この参照設定をしなくても、コード内で「MSXML2.DOMDocument60」をCreateObjectすれば使えますが、入力補完などが使えないので、参照設定で事前バインディングをしておいたほうが便利です。
XML作成時の基本オブジェクト
VBAでXMLを扱う場合は、まず「XML DOMオブジェクト」を作成します。
今使用されているWindowsであれば、対象のライブラリは「MSXML2.DOMDocument60」です。
※Windows7など古いOSの場合は、用意されているライブラリは「MSXML2.DOMDocument」で名前が異なります。
このライブラリを指定してオブジェクトを生成し、用意されているメンバーを使用しながらXMLの作成や取得などを行います。
今回のサンプルコードでは、オブジェクト変数の宣言を以下のように実施しております。
Dim DOMDoc As MSXML2.DOMDocument60
その後、処理の冒頭でインスタンスを生成します。
Set DOMDoc = New MSXML2.DOMDocument60
このオブジェクト変数を使用してXMLの各操作を実施していきます。
尚、Microsoftのドキュメントのリンクも紹介しておきます。
VBA用のドキュメントではないのですが、「XML DOMオブジェクト」の仕様については変わらないので十分参考になると思います。
XMLの宣言部を作成する
XMLフォーマットでは、いくつかの決められた記述ルールがあります。
XMLフォーマットの冒頭で宣言文を入れるのは定型のルールです。
宣言部を作成する際のメソッドは「createProcessingInstruction」です。
「createProcessingInstruction」で宣言部を作成し、XML DOMオブジェクトの子ノードとして追加します。
子ノードを作成するには「appendChild」メソッドを使用します。
「appendChild」メソッドの詳細は後述します。
メソッド | createProcessingInstruction(DOMDocument60) |
---|---|
引数1 | target(string) |
引数2 | data(string) バージョンと文字コードを指定する文字列 |
実装例 | [XMLDOMオブジェクト].createProcessingInstruction("xml", "version=""1.0"" encoding=""UTF-8""") |
通常ノードを作成する
前述した宣言ノードは特殊なノードであり、それ以外の通常のノードを作成するメソッドは「createNode」です。
「createNode」メソッドの引数の「NodeType」に定数「NODE_ELEMENT」を指定して新規ノードを生成し、そのノードを後述する「appendChild」メソッドに渡しながら構造化していきます。
メソッド | createNode(DOMDocument60) |
---|---|
引数1 | type(XmlNodeType) 作成するノードのタイプを定数で指定 |
引数2 | name(string) ノードの要素名を文字列で指定 |
引数3 | namespaceURI(string) 空文字列可。値を入れると「xmlns=”入力文字列”」が指定される |
実装例 | [XMLDOMオブジェクト].createNode(NODE_ELEMENT, "要素名", "名前空間文字列") |
子ノードを作成する。
XMLでは、ルートノードを階層構造の頂点として、そのルートノードに各要素を子ノードとしてぶら下げていきます。
当記事では、上位階層のノードを「親ノード」、下位階層のノードを「子ノード」を呼称しておりますが、好きに表現してもらえれば結構です。
子ノードを追加するメソッドは「appendChild」です。
XMLを作成する処理の大半は、この子ノードを追加していく処理になります。
ここまで紹介した「XML DOMオブジェクト」や宣言部ノード、それ以外のノードの関連性のイメージとしては以下になります。
メソッド | appendChild(IXMLDOMNode) |
---|---|
引数1 | newChild(XmlNode) |
実装例 | [親ノードオブジェクト].appendChild([XMLDOMオブジェクト].createNode(NODE_ELEMENT, "作成する要素名", "")) |
ノードに値を追加する
各ノードに対して値を追加するには、ノードオブジェクトの「Text」プロパティに値を代入します。
実装例 | ノードオブジェクト.Text = "要素の値" |
---|
要素の値がない「空要素」について
createNode(NODE_ELEMENT, "作成する要素名", "")で要素を作り、その要素に値を指定しない場合は「空要素」になります。
XMLでは以下のように表現されます。
<email></email>
また、空要素ではタグで囲む記述が省略され、以下のようにも表現できます。
</email>
VBAで使用する「XMLDOMオブジェクト」では後者の </email> の書式で生成されます。
ノードに属性を追加する
XMLでは要素毎に任意のタグで値を囲みながらデータ構造を表現できますが、要素に対して「属性」を持たせて、その属性に値を持たせることもできます。
以下のようなイメージです。
<要素 属性="属性の値">要素の値</要素>
この属性は一つの要素に対して複数指定することもできます。
<要素 属性="属性の値" 属性="属性の値">要素の値</要素>
この属性でもデータを表現することができて、「要素」と「属性」をどのように使い分けるかについての決まりはありません。
基本的には「要素」でデータを扱い、多次元的に異なるデータも扱う場合などに「属性」も組み合わせるとかでしょうか。
属性を作成する際のメソッドは「setNamedItem(IXMLDOMNode.attributes)」です。
メソッド | setNamedItem(IXMLDOMNode.attributes) |
---|---|
引数1 | 属性ノード(node) |
実装例 | [属性追加対象要素のノードオブジェクト].Attributes.setNamedItem([XMLDOMオブジェクト].createNode(NODE_ATTRIBUTE, "属性名", "")) |
属性に値を指定する。
前述した属性に値を指定します。
IXMLDOMAttributeオブジェクトの「NodeValue」プロパティに値を代入します。
実装例 | 属性オブジェクト.NodeValue = "属性の値" |
---|
XMLファイルに出力する。
「XML DOMオブジェクト」に対して作成したxmlデータをファイルに出力する場合は、「save」メソッドを使用します。
メソッド | save(DOMDocument60) |
---|---|
引数1 | ファイル名(string) |
実装例 | [XMLDOMオブジェクト].Save "C:\tmp\test.xml" |
冒頭の宣言部のみ改行が入り、以降は1行で全ての要素が出力されます。
XML文章のルールとしては、改行やインデントは必須ではないため、本来は問題はないはずですが、人が目視で内容を確認する際には見辛いです。
また、XMLを取り込む他システムの仕様によっては、要素ごとに改行されていることが前提となっている場合もあります。
出力するXMLファイルに改行とインデントを付与する。
前項で紹介したとおり、XMLDOMオブジェクトのsaveメソッドで出力しただけでは、要素ごとの改行やインデントは付与されません。
ただ、色々な事情により改行やインデントを付けたい場合もあるかと思います。
その場合は、SAXXMLReaderオブジェクトとMXXMLWriterオブジェクトを使用することで綺麗に改行とインデントを付けてくれます。
以下のリンク先の記事を参考に、今のライブラリのバージョンに合わせて微修正したコードを紹介しておきます。
Function indent(ByVal xml As String) As String Dim writer As MSXML2.MXXMLWriter60 Dim reader As MSXML2.SAXXMLReader60 Dim dom As MSXML2.DOMDocument60 Dim n As MSXML2.IXMLDOMNode Set writer = New MSXML2.MXXMLWriter60 ' xml宣言を書き込まない writer.omitXMLDeclaration = True ' インデントする writer.indent = True Set reader = New MSXML2.SAXXMLReader60 Set reader.contentHandler = writer reader.Parse xml ' 元のxmlから、xml宣言候補を退避 Set dom = New MSXML2.DOMDocument60 dom.LoadXML xml Set n = dom.ChildNodes(0) ' インデントされたxmlを読み込む ' 元のxmlにxml宣言があったとしても、除外されている dom.LoadXML writer.output ' 元のxmlにxml宣言があれば、インデントされたxmlに追加 If n.nodeName = "xml" And n.NodeType = NODE_PROCESSING_INSTRUCTION Then dom.InsertBefore n, dom.ChildNodes(0) End If ' インデントされたxmlを返す indent = dom.xml End Function
このFunctionプロシージャの呼び出し例は以下です。
[XMLDOMオブジェクト].LoadXML indent([XMLDOMオブジェクト].xml) [XMLDOMオブジェクト].Save "出力先ファイルフルパス"
このFunctionプロシージャの引数として、XMLを生成したXML DOMオブジェクを渡すことで改行とインデントが付与されたXMLを返してくるため、それをXML DOMオブジェクトの「LoadXML」メソッドで読み込んだうえで「Save」メソッドで出力します。
これで出力されたXMLファイルは綺麗に改行とインデントが付与されます。
ただ、宣言部の文字コードの指定箇所において、UTF-8を指定していた場合はその記述が省略されて消えます。
これは、本来XMLフォーマットの標準文字コードがUTF-8であり、明示的にUTF-8を指定する必要がないがためにわざわざ消してしまうみたいです。
基本的にはUTF-8であれば文字コードを明示的に指定しなくても問題はありません。
【おまけ】改行コードをLFに置き換えてインデントを削除しBOMを外す
個人的に使用したコードも紹介しておきます。
上記の改行及びインデント付与のサンプルコードを介して作成されたXMLですが、これは改行がLFCFになります。
また、インデントはタブコードが使われています。
また、元々XMLDOMオブジェクトで生成されるXMLでは、空要素は <email></email> この形式ではなく、</email> この形式が使われます。
諸事情で上記仕様では問題があり、それを以下のように更に変換する処理を作ることになりました。
- 上記の改行及びインデント付与処理を介して出力したXMLファイルを更に読み込んで変換を掛ける。
- 改行コードをLFCFからLFに置き換える。
- インデントで使われているタブを消す。
- BOMを除去する。
- 空要素を省略せず開始タグと終了タグで囲む。
かなり強引なコードですが、おまけとして紹介しておきます。
役に立つかはわかりませんが。
'引数で渡されたxmlファイルを読み込み、タブを排除して、改行コードをLFに置き換えて保存します。 Sub ReCreateXMLFile(TargetFilePath As String, OutputFilePath As String) Dim LineText As String Dim AllText As String 'Stramオブジェクトを生成して変換対象のファイルを読み込みます。 With CreateObject("ADODB.Stream") .Charset = "UTF-8" .Open .LoadFromFile TargetFilePath Do Until .EOS LineText = .ReadText(-2) '空要素があれば、省略した書式ではなく、タグで囲むように変更。 If Right(LineText, 2) = "/>" Then LineText = Replace(LineText, "/", "") & vbLf & "</" & Replace(Replace(LineText, "<", ""), "/", "") & vbLf Else LineText = LineText & vbLf End If 'タブコードを空白に置換します。 AllText = AllText & Replace(Replace(LineText, vbTab, ""), vbCrLf, vbLf) Loop .Close End With '再度Streamオブジェクトを生成して読み込んだテキストからBOMを排除して書き込みます。 With CreateObject("ADODB.Stream") .Charset = "UTF-8" .Open .WriteText AllText, 0 'ストリームの位置を0にする .Position = 0 .Type = adTypeBinary .Position = 3 Dim byteData() As Byte byteData = .Read .Close .Open .Write byteData .SaveToFile OutputFilePath & ".xml", 2 .Close End With End Sub
最後に
今回はVBAでXMLを生成する際に最低限知っておいたほうが良い知識や実装サンプルを紹介しました。
初めてXMLを扱う場合は覚えるべきことが多くて大変ですが、今回の記事では一般的なXMLの生成処理でこれだけ知っていれば大丈夫といった基本的なXML DOMオブジェクトの各メソッドやノード、属性などの扱い方を解説してみました。
是非参考にしてみてください。
今回も長々と読んでいただきましてありがとうございました。
それでは皆さまご機嫌よう!