1 require 'nokogiri'
2
3 # = XmlMini Nokogiri implementation using a SAX-based parser
4 module ActiveSupport
5 module XmlMini_NokogiriSAX
6 extend self
7
8 # Class that will build the hash while the XML document
9 # is being parsed using SAX events.
10 class HashBuilder < Nokogiri::XML::SAX::Document
11
12 CONTENT_KEY = '__content__'.freeze
13 HASH_SIZE_KEY = '__hash_size__'.freeze
14
15 attr_reader :hash
16
17 def current_hash
18 @hash_stack.last
19 end
20
21 def start_document
22 @hash = {}
23 @hash_stack = [@hash]
24 end
25
26 def end_document
27 raise "Parse stack not empty!" if @hash_stack.size > 1
28 end
29
30 def error(error_message)
31 raise error_message
32 end
33
34 def start_element(name, attrs = [])
35 new_hash = { CONTENT_KEY => '' }
36 new_hash[attrs.shift] = attrs.shift while attrs.length > 0
37 new_hash[HASH_SIZE_KEY] = new_hash.size + 1
38
39 case current_hash[name]
40 when Array then current_hash[name] << new_hash
41 when Hash then current_hash[name] = [current_hash[name], new_hash]
42 when nil then current_hash[name] = new_hash
43 end
44
45 @hash_stack.push(new_hash)
46 end
47
48 def end_element(name)
49 if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ''
50 current_hash.delete(CONTENT_KEY)
51 end
52 @hash_stack.pop
53 end
54
55 def characters(string)
56 current_hash[CONTENT_KEY] << string
57 end
58
59 alias_method :cdata_block, :characters
60 end
61
62 attr_accessor :document_class
63 self.document_class = HashBuilder
64
65 def parse(string)
66 return {} if string.blank?
67 document = self.document_class.new
68 parser = Nokogiri::XML::SAX::Parser.new(document)
69 parser.parse(string)
70 document.hash
71 end
72 end
73 en