両方の世界のベスト: XPath と XmlReader の組み合わせ
大胆なオバサンジョとハワード・ハオ
Microsoft Corporation
2004 年 5 月 5 日
XPathReader.exe サンプル ファイルをダウンロードします。
概要: Dare Obasanjo では、XPath 対応 XmlReader を使用して大規模な XML ドキュメントを効率的にフィルター処理および処理する機能を提供する XPathReader について説明します。 XPathReader を使用すると、大きなドキュメントを順番に処理し、XPath 式に一致する識別されたサブツリーを抽出できます。
(11ページ印刷)
はじめに
約 1 年前、Tim Bray の記事「 XML Is Too Hard For Programmers」を読みました。この記事では、SAX のようなプッシュ モデル API の煩雑な性質について、XML の大規模なストリームを扱うことについて苦情を申し立てました。 Tim Bray は、XML の理想的なプログラミング モデルとして、Perl でテキストを操作するのと似ています。このモデルでは、正規表現を使用して目的の項目を照合することでテキストのストリームを処理できます。 XML ストリームの理想的なプログラミング モデルを示す Tim Bray の記事からの抜粋を次に示します。
while (<STDIN>) {
next if (X<meta>X);
if (X<h1>|<h2>|<h3>|<h4>X)
{ $divert = 'head'; }
elsif (X<img src="/^(.*\.jpg)$/i>X)
{ &proc_jpeg($1); }
# and so on...
}
Tim Bray だけがこの XML 処理モデルに対して年を取ったわけではありません。 ここ数年、私が一緒に働くさまざまな人が、正規表現を使用してテキスト ストリームを処理する方法に似た方法で XML ドキュメントのストリームを処理するためのプログラミング モデルの作成に取り組んできました。 この記事では、この作業の集大成である XPathReader について説明します。
貸与された書籍の検索: XmlTextReader ソリューション
XmlReader を使用した既存の XML 処理手法と比較して 、XPathReader の生産性の向上を明確に示すために、 基本的な XML 処理タスクを実行するサンプル プログラムを作成しました。 次のサンプル ドキュメントでは、所有している書籍の数と、現在友人に貸し出されているかどうかについて説明します。
<books>
<book publisher="IDG books" on-loan="Sanjay">
<title>XML Bible</title>
<author>Elliotte Rusty Harold</author>
</book>
<book publisher="Addison-Wesley">
<title>The Mythical Man Month</title>
<author>Frederick Brooks</author>
</book>
<book publisher="WROX">
<title>Professional XSLT 2nd Edition</title>
<author>Michael Kay</author>
</book>
<book publisher="Prentice Hall" on-loan="Sander" >
<title>Definitive XML Schema</title>
<author>Priscilla Walmsley</author>
</book>
<book publisher="APress">
<title>A Programmer's Introduction to C#</title>
<author>Eric Gunnerson</author>
</book>
</books>
次のコード サンプルでは、書籍を貸し出したユーザーの名前と、貸し出した書籍を表示します。 コード サンプルでは、次の出力が生成されます。
Sanjay was loaned XML Bible by Elliotte Rusty Harold
Sander was loaned Definitive XML Schema by Priscilla Walmsley
XmlTextReader Sample:
using System;
using System.IO;
using System.Xml;
public class Test{
static void Main(string[] args) {
try{
XmlTextReader reader = new XmlTextReader("books.xml");
ProcessBooks(reader);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
static void ProcessBooks(XmlTextReader reader) {
while(reader.Read()){
//keep reading until we see a book element
if(reader.Name.Equals("book") &&
(reader.NodeType == XmlNodeType.Element)){
if(reader.GetAttribute("on-loan") != null){
ProcessBorrowedBook(reader);
}else {
reader.Skip();
}
}
}
}
static void ProcessBorrowedBook(XmlTextReader reader){
Console.Write("{0} was loaned ",
reader.GetAttribute("on-loan"));
while(reader.NodeType != XmlNodeType.EndElement &&
reader.Read()){
if (reader.NodeType == XmlNodeType.Element) {
switch (reader.Name) {
case "title":
Console.Write(reader.ReadString());
reader.Read(); // consume end tag
break;
case "author":
Console.Write(" by ");
Console.Write(reader.ReadString());
reader.Read(); // consume end tag
break;
}
}
}
Console.WriteLine();
}
}
XPath を XML の正規表現として使用する
最初に必要なのは、テキスト ストリーム内の文字列の正規表現と同じ方法で、XML ストリームの対象ノードに対してパターン マッチングを実行する方法です。 XML には、 XPath と呼ばれるノードを照合するための言語が既に用意されています。これは、出発点として役立ちます。 XPath では、大きな XML ドキュメント内のノードをストリーミング方式で照合するメカニズムとして、変更なしで使用できなくなるという問題があります。 XPath は、XML ドキュメント全体がメモリに格納されていることを前提としており、ドキュメントに対して複数のパスを必要とする操作や、少なくとも XML ドキュメントの大部分をメモリに格納する必要がある操作を許可します。 次の XPath 式は、このようなクエリの例です。
/books/book[author='Frederick Brooks']/@publisher
book 要素の publisher 属性が、値が 'Resellerk Brooks' の子 author 要素を持つ場合、クエリは book 要素の publisher 属性を返します。 このクエリを実行するには、ストリーミング パーサーの一般的なデータよりも多くのデータをキャッシュする必要があります。これは、子 author 要素が表示され、その値が調べられるまで、book 要素でパブリッシャー属性をキャッシュする必要があるためです。 ドキュメントとクエリのサイズによっては、メモリにキャッシュする必要があるデータの量が非常に大きくなり、キャッシュするデータを特定することは非常に複雑になる可能性があります。 これらの問題に対処する必要を回避するために、同僚 である Arpan Desai は、XML の前方専用処理に適した XPath のサブセットの提案を思い付きました。 XPath のこのサブセットについては、彼の論文「 シーケンシャル XPath の概要」で説明されています。
シーケンシャル XPath の標準 XPath 文法にはいくつかの変更がありますが、最大の変更は軸の使用の制限です。 現在、特定の軸は述語で有効ですが、他の軸はシーケンシャル XPath 式の非述語部分でのみ有効です。 軸を 3 つの異なるグループに分類しました。
- 共通軸: 現在のノードのコンテキストに関する情報を提供します。 これらは、シーケンシャル XPath 式の任意の場所に適用できます。
- 前方軸: ストリーム内のコンテキスト ノードの前にあるノードに関する情報を提供します。 これらは、"将来" ノードを探しているため、場所パス コンテキストでのみ適用できます。 例として "child.'" があります。 "child" がパス内にある場合は、指定されたパスの子ノードを正常に選択できます。 ただし、述語に "子" が含まれている場合、現在のノードを選択することはできません。これは、その子を先に見て述語式をテストし、リーダーを巻き戻してノードを選択できないためです。
- 逆軸: 基本的に前方軸の反対です。 たとえば、"parent" になります。場所のパスに親が含まれている場合は、特定のノードの親を返します。 ここでも、後方に移動できないため、位置パスまたは述語でこれらの軸をサポートすることはできません。
XPathReader でサポートされている XPath 軸を示す表を次に示します。
Type | 軸 | サポートされている場所 |
---|---|---|
共通軸 | 属性、名前空間、self | XPath 式内の任意の場所 |
前方軸 | child、descendant、descendant-or-self、following、following-sibling | 述語を除く XPath 式内の任意の場所 |
軸の反転 | 先祖、先祖または自己、親、前、前兄弟 | サポートされていません |
XPathReader ではサポートされていない XPath 関数がいくつかあります。これは、XML ドキュメントの大部分をメモリにキャッシュしたり、XML パーサーをバックトラッキングしたりする必要があるためです。 count() や sum() などの関数は一切サポートされていませんが、local-name() や namespace-uri() などの関数は、引数が指定されていない場合にのみ機能します (つまり、コンテキスト ノードでこれらのプロパティを要求する場合のみ)。 次の表に、XPathReader でサポートされていない、または機能の一部が制限されている XPath 関数の一覧を示します。
XPath 関数 | サポートされているサブセット | 説明 |
---|---|---|
number last() | サポートされていません | バッファリングなしでは機能しません |
number count(node-set) | サポートされていません | バッファリングなしでは機能しません |
string local-name(node-set?) | string local-name() | ノード セットをパラメーターとして使用できない |
string namespace-uri(node-set?) | string namespace-uri() | ノード セットをパラメーターとして使用できない |
string name(node-set?) | string name() | ノード セットをパラメーターとして使用できない |
number sum(node-set) | サポートされていません | バッファリングなしでは機能しません |
XPathReader の XPath に対する最終的な主な制限は、要素またはテキスト ノードの値のテストを禁止することです。 XPathReader では、次の XPath 式はサポートされていません。
/books/book[contains(.,'Frederick Brooks')]
上記のクエリでは、文字列に "Frederick Brooks" というテキストが含まれている場合、 book 要素が選択されます。 このようなクエリをサポートするには、ドキュメントの大部分をキャッシュする必要があり、 XPathReader でその状態を巻き戻すことができる必要があります。 ただし、属性、コメント、または処理命令の値のテストはサポートされています。 XPathReader では、次の XPath 式がサポートされています。
/books/book[contains(@publisher,'WROX')]
前述の XPath のサブセットは、テキストストリームに一致する正規表現に似たメモリ効率の高いストリーミング XPath ベースの XML パーサーを提供できるように十分に削減されています。
XPathReader の最初の外観
XPathReader は、前のセクションで説明した XPath のサブセットをサポートする XmlReader のサブクラスです。 XPathReader を使用して、URL から読み込まれたファイルを処理したり、XmlReader の他のインスタンスに階層化したりできます。 次の表は、XPathReader によって XmlReader に追加されたメソッドを示しています。
Method | 説明 |
---|---|
Match(XPathExpression) | リーダーが現在配置されているノードが XPathExpression と一致するかどうかをテストします。 |
Match(string) | リーダーが現在配置されているノードが XPath 文字列と一致するかどうかをテストします。 |
Match(int) | リーダーが現在配置されているノードが、リーダーの XPathCollection 内の指定したインデックスにある XPath 式と一致するかどうかをテストします。 |
MatchesAny(ArrayList) | リーダーが現在配置されているノードが、リスト内の 任意の XPathExpressions と一致するかどうかをテストします。 |
ReadUntilMatch() | 現在のノードが指定された XPath 式のいずれかに一致するまで、XML ストリームの読み取りを続行します。 |
次の例では、 XPathReader を使用して、ライブラリ内のすべての書籍のタイトルを印刷します。
using System;
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;
public class Test{
static void Main(string[] args) {
try{
XPathReader xpr = new XPathReader("books.xml", "//book/title");
while (xpr.ReadUntilMatch()) {
Console.WriteLine(xpr.ReadString());
}
Console.ReadLine();
}catch(XPathReaderException xpre){
Console.WriteLine("XPath Error: " + xpre);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
}
XmlTextReader を使用した従来の XML 処理に対する XPathReader の明らかな利点は、アプリケーションが XML ストリームの処理中に現在のノード コンテキストを追跡する必要ができないことです。 上記の例では、コンテンツが表示および印刷されている title 要素が book 要素の子かどうかについて、アプリケーション コードで心配する必要はありません。これは XPath によって既に行われているため、状態を明示的に追跡することによって行われます。
パズルのもう 1 つの部分は 、XPathCollection クラスです。 XPathCollection は、XPathReader が照合する XPath 式のコレクションです。 XPathReader は、XPathCollection オブジェクトに含まれるノードにのみ一致します。 この一致は動的です。つまり、XPath 式は、必要に応じて解析プロセス中に XPathCollection に追加および削除できます。 これにより、必要になるまで XPath 式に対してテストを行わないパフォーマンスの最適化が可能になります。 XPathCollection は、XPath 式に対してノードを照合するときに XPathReader によって使用されるプレフィックス<>と名前空間のバインドを指定するためにも使用されます。 次のコード フラグメントは、これを実現する方法を示しています。
XPathCollection xc = new XPathCollection();
xc.NamespaceManager = new XmlNamespaceManager(new NameTable());
xc.NamespaceManager.AddNamespace("ex", "http://www.example.com");
xc.Add("//ex:book/ex:title");
XPathReader xpr = new XPathReader("books.xml", xc);
貸与された書籍の検索: XPathReader ソリューション
XPathReader を見てみましょう。次は、XmlTextReader の使用と比較して、XML ファイルの処理をどれだけ改善できるかを確認します。 次のコード サンプルでは、"ローン付き書籍の検索: XmlTextReader ソリューション" というセクションの XML ファイルを使用し、次の出力を生成する必要があります。
Sanjay was loaned XML Bible by Elliotte Rusty Harold
Sander was loaned Definitive XML Schema by Priscilla Walmsley
XPathReader Sample:
using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;
public class Test{
static void Main(string[] args) {
try{
XmlTextReader xtr = new XmlTextReader("books.xml");
XPathCollection xc = new XPathCollection();
int onloanQuery = xc.Add("/books/book[@on-loan]");
int titleQuery = xc.Add("/books/book[@on-loan]/title");
int authorQuery = xc.Add("/books/book[@on-loan]/author");
XPathReader xpr = new XPathReader(xtr, xc);
while (xpr.ReadUntilMatch()) {
if(xpr.Match(onloanQuery)){
Console.Write("{0} was loaned ", xpr.GetAttribute("on-loan"));
}else if(xpr.Match(titleQuery)){
Console.Write(xpr.ReadString());
}else if(xpr.Match(authorQuery)){
Console.WriteLine(" by {0}", xpr.ReadString());
}
}
Console.ReadLine();
}catch(XPathReaderException xpre){
Console.WriteLine("XPath Error: " + xpre);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
}
この出力は、元のコード ブロックから大幅に簡略化され、メモリ面でほぼ効率的であり、正規表現を使用したテキスト ストリームの処理に非常に似ています。 Tim Bray は、大きな XML ストリームを処理するための XML プログラミング モデルに最適に達したようです。
XPathReader のしくみ
XPathReader は、抽象構文ツリー (AST) にコンパイルされた XPath 式のコレクションを作成し、基になる XmlReader から受信ノードを受け取りながらこの構文ツリーを歩くことで、XML ノードと一致します。 AST ツリーを移動すると、クエリ ツリーが生成され、スタックにプッシュされます。 クエリによって照合されるノードの深さが計算され、XML ストリームでノードが検出されると、XmlReader の Depth プロパティと比較されます。 XPath 式の AST を生成するためのコードは、System.Xml内のクラスの基になるコードから取得 されます。Xpath。 これは、共有ソース共通言語インフラストラクチャ 1.0 リリースのソース コードの一部として使用できます。
AST の各ノードは、次の 3 つのメソッドを定義する IQuery インターフェイスを実装します。
internal virtual object GetValue(XPathReader reader);
internal virtual bool MatchNode(XPathReader reader);
internal abstract XPathResultType ReturnType()
GetValue メソッドは、クエリ式の現在の側面に対する相対的な入力ノードの値を返します。 MatchNode メソッドは、入力ノードが解析されたクエリ コンテキストと一致するかどうかをテストしますが、ReturnType プロパティはクエリ式が評価する XPath 型を指定します。
XPathReader の今後の計画
この実装のバリエーションに付属するBizTalk Serverなど、Microsoft のさまざまなユーザーが XPathReader を見つけたことに基づいて、プロジェクト用の GotDotNet ワークスペースを作成することにしました。 EXSLT.NET プロジェクトから XPathReader への一部の関数の統合や、より広範な XPath のサポートなど、いくつかの機能が追加されています。 XPathReader の開発をさらに進めたい開発者は、GotDotNet ワークスペースに参加できます。
まとめ
XPathReader は、XPath の機能を利用し、それを XmlReader のプルベース XML パーサー モデルの柔軟性と組み合わせることで、XML ストリームを処理するための強力な方法を提供します。 System.Xmlの構成設計により、XPathReader を XmlReader の他の実装に重ね合わすことができます。また、その逆も可能です。 XML ストリームの処理に XPathReader を使用することは、 XmlTextReader の使用とほぼ同じ速度ですが、同時に XmlDocument での XPath と同じように使用できます。 本当にそれは両方の世界の最高です。
Dare Obasanjo は Microsoft の WebData チームのメンバーであり、特に、.NET Framework、Microsoft XML Core Services (MSXML)、Microsoft Data Access Components (MDAC) の System.Xml 名前空間と System.Data 名前空間内のコンポーネントを開発しています。
Howard Hao は、WebData XML チームのテストのソフトウェア デザイン エンジニアであり、XPathReader のメイン開発者です。
この記事に関する質問やコメントは、GotDotNet の Extreme XML メッセージ ボード に自由に投稿できます。