แปลง xml (cn140 in ruby)

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.
  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.

โจทย์ http://www.codenone.com/node/140
ทาง python เขาใช้ SAX ไปแล้ว
ดังนั้นเราลองหันไปใช้ DOM บ้าง

require 'rexml/document'
include REXML
 
src =<<-EOF
<node>  
  <node level="0" type="c" name="toplevel"/>  
  <node level="1" type="i" name="1. item"/>  
  <node level="1" type="c" name="2. container"/>  
  <node level="2" type="i" name="2.1 item"/>  
  <node level="2" type="i" name="2.2 item"/>  
  <node level="1" type="i" name="3.  item"/>
  <node level="1" type="c" name="4. container"/>  
  <node level="2" type="i" name="4.1 item"/>  
  <node level="2" type="c" name="4.2 container"/>  
  <node level="3" type="i" name="4.2.1 item"/> 
</node>
EOF
 
def solve(doc)
  stack = [Element.new("node")]
  old_level = 0
  doc.root.each_element('//node/node') do |node|
    level = node.attributes['level'].to_i
    node.attributes.delete('level')
    (old_level-level).times {stack.pop} if level < old_level
    stack.last << node  
    stack << node if node.attributes['type'] == 'c'
    old_level = level
  end
  stack.first
end
 
doc = Document.new(src)
puts solve(doc)

ไม่รู้มี bug หรือเปล่า

veer's picture

(ก่อนจะทำโจทย์) ผมนึกถึง rexml เหมือนกันนะ แต่นึกถึง code แบบนี้ไม่ออก

sugree's picture

ว่าแล้วเชียว DOM ต้องง่ายกว่า

SAX มีข้อดีตรงความเร็ว, ไม่กิน memory
DOM มีข้อดีตรงความง่ายในการใช้

ก็เลยมีคนทำ XML Pull Parser
parse แบบเดียวกับ SAX แต่แทนที่จะเป็น push (callback)
ก็เป็น pull แทน (ทำให้ดึง element ออกมาได้ง่ายกว่า)

ผมลองเขียนโดยใช้ pull parser บ้าง
โปรแกรมนี้จะ parse xml และให้ output เป็น DOM object
(่โชคร้ายที่โจทย์นี้ ไม่ส่งเสริมให้เห็นความสวยงามของ pull เลย)

## ผม open class parser แล้วใส่ helper ที่ช่วยดึง 
## tag ออกมาง่ายๆ
class REXML::Parsers::PullParser 
  def nextTag(name)
    while has_next?
      # อยากได้อะไรจาก xml, ก็ต้องดึงออกมาเอง
      event = pull
      return event if event.start_element? && event[0] == name        
    end    
  end
end
 
class Tranform
  include REXML
 
  def initialize(src)
    @parser = REXML::Parsers::PullParser.new(src)    
    @stack = [Element.new('node')]
  end
 
  ## algorithm หลักอยู่ตรงนี้
  def exec()
    ## skip first node
    @parser.nextTag('node')
 
    eachNodes do |node, level, old_level|
      if level <= old_level
        (old_level - level + 1).times {@stack.pop}
      end
      @stack.last << node 
      @stack << node
    end
 
    @stack.first
  end
 
  private 
  def eachNodes
    old_level = -1
    while (1)
      event = @parser.nextTag('node')
      if event
        node = Element.new('node')
        level = (event[1]['level']).to_i
        node.attributes['type'] = event[1]['type']
        node.attributes['name'] = event[1]['name']
        yield node, level, old_level
        old_level = level
      else
        return
      end
    end
  end
end

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น