xml-fu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .rvmrc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ### 0.1.0
2
+
3
+ * Initial version. born as a replacement of
4
+ [Gyoku](http://www.rubygems.org/gems/gyoku)
5
+ with corrected assumptions about Array values and
6
+ no need for meta tags such as:
7
+ * :order!
8
+ * :attributes!
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xml-fu.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 Ryan Johnson
2
+ Copyright (c) 2010 Daniel Harrington (for logic inspired by Gyoku)
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,291 @@
1
+ # Xml::Fu
2
+
3
+ Convert Ruby Hashes to XML
4
+
5
+ A hash is meant to be a structured set of data. So is XML. The two are very similar in that they have
6
+ the capability of nesting information within a tree structure. With XML you have nodes. With Hashes, you
7
+ have key/value pairs. The value of an XML node is referenced by its parent's name. A hash value is referenced
8
+ by its key. This basic lesson tells the majority of what you need to know about creating XML via Hashes in
9
+ Ruby using the XmlFu gem.
10
+
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'xml-fu'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install xml-fu
25
+
26
+
27
+ ## Hash Keys
28
+
29
+ Hash keys are translated into XML nodes (whether it be a document node or attribute node).
30
+
31
+
32
+ ### Translation
33
+
34
+ With Ruby, a hash key may be a string or a symbol. XmlFu will convert the symbols into an XML safe name
35
+ by lower camel-casing them. So :foo\_bar will become "fooBar". You may change the conversion algorithm to your
36
+ liking by setting the XmlFu.symbol\_conversion\_algorithm to a lambda or proc of your liking.
37
+
38
+
39
+ #### Built-In Algorithms
40
+
41
+ * :lower\_camelcase **(default)**
42
+ * :camelcase
43
+ * :none (result of :sym.to\_s)
44
+ * :snake\_case (alias for :none)
45
+
46
+ ```ruby
47
+ # Built-in Algorithm
48
+ XmlFu.symbol_conversion_algorithm(:camelcase)
49
+
50
+ XmlFu.xml( :foo => "bar" ) #=> "<foo>bar</foo>"
51
+ XmlFu.xml( "foo" => "bar" ) #=> "<foo>bar</foo>"
52
+ # :foo and "foo" both translate to <foo>
53
+
54
+ # Custom Algorithm
55
+ XmlFu.symbol_conversion_algorithm {|sym| sym.downcase }
56
+ ```
57
+
58
+ ### Types of Nodes
59
+
60
+ Because there are multiple types of XML nodes, there are also multiple types of keys to denote them.
61
+
62
+
63
+ #### Self-Closing Nodes (key/)
64
+
65
+ By default, XmlFu assumes that all XML nodes will contain closing tags. However, if you want to explicitly
66
+ create a self-closing node, use the following syntax when you define the key.
67
+
68
+ ``` ruby
69
+ XmlFu.xml("foo/" => "bar") #=> <foo/>
70
+ ```
71
+
72
+ One thing to take note of this syntax is that XmlFu will ignore ANY value you throw at it if the key syntax
73
+ denotes a self-closing tag. This is because a self-closing tag cannot have any contents (hence the use for
74
+ a self-closing tag).
75
+
76
+
77
+ #### Unescaped Content Nodes (key!)
78
+
79
+ By default, if you pass a pure string as a value, special characters will be escaped to keep the XML compliant.
80
+ If you know that the string is valid XML and can be trusted, you can add the exclamation point to the end of
81
+ the key name to denote that XmlFu should NOT escape special characters in the value.
82
+
83
+ ```ruby
84
+ # Default Functionality (Escaped Characters)
85
+ XmlFu.xml("foo" => "<bar/>") #=> "<foo>&lt;bar/&gt;</foo>"
86
+
87
+ # Unescaped Characters
88
+ XmlFu.xml("foo!" => "<bar/>") #=> "<foo><bar/></foo>"
89
+ ```
90
+
91
+
92
+ #### Attribute Node (@key)
93
+
94
+ Yes, the attributes of an XML node are nodes themselves, so we need a way of defining them. Since XPath syntax
95
+ uses @ to denote an attribute, so does XmlFu.
96
+
97
+ ``` ruby
98
+ XmlFu.xml(:agent => {
99
+ "@id" => "007",
100
+ "FirstName" => "James",
101
+ "LastName" => "Bond"
102
+ })
103
+ #=> <agent id="007"><FirstName>James</FirstName><LastName>Bond</LastName></agent>
104
+ ```
105
+
106
+
107
+ ## Hash Values
108
+
109
+ The value in a key/value pair describes the key/node. Different value types determine the extent of this description.
110
+
111
+
112
+ ### Simple Values
113
+
114
+ Simple value types describe the contents of the XML node.
115
+
116
+
117
+ #### Strings
118
+
119
+ ``` ruby
120
+ XmlFu.xml( :foo => "bar" ) #=> "<foo>bar</foo>"
121
+ XmlFu.xml( "foo" => "bar" ) #=> "<foo>bar</foo>"
122
+ ```
123
+
124
+
125
+ #### Numbers
126
+
127
+ ``` ruby
128
+ XmlFu.xml( :foo => 0 ) #=> "<foo>0</foo>"
129
+ XmlFu.xml( :pi => 3.14159 ) #=> "<pi>3.14159</pi>"
130
+ ```
131
+
132
+
133
+ #### Nil
134
+
135
+ ``` ruby
136
+ XmlFu.xml( :foo => nil ) #=> "<foo xsi:nil=\"true\"/>"
137
+ ```
138
+
139
+
140
+ ### Hashes
141
+
142
+ Hash are parsed for their translated values prior to returning a XmlFu value.
143
+
144
+ ```ruby
145
+ XmlFu.xml(:foo => {:bar => {:biz => "bang"} })
146
+ #=> "<foo><bar><biz>bang</biz></bar></foo>"
147
+ ```
148
+
149
+ #### Content in Hash (=)
150
+
151
+ Should you require setting node attributes as well as setting the value of the XML node, you may use the "="
152
+ key in a nested hash to denote explicit content.
153
+
154
+ ```ruby
155
+ XmlFu.xml(:agent => {"@id" => "007", "=" => "James Bond"})
156
+ #=> "<agent id=\"007\">James Bond</agent>"
157
+ ```
158
+
159
+ This key will not get around the self-closing node rule. The only nodes that will be used in this case will be
160
+ attribute nodes and additional content will be ignored.
161
+
162
+ ```ruby
163
+ XmlFu.xml("foo/" => {"@id" => "123", "=" => "You can't see me."})
164
+ #=> "<foo id=\"123\"/>"
165
+ ```
166
+
167
+
168
+ ### Arrays
169
+
170
+ Since the value in a key/value pair is (for the most part) used as the contents of a key/node, there are some
171
+ assumptions that XmlFu makes when dealing with Array values.
172
+
173
+ * For a typical key, the contents of the array are considered to be nodes to be contained within the <key> node.
174
+
175
+
176
+ #### Array of Hashes
177
+
178
+ ``` ruby
179
+ XmlFu.xml( "SecretAgents" => [
180
+ { "agent/" => { "@id"=>"006", "@name"=>"Alec Trevelyan" } },
181
+ { "agent/" => { "@id"=>"007", "@name"=>"James Bond" } }
182
+ ])
183
+ #=> "<SecretAgents><agent name=\"Alec Trevelyan\" id=\"006\"/><agent name=\"James Bond\" id=\"007\"/></SecretAgents>"
184
+ ```
185
+
186
+
187
+ #### Alternate Array of Hashes (key\*)
188
+
189
+ There comes a time that you may want to declare the contents of an array as a collection of items denoted by the
190
+ key name. Using the asterisk (also known for multiplication --- hence multiple keys) we denote that we want a
191
+ collection of &lt;key&gt; nodes.
192
+
193
+ ```ruby
194
+ XmlFu.xml( "person*" => ["Bob", "Sheila"] )
195
+ #=> "<person>Bob</person><person>Sheila</person>"
196
+ ```
197
+
198
+ In this case, the value of "person*" is an array of two names. These names are to be the contents of multiple
199
+ &lt;person&gt; nodes and the result is a set of sibling XML nodes with no parent.
200
+
201
+ How about a more complex example:
202
+
203
+ ```ruby
204
+ XmlFu.xml(
205
+ "person*" => {
206
+ "@foo" => "bar",
207
+ "=" => [
208
+ {"@foo" => "nope", "=" => "Bob"},
209
+ "Sheila"
210
+ ]
211
+ }
212
+ )
213
+ #=> "<person foo=\"nope\">Bob</person><person foo=\"bar\">Sheila</person>"
214
+ ```
215
+
216
+ *This is getting interesting, isn't it?* In this example, we are setting a default "foo" attribute on each of the
217
+ items in the collection of &lt;person&gt; nodes. However, you'll notice that we overwrote the default "foo" with Bob.
218
+
219
+
220
+ #### Array of Arrays
221
+
222
+ Array values are flattened prior to translation, to reduce the need to iterate over nested arrays.
223
+
224
+ ```ruby
225
+ XmlFu.xml(
226
+ :foo => [
227
+ [{"a/" => nil}, {"b/" => nil}],
228
+ {"c/" => nil},
229
+ [
230
+ [{"d/" => nil}, {"e/" => nil}],
231
+ {"f/" => nil}
232
+ ]
233
+ ]
234
+ )
235
+ #=> "<foo><a/><b/><c/><d/><e/><f/></foo>"
236
+ ```
237
+
238
+ :foo in this case, is the parent node of it's contents
239
+
240
+
241
+ #### Array of Mixed Types
242
+
243
+ Since with simple values, you cannot infer the value of their node container purely on their value, simple values
244
+ are currently ignored in arrays and only Hashes are translated.
245
+
246
+ ``` ruby
247
+ "foo" => [
248
+ {:bar => "biz"},
249
+ nil, # ignored
250
+ true, # ignored
251
+ false, # ignored
252
+ 42, # ignored
253
+ 3.14, # ignored
254
+ "simple string", # ignored
255
+ ['another','array','of','values'] # ignored
256
+ ]
257
+ #=> "<foo><bar>biz</bar></foo>"
258
+ ```
259
+
260
+
261
+ ### Cheat Sheet
262
+
263
+ #### Key
264
+ 1. if key denotes self-closing node (key/)
265
+ * attributes are preserved with Hash values
266
+ * value and "=" values are ignored
267
+ 2. if key denotes collection (key*) with Array value
268
+ * Array is flattened
269
+ * Only Hash and Simple values are translated
270
+ * Hashes may override default attributes set by parent
271
+ * **(applies to Array values only)**
272
+ 3. if key denotes contents (key) with Array value
273
+ * Array is flattened
274
+ * Only Hash items in array are translated
275
+
276
+ #### Value
277
+ 1. if value is Hash:
278
+ * "@" keys are attributes of the node
279
+ * "=" key can be used in conjunction with any "@" keys to specify content of node
280
+ 3. if value is simple value:
281
+ * it is content of <key> node
282
+ * **unless:** key denotes a self-closing node
283
+
284
+
285
+ ## Contributing
286
+
287
+ 1. Fork it
288
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
289
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
290
+ 4. Push to the branch (`git push origin my-new-feature`)
291
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
Binary file
@@ -0,0 +1,98 @@
1
+ require 'builder'
2
+ require 'xml-fu/hash'
3
+ require 'xml-fu/node'
4
+
5
+ module XmlFu
6
+
7
+ # Convert Array to XML String
8
+ class Array
9
+
10
+ # Custom exception class
11
+ class MissingKeyException < Exception; end
12
+
13
+ # Convert Array to XML String of sibling XML nodes
14
+ # @param [Array] array
15
+ # @param [Hash] options
16
+ # @option options [String] :content_type Either "collection" or "content". (defaults to "content")
17
+ # @option options [String, Symbol] :key Name of node.
18
+ # @option options [Hash] :attributes Possible hash of attributes to assign to nodes in array.
19
+ # @return String
20
+ def self.to_xml(array, options={})
21
+ each_with_xml(array, options) do |xml, key, item, attributes|
22
+ case options[:content_type].to_s
23
+ when "collection"
24
+ raise(MissingKeyException, "Key name missing for collection") if key.empty?
25
+
26
+ case
27
+ when key[-1,1] == "/"
28
+ xml << Node.new(key, nil, attributes).to_xml
29
+ when ::Hash === item
30
+ xml.tag!(key, attributes) { xml << Hash.to_xml(item,options) }
31
+ when ::Array === item
32
+ xml << Array.to_xml(item.flatten,options)
33
+ else
34
+ xml << Node.new(key, item, attributes).to_xml
35
+ end
36
+ else
37
+ # Array is content of node rather than collection of node elements
38
+ case
39
+ when ::Hash === item
40
+ xml << Hash.to_xml(item, options)
41
+ when ::Array === item
42
+ xml << Array.to_xml(item, options)
43
+ when XmlFu.infer_simple_value_nodes == true
44
+ xml << infer_node(item, attributes)
45
+ else
46
+ # only act on item if it responds to to_xml
47
+ xml << item.to_xml if item.respond_to?(:to_xml)
48
+ end
49
+ end
50
+ end
51
+ end#self.to_xml
52
+
53
+ # Future Functionality - VERY ALPHA STAGE!!!
54
+ # @todo Add node inferrance functionality
55
+ # @note Do not use if you want stable functionality
56
+ # @param item Simple Value
57
+ # @param [Hash] attributes Hash of attributes to assign to inferred node.
58
+ def self.infer_node(item, attributes={})
59
+ node_name = case item.class
60
+ when "TrueClass"
61
+ when "FalseClass"
62
+ "Boolean"
63
+ else
64
+ item.class
65
+ end
66
+ Node.new(node_name, item, attributes).to_xml
67
+ end#self.infer_node
68
+
69
+ # Convenience function to iterate over array items as well as
70
+ # providing a single location for logic
71
+ # @param [Array] arr Array to iterate over
72
+ # @param [Hash] opts Hash of options to pass to the iteration
73
+ def self.each_with_xml(arr, opts={})
74
+ xml = Builder::XmlMarkup.new
75
+
76
+ arr.each do |item|
77
+ key = opts.fetch(:key, "")
78
+ item_content = item
79
+
80
+ # Attributes reuires duplicate or child elements will
81
+ # contain attributes of their siblings.
82
+ attributes = (opts[:attributes] ? opts[:attributes].dup : {})
83
+
84
+ if item.respond_to?(:keys)
85
+ filtered = Hash.filter(item)
86
+ attributes = filtered.last
87
+ item_content = filtered.first
88
+ end
89
+
90
+ yield xml, key, item_content, attributes
91
+ end
92
+
93
+ xml.target!
94
+ end#self.each_with_xml
95
+
96
+ end#Array
97
+
98
+ end#XmlFu
@@ -0,0 +1,14 @@
1
+ # Redefined parts of the String class to suit our needs in the gem.
2
+ class String
3
+
4
+ # Returns the string in camelcase (with first character lowercase)
5
+ def lower_camelcase
6
+ self[0].chr.downcase + self.camelcase[1..-1]
7
+ end#lower_camelcase
8
+
9
+ # Returns the string in camelcase (with the first character uppercase)
10
+ def camelcase
11
+ self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
12
+ end#camelcase
13
+
14
+ end#String
@@ -0,0 +1,87 @@
1
+ require 'builder'
2
+ require 'xml-fu/array'
3
+ require 'xml-fu/node'
4
+
5
+ module XmlFu
6
+
7
+ # By definition, a hash is an UNORDERED list of key/value pairs
8
+ # There's no sense in trying to order the keys.
9
+ # If order is of concern, use Array.to_xml
10
+ class Hash
11
+
12
+ # Convert Hash to XML String
13
+ def self.to_xml(hash, options={})
14
+ each_with_xml hash do |xml, key, value, attributes|
15
+ # Use symbol conversion algorithm to set tag name
16
+ tag_name = ( Symbol === key ?
17
+ XmlFu::Node.symbol_conversion_algorithm.call(key) :
18
+ key.to_s )
19
+
20
+ case
21
+ when tag_name[-1,1] == "/"
22
+ xml << Node.new(tag_name, nil, attributes).to_xml
23
+ when ::Array === value
24
+ if tag_name[-1,1] == '*'
25
+ options.merge!({
26
+ :content_type => "collection",
27
+ :key => tag_name.chop,
28
+ :attributes => attributes
29
+ })
30
+ # Collection is merely a set of sibling nodes
31
+ xml << Array.to_xml(value.flatten, options)
32
+ else
33
+ # Contents will contain a parent node
34
+ xml.tag!(tag_name, attributes) { xml << Array.to_xml(value, options) }
35
+ end
36
+ when ::Hash === value
37
+ xml.tag!(tag_name, attributes) { xml << Hash.to_xml(value, options) }
38
+ else
39
+ xml << Node.new(tag_name, value, attributes).to_xml
40
+ end
41
+ end
42
+ end#self.to_xml
43
+
44
+
45
+ # Class method to filter out attributes and content
46
+ # from a given hash
47
+ def self.filter(hash)
48
+ attribs = {}
49
+ content = hash.dup
50
+
51
+ content.keys.select{|k| k =~ /^@/ }.each do |k|
52
+ attribs[k[1..-1]] = content.delete(k)
53
+ end
54
+
55
+ # Use _content value if defined
56
+ content = content.delete("=") || content
57
+
58
+ return [content, attribs]
59
+ end#self.filter
60
+
61
+ private
62
+
63
+ # Provides a convenience function to iterate over the hash
64
+ # Logic will filter out attribute and content keys from hash values
65
+ def self.each_with_xml(hash)
66
+ xml = Builder::XmlMarkup.new
67
+
68
+ hash.each do |key,value|
69
+ node_value = value
70
+ node_attrs = {}
71
+
72
+ # yank the attribute keys into their own hash
73
+ if value.respond_to?(:keys)
74
+ filtered = Hash.filter(value)
75
+ node_attrs = filtered.last
76
+ node_value = filtered.first
77
+ end
78
+
79
+ yield xml, key, node_value, node_attrs
80
+ end
81
+
82
+ xml.target!
83
+ end#self.each_with_xml
84
+
85
+ end#Hash
86
+
87
+ end#XmlFu
@@ -0,0 +1,143 @@
1
+ require 'builder'
2
+ require 'cgi'
3
+ require 'date'
4
+
5
+ require 'xml-fu/core_ext/string'
6
+
7
+ module XmlFu
8
+
9
+ # Class to contain logic for converting a key/value pair into an XML node
10
+ class Node
11
+
12
+ # Custom exception class
13
+ class InvalidAttributesException < Exception; end
14
+
15
+ # xs:dateTime format.
16
+ XS_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
17
+
18
+ # default set of algorithms to choose from
19
+ ALGORITHMS = {
20
+ :lower_camelcase => lambda { |sym| sym.to_s.lower_camelcase },
21
+ :camelcase => lambda { |sym| sym.to_s.camelcase },
22
+ :snakecase => lambda { |sym| sym.to_s },
23
+ :none => lambda { |sym| sym.to_s }
24
+ }
25
+
26
+ # Class method for retrieving global Symbol-to-string conversion algorithm
27
+ # @return [lambda]
28
+ def self.symbol_conversion_algorithm
29
+ @symbol_conversion_algorithm ||= ALGORITHMS[:lower_camelcase]
30
+ end#self.symbol_conversion_algorithm
31
+
32
+ # Class method for setting global Symbol-to-string conversion algorithm
33
+ # @param [lambda] algorithm Should accept a symbol as an argument and return a string
34
+ def self.symbol_conversion_algorithm=(algorithm)
35
+ algorithm = ALGORITHMS[algorithm] unless algorithm.respond_to?(:call)
36
+ raise(ArgumentError, "Invalid symbol conversion algorithm") unless algorithm
37
+ @symbol_conversion_algorithm = algorithm
38
+ end#self.symbol_conversion_algorithm=
39
+
40
+ attr_accessor :escape_xml
41
+ attr_accessor :self_closing
42
+
43
+ # Create XmlFu::Node object
44
+ # @param [String, Symbol] name Name of node
45
+ # @param value Simple Value or nil
46
+ # @param [Hash] attributes Optional hash of attributes to apply to XML Node
47
+ def initialize(name, value, attributes={})
48
+ @escape_xml = true
49
+ @self_closing = false
50
+ self.attributes = attributes
51
+ self.value = value
52
+ self.name = name
53
+ end#initialize
54
+
55
+ attr_reader :attributes
56
+ def attributes=(val)
57
+ if ::Hash === val
58
+ @attributes = val
59
+ else
60
+ raise(InvalidAttributesException, "Attempted to set attributes to non-hash value")
61
+ end
62
+ end
63
+
64
+ attr_reader :name
65
+ def name=(val)
66
+ use_name = val.dup
67
+
68
+ use_name = name_parse_special_characters(use_name)
69
+
70
+ # TODO: Add additional logic that Gyoku XmlKey puts in place
71
+
72
+ # remove ":" if name begins with ":" (i.e. no namespace)
73
+ use_name = use_name[1..-1] if use_name[0,1] == ":"
74
+
75
+ if Symbol === val
76
+ use_name = self.class.symbol_conversion_algorithm.call(use_name)
77
+ end
78
+
79
+ # Set name to remaining value
80
+ @name = "#{use_name}"
81
+ end#name=
82
+
83
+ # Converts name into proper XML node name
84
+ # @param [String, Symbol] val Raw name
85
+ def name_parse_special_characters(val)
86
+ use_this = val.dup
87
+
88
+ # Will this be a self closing node?
89
+ if use_this.to_s[-1,1] == '/'
90
+ @self_closing = true
91
+ use_this.chop!
92
+ end
93
+
94
+ # Will this node contain escaped XML?
95
+ if use_this.to_s[-1,1] == '!'
96
+ @escape_xml = false
97
+ use_this.chop!
98
+ end
99
+
100
+ # Ensure that we don't have special characters at end of name
101
+ while ["!","/","*"].include?(use_this.to_s[-1,1]) do
102
+ use_this.chop!
103
+ end
104
+
105
+ return use_this
106
+ end#name_parse_special_characters
107
+
108
+ # Custom Setter for @value instance method
109
+ def value=(val)
110
+ if DateTime === val || Time === val || Date === val
111
+ @value = val.strftime XS_DATETIME_FORMAT
112
+ elsif val.respond_to?(:to_datetime)
113
+ @value = val.to_datetime
114
+ elsif val.respond_to?(:call)
115
+ @value = val.call
116
+ elsif val.nil?
117
+ @value = nil
118
+ else
119
+ @value = val.to_s
120
+ end
121
+ end#value=
122
+
123
+ # @return [String, nil]
124
+ # Value can be nil, else it should return a String value.
125
+ def value
126
+ return CGI.escapeHTML(@value) if String === @value && @escape_xml
127
+ return @value
128
+ end#value
129
+
130
+ # Create XML String from XmlFu::Node object
131
+ def to_xml
132
+ xml = Builder::XmlMarkup.new
133
+ case
134
+ when @self_closing then xml.tag!(@name, @attributes)
135
+ when @value.nil? then xml.tag!(@name, @attributes.merge!("xsi:nil" => "true"))
136
+ else xml.tag!(@name, @attributes) { xml << self.value }
137
+ end
138
+ xml.target!
139
+ end#to_xml
140
+
141
+ end#Node
142
+
143
+ end#XmlFu
@@ -0,0 +1,3 @@
1
+ module XmlFu
2
+ VERSION = "0.1.0"
3
+ end
data/lib/xml-fu.rb ADDED
@@ -0,0 +1,53 @@
1
+ #require "xml-fu/version"
2
+ require "xml-fu/hash"
3
+ require "xml-fu/array"
4
+
5
+ module XmlFu
6
+ class << self
7
+
8
+ @@infer_simple_value_nodes = false
9
+
10
+ # Convert construct into XML
11
+ def xml(construct, options={})
12
+ case construct
13
+ when ::Hash then Hash.to_xml( construct.dup, options )
14
+ when ::Array then Array.to_xml( construct.dup, options )
15
+ end
16
+ end#convert
17
+
18
+ # @todo Add Nori-like parsing capability to convert XML back into XmlFu-compatible Hash/Array
19
+ # Parse XML into array of hashes. If XML used as input contains only sibling nodes, output
20
+ # will be array of hashes corresponding to those sibling nodes.
21
+ #
22
+ # <foo/><bar/> => [{"foo/" => ""}, {"bar/" => ""}]
23
+ #
24
+ # If XML used as input contains a full document with root node, output will be
25
+ # an array of one hash (the root node hash)
26
+ #
27
+ # <foo><bar/><baz/></foo> => [{"foo" => [{"bar/" => ""},{"baz/" => ""}] }]
28
+ def hash(xml, options)
29
+ end
30
+
31
+ def configure
32
+ yield self
33
+ end
34
+
35
+ # Set configuration option to be used with future releases
36
+ def infer_simple_value_nodes=(val)
37
+ @@infer_simple_value_nodes = val
38
+ end
39
+
40
+ # Configuration option to be used with future releases
41
+ # This option should allow for the inferrance of parent node names of simple value types
42
+ #
43
+ # Example:
44
+ # 1 => <Integer>1</Integer>
45
+ # true => <Boolean>true</Boolean>
46
+ #
47
+ # This is disabled by default as it is conflicting with working logic.
48
+ def infer_simple_value_nodes
49
+ return @@infer_simple_value_nodes
50
+ end
51
+
52
+ end#class<<self
53
+ end#XmlFu
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe XmlFu::Array do
4
+
5
+ describe ".to_xml" do
6
+ #after do
7
+ # XmlFu.infer_simple_value_nodes = false
8
+ #end
9
+
10
+ #it "should infer simple value type nodes with configuration turned on" do
11
+ # mixed_hash = {
12
+ # "foo" => [
13
+ # {:bar => "biz"},
14
+ # nil,
15
+ # true,
16
+ # false,
17
+ # 3.14,
18
+ # "simple string",
19
+ # ["another","array","of","values"]
20
+ # ]
21
+ # }
22
+ # XmlFu.infer_simple_value_nodes = true
23
+ # XmlFu.xml(mixed_hash).should_not == "<foo><bar>biz</bar></foo>"
24
+ #
25
+ # XmlFu.infer_simple_value_nodes = false
26
+ # XmlFu.xml(mixed_hash).should == "<foo><bar>biz</bar></foo>"
27
+ #
28
+ # XmlFu.infer_simple_value_nodes = false
29
+ #end
30
+
31
+ it "should flatten nested arrays properly" do
32
+ hash = {
33
+ :foo => [
34
+ [{"a/" => nil}, {"b/" => nil}],
35
+ {"c/" => nil},
36
+ [
37
+ [{"d/" => nil}, {"e/" => nil}],
38
+ {"f/" => nil}
39
+ ]
40
+ ]
41
+ }
42
+ expected = "<foo><a/><b/><c/><d/><e/><f/></foo>"
43
+ XmlFu.xml(hash).should == expected
44
+ end
45
+
46
+ describe "creating siblings with special key character (*)" do
47
+ it "should create siblings with special key character" do
48
+ hash = { "person*" => ["Bob", "Sheila"] }
49
+ XmlFu.xml(hash).should == "<person>Bob</person><person>Sheila</person>"
50
+ end
51
+
52
+ it "should create siblings with mixed attributes" do
53
+ hash = {
54
+ "person*" => {
55
+ "@foo" => "bar",
56
+ "=" => ["Bob", "Sheila"]
57
+ }
58
+ }
59
+ XmlFu.xml(hash).should == "<person foo=\"bar\">Bob</person><person foo=\"bar\">Sheila</person>"
60
+ end
61
+
62
+ it "should create siblings with complex mixed attributes" do
63
+ hash = {
64
+ "person*" => {
65
+ "@foo" => "bar",
66
+ "=" => [
67
+ {"@foo" => "nope", "=" => "Bob"},
68
+ "Sheila"
69
+ ]
70
+ }
71
+ }
72
+ XmlFu.xml(hash).should == "<person foo=\"nope\">Bob</person><person foo=\"bar\">Sheila</person>"
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe XmlFu::Hash do
4
+
5
+ describe ".to_xml" do
6
+ it "for a simple Hash" do
7
+ XmlFu::Hash.to_xml(:some => "body").should == "<some>body</some>"
8
+ end
9
+
10
+ it "for a nested Hash" do
11
+ XmlFu::Hash.to_xml(:foo => {:bar => "biz" }).should == "<foo><bar>biz</bar></foo>"
12
+ end
13
+
14
+ it "for a hash with multiple keys" do
15
+ XmlFu::Hash.to_xml(:first => "myself", :second => "the world").should include(
16
+ "<first>myself</first>",
17
+ "<second>the world</second>"
18
+ )
19
+ end
20
+
21
+
22
+ describe "for a hash value with '=' key defined" do
23
+ it "should ignore '=' for self-closing tag" do
24
+ hash = {"foo/" => {"@id" => "1", "=" => "PEEKABOO"}}
25
+ XmlFu.xml(hash).should == "<foo id=\"1\"/>"
26
+ end
27
+
28
+ it "should set additional content using '=' key" do
29
+ hash = {:foo => {"@id" => "1", "=" => "Hello"}}
30
+ XmlFu.xml(hash).should == "<foo id=\"1\">Hello</foo>"
31
+ end
32
+ end
33
+
34
+ describe "with a key that will contain multiple nodes" do
35
+ describe "when key explicitly denotes value is a collection" do
36
+ hash = { "foo*" => ["bar", "biz"] }
37
+ XmlFu::Hash.to_xml(hash).should == "<foo>bar</foo><foo>biz</foo>"
38
+ end
39
+
40
+ describe "when key denotes value contains children" do
41
+ it "for array consisting entirely of simple values" do
42
+ XmlFu::Hash.to_xml(:foo => ["bar", "biz"]).should == "<foo></foo>"
43
+ end
44
+
45
+ it "for array containing mix of simple and complex values" do
46
+ XmlFu::Hash.to_xml(:foo => ["bar", {:biz => "bang"}]).should == "<foo><biz>bang</biz></foo>"
47
+ end
48
+
49
+ it "for array containing complex values" do
50
+ hash1 = {:foo => "bar"}
51
+ hash2 = {:bar => "biz"}
52
+ XmlFu::Hash.to_xml(:lol => [hash1, hash2]).should == "<lol><foo>bar</foo><bar>biz</bar></lol>"
53
+ end
54
+
55
+ it "for array containing nil values" do
56
+ XmlFu::Hash.to_xml(:foo => [nil, {:bar => "biz"}]).should == "<foo><bar>biz</bar></foo>"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe XmlFu::Node do
4
+
5
+ describe "setting instance variables" do
6
+ it "should correctly remove special characters from a name" do
7
+ node = XmlFu::Node.new("foo/", "something")
8
+ node.name.should == "foo"
9
+
10
+ node = XmlFu::Node.new("foo*", "something")
11
+ node.name.should == "foo"
12
+
13
+ node = XmlFu::Node.new("foo!", "something")
14
+ node.name.should == "foo"
15
+ end
16
+
17
+ it "should set self-closing with special name character" do
18
+ node = XmlFu::Node.new("foo/", "something")
19
+ node.self_closing.should == true
20
+ end
21
+
22
+ it "should set escape_xml with special name character" do
23
+ node = XmlFu::Node.new("foo!", "something")
24
+ node.escape_xml.should == false
25
+ end
26
+
27
+ it "should set attributes with a hash" do
28
+ node = XmlFu::Node.new("foo", "bar", {:this => "that"})
29
+ node.attributes.should == {:this => "that"}
30
+
31
+ lambda { node.attributes = "foo" }.should raise_error(XmlFu::Node::InvalidAttributesException)
32
+ end
33
+
34
+ it "should be able to set a nil value" do
35
+ node = XmlFu::Node.new("foo", nil)
36
+ node.value.should == nil
37
+ end
38
+
39
+ it "should format a Data/Time value to acceptable string value" do
40
+ formatted_regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
41
+
42
+ node = XmlFu::Node.new("now", Time.now)
43
+ node.value.should match formatted_regex
44
+
45
+ node = XmlFu::Node.new("today", Date.today)
46
+ node.value.should match formatted_regex
47
+ end
48
+
49
+ it "should properly convert names without proper namespacing" do
50
+ node = XmlFu::Node.new(":foo", "bar")
51
+ node.name.should == "foo"
52
+ end
53
+
54
+ it "should properly preserve namespaceds names" do
55
+ node = XmlFu::Node.new("foo:bar", "biz")
56
+ node.name.should == "foo:bar"
57
+ end
58
+ end
59
+
60
+ describe "to_xml" do
61
+
62
+ describe "should return self-closing nil XML node for nil value" do
63
+
64
+ it "provided ANY non-blank name" do
65
+ nil_foo = "<foo xsi:nil=\"true\"/>"
66
+ node = XmlFu::Node.new("foo", nil)
67
+ node.to_xml.should == nil_foo
68
+
69
+ node = XmlFu::Node.new("foo!", nil)
70
+ node.to_xml.should == nil_foo
71
+
72
+ node = XmlFu::Node.new("foo*", nil)
73
+ node.to_xml.should == nil_foo
74
+ end
75
+
76
+ it "with additional attributes provided" do
77
+ node = XmlFu::Node.new("foo", nil, {:this => "that"})
78
+ node.to_xml.should == "<foo this=\"that\" xsi:nil=\"true\"/>"
79
+ end
80
+
81
+ end
82
+
83
+ it "should escape values by default" do
84
+ node = XmlFu::Node.new("foo", "<bar/>")
85
+ node.to_xml.should == "<foo>&lt;bar/&gt;</foo>"
86
+ end
87
+
88
+ it "should not escape values when provided with a special name" do
89
+ node = XmlFu::Node.new("foo!", "<bar/>")
90
+ node.to_xml.should == "<foo><bar/></foo>"
91
+ end
92
+
93
+ it "should ignore starred key (key*) for simple values" do
94
+ node = XmlFu::Node.new("foo*", "bar")
95
+ node.to_xml.should == "<foo>bar</foo>"
96
+
97
+ node = XmlFu::Node.new("pi*", 3.14159)
98
+ node.to_xml.should == "<pi>3.14159</pi>"
99
+ end
100
+
101
+ describe "when name denotes a self-closing XML node" do
102
+ it "should ignore tag content/value if it isn't a hash" do
103
+ node = XmlFu::Node.new("foo/", nil)
104
+ node.to_xml.should == "<foo/>"
105
+
106
+ node = XmlFu::Node.new("foo/", "bar")
107
+ node.to_xml.should == "<foo/>"
108
+ end
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'xml-fu'
3
+
4
+ RSpec.configure do |config|
5
+ config.color_enabled = true
6
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe XmlFu do
4
+ describe ".xml" do
5
+ it "translates a given Hash to XML" do
6
+ XmlFu.xml( :id => 1 ).should == "<id>1</id>"
7
+ end
8
+
9
+ it "doesn't modify the input hash" do
10
+ the_hash = {
11
+ :person => {
12
+ "@id" => "007",
13
+ :first_name => "James",
14
+ :last_name => "Bond"
15
+ }
16
+ }
17
+ original_hash = the_hash.dup
18
+
19
+ XmlFu.xml(the_hash)
20
+ original_hash.should == the_hash
21
+ end
22
+
23
+ it "should return correct value based on nested array of hashes" do
24
+ hash = {
25
+ "SecretAgents" => [
26
+ {"agent/" => {"@id"=>"006", "@name"=>"Alec Trevelyan"}},
27
+ {"agent/" => {"@id"=>"007", "@name"=>"James Bond"}}
28
+ ]
29
+ }
30
+ expected = "<SecretAgents><agent name=\"Alec Trevelyan\" id=\"006\"/><agent name=\"James Bond\" id=\"007\"/></SecretAgents>"
31
+ XmlFu.xml(hash).should == expected
32
+ end
33
+
34
+ it "should return correct value for nested collection of hashes" do
35
+ hash = {
36
+ "foo*" => [
37
+ {"@bar" => "biz"},
38
+ {"@biz" => "bang"}
39
+ ]
40
+ }
41
+ XmlFu.xml(hash).should == "<foo bar=\"biz\"></foo><foo biz=\"bang\"></foo>"
42
+ end
43
+
44
+ it "should ignore nested values for content array" do
45
+ output = XmlFu.xml("foo/" => [{:bar => "biz"}, {:bar => "biz"}])
46
+ output.should == "<foo/>"
47
+ end
48
+
49
+ it "should ignore nested keys if they aren't attributes" do
50
+ output = XmlFu.xml("foo/" => {"bar" => "biz"})
51
+ output.should == "<foo/>"
52
+
53
+ output = XmlFu.xml("foo/" => {"@id" => "0"})
54
+ output.should == "<foo id=\"0\"/>"
55
+ end
56
+
57
+ end
58
+
59
+ describe "configure" do
60
+ it "yields the XmlFu module" do
61
+ XmlFu.configure do |xf|
62
+ xf.should respond_to(:infer_simple_value_nodes)
63
+ end
64
+ end
65
+ end
66
+
67
+ it "should set XmlFu Module variable 'infer_simple_value_nodes'" do
68
+ XmlFu.infer_simple_value_nodes.should == false
69
+ XmlFu.infer_simple_value_nodes = true
70
+ XmlFu.infer_simple_value_nodes.should == true
71
+ XmlFu.infer_simple_value_nodes = false
72
+ XmlFu.infer_simple_value_nodes.should == false
73
+ end
74
+
75
+ end
data/xml-fu.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/xml-fu/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "xml-fu"
6
+ gem.version = XmlFu::VERSION
7
+ gem.authors = ["Ryan Johnson"]
8
+ gem.email = ["rhino.citguy@gmail.com"]
9
+ gem.homepage = "http://github.com/CITguy/#{gem.name}"
10
+ gem.summary = %q{Simple Hash/Array to XML generation}
11
+ gem.description = %q{
12
+ Inspired by the Gyoku gem for hash to xml conversion,
13
+ XmlFu is designed to require no meta tagging for
14
+ node attributes and content. (i.e. no :attributes! and no :order!)
15
+ }
16
+
17
+ gem.rubyforge_project = 'xml-fu'
18
+
19
+ gem.add_dependency "builder", ">= 2.1.2"
20
+
21
+ gem.add_development_dependency "rspec", ">= 2.4.0"
22
+ gem.add_development_dependency "autotest"
23
+ gem.add_development_dependency "mocha", "~> 0.9.9"
24
+
25
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ gem.files = `git ls-files`.split("\n")
27
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ gem.require_paths = ["lib"]
29
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xml-fu
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ryan Johnson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-13 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: builder
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 2
31
+ - 1
32
+ - 2
33
+ version: 2.1.2
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 31
45
+ segments:
46
+ - 2
47
+ - 4
48
+ - 0
49
+ version: 2.4.0
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: autotest
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :development
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: mocha
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ hash: 41
75
+ segments:
76
+ - 0
77
+ - 9
78
+ - 9
79
+ version: 0.9.9
80
+ type: :development
81
+ version_requirements: *id004
82
+ description: "\n Inspired by the Gyoku gem for hash to xml conversion, \n XmlFu is designed to require no meta tagging for \n node attributes and content. (i.e. no :attributes! and no :order!)\n "
83
+ email:
84
+ - rhino.citguy@gmail.com
85
+ executables: []
86
+
87
+ extensions: []
88
+
89
+ extra_rdoc_files: []
90
+
91
+ files:
92
+ - .gitignore
93
+ - CHANGELOG.md
94
+ - Gemfile
95
+ - LICENSE
96
+ - README.md
97
+ - Rakefile
98
+ - lib/xml-fu.rb
99
+ - lib/xml-fu/.FuNode.rb.swp
100
+ - lib/xml-fu/array.rb
101
+ - lib/xml-fu/core_ext/string.rb
102
+ - lib/xml-fu/hash.rb
103
+ - lib/xml-fu/node.rb
104
+ - lib/xml-fu/version.rb
105
+ - spec/lib/array_spec.rb
106
+ - spec/lib/hash_spec.rb
107
+ - spec/lib/node_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/xmlfu_spec.rb
110
+ - xml-fu.gemspec
111
+ homepage: http://github.com/CITguy/xml-fu
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project: xml-fu
140
+ rubygems_version: 1.8.10
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Simple Hash/Array to XML generation
144
+ test_files:
145
+ - spec/lib/array_spec.rb
146
+ - spec/lib/hash_spec.rb
147
+ - spec/lib/node_spec.rb
148
+ - spec/spec_helper.rb
149
+ - spec/xmlfu_spec.rb
150
+ has_rdoc: