Hatena::Grouprubyist

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

Ruby ロゴ (C) Ruby Association LLC

2011年07月19日(火)

each_with_object

| 22:59 |  each_with_object - Going My Ruby Way を含むブックマーク はてなブックマーク -  each_with_object - Going My Ruby Way  each_with_object - Going My Ruby Way のブックマークコメント

Ruby1.9.x では、Enumerable に each_with_object というメソッドがあります。

知らんかった。。。

>> {:a=>10,:b=>20}.each_with_object([]) {|(k,v),a| a << v}
=> [10, 20]

あと、ブロックの引数で、Array を受けるとき

{|(k,v),a| ... }

と書けるのも初めて知りました。いつからできるんでしょう。。。

PYX 形式 XMLのパーザ

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

PYX 形式は XML の別の構文です。

こんな感じです。

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

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

その簡易パーザを書いてみました。

Ruby1.9.x で動作します。使い方はスクリプト中のサンプルを参照のこと。

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

class PYXParser
  private
  def cmd_to_sym(cmd)
    {
      '(' => :stag,
      ')' => :etag,
      'A' => :attr,
      '-' => :text,
      '?' => :inst,
    }[cmd]
  end

  def initialize(source=nil)
    @source     = source
    @listeners  = Hash.new {|hash,key| hash[key] = proc{}}
    @listener   = proc do |cmd, name, value, line|
      @listeners[cmd_to_sym(cmd)].(name, value, line)
    end

    yield(self) if block_given?
  end

  public
  attr_accessor :source

  def listener(&block)
    @listener = block || @listener
  end

  def method_missing(name, &block)
    return super unless name =~ /(.+?)_listener$/

    @listeners[$1.intern] = block || @listeners[$1.intern]
  end

  def parse(source=nil, &block)
    (source || @source || ARGF).grep(/^(.)((\S+)(?:\s+(.+))?)/) do
      cmd   = $1
      text  = (cmd_to_sym(cmd) == :text) ? eval(%|"#{$2}"|) : $2
      name  = $3
      value = $4
      (block || listener).(cmd, name, value, text)
    end
  end
end

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

#
# example 2
#
PYXParser.new do |parser|
  parser.stag_listener do |name|
    puts "START TAG: '#{name}'"
  end

  parser.etag_listener do |name|
    puts "END TAG: '#{name}'"
  end

  parser.attr_listener do |name, value|
    puts %|Attribute: #{name}="#{value}"|
  end

  parser.text_listener do |name, value, text|
    puts "TEXT: '#{text}'"
  end

  parser.inst_listener do |name, value, text|
    puts "PI: '#{text}'"
  end

  parser.parse
end
=begin
=end

=begin
#
# example 3
#
stack = [doc = [nil,nil,[]]]
ATTRS = 1
CHILD = 2

PYXParser.new do |parser|
  parser.stag_listener do |name|
    stack.last[CHILD] << me = [name, {}, []]
    stack << me
  end
  parser.etag_listener do |name|
    stack.pop
  end

  parser.attr_listener do |name, value|
    stack.last[ATTRS][name] = value
  end

  parser.text_listener do |name, value, text|
    stack.last[CHILD] << text
  end

  parser.inst_listener do |name, value, text|
    # ignore
  end

  parser.parse
end

require 'pp'

pp doc[CHILD]

=end

=begin
#
# sample PYX 1
#
print <<-PYX1
?xml version="1.0"
(html
(head
Alang ja
(title
-pyx sample 1
)title
)head

(body
(p
-これは\tPYX 形式のサンプルです.
)p
)body
)html
PYX1
=end

=begin
#
# sample PYX 2
#
print <<-PYX2
?xml version="1.0"
(html
(head
Alang ja
(title
-pyx sample 2
)title
)head

(body
(p
-PYX は行指向の表現形式です.
)p
(table

(tr
(th
-(
)th
(td
-開始タグ
)td
)tr

(tr
(th
-)
)th
(td
-終了タグ
)td
)tr

(tr
(th
-A
)th
(td
-属性
)td
)tr

(tr
(th
--
)th
(td
-テキスト
)td
)tr

(tr
(th
-?
)th
(td
-PI
)td
)tr

)table
)body
  
end

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

上の例の PYX形式ファイルを入力とした時の実行結果です。

PI: 'xml version="1.0"'
START TAG: 'html'
START TAG: 'head'
Attribute: lang="ja"
START TAG: 'title'
TEXT: 'pyx sample 1'
END TAG: 'title'
END TAG: 'head'
START TAG: 'body'
START TAG: 'p'
TEXT: 'これは	PYX 形式のサンプルです.'
END TAG: 'p'
END TAG: 'body'
END TAG: 'html'