rubyの最近のブログ記事

libxml-rubyのAPIの変更に気がつかずにはまったので、リリースをウォッチしようとRubyForgeのページを確認するも、フィードが見つからない(あったりして)。
RubyForge全体のnewsのフィードと、プロジェクトのnewsのフィードはあった。
だけどリリースとnewsは連動しているわけではないらしく投稿するかしないかは管理者次第のようだ。
RubyForge: LibXML: ニュース
RubyForge: LibXML: ファイルリスト
じゃあPipesでも使ってみるかと気軽に始めたのだけどかなり手間取った。

やりたいこと
  • libxml-rubyのリリース(「libxml-ruby-0.9.8」「libxml-ruby-0.9.7」部分)を抜き出してフィードに。
  • descriptionに上記文字列のリンク先(リリースノート)の内容を入れる
最初のリリース一覧の抜出からしてなかなか意図した結果にならない。XPathでさくっと取り出してぇ、と試行錯誤。結局YQLで大枠を抜き出してOperators/Renameでちまちまとtitle要素/link要素にとり出すことに。

select  * from html
where
url="http://rubyforge.org/frs/?group_id=494"
and
xpath="/html/body/div[3]/table/tr/td[2]/table/tr[position()>=4 and td/p/strong/text()]"
得られたtrリストの直下の一番目のtdと二番目のtdを指定する方法でまた悩む。
item.td.0.p.strong.a.href  →link
item.td.0.p.strong.a.content →title
item.td.1.p.strong →日時
てな感じ。

リリースノートの内容は、<pre>の中身でざくっと指定できたのでLoop+Fetch Pageの結果をdescriptionにassign。

日時をフィードのpubDateにどう反映するのかがまたよくわからなくて、他の人のPipesを参考にLoop+Date Builderでitem.y:publishedにassignした。フィード出力の時点でpubDateに突っ込まれるようだ。

ようやく出来たのが↓のPipes。Livedoor Readerに食わせてウォッチ中。
http://pipes.yahoo.com/pipes/pipe.info?_id=78e1182d42024a179dfda9fc6f708550

面白いしすごく便利だけど、やりたいことをPipes流の発想に変換するのがなかなか大変。。。
YQLを使えばXPathでスクレイピング可能なのが救い。



libxml-ruby のI/Fが変わってた 

|
ふと気づいたらlibxml-rubyが0.9.8になってた。
gemで0.8.3からupdateしたら動かないスクリプトが出てきた。
XML::Document#to_s がエラーになる。

CHANGESを見ると
  • 出力オプションをHash指定に変えた
  • エンコード名を文字列指定からLibXML::XML::Encoding下の定数指定に変えた
ということらしい。
前者はともかく後者は面倒だなー。というのもjrubyでも使うスクリプトなのでtransformer.setOutputProperty(javax.xml.transform.OutputKeys::ENCODING, エンコード名な文字列) とI/Fを合わせて文字列指定で作ってたから。

libxml-rubyのマニュアルによればエンコード名文字列から定数を得るI/Fがあるみたいなんだけど、どうも期待した結果にならない。
(マニュアル上はInput.s_to_encoding("UTF_8") なんだけどext/libxml/ruby_xml_encoding.cを見るとfrom_sのようで混乱する

$ irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'libxml'
=> true
irb(main):003:0> LibXML::XML::Encoding.from_s("UTF_8")
=> -1
irb(main):004:0> LibXML::XML::Encoding.from_s("UTF-8")
=> false
irb(main):005:0> LibXML::XML::Encoding.from_s("utf8")
=> false
irb(main):006:0> LibXML::XML::Encoding.from_s("utf-8")
=> false
irb(main):007:0> LibXML::XML::Encoding.from_s("utf_8")
=> -1
irb(main):008:0>

とりあえず文字列をいじくって定数名とみなしてお茶にごす。
alias名が困るなー

      const_name = encoding_name.gsub(/-/, "_").upcase
encoding = LibXML::XML::Encoding.const_get(const_name)
doc.to_s(:encoding => encoding)

環境
    - CentOS 5.2 i686
    - libxml2: 2.6.26-2.1.2.7
    - ruby: 1.8.5-5.el5_2.6
    - rubygems: 1.3.1
    - libxml-ruby: 0.9.8 (gem)

libxml-ruby 0.8.3が出てた 

|
ふと、libxml-ruby本家を確認したら、いつのまにか0.8.3になってた。
RubyForge: LibXML: ファイルリスト
7月に入ってから怒涛のupdate

jruby+Java DOMに逃げてなんとかやりすごしていたのだけど、これは期待できるか?と再チャレンジ。
とりあえず自分ところのアプリの範囲では「[BUG] object allocation during garbage collection phase」で落ちることがなくなった。

I/Fをlibxml-rubyのままにしておいて正解だった。単にCRubyで実行しただけ。

やはりlibxml2の性能は魅力。

OS: CentOS 5.2
ruby: 1.8.5
libxml2: 2.6.26
libxml-ruby: 0.8.3

gemでlibxml-ruby(0.5.4)をインストールして使っていた。
いつからか、以下のようなメッセージでクラッシュしたりしなかったり、という事象が出始めた。
[BUG] object allocation during garbage collection phase
・・・という問題に悩んでいたのだけど、そのものズバリの答えが。
[ruby-dev:35157] Re: error: Ruby 1.8.7 object allocation during garbage collection phase
クラッシュといっては心外な話しか。
新しいrubyだと問題を検出して[BUG]を出す、ということだそう。

原因はわかったが、とりあえず手元のlibxml-ruby前提で書いたツールをどうするか。

libxml-rubyのパッチを書く?
とコードを眺めたものの早々にあきらめた。これはすぐにどうこう出来そうもない。。

REXMLで書き直す?
そもそもREXMLが遅いので、libxml-rubyにしただけにこれは避けたい。

perlのLibXMLで書き直す?
んー、時間もかかるし同じものをもう一回書くのもな。

で、jruby
REXMLを使うとスタート地点に戻ってしまうので、javaのDOM・XPath APIを使うことに。

とりあえず、libxml-rubyで使ってるインタフェースだけラッパーを用意。
ツール側のコードはほぼ変更がなかった。
libxml2の軽快さには遠いがjrubyありがとうって感じ。

jruby:1.1.2
Sun Java: 1.6.0_05
OS:CentOS 5.1

  $KCODE='u'
require 'java'

class WrapNode
attr_reader :node

def initialize(e)
@node = e
@xpath = nil
end

def to_s
@node.nodeValue.to_s
end

def wrap(e)
if e == nil
return nil
end
WrapNode.new(e)
end

def next
self.wrap(@node.nextSibling)
end

def text?
@node.nodeType == org.w3c.dom.Node::TEXT_NODE
end

def [](name)
@node.getAttribute(name)
end

def parent
self.wrap @node.parentNode
end

def child
self.wrap @node.firstChild
end

def xpath
if !@xpath
factory = javax.xml.xpath.XPathFactory.newInstance()
@xpath = factory.newXPath()
end

@xpath
end

def find(expr)
nodelist = self.xpath.evaluate(expr, @node, javax.xml.xpath.XPathConstants::NODESET)

ret = []
for i in 0 .. (nodelist.length - 1)
ret.push(WrapNode.new(nodelist.item(i)))
end
ret
end
end

class WrapDocument < WrapNode
def initialize(e)
super(e)
end

def root
self.wrap(@node.documentElement)
end
end

# 指定されたファイルをopenしてXML文書を返す
#
# fn_in::
# ファイル名。nilの場合、stdinを適用
def build_doc(fn_in)
st = java.lang.System.in
if fn_in
st = java.io.FileInputStream.new(fn_in)
end

factory = javax.xml.parsers.DocumentBuilderFactory.newInstance()
builder = factory.newDocumentBuilder()
WrapDocument.new(builder.parse(st))
end

doc = build_doc("foo.xml")
doc.find("//bar").each { |e|
puts e["name"]
}

Powered by Google App Engine

このアーカイブについて

このページには、過去に書かれたブログ記事のうちrubyカテゴリに属しているものが含まれています。

前のカテゴリはredmineです。

次のカテゴリはsubversionです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 5.04