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次元しか無いので、階層が深くなると見やすく表示するのがお手上げなんですよねえ。
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
ヒロユキ様
大変ありがとうございます。
答えを頂いているのに四苦八苦いたしましたが、無事に思い通りの動きをすることができました。