Hatena::Grouprubyist

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

Ruby ロゴ (C) Ruby Association LLC

2011年07月22日(金)

PYX 形式の XML パーザ その 3

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

(2011/07/23 Obsoletes by

PYX 形式の XML パーザ その 4 - Going My Ruby Way - Rubyist)

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

----

このパーザは基本的にストリームパースをしますが、Treeパースする処理をクラスメソッドとして追加しました。(といっても前書いたサンプルをクラスの中に取りこんだだけですが)

前の日記で書いた object.rb も使ってます。(cascade を使うためです。代わりに config メソッドは廃止しました)

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

require 'object'

class PYXParser
  def initialize(source=nil, &block)
    @source = source

    cascade(&block)
  end

  attr_accessor :source

  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

  def self.parse(source=nil)
    stack = [doc = [nil, nil, []]]

    new(source).cascade do
      listener('(') do |name,|
        stack.last[2] << elem = [name, {}, []]
        stack << elem
      end

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

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

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

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

      parse
    end

    doc
  end
end

if $0 == __FILE__
=begin
#
# example 1
#
PYXParser.new do |parser|
  parser.parse do |c, name, val, line|
    puts "c:'#{c}' name:#{name} val:#{val} |#{line}|"
  end
end
=end

=begin
#
# example 2
#
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
=end

#
# example 3
#
require 'pp'

doc = PYXParser.parse
pp doc[2]
=begin
=end

end

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

使い方は example 3 のところの通りです。

----

ノードツリー構造についてです。

ノードツリー構造は Array で表現された Element の入れ子です。

  Element : [ tag, {}, [] ]     : [String, Hash, Array]
  Document: [ nil, nil, [] ]    : [nil, nil, Array]
  Text    : "..."               : String

Element の 0番目の要素がタグ、1番目の要素が属性(属性名、値とも String)、2番目の要素が子要素のArray(ノードリスト)の Array です。子要素のArray(ノードリスト)に Element, Text などが格納されてツリー構造を成します。

Document は Element の特殊な形です。子要素のArrayだけが有効に使用されます。

Text は String で表現されます。