Nokogiriメモ。XMLを解析する

別途で、XMLファイルを出力するのも書きます(次の日記)


まず今回の日記は
Nokogiriでxmlファイルを読み込む・解析のやり方について(メモ程度)。


抽象的すぎて良い例ではないですが、こんなXMLがあったとする

<xml_node>
  <test_node type="a">
    <hoge>
      <hoge_child>hoge1</hoge_child>
      <hogehoge>hoge2</hogehoge>
    </hoge>
    <fuga>fuga1</fuga>
    <piyo>piyo1</piyo>
  </test_node>

  <test_node type="a">
    <hoge>
      <hoge_child>hoge3</hoge_child>
      <hogehoge>hoge4</hogehoge>
    </hoge>
  </test_node>

  <test_node type="b">
    <hoge>
      <hoge_child>hoge5</hoge_child>
      <hogehoge>hoge6</hogehoge>
    </hoge>
    <fuga>fuga2</fuga>
  </test_node>

</xml_node>


まず、XMLをNokogiriで開いて扱うためにはこうする。

require "nokogiri"
file = File.open("xmlファイル.xml")
xml_doc = Nokogiri::XML(file)

これでNokogiri::XML::Documentのオブジェクトが生成されて、扱えるようになる。
ここから、ノードを探り当てるには、xpathメソッドを使う。

test_nodes = xml_doc.xpath("//test_node")

とすると、
多分XMLファイルのルートのノードから探って一致した
Nokogiri::XML::NodeSetオブジェクトが返って来る。
これは配列の拡張みたいな戻り値になっている。

例だと、

の構造が3つあるので、それら3つが丸ごと格納されたNokogiri::XML::NodeSetオブジェクトが返る。

配列の拡張感覚なので、ここに対して、
test_nodes.size
test_nodes.each do 処理 end
とか書けるし、
更にそこから相対パスのようなサーチで
test_nodes.xpath("fuga")
test_nodes.xpath("hoge//hoge_child")
のようにすることも出来る。

最初をxpath("//node名")でかくと、また大元から探っちゃう感じがする。
間違ってるかもしれないけど。

構造がわかってるなら
xml_doc.xpath("//test_node//hoge//hogehoge")とかも可。


で、NodeSetに対しては、eachとか使うと、その中に
Nokogiri::XML::Elementオブジェクトが格納されている。
これが末端の、子部分(leaf node)になる。


Nokogiri::XML::Elementに対して.textメソッドを呼ぶことで中身が取得出来る。

属性は、XML::Elementオブジェクトのレシーバに対して
attribute(属性名) #=> 属性の値
attributes #=> 属性一覧
で取れる。


属性まで含めてxpathメソッドで探索する場合は
xml_doc.xpath("//test_node[@type='a']")
みたいな感じにすると、

のノード一覧だけ取得出来る。

動的にここを指定したいなら、
xml_doc.xpath(%Q(//test_node[@type='#{attribute_value}']))
とかこういう感じで応用出来た気がする。


まとめると

require "nokogiri"
file = File.open("xmlファイル.xml")
xml_doc = Nokogiri::XML(file)

p xml_doc.xpath(//test_node).class #=> Nokogiri::XML::NodeSet
xml_doc.xpath(//test_node).each |xpath_node|
  p xpath_node.class #=> Nokogiri::XML::Element

  p xpath_node.node_name #=> "test_node"

  p xpath_node.attributes.class #=> Hash
  p xpath_node.attributes.size  #=> 1

  p xpath_node.attribute("type").class  #=> Nokogiri::XML::Attr
  p xpath_node.attribute("type").text  #=> "a"

  #多分このchildrenメソッドでhogeのリーフノードを取っていけるのではないかと思うけれども、
  #もうちょっと複雑なXMLに頑張ってxpathで引き当てていったので保証はしない
  p xpath_node.children.class #=> Nokogiri::XML::NodeSet

  p xpath_node.text #=>そのNokogiri::XML::Elementのノードの中のvalueを返す(<xxx>value</xxx>のvalue。更にノード持ってたらそれごと出る)
  
end

他に、使えそうな雰囲気を感じたメソッドを適当にメモ程度に。
at_css
at_xpath
attr
child
namespace
cdata?
css
css_path
document
elem?
element?
element_children
elements
fragment
fragment?
get_attribute
has_attribute?
inner_html
inner_text
name
namespace
namespace_definitions
namespace_scopes
namespaced_key?
namespaces
node_type
parse
partition
メソッドとかが、
Nokogiri::XML::Elementオブジェクトで使えるみたい。

Nokogiri::XML::NodeSetオブジェクトだともうちょっと違ったメソッドもあると思いますが、
それは使う方々でmethodsメソッドを呼んで確認してみてください。

この辺上手く使えば、スマートにXMLをパース出来ると思いますが、
手探りだったもので、xpathを書きまくってごりごり自分の必要処理のパースをしてしまった。


今後、これを見ながら、もうちょっとスマートに書き直したり、
別の実装があったら綺麗に書きたいもんです。