タイトルのとおり(ぇ
某所のOpenSerach APIを叩いてatomで結果を得たと仮定。 レスポンス中の、
<feed xmlns="[URL]" xmlns:opensearch="[URL]"> (略) <opensearch:totalResults>21</opensearch:totalResults> (略) </feed>
この要素を取り出したいとする。
doc = Nokogiri::XML(atom) doc/'openserch:totalResults' #=> []
むむう。
原因は以下の2点
Nokogiriは(バックエンドであるlibxmlの機能により)名前空間を結構真面目に扱う。
Nokogiri::XML::Node#at, #search, #xpath では、引数の最後に名前空間情報のHashを取る。
see also [URL]
doc.at('.//opensearch:totalResults', {'opensearch' => '[URL]}).content #=> "21"
キーが名前空間のprefix、値がスキーマのURI、これはxml中で指定されるものと一致しなくてはならない。 逆にprefixの方は引数の間で整合性が取れていれば、xml中の記述と一致する必要は無い。 (そんなことをする必要があるとも思えないが……)
doc.at('.//os:totalResults', {'os' => '[URL]}).content #=> "21"
引数の最初の'.//'はxpathであることを明示するための指定
厳密には、Nokogiri::XML::Node#at, #search では、引数が'./'または'/'で始まる場合、引数はxpathだと見なす。 './/'なのは子ノードも検索対象にするため。
とりあえず、最初の目的は達成されたのだが……。
'.//' だとどこか他に opensearch:totalResults と同名の要素が存在した場合に曖昧になるので、pathをきっちり書きたいと思うと、 もうひとつ面倒に直面することなる。
doc.at('./feed/opensearch:totalResults', {'opensearch' => '[URL]}) #=> nil
見付からない……。
実は名前空間を指定するHashを省略した場合は、内部で document.root.namespace が補完されているのだが、引数で名前空間を明示された場合は引数のHashにマージしてくれたりはしないのだ。 そのため、rootの名前空間も明示的に指定する必要がある。
doc.at('./atom:feed/opensearch:totalResults', {'atom' => '[URL] 'opensearch' => '[URL]}).content #=> "21"
これが正解。
後でパッチでも書こう。
セコメントをする