読者です 読者をやめる 読者になる 読者になる

本当は怖い情報科学

情報系大学院生の趣味&実益ブログ。

libxml-rubyでのXPathと名前空間の扱い - 結論:今日がダメでも明日があるさ

Ruby

追記:トラバいただいて、以下の記事の内容のことは可能であることを教えていただいた。要はドンマイなのは僕の頭だったというわけで。3日間検索してまわってCのソースまで読んでその事実に気づかなかった自分って…orz

追記2:可能なはずだったけど、バグでできなかったことが判明。修正してパッチを送って採用されました。時期バージョン(5.3.0 or 5.2.1)では直るはず。

      • -

数日、libxml-rubyでの名前空間の扱いに悩んでいたのだが、一応結論が出た気がする。

結論:ドンマイ libxml-ruby

というか、自分もXMLの基礎ができていない人間で、On the Jobで苦し紛れに調べまくった結果なんとなく見えてきた、という感じなので、間違いがあれば指摘してください。

例えば、以下のようなXMLを処理する場合を考えてみよう。これはRFC 5023 The Atom Publishing Protocolから引いてきた例。

<?xml version="1.0" encoding='utf-8'?>
<!-- atom.xml として保存 -->
<service xmlns="http://www.w3.org/2007/app"
         xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title>Main Site</atom:title>
    <collection href="http://example.org/blog/main">
      <atom:title>My Blog Entries</atom:title>
      <categories href="http://example.com/cats/forMain.cats" />
    </collection>
  </workspace>
  <!-- 省略 -->
</service>

さて、このXMLを処理して、例えば '/service/workspace/collection/atom:title' の要素を取得したい。PerlXML::LibXMLであれば、次のように書けばよい。

use strict;
use warnings;
use XML::LibXML;
use XML::LibXML::XPathContext;

my $parser = XML::LibXML->new();
my $doc = $parser->parse_file("atom.xml");

my $xc = XML::LibXML::XPathContext->new($doc);
$xc->registerNs('app', 'http://www.w3.org/2007/app');
$xc->registerNs('atom', 'http://www.w3.org/2005/Atom');

my @nodes = $xc->findnodes('/app:service/app:workspace/app:collection/atom:title');
print $_->toString,"\n" for @nodes;

これを実行すれば、

<atom:title>My Blog Entries</atom:title>

と表示される。つまり要素が取得できている。名前空間のテストなので、ちょっと変則的に書いているのだが、注目する点としては、

  • XPathの中に、複数の名前空間の要素が混在している(コード上ではatomappXML上ではデフォルト名前空間app)。
  • 名前空間URIによって区別されるので、接頭辞(app/atom)は可変である("受け入れるものに対しては寛容に")

といったところ。この例でも、XPathではプログラム上からURIを用いて設定した接頭辞を使っているので、実際にXMLに記述されている接頭辞が変更されたとしても実行結果は変わらない。

なるほど。さて、同じことがlibxml-rubyでもできるはず…と思ったらできない。これは簡単に言えばXML::XPath::Contextクラス(上記のPerlの例でいうところのXML::LibXML::XPathContextに相当する)の実装がショボイからだ。それは、ドキュメントを読んでもいいが、下のスクリプトを実行してみれば明らかだ。

require 'rubygems'
require 'xml/libxml'
puts XML::XPath::Context.instance_methods - Object.instance_methods

結果:

doc
register_namespace

つまり、docregister_namespaceしか実装されていないわけだ。まぁショボイというか、実装が手付かずなんだろう。

というわけで、ちょっくら改造してみることにする。さてどうなることやら。

【広告】