2週に渡って,JAXBを使用したアンマーシャリング/マーシャリングを解説してきました。とはいうものの,そこで扱ったXMLドキュメントは,すべてファイルでした。
そこで,今週はファイルではない対象を扱ってみましょう。
取りあげるのはStAXとDOMです。もちろん,StAXもDOMもXMLパーサなので,単独でXMLドキュメントを解釈することが可能です。
では,なぜ複数のパースを組み合わせる必要があるのでしょうか。
たとえば,長大なXMLドキュメントの一部しか必要がない場合はどうでしょう。SAXやStAXで必要なところまで読み飛ばし,必要なところだけJAXBでアンマーシャリングします。もちろん,そのままSAXやStAXでパースしてもかまいませんが,スキーマがある場合はJAXBが簡単です。
また,DOMとXPathを組み合わせれば,必要な部分をクエリーすることが簡単にできます。必要な部分が見つかれば,後はJAXBにまかせてしまうというわけです。
このように用途によっては,パースの手段を組み合わせるということも十分意味を持ちます。
では,さっそくJAXBと他のパースを組み合わせてみましょう。
その前に,今週使用するXMLドキュメントを決めておきます。今週は,CDなどの音楽アルバムを表すXMLドキュメントを使用することにしました。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <album xmlns="http://www.javainthebox.net/music"> <title>Deja Vu</title> <credit>Crosby, Stills, Nash & Young</credit> <songs> <song title="Carry On" writer="Stephen Stills" /> <song title="Tech Your Children" writer="Graham Nash" /> <song title="Almost Cut My Hair" writer="David Crosby" /> <song title="Helpless" writer="Neil Young" /> <song title="Woodstock" writer="Joni Mitchell" /> <song title="Deja Vu" writer="David Crosby" /> <song title="Our House" writer="Graham Nash" /> <song title="4 + 20" writer="Stephen Stills" /> <song title="Country Girl" writer="Neil Young" /> <song title="Everybody I Love You" writer="Stephen Stills, Neil Young" /> </songs> </album>
ルートタグが<album>タグで,その中に<title>タグと<credit>タグがあります。そして,曲のデータをまとめる<songs>タグがあります。個々の曲は<song>タグで表されます。
このXMLドキュメントのスキーマをXML Schemaで表すと次のようになります。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" targetNamespace="http://www.javainthebox.net/music" xmlns:tns="http://www.javainthebox.net/music" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="album"> <xs:complexType> <xs:sequence> <xs:element ref="tns:title" minOccurs="1"/> <xs:element ref="tns:credit" minOccurs="1"/> <xs:element ref="tns:songs" minOccurs="1"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="title" type="xs:string"/> <xs:element name="credit" type="xs:string"/> <xs:element name="songs"> <xs:complexType> <xs:sequence> <xs:element ref="tns:song" minOccurs="1" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="song"> <xs:complexType> <xs:attribute name="title" type="xs:string"/> <xs:attribute name="writer" type="xs:string"/> </xs:complexType> </xs:element> </xs:schema>
このようなXMLドキュメントで,JAXBで扱うのは<song>タグとしてみましょう。
他のパースと組み合わせる前に,このスキーマからJavaのクラスを生成しておきます。
C:\jaxb>xjc album.xsd net\javainthebox\music\Album.java net\javainthebox\music\ObjectFactory.java net\javainthebox\music\Song.java net\javainthebox\music\Songs.java net\javainthebox\music\package-info.java
これで準備は整いました。
StAXとJAXBの組み合わせ
まず,StAXとJAXBを組み合わせてみます。
サンプルのソース | StAXUnmarshallerSample.java |
---|
StAXで<song>タグまでパースし,その後JAXBに引き渡します。
まず,StAXUnmarshallerSampleクラスのフィールドとコンストラクタを示します。
public class StAXUnmarshallerSample { // StAX 用ファクトリ private XMLInputFactory factory; // JAXB 用ファクトリ private JAXBContext context; // JAXB アンマーシャラー private Unmarshaller unmarshaller; public StAXUnmarshallerSample() throws JAXBException { // StAX用ファクトリの生成 factory = XMLInputFactory.newInstance(); // JAXB用ファクトリの生成 context = JAXBContext.newInstance("net.javainthebox.music"); // アンマーシャラー生成 unmarshaller = context.createUnmarshaller(); }
StAXもJAXBも,ファクトリクラスはパースやアンマーシャリング/マーシャリングのたびに生成するのではなく,なるべくまとめて生成するようにします。そのために,ここではコンストラクタで一度だけ生成するようにしました。
次にStAXでのパースをおこなうparseメソッドです。mainメソッドからは,このparseメソッドがコールされます。
// StAX でパース public void parse(String xmlfile) { XMLStreamReader reader = null; InputStream stream = null; try { stream = new FileInputStream(xmlfile); // パーサの生成 reader = factory.createXMLStreamReader(stream); // イベントループ while (reader.hasNext()) { // 次のイベントを取得 int eventType = reader.next(); if (eventType == XMLStreamReader.START_ELEMENT) { // タグ名がsongかどうか調べる if ("song".equals(reader.getLocalName())) { // song タグの場合JAXBでアンマーシャル unmarshal(reader); } } } } catch (FileNotFoundException ex) { System.err.println("ファイルがオープンできません"); } catch (XMLStreamException ex) { System.err.println("パースに失敗しました"); } finally { if (reader != null) { try { reader.close(); } catch (XMLStreamException ex) {} } if (stream != null) { try { stream.close(); } catch (IOException ex) {} } } }
ここではStAXのカーソルAPIを使用してパースをおこなっています。StAXの詳細はJava SE 6完全攻略 第69回 第三のパーサ - StAXをご参照ください。
カーソルAPIでは,XMLStreamReaderオブジェクトからイベントを取り出し,そのイベントに応じた処理を行ないます。
ここではSTART_ELEMENTイベントだけを解釈しています。この時,青字で示したように,タグ名がsongであるかを調べています。
タグ名がsongの場合はunmarshalメソッドをコールし,JAXBでアンマーシャリングします。赤字で示したように,引数はXMLStreamReaderオブジェクトです。
// JAXB でアンマーシャリング
private void unmarshal(XMLStreamReader reader) {
try {
// アンマーシャリング
// 戻り値の型はObjectクラス
Object obj = unmarshaller.unmarshal(reader);
Song song = (Song)obj;
// song タグの内容を出力
System.out.println(song.getTitle()
+ " (" + song.getWriter() + ")");
} catch (JAXBException ex) {
// 例外処理
System.err.println("アンマーシャリングに失敗しました");
}
}
unmarshalメソッドの引数は,XMLStreamReaderオブジェクトです。アンマーシャリングの対象がストリームの場合と違うのは,この引数だけです。
ただしくアンマーシャリングできれば,unmarshalメソッドの戻り値は,XMLドキュメントに対応するオブジェクトになります。
ここでは,StAXで<song>タグまでパースしたので,JAXBでアンマーシャリングを開始するのは<song>タグからとなります。このため,unmarshalメソッドの戻り値の型はSongクラスになります。
しかし,unmarshalメソッドの戻り値の型はObjectクラスと定義されているため,Songクラスにキャストします。
もし,このキャストを排除したい場合は,以下に示すように,引数がXMLStreamReaderインタフェースと,Classクラスにオーバーロードされているunmarshalメソッドを使用します。この場合,戻り値はジェネリクスを用い,JAXBElement<T>となります。Tは第2引数で指定されたクラスとなります。
JAXBElement<Song> element = unmarshaller.unmarshal(reader, Song.class); Song song = element.getValue();
では,実行してみましょう。使用するXMLドキュメントは一番始めに示したXMLドキュメントを使用しました。
C:\jaxb>java StAXUnmarshallerSample dejavu.xml Carry On (Stephen Stills) Tech Your Children (Graham Nash) Almost Cut My Hair (David Crosby) Helpless (Neil Young) Woodstock (Joni Mitchell) Deja Vu (David Crosby) Our House (Graham Nash) 4 + 20 (Stephen Stills) Country Girl (Neil Young) Everybody I Love You (Stephen Stills, Neil Young)
曲名と,カッコ内に作曲者が表示されていることがお分かりのはずです。
DOMとJAXBの組み合わせ
次に,DOMとJAXBの組み合わせです。
サンプルのソース | DOMUnmarshallerSample.java |
---|
このサンプルも先ほどのサンプルと同様,<song>タグだけをJAXBでアンマーシャリングします。ここで,<song>タグを抜き出すためにXPathを使用します。
まず,DOMUnmarshallerSampleクラスのコンストラクタを示します。
public DOMUnmarshallerSample()
throws ParserConfigurationException, JAXBException {
// DOM の初期化
factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
builder = factory.newDocumentBuilder();
// XPath の初期化
xpathFactory = XPathFactory.newInstance();
xpath = xpathFactory.newXPath();
// JAXB の初期化
context = JAXBContext.newInstance("net.javainthebox.music");
unmarshaller = context.createUnmarshaller();
}
コンストラクタでは,DOMのDocumentBuilderオブジェクト,XPathのXPathオブジェクト,JAXBのUmarshallerオブジェクトを生成しています。
DocumentBuilderFactoryクラスのsetNamespaceAwareメソッドを引数trueでコールしているのは,DOMで名前空間を認識させるためです(赤字部分)。
つづいて,DOMによるバース処理です。
// DOM でパース public void parse(String xmlfile) { try { // パース Document document = builder.parse(new File(xmlfile)); // 名前空間の処理 NamespaceContext context = new NamespaceContext() { public String getNamespaceURI(String prefix) { if ("pre".equals(prefix)) { return "http://www.javainthebox.net/music"; } else { return XMLConstants.NULL_NS_URI; } } public String getPrefix(String uri) { throw new UnsupportedOperationException(); } public Iterator getPrefixes(String uri) { throw new UnsupportedOperationException(); } }; xpath.setNamespaceContext(context); // XPath でクエリ NodeList list = (NodeList)xpath.evaluate("/pre:album/pre:songs/pre:song", document, XPathConstants.NODESET); // クエリ結果のノードを JAXB でアンマーシャリング for (int i = 0; i < list.getLength(); i++) { Node node = list.item(i); unmarshal(node); } } catch (IOException ex) { System.err.println("パースに失敗しました"); } catch (SAXException ex) { System.err.println("パースに失敗しました"); } catch (XPathExpressionException ex) { System.err.println("パースに失敗しました"); } }
NamespaceContextインタフェースを実装した無名クラスを作成しているのは,XPathで名前空間を扱うためです。青字で示した,getNamespaceURIメソッドでプリフィックスと名前空間の対応を決めます。
そして,XPathでクエリーを行なっているのがXPathクラスのevaluateメソッドです(赤字部分)。ここで,getNamespaceURIメソッドで対応付けしたプリフィックスを使用して,ノードのクエリを行ないます。
evaluateメソッドの第3引数でNODESETを指定しているので,戻り値はNodeListオブジェクトになります。後はNodeListオブジェクトが保持するNodeオブジェクトをJAXBでアンマーシャリングします。
// JAXB でアンマーシャリング
private void unmarshal(Node node) {
try {
// アンマーシャリング
Object obj = unmarshaller.unmarshal(node);
Song song = (Song)obj;
// song タグの内容を出力
System.out.println(song.getTitle()
+ " (" + song.getWriter() + ")");
} catch (JAXBException ex) {
// 例外処理
System.err.prntln("アンマーシャリングに失敗しました");
}
}
Nodeオブジェクトを使用した場合,unmarshalメソッドの引数が変化するだけで,後は何も変りません。他の場合と,同じように処理を行なうことができます。
実行した結果はStAXの場合とまったく一緒なので,ここでは省略します。
ここで示したように,JAXBは他のパースと組み合わせて使うことも簡単にできます。XMLのパースにはそれぞれ得手,不得手があります。特性に応じた使い分けをすることで効率的にXMLドキュメントを扱うことができるはずです。
さて,今までの解説でははじめにスキーマがあることを前提に考えていました。来週はそれとは逆に,JavaのクラスからXMLのスキーマを生成することを考えていきます。お楽しみに。