8. XPath
条件に一致する文書ノードの部分集合を選ぶ場合、実用的な条件であれば、既存の走査機能を使って関数を書くことはできます。ただし、条件が事前に決まっておらずファイルから与えられる場合には、データに基づく方法が望ましいことがよくあります。また、走査インターフェースが使いにくく、より高水準のDSLが必要な場合もあります。XML処理の標準言語XPathは、こうした場合に役立ちます。pugixmlは、XPath 1.0のほぼ完全な部分集合を実装しています。文書オブジェクトモデルの違いや性能面の理由から、公式仕様にわずかな非準拠があります。W3C仕様への準拠を参照してください。この節の残りでは、XPath機能のインターフェースを説明します。XPath言語の使い方を学ぶには、ほかのチュートリアルやマニュアルを参照してください。たとえば、W3SchoolsのXPathチュートリアルやXPath 1.0仕様があります。
8.1. XPathの型
各XPath式の型は、真偽値、数値、文字列、ノード集合のいずれかです。真偽値はbool、数値はdoubleに対応します。文字列は、ワイド文字インターフェースが有効かどうかに応じて、std::stringまたはstd::wstringに対応します。ノード集合はxpath_node_set型に対応します。列挙型xpath_value_typeは、それぞれxpath_type_boolean、xpath_type_number、xpath_type_string、xpath_type_node_setの値を取ります。
XPathノードはノードか属性のどちらかなので、これらの型の判別可能な共用体である専用の型xpath_nodeがあります。この型の値には、xml_node型とxml_attribute型のハンドルが1つずつ含まれ、nullでないものは最大1つです。これらのハンドルを取得するアクセサーがあります。
xml_node xpath_node::node() const;
xml_attribute xpath_node::attribute() const;XPathノードはnullにもできます。その場合、どちらのアクセサーもnullハンドルを返します。
XPath仕様では、各XPathノードには親があり、次の関数で取得できます。
xml_node xpath_node::parent() const;parentは、XPathノードがxml_nodeハンドルに対応する場合には、そのノードの親を返します(node().parent()と同じです)。xml_attributeハンドルに対応する場合には、属性が属するノードを返します。nullノードでは、parentはnullハンドルを返します。
ノードや属性のハンドルと同様に、XPathノードのハンドルは、真偽値のように扱えるオブジェクトへ暗黙に変換でき、nullノードかどうかを確認できます。また、互いに等しいかどうかを比較できます。
XPathノードは、3つのコンストラクターのいずれかでも作成できます。既定のコンストラクター、ノードを引数に取るもの、属性とノードを引数に取るものです。最後の場合、属性は、そのノードの属性リストに属していなければなりません。xml_nodeからのコンストラクターは暗黙的なので、通常、xpath_nodeを受け取る関数にはxml_nodeを渡せます。それ以外では、選択関数からXPathノードが返されるため、通常、自分でXPathノードオブジェクトを作成する必要はありません。
XPath式は、単一のノードではなく、ノード集合に対して操作します。ノード集合はノードの集まりで、必要に応じて文書順かその逆順に並べられます。文書順はXPath仕様で定義されています。対応する文書のXML表現で、あるXPathノードが別のノードより前に現れる場合、文書順でも前になります。
ノード集合は、列として扱うランダムアクセスコンテナーに似たインターフェースを持つxpath_node_setオブジェクトで表されます。イテレーター型と、通常の先頭・末尾の次を指すイテレーターのアクセサーがあります。
typedef const xpath_node* xpath_node_set::const_iterator;
const_iterator xpath_node_set::begin() const;
const_iterator xpath_node_set::end() const; std::vectorと同様に、インデックスでも反復処理できます。
const xpath_node& xpath_node_set::operator[](size_t index) const;
size_t xpath_node_set::size() const;
bool xpath_node_set::empty() const;上記の操作の意味は、std::vectorと同じです。イテレーターはランダムアクセスで、上記の操作はすべて定数時間です。集合のサイズ以上のインデックスで要素へアクセスした場合の動作は未定義です。反復処理にはイテレーターとインデックスのどちらも使えますが、イテレーターの方が高速な場合があります。
反復処理の順序は集合内のノードの順序に依存し、次の関数で確認できます。
enum xpath_node_set::type_t {type_unsorted, type_sorted, type_sorted_reverse};
type_t xpath_node_set::type() const;typeは現在のノードの順序を返します。type_sortedは文書順、type_sorted_reverseは逆文書順、type_unsortedはどちらの順序も保証されないことを意味します。type()がtype_unsortedを返しても、偶然整列している場合はあります。特定の順序で反復処理したい場合は、sortで変更できます。
void xpath_node_set::sort(bool reverse = false);sortを呼び出すと、引数に応じて、文書順か逆文書順にノードを並べ替えます。その後は、type()がtype_sortedまたはtype_sorted_reverseを返します。
実際に反復処理する必要がなく、文書順で最初の要素だけが必要な場合もよくあります。そのための専用のアクセサーがあります。
xpath_node xpath_node_set::first() const;この関数は、集合から文書順で最初のノードを返します。集合が空なら、nullノードを返します。返すノードは集合内の順序(type()の結果)に依存しませんが、計算量は依存します。集合が整列済みなら定数時間、それ以外なら要素数に対して線形時間か、それ以上になります。
多くの場合、ノード集合はXPath関数から返されますが、手動で作成する必要がある場合もあります。そのため、イテレーター範囲と、省略可能な型を取るコンストラクターがあります。const_iteratorはconst xpath_node*のtypedefです。
xpath_node_set::xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted);コンストラクターは指定した範囲をコピーし、指定した型を設定します。範囲内のオブジェクトは一切検査しません。重複がなく、typeパラメーターに従って整列していることを、自分で保証する必要があります。そうしないと、この集合を使うXPath操作は予想外の結果になる場合があります。
8.2. XPath式によるノードの選択
XPath式に一致するノードを選ぶには、次の関数を使えます。
xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;select_nodesは式をコンパイルし、ノードをコンテキストノードとして実行して、結果のノード集合を返します。select_nodeは、結果のうち文書順で最初のノードだけを返し、select_nodes(query).first()と同じです。XPath式に一致するものがない場合や、ノードハンドルがnullの場合、select_nodesは空の集合を返し、select_nodeはnullのXPathノードを返します。
例外処理が無効になっていなければ、問い合わせをコンパイルできない場合や、結果の型がノード集合以外の場合、どちらの関数もxpath_exceptionを送出します。詳細はエラー処理を参照してください。
式のコンパイルは高速ですが、同じ式を小さい部分ツリーに対して何度も使う場合、コンパイル時間が大きなオーバーヘッドになることがあります。似た問い合わせを何度も行う場合は、問い合わせオブジェクトへコンパイルすることを検討してください(問い合わせオブジェクトの使用を参照)。コンパイル済みの問い合わせオブジェクトがあれば、式の文字列の代わりに選択関数へ渡せます。
xpath_node xml_node::select_node(const xpath_query& query) const;
xpath_node_set xml_node::select_nodes(const xpath_query& query) const;例外処理が無効になっていなければ、問い合わせの結果の型がノード集合以外の場合、どちらの関数もxpath_exceptionを送出します。
XPath式でノードを選ぶ例です(samples/xpath_select.cpp)。
pugi::xpath_node_set tools = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote='true' and @DeriveCaptionFrom='lastparam']");
std::cout << "Tools:\n";
for (pugi::xpath_node_set::const_iterator it = tools.begin(); it != tools.end(); ++it)
{
pugi::xpath_node node = *it;
std::cout << node.node().attribute("Filename").value() << "\n";
}
pugi::xpath_node build_tool = doc.select_node("//Tool[contains(Description, 'build system')]");
if (build_tool)
std::cout << "Build tool: " << build_tool.node().attribute("Filename").value() << "\n";
8.3. 問い合わせオブジェクトの使用
式の文字列を引数としてselect_nodesを呼ぶと、内部で問い合わせオブジェクトが作成されます。問い合わせオブジェクトは、コンパイル済みのXPath式を表します。次のような場合に必要になることがあります。
-
コンパイル時間が問題になる場合は、式をあらかじめ問い合わせオブジェクトへコンパイルして、その時間を節約できます。
-
真偽値、数値、文字列を結果として返すXPath式を、問い合わせオブジェクトで評価できます。
-
問い合わせオブジェクトを通じて、式の値の型を取得できます。
問い合わせオブジェクトはxpath_query型に対応します。不変で、コピーできません。作成時に式へ結び付けられ、複製できません。コンテナーに格納したい場合は、new演算子でヒープ上に確保し、xpath_queryへのポインターをコンテナーへ格納するか、C++11コンパイラーを使用してください(問い合わせオブジェクトはC++11でムーブ可能です)。
XPath式を引数に取るコンストラクターで、問い合わせオブジェクトを作成できます。
explicit xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables = 0);式がコンパイルされ、そのコンパイル済みの表現が新しい問い合わせオブジェクトに格納されます。コンパイルに失敗し、例外処理が無効になっていなければ、xpath_exceptionを送出します(詳細はエラー処理を参照)。作成後は、次の関数で評価結果の型を問い合わせることができます。
xpath_value_type xpath_query::return_type() const;次のいずれかの関数で問い合わせを評価できます。
bool xpath_query::evaluate_boolean(const xpath_node& n) const;
double xpath_query::evaluate_number(const xpath_node& n) const;
string_t xpath_query::evaluate_string(const xpath_node& n) const;
xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const;
xpath_node xpath_query::evaluate_node(const xpath_node& n) const;すべての関数はコンテキストノードを引数に取り、式を計算し、指定した型へ変換した結果を返します。XPath仕様によれば、どの型の値も真偽値、数値、文字列へ変換できますが、ノード集合以外の型はノード集合へ変換できません。そのため、evaluate_boolean、evaluate_number、evaluate_stringは常に結果を返しますが、戻り値の型がノード集合以外の場合、evaluate_node_setとevaluate_nodeはエラーになります(エラー処理を参照)。
注記
|
node.select_nodes("query")の呼び出しは、xpath_query("query").evaluate_node_set(node)の呼び出しと同じです。node.select_node("query")の呼び出しは、xpath_query("query").evaluate_node(node)の呼び出しと同じです。 |
evaluate_string関数はSTLの文字列を返すため、PUGIXML_NO_STLモードでは使えず、通常はメモリーも確保することに注意してください。別の文字列評価関数もあります。
size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const;この関数は文字列を評価して、結果をbufferへ書き込みます(ただし、最大capacity文字まで)。その後、終端のゼロを含めた結果全体の文字数を返します。capacityが0でなければ、出力バッファーは必ずゼロで終端されます。次のように使用できます。
-
最初に
buffer = 0、capacity = 0で呼び出します。次に、返された文字数分の領域を確保し、確保した領域と文字数を渡して、もう一度呼び出します。 -
最初に小さいバッファーとその容量を渡して呼び出します。結果が容量より大きければ、出力は切り詰められています。その場合は、より大きいバッファーを確保して、もう一度呼び出します。
問い合わせオブジェクトを使う例です(samples/xpath_query.cpp)。
// Select nodes via compiled query
pugi::xpath_query query_remote_tools("/Profile/Tools/Tool[@AllowRemote='true']");
pugi::xpath_node_set tools = query_remote_tools.evaluate_node_set(doc);
std::cout << "Remote tool: ";
tools[2].node().print(std::cout);
// Evaluate numbers via compiled query
pugi::xpath_query query_timeouts("sum(//Tool/@Timeout)");
std::cout << query_timeouts.evaluate_number(doc) << std::endl;
// Evaluate strings via compiled query for different context nodes
pugi::xpath_query query_name_valid("string-length(substring-before(@Filename, '_')) > 0 and @OutputFileMasks");
pugi::xpath_query query_name("concat(substring-before(@Filename, '_'), ' produces ', @OutputFileMasks)");
for (pugi::xml_node tool = doc.first_element_by_path("Profile/Tools/Tool"); tool; tool = tool.next_sibling())
{
std::string s = query_name.evaluate_string(tool);
if (query_name_valid.evaluate_boolean(tool)) std::cout << s << std::endl;
}
8.4. 変数の使用
XPathの問い合わせには、変数への参照を含めることができます。動的なパラメーターに依存する問い合わせを、完全な問い合わせ文字列を手動で組み立てずに使いたい場合や、似た問い合わせに同じ問い合わせオブジェクトを再利用したい場合に役立ちます。
変数への参照は$nameという形式です。使うには、問い合わせに現れるすべての変数を正しい型で含む変数集合を用意する必要があります。この集合を、xpath_queryのコンストラクターか、select_nodes/select_node関数へ渡します。
explicit xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables = 0);
xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;問い合わせオブジェクトを使う場合、evaluate/selectの呼び出し前に変数の値を変更して、問い合わせの動作を変えられます。
注記
|
変数集合へのポインターと、参照している変数へのポインターは、問い合わせオブジェクトに格納されます。集合の寿命が問い合わせオブジェクトの寿命より長いことを保証する必要があります。また、問い合わせが生存している間は、集合を代入やムーブ代入の対象にしてはいけません。 |
変数集合はxpath_variable_set型に対応し、基本的には変数のコンテナーです。
次の関数で、新しい変数を追加できます。
xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type);この関数は、指定した名前と型の新しい変数を追加しようとします。その名前の変数が集合に存在しなければ、新しい変数を追加し、そのハンドルを返します。同じ名前の変数がすでにあり、指定した型と一致する場合は、その変数のハンドルを返します。それ以外の場合はnullポインターを返します。メモリーの確保に失敗した場合も、nullポインターを返します。
新しい変数には、型に応じた既定値が代入されます。数値は0、真偽値はfalse、文字列は空文字列、ノード集合は空の集合です。
次の関数で、既存の変数を取得できます。
xpath_variable* xpath_variable_set::get(const char_t* name);
const xpath_variable* xpath_variable_set::get(const char_t* name) const;これらの関数は変数のハンドルを返します。指定した名前の変数が見つからない場合は、nullポインターを返します。
さらに、名前を指定して変数の値を設定する補助関数もあります。変数が存在しなければ、対応する型の変数を追加し、その値を設定しようとします。同じ名前で型が異なる変数がすでにある場合は、falseを返します。メモリーの確保に失敗した場合も、falseを返します。型の変換は一切行わないことに注意してください。
bool xpath_variable_set::set(const char_t* name, bool value);
bool xpath_variable_set::set(const char_t* name, double value);
bool xpath_variable_set::set(const char_t* name, const char_t* value);
bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value);変数の値は内部の変数格納領域へコピーされるため、関数が返った後は、渡した値を変更したり破棄したりできます。
名前による変数の設定では効率が不十分な場合や、変数の情報を調べたり値を取得したりする必要がある場合は、変数のハンドルを使えます。変数はxpath_variable型に対応し、そのハンドルは単にxpath_variableへのポインターです。
次のいずれかの関数で、変数の情報を取得できます。
const char_t* xpath_variable::name() const;
xpath_value_type xpath_variable::type() const;各変数には、作成時に指定する固有の型があり、後から変更できないことに注意してください。
変数の値を取得するには、その型に応じて次のいずれかの関数を使います。
bool xpath_variable::get_boolean() const;
double xpath_variable::get_number() const;
const char_t* xpath_variable::get_string() const;
const xpath_node_set& xpath_variable::get_node_set() const;これらの関数は、変数の値を返します。型の変換は一切行いません。型が一致しない場合は、代替の値を返します(真偽値はfalse、数値はNaN、文字列は空文字列、ノード集合は空の集合)。
変数の値を設定するには、その型に応じて次のいずれかの関数を使います。
bool xpath_variable::set(bool value);
bool xpath_variable::set(double value);
bool xpath_variable::set(const char_t* value);
bool xpath_variable::set(const xpath_node_set& value);これらの関数は変数の値を変更します。型の変換は一切行いません。型が一致しない場合は、falseを返します。メモリーの確保に失敗した場合も、falseを返します。値は内部の変数格納領域へコピーされるため、関数が返った後は、渡した値を変更したり破棄したりできます。
XPathの問い合わせで変数を使う例です(samples/xpath_variables.cpp)。
// Select nodes via compiled query
pugi::xpath_variable_set vars;
vars.add("remote", pugi::xpath_type_boolean);
pugi::xpath_query query_remote_tools("/Profile/Tools/Tool[@AllowRemote = string($remote)]", &vars);
vars.set("remote", true);
pugi::xpath_node_set tools_remote = query_remote_tools.evaluate_node_set(doc);
vars.set("remote", false);
pugi::xpath_node_set tools_local = query_remote_tools.evaluate_node_set(doc);
std::cout << "Remote tool: ";
tools_remote[2].node().print(std::cout);
std::cout << "Local tool: ";
tools_local[0].node().print(std::cout);
// You can pass the context directly to select_nodes/select_node
pugi::xpath_node_set tools_local_imm = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote = string($remote)]", &vars);
std::cout << "Local tool imm: ";
tools_local_imm[0].node().print(std::cout);
8.5. エラー処理
XPathの実装には、2種類のエラー処理の仕組みがあります。使われる仕組みは、例外対応が無効になっているかどうかで決まります(PUGIXML_NO_EXCEPTIONSの定義で制御します)。
既定では、XPath関数はエラー時にxpath_exceptionオブジェクトを送出します。さらに、メモリーの確保に失敗すると、std::bad_alloc例外を送出します。また、問い合わせの戻り値の型がノード集合以外なのに、ノード集合として評価した場合も、xpath_exceptionを送出します。問い合わせのコンストラクターが成功した場合(つまり例外を送出しなかった場合)、問い合わせオブジェクトは有効です。それ以外の場合は、次のいずれかの関数でエラーの詳細を取得できます。
virtual const char* xpath_exception::what() const noexcept;
const xpath_parse_result& xpath_exception::result() const; 例外が無効の場合、解析に失敗すると、問い合わせは無効な状態で初期化されます。if (query) { … }のように真偽値の式で使うと、問い合わせオブジェクトが有効かどうかを確認できます。また、result()アクセサーで解析結果を取得できます。
const xpath_parse_result& xpath_query::result() const;例外を使わない場合、無効な問い合わせの評価結果は、型に応じてfalse、空文字列、NaN、空のノード集合になります。戻り値の型がノード集合以外の問い合わせをノード集合として評価すると、空のノード集合になります。
解析結果の情報は、xpath_parse_resultオブジェクトで返されます。解析の状態と、入力ストリームの先頭から、最後に解析に成功した文字までのオフセットを含みます。
struct xpath_parse_result
{
const char* error;
ptrdiff_t offset;
operator bool() const;
const char* description() const;
};解析結果はエラーメッセージで表されます。エラーがなければnullポインター、エラーがあればASCIIのゼロ終端文字列によるエラーメッセージです。
description()メンバー関数で、エラーメッセージを取得できます。nullポインターは決して返さないため、問い合わせの解析が成功した場合にも、安全にdescription()を使えます。description()はPUGIXML_WCHAR_MODEでもchar文字列を返すことに注意してください。wchar_t文字列を取得するには、as_wideを呼ぶ必要があります。
解析結果には、エラーメッセージに加えて、最後に解析に成功した文字のオフセットを含むoffsetメンバーがあります。単位はpugi::char_tです(文字モードではバイト、ワイド文字モードではワイド文字)。
解析結果のオブジェクトは、if (result) { … } else { … }のように、暗黙にboolへ変換できます。
XPathのエラー処理の例です(samples/xpath_error.cpp)。
// Exception is thrown for incorrect query syntax
try
{
doc.select_nodes("//nodes[#true()]");
}
catch (const pugi::xpath_exception& e)
{
std::cout << "Select failed: " << e.what() << std::endl;
}
// Exception is thrown for incorrect query semantics
try
{
doc.select_nodes("(123)/next");
}
catch (const pugi::xpath_exception& e)
{
std::cout << "Select failed: " << e.what() << std::endl;
}
// Exception is thrown for query with incorrect return type
try
{
doc.select_nodes("123");
}
catch (const pugi::xpath_exception& e)
{
std::cout << "Select failed: " << e.what() << std::endl;
}
8.6. W3C仕様への準拠
文書オブジェクトモデルの違い、性能上の配慮、実装の複雑さにより、pugixmlのXPath 1.0実装は完全準拠ではありません。現在の非互換事項は次のとおりです。
-
同じ親を持つ連続したテキストノードは結合されません。たとえば、
<node>text1 <![CDATA[data]]> text2</node>では、本来nodeのテキストノードの子は1つになるべきですが、実際には3つになります。 -
文書型宣言を解析に使わないため、
id()関数は常に空のノード集合を返します。 -
名前空間ノードには対応していません(
namespace::軸に影響します)。 -
名前の検査には、展開名ではなくXML文書内のQNameを使います。
<foo xmlns:ns1='uri' xmlns:ns2='uri'><ns1:child/><ns2:child/></foo>に対して、foo/ns1:*という問い合わせは、両方ではなく最初の子だけを返します。準拠したXPath実装では、適切な名前空間宣言を利用者が用意すると、両方のノードを返せます。 -
文字列関数は、ライブラリーの設定に応じて、単一の
char値か単一のwchar_t値を1文字として扱います。このため、一部の文字列関数はUnicodeに完全には対応していません。substring()、string-length()、translate()関数に影響します。