Nokogiri で XML のパース導入編
「Ruby で XML をパースしたい」ということで(蛇足気味ですが)調べたメモを公開します。
前提条件
libxml2 と libxslt が必要です。CentOSであれば以下のように準備します。
yum install libxml2-devel yum install libxslt-devel
Windows の場合はここから
ftp://ftp.zlatkovic.com/libxml/
「libxml2-2.7.8.win32.zip」を解凍してできた「bin/libxml2.dll」を「libxml2-2.dll」にリネームして「%RUBY_HOME%/bin」に置く。
「libxslt-1.1.26.win32.zip」を解凍してできた「bin/libexslt.dll」「bin/libxslt.dll」を「%RUBY_HOME%/bin」に置く。
Ruby Gems
Windows の場合は sudo は要りません。
sudo gem install nokogiri
ただし libxml2 のバージョンが古いと Nokogiri インストール時に警告が出て失敗してしまいます。
$ sudo gem install nokogiri checking for libxml/parser.h... yes checking for libxslt/xslt.h... yes checking for libexslt/exslt.h... yes checking for iconv_open() in iconv.h... yes checking for xmlParseDoc() in -lxml2... yes checking for xsltParseStylesheetDoc() in -lxslt... yes checking for exsltFuncRegister() in -lexslt... yes checking for xmlHasFeature()... no ----- The function 'xmlHasFeature' is missing from your installation of libxml2. Likely this means that your installed version of libxml2 is old enough that nokogiri will not work well. To get around this problem, please upgrade your installation of libxml2. Please visit http://nokogiri.org/tutorials/installing_nokogiri.html for more help! *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. ...
libxml2 のバージョンを勝手に上げてしまうと影響度が大きすぎる場合、以下のように最新版を別の場所にインストールしてから gem install nokogiri でそのパスを指定します。
というか、こちらの受け売りなので本家を参照してください。
http://d.hatena.ne.jp/kitamomonga/20100223/ruby_nokogiri_install_with_any_libxml2_path
https://github.com/tenderlove/nokogiri/wiki/what-to-do-if-libxml2-is-being-a-jerk
手元でやったものは以下のようになります。
wget ftp://xmlsoft.org/libxml2/libxml2-2.7.8.tar.gz tar xvfzp libxml2-2.7.8.tar.gz cd libxml2-2.7.8 ./configure --prefix $HOME/usr/local --with-libxml-src=../libxml2-2.7.8 make make install LIBXML2_DIR=/opt/libxml2 sudo gem install nokogiri -- \ --with-xml2-include=${LIBXML2_DIR}/include/libxml2 \ --with-xml2-lib=${LIBXML2_DIR}/lib \ --with-xslt-dir=/usr/include # with-xml2-include は find /opt/libxml2 -name parser.h で探したパス # with-xml2-lib は find /opt/libxml2 -name libxml2.so で探したパス # with-xslt-dir は libxslt インストール済の前提で find /usr/ -name xslt.h で探したパス
XPath を使って XML をパースする
http://nokogiri.org/Nokogiri.html
require 'nokogiri' xml = <<EOM <?xml version="1.0" encoding="UTF-8"?> <items> <item id="123">Andy</item> <item id="234">Brian</item> <item id="345">Charles</item> </items> EOM doc = Nokogiri::XML(xml)
要素の扱い方は libxml-ruby と同様で、属性は Hash のようにして取得し値は #content で取り出せます。また #text でも値を取り出せます。
irb(main):015:0> xpath_obj = doc.xpath('/items/item') => [#<Nokogiri::XML::Element:0x9b60dc name="item" attributes=[#<Nokogiri::XML::Attr:0x9b60a0 name="id" value="123">] children=[#<Nokogiri::XML::Text:0x9b5c74 "Andy">]>, #<Nokogiri::XML::Element:0x9b5a 28 name="item" attributes=[#<Nokogiri::XML::Attr:0x9b59ec name="id" value="234">] children=[#<Nokogiri::XML::Text:0x9b5584 "Brian">]>, #<Nokogiri::XML::Element:0x9b5110 name="item" attributes=[#<Nokog iri::XML::Attr:0x9b50c8 name="id" value="345">] children=[#<Nokogiri::XML::Text:0x9b4ba0 "Charles">]>] irb(main):016:0> xpath_obj.is_a?(Enumerable) => true irb(main):017:0> irb(main):069:0* doc.xpath('/items/item').each do |item| irb(main):070:1* puts item['id'] + ' -> ' + item.content irb(main):071:1> end 123 -> Andy 234 -> Brian 345 -> Charles => 0 irb(main):074:0> doc.xpath('//item').each do |item| puts item end <item id="123">Andy</item> <item id="234">Brian</item> <item id="345">Charles</item> => 0
first や last で先頭・末尾を取り出せます*1。
irb(main):072:0> puts doc.xpath('/items/item').first <item id="123">Andy</item> => nil irb(main):073:0> puts doc.xpath('/items/item').last <item id="345">Charles</item> => nil
CSS セレクターを使って XML をパースする
Nokogiri.XML でパースした Nokogiri::XML::Document オブジェクトでは CSS セレクターを使うことができます。
irb(main):019:0> doc.css('items item').each do |i| puts i end <item id="123">Andy</item> <item id="234">Brian</item> <item id="345">Charles</item> => 0
Slop を使って XML をパースする
Nokogiri.Slop でパースした Nokogiri::XML::Document オブジェクトでは XPath、CSSセレクターはもちろんのこと、以下のようにタグ名のメソッドを呼び出すことで要素を取り出すことができます。仕組みとしては method_missing で実現されているようです。
http://nokogiri.rubyforge.org/nokogiri/Nokogiri.html#M000360
doc = Nokogiri::Slop(xml)
まずは先ほどと同じようなサンプルから。
irb(main):026:0> doc.items.item.each do |item| puts item end <item id="123">Andy</item> <item id="234">Brian</item> <item id="345">Charles</item> => 0 irb(main):027:0>
次はもう少し階層が深いサンプルです。
xml2 = <<EOM <?xml version="1.0" encoding="UTF-8"?> <items> <item id="123"> <name>Andy</name> <age>21</age> </item> <item id="234"> <name>Brian</name> <age>23</age> </item> <item id="345"> <name>Charles</name> <age>19</age> </item> </items> EOM doc2 = Nokogiri::Slop(xml2)
このように「items.item」で「xpath('/items/item')」と同等のことができます。
irb(main):071:0> doc2.items.item.each do |e| puts e.age.content end 21 23 19 => 0
一度 CSS セレクターや XPath のモードに入ってしまうとメソッドによるタグ指定はできなくなりますが
rb(main):050:0> doc2.css('items').item.each do |e| puts e.age.text end oMethodError: undefined method `item' for #<Nokogiri::XML::NodeSet:0x16721b8> from (irb):50 from /bin/irb:12:in `<main>' rb(main):051:0> irb(main):051:0> doc2.xpath('items').item.each do |e| puts e.age.text end NoMethodError: undefined method `item' for #<Nokogiri::XML::NodeSet:0x16583e8> from (irb):51 from /bin/irb:12:in `<main>' irb(main):052:0>
XPath や CSS セレクターのやり方を組み合わせても問題ないようです。
irb(main):072:0> doc2.items.xpath('item').each do |e| puts e.age.text end 21 23 19 => 0 irb(main):073:0> doc2.items.css('item').each do |e| puts e.age.text end 21 23 19 => 0
とすると、name のようにメソッド名と被ってしまった場合は XPath とかを使う・・のかな?
irb(main):082:0> doc2.items.item.each do |e| puts e.name end item item item => 0 irb(main):081:0> doc2.items.item.each do |e| puts e.xpath('name') end <name>Andy</name> <name>Brian</name> <name>Charles</name> => 0