Hatena::Grouprubyist

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

Ruby ロゴ (C) Ruby Association LLC

2011年07月23日(土)

PYX 形式の XML パーザ その 4

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

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

----

なんか毎日変えてますが。。。

PYXParser.parse の Tree 構造の構築部分を NodeTree::Composer クラスに任せるように変更です。

pyxparser.rb

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

require 'object'
require 'nodetree'

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)
    NodeTree::Composer.new {|composer|
      new(source).parse do |c, name, value, text|
        case c
          when '(' ; composer << [:stag, name]
          when ')' ; composer << [:etag, name]
          when 'A' ; composer << [:attr, name, value]
          when '-' ; composer << [:text, text]
          when '?' ; # ignore
        end
      end
    }.doc
  end
end

if $0 == __FILE__
#
# example 1 (stream parse)
#

#  (ここは略)

#
# example 2 (parsed tree)
#
require 'pp'

doc = PYXParser.parse
pp doc.children

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

以下は、変更した nodetree.rb です。Composer クラス、DocElem モジュール、が追加になり、NodeList モジュールが変更になりました。

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

require 'object'
require 'property'

module NodeTree
  module Attrs
    def to_s
      map{|n,v| %( #{n}="#{v}") }.join
    end
  end

  module ElemNode
    def tag       ; at(0)                  ; end
    def attrs     ; at(1).extend(Attrs)    ; end
    def children  ; at(2).extend(NodeList) ; end

    def [](i, *a)
      (i.kind_of?(Integer) ? children : attrs)[i, *a]
    end

    def to_s
      "<#{tag}#{attrs}>#{children}</#{tag}>"
    end
  end

  module DocNode
    def self.create(ary=[])
      ary.replace([nil, nil, []]).extend(DocNode)
    end

    def children
      at(2).extend(NodeList)
    end

    def [](i, *a)
      children[i, *a]
    end

    def to_s
      children.to_s
    end
  end

  module NodeList
    def self.to_node(node)
      node.kind_of?(Array) ? node.extend(ElemNode) : node
    end

    def [](i, *a)
      a.empty? ? at(i) : at(i)[*a]
    end

    def []=(i, node)
      super(i, NodeList.to_node(node))
    end

    def push(node)
      super(NodeList.to_node(node))
    end

    def unshift(node)
      super(NodeList.to_node(node))
    end

    alias << push
    alias >> unshift

    def to_s
      map{|s| s.to_s }.join
    end
  end

  class Composer
    def initialize(doc=[], &block)
      stack = [@doc = DocNode.create(doc)]

      @handlers = {
        :stag => proc {|nm,|
          stack.last.children << elem = [nm, {}, []]
          stack << elem
        },

        :etag => proc { stack.pop },

        :attr => proc {|nm, val,| stack.last.attrs[nm] = encode[val] },

        :text => proc {|s,| stack.last.children << encode[s] unless ignore_text[s] },
      }

      @handlers[:attrs] = proc {|attrs,| attrs.each(&@handlers[:attr])}

      cascade(&block)
    end

    attr_reader :doc
    property    :encode,      'proc {|s| s }'
    property    :ignore_text, 'proc {|s| false }'

    def push(key, *a)
      (@handlers[key] || proc {}).call(*a)
      self
    end

    def <<(a)
      push(*a)
    end
  end

  def self.view(nodelist, n=[])
    nodelist.each_with_index do |c, i|
      print (n+[i]).inspect + ' >>> '

      if c.kind_of?(Array)
        p c.slice(0,2)
        view(c.children, n+[i])
      else
        p c
      end
    end
  end
end

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

なぜか NodeTree が class になってたので module に修正しました。