シートからXML作成、タグ追加関数、属性追加関数、インデント整形、エンコーティング
以下のシートから、XMLファイルを作成するマクロの紹介です。
マクロを実行すると、以下のXMLファイルが作成されます。
<?xml version="1.0" encoding="UTF-8"?>
<all>
<row>
<A attrid="attrV">
<AA>データ1</AA>
<C id="text">データ2</C>
</A>
<D>
<E>データ3</E>
<F>データ4</F>
</D>
<G>
<H>
<I klm="post">
<J year="2024">データ5</J>
<K>データ6</K>
</I>
<L>データ7</L>
</H>
<M>データ8<N>データ9</N>
</M>
</G>
<O>データ10</O>
</row>
<row>
<A attrid="attrV">
<AA>データ11</AA>
<C id="text">データ12</C>
</A>
<D>
<E>データ13</E>
<F>データ14</F>
</D>
<G>
<H>
<I klm="post">
<J year="2024">データ15</J>
<K>データ16</K>
</I>
<L>データ17</L>
</H>
<M>データ18<N>データ19</N>
</M>
</G>
<O>データ20</O>
</row>
<row>
<A attrid="attrV">
<AA>21</AA>
<C id="text"/>
</A>
<D>
<E>23</E>
<F>24</F>
</D>
<G>
<H>
<I klm="post">
<J year="2024"/>
<K>26</K>
</I>
<L/>
</H>
<M>28<N/>
</M>
</G>
<O/>
</row>
</all>
全体は<all>タグで囲まれていて、16行の値を元に、1~12行に設定しているタグと属性を元に、<row>タグに囲まれたxmlを作成しています。16行と同じように、18行目まで、xmlを作成しています。
以下が、マクロです。このマクロはMSXML2.DOMDocument60オブジェクトを使用するため、「Microsoft XML v6.0」の参照設定が必要です。
Option Explicit
Sub MakeXML()
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
' XMLの書き込み準備を行う
Dim xw As XmlWriter ' クラス
Set xw = New XmlWriter
xw.Prepare ' 準備
' 複数行繰り返し
Dim lastRw As Long ' A列、最後の行
lastRw = ws.Cells(Rows.Count, 1).End(xlUp).Row
Dim rw As Long ' 行
For rw = 16 To lastRw
' 各行のNode作成
Call xw.AppendElement(ws, rw)
Next rw
' ルートNodeに追加
xw.AppendRoot
' xmlのインデント整形
xw.CodeBeautify
' UTF-16からUTF-8に変換
xw.ChangeEncoding
' XMLファイルに書き込む
Dim outputFileName As String ' 出力ファイル
outputFileName = ThisWorkbook.path & _
"\出力ファイル_" & Format(Now, "yyyymmddhhmmss") & ".xml"
xw.Save (outputFileName)
Set xw = Nothing
End Sub
以下は、クラスモジュールに入れたクラス「XmlWriter」のマクロです。
' DOM
Private xmlDocument As MSXML2.DOMDocument60
'出力用DOM
Private xmlDoc2 As New MSXML2.DOMDocument60
' ルート要素のノード
Private rootNode As IXMLDOMNode
' ルート以降のノード
Private Node(0 To 4) As IXMLDOMNode
' コンストラクタ
Public Sub class_initialize()
End Sub
' ファイルの書き込み準備を行う
Public Sub Prepare()
' MSXMLオブジェクトを生成
Set xmlDocument = Nothing
Set xmlDocument = New MSXML2.DOMDocument60
' XML宣言を生成
Dim processingInstruction As IXMLDOMProcessingInstruction
Set processingInstruction = xmlDocument.createProcessingInstruction("xml", "version=""1.0"" encoding=""utf-8""")
Call xmlDocument.appendChild(processingInstruction)
' 共通要素/属性を生成
Set rootNode = xmlDocument.appendChild(xmlDocument.createElement("all"))
End Sub
' 各Node作成
Public Sub AppendElement(ByVal ws As Worksheet, ByVal rw As Long)
Dim str As String ' 値
Dim tag As String ' タグ
Dim nextTag As String ' 次のタグ
Dim attrType As String ' 属性名
Dim attrValue As String ' 属性値
Dim i As Long ' タグの繰り返し
' 1行全体のノード
Set Node(0) = AppendNodeElement(rootNode, "row")
' 列繰り返し
Dim clm As Long
Dim lastClm As Long
lastClm = ws.Cells(15, Columns.Count).End(xlToLeft).Column
For clm = 2 To lastClm
str = ws.Cells(rw, clm) ' 値
For i = 1 To 4
tag = ws.Cells(i * 3 - 2, clm) ' タグ
nextTag = ws.Cells(i * 3 + 1, clm) ' 次のタグ
attrType = ws.Cells(i * 3 - 1, clm) ' 属性名
attrValue = ws.Cells(i * 3, clm) ' 属性値
If tag <> "" Then
' ノードの設定。次のタグが空白の場合、内容を入れる
If nextTag = "" Then
Set Node(i) = AppendNodeElement(Node(i - 1), tag, str)
Else
Set Node(i) = AppendNodeElement(Node(i - 1), tag)
End If
' 属性の設定
If attrType <> "" Then
Call AppendAttr(Node(i), attrType, attrValue)
End If
End If
Next i
Next clm
End Sub
'***********************************
'* 子ノードの作成
'* 戻り値:子ノード
'* Node:ノード、このノードの子ノードを追加
'* elementName:子ノードのタグ名
'* nodeText:子ノードの内容
Private Function AppendNodeElement(ByVal Node As IXMLDOMNode, ByVal elementName As String, Optional nodeText As String) As IXMLDOMNode
Dim appendNode As IXMLDOMNode
Set appendNode = xmlDocument.createElement(elementName)
appendNode.Text = nodeText
Call Node.appendChild(appendNode)
Set AppendNodeElement = appendNode
End Function
'***********************************
'* 属性の追加
'* Node:ノード、このノードに属性を追加
'* attrType:属性名
'* attrValue:属性値
Private Sub AppendAttr(ByVal Node As IXMLDOMNode, ByVal attrType As String, ByVal attrValue As String)
Dim idAttribute As MSXML2.IXMLDOMAttribute
Set idAttribute = xmlDocument.createAttribute(attrType)
idAttribute.NodeValue = attrValue
Call Node.Attributes.setNamedItem(idAttribute)
End Sub
' 作成したエレメントをrootに追加
Public Sub AppendRoot()
Call rootNode.appendChild(Node(0))
End Sub
' xmlのインデント整形
' https://teratail.com/questions/118314
Public Sub CodeBeautify()
'Reader/Writerを用意する
Dim oXmlReader As New SAXXMLReader60
Dim oXmlWriter As New MXXMLWriter60
'Writerでインデント設定する
oXmlWriter.indent = True
'ReaderとWriterを紐づける
Set oXmlReader.contentHandler = oXmlWriter
'作成したXMLをReaderで読み込む
oXmlReader.Parse xmlDocument.XML
'出力用DOMにWriterの整形結果を読ませる
xmlDoc2.LoadXML oXmlWriter.output
End Sub
' XMLファイルのencodingの変更
' https://tsuyorid.hatenadiary.org/entry/20051216/p2
Public Sub ChangeEncoding()
' UTF-16からUTF-8に変換
Dim domDecXml As IXMLDOMProcessingInstruction
'UTF-8に変換
Set domDecXml = xmlDoc2.createProcessingInstruction("xml", "version=""1.0"" encoding=""UTF-8""")
'元々のencodingを無視して変更する
Call xmlDoc2.replaceChild(domDecXml, xmlDoc2.FirstChild)
End Sub
' XMLファイルを保存する
Public Sub Save(filePath As String)
If xmlDoc2.XML = "" Then
xmlDocument.Save (filePath)
Else
'出力用DOMで保存
xmlDoc2.Save (filePath)
End If
End Sub
' デストラクタ
Public Sub Class_Terminate()
If Not xmlDocument Is Nothing Then Set xmlDocument = Nothing
If Not xmlDoc2 Is Nothing Then Set xmlDoc2 = Nothing
If Not rootNode Is Nothing Then Set rootNode = Nothing
Dim i As Long
For i = LBound(Node) To UBound(Node)
If Not Node(i) Is Nothing Then Set Node(i) = Nothing
Next i
End Sub
順番に解説します。
' XMLの書き込み準備を行う
Dim xw As XmlWriter ' クラス
Set xw = New XmlWriter
xw.Prepare ' 準備
XmlWriterというクラスの新しいインスタンスを作成して、XmlWriterクラスに設定されている以下「Prepare」を実行します。
' ファイルの書き込み準備を行う
Public Sub Prepare()
' MSXMLオブジェクトを生成
Set xmlDocument = Nothing
Set xmlDocument = New MSXML2.DOMDocument60
' XML宣言を生成
Dim processingInstruction As IXMLDOMProcessingInstruction
Set processingInstruction = xmlDocument.createProcessingInstruction("xml", "version=""1.0"" encoding=""utf-8""")
Call xmlDocument.appendChild(processingInstruction)
' 共通要素/属性を生成
Set rootNode = xmlDocument.appendChild(xmlDocument.createElement("all"))
End Sub
このサブルーチンでは、XMLの書き込みの準備を行っています。具体的には、MSXMLオブジェクトを生成し、XML宣言を生成し、共通要素/属性を生成しています。ルートノードのタグは、<all>にしています。
' 複数行繰り返し
Dim lastRw As Long ' A列、最後の行
lastRw = ws.Cells(Rows.Count, 1).End(xlUp).Row
Dim rw As Long ' 行
For rw = 16 To lastRw
' 各行のNode作成
Call xw.AppendElement(ws, rw)
Next rw
16行目から最後の行までを繰り返します。各行のNodeを作成するため、XmlWriterクラスに設定されている以下「AppendElement」を実行します。引数として、ワークシート「ws」と行「rw」を渡しています。
' 各Node作成
Public Sub AppendElement(ByVal ws As Worksheet, ByVal rw As Long)
このサブルーチンは、各Nodeを作成するためのもので、中身は、以下になります。
Dim str As String ' 値
Dim tag As String ' タグ
Dim nextTag As String ' 次のタグ
Dim attrType As String ' 属性名
Dim attrValue As String ' 属性値
Dim i As Long ' タグの繰り返し
変数を宣言します。
' 1行全体のノード
Set Node(0) = AppendNodeElement(rootNode, "row")
1行全体のノードを作成し、Node(0)に代入します。タグは、<row>にしています。
' 列繰り返し
Dim clm As Long
Dim lastClm As Long
lastClm = ws.Cells(15, Columns.Count).End(xlToLeft).Column
For clm = 2 To lastClm
列の繰り返し処理を行います。2列目から15行目の最後の列までを対象にします。
str = ws.Cells(rw, clm) ' 値
値「str」に、rw行clm列のセルの内容を代入します。rwは16~18、clmは2(B列)~11(K列)と増加しながら処理します。
For i = 1 To 4
iが1から4までの間、繰り返します。タグが、4重で囲えるように、シートを作成しているので、1~4にしています。
tag = ws.Cells(i * 3 - 2, clm) ' タグ
nextTag = ws.Cells(i * 3 + 1, clm) ' 次のタグ
attrType = ws.Cells(i * 3 - 1, clm) ' 属性名
attrValue = ws.Cells(i * 3, clm) ' 属性値
tag、nextTag、attrType、attrValueにそれぞれセルの内容を代入します。
If tag <> "" Then
' ノードの設定。次のタグが空白の場合、内容を入れる
If nextTag = "" Then
Set Node(i) = AppendNodeElement(Node(i - 1), tag, str)
Else
Set Node(i) = AppendNodeElement(Node(i - 1), tag)
End If
' 属性の設定
If attrType <> "" Then
Call AppendAttr(Node(i), attrType, attrValue)
End If
End If
tagが空でない場合、以下の処理を行います。
ノードの設定を行います。次のタグが空の場合は、値を持つノードを作成し、次のタグが空でない場合、値なしのノードを作成します。最後のタグの次のタグは空白にしたいので、シートの13行目は空白行にしています。「AppendNodeElement」は、次に解説します。
属性の設定を行います。属性が空でない場合、属性を追加します。「AppendAttr」は、次の次に解説します。
'***********************************
'* 子ノードの作成
'* 戻り値:子ノード
'* Node:ノード、このノードの子ノードを追加
'* elementName:子ノードのタグ名
'* nodeText:子ノードの内容
Private Function AppendNodeElement(ByVal Node As IXMLDOMNode, ByVal elementName As String, Optional nodeText As String) As IXMLDOMNode
Dim appendNode As IXMLDOMNode
Set appendNode = xmlDocument.createElement(elementName)
appendNode.Text = nodeText
Call Node.appendChild(appendNode)
Set AppendNodeElement = appendNode
End Function
新しい子ノードを作成し、指定された親ノードに追加するための関数です。戻り値は、作成した子ノードになっています。
'***********************************
'* 属性の追加
'* Node:ノード、このノードに属性を追加
'* attrType:属性名
'* attrValue:属性値
Private Sub AppendAttr(ByVal Node As IXMLDOMNode, ByVal attrType As String, ByVal attrValue As String)
Dim idAttribute As MSXML2.IXMLDOMAttribute
Set idAttribute = xmlDocument.createAttribute(attrType)
idAttribute.NodeValue = attrValue
Call Node.Attributes.setNamedItem(idAttribute)
End Sub
指定されたノードに属性を追加するためのサブルーチンです。
' ルートNodeに追加
xw.AppendRoot
標準モジュールプロシージャ「MakeXML」に戻ります。ルートNodeに子ノードを追加します。XmlWriterクラスに設定されている以下「AppendRoot」を実行します。
' 作成したエレメントをrootに追加
Public Sub AppendRoot()
Call rootNode.appendChild(Node(0))
End Sub
' xmlのインデント整形
xw.CodeBeautify
XMLのコードを整形し、インデントを整えます。XmlWriterクラスに設定されている以下「CodeBeautify」を実行します。
' xmlのインデント整形
' https://teratail.com/questions/118314
Public Sub CodeBeautify()
'Reader/Writerを用意する
Dim oXmlReader As New SAXXMLReader60
Dim oXmlWriter As New MXXMLWriter60
'Writerでインデント設定する
oXmlWriter.indent = True
'ReaderとWriterを紐づける
Set oXmlReader.contentHandler = oXmlWriter
'作成したXMLをReaderで読み込む
oXmlReader.Parse xmlDocument.XML
'出力用DOMにWriterの整形結果を読ませる
xmlDoc2.LoadXML oXmlWriter.output
End Sub
「https://teratail.com/questions/118314」を参考にしています。
' UTF-16からUTF-8に変換
xw.ChangeEncoding
UTF-16形式からUTF-8形式にエンコーディングを変換します。XmlWriterクラスに設定されている以下「ChangeEncoding」を実行します。
' XMLファイルのencodingの変更
' https://tsuyorid.hatenadiary.org/entry/20051216/p2
Public Sub ChangeEncoding()
' UTF-16からUTF-8に変換
Dim domDecXml As IXMLDOMProcessingInstruction
'UTF-8に変換
Set domDecXml = xmlDoc2.createProcessingInstruction("xml", "version=""1.0"" encoding=""UTF-8""")
'元々のencodingを無視して変更する
Call xmlDoc2.replaceChild(domDecXml, xmlDoc2.FirstChild)
End Sub
「https://tsuyorid.hatenadiary.org/entry/20051216/p2」を参考にしています。
' XMLファイルに書き込む
Dim outputFileName As String ' 出力ファイル
outputFileName = ThisWorkbook.path & _
"\出力ファイル_" & Format(Now, "yyyymmddhhmmss") & ".xml"
xw.Save (outputFileName)
出力ファイルの名前を作成し、XMLファイルに書き込みます。XmlWriterクラスに設定されている以下「Save」を実行します。
' XMLファイルを保存する
Public Sub Save(filePath As String)
If xmlDoc2.XML = "" Then
xmlDocument.Save (filePath)
Else
'出力用DOMで保存
xmlDoc2.Save (filePath)
End If
End Sub
以下が、このExcelファイルです。
シートからXML作成
この投稿に関する追加解説です。