今日も元気にテクニカル

技術情報書きたいけど本ブログに書きたくないからこんな名前になりました。

REXML 昨日の続き

昨日のエントリで「REXMLを使うときにはクラスに注意しましょう」みたいなことを書いたが、基本は

require 'kconv'
require 'rexml/document'
include REXML

doc = REXML::Document.new File.open("newsing.xml","r")
p doc.class #=> REXML::Document
p doc.root.class #=> REXML::Element

でREXML::Documentクラスを生成した後、rootメソッドをつけてDOMツリーの一番上から要素を指定してやればよいみたい。

RubyでXML操作 | Netsphere Laboratories

XMLのサンプルは昨日の日記参照。…データがちょっと違うけど構成は一緒なのでまぁ察してください(´ー`)

puts doc.root.elements[1].elements[1].class
 #=> REXML::Element
puts doc.root.elements[1].elements[1].text.tosjis
 #=> http://newsing.jp/entry?url=www.shopjapan.co.jp%2Fbillysbootcamp%2F%3Faf_id%3D23%26banner_id%3Dya_00164
puts doc.root.elements[1].elements[2].text.tosjis
 #=> うわさのビリーはよく見ると " ET " みたい
puts doc.root.elements[1].elements[3].text.tosjis
 #=> undefined method `tosjis' for nil:NilClass (NoMethodError)

空要素の時はエラーとなるので条件分岐を入れなければいけない。

p doc.root.elements[1].elements[3].has_text?
 #=> false
puts doc.root.elements[1].elements[3].text.tosjis if doc.root.elements[1].elements[3].has_text?
 #=> 何も出力されない

ちなみにREXML::Elements#[index, name=nil]という形で「nameという要素名を持つindex番目の要素」を返せる。
が、今回使うXMLのように全ての要素名がユニークな時には使う意味はない。逆に

<?xml version="1.0" encoding="Shift-JIS" standalone="yes"?>
<items>
  <item>
    <url>http://newsing.jp/entry?url=www.shopjapan.co.jp%2Fbillysbootcamp%2F%3Faf_id%3D23%26banner_id%3Dya_00164</url>
    <title>うわさのビリーはよく見ると &quot; ET &quot; みたい</title>
    <comment>コメント1</comment>
    <comment>コメント2</comment>
    <comment>コメント3</comment>
    <comment>コメント4</comment>
  </item>
</items>

みたいなフラットなXMLなら「X番目のコメントを取り出す」とかに使えて便利。

XPATHでやる

以上がindex番号による指定で、当然XPATHでも指定できる。

puts doc.root.elements[1].elements["url"].text.tosjis
 #=> http://newsing.jp/entry?url=www.shopjapan.co.jp%2Fbillysbootcamp%2F%3Faf_id%3D23%26banner_id%3Dya_00164
puts doc.root.elements[1].elements["title"].text.tosjis
 #=> うわさのビリーはよく見ると " ET " みたい
puts doc.root.elements[1].elements["main_text"].text.tosjis
 #=> undefined method `tosjis&#39; for nil:NilClass (NoMethodError)

後から要素が追加される可能性を考えると、こっちの方が賢そう。

指定した要素全てを抜き出す

each(ブロック)を使わずXMLの指定した要素を全て取り出す方法はいろいろ調べたけどないっぽい。残念。

REXML::Elements#[xpath]
xpathに最初にマッチした子要素を返す。

XPATHで見つかった要素全てを配列で返してくれるとかだったら良かったのに。

仕方がないのでブロックで

require &#39;kconv&#39;
require &#39;rexml/document&#39;
include REXML

doc = REXML::Document.new File.open("newsing.xml","r")

# 1.基本形
doc.root.each_element do |elem|
  puts elem.elements["title"].text.tosjis
end

# 2.回数を指定する場合
doc.root.each_element_with_text(nil, 10) do |elem|
  puts elem.elements["title"].text.tosjis
end

# 3.XPATHでダイレクト指定
doc.elements.each("//title") do |elem|
  puts elem.text.tosjis
end

# 4.uptoでごり押し
1.upto(10) do |i|
  entry = doc.root.elements[i]
  puts entry.elements["title"].text.tosjis
end

結果は全部同じ。#each_element_with_textが回数指定できて、#each_elementはできないってのが疑問だけど仕方ない。それぞれの方法に得手不得手があるから目的に合わせて使い分ければいいんじゃないかな(・ω・)人(・ω・)

textとget_text

get_textメソッドを使うとREXML::Textクラスで抽出し、特殊文字実体参照に変換してくれる。

require &#39;kconv&#39;
require &#39;rexml/document&#39;
include REXML

doc = REXML::Document.new File.open("newsing.xml","r")

puts doc.root.elements["//title"].text.class
 #=> String
puts doc.root.elements["//title"].text.tosjis
 #=> うわさのビリーはよく見ると " ET " みたい
puts doc.root.elements["//title"].get_text.class
 #=> REXML::Text
puts doc.root.elements["//title"].get_text.to_s.tosjis
 #=> うわさのビリーはよく見ると &quot; ET &quot; みたい
puts doc.root.elements["//title"].get_text.tosjis
 #=> undefined method `tosjis&#39; for #<REXML::Text:0x2ba54d0> (NoMethodError)

普通にエスケープすればよいのであまり使わないかな…。