VBチームのブログ(2)

VBチームのブログの言わんとするところは次のようなことではないかと考えられます.

1.XMLファイルをコピーする抽象クラスをつくる.XMLの構成要素を個別にコピーする処理はすべて実装する.
2.このクラスを継承した目的の処理を実装するクラスを作る.特別な処理をしたい要素はオーバーライド関数を追加する.
3.こうすればVBでもXSLTのような処理が作れちゃいます!

VBチームの作ってくれた1のコードは以下のようなものです.


Public MustInherit Class VBXmlTransform

    Public Overridable Function Transform(ByVal xmlDoc As XDocument) As XDocument
        Return <?xml version="1.0" encoding="utf-8"?>
               <%= ProcessElement(xmlDoc.Root) %>
    End Function

    Public Overridable Function ProcessNode(ByVal xmlNode As XNode) As XNode
        ' This method ignores DTD (XDocumentType) content.
        Dim nodeType = xmlNode.GetType()
        ' Because XCData inherits from XText, check for the XCData type before checking
        ' for XText.
        If nodeType Is GetType(XCData) Then Return ProcessCData(xmlNode)
        If nodeType Is GetType(XText) Then Return ProcessText(xmlNode)
        If nodeType Is GetType(XElement) Then Return ProcessElement(xmlNode)
        If nodeType Is GetType(XComment) Then Return ProcessComment(xmlNode)
        If nodeType Is GetType(XProcessingInstruction) Then Return _
          ProcessProcessingInstruction(xmlNode)
        Return xmlNode
    End Function

    Public Overridable Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
        Return xmlAttribute
    End Function

    Public Overridable Function ProcessCData(ByVal xmlCData As XCData) As XCData
        Return xmlCData
    End Function

    Public Overridable Function ProcessText(ByVal xmlText As XText) As XText
        Return xmlText
    End Function

    Protected Overridable Function ProcessComment(ByVal xmlComment As XComment) As XComment
        Return xmlComment
    End Function

    Public Overridable Function ProcessProcessingInstruction( _
      ByVal pi As XProcessingInstruction) As XProcessingInstruction
        Return pi
    End Function

    Public Overridable Function ProcessElement(ByVal xmlElement As XElement) As XElement
        Return CopyElement(xmlElement)
    End Function

    Public Overridable Function CopyElement(ByVal xmlElement As XElement) As XElement
        Return <<%= xmlElement.Name %>
                   <%= From attribute In xmlElement.Attributes() _
                     Select ProcessAttribute(attribute) %>>
                   <%= From node In xmlElement.Nodes() _
                     Select ProcessNode(node) %>
               </>
    End Function

End Class

そして2のサンプルは以下のようなものです.



Public Class AWTransform
    Inherits VBXmlTransform

    ' Rename <act:PostalCode> to <ZipCode>.
    '
    ' Create an XName object to use for comparisons. This will perform better than comparing
    ' xmlElement.Name.LocalName to a string.

    Private postalCodeXName As XName = _
      XName.Get("PostalCode", _

    Public Overrides Function ProcessElement(ByVal xmlElement As XElement) As XElement
        Select Case xmlElement.Name
            Case postalCodeXName
                Return TransformPostalCode(xmlElement)
            Case Else
                Return MyBase.ProcessElement(xmlElement)
        End Select

        Return Nothing
    End Function

    Public Function TransformPostalCode(ByVal postalCodeElement As XElement) As XElement
        Return <ZipCode><%= postalCodeElement.Value %></ZipCode>
    End Function
End Class

コメントにあるように、<act:PostalCode> を <ZipCode>に置換します.ここで一番大切なところは、赤字で示した条件分岐です.もし本格的にXSLTコンパチブルなコードを書こうと思ったら、この箇所にすべての処理したい要素の条件を記述し、すべての変換関数を実装しなければなりません.

これはXSLTを一般のプログラミング言語で実装しようとしたら、どの言語でも行わなければならないものです.XSLTならば、上記の処理の、

  Case postalCodeXName
    Return TransformPostalCode(xmlElement)


<xsl:template match="act:PostalCode">
  <ZipCode>
    <xsl:value-of select="string(.)"/>
  </ZipCode>
</xsl:template>

と書くのと等価です.

また

  Case Else
    Return MyBase.ProcessElement(xmlElement)


<xsl:template match="*">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

と等価です.

つまりXSLTスタイルシートだったら、テンプレートへの処理振り分けは自動的にXSLTプロセッサがやってくれますが、一般のプログラミング言語ではすべて自力でやらなばなりません.

また、XSLTテンプレートなら

<xsl:template match="パターン">

と書けます.特定の要素にマッチさせたいなら、例えば

match="acme:someElem[制約条件]"

と簡単に記述できます.でもVBでは条件分岐を全部記述して目的の関数を呼び出さねばなりません.

このような処理を一般のプログラミング言語でもなるべく簡単にかける仕組みはないものでしょうか?

VBではそういうものは残念ながら見当たりません.しかしF#やScalaにはmatch式と呼ばれるものがあり、XSLTスタイルシートのパターンに対応するものが書けそうな感じです.

次ではこのmatch式がF#やScalaXMLに対してどこまで使えそうなのかを調べたいと思います.