Skip to content

Commit fea8d9d

Browse files
josephholstenjeremy
authored andcommitted
Extract XmlMini from XmlSimple. [#1474 state:committed]
Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
1 parent 8d2ca7d commit fea8d9d

File tree

1 file changed

+106
-21
lines changed

1 file changed

+106
-21
lines changed

activesupport/lib/active_support/core_ext/hash/conversions.rb

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,116 @@
11
require 'date'
22

3-
# Locked down XmlSimple#xml_in_string
4-
class XmlSimple
5-
# Same as xml_in but doesn't try to smartly shoot itself in the foot.
6-
def xml_in_string(string, options = nil)
7-
handle_options('in', options)
3+
# = XmlMini
4+
# This is a derivitive work of XmlSimple 1.0.11
5+
# Author:: Joseph Holsten <joseph@josephholsten.com>
6+
# Copyright:: Copyright (c) 2008 Joseph Holsten
7+
# Copyright:: Copyright (c) 2003-2006 Maik Schmidt <contact@maik-schmidt.de>
8+
# License:: Distributes under the same terms as Ruby.
9+
class XmlMini
10+
require 'rexml/document'
11+
include REXML
812

9-
@doc = parse(string)
10-
result = collapse(@doc.root)
13+
CONTENT_KEY = '__content__'
1114

12-
if @options['keeproot']
13-
merge({}, @doc.root.name, result)
15+
# Parse an XML Document string into a simple hash
16+
#
17+
# Same as XmlSimple::xml_in but doesn't shoot itself in the foot,
18+
# and uses the defaults from ActiveSupport
19+
#
20+
# string::
21+
# XML Document string to parse
22+
#
23+
def self.parse(string)
24+
doc = REXML::Document.new(string)
25+
merge_element!({}, doc.root)
26+
end
27+
28+
private
29+
# Convert an XML element and merge into the hash
30+
#
31+
# hash::
32+
# Hash to merge the converted element into.
33+
# element::
34+
# XML element to merge into hash
35+
def self.merge_element!(hash, element)
36+
merge!(hash, element.name, collapse(element))
37+
end
38+
39+
# Actually converts an XML document element into a data structure.
40+
#
41+
# element::
42+
# The document element to be collapsed.
43+
def self.collapse(element)
44+
hash = get_attributes(element)
45+
46+
if element.has_elements?
47+
element.each_element {|child| merge_element!(hash, child) }
48+
merge_texts!(hash, element) unless empty_content?(element)
1449
else
15-
result
50+
return merge_texts!(hash, element)
1651
end
52+
hash
1753
end
1854

19-
def self.xml_in_string(string, options = nil)
20-
new.xml_in_string(string, options)
55+
# Merge all the texts of an element into the hash
56+
#
57+
# hash::
58+
# Hash to add the converted emement to.
59+
# element::
60+
# XML element whose texts are to me merged into the hash
61+
def self.merge_texts!(hash, element)
62+
unless element.has_text?
63+
hash
64+
else
65+
# must use value to prevent double-escaping
66+
text_values = element.texts.map {|t| t.value }
67+
merge!(hash, CONTENT_KEY, text_values.join)
68+
end
69+
end
70+
71+
# Adds a new key/value pair to an existing Hash. If the key to be added
72+
# already exists and the existing value associated with key is not
73+
# an Array, it will be wrapped in an Array. Then the new value is
74+
# appended to that Array.
75+
#
76+
# hash::
77+
# Hash to add key/value pair to.
78+
# key::
79+
# Key to be added.
80+
# value::
81+
# Value to be associated with key.
82+
def self.merge!(hash, key, value)
83+
if hash.has_key?(key)
84+
if hash[key].instance_of?(Array)
85+
hash[key] << value
86+
else
87+
hash[key] = [ hash[key], value ]
88+
end
89+
elsif value.instance_of?(Array)
90+
hash[key] = [ value ]
91+
else
92+
hash[key] = value
93+
end
94+
hash
95+
end
96+
97+
# Converts the attributes array of an XML element into a hash.
98+
# Returns an empty Hash if node has no attributes.
99+
#
100+
# element::
101+
# XML element to extract attributes from.
102+
def self.get_attributes(element)
103+
attributes = {}
104+
element.attributes.each { |n,v| attributes[n] = v }
105+
attributes
106+
end
107+
108+
# Determines if a document element has text content
109+
#
110+
# element::
111+
# XML element to be checked.
112+
def self.empty_content?(element)
113+
element.texts.join.strip.empty?
21114
end
22115
end
23116

@@ -166,15 +259,7 @@ def to_xml(options = {})
166259

167260
module ClassMethods
168261
def from_xml(xml)
169-
require 'xmlsimple'
170-
171-
# TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
172-
typecast_xml_value(undasherize_keys(XmlSimple.xml_in_string(xml,
173-
'forcearray' => false,
174-
'forcecontent' => true,
175-
'keeproot' => true,
176-
'contentkey' => '__content__')
177-
))
262+
typecast_xml_value(undasherize_keys(XmlMini.parse(xml)))
178263
end
179264

180265
private

0 commit comments

Comments
 (0)