注: このヒントではJAXPを使用します。このクラスはJava 2 SDK 1.4の一部でもあるので、1.4をインストール済みでしたら他にソフトウェアを追加する必要はありません。SAXの基本的な点については簡単に説明しますが、JavaとXMLの基本的な点については事前に理解しておく必要があります。
このヒントでは、特定の緊急事態が生じたときの通知先となる従業員を決定し、それに応じて機能を果たすアプリケーションを考察します。(連絡そのものは、読者のために課題として残しておきます。)リスト1 に示すソース・ドキュメントは、単に従業員、部門、および状況をリストしたものに過ぎません。
リスト1. ソース・ドキュメント
<?xml version="1.0"?>
<personnel>
<employeeempid="332" deptid="24" shift="night" status="contact">
JennyBerman
</employee>
<employeeempid="994" deptid="24" shift="day" status="donotcontact">
AndrewFule
</employee>
<employeeempid="948" deptid="3" shift="night" status="contact">
AnnaBangle
</employee>
<employeeempid="1032" deptid="3" shift="day" status="contact">
DavidBaines
</employee>
</personnel>
|
SAXアプリケーションは2つの部分から成り立っています。メイン・アプリケーションはXMLReader を作成します。これは、実際に文書を構文解析し、startElement やendDocument などのイベントをコンテンツ・ハンドラーに送ります。エラーは、別個のエラー・ハンドラーに送ることができます。ハンドラー・オブジェクトは、これらのイベントを受け取って、処理します。
メイン・アプリケーションは、コンテンツ・ハンドラーまたはエラー・ハンドラー (あるいはその両方) として機能することもできますが、リスト2 では、これらを別個の3つのクラスとしています。
リスト2. メイン・アプリケーション
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.InputSource;
import java.io.IOException;
public class MainSaxApp {
public staticvoid main (String[] args){
try {
StringparserClass = "org.apache.crimson.parser.XMLReaderImpl";
XMLReader reader = XMLReaderFactory.createXMLReader(parserClass);
reader.setContentHandler(new DataProcessor());
reader.setErrorHandler(new ErrorProcessor());
InputSource file = new InputSource("employees.xml");
reader.parse(file);
} catch (IOException ioe) {
System.out.println("IO Exception: "+ioe.getMessage());
} catch(SAXException se) {
System.out.println("SAX Exception: "+se.getMessage());
}
}
} |
リーダーのコンテンツ・ハンドラーをDataProcessor オブジェクトとするよう設定することにより、アプリケーションはリーダーに対して、イベントをこのオブジェクトに送るよう命じます。リスト3 に示すDataProcessor は単純なものであり、要素の名前と従業員の状況だけを調べて、従業員と連絡を取るかどうかを決めます。
リスト3. コンテンツ・ハンドラー
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.Attributes;
public class DataProcessor extends DefaultHandler
{
public voidstartElement (String namespaceUri, String localName,
String qualifiedName, Attributesattributes) {
if(localName.equals("employee")){
if(attributes.getValue("status").equals("contact")){
System.out.println("Contacting employee "+
attributes.getValue("empid"));
//Implement actual contact here
}
}
}
} |
ErrorProcessor クラスはそれほど重要ではありません。このクラスは、このヒント用のソース・コードに組み込まれています。(ソース・コードをダウンロードするには、参考文献を参照してください。)
アプリケーションを実行すると、どの部門に所属しているかに関係なく、状況の属性がcontact の従業員が全員出力に含まれることになります。
Contacting employee 332
Contacting employee 948
Contacting employee 1032
|
現段階で、アプリケーションは、部門に関係なく、on duty (勤務中) としてリストされているすべての従業員と連絡を取ります。そして、これは正しく機能しています (あるいは、正しく機能するものと期待できます)。特定の部門の従業員だけと連絡を取るようにとの新しい要件を受け取った場合、選択肢は2つあります。
- コンテンツ・ハンドラーを変更し、あらゆる種類の新しいバグが入り込む危険を冒す
- コンテンツ・ハンドラーに送るデータを変更し、該当する従業員だけが勤務中と判断されるようにする
後で別の要件が追加されることも考えられますから、それぞれを別個に実装する方が理にかなっています。
SAXフィルターは、パーサーとコンテンツ・ハンドラーの間に位置します。パーサーからイベントを受け取ると、他の指示がない限り、変更を加えずにイベントをコンテンツ・ハンドラーに渡します。たとえば、リスト4 に示す、次のフィルターを考慮してみましょう。
リスト4. 単純なXMLフィルター
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
public class DataFilter extends XMLFilterImpl
{
} |
XMLFilterImpl クラスには、変更を加えずに単にデータを渡すだけのメソッドが組み込まれています。必要なのは、メイン・アプリケーション内のストリームにフィルターを挿入することだけです (リスト5 を参照)。
リスト5. メイン・アプリケーションにフィルターを挿入
...
XMLReader reader = XMLReaderFactory.createXMLReader(parserClass);
DataFilter filter = new DataFilter();
filter.setParent(reader);
filter.setContentHandler(new DataProcessor());
filter.setErrorHandler(new ErrorProcessor());
filter.parse("employees.xml"); } catch (IOException ioe) {
...
|
アプリケーションは従来どおりにXMLReader を作成します。しかし、実際にファイルの構文解析を開始するのはフィルターです。フィルターは、自分の親であるXMLReader からイベントを受け取ると、構文解析を開始します。(覚えておいていただきたい点ですが、フィルターはsuper(parent) を呼び出します。)フィルターは、イベントをコンテンツ・ハンドラーに渡します。これは、元のバージョンで使用したのと同じ、DataProcessor オブジェクトです。
現時点で、フィルターは、変更を加えることなくイベントを渡すだけなので、アプリケーションを実行しても、得られる出力は次のとおりで前と変わりがありません。
Contacting employee 332 Contacting employee 948 Contacting employee 1032 |
しかし、フィルターを適用すれば、メイン・アプリケーションを修正しないで、容易に変更を加えることができます。たとえば、リスト6 で、フィルターは、部門24に所属しない従業員を全員除外します。そのために行うことは、この部門に属さない従業員の状況をdonotcontact に設定することだけです。
リスト6. データのフィルター操作
...
import org.xml.sax.helpers.AttributesImpl;
public class DataFilter extends XMLFilterImpl
{s
public void startElement (String namespaceUri, String localName,
String qualifiedName, Attributes attributes)
throws SAXException
{
AttributesImpl attributesImpl = new AttributesImpl(attributes);
if (localName.equals("employee")){
if (!attributes.getValue("deptid").equals("24")){
attributesImpl.setValue(3, "donotcontact");
}
}
super.startElement(namespaceUri, localName, qualifiedName, attributesImpl);
}
}
|
この場合、XMLFilterImpl に定義されているstartElement() メソッドをオーバーライドすることになります。依然としてイベントの受け渡しを行うものの、従業員が部門24に所属していない場合、フィルターが、この従業員にリストされているAttributes オブジェクトをdo not contact に変更して、イベントを渡します。
DataProcessor オブジェクトは、データが操作されたことを知りません。このオブジェクトは、連絡を取るべき従業員と、連絡を取るべきでない従業員がいることしか知りません。処理の結果の出力は、次のように変わります。
Contacting employee 332 |
このヒントでは、XMLフィルターを使ってSAXアプリケーションの処理を変更するための簡単な方法を示しました。この場合、フィルターは事前に定義されたものでした。しかし、フィルターの動作を実行時に選択することにより、さまざまな状況に対応するアプリケーションを構築することもできます。これは、DataFilter クラスを置換することで実現できるでしょう。そのためには、パラメーターを実行時に渡すことや、あるいは最初にフィルター・クラスを作成するファクトリーを使用することさえできるかもしれません。
SAXアプリケーションでは、複数のフィルターを連鎖させ、1つのフィルターの出力を別のフィルターの入力とすることができます。これにより、モジュラーのチャンクを使って複雑なプログラミングが可能になります。
- このヒントのソース・コードを入手するには、saxfiltertipsourcecode.zip をダウンロードしてください。
-
SAX の仕様を調べてください。
-
Java API for XML Processing (JAXP) をダウンロードしてください。
-
developerWorks XMLゾーンには、他にもたくさんのXML参考文献があります。
-
XMLおよびその関連テクノロジーのIBM Certified Developer になる方法をお調べください。
- 毎週、このような有益なXMLのヒントをお届けできます。developerWorksXMLヒント・ニュースレターの購読登録をしてください。
