xml-fu 0.1.0

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/.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: