1 require 'libxml'
2
3 # = XmlMini LibXML implementation using a SAX-based parser
4 module ActiveSupport
5 module XmlMini_LibXMLSAX
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
11
12 include LibXML::XML::SaxParser::Callbacks
13
14 CONTENT_KEY = '__content__'.freeze
15 HASH_SIZE_KEY = '__hash_size__'.freeze
16
17 attr_reader :hash
18
19 def current_hash
20 @hash_stack.last
21 end
22
23 def on_start_document
24 @hash = { CONTENT_KEY => '' }
25 @hash_stack = [@hash]
26 end
27
28 def on_end_document
29 @hash = @hash_stack.pop
30 @hash.delete(CONTENT_KEY)
31 end
32
33 def on_start_element(name, attrs = {})
34 new_hash = { CONTENT_KEY => '' }.merge(attrs)
35 new_hash[HASH_SIZE_KEY] = new_hash.size + 1
36
37 case current_hash[name]
38 when Array then current_hash[name] << new_hash
39 when Hash then current_hash[name] = [current_hash[name], new_hash]
40 when nil then current_hash[name] = new_hash
41 end
42
43 @hash_stack.push(new_hash)
44 end
45
46 def on_end_element(name)
47 if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == ''
48 current_hash.delete(CONTENT_KEY)
49 end
50 @hash_stack.pop
51 end
52
53 def on_characters(string)
54 current_hash[CONTENT_KEY] << string
55 end
56
57 alias_method :on_cdata_block, :on_characters
58 end
59
60 attr_accessor :document_class
61 self.document_class = HashBuilder
62
63 def parse(string)
64 return {} if string.blank?
65 LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
66 parser = LibXML::XML::SaxParser.string(string)
67 document = self.document_class.new
68
69 parser.callbacks = document
70 parser.parse
71 document.hash
72 end
73 end
74 en