このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
XPath は XML 中の特定の要素を指し示すときに使う表現です。 一つだけではなく集合も扱え、さらに関数も使えます。 いわば XML 文書用のデータベース検索です。
なお、 XPath の最新バージョンは XPath 2.0 ですが、 Java 6 で対応してい るのは XPath 1.0 ですので、以下は XPath 1.0 を解説します。 また、 XQuery 1.0 は XPath2.0 の拡張になっています。 一方、 XPointer は XML 文書の特定の位置を指すための規格ですが、このう ち XSL との共通部分が XPath になっています。
次のような XML 文書を考えます。
<?xml version="1.0" encoding="Shift_JIS" standalone="no" ?> <class name="データ構造とアルゴリズム II" > <teacher name="坂本直志" /> <time day="tue" period="2" /> </class>
これに対して、「/class/teacher
」という XPath は class 要
素に含まれている teacher 要素を指すことになります。
但し、これは該当するもの全てを指すので、
以下の XML だと二つの teacher 要素を指すことになります。
<?xml version="1.0" encoding="Shift_JIS" standalone="no" ?> <class name="情報通信基礎実験" > <teacher name="金田先生" /> <teacher name="坂本直志" /> <time day="mon" period="3" /> <time day="mon" period="4" /> </class>
Java 6 では XPath 1.0 に対応しています。 javax.xml.xpath パッケージで抽象化していますので、他の API 同様、ビル ダを作成したのち、 XPath オブジェクトを作ります。 XPath オブジェクトの evaluate メソッドで XPath の解釈をします。 evaluate の引数は 3 つあります。
ここで、 XPathConstants の定数とは以下のものです。
型 | 実際に受け取る型 |
---|---|
BOOLEAN | java.lang.Boolean |
NODE | org.w3c.dom.Node |
NODESET | org.w3c.dom.NodeList |
NUMBER | java.lang.Double |
STRING | java.lang.String |
XPath は該当する要素の集まりを指すことが多いですから、通常は XPathConstants.NODESET を使用することになります。 このとき、 evaluate の戻り値は Object 型なので、戻り値にダウンキャスト する必要があります。 つまり xpath を javax.xml.xpath.XPath オブジェクト、 is を org.xml.sax.InputSource オブジェクトとすると、 典型的な問い合わせは次のようになります。
NodeList nodes = (NodeList) xpath.evaluate(
xpathの表現,is, XPathConstants.NODESET);
下記は XML 文書を標準入力で受け取り、 /class/teacher で得られる NodeList の各 Element に対して、 name 属性の値を表示しています。
import javax.xml.xpath.*;
import org.xml.sax.*;
import org.w3c.dom.*;
class Rei {
private static XPath getXPath(){
final XPathFactory factory = XPathFactory.newInstance();
return factory.newXPath();
}
public static void main(String[] args) throws Exception {
final InputSource is = new InputSource(new FileInputStream("sample.xml"));
final XPath xpath = getXPath();
final NodeList nodes = (NodeList) xpath.evaluate(
"/class/teacher",is, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(((Element)nodes.item(i)).getAttribute("name"));
}
}
}
さらに、InputSource オブジェクトの代わりに、 DOM のオブジェクトを与え ることもできます。 一方、同じ XPath の表現を何度も使う場合は、java.util.regex.Pattern 同 様にパターンを与えてコンパイルすることで高速化することもできます。 この場合 compile メソッドの戻り値の型は XPathExpression 型になります。
下記は入力として DOM を与え、XPath の表現をコンパイルしてから結果を得 ています。
import java.io.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
class Rei {
private static Document getDocument(InputStream is) throws Exception {
final DocumentBuilderFactory dbf
= DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(is);
}
private static XPath getXPath(){
final XPathFactory factory = XPathFactory.newInstance();
return factory.newXPath();
}
public static void main(String[] args) throws Exception {
final Document doc = getDocument(new FileInputStream("sample.xml"));
final XPath xpath = getXPath();
final XPathExpression expr = xpath.compile("/class/teacher");
final NodeList nodes = (NodeList) expr.evaluate(doc,
XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(((Element)nodes.item(i)).getAttribute("name"));
}
}
}
それでは XPath の表現について見ていきましょう。
XPath は一つの式であり、通常の計算も可能です。
XPath に 1+1 を計算させるくだらない例
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
class Rei {
private static Document getNewDocument() throws Exception {
final DocumentBuilderFactory dbf
= DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
return db.newDocument();
}
private static XPath getXPath(){
final XPathFactory factory = XPathFactory.newInstance();
return factory.newXPath();
}
public static void main(String[] args) throws Exception {
final XPath xpath = getXPath();
final Double x = (Double) xpath.evaluate(
"1+1",getNewDocument(), XPathConstants.NUMBER);
System.out.println(x);
}
}
式として返す値は既に示したように、NODESET, BOOLEAN, NUMBER, STRING のどれ かです。 これは、あらかじめ確認し、 evaluate の呼び出し時に指定しなければ なりません。
さて、式のうち、もっとも重要な要素がロケーションパスと呼ば れる、 XML の NODESET を取り出す構文です。 これには様々な機能が用意されていて、豊富な指定方法があります。 そのため、良く使う表現については省略文法という記法が用意さ れています。 しかし、まず、省略なしの文法を見てから、よく使う省略文法を見ることにします。
ロケーションパスは次の構文からなります。
[1] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath [2] AbsoluteLocationPath ::= '/' RelativeLocationPath ? | 省略型絶対ロケーション記法 [3] RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | 省略型相対ロケーション記法
これは、つまり、 Step が /(スラッシュ) で区切られて、複数回現れるとい う構造です。 次に Step ですが、次のようになっています。
[4] Step ::= AxisSpecifire NodeTest Predicate* | 省略型Step [5] AxisSpecifire ::= AxisName '::' | 省略型Step識別子
つまり、 Step は 「AxisName::NodeTest」という形の後に 0 個以上の Predicate が続くという書式になります。 それでは、 Axis(軸), NodeTest, Predicate(述語) について説明します。
Axis とは選択するノードの関係を指定するものです。 AxisName には次のものがあります。
AxisName | 意味 |
---|---|
child | ノードの子のノード |
descendant | ノードの子や孫などの子孫全てのノード |
parent | ノードの親 |
ancestor | ノードの親、親の親など全ての先祖 |
following-sibling | 後続の兄弟ノード |
preceding-sibling | 先方の兄弟ノード |
following | 子孫を除いた以降のノード |
preceding | 祖先を除いた以前のノード |
attribute | 属性を返す |
namespace | 名前空間ノード |
self | 自分自身 |
descendant-or-self | 自分自身と子孫のノード |
ancestor-or-self | 自分自身と祖先のノード |
なお、上記において、 attribute は Attr、 namespace は 名前空間の型を返 しますが、それ以外は Element またはそのリストを返します。
なお、この選択とは、木構造で言う所の部分木を取得するものです。 そのため、子要素が孫要素を含んでいる場合、子要素を選択すると、各子要素 ごとに孫要素を子要素と含むように取り出されます。
NodeTest は、要素の名前などを記述します。
さらに *(アスタリスク) を書くことで、全ての名前とマッチさせることがで
きます。
例えば、 child::*
は、対象となっているノードに対して子の
要素を全て選択することになります。
一方、「attribute::*」は注目している要素の属性を全て選択することになり
ます。
さらに、この Self を /(スラッシュ) で連続させるということは、その選択 したノードに対して、さらに選択をします。 なお、 XML 文書に対して、木構造の根は空で、その子ノードが根の要素ノー ドになります。
以下の XML 文書に対して XPath とその値を示します。
<?xml version="1.0" encoding="Shift_JIS" standalone="no" ?> <class name="データ構造とアルゴリズム II" > <teacher name="坂本直志" /> <time day="tue" period="2" /> </class>
XPath | 値 |
---|---|
/self::* | 空 |
/child::* |
<class name="データ構造とアルゴリズム II" > <teacher name="坂本直志" /> <time day="tue" period="2" /> </class> |
/child::*/child::* |
<teacher name="坂本直志" />, <time day="tue" period="2" /> |
/child::class/child::teacher |
<teacher name="坂本直志" /> |
/descendant::* |
<class name="データ構造とアルゴリズム II" > <teacher name="坂本直志" /> <time day="tue" period="2" /> </class>, <teacher name="坂本直志" />, <time day="tue" period="2" /> |
さらに NodeTest はこの他に、comment(), text(), processing-instruction(), node()という表現が可能です。
なお、上記の XML 文書に対して、/child::class/child::*
と /child::class/child::node()
は同じになりません。
後者は node() として Element 以外にもマッチするので、 teacher 要素、
time 要素のタグの隙間にある改行コードをテキストノードとして認識します。
述語は [](角カッコ) の中に式を書きます。 式の内容が BOOLEAN である場合は、対象となるノードに対して true のもの だけが選択されます。 一方式の内容が NUMBER だった場合は、対象となるノードのポジション番号が 一致しているものだけが選択されます。 ここでポジション番号とは対象となるノードを、先頭から 1, 2, ... と番号 をつけた値です。
XPath | 値 |
---|---|
/child::*/child::*[0=0] |
<teacher name="坂本直志" />, <time day="tue" period="2" /> |
/child::*/child::*[0=1] | 空 |
/child::*/child::*[2] |
<time day="tue" period="2" /> |
関数呼び出しはそのまま式になります。 関数には次のものがあります。
戻り値の型 | 関数 | 意味 |
---|---|---|
NUMBER | last() | 対象となるノードのサイズ(つまり最後の要素の番号)を返す |
NUMBER | position() | 対象となるノードの位置の番号を返す |
NUMBER | count(NODESET) | node-set のノード数を返す |
NODESET | id(object) | 引数が STRING なら、その文字列を ID として持つ NODESET を返す |
STRING | local-name(node-set?) | node-set あるいは無ければ対象ノードの展開名のローカル部分を返す |
STRING | name(node-set?) | node-set あるいは無ければ対象ノードの展開名を返す |
STRING | namespace-uri(node-set?) | node-set あるいは無ければ対象ノードの展開名の名前空間URIを返す |
戻り値の型 | 関数 | 意味 |
---|---|---|
STRING | string(object?) | 対象となるオブジェクトあるいは無ければ対象ノードを文字列に変換する |
STRING | concat(STRING,STRING,STRING*) | 引数を結合する |
BOOLEAN | starts-with(STRING,STRING) | 一つ目の引数が二つ目の引数で始まるとき true |
BOOLEAN | contains(STRING,STRING) | 一つ目の引数が二つ目の引数を含んでいるとき true |
STRING | substring-before(STRING,STRING) | 一つ目の引数の中で二つ目の引数が始まる直前までの文字列を返す。 substring-before("1999/04/01","/") は 1999 を返す |
STRING | substring-after(STRING,STRING) | 一つ目の引数の中で二つ目の引数の最初の出現以降の文字列を返す。 substring-after("1999/04/01","/") は 04/01 を返す |
STRING | substring(STRING,NUMBER,NUMBER?) | 一つ目の引数の文字列の二つ目の引数から始まり、あれば三番目の引数の 長さの、無ければ最後までの部分列を返す。 substring("12345",1.5,2.6) は 234 を返す |
NUMBER | string-length(STRING?) | 引数あるいは無ければ対象ノードの文字列値の長さを返す |
STRING | normalize-space(STRING?) | 引数あるいは無ければ対象ノードの文字列中の空白列を全て一つの空白に した文字列を返す |
STRING | translate(STRING,STRING,STRING) | 一つ目の引数の文字のうち、二つ目の引数中の文字がある場合、二つ目の 引数中の文字の位置と同じ位置にある三つ目の引数の文字に交換した文字列 を返す。 translate("bar","abc","ABC") は BAr を返す |
戻り値の型 | 関数 | 意味 |
---|---|---|
BOOLEAN | boolean(object) | オブジェクトをブール値に変換する。数値の場合は NaN と正負の 0 以外 は true |
BOOLEAN | not(BOOLEAN) | 真偽を反転させた値を返す |
BOOLEAN | true() | true を返す |
BOOLEAN | false() | false を返す |
BOOLEAN | lang(STRING) | 引数の示す言語が xml:lang の示す言語を含む時 true を返す |
戻り値の型 | 関数 | 意味 |
---|---|---|
NUMBER | number(object) | オブジェクトを数値に変換する。NODESETでは一旦文字列に変換された 後、数値に変換される |
NUMBER | sum(NODESET) | node-set の各ノードについて、数値に変換した後合計の値を求め、返す |
NUMBER | floor(NUMBER) | 引数よりも大きくない最大の整数を返す |
NUMBER | celing(NUMBER) | 引数よりも小さくない最小の整数を返す |
NUMBER | round(NUMBER) | 引数にもっとも近い整数を返す |
BOOLEAN は java.lang.Boolean に結びつけられ、論理値を表します。 演算子には or と and があります。=, !=, <, <=, >, >= があ ります。
NUMBER は java.lang.Double と結びつけられ、浮動小数点型を意味します。 これには通常の数の他、正負の無限大と、NaN と呼ばれる非数値も含みます。 演算子には、 +, -, *, div, mod があります。 div は割り算、 mod は Java の % と同じ余りを求める整数演算です。
なお、符号を反転させるには -(マイナス) 記号を使いますが、 XML の名前に も -(マイナス)記号を許してます。 そのため、名前に対して符号を反転するには、空白を入れて名前の一部でない ことを示す必要があります。
XML の仕様により、文字列は UNICODE の抽象キャラクタにより構成される。 これは UTF-16 とは異なる。
|(縦棒) 演算子はノードセットを合成します。
省略構文は以下の表現になります。 基本的に child:: は省略可能です。
省略構文 | 等価な非省略構文 |
---|---|
// | /descendant-self::node() |
名前 | child::名前 |
. | self::node() |
.. | parent::node() |
@value | attribute::value |
以下の XML 文書に対して XPath とその値を示します。
<?xml version="1.0" encoding="Shift_JIS" standalone="no"?> <itemlist> <item> <data name="品名" value="りんご"/> <data name="単価" value="200"/> <data name="個数" value="3"/> <data name="合計" value="600"/> </item> <item> <data name="品名" value="みかん"/> <data name="単価" value="100"/> <data name="個数" value="5"/> <data name="合計" value="500"/> </item> <item> <data name="品名" value="もも"/> <data name="単価" value="300"/> <data name="個数" value="1"/> <data name="合計" value="301"/> </item> </itemlist>
XPath | 意味 | 値 |
---|---|---|
/*/*[2] | 2階層目の要素のうち、 2 番目のもの |
<item> <data name="品名" value="みかん"/> <data name="単価" value="100"/> <data name="個数" value="5"/> <data name="合計" value="500"/> </item> |
/*/*/*[2] | 全ての3階層目の 2 番目要素 |
<data name="単価" value="200"/>, <data name="単価" value="100"/>, <data name="単価" value="300"/> |
//data[@name='品名'] | data 要素のうち name 属性が '品名' と等しいもの |
<data name="品名" value="りんご"/>, <data name="品名" value="みかん"/>, <data name="品名" value="もも"/> |
//data[@name='品名']/@value | data 要素のうち name 属性が '品名' の value 属性の値 | 「attribute の value='りんご' のもの」, 「attribute の value='みかん' のもの」, 「attribute の value='もも' のもの」 |
//data[@name='品名' and @value='りんご'] | data 要素のうち name 属性が '品名' で value 属性が 'りんご' のもの |
<data name="品名" value="りんご"/> |
//data[@name='品名' and @value='りんご']/../data[@name='単価'] | <data name='品名' value='りんご'/> と兄弟ノードのうち name属 性が '単価' の data 要素 |
<data name="単価" value="200"/> |
//item[data[@name='単価']/@value * data[@name='個数']/@value != data[@name='合計']/@value] | 各 item 要素のうち、 name="単価"を含む data 要素の value 属性の値と name="個数"を含む data 要素の value 属性の値との積が、 name="合計"を含む data 要素の value 属性の値と異なるもの |
<item> <data name="品名" value="もも"/> <data name="単価" value="300"/> <data name="個数" value="1"/> <data name="合計" value="301"/> </item> |
sum(//data[@name='合計']/@value) | 各 data 要素のうち、name="合計" 属性をもつものの value 属性の値の 和を求める | 1401.0 |
itemlist.xml という XML 文書に対し、引数を XPath として解釈し、結果を XML で出力するプログラムを示します。
実行方法はjava Dentaku XPath式
です。
なお、XPath式が空白を含んでいる場合は、XPath式を"(ダブルクォーテーショ
ンマーク)で括って下さい。
import javax.xml.xpath.*;
import org.xml.sax.*;
import org.w3c.dom.*;
import java.io.*;
import java.util.*;
import javax.xml.namespace.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
class Dentaku {
private static final XPath xpath = XPathFactory.newInstance().newXPath();
private static XPath getXPath(){
return xpath;
}
private static Transformer transformer = null;
private static Transformer getTransformer() throws Exception {
if(transformer == null){
transformer = TransformerFactory.newInstance().newTransformer();
final Properties prop = new Properties();
prop.setProperty("encoding", "Shift_JIS");
prop.setProperty("indent", "yes");
transformer.setOutputProperties(prop);
}
return transformer;
}
private final static StreamResult streamResult = new StreamResult(System.out);
private static StreamResult getStreamResult() throws Exception {
return streamResult;
}
private static void printNode(Node n) throws Exception {
if(n.getNodeType() == Node.ATTRIBUTE_NODE){
System.out.println("Attr "+n.getNodeName()+": "+n.getNodeValue());
}else{
getTransformer().transform(new DOMSource(n),getStreamResult());
System.out.println();
}
}
private static void evalXPath(String formula,
InputSource is,
QName returnType) throws Exception {
if(returnType==XPathConstants.NODESET){
final NodeList nodes =
(NodeList) getXPath().evaluate(formula,is,returnType);
for (int i = 0; i < nodes.getLength(); i++) {
printNode(nodes.item(i));
}
}else{
System.out.println(
getXPath().evaluate(formula,is,returnType));
}
}
public static void main(String[] args) throws Exception {
evalXPath(args[0],
new InputSource(new FileInputStream("itemlist.xml")),
XPathConstants.NODESET);
}
}