次は同じVisual Basicでも、LINQ(統合言語クエリ, Language INtegrated Query)for XMLを使ってみます.出力も「直に」タグを書き込んでいては芸がないので、XmlWriterクラスを使うようにします.
Imports System.IO
Imports System.Xml
Imports System.Text.Encoding
Module Main
Sub Main()
Dim xd As XDocument = CreateXDocument("MusicLibrary.xml")
Dim xw As XmlWriter = CreateXmlWriter("MusicLibrary_Copy.xml")
Dim root As XElement = XmlCopy(xd)
root.Save(xw)
xw.Close()
End Sub
Function CreateXDocument(inputPath As String) As XDocument
Dim rs As XmlReaderSettings = New XmlReaderSettings
rs.IgnoreProcessingInstructions = True
rs.IgnoreComments = True
rs.ConformanceLevel = ConformanceLevel.Document
Dim xr As XmlReader = XmlReader.Create(inputPath, rs)
Dim xd As XDocument = XDocument.Load(xr)
Return xd
End Function
Function CreateXmlWriter(outputPath As String) As XmlWriter
Dim settings As XmlWriterSettings = New XmlWriterSettings()
settings.CloseOutput = True
settings.ConformanceLevel = ConformanceLevel.Document
settings.Encoding = UTF8
settings.Indent = False
settings.NewLineChars = vbCrLf
settings.NewLineHandling = NewLineHandling.None
settings.OmitXmlDeclaration = False
settings.WriteEndDocumentOnClose = False
Dim xw As XmlWriter = XmlWriter.Create(outputPath, settings)
Return xw
End Function
Function XmlCopy(xd As XDocument) As XElement
Dim root As XElement = xd.Root
Return ProcessNode(root)
End Function
Function ProcessNode(node As XNode) As XNode
Select Case node.NodeType
Case XmlNodeType.Element
Dim elem As XElement = DirectCast(node, XElement)
Dim newElem As XElement = New XElement(elem.Name)
newElem.Add(elem.Attributes)
Dim childNode As IEnumerable(Of XNode) = elem.Nodes
For Each node In childNode
Dim newNode As XNode = ProcessNode(node)
newElem.Add(newNode)
Next
Return newElem
Case XmlNodeType.Text
Dim text As XText = DirectCast(node, XText)
Dim newText As XText = New XText(text.Value)
Return newText
Case XmlNodeType.Whitespace
Dim text As XText = DirectCast(node, XText)
Dim newText As XText = New XText(text.Value)
Return newText
Case Else
Return Nothing
End Select
End Function
End Module
このプログラムのポイントはループがないことです.XDocumentに読み込んでルート要素をProcessNode関数に渡して処理させて結果をXElementとして受け取ります.たったこれだけです.その結果はXElementのSaveメソッドで出力ファイルに書き込まれます.
ProcessNode関数がすべてを処理するのですが、パラメータとして渡されたXNodeがXElementのとき、下位のXNodeを自身(ProcessNode関数)を呼び出して再帰的に処理します.それが
Dim childNode As IEnumerable(Of XNode) = elem.Nodes
For Each node In childNode
Dim newNode As XNode = ProcessNode(node)
newElem.Add(newNode)
Next
というコードです.ここが入力XMLファイルのコンテンツ(といっても要素とテキストですが)をすべて処理する中核の箇所となります.
使ってみると感じるのですが、XElementはとても柔軟性があります.New XElement(elem.Name)でインスタンスを生成して、Addメソッドで属性も追加できるし、下位のXNodeも追加できます.
次はこのコードをF#に書き換えてみます.