StAX で XML を処理する 第 2 回: プル解析とイベント

Java 開発者向け StAX イベント・イテレーター API を探る

Streaming API for XML (StAX) が提供するイベント・イテレーター API には、パフォーマンスとユーザビリティーに関して他の XML 処理方法に勝る利点が独特に融合されています。第 1 回では StAX を紹介し、カーソル API について詳しく説明しました。今回の記事ではイベント・イテレーター API の詳細を深く掘り下げ、Java™ 開発者にとってのメリットを探ります。

Peter Nehrer (pnehrer@ecliptical.ca), Freelance Writer, Consultant

Peter Nehrerはオンタリオ州トロントに住むソフトウェア・コンサルタントであり、Eclipseベースのエンタープライズ・ソリューションやJ2EEアプリケーションを専門としています。専門家としての関心分野は、開発ツールやモデル・ドリブンのソフトウェア開発、情報共有などです。彼はEclipseに関連する幾つかのオープンソース・プロジェクトの作成者であり貢献者です。またXMLおよび関連技術のIBM認定のソリューション開発者(IBM Certified Solutions Developer for XML and related technologies)でもあります。マサチューセッツ大学Amherst校にてコンピューター・サイエンスで修士を取得しています。



2006年 12月 05日

StAX による XML の解析

第 1 回 (「参考文献」を参照) では、StAX が 2 つの形式の API で XML を処理することを説明しました。そのうちの 1 つ、カーソル API は下位レベルの XML 解析方法です。この手法では、アプリケーションが一連の XML トークンでカーソルを進めながら、各ステップでパーサーの状態を調べ、解析された内容についての情報を取得していきます。これは非常に効率的な方法で、特にリソースが限られた環境に適しています。ただし、カーソル API はオブジェクト指向ではないため、Java アプリケーションにぴったりとは言えません。特に、パフォーマンスだけでなくコードの拡張性と保守容易性も重要なエンタープライズ・ドメインには不十分です。例えば、(汎用コンポーネントでメッセージ・エンベロープを処理する一方、メッセージ固有のコンテンツ処理 (引数のバインディングなど) を他のコンポーネントに委譲する) 多層 Web サービスには、オブジェクト指向の手法のほうが有効なはずです。

StAX が提供するもう一方の API 形式は、イベント・オブジェクトを中心としています。カーソル API と同じく、これもプル・ベースの XML 解析方法で、アプリケーションはストリームの解析が終わるまで (またはアプリケーションが解析の停止を決定するまで)、提供されたメソッドの 1 つを使ってパーサーから各イベントをプルし、必要に応じてイベントを処理します。

XMLEventReader インターフェースの概要

イベント・イテレーター API のメイン・インターフェースとなるのは、XMLEventReader です。XMLStreamReader に比べ、このインターフェースにはほんの少数のメソッドしかありません。これは、XMLEventReader ではイベント・オブジェクトのストリームを繰り返し処理するためです (実際には、XMLEventReader は java.util.Iterator を拡張します)。解析したイベントに関するすべての情報は、リーダーにではなくイベント・オブジェクトにカプセル化されます。

イベント・イテレーター API を使用するには、アプリケーションがまず、XMLInputFactory から XMLEventReader のインスタンスを取得する必要があります。このファクトリー自体は、標準の JAXP 手法で取得できます。これは Abstract Factory パターンに依存して接続可能サービス・プロバイダーをサポートするという手法で、これにより、デフォルトの XMLInputFactory 実装のインスタンスの取得が XMLInputFactory.getInstance() の呼び出しと同じくらい単純になります。リスト 1 を見てください。

リスト 1. デフォルトの XMLInputFactory 実装を使用した XMLEventReader の作成方法
String uri = "http://www.atomenabled.org/atom.xml";
URL url = new URL(uri);
InputStream input = url.openStream();
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader reader = factory.createXMLEventReader(uri, input);
...

XMLInputFactory は、XMLEventReader の作成に使用できる多彩な入力ソースをサポートします。XMLInputFactory では、Java I/O パッケージに含まれる InputStream および Reader の他、(TrAX の) JAXP Sourceもサポートされているので、StAX を JAXP の構造変換 API (TrAX) に容易に統合できます。さらに、XMLStreamReader から XMLEventReader を作成することも可能です。このオプションは特に、イベント・イテレーター API がどのようにカーソル API の上にスタック (StAX と stacks の語呂合わせではありませんが) するかを表しています。実際、この実装は他の入力ソースのいずれかを使用して XMLStreamReader を作成し、それを使って XMLEventReader を作成するというのが一般的です。

XMLEventReader の使用

XMLEventReader を作成すると、アプリケーションはこれを使用して、基底 XML ストリームの InfoSet の各構成部分を表すイベントを繰り返し処理できるようになります。XMLEventReader インターフェースは java.util.Iterator を拡張するため、hasNext() や next() などの標準 Iterator メソッドを使用できます。ただし、remove() メソッドはサポートされていないため、このメソッドを呼び出すとその結果に例外がスローされるのでご注意ください。

XMLEventReader は、XML 処理を容易にするための以下の便利なメソッドも提供します。

  • nextEvent() は基本的にIterator の next() に相当し、強力に型定義されています。このメソッドは、すべてのイベント・オブジェクトの基本インターフェースである XMLEvent を返します。
  • nextTag() は、重要でない空白文字をスキップして次の開始タグまたは終了タブまで飛びます。つまり、戻り値は StartElement イベントまたは EndElement イベントのいずれかとなります (後で詳しく説明します)。このメソッドは特に、要素のみのコンテンツ (文書型宣言 (DTD: Document Type Declaration) で EMPTY と宣言された要素) を処理する際に役立ちます。
  • getElementText() は、テキストのみの要素から、開始タグから終了タグまでのテキスト・コンテンツを取得します。このメソッドは、次の期待されるイベントとして StartElement から始まり、EndElement が検出されるまでのすべての文字を連結し、結果をストリングとして返します。
  • peek() はイテレーターを進めずに、イテレーターが返すはずの次のイベントをチェックします。

リスト 2 は、Atom フィードを繰り返し処理する XMLEventReader メソッドの使用方法を示しています。Atom は Web パブリッシングで使用されるシンジケーション・フォーマットです。この例では、最初に XMLInputFactory のデフォルト・インスタンスを取得し、そのインスタンスを使って、指定された URL にある Atom フィードを解析する XMLEventReader を作成しています。イベントを繰り返し処理する間、peek() メソッドで次のイベントがフィードのアイコン URL を含むアイコン要素の開始であるかどうかを判断します。アイコン要素の開始が検出されると、getElementText() メソッドで要素のテキスト・コンテンツ (アイコン URL) を取得します。この時点で、繰り返し処理が終了します。

リスト 2. peek() および getElementText() メソッドを使用した Atom フィードのアイコン URL の抽出方法
final QName ICON = new QName("http://www.w3.org/2005/Atom", "icon");
URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader reader = factory.createXMLEventReader(uri, input);
try {
while (reader.hasNext()) {
XMLEvent event = reader.peek();
if (event.isStartElement()) {
StartElement start = event.asStartElement();
if (ICON.equals(start.getName())) {
System.out.println(reader.getElementText());
break;
}
}

reader.nextEvent();
}
} finally {
reader.close();
}

input.close();

返されるイベント・オブジェクトは不変なので、アプリケーションはこれらのイベント・オブジェクトを解析処理が過ぎてもキャッシュに入れておくことができます。その一方で、アプリケーションが別のイベント保存 (および再利用) ポリシーを定義することも可能です。これについては、第 3 回で説明します。

アプリケーションは getProperty(String) を使用して、基礎となる実装からカスタム・プロパティーまたは事前定義プロパティーの値を取得することもできます。プロセスが完了した時点で、アプリケーションは close() メソッドを呼び出してリーダーを終了し、プロセスで獲得したリソースを解放しなければなりません。

XMLEventReader を使用したイベント・ストリームの繰り返し処理は、かなり単純なものです。これらのイベントを処理するには、次に説明する StAX の XMLEvent 階層に関する知識と理解が必要です。


イベント、および XML パーサーでの使用方法

前にも強調したように、XMLEventReader は解析プロセスの各ステップで、その状態をイベント・オブジェクトを介してアプリケーションに伝えます。この API 全体で使用されるイベント・オブジェクトの標準タイプは javax.xml.stream.events パッケージに定義されます。このタイプの階層のルートを表すのが、XMLEvent インターフェースです。すべてのイベント・タイプは、このインターフェースを拡張しなければなりません。XMLStreamConstants インターフェースには、それぞれのカーソル・レベルのイベント・タイプを表すインターフェースが (カーソル API のインターフェースと同様に) 定義されていますが、カスタム・インターフェースを使用することも可能です (そのカスタム・インターフェースが XMLEvent を拡張する限り)。これについては、第 3 回で説明します。


XMLEvent 階層のナビゲート

パーサーからイベントを取得した後、アプリケーションに通常必要となるのは、そのイベントを XMLEvent サブタイプのいずれかに分類して、タイプ固有の情報にアクセスできるようにすることです。その方法は何通りかあります。その 1 つは、強力な instanceof チェックです。これは、一連の if/then ステートメントによるテストによって、返されたイベントが目的のインターフェースを実装するかどうかを調べるという方法です。さらに、XMLEvent には XMLStreamConstants に定義されたイベント定数のいずれかを返す getEventType() メソッドもあります。このメソッドによって返された情報を基準にして、イベントを分類することができます。例えば、イベントの getEventType() が START_ELEMENT を返した場合、そのイベントは確実に StartElement に分類できます。

イベントの具体的なタイプを判断するもう 1 つの方法として、この目的のために用意されたブール値照会メソッドのいずれかを使用することもできます。例えば isAttribute() は、イベントが Attribute の場合は true を返し、StartElement の場合は isStartElement() を返します。分類に便利なメソッドはそれだけではありません。asStartElement()、asEndElement()、および asCharacters() は該当するイベントをそれぞれ StartElement、EndElement、Characters に分類します。

リスト 3 では、isStartElement() および asStartElement() メソッドを使って、取得したイベントがStartElement であるかどうかを判断してから StartElement タイプに分類することによって、この要素の名前にアクセスできるようにしています。

リスト 3. イベント・タイプの判断と対応するインターフェースへの分類
// get an event from the reader
...
if (event.isStartElement()) {
StartElement start = event.asStartElement();
// use methods provided by StartElement
...

XMLEventType にはタイプ階層関連のメソッドに加え、getLocation()、getSchemaType()、および writeAsEncodedUnicode(Writer) メソッドがあります。getLocation() メソッドが返す Location オブジェクトは、基礎となる入力ソースでのイベントの場所に関するオプション情報 (イベントが終了する場所を示す行と列番号など) を提供します。getSchemaType() メソッドでは、指定されたイベントに関するオプションの XML スキーマ情報を取得できます (実装で使用可能にされている場合)。そして writeAsEncodedUnicode(Writer) メソッドは、イベント・オブジェクトを標準的な方法で java.io.Writer に書き込めるようにするための規約を定義します。これはとりわけ、カスタム・イベントを定義する際 (次回の記事で説明) に役立ちます。このメソッドを使用すると、シリアライザーが代行して XMLEvent の派生をシリアライズするため、アプリケーションがカスタム・シリアライザーを使用する必要がなくなります。


XML 文書の処理

XML 文書全体を表すストリームを解析する際に、XMLEventReader が最初に返すイベントは StartDocument です。このインターフェースには、文書自体に関する情報を取得するためのメソッドがあります。例えば、getSystemId() メソッドは文書のシステム ID (既知の場合) を返します。getVersion() メソッドは文書で使用されている XML バージョンを返します。文書の XML 宣言で他の値が指定されていない限り、デフォルトのバージョン 1.0 が使用されます。

getCharacterEncodingScheme() メソッドは、文書の文字エンコード方式を返します。これは XML 宣言で明示的に指定されているエンコード方式か、そうでない場合はパーサーが自動検出します。デフォルト値は UTF-8 です。また、isStandalone() メソッドは外部マークアップ宣言が存在しない限り true を返します。文書の XML 宣言でエンコード方式が明示的に指定されている場合には、その値を返します。

DTD へのアクセス

XMLEventReader は DTD を検出すると、これを DTD イベントとして返します。アプリケーションが DTD を考慮しない場合は、パーサーの javax.xml.stream.supportDTD プロパティーを false に設定することで、この動作をオフにするよう要求できます。DTD イベントの getDocumentTypeDeclaration() メソッドでは、内部サブセットも含む DTD 全体をストリングとして取得できます。この DTD は、実際には実装によって一層構造化された表現 (DTD によって固有の表現) に処理され、getProcessedDTD() メソッドの呼び出しで使用可能にされる場合があります。getEntities() メソッドは、内部的にも外部的にも一般外部エンティティー宣言を表す EntityDeclaration イベント (以下に説明) のリストを返します。最後に getNotations() メソッドが、宣言された表記を表すために使用される NotationDeclaration イベント (同じく以下に説明) のリストを返します。

EntityDeclaration イベントは、文書の DTD で宣言された解析対象外の一般エンティティーを表します。このイベントは単独でレポートされるのではなく、DTD イベントの一部としてレポートされます。このイベントは、エンティティーの名前、公開 ID とシステム ID、そして関連付けられた表記名を取得するためのメソッド (それぞれ、getName()、getPublicId()、getSystemId()、および getNotationName() メソッド) を提供します。イベントが内部エンティティーを表す場合は、getReplacementText() メソッドによって代替テキストを取得できます。

同様に、NotationDeclaration も DTD イベントを介してのみアクセスできるイベントです。このイベントは表記宣言を表します。このインターフェースには、名前を取得するメソッド (getName()) だけでなく、表記の公開 ID とシステム ID を取得するメソッド (それぞれ getPublicId() と getSystemId() メソッド) もあります。少なくともこの 2 つのいずれかが使用可能でなければなりません。

リスト 4 に、解析対象外の外部エンティティー参照を処理する方法を示します。この例では、架空のカタログ文書に複数の出版物への参照が含まれています。これらの出版物のコンテンツは、PDF または HTML ファイルにある可能性があります (いずれも有効な XML ではありません)。イベントを繰り返し処理しながら、DTD イベントから表記宣言を抽出して名前別にキャッシュに入れます。エンティティー参照を検出すると、そのエンティティー宣言を取得し、キャッシュに入れられた表記宣言を名前を基準に検索します。現実のアプリケーションでは、表記識別子を使用して該当するコンテンツ・プロセッサーの場所を見つけ、エンティティーのシステム識別子を入力として使用するという方法が考えられます。

リスト 4. 解析対象外エンティティーと表記に関する情報の取得方法を示す例
final String xml = "<?xml version=\"1.0\" standalone=\"no\" ?>" +
"<!DOCTYPE catalog [" +
"<!ELEMENT catalog (publication+) >" +
"<!ELEMENT publication (#PCDATA) >" +
"<!ATTLIST publication title CDATA #REQUIRED >" +
"<!NOTATION pdf SYSTEM \"application/pdf\" >" +
"<!NOTATION html SYSTEM \"text/html\" >" +
"<!ENTITY overview SYSTEM \"resources/overview.pdf\" NDATA pdf 
>" +
"<!ENTITY chapter1 SYSTEM \"resources/chapter_1.html\" NDATA html 
>" +
"]>" +
"<catalog>" +
"<ext title=\"Overview\">&overview;</ext>" +
"<ext title=\"Chapter 1\">&chapter1;</ext>" +
"</catalog>";
Map notations = new HashMap();
StringReader input = new StringReader(xml);
XMLInputFactory f = XMLInputFactory.newInstance();
XMLEventReader r = f.createXMLEventReader("http://example.com/catalog.xml", 
input);
PrintWriter out = new PrintWriter(System.out);
try {
while (r.hasNext()) {
XMLEvent event = r.nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.ENTITY_REFERENCE:
EntityReference ref = (EntityReference) event;
EntityDeclaration decl = ref.getDeclaration();
NotationDeclaration n = (NotationDeclaration) 
notations.get(decl.getNotationName());

out.print("Object of type ");
out.print(n.getSystemId());
out.print(" located at ");
out.print(decl.getSystemId());
out.print(" would be placed here.");
break;
case XMLStreamConstants.DTD:
DTD dtd = (DTD) event;
for (Iterator i = dtd.getNotations().iterator(); i.hasNext();) 
{
n = (NotationDeclaration) i.next();
notations.put(n.getName(), n);
}
default:
event.writeAsEncodedUnicode(out);
out.println();
}
}
} finally {
r.close();
}

input.close();
out.flush();

要素、属性、名前空間宣言の処理

XMLEventReader は要素ごとに、開始タグを表す StartElement イベントを返し、最終的にはそれに対応する終了タグを表す EndElement イベントを返します。個別の開始タグと終了タグを持たない空の要素 (<empty-element/> など) であっても、リーダーは StartElement の直後に EndElement イベントを返します。

XML 文書に含まれる情報の大部分は通常StartElement で表されるため、他のどのイベントよりも、このイベントを扱う機会が多くなるはずです。要素の修飾名を取得するには、getName() を呼び出します。修飾 XML 名を表す QName クラスは、修飾名のすべてのコンポーネント (名前空間 URI、接頭部、ローカル名など) をカプセル化します。getNamespaceContext() メソッドでは、現行の名前空間のコンテキストを現在対象範囲内にあるすべての名前空間に関する情報と併せて取得できます。要素の属性を取得するには、getAttributes() を呼び出すか、あるいは getAttributeByName(QName) を使って名前 (あらかじめ分かっている場合) を基準にして行います。同様に、各要素で宣言されたすべての名前空間を取得するには getNamespaces() メソッドを呼び出し、現行コンテキストに固有の接頭部を結合した名前空間を返すには getNamespaceURI(String) メソッドを呼び出します。

これらの要素の属性はイベントとしてモデル化され、Attribute インターフェースによって表されますが、要素の属性は通常、個別のイベントとしてはレポートされません。属性には、StartElement イベントからアクセスします。getName() メソッドは属性の修飾名を返し、getValue() は属性の値をストリングとして返します。属性が実際に要素に指定されているものなのか、あるいは文書のスキーマによって暗黙的に示されているものなのかを判断するには、isSpecified() を呼び出します。getDTDType() メソッドは属性の宣言型 (CDATA、IDREF、または NMTOKEN) を返します。

要素で宣言された名前空間も同じく、個別にレポートされるのではなく、StartElement イベントからアクセスします。Namespace インターフェースは実際には Attribute を拡張します。これは、名前空間は要素の属性として(特殊な接頭部を使って) 指定されるためです。getPrefix() メソッドは、名前空間属性のローカル名を取得するための省略表現です (デフォルト名前空間宣言の場合は、接頭部が「xmlns」ではなく空のストリングであるため、その限りではありません。)。同様に、getNamespaceURI() メソッドは属性の値 (宣言された名前空間 URI) を返します。名前空間がデフォルト名前空間 (空の接頭部を使用) であるかどうかを判断するには、isDefaultNamespaceDeclaration() を呼び出してください。

EndElement は、要素の終了タグ (空の要素の場合は、単なる要素のマークアップの終わり) を表します。要素の修飾名を取得するには getName() メソッドを使用し、対象範囲外になった名前空間を調べるには getNamespaces() を使用します。

リスト 5 は、Atom 拡張のすべての要素と属性 (つまり、Atom 名前空間にも XML 名前空間にも属さないもの) をレポートします。まず、それぞれの StartElement イベントについて、名前空間 URI が Atom 名前空間 URI であることが確認されます。次にすべての属性を繰り返し処理し、Attribute インターフェースを使ってそれぞれの名前を取得します。最後に、Atom 名前空間にも XML 名前空間にも属さない属性をレポートします。

リスト 5. StartElement イベントから要素の属性を取得する方法
final String ATOM_NS = "http://www.w3.org/2005/Atom";

URL url = new URL(uri);
InputStream input = url.openStream();
XMLInputFactory f = XMLInputFactory.newInstance();
XMLEventReader r = f.createXMLEventReader(uri, input);
try {
while (r.hasNext()) {
XMLEvent event = r.nextEvent();
if (event.isStartElement()) {
StartElement start = event.asStartElement();
boolean isExtension = false;
boolean elementPrinted = false;
if (!ATOM_NS.equals(start.getName().getNamespaceURI())) {
System.out.println(start.getName());
isExtension = true;
elementPrinted = true;
}

for (Iterator i = start.getAttributes(); i.hasNext();) {
Attribute attr = (Attribute) i.next();
String ns = attr.getName().getNamespaceURI();
if (ATOM_NS.equals(ns))
continue;

if ("".equals(ns) && !isExtension)
continue;

if ("xml".equals(attr.getName().getPrefix()))
continue;

if (!elementPrinted) {
elementPrinted = true;
System.out.println(start.getName());
}

System.out.print("\t");
System.out.println(attr);
}
}
}
} finally {
r.close();
}

input.close();

テキスト・コンテンツの表現

Characters イベントは実際には、3 タイプのテキスト・イベントを表すために使用されます。この 3 つの・タイプとは、実際のコンテンツ (CHARACTERS)、CDATA セクション、そして無視可能な空白文字 (SPACE) です。このイベントには、これらのサブタイプを区別するためのメソッドがあります。まず、isCData() は CDATA イベントの場合には true を返し、SPACE イベントの場合には isIgnorableWhitespace() を返します。getData() メソッドは、イベントのテキストを返します。そして isWhiteSpace() は、テキストを構成しているのがすべて空白文字 (必ずしも無視可能な空白文字とは限りません) であるかどうかを示します。

未解決の一般エンティティー参照については、EntityReference イベントがレポートされます。解析済みエンティティーについては、リーダーの javax.xml.stream.isReplacingEntityReferences プロパティーがfalse に設定されている場合のみ、EntityReference イベントがレポートされます。それ以外の場合は、パーサーは、内部エンティティー参照を代替テキスト (宣言での指定に従って) に置き換えて通常の文字イベントとしてレポートするか、あるいは外部エンティティーを解決して通常のマークアップとしてレポートするように要求されます。EntityReference インターフェースでは、getName() メソッドと getDeclaration() メソッドを呼び出して、それぞれエンティティーの名前、宣言 (EntityDeclaration イベントとして) を取得できます。

PROCESSING_INSTRUCTION および COMMENTS イベントはそれぞれ、ProcessingInstruction と Comment で表現されます。ProcessingInstruction には命令のターゲットとデータを取得する getTarget() および getData() メソッドがあります。一方、Comment インターフェースに定義された getText() メソッドでは、コメントのテキストを取得できます。

リスト 6 は、Characters イベントを使って各種のテキスト・コンテンツをレポートする一例です。このリストを見ると、Comment および ProcessingInstruction インターフェースの使用方法もわかります。

リスト 6. 文字イベントと処理命令イベントのレポート方法
final String ATOM_NS = "http://www.w3.org/2005/Atom";

URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory f = XMLInputFactory.newInstance();
XMLEventReader r = f.createXMLEventReader(uri, input);
try {
while (r.hasNext()) {
XMLEvent event = r.nextEvent();
if (event.isCharacters()) {
Characters c = event.asCharacters();
System.out.print("Characters");
if (c.isCData()) {
System.out.print(" (CDATA):");
System.out.println(c.getData());
} else if (c.isIgnorableWhiteSpace()) {
System.out.println(" (IGNORABLE SPACE)");
} else if (c.isWhiteSpace()) {
System.out.println(" (EMPTY SPACE)");
} else {
System.out.print(": ");
System.out.println(c.getData());
}
} else if (event.isProcessingInstruction()) {
ProcessingInstruction pi = (ProcessingInstruction) event;
System.out.print("PI(");
System.out.print(pi.getTarget());
System.out.print(", ");
System.out.print(pi.getData());
System.out.println(")");
} else if (event.getEventType() == XMLStreamConstants.COMMENT) {
System.out.print("Comment: ");
System.out.println(((Comment) event).getText());
}
}
} finally {
r.close();
}

input.close();

XMLEventReader が渡す最後のイベントは通常、EndDocument で、これによって新しいメソッドが定義されることはありません。


イベントのフィルタリングとイベント・ストリームの操作

今までの説明でおわかりのように、XMLEventReader を XMLEvent とそのサブタイプと併せて使用すると、XML を極めて簡単に解析できます。アプリケーションは解析プロセスを制御することで、それぞれのイベントでの処理内容を決めることができます。その一方で、アプリケーション (またはそのコンポーネント) が特定タイプのコンテンツを持つイベント・ストリームを待機している場合に対応した、特殊なイベント・リーダーを作成することも可能です。例えば、特定のイベントだけを呼び出し側に渡すためのフィルターを備えた XMLEventStream を作成できます。その方法は、XMLInputFactory のインスタンスで createXMLEventReader(XMLEventReader, EventFilter) メソッドを呼び出し、基本イベント・リーダーと、このリーダーから取得したイベントを許容または拒否する単純なフィルターを渡すだけです。リスト 7 に、このようなフィルターの例を示します (このフィルターは Processing Instruction イベントだけを許容するものですが、アプリケーションは許容するイベントの基準を自由に定義できます)。

リスト 7. EventFilter による文書中の Processing Instructions の検索
URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader r = factory.createXMLEventReader(uri, input);
XMLEventReader fr = factory.createFilteredReader(r, new EventFilter() {
public boolean accept(XMLEvent e) {
return e.getEventType() == PROCESSING_INSTRUCTION;
}
});

try {
while (fr.hasNext()) {
XMLEvent e = fr.nextEvent();
if (e.getEventType() == PROCESSING_INSTRUCTION) {
ProcessingInstruction pi = (ProcessingInstruction) e;
System.out.println(pi.getTarget() + ": " + pi.getData());
}
}
} finally {
fr.close();
r.close();
}

input.close();

より高度なストリーム操作を行うには、javax.xml.stream.util パッケージに定義されたユーティリティー・クラス、EventReaderDelegate を拡張します。開発者はこのクラスを使用して、デフォルトですべての呼び出しが委譲される既存の XMLEventStream をラップします。これにより、サブクラスで特定のメソッドをオーバーライドして基本リーダーの動作を変更することができます。この手法を使用すれば、例えば合成イベントをイベント・ストリームに注入したり、変換することも可能です。このように変更されたストリームを繰り返し処理するアプリケーションは、ストリームが操作されたことを認識する必要はありません。リスト 8 に、この手法の例を示します。

リスト 8. EventReaderDelegate によるストリームへの Comment の注入
URL url = new URL(uri);
InputStream input = url.openStream();

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader r = factory.createXMLEventReader(uri, input);

XMLEventReader fr = new EventReaderDelegate(r) {

private Comment comment;

public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = null;
if (comment != null) {
event = comment;
comment = null;
return event;
}

event = super.nextEvent();
if (event.isStartDocument()) {
XMLEventFactory ef = XMLEventFactory.newInstance();
comment = ef.createComment("Generated " + new Date());
}

return event;
}
};

OutputStreamWriter writer = new OutputStreamWriter(System.out);
try {
while (fr.hasNext()) {
XMLEvent event = fr.nextEvent();
event.writeAsEncodedUnicode(writer);
}
} finally {
fr.close();
r.close();
}

input.close();
writer.flush();

上記の例を実装するには、アプリケーションが標準イベント (この例の場合は Comment) のインスタンスを作成できなければならないことに注意してください。この機能は、標準イベント・タイプごとに作成メソッドを定義する XMLEventFactory クラスによって実現されるためです (実際は、各メソッドに複数のオーバーロード・バージョンがあり、イベント・タイプに応じて異なる引数のセットが使用されます)。XMLInputFactory と同じく、このクラスも Abstract Factory パターンを実装するため、具体的なインスタンスを取得するには getInstance() を呼び出してください。


まとめ

この記事で紹介した例は、イベント・イテレーター API で実行可能な内容のほんの一部に過ぎません。柔軟性と使いやすさに優れた StAX が提供するイベント・イテレーター API では、他にもさまざまなことを実行できます。第 3 回の記事では、カスタム・イベントの作成方法と使用方法を紹介するとともに、StAX シリアライザー API についても説明する予定です。

参考文献

学ぶために

  • 「StAX'ing up XML, Part 1: An introduction to Streaming API for XML (StAX)」 (Peter Nehrer 著、developerWorks、2006年11月): この 3 回シリーズの第 1 回では、StAX とその XML 解析用のカーソル API の概要を説明しています。
  • JSR 173: Streaming API for XML: XML のプル解析用に Java ベースの API を提案している Java Specification Request を読んでください。
  • XML Pull Parsing: プル・ベースの XML 解析の促進と教育を専門としたサイトです。
  • BEA Dev2Dev Online: StAX: BEA の StAX に関する Web ページにアクセスしてください。WebLogic の StAX 実装へのリンクも記載されています。
  • 「XML programming in Java technology, Part 1」(Doug Tidwell 著、developerWorks、2004年1月): Java での XML プログラミングに関するチュートリアルです。XML 用の共通 APIや、XML 文書の構文解析、作成、操作、そして変換方法についても説明しています。
  • 「JAXP のすべて」(Brett McLaughlin 著、developerWorks、2005年5月): JAXP API の解説および検証機能の操作方法を説明しています。
  • 「Tip: Use XML streaming parsers」(Berthold Daum 著、developerWorks、2003年11月): このヒントを読んで、XML 解析を一層効率的にするために StAX が提供するストリーミング・パーサーの使用方法を学んでください。
  • 「Tip: Write XML documents with StAX」(Berthold Daum 著、developerWorks、2003年12月): StAX による XML のシリアライゼーションに関するヒントです。XML 文書を構文解析し、XML 文書を出力ストリームに書き込む方法を学んでください。
  • 「An XSLT style sheet and an XML dictionary approach to internationalization」(Laura Menke 著、developerWorks、2001年4月): XSLT が実世界の問題にどのように役立つかについての例を取り上げています。ディクショナリー駆動型の手法で Web ページの動的な国際化を有効にしたときに、サイトのコンテンツが変更された場合、編集する必要があるファイル数を最小限にします。
  • 「How an XSLT processor works」(Benoit Marchal 著、developerWorks、2004年3月): XSLT プロセッサーを支える理論をより深く理解して、一層生産的な XSLT プログラマーになってください。
  • 「XSLT 1.0 から 2.0 へのアップグレード計画 第 1 回: XSLT の改善内容」(David Marston、Joanne Tong 共著、developerWorks、2006年10月): XSLT 2.0 の主要な機能と、それによって解決される XSLT 1.0 (現在使用されているバージョン) の欠点を比較した記事です。
  • IBM XML 認証: XML や関連技術の IBM 認定開発者になる方法について調べてください。
  • XML の Technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM レッドブックについては、developerWorks XML ゾーンを参照してください。
  • developerWorks technical events and webcasts: これらのセッションで最新情報を入手してください。
  • developerWorks XML ゾーンで XML のすべてについて学んでください。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology
ArticleID=243228
ArticleTitle=StAX で XML を処理する 第 2 回: プル解析とイベント
publish-date=12052006