XML文書から必要な情報を抽出する際、その情報がDOMツリーのどこにあるのかが問題です。 例えばフィード(RSS 2.0)の先頭の項目のタイトルが欲しいのであれば、 DOMツリーでの位置は、 ルート要素である rss 要素の子ノードの channel 要素の先頭の子ノードである item 要素の子ノードの title 要素の内容、ということになります。
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<item>
<title>りんご</title>
<link>http://www.apple.com/jp/</link>
</item>
<item>
<title>みかん</title>
<link>https://orangeamps.com/</link>
</item>
<item>
<title>バナナ</title>
<link>https://en.wikipedia.org/wiki/The_Velvet_Underground_%26_Nico</link>
</item>
</channel>
</rss>
この位置の情報を、単純な文字列で表現する方法があります。それがパス(path)です。
/rss/channel/item
パスは、ファイルシステムにおけるファイルやフォルダ(ディレクトリ)の位置、 URLにおけるサイト内のフォルダ階層を表現する方法として広く使われています。
C:\Program Files\Java https://www.w3.org/TR/xpath-3/
XML文書の特定の部分をパスで指定する方法として、 XPath (XML Path Language) があります。
必要な情報がどこにあるのかをパスを用いて表現することにより、 そのパスを入力とした抽出処理を考えることができるようになります。
XPath では、XML文書がノードの親子関係に基づくツリー構造になっているとします。
ただ要素名を書くと、カレントノードの子要素になります。
カレントノードが "/" の場合の "RDF" は、"/RDF" とも書けるわけです。 ディレクトリの相対/絶対パスと同じ話です。
"//item" とすると、DOMツリー上の全item要素がマッチします。 これは、getElementsByTagName("item") とするのと同じことです。 便利なようですが、指定された要素をツリー内で全探索することになるので、 パスが分かっている場合には省略せずに記述することで無駄な探索を避けることができます。
XPathでは、条件を付加することによりマッチする要素を絞り込むことができます。 それをうまく使うとプログラムをスマートに記述することができます。
Java では java.xml モジュールの javax.xml.xpath パッケージが XPath version 1.0 をサポートします。
import javax.xml.xpath.*;
以下の手順を踏みます。
XML文書の DOM ツリーをあらかじめ構築しておきます。 その際、名前空間を使わないように指定しておくと、XPath 式の中でも名前空間の指定が不要になります。
// inputStream は対象に合わせて用意しておく DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); DOMImplementationLS implementation = (DOMImplementationLS)registry.getDOMImplementation("XML 1.0"); // 読み込み対象の用意 LSInput input = implementation.createLSInput(); input.setByteStream(inputStream); input.setEncoding("UTF-8"); // 構文解析器(parser)の用意 LSParser parser = implementation.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); parser.getDomConfig().setParameter("namespaces", false); // DOMの構築 Document document = parser.parse(input);
この授業のサンプルプログラムでは、名前空間を使わないよう指定されているものが多くなっています。
XPath クラスのインスタンスを、XPathFactory クラスの newXPath() メソッドにより生成します。
// XPath の表現を扱う XPath オブジェクトを生成 XPathFactory factory = XPathFactory.newInstance(); XPath xPath = factory.newXPath();
XPath クラスの evaluate メソッドを使って XPath 式を評価します。 その際、2つめの引数に基準となる位置 (Document, Nodeなど)、 3つめの引数に戻り値の種類 (NODESET, NODEなど) を与えます。
XPath 式にあてはまるノードは一般に複数あるため、戻り値の型は NodeList になります。
// document は DOM ツリーを参照しているものとする NodeList itemNodeList = (NodeList)xPath.evaluate("/rss/channel/item", document, XPathConstants.NODESET); // RSS 2.0
evaluate の 3つめの引数に与えている XPathConstants.NODESET は、 ノードの集合を戻り値として返してね、という指示を表しています。
あてはまるノードが1つしかないことがわかっている場合や、先頭の1つしか必要ない場合には、 ノード1つを返すように指定することもできます。 例えば、ノード node の子ノード title を得るには次のように書くことができます。
// node は item要素のノードを参照しているものとする Node titleNode = (Node)xPath.evaluate("title", node, XPathConstants.NODE); // RSS
XPathConstants.NODE は、ノード1つを戻り値として返してね、という指示を表していて、 戻り値は Node オブジェクトになります。よって Node 型にキャストします。
ノード自身ではなくノードの内容の文字列が欲しい場合には、戻り値として XPathConstants.STRING を指定することができます。 また、その目的専用の、3つめの引数がない evaluate も用意されています。 これは戻り値の型がはじめから String になっているためキャストする必要がありません。
// node は item要素のノードを参照しているものとする String title = xPath.evaluate("title", node); // RSS
例外処理が必要な点に注意しましょう。
import javax.xml.xpath.*; ... /** Feed の内容を Item オブジェクトのリストとして返す */ public ArrayList<Item> getItemList() { ArrayList<Item> itemList = new ArrayList<Item>(); try { // XPath の表現を扱う XPath オブジェクトを生成 XPathFactory factory = XPathFactory.newInstance(); XPath xPath = factory.newXPath(); // document に対して XPath を適用 // XPathConstants.NODESET は戻り値がノードの集合という意味 // RSS 2.0 NodeList itemNodeList = (NodeList)xPath.evaluate("/rss/channel/item", document, XPathConstants.NODESET); // RSS 1.0 if(itemNodeList.getLength() == 0) { itemNodeList = (NodeList)xPath.evaluate("/RDF/item", document, XPathConstants.NODESET); } // 名前空間が有効な DOM tree の場合は以下のように書く // "/*[local-name()='RDF']/*[local-name()='item']" // ノードリストの各 item要素から Item オブジェクトを生成 for(int i = 0; i < itemNodeList.getLength(); i++) { // Node から Item を生成する getItem(Node) があるとする itemList.add(getItem(itemNodeList.item(i))); } } catch (DOMException e) { System.err.println("DOMエラー:" + e); } catch (XPathExpressionException e) { // 追加 System.err.println("XPath 表現のエラー:" + e); } return itemList; }
Node から Item を生成する getItem(Node) が存在すると仮定しています。 new で Item のインスタンスを生成してもよいでしょう。