Hatena::Grouprubyist

Going My Ruby Way このページをアンテナに追加 RSSフィード

Ruby ロゴ (C) Ruby Association LLC

2011年07月21日(木)

PYX 形式の XML パーザ その2

21:31 | PYX 形式の XML パーザ その2 - Going My Ruby Way を含むブックマーク はてなブックマーク - PYX 形式の XML パーザ その2 - Going My Ruby Way PYX 形式の XML パーザ その2 - Going My Ruby Way のブックマークコメント

(2011/07/22 Obsoleted by PYX 形式の XML パーザ その 3 - Going My Ruby Way - Rubyist)

----

以下の日記に書いた PYX 形式の XML パーザを書き直しました。

#!/usr/bin/env ruby
# -*- coding: UTF-8 -*-

class PYXParser
  def initialize(source=nil)
    @source = source

    yield(self) if block_given?
  end

  attr_accessor :source

  def config(&block)
    instance_eval(&block)
    self
  end

  def listener(c, &block)
    (@listeners ||= {})[c] = block || @listeners[c] || proc {}
  end

  def parse(source=nil, &block)
    (source || @source || ARGF).grep(/^(.)((\S+)(?:\s+(.+))?)/) {
      [$1, $2, $3, $4] }.map{ |c, text, name, value|

      text.gsub!(/\\(.)/) {{'r'=>"\r",'n'=>"\n",'t'=>"\t"}[$1] || $1}
      
      dispatcher = proc {|c, *a| listener(c).call(*a) }

      (block || dispatcher).call(c, name, value, text)
    }
  end
end

# vi:set ts=2 sw=2 et fenc=UTF-8:

変更点です。

(0) ruby1.8.7 でも動くようにしました。

(1) text(リスナーに渡される 3番目の引数)はアンエスケープされます。

  • '\n' => LF
  • '\r' => CR
  • '\t' => HT (TAB)
  • '\x' => x 自身

value(リスナーに渡される 2番目の引数)はそのまま渡されます。

(2) 開始タグ('(' で始まる行)を処理するリスナーに stag_listener などの名前付けをやめました。以下のようにリスナーを設定します。

PYXParser.new do |parser|
  parser.listener('(') do |name,|   # 開始タグ
    puts "START TAG: '#{name}'"
  end

  parser.listener(')') do |name,|   # 終了タグ
    puts "END TAG: '#{name}'"
  end

  parser.listener('A') do |name, value,|   # 属性
    puts %|Attribute: #{name}="#{value}"|
  end

  parser.listener('-') do |name, value, text|  # テキスト
    puts "TEXT: '#{text}'"
  end

  parser.listener('?') do |name, value, text|  # 処理命令
    puts "PI: '?#{name} #{value}?'"
  end

  parser.parse
end

(3) self で instance_eval するブロックを渡せる config メソッドを追加しました。以下のように使えます。

stack = [doc = [nil,nil,[]]]
ATTRS = 1
CHILD = 2

parser = PYXParser.new
parser.config do
  listener('(') do |name,|    # listener のレシーバの指定は不要
    stack.last[CHILD] << elem = [name, {}, []]
    stack << elem
  end

  listener(')') do |name,|
    stack.pop
  end

  listener('A') do |name, value,|
    stack.last[ATTRS][name] = value
  end

  listener('-') do |name, value, text|
    stack.last[CHILD] << text
  end

  listener('?') do |name, value, text|
    # ignore
  end
end

parser.parse

require 'pp'

pp doc[CHILD]

よく見たら config のブロックの中で parse まで出来ますね。config という名前はどうだろか。。。


このパーザ作ったけど使う機会がありません。。。

----

おまけ。sample 用 PYX 形式 XML

?xml version="1.0"
(html
(head
Alang ja
(title
-pyx sample 1
)title
)head

(body
(p
-これは PYX 形式のサンプルです.
)p
)body
)html

-----

(PYX 形式 XML の)参考リンク