XMLフロンティア探訪
第6回 将来のXMLインターフェイスを占う

XML文書をプログラムから操作するといえば、インターフェイスとして利用するのはDOMやSAX、もしくはXSLTといったところだろう。しかし、これらは低レベルな機能しか持たないインターフェイスであり、将来はこれらに取って代わるインターフェイスが登場することは十分あり得る話だ。(編集局)

川俣 晶
株式会社ピーデー
2001/10/26

今回の主な内容
DOMとSAXが本当に正解なのか
オーソドックスなDOMのサンプル
.NET FrameworkのXPath式による選択
XSLTスクリプトによる選択
カーソルモデルによるアクセス
シーケンシャルなXML文書の読み込み
次世代の解答・データ構造変換
低レベルの実装としてDOM/SAXは今後も使われ続けるだろう
DOMとSAXが本当に正解なのか

 XML文書は基本的にただのテキストファイルであり、プログラムから利用しようとすると、単純な文字列処理が多数発生して煩雑である。そこで、XMLパーサという共通ライブラリを用意して、それに文字列処理を任せるのが普通である。

 しかし、XMLパーサを使うようになると、XMLパーサを呼び出すインターフェイスの仕様はどうあるべきかという問題が発生する。つまり、XMLパーサごとにまちまちのインターフェイスを採用していたのでは使い方を学習する必要も生じて効率も悪いし、何かあったときにXMLパーサを取り替えるのも面倒なことになる。そこで、XMLパーサへのインターフェイスの標準化が求められ、その結果、W3Cより示されたのがDOM(Document Object Model)ということになる。しかし、DOMには不必要に機能が多く、重い仕様であるという批判もあり、有志がDOMとは別にSAX(Simple API for XML)というインターフェイスを標準化した。DOMとSAXは相互に相反する特徴を持ち、お互いの不得意分野をよくフォローし合い、XMLパーサへのインターフェイスの双璧として君臨してきた。

 DOMとSAXが安定した標準の座を得たいま、次に問題になるのは、DOMとSAXが本当に正解なのか、ほかに別の選択肢はなかったのかということだ。そして最近になって、ほかにもいろいろなやり方があるということが分かってきた。今回は、そのいろいろなやり方の一部を紹介するが、ここではどれが正しいという結論付けは行わない。どれが良いと思うかは、読者の感想次第としよう。

同じルールでXML文書へのアクセスを比べてみる

 今回は、1つのルールを定めて、それを異なる手段で実現するソースコードを並べてみたい。まず、プログラム言語の違いによる差異を排除するため、すべてC#で記述する。C#は、C/C++/Javaに近い構文なので、それらの経験があれば何となく読めると思う。方法の違いを感じていただくのが今回の主なテーマなので、コードの意味を厳密に理解できなくても構わない。

 実際の処理内容としては、以下のXML文書を読み込んで、要素bの内容をコンソールに出力するものとする。なお、残念ながら現在の.NET FrameworkにはSAXのサポートがないので、SAXのサンプルは割愛した。SAXによる処理内容が気になる人は、この「XML eXpert eXchangeフォーラム」のほかの記事などを参照していただきたい。

1: <?xml version="1.0" ?>
2: <sample>
3:   <a>Kokoro</a>
4:   <b>Alto</b>
5:   <c>Iina</c>
6: </sample>
今回のサンプルとなるXML文書(先頭の番号は行番号)

 なお、今回のプログラムは基本的にすべて以下のような出力を結果する。

プログラムの実行結果はこのようになる

オーソドックスなDOMのサンプル

 以下はDOMを用いて組んだものである。ごくオーソドックスに、再帰呼び出しを用いて、文書ツリー全体を検索し、要素型のノードで、かつ、ノード名がbであるノードを探す。

1: using System;
2: using System.Xml;
3: 
4: namespace SampleProgram
5: {
6:     class SampleClass
7:     {
8:         static void walk( XmlNode node ) 
9:         {
10:             if( node.NodeType == XmlNodeType.Element ) 
11:             {
12:                 if( node.Name == "b" ) 
13:                 {
14:                     Console.WriteLine( node.InnerText );
15:                 } 
16:             }
17:             foreach( XmlNode child in node.ChildNodes )
18:             {
19:                 walk( child );
20:             }
21:         }
22:         static void Main(string[] args)
23:         {
24:             XmlDocument doc = new XmlDocument();
25:             doc.Load(args[0]);
26:             walk(doc);
27:         }
28:     }
29: }
DOMを使ったサンプルプログラム

 なお、14行目のInnerTextは、標準のDOMにはないもので、あるノード以下にある文字列を返すプロパティである。ここは今回のテーマから見て本質的な部分ではないので、標準DOMにはない機能を使ってその分だけソースを簡潔にしておいた。

もう1つの方法を用いたDOMのサンプル

 今回設定した条件なら、DOMを用いてももっと簡潔に記述できる。DOMにはGetElementsByTagNameという便利なメソッドがあり、ある特定の名前の要素を探すだけなら、これを使うことができる。これを使った例を以下に示す。

1: using System;
2: using System.Xml;
3: 
4: namespace SampleProgram
5: {
6:     class SampleClass
7:     {
8:         static void Main(string[] args)
9:         {
10:             XmlDocument doc = new XmlDocument();
11:             doc.Load(args[0]);
12:             XmlNodeList list = doc.GetElementsByTagName("b");
13:             foreach( XmlNode node in list )
14:             {
15:                 Console.WriteLine( node.InnerText );
16:             }
17:         }
18:     }
19: }
DOMのGetElementsByTagNameを使ったサンプルプログラム

 見て分かるとおり、再帰呼び出しは必要なく、単純に指定した名前の要素のノードリストを受け取るだけの処理になる。この方法は簡潔だが特定の名前の要素を探す場合にしか使えず、別の条件で探す場合には応用が利かない。DOMのインターフェイスにはこのほかにも特定のIDを持つ要素を探すメソッドは存在する。しかし、それにも込み入った条件を指定して探す機能はない。

.NET FrameworkのXPath式による選択

 GetElementsByTagNameメソッドでは、込み入った条件を指定できない。そこでXPath式で指定する方法があればうれしいな、と思うのは私だけではなかったようで、.NET FrameworkのDOMノードには、XPath式を指定してノードのリストを返すメソッド、SelectNodesが存在する。これを使えば、込み入った条件もXPath式で記述さえできれば容易に扱える。以下は、XPath式でノードを探している例である。もっとも今回のルールでは、前の例に比べてソースが簡略化されたとはいえないのだが、式が複雑化すれば歴然とした差が出るだろう。例えば、XPath式を//b[@age="15"]とすればage属性が15のb要素だけが指定される。

1: using System;
2: using System.Xml;
3: 
4: namespace SampleProgram
5: {
6:     class SampleClass
7:     {
8:         static void Main(string[] args)
9:         {
10:             XmlDocument doc = new XmlDocument();
11:             doc.Load(args[0]);
12:             XmlNodeList list = doc.SelectNodes( "//b" );
13:             foreach( XmlNode node in list )
14:             {
15:                 Console.WriteLine( node.InnerText );
16:             }
17:         }
18:     }
19: }
.NET Frameworkが持つXPath式の条件指定を使ったサンプルプログラム

 念のために、12行目のXPath式を説明しておく。「//b」と書かれた部分がXPath式だ。先頭の//は指定対象がXML文書ツリーのどの位置にあってもよいことを示し、bはその名前の要素を示す。

XSLTスクリプトによる選択

 実は.NET Frameworkには、XPath式だけでなくXSLTプロセッサも標準で入っており、容易にプログラム内部で呼び出して利用できる。実際に、取り出すべき条件をすべてXSLTスクリプトとして記述して、それをC#プログラム内部に埋め込んだ例を以下に示す。

1: using System;
2: using System.IO;
3: using System.Xml;
4: using System.Xml.XPath;
5: using System.Xml.Xsl;
6: 
7: namespace SampleProgram
8: {
9:     class SampleClass
10:     {
11:         const string xsltScript =
12:             "<xsl:stylesheet version='1.0'" +
13:             " xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >" +
14:             "<xsl:output method='text' />" +
15:             "<xsl:template match='text()'>" +
16:             "</xsl:template>" +
17:             "<xsl:template match='b'>" +
18:             "  <xsl:value-of select='.' />" +
19:             "</xsl:template>" +
20:             "</xsl:stylesheet>";
21: 
22:         static void Main(string[] args)
23:         {
24:             XslTransform xslt = new XslTransform();
25:             xslt.Load(new XmlTextReader(new StringReader(xsltScript)));
26:             XPathDocument doc = new XPathDocument(args[0]);
27:             xslt.Transform(doc,null,Console.Out);
28:         }
29:     }
30: }
XSLTを使ったサンプルプログラム

 ここで注目すべき点は、11〜20行目に直接記述されたXSLTスクリプトが、そのまま25行目でXSLTオブジェクトに読み込まれ、27行目の変換で使われているところである。外部からは、XSLTが使われていることはまったく分からない。だが、XSLTで気軽に処理できるような機能なら、このようにソースコードにスクリプトを埋め込んでしまって処理させることも現実的に可能である。この方法なら、DOMにもSAXにも触れることなく、データを変形させたり、抽出したりすることができる。

カーソルモデルによるアクセス

 カーソルモデルは、リレーショナルデータベースなどによく見られるデータアクセス方式である。データにアクセスする際には、まずカーソルをデータのある場所に移動させる。そして、カーソルを通して、データの読み書きを行うのである。これと同じような発想で、XML文書にアクセスすることができる。

 カーソルモデルを実現しているものには、W3CのDOM Level-2のTraversal and Range Specificationに記述されているTreeWalkerなどがある。ここではC#で記述する関係上.NET Frameworkに含まれるXPathNavigatorを使ってサンプルを記述した。

1: using System;
2: using System.Xml;
3: using System.Xml.XPath;
4: 
5: namespace SampleProgram
6: {
7:     class SampleClass
8:     {
9:         static void Main(string[] args)
10:         {
11:             XPathDocument doc = new XPathDocument(args[0]);
12:             XPathNavigator nav = doc.CreateNavigator();
13:             int depth = 0;
14:             while( true ) 
15:             {
16:                 if( nav.NodeType == XPathNodeType.Element ) 
17:                 {
18:                     if( nav.Name == "b" ) 
19:                     {
20:                         depth ++;
21:                     }
22:                 }
23:                 if( nav.NodeType == XPathNodeType.Text && depth > 0 ) 
24:                 {
25:                     Console.WriteLine( nav.Value );
26:                 }
27:                 bool hasFirst = nav.MoveToFirstChild();
28:                 if( hasFirst == false ) 
29:                 {
30:                     bool leftNext = nav.MoveToNext();
31:                     if( leftNext == false ) 
32:                     {
33:                         while( true ) 
34:                         {
35:                             bool leftParent = nav.MoveToParent();
36:                             if( leftParent == false ) return;
37:                             if( depth > 0 ) depth--;
38:                             bool leftNext2 = nav.MoveToNext();
39:                             if( leftNext2 != false ) break;
40:                         }
41:                     }
42:                 }
43:             }
44:         }
45:     }
46: }
カーソルモデルを使ったサンプルプログラム

 ここで、XPathNavigatorに使用されるMoveToFirstChildメソッドは、注目しているノードの最初の子ノードにカーソルを移動させる。MoveToNextメソッドは次の兄弟に移動させ、MoveToParentメソッドは親ノードにカーソルを移動させる、という機能を持つ。これらを組み合わせることによって、XML文書ツリー全体を歩き切ることが可能になる。

 ここで疑問を感じる人もいると思う。カーソルモデルといっても、むしろDOMよりも煩雑になるだけではないかと。だがカーソルモデルには、ソースが煩雑になっても余りあるメリットがあるのだ。1つのメリットは安全性だ。カーソルは実際に存在するノードの上にしか動かせないので、存在しないノードに向かって動かしてしまっても、その移動が受け付けられないだけで、無効なノードを指し示したりすることはない。また、注目点が1つに決まるので、データをロックしやすい。1つのXML文書ツリーを複数のスレッドから読み書きする場合、好き勝手にデータを書き換えると整合性が壊れる恐れがあるが、カーソルモデルなら一度に1つの場所しか注目しないので、最小限の範囲をロックするだけで、安全性を確保できる。

カーソルモデルによるイージーなコーディング

 XML文書がある型に収まっていると確信できるなら、カーソルモデルはずっとイージーに使うことができる。ここでは、要素bは、第2レベルの要素としてしか出現せず、たった1つのテキストノードだけを持つと仮定しよう。すると、以下のようにずっと短く記述することができる。

1: using System;
2: using System.Xml;
3: using System.Xml.XPath;
4: 
5: namespace SampleProgram
6: {
7:     class SampleClass
8:     {
9:         static void Main(string[] args)
10:         {
11:             XPathDocument doc = new XPathDocument(args[0]);
12:             XPathNavigator nav = doc.CreateNavigator();
13:             nav.MoveToFirstChild();
14:             bool r = nav.MoveToFirstChild();
15:             while( true ) 
16:             {
17:                 if( r == false ) break;
18:                 if( nav.NodeType == XPathNodeType.Element ) 
19:                 {
20:                     if( nav.Name == "b" ) 
21:                     {
22:                         nav.MoveToFirstChild();
23:                         Console.WriteLine( nav.Value );
24:                         nav.MoveToParent();
25:                     }
26:                 }
27:                 r = nav.MoveToNext();
28:             }
29:         }
30:     }
31: }
カーソルモデルを使ったもう1つのサンプルプログラム

 つまり、13行目でルートノードからルート要素を得、14行目でさらに第2レベルの要素の先頭に進み、あとは、15〜26行目のループを回りつつ、27行目のMoveToNextメソッドで兄弟間を1個ずつ進めていくわけだ。そして、発見すると、22行目でさらに下のレベルまでおりて、テキストノードの値を出力してから、24行目で下がったレベルを上がっている。

 このように階層や並び順を固定できるなら、カーソルモデルで簡潔に記述できる。また、万一予想外の並び順の文書がきた場合でも、nullポインタ参照のエラーで落ちたりはしない。カーソルは存在しないノードには移動しないためだ。

シーケンシャルなXML文書の読み込み

 .NET Frameworkに含まれるXmlReaderクラスはSAXと同様にシーケンシャルにXML文書を読み込む機能である。しかし、SAXと違って呼び出す側と呼び出される側が逆転している。このような機能も存在するということで紹介しよう。

1: using System;
2: using System.Text;
3: using System.Xml;
4: 
5: namespace SampleProgram
6: {
7:     class SampleClass
8:     {
9:         static void Main(string[] args)
10:         {
11:             XmlTextReader reader = new XmlTextReader(args[0]);
12:             bool inElementB = false;
13:             StringBuilder sb = new StringBuilder();
14:             while( true ) 
15:             {
16:                 bool r = reader.Read();
17:                 if( r == false ) break;
18:                 switch( reader.NodeType ) 
19:                 {
20:                     case XmlNodeType.Element:
21:                         if( reader.Name == "b" ) 
22:                         {
23:                             inElementB = true;
24:                         }
25:                         break;
26:                     case XmlNodeType.EndElement:
27:                         if( reader.Name == "b" ) 
28:                         {
29:                             Console.WriteLine( sb );
30:                             inElementB = false;
31:                         }
32:                         break;
33:                     case XmlNodeType.Text:
34:                         if( inElementB ) sb.Append( reader.Value );
35:                         break;
36:                     case XmlNodeType.CDATA:
37:                         if( inElementB ) sb.Append( reader.Value );
38:                         break;
39:                 }
40:             }
41:         }
42:     }
43: }
XML文書をシーケンシャルに読み込むサンプルプログラム

 XmlReaderは抽象的なクラスなので、そのままでは使えない。テキストデータを対象にXmlReaderを扱うクラスがXmlTextReader(System.Xml.XmlTextReader)なので、11行目のようにこれを使う。

 ここでポイントになるのは、16行目に記述されたReadメソッドである。Readメソッドは次のノードを読み出す。ノードの種類は、18行目〜のようにNodeTypeプロパティで判断できる。同様にノードの名前やノードの値も、Nameプロパティや、Valueプロパティ経由で読み出すことができる。これにより、Readメソッドを呼び出して、次々とノードを取得しながら順番に処理していくことができる。

 XmlReaderのSAXに対するアドバンテージは、ハンドラを作らなくてよい点にある。簡単な処理なら、このように50行にも満たない短い1つのメソッド内に収まってしまい、プログラムの見通しが良くなる。

XmlTextReaderクラスの問題点

 実はXmlTextReaderクラスには重大な問題がある。それを体感するには上記のサンプルソースに以下のXML文書を入力してみるとすぐに分かる。つまり、結果の文字列が出てこないのだ。

1: <?xml version="1.0" ?>
2: <!DOCTYPE sample [
3:   <!ENTITY ALTO "Alto">
4: ]>
5: 
6: <sample>
7:   <a>Kokoro</a>
8:   <b>&ALTO;</b>
9:   <c>Iina</c>
10: </sample>
先ほどのプログラムに、このXML文書を処理させてみると、結果が出てこない

 このような結果になるのは、XmlTextReaderクラスが実体を展開する能力を持っていないことによる。では、どのクラスが実体を展開する能力を持っているのかというと、XmlValidatingReaderクラスである。一見、整形式ならXmlTextReaderクラスでよく、妥当なXML文書を扱う場合だけXmlValidatingReaderクラスを使うとよいように思えるがそうではない。実際には、XmlTextReaderクラスにはテキストファイルからXML文書を読み込むだけの機能しかなく、どちらの場合でもXmlValidatingReaderクラスを使わねばならない。ただし、整形式のXML文書を処理する場合は、妥当性の検証が行われるとエラー扱いになる可能性があるので、これは抑止しなければならない。これらの処理を入れたソースを以下に示す。

1: using System;
2: using System.Text;
3: using System.Xml;
4: 
5: namespace SampleProgram
6: {
7:     class SampleClass
8:     {
9:         static void Main(string[] args)
10:         {
11:             XmlValidatingReader reader = new XmlValidatingReader( new XmlTextReader(args[0]) );
12:             reader.ValidationType = ValidationType.None;
13:             bool inElementB = false;
14:             StringBuilder sb = new StringBuilder();
15:             while( true ) 
16:             {
17:                 bool r = reader.Read();
18:                 if( r == false ) break;
19:                 switch( reader.NodeType ) 
20:                 {
21:                     case XmlNodeType.Element:
22:                         if( reader.Name == "b" ) 
23:                         {
24:                             inElementB = true;
25:                         }
26:                         break;
27:                     case XmlNodeType.EndElement:
28:                         if( reader.Name == "b" ) 
29:                         {
30:                             Console.WriteLine( sb );
31:                             inElementB = false;
32:                         }
33:                         break;
34:                     case XmlNodeType.Text:
35:                         if( inElementB ) sb.Append( reader.Value );
36:                         break;
37:                     case XmlNodeType.CDATA:
38:                         if( inElementB ) sb.Append( reader.Value );
39:                         break;
40:                 }
41:             }
42:         }
43:     }
44: }
実体を展開する機能を追加したサンプルプログラム

 前のソースと比較して変更点は2つある。1つは11行目で、XmlValidatingReaderクラスのインスタンスを作成するようにしたこと。もう1つは12行目に、妥当性の検証方法を指定するプロパティに、検証しない(ValidationType.None)を明示的に指定したことである。

XmlReaderクラスで文字列取得のメソッドを使う

 今回のルールに従うと、要素bの内容には文字列しかこないので、上記のソースはもっと簡略化して記述できる。具体的にはXmlReaderクラスのReadElementStringメソッドを使う。これは、ある要素が文字列だけを子に持つ場合にその文字列を取得してくれるものである。つまり、ReadElementStringメソッド内部で自動的にReadメソッドを呼び出して、必要な情報を自分で取ってきて処理してくれるのである。用意されているのは文字列取得だが、その気になれば次の整数を取ってくるなどのメソッドを自作するのも容易である。イベント駆動のSAXでは、このような便利なメソッドを作るのはちょっと面倒な話になる。実際にこれを使った例を以下に示す。

1: using System;
2: using System.Text;
3: using System.Xml;
4: 
5: namespace SampleProgram
6: {
7:     class SampleClass
8:     {
9:         static void Main(string[] args)
10:         {
11:             XmlValidatingReader reader = new XmlValidatingReader( new XmlTextReader(args[0]) );
12:             reader.ValidationType = ValidationType.None;
13:             while( true ) 
14:             {
15:                 bool r = reader.Read();
16:                 if( r == false ) break;
17:                 switch( reader.NodeType ) 
18:                 {
19:                     case XmlNodeType.Element:
20:                         if( reader.Name == "b" ) 
21:                         {
22:                             Console.WriteLine( reader.ReadElementString() );
23:                         }
24:                         break;
25:                 }
26:             }
27:         }
28:     }
29: }
ReadElementStringメソッドを使ったサンプルプログラム

次世代の解答・データ構造変換

 ここからの内容は、いわばオマケ編である。ここで示すプログラムは、ソースコードを入力しても実行できない。また、筆者が作成中の未公開のソフトに依存する話題を含んでいる。ここだけは参考程度に読んでほしい。

 XML文書にアクセスする際に、スキーマの情報を利用できると、まったく別次元のコーディングが可能になる。XMLのスキーマ言語で記述されたデータ定義をプログラム言語のデータ定義に変換してしまえば、XML文書の低レベルの構造を一切意識しないコーディングが可能になるのだ。これを行うソフトとしては、すでにRelaxerなどがある。

 実際にそれを行った例を示そう。今回はC#で統一すると決めたので、Relaxerではなく、筆者が開発中のRelax NGのサブセットからC#への変換ツールを使った。これについては、公開するかどうか、公開するにしても、どのように公開するか何もかも未定である。万一、欲しい人がいたら、欲しいと声を上げてほしい。

 さて、データ構造変換を使うには、まず、スキーマ言語でスキーマを記述しなければならない。これは決して難しいものではない。今回のルールのXML文書に対応するスキーマを、Relax NGのサブセットで記述したものが以下のりストだ。

1: <element name="sample">
2:   <element name="a"><text/></element>
3:   <element name="b"><text/></element>
4:   <element name="c"><text/></element>
5: </element>
サンプルのXML文書のスキーマをRelax NGで記述した

 そして、これを筆者の開発中のツールで変換したものが以下のとおりだ。ちょっと長いが全部プログラムが自動生成したもので、筆者は1行も書いていない。ここは読み飛ばしても問題ない。

1: //Auto Generated File by relaxng2cs Version 0.01
2: //2001/10/20 13:00:57
3: //
4: using System;
5: using System.Collections;
6: using System.Xml;
7: namespace test
8: {
9:     public class _base
10:     {
11:     }
12:     public class sample0 : _base 
13:     {
14:         public class a0 : _base 
15:         {
16:             public string val = "";
17:         }
18:         public a0 e_a0 = new a0();
19:         public class b0 : _base 
20:         {
21:             public string val = "";
22:         }
23:         public b0 e_b0 = new b0();
24:         public class c0 : _base 
25:         {
26:             public string val = "";
27:         }
28:         public c0 e_c0 = new c0();
29:     }
30:     public class document
31:     {
32:         public sample0 e_sample0 = new sample0();
33:         public void load( XmlValidatingReader targetReader )
34:         {
35:             relax2cs.runtime.MyReader reader = 
new relax2cs.runtime.MyReader(targetReader);
36:             // -------- Element sample Start --------
37:             reader.confirmStartTag( "sample", "" );
38:             sample0 temp0 = new sample0();
39:             reader.readNext();
40:             // -------- Element a Start --------
41:             reader.confirmStartTag( "a", "" );
42:             sample0.a0 temp1 = new sample0.a0();
43:             reader.readNext();
44:             temp1.val = reader.getNextText();
45:             temp0.e_a0 = temp1;
46:             reader.skipEndElement( "a", "" );
47:             // -------- Element a End --------
48:             // -------- Element b Start --------
49:             reader.confirmStartTag( "b", "" );
50:             sample0.b0 temp2 = new sample0.b0();
51:             reader.readNext();
52:             temp2.val = reader.getNextText();
53:             temp0.e_b0 = temp2;
54:             reader.skipEndElement( "b", "" );
55:             // -------- Element b End --------
56:             // -------- Element c Start --------
57:             reader.confirmStartTag( "c", "" );
58:             sample0.c0 temp3 = new sample0.c0();
59:             reader.readNext();
60:             temp3.val = reader.getNextText();
61:             temp0.e_c0 = temp3;
62:             reader.skipEndElement( "c", "" );
63:             // -------- Element c End --------
64:             e_sample0 = temp0;
65:             reader.skipEndElement( "sample", "" );
66:             // -------- Element sample End --------
67:             targetReader.Close();
68:         }
69:         public void save( XmlWriter writer )
70:         {
71:             writer.WriteStartDocument();
72:             // -------- Element sample Start --------
73:             writer.WriteStartElement( "sample", "" );
74:             sample0 temp4 = e_sample0;
75:             // -------- Element a Start --------
76:             writer.WriteStartElement( "a", "" );
77:             sample0.a0 temp5 = temp4.e_a0;
78:             // -------- text Start --------
79:             writer.WriteString( temp5.val.ToString() ); 
80:             // -------- text End --------
81:             writer.WriteEndElement();
82:             // -------- Element a End --------
83:             // -------- Element b Start --------
84:             writer.WriteStartElement( "b", "" );
85:             sample0.b0 temp6 = temp4.e_b0;
86:             // -------- text Start --------
87:             writer.WriteString( temp6.val.ToString() ); 
88:             // -------- text End --------
89:             writer.WriteEndElement();
90:             // -------- Element b End --------
91:             // -------- Element c Start --------
92:             writer.WriteStartElement( "c", "" );
93:             sample0.c0 temp7 = temp4.e_c0;
94:             // -------- text Start --------
95:             writer.WriteString( temp7.val.ToString() ); 
96:             // -------- text End --------
97:             writer.WriteEndElement();
98:             // -------- Element c End --------
99:             writer.WriteEndElement();
100:             // -------- Element sample End --------
101:             writer.WriteEndDocument();
102:             writer.Flush();
103:             writer.Close();
104:         }
105:     }
106: }
Relax NGによるスキーマを基に生成したクラス定義

 さて、次に行うべきことは、自動生成されたクラスを利用するプログラムを自分で記述することである。実際に書いてみた例は以下のとおりである。

1: using System;
2: using System.Xml;
3: 
4: namespace ConsoleApplication92
5: {
6:     class Class1
7:     {
8:         static void Main(string[] args)
9:         {
10:             test.document doc = new test.document();
11:             doc.load( new XmlValidatingReader( new XmlTextReader(args[0]) ) );
12:             Console.WriteLine( doc.e_sample0.e_b0.val );
13:         }
14:     }
15: }
上記のクラスを利用したサンプルプログラム

 見てのとおり、実質はたった3行である。10行目でドキュメントオブジェクトを生成し、11行目でXML文書をロードする。そして12行目でXML文書の階層に相当するオブジェクトの階層をたどって、文字列の収納されたメンバ変数にアクセスする。doc.e_sample0.e_b0.valは、e_sample0は要素sampleに、e_b0は要素bに、valは要素bの内容にそれぞれ相当する。

 コードの総量は最も大きいが、自分でコーディングする量に限ればとても小さい。

低レベルの実装としてDOM/SAXは今後も使われ続けるだろう

 XMLパーサへの標準インターフェイスとしてのDOM/SAXの普及は大変に意義深いものがあった。しかし、実際にコーディングをしているときにDOM/SAXが扱いやすいかというと、必ずしもそうではなかった。DOM/SAXの上位にもっと別の高レベルのレイヤが出現することは時代の必須の要請といえるだろう。しかし、DOM/SAXがXML文書の構造を忠実に再現するのにとどまっているのと異なり、高レベルのレイヤはさまざまなモデルを立ててさまざまな解釈を行っている。つまり、DOM/SAXのように、少数のインターフェイスのみを標準化するという方向性は似合わないかもしれない。むしろ目的ややり方別に、多数のインターフェイスが出現すると考える方が現実的かもしれない。いずれは、それらの新しいインターフェイスも淘汰されて、標準と呼ぶに値するものが残っていくだろう。だが、どれが標準に値するかを判断するには、まだまだ2001年現在のわれわれは経験不足である。結論を出すにはまだ早そうだ。

 しかし、1点だけ確かなことは、より高レベルのインターフェイスが増加することにより、プログラマーが生のDOM/SAXに触れる機会は減りそうだということだ。低レベルの実装としてDOM/SAXは今後も使われ続けるだろう。しかし、応用ソフトをコーディングするプログラマーがそれを直接使うかどうかは別問題である。

「連載 XMLフロンティア探訪」


XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間