smacks-apricoteatsgorilla 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/apricoteatsgorilla.rb +98 -86
- data/tests/hash_to_xml_test.rb +4 -0
- metadata +1 -1
data/lib/apricoteatsgorilla.rb
CHANGED
@@ -10,108 +10,120 @@ require 'hpricot'
|
|
10
10
|
# but it quickly evolved into a more general translation tool.
|
11
11
|
class ApricotEatsGorilla
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
# a custom root node. The root node itself will not be included in the Hash.
|
16
|
-
#
|
17
|
-
# E.g.
|
18
|
-
# xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>"
|
19
|
-
# ApricotEatsGorilla.xml_to_hash(xml)
|
20
|
-
# # => { "hates" => "appletini", "likes" => "beer" }
|
21
|
-
def self.xml_to_hash(xml, root_node = nil)
|
22
|
-
xml = clean_xml(xml)
|
23
|
-
doc = Hpricot.XML(xml)
|
24
|
-
root = root_node ? doc.at(root_node) : doc.root
|
25
|
-
xml_node_to_hash(root)
|
26
|
-
end
|
13
|
+
class << self
|
14
|
+
attr_accessor :sort_keys
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
16
|
+
# Converts XML into a Hash. Starts parsing at root node by default.
|
17
|
+
# Call with XPath expession (Hpricot search) as second parameter to define
|
18
|
+
# a custom root node. The root node itself will not be included in the Hash.
|
19
|
+
#
|
20
|
+
# E.g.
|
21
|
+
# xml = "<dude><likes>beer</likes><hates>appletini</hates></dude>"
|
22
|
+
# ApricotEatsGorilla.xml_to_hash(xml)
|
23
|
+
# # => { "hates" => "appletini", "likes" => "beer" }
|
24
|
+
def xml_to_hash(xml, root_node = nil)
|
25
|
+
xml = clean_xml(xml)
|
26
|
+
doc = Hpricot.XML(xml)
|
27
|
+
root = root_node ? doc.at(root_node) : doc.root
|
28
|
+
xml_node_to_hash(root)
|
29
|
+
end
|
37
30
|
|
38
|
-
|
31
|
+
# Converts a Hash to XML.
|
32
|
+
#
|
33
|
+
# E.g.
|
34
|
+
# hash = { "dude" => { "likes" => "beer", "hates" => "appletini" } }
|
35
|
+
# ApricotEatsGorilla.hash_to_xml(hash)
|
36
|
+
# # => "<dude><hates>appletini</hates><likes>beer</likes></dude>"
|
37
|
+
def hash_to_xml(hash)
|
38
|
+
nested_data_to_xml(hash.keys.first, hash.values.first)
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
def self.xml_node_to_hash(node)
|
42
|
-
this_node = {}
|
41
|
+
private
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
elsif child.children.size == 1 && child.children.first.text?
|
48
|
-
key, value = child.name, string_to_bool?(child.children.first.raw_string)
|
49
|
-
else
|
50
|
-
key, value = child.name, xml_node_to_hash(child)
|
51
|
-
end
|
43
|
+
# Converts XML into a Hash.
|
44
|
+
def xml_node_to_hash(node)
|
45
|
+
this_node = {}
|
52
46
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
this_node[key] = value
|
47
|
+
node.each_child do |child|
|
48
|
+
if child.children.nil? || child.children.empty?
|
49
|
+
key, value = child.name, nil
|
50
|
+
elsif child.children.size == 1 && child.children.first.text?
|
51
|
+
key, value = child.name, string_to_bool?(child.children.first.inner_text)
|
59
52
|
else
|
60
|
-
|
53
|
+
key, value = child.name, xml_node_to_hash(child)
|
54
|
+
end
|
55
|
+
|
56
|
+
current = this_node[key]
|
57
|
+
case current
|
58
|
+
when Array:
|
59
|
+
this_node[key] << value
|
60
|
+
when nil
|
61
|
+
this_node[key] = value
|
62
|
+
else
|
63
|
+
this_node[key] = [current.dup, value]
|
64
|
+
end
|
61
65
|
end
|
66
|
+
|
67
|
+
this_node
|
62
68
|
end
|
63
69
|
|
64
|
-
|
65
|
-
|
70
|
+
# Converts a Hash to XML.
|
71
|
+
def nested_data_to_xml(name, item)
|
72
|
+
case item
|
73
|
+
when String
|
74
|
+
make_tag(name) { item }
|
75
|
+
when Array
|
76
|
+
item.map { |subitem| nested_data_to_xml(name, subitem) }.join
|
77
|
+
when Hash
|
78
|
+
make_tag(name) do
|
79
|
+
opt_order(item).map { |tag, value|
|
80
|
+
case value
|
81
|
+
when String
|
82
|
+
make_tag(tag) { value }
|
83
|
+
when Array
|
84
|
+
value.map { |subitem| nested_data_to_xml(tag, subitem) }.join
|
85
|
+
when Hash
|
86
|
+
nested_data_to_xml(tag, value)
|
87
|
+
end
|
88
|
+
}.join
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
66
92
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
make_tag(name) do
|
76
|
-
item.map { |tag, value|
|
77
|
-
case value
|
78
|
-
when String
|
79
|
-
make_tag(tag) { value }
|
80
|
-
when Array
|
81
|
-
value.map { |subitem| nested_data_to_xml(tag, subitem) }.join
|
82
|
-
when Hash
|
83
|
-
nested_data_to_xml(tag, value)
|
84
|
-
end
|
85
|
-
}.join
|
93
|
+
# Helper to create an XML tag. Expects a block for tag content.
|
94
|
+
# Defaults to an empty element tag in case no block was supplied.
|
95
|
+
def make_tag(name)
|
96
|
+
body = yield
|
97
|
+
if body && !body.empty?
|
98
|
+
"<#{name}>" << body << "</#{name}>"
|
99
|
+
else
|
100
|
+
"<#{name} />"
|
86
101
|
end
|
87
102
|
end
|
88
|
-
end
|
89
103
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
else
|
97
|
-
"<#{name} />"
|
104
|
+
def opt_order(hash)
|
105
|
+
if sort_keys
|
106
|
+
hash.sort_by{ |kv| kv.first }
|
107
|
+
else
|
108
|
+
hash
|
109
|
+
end
|
98
110
|
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Helper to remove line breaks and whitespace between XML tags.
|
102
|
-
def self.clean_xml(xml)
|
103
|
-
xml = xml.gsub(/\n+/, "")
|
104
|
-
xml = xml.gsub(/(>)\s*(<)/, '\1\2')
|
105
|
-
end
|
106
111
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
string
|
113
|
-
end
|
112
|
+
# Helper to remove line breaks and whitespace between XML tags.
|
113
|
+
def clean_xml(xml)
|
114
|
+
xml = xml.gsub(/\n+/, "")
|
115
|
+
xml = xml.gsub(/(>)\s*(<)/, '\1\2')
|
116
|
+
end
|
114
117
|
|
118
|
+
# Helper to convert "true" and "false" strings to boolean values.
|
119
|
+
# Returns the original string in case it doesn't match "true" or "false".
|
120
|
+
def string_to_bool?(string)
|
121
|
+
return true if string == "true"
|
122
|
+
return false if string == "false"
|
123
|
+
string
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
115
127
|
end
|
116
128
|
|
117
129
|
# Shortcut method for translating between XML documents and Hashes.
|
data/tests/hash_to_xml_test.rb
CHANGED
@@ -4,6 +4,10 @@ require File.join(File.dirname(__FILE__), "..", "lib", "apricoteatsgorilla")
|
|
4
4
|
|
5
5
|
class HashToXmlTest < Test::Unit::TestCase
|
6
6
|
|
7
|
+
def setup
|
8
|
+
ApricotEatsGorilla.sort_keys = true
|
9
|
+
end
|
10
|
+
|
7
11
|
def test_dead_simple
|
8
12
|
hash = { "dude" => "likes beer" }
|
9
13
|
expected = "<dude>likes beer</dude>"
|