1 raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM =~ /java/
2
3 require 'jruby'
4 include Java
5
6 import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder
7 import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory
8 import java.io.StringReader unless defined? StringReader
9 import org.xml.sax.InputSource unless defined? InputSource
10 import org.xml.sax.Attributes unless defined? Attributes
11 import org.w3c.dom.Node unless defined? Node
12
13 # = XmlMini JRuby JDOM implementation
14 module ActiveSupport
15 module XmlMini_JDOM #:nodoc:
16 extend self
17
18 CONTENT_KEY = '__content__'.freeze
19
20 NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE
21 DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE
22 PROCESSING_INSTRUCTION_NODE TEXT_NODE}
23
24 node_type_map = {}
25 NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type }
26
27 # Parse an XML Document string into a simple hash using Java's jdom.
28 # string::
29 # XML Document string to parse
30 def parse(string)
31 if string.blank?
32 {}
33 else
34 @dbf = DocumentBuilderFactory.new_instance
35 xml_string_reader = StringReader.new(string)
36 xml_input_source = InputSource.new(xml_string_reader)
37 doc = @dbf.new_document_builder.parse(xml_input_source)
38 merge_element!({}, doc.document_element)
39 end
40 end
41
42 private
43
44 # Convert an XML element and merge into the hash
45 #
46 # hash::
47 # Hash to merge the converted element into.
48 # element::
49 # XML element to merge into hash
50 def merge_element!(hash, element)
51 merge!(hash, element.tag_name, collapse(element))
52 end
53
54 # Actually converts an XML document element into a data structure.
55 #
56 # element::
57 # The document element to be collapsed.
58 def collapse(element)
59 hash = get_attributes(element)
60
61 child_nodes = element.child_nodes
62 if child_nodes.length > 0
63 for i in 0...child_nodes.length
64 child = child_nodes.item(i)
65 merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
66 end
67 merge_texts!(hash, element) unless empty_content?(element)
68 hash
69 else
70 merge_texts!(hash, element)
71 end
72 end
73
74 # Merge all the texts of an element into the hash
75 #
76 # hash::
77 # Hash to add the converted emement to.
78 # element::
79 # XML element whose texts are to me merged into the hash
80 def merge_texts!(hash, element)
81 text_children = texts(element)
82 if text_children.join.empty?
83 hash
84 else
85 # must use value to prevent double-escaping
86 merge!(hash, CONTENT_KEY, text_children.join)
87 end
88 end
89
90 # Adds a new key/value pair to an existing Hash. If the key to be added
91 # already exists and the existing value associated with key is not
92 # an Array, it will be wrapped in an Array. Then the new value is
93 # appended to that Array.
94 #
95 # hash::
96 # Hash to add key/value pair to.
97 # key::
98 # Key to be added.
99 # value::
100 # Value to be associated with key.
101 def merge!(hash, key, value)
102 if hash.has_key?(key)
103 if hash[key].instance_of?(Array)
104 hash[key] << value
105 else
106 hash[key] = [hash[key], value]
107 end
108 elsif value.instance_of?(Array)
109 hash[key] = [value]
110 else
111 hash[key] = value
112 end
113 hash
114 end
115
116 # Converts the attributes array of an XML element into a hash.
117 # Returns an empty Hash if node has no attributes.
118 #
119 # element::
120 # XML element to extract attributes from.
121 def get_attributes(element)
122 attribute_hash = {}
123 attributes = element.attributes
124 for i in 0...attributes.length
125 attribute_hash[attributes.item(i).name] = attributes.item(i).value
126 end
127 attribute_hash
128 end
129
130 # Determines if a document element has text content
131 #
132 # element::
133 # XML element to be checked.
134 def texts(element)
135 texts = []
136 child_nodes = element.child_nodes
137 for i in 0...child_nodes.length
138 item = child_nodes.item(i)
139 if item.node_type == Node.TEXT_NODE
140 texts << item.get_data
141 end
142 end
143 texts
144 end
145
146 # Determines if a document element has text content
147 #
148 # element::
149 # XML element to be checked.
150 def empty_content?(element)
151 text = ''
152 child_nodes = element.child_nodes
153 for i in 0...child_nodes.length
154 item = child_nodes.item(i)
155 if item.node_type == Node.TEXT_NODE
156 text << item.get_data.strip
157 end
158 end
159 text.strip.length == 0
160 end
161 end
162 end