連載

.NETで簡単XML

第6回 .NETプログラムでXSLTスクリプトを使う

株式会社ピーデー 川俣 晶
2003/07/08

XSLT処理を高速化するXPathDocument

 .NET FrameworkでXML文書を持つオブジェクトを作成するには、通常DOMのXmlDocumentクラスが使われる。しかし、XSLTによる変換処理だけに限定すれば、もっと高速に処理可能なものが存在する。それがSystem.Xml.XPath名前空間のXPathDocumentクラスである。以下にXmlDocumentクラスとXPathDocumentクラスの簡単な速度比較を行うサンプル・プログラムを示す。

Private Const tryCount As Integer = 100000

Private Const xsltStyleSheet As String = "<?xml version='1.0' encoding='UTF-8' ?>" + vbCrLf _
  & "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" & vbCrLf _
  & "  <xsl:template match='person'>" & vbCrLf _
  & "  <xsl:value-of select='name' />" & vbCrLf _
  & "  </xsl:template>" & vbCrLf _
  & "  <xsl:template match='/'>" & vbCrLf _
  & "  <result><xsl:apply-templates select='/database/person[birth = 1775]' /></result>" & vbCrLf _
  & "  </xsl:template>" & vbCrLf _
  & "</xsl:stylesheet>" & vbCrLf

Private Const xmlDocument As String = "<?xml version='1.0' encoding='UTF-8' ?>" & vbCrLf _
  & "<database>" & vbCrLf _
  & "  <person>" & vbCrLf _
  & "  <name>伊能忠敬</name>" & vbCrLf _
  & "  <birth>1745</birth>" & vbCrLf _
  & "  </person>" & vbCrLf _
  & "  <person>" & vbCrLf _
  & "  <name>間宮林蔵</name>" & vbCrLf _
  & "  <birth>1775</birth>" & vbCrLf _
  & "  </person>" & vbCrLf _
  & "</database>" & vbCrLf

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Dim normalStart As DateTime = DateTime.Now
  Dim xslt As XslTransform = New XslTransform
  xslt.Load(New XmlTextReader(New StringReader(xsltStyleSheet)), Nothing, Nothing)
  Dim document As XmlDocument = New XmlDocument
  document.LoadXml(xmlDocument)
  For i As Integer = 0 To tryCount - 1
    Dim swriter As StringWriter = New StringWriter
    Dim writer As XmlTextWriter = New XmlTextWriter(swriter)
    xslt.Transform(document, Nothing, writer, Nothing)
  Next
  Dim normalEnd As DateTime = DateTime.Now
  System.Diagnostics.Trace.WriteLine(normalEnd.Subtract(normalStart))

  Dim xPathDocumentStart As DateTime = DateTime.Now
  Dim document2 As XPathDocument = New XPathDocument(New StringReader(xmlDocument))
  For i As Integer = 0 To tryCount - 1
    Dim swriter As StringWriter = New StringWriter
    Dim writer As XmlTextWriter = New XmlTextWriter(swriter)
    xslt.Transform(document2, Nothing, writer, Nothing)
  Next
  Dim xPathDocumentEnd As DateTime = DateTime.Now
  System.Diagnostics.Trace.WriteLine(xPathDocumentEnd.Subtract(xPathDocumentStart))
End Sub
XmlDocumentクラスを使用した場合とXPathDocumentクラスを使用した場合の簡単な速度比較を行うサンプル・プログラム(VB.NET版/C#版のアーカイブ・ファイル

 これを実行すると以下のような結果が得られる(これは筆者のPCで実行したもので、環境によって結果は変化する)。

00:00:20.8105466
00:00:15.4026086
10万回処理を繰り返して実行時間を比較する
1行目がXmlDocumentクラスを使用した場合の実行時間、2行目はXPathDocumentクラスを使用した場合の実行時間。

 このサンプルで説明が必要なのは、XPathDocumentクラスのインスタンスを作成している部分だけだろう。とはいえ、それほど複雑というわけではない。ここでは、XPathDocumentクラスのコンストラクタに、StringReaderクラスのインスタンスを渡している。それには、もちろん、処理対象とすべきXML文書が文字列として含まれている。

 さて、実行結果からその差は一目瞭然だろう。2つのケースでの処理時間を出力させているが、両者には歴然とした差があり、XPathDocumentクラスを使った方が早く作業が終わっている。クラス・ライブラリのリファレンス・マニュアルでXmlDocumentクラスとXPathDocumentクラスを比較すると、驚くほどXPathDocumentクラスは機能が少ないことが分かるだろう。しかし、速度が重要なファクターであるとき、XPathDocumentクラスの高速性は機能の少なさを補って余りあるメリットをもたらしてくれる。

XSLTスクリプトへのプログラムの埋め込み

 XSLTはW3Cの標準であって、マイクロソフト独自の技術というわけではない。しかし、マイクロソフトはXSLTに独自の拡張機能を付加している。独自といっても、標準を無視して勝手に非互換を作っているわけではない。XML特有の名前空間の機能を用いて、正統的に独自の拡張機能を提供しているのである。それにより、XSLTスクリプトの内部にプログラム言語による処理を埋め込むということが可能となっている。

 この拡張機能を用いると、VB.NETやC#のプログラムのソース・コードの中にXSLTスクリプトを埋め込み、その中にさらにVB.NETやC#のプログラムのソース・コードを埋め込むことができる。もしかしたら、奇をてらったサーカスもどきのソース・コードにしかならないように思えるかもしれないが、そうではない。XSLTはそもそもスタイルシート変換のための言語であって、機能面ではそれほど強力ではない。ちょっと込み入った機能が必要になると、複雑で回りくどいコーディングが必要とされたり、そもそも記述不可能であったりする。そのような場合に、ほかのプログラム言語で機能を補ってやるとスムーズにスクリプトが記述できる場合がある。もちろん、マイクロソフト独自拡張機能を使うと非互換性の問題が発生するのだが、ここではVB.NETやC#のプログラムからXSLTを利用する場合について解説を行っている。そのようなプログラムが実行可能な環境なら、当然、マイクロソフト独自拡張機能が利用可能であることも期待できる。ということは、これは、かなり現実的に使えるものだといえないだろうか?

 以下は、その機能を活用したサンプル・プログラムである。文字列をBase64形式でエンコードする機能をVB.NETやC#のメソッドとして記述して、XSLTに埋め込んだ例である。もちろん、そのXSLTスクリプトが、VB.NETやC#のソース・コード中に埋め込まれている。

Private Const xsltStyleSheet As String = "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'" & vbCrLf _
  & "  xmlns:msxsl='urn:schemas-microsoft-com:xslt'" & vbCrLf _
  & "  xmlns:user='http://www.atmarkit.co.jp/sample/xslt/script'>" & vbCrLf _
  & "  <msxsl:script language='VisualBasic' implements-prefix='user'>" & vbCrLf _
  & "   <![CDATA[" & vbCrLf _
  & "  Public Function ToBase64(ByVal s As String) As String" & vbCrLf _
  & "    If s.Length = 0 Then Return " & Chr(34) & Chr(34) & vbCrLf _
  & "    Dim ar() As Byte = System.Text.Encoding.UTF8.GetBytes(s)" & vbCrLf _
  & "    Return Convert.ToBase64String(ar)" & vbCrLf _
  & "  End Function" & vbCrLf _
  & "    ]]>" & vbCrLf _
  & "  </msxsl:script>" & vbCrLf _
  & "  <xsl:template match='name'>" & vbCrLf _
  & "  <name encode='base64'>" & vbCrLf _
  & "      <xsl:value-of select='user:ToBase64(.)'/>" & vbCrLf _
  & "  </name>" & vbCrLf _
  & "  </xsl:template>" & vbCrLf _
  & "</xsl:stylesheet>" & vbCrLf

Private Const xmlDocument As String = "<name>間宮林蔵</name>"

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  Dim xslt As XslTransform = New XslTransform
  xslt.Load(New XmlTextReader(New StringReader(xsltStyleSheet)), Nothing, Me.GetType().Assembly.Evidence)
  Dim document As XmlDocument = New XmlDocument
  document.LoadXml(xmlDocument)
  Dim swriter As StringWriter = New StringWriter
  Dim writer As XmlTextWriter = New XmlTextWriter(swriter)
  xslt.Transform(document, Nothing, writer, Nothing)
  System.Diagnostics.Trace.Write(swriter.ToString())
End Sub
XSLTスクリプトにプログラムを埋め込んだサンプル・プログラム(VB.NET版C#版

 これを実行すると以下のようになる。

<name encode="base64" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://www.atmarkit.co.jp/sample/xslt/script">
6ZaT5a6u5p6X6JS1</name>
実行結果

 このソースで注意すべき点は2つある。1つは、XSLTスクリプト中のmsxsl:script要素である。この内部にプログラムを記述することができる。プログラム言語の種類はlanguage属性で指定する。VisualBasicやC#という値を指定することができる。そして、implements-prefix属性でそのプログラムが属するXMLの名前空間の接頭辞を指定する。呼び出す際はXPathの式として「user:ToBase64(.)」というように記述すればよい。ここで“.”(ドット)はこのノードを意味する表記である。このノードのテキスト値が文字列としてToBase64メソッドに渡り、そこで処理される。プログラムの前後にある“<![CDATA[”で始まり、“]]>”で終わる部分は、CDATAセクションと呼ばれるもので、その内部の文字はそのまま認識されるものである。通常は、“<”記号は“&lt;”というように記述する必要があるが、その手間を省くものである。プログラム言語のソース・コードをXML文書に埋め込む際に便利だ。

 もう1つ注意すべき点は、Loadメソッドの第3引数である。これは証拠(evidence)というものを指定する引数である。この広いネットワークには危険がたくさんある。それを適切に扱うために、システムにはさまざまな制約が科せられている。それを正しく機能させるために証拠という機能が必要とされているのである。この場合でいうと、プログラム言語の埋め込み機能は適切な証拠が設定されていない場合には動作しないようになっている。悪意あるプログラムを安易に実行させないためである。この例では証拠を渡さずにNothing(C#ではnull)を渡すと、サンプル・プログラムは動作しない。もしこの機能を動作させたいとするなら、適切な証拠を指定する必要がある。ここでは、Me.GetType().Assembly.Evidence(C#ではthis.GetType().Assembly.Evidence)という式で、このクラス自身を含んでいるアセンブリの証拠をそのまま使っている。アセンブリ中に埋め込まれたスクリプトだから、アセンブリの証拠をそのまま使うわけである。これで、スクリプト内に埋め込まれたプログラムが実行可能となる。もちろん、このアセンブリ自身の証拠が不十分な状態で実行されれば、実行不能という可能性もあり得るわけである。

 外部から読み込んだXSLTスクリプトを使う場合は、System.Xml.XmlSecureResolverクラスのCreateEvidenceForUrlメソッドなどを使って証拠を作成することができる。このメソッドは指定されたURLに対応する証拠を作成する。証拠は対象によって変化するものなので、URLが変われば証拠も変わるものなのである。社内のイントラネットと、世界のどこにあるかも明確ではない未知のサイトで、異なる証拠を適用したいことも珍しくないだろう。そのため、URLに対応した証拠を作成する機能が必要とされるわけである。逆にいえば、直接ファイル名(正確にはURL)を指定して処理させるメソッドに証拠を指定する必要がないのは、指定されたURLから適切な証拠が生成できるためである。あえて、それに加えて証拠を指定する必要はない。

 さて、結果となるXML文書には、残念ながら不必要な名前空間の宣言が付いてしまっている。通常、これがあっても大きな弊害はないと思われ、またこれを取り除く処理を記述することも難しくはないので、今回の解説はここまでとする。

次回予告

 XSLTはなかなか奥が深く複雑な言語であり、今回の解説はその表面をわずかに撫でたに過ぎない。XSLTはXML技術者が知っているべき必須技術という認識を持っている人たちも多いので、じっくり取り組んで学ぶのもよいだろう。しかし、筆者としては、XSLTがXML技術者として絶対に必須の言語とは思わない。新しくXSLTを学ぶぐらいなら、すでに知っているVB.NETやC#で書いた方が早いという場合もあるだろう。それはそれで正しいXMLの使い方のうちである。XMLはプログラム言語を選ばない中立の言語であって、特定のプログラム言語やスクリプト言語を強制することは適切ではないと思う。とはいえ、はっきりとXSLTを使った方が有利な場面は存在する。例えば、XML文書をInternet Explorerで閲覧可能にするだけなら、XSLTスクリプトを1つ作成して添えるのが最も簡単な手段である。そのような場合にXSLTを活用することをためらう必要はない。

 次回は、XMLのスキーマ言語であるXML Schemaをプログラミングに活用する手段を解説する予定である。End of Article


 INDEX
  .NETで簡単XML
  第6回 .NETプログラムでXSLTスクリプトを使う
    1.XSLTとは何か
    2.XSLTミニ入門その1 要素に対応する出力
    3.XSLTミニ入門その2、3 属性に対応する出力/XPath式の値を出力
    4.プログラムでXSLTを処理/プログラム内のデータを変換
  5.XPathDocumentで処理を高速化/XSLTにプログラムを埋め込む
 
インデックス・ページヘ  「連載 :.NETで簡単XML」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間