XMLファイルをコピーする(4)


次は同じVisual Basicでも、LINQ(統合言語クエリ, Language INtegrated Query)for XMLを使ってみます.出力も「直に」タグを書き込んでいては芸がないので、XmlWriterクラスを使うようにします.

Imports System.IO
Imports System.Xml
Imports System.Xml.Linq
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

LINQのXDocumentクラスのLoadメソッドで入力XMLファイルを読み込みます.Loadメソッドは実際の読み込みは以前使ったSystem.Xml.XmlReaderクラスを使用します.

このプログラムのポイントはループがないことです.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も追加できます.

このようなXMLファイルのコピーというレベルのプログラムではLINQの魅力は引き出せないでしょうけれども、なかなか面白いのがわかります.

次はこのコードをF#に書き換えてみます.

LINQ for XMLは初心者です.まだ単に動かせたというレベルで、冗長な箇所はあると思います.また相変わらずエラー処理は一切考慮されていません.