right_cloud_api_base 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY +2 -0
  3. data/LICENSE +19 -0
  4. data/README.md +14 -0
  5. data/Rakefile +37 -0
  6. data/lib/base/api_manager.rb +707 -0
  7. data/lib/base/helpers/cloud_api_logger.rb +214 -0
  8. data/lib/base/helpers/http_headers.rb +239 -0
  9. data/lib/base/helpers/http_parent.rb +103 -0
  10. data/lib/base/helpers/http_request.rb +173 -0
  11. data/lib/base/helpers/http_response.rb +122 -0
  12. data/lib/base/helpers/net_http_patch.rb +31 -0
  13. data/lib/base/helpers/query_api_patterns.rb +862 -0
  14. data/lib/base/helpers/support.rb +270 -0
  15. data/lib/base/helpers/support.xml.rb +306 -0
  16. data/lib/base/helpers/utils.rb +380 -0
  17. data/lib/base/manager.rb +122 -0
  18. data/lib/base/parsers/json.rb +38 -0
  19. data/lib/base/parsers/plain.rb +36 -0
  20. data/lib/base/parsers/rexml.rb +83 -0
  21. data/lib/base/parsers/sax.rb +200 -0
  22. data/lib/base/routines/cache_validator.rb +184 -0
  23. data/lib/base/routines/connection_proxies/net_http_persistent_proxy.rb +194 -0
  24. data/lib/base/routines/connection_proxies/right_http_connection_proxy.rb +224 -0
  25. data/lib/base/routines/connection_proxy.rb +66 -0
  26. data/lib/base/routines/request_analyzer.rb +122 -0
  27. data/lib/base/routines/request_generator.rb +48 -0
  28. data/lib/base/routines/request_initializer.rb +52 -0
  29. data/lib/base/routines/response_analyzer.rb +152 -0
  30. data/lib/base/routines/response_parser.rb +79 -0
  31. data/lib/base/routines/result_wrapper.rb +75 -0
  32. data/lib/base/routines/retry_manager.rb +106 -0
  33. data/lib/base/routines/routine.rb +98 -0
  34. data/lib/right_cloud_api_base.rb +72 -0
  35. data/lib/right_cloud_api_base_version.rb +37 -0
  36. data/right_cloud_api_base.gemspec +63 -0
  37. data/spec/helpers/query_api_pattern_spec.rb +312 -0
  38. data/spec/helpers/support_spec.rb +211 -0
  39. data/spec/helpers/support_xml_spec.rb +207 -0
  40. data/spec/helpers/utils_spec.rb +179 -0
  41. data/spec/routines/connection_proxies/test_net_http_persistent_proxy_spec.rb +143 -0
  42. data/spec/routines/test_cache_validator_spec.rb +152 -0
  43. data/spec/routines/test_connection_proxy_spec.rb +44 -0
  44. data/spec/routines/test_request_analyzer_spec.rb +106 -0
  45. data/spec/routines/test_response_analyzer_spec.rb +132 -0
  46. data/spec/routines/test_response_parser_spec.rb +228 -0
  47. data/spec/routines/test_result_wrapper_spec.rb +63 -0
  48. data/spec/routines/test_retry_manager_spec.rb +84 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +215 -0
@@ -0,0 +1,270 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ class String
25
+
26
+ # Constantizes the string.
27
+ #
28
+ # @return [Class, Module] The constantized class/module.
29
+ #
30
+ # @raise [NameError] If the name is not in CamelCase or is not initialized.
31
+ #
32
+ # @example
33
+ # "Module"._constantize #=> Module
34
+ # "Class"._constantize #=> Class
35
+ #
36
+ def _constantize
37
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
38
+ fail(::NameError, "#{self.inspect} is not a valid constant name!")
39
+ end
40
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
41
+ end
42
+
43
+ # Camelizes the string.
44
+ #
45
+ # @param [Boolean] lower_case When set to true it downcases the very first symbol of the string.
46
+ #
47
+ # @return [String] The camelized string value.
48
+ #
49
+ # @example
50
+ # 'hello_world'._camelize #=> 'HelloWorld'
51
+ # 'hello_world'._camelize(true) #=> 'helloWorld'
52
+ # 'HelloWorld'._camelize #=> 'HelloWorld'
53
+ #
54
+ def _camelize(lower_case = false)
55
+ words = self.gsub(/([A-Z])/, '_\1').
56
+ split(/_|\b/).
57
+ map{ |word| word.capitalize }.
58
+ reject{ |word| word == '' }
59
+ words[0] = words[0].downcase if words[0] && lower_case
60
+ words.join('')
61
+ end
62
+ alias_method :_camel_case, :_camelize
63
+
64
+ # Underscorizes the string.
65
+ #
66
+ # @return [String] The camelized string value.
67
+ #
68
+ # @example
69
+ # 'HelloWorld'._underscore #=> 'hello_world'
70
+ #
71
+ def _snake_case
72
+ self.split(/\b/).
73
+ map{ |word| word.gsub(/[A-Z]/){ |match| "#{$`=='' ? '' : '_'}#{match.downcase}" } }.
74
+ join('')
75
+ end
76
+ alias_method :_underscore, :_snake_case
77
+
78
+ # Wraps the string into an array.
79
+ #
80
+ # @return [Array]
81
+ #
82
+ # @example
83
+ # 'hahaha'._arrayify #=> ['hahaha']
84
+ #
85
+ def _arrayify
86
+ [ self ]
87
+ end
88
+
89
+ # Returns +true+ is the string has zero length or contains spaces only. And it returns +false+
90
+ # if the string has any meaningful value.
91
+ #
92
+ # @return [Boolean]
93
+ #
94
+ def _blank?
95
+ empty? || strip.empty?
96
+ end
97
+ end
98
+
99
+
100
+ class Object
101
+
102
+ # Checks if the current object is blank or empty.
103
+ # "", " ", nil, [] and {} are assumes as blank.
104
+ #
105
+ # @return [Boolean] +True+ if the object is blank and +false+ otherwise.
106
+ #
107
+ def _blank?
108
+ case
109
+ when respond_to?(:blank?) then blank?
110
+ when respond_to?(:empty?) then empty?
111
+ else !self
112
+ end
113
+ end
114
+
115
+ # Checks if the object has any non-blank value (opposite to Object#_blank?)
116
+ #
117
+ # @return [Boolean] +True+ if the object has any meaningful value and +false+ otherwise.
118
+ #
119
+ def _present?
120
+ !_blank?
121
+ end
122
+
123
+ # Returns a list of modules an object is extended with.
124
+ #
125
+ # @return [Array] A list of modules.
126
+ #
127
+ def _extended
128
+ (class << self; self; end).included_modules
129
+ end
130
+
131
+ # Checks whether an object was extended with a module.
132
+ #
133
+ # @return [Boolean] +True+ if the object is extended with the given module.
134
+ #
135
+ def _extended?(_module)
136
+ _extended.include?(_module)
137
+ end
138
+
139
+ # Wraps the object into an array.
140
+ #
141
+ # @return [Array]
142
+ #
143
+ # @example
144
+ # nil._arrayify #=> []
145
+ # 1._arrayify #=> [1]
146
+ # :sym._arrayify #=> [:sym]
147
+ #
148
+ def _arrayify
149
+ Array(self)
150
+ end
151
+ end
152
+
153
+ class Array
154
+
155
+ # Stringifies keys on all the hash items.
156
+ #
157
+ def _symbolize_keys
158
+ map do |item|
159
+ item.respond_to?(:_symbolize_keys) ? item._symbolize_keys : item
160
+ end
161
+ end
162
+
163
+ # Stringifies keys on all the hash items.
164
+ #
165
+ def _stringify_keys
166
+ map do |item|
167
+ item.respond_to?(:_stringify_keys) ? item._stringify_keys : item
168
+ end
169
+ end
170
+ end
171
+
172
+ class Hash
173
+
174
+ # Converts the root keys of the hash to symbols.
175
+ #
176
+ # @return [Hash]
177
+ #
178
+ def _symbolize_keys
179
+ inject({}) do |hash, (key, value)|
180
+ new_key = key.respond_to?(:to_sym) ? key.to_sym : key
181
+ value = value._symbolize_keys if value.respond_to?(:_symbolize_keys)
182
+ hash[new_key] = value
183
+ hash
184
+ end
185
+ end
186
+
187
+ # Converts the keys of the hash to strings.
188
+ #
189
+ # @return [Hash]
190
+ #
191
+ def _stringify_keys
192
+ inject({}) do |hash, (key, value)|
193
+ new_key = key.to_s if key.respond_to?(:to_s)
194
+ value = value._stringify_keys if value.respond_to?(:_stringify_keys)
195
+ hash[new_key] = value
196
+ hash
197
+ end
198
+ end
199
+
200
+ # Extract a value from the hash by its path. The path is a comma-separated list of keys, staring
201
+ # from the root key.
202
+ #
203
+ # @param [Array] path The path to the key. If the very last value is a hash then it is treated as
204
+ # a set of options.
205
+ #
206
+ # The options are:
207
+ # - :arrayify Convert the result into Array (unless it is).
208
+ # - :default A value to be returned unless the requested key exist.
209
+ #
210
+ # @yield [] If a block is given and the key is not found then it calls the block.
211
+ # @yieldreturn [Object] he block may raise a custom exception or return anything. The returned
212
+ # value it used for the method return.
213
+ #
214
+ # @return [Object] Whatever value the requested key has or the default value.
215
+ #
216
+ # @example
217
+ # {}._at('x','y') #=> Item at "x"->"y" is not found or not a Hash instance (RuntimeError)
218
+ # {}._at('x', :default => 'defval') #=> 'defval'
219
+ # {}._at('x'){ 'defval' } #=> 'defval'
220
+ # {}._at('x'){ fail "NotFound.MyCoolError" } #=> NotFound.MyCoolError (RuntimeError)
221
+ # {'x' => nil}._at('x') #=> nil
222
+ # {'x' => 4}._at('x') #=> 4
223
+ # {'x' => { 'y' => { 'z' => 'value'} } }._at('x', 'y', 'z') #=> 'value'
224
+ # {'x' => { 'y' => { 'z' => 'value'} } }._at('x', 'y', 'z', :arrayify => true) #=> ['value']
225
+ #
226
+ def _at(*path, &block)
227
+ path = path.flatten
228
+ options = path.last.is_a?(Hash) ? path.pop.dup : {}
229
+ key = path.shift
230
+ (options[:path] ||= []) << key
231
+ if key?(key)
232
+ if path._blank?
233
+ # We have reached the final key in the list - report it back.
234
+ return options[:arrayify] ? self[key]._arrayify : self[key]
235
+ end
236
+ return self[key]._at(path << options, &block) if self[key].is_a?(Hash)
237
+ end
238
+ return options[:default] if options.key?(:default)
239
+ return block.call if block
240
+ fail(StandardError.new("Item at #{options[:path].map{|i| i.inspect}.join('->')} is not found or not a Hash instance"))
241
+ end
242
+
243
+
244
+ # Extracts a value from the hash by its path and arrayifies it.
245
+ #
246
+ # @param [Array] path The path to the key. If the very last value is a hash then it is treated as
247
+ # a set of options.
248
+ #
249
+ # @return [Array] Single item array with whatever value the requested key has.
250
+ #
251
+ # @example
252
+ # {}._arrayify_at('x', 'y', 'z') #=> []
253
+ # { 'x' => { 'y' => { 'z' => 'value'} }}._arrayify_at('x', 'y', 'z') #=> ['value']
254
+ #
255
+ #
256
+ def _arrayify_at(*path)
257
+ _at(path << { :arrayify => true, :default => [] })
258
+ end
259
+
260
+ # Wraps the hash into an array.
261
+ #
262
+ # @return [Array]
263
+ #
264
+ # @example
265
+ # {1 => 2}._arrayify #=> [{1 => 2}]
266
+ #
267
+ def _arrayify
268
+ [ self ]
269
+ end
270
+ end
@@ -0,0 +1,306 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ class Object #:nodoc:
25
+
26
+ RIGHTXMLSUPPORT_XMLESCAPE = {'"' => '&quot;', '\'' =>'&apos;', '<' => '&lt;', '>' => '&gt;'}
27
+ RIGHTXMLSUPPORT_XMLUNESCAPE = RIGHTXMLSUPPORT_XMLESCAPE.invert
28
+ RIGHTXMLSUPPORT_XMLINDENT = ""
29
+ RIGHTXMLSUPPORT_XMLLEVEL = 0
30
+ RIGHTXMLSUPPORT_XMLCRLF = "\n"
31
+
32
+ # Escapes non-XML symbols.
33
+ #
34
+ # @return [String] XML-escaped string.
35
+ #
36
+ # @example
37
+ # "Hello <'world'> & \"the Universe\""._xml_escape #=>
38
+ # "Hello &lt;&apos;world&apos;&gt; &amp; &quot;the Universe&quot;"
39
+ #
40
+ def _xml_escape
41
+ self.to_s.gsub('&', '&amp;').gsub(/#{RIGHTXMLSUPPORT_XMLESCAPE.keys.join('|')}/) { |match| RIGHTXMLSUPPORT_XMLESCAPE[match] }
42
+ end
43
+
44
+
45
+ # Conditionally escapes non-XML symbols.
46
+ #
47
+ # @param [Hash] opts A set of options.
48
+ # @option opts [Boolean] :escape The flag.
49
+ #
50
+ # @return [String] XML-escaped string if :escape it set ot true or self otherwise.
51
+ #
52
+ def _xml_conditional_escape(opts={})
53
+ opts[:escape] ? self._xml_escape : self.to_s
54
+ end
55
+
56
+ # Unescapes XML-escaped symbols.
57
+ #
58
+ # @return [String] XML-unscaped string.
59
+ #
60
+ # @example
61
+ # "Hello &lt;&apos;world&apos;&gt; &amp; &quot;the Universe&quot;"._xml_unescape #=>
62
+ # "Hello <'world'> & \"the Universe\"
63
+ #
64
+ def _xml_unescape
65
+ self.to_s.gsub(/#{RIGHTXMLSUPPORT_XMLUNESCAPE.keys.join('|')}/) { |match| RIGHTXMLSUPPORT_XMLUNESCAPE[match] }.gsub('&amp;','&')
66
+ end
67
+
68
+ # Fixes the given set of options.
69
+ #
70
+ def _xml_get_opts(opts={}) # :nodoc:
71
+ opts[:level] ||= RIGHTXMLSUPPORT_XMLLEVEL
72
+ opts[:indent] ||= RIGHTXMLSUPPORT_XMLINDENT
73
+ opts[:crlf] ||= opts[:indent].empty? ? "" : RIGHTXMLSUPPORT_XMLCRLF
74
+ opts
75
+ end
76
+
77
+ # Returns an aligned piece of XML text.
78
+ #
79
+ def _xml_align(opts={}) # :nodoc:
80
+ return '' if self.to_s.empty?
81
+ opts = _xml_get_opts(opts)
82
+ "#{opts[:indent]*opts[:level]}#{self}#{opts[:crlf]}"
83
+ end
84
+
85
+
86
+ # Returns an XML-representation of the object.
87
+ #
88
+ # @param [Hash] opts A set of options.
89
+ # @option opts [Boolean] :escape The flag.
90
+ #
91
+ # @return [String] The result is an XML-escaped string (if :escape flag is set) or self otherwise.
92
+ #
93
+ def _to_xml(opts={})
94
+ _xml_conditional_escape(_xml_get_opts(opts))
95
+ end
96
+
97
+ # Returns an XML-representation of the object starting with '<?xml version="1.0" encoding="UTF-8"?>'
98
+ # string.
99
+ #
100
+ # @param [Hash] args A set of arguments (see _to_xml)
101
+ #
102
+ # @return [String]
103
+ #
104
+ def _to_xml!(*args)
105
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
106
+ "#{_to_xml(*args)}"
107
+ end
108
+ end
109
+
110
+ # --- Array ---
111
+
112
+ class Array #:nodoc:
113
+
114
+ # Returns an XML-representation if the array object.
115
+ #
116
+ # @param [Hash] opts A set of options.
117
+ # @option opts [Boolean] :escape The flag.
118
+ # @option opts [String] :tag The tag every array item is to be wrapped with ('<item>' by default)
119
+ #
120
+ # @return [String]
121
+ #
122
+ # @example
123
+ # [1,2,3,4]._to_xml #=>
124
+ # '<item>1</item><item>2</item><item>3</item><item>4</item>'
125
+ #
126
+ # @example
127
+ # [1,2,3,4]._to_xml(:crlf => "\n") #=>
128
+ # <item>1</item>
129
+ # <item>2</item>
130
+ # <item>3</item>
131
+ # <item>4</item>
132
+ #
133
+ # @example
134
+ # [1,2,[3,4,[5]]]._to_xml(:indent => ' ', :tag => 'hoho') #=>
135
+ # <hoho>1</hoho>
136
+ # <hoho>2</hoho>
137
+ # <hoho>
138
+ # <item>3</item>
139
+ # <item>4</item>
140
+ # <item>
141
+ # <item>5</item>
142
+ # </item>
143
+ # </hoho>
144
+ #
145
+ def _to_xml(opts={})
146
+ opts = _xml_get_opts(opts)
147
+ tag = opts.delete(:tag) || 'item'
148
+ { tag => self }._to_xml(opts)
149
+ end
150
+ end
151
+
152
+ class Hash #:nodoc:
153
+ RIGHTXMLSUPPORT_SORTORDERREGEXP = /(\{#(\d+)\})$/
154
+
155
+ # Generate a consecutive id for a new key.
156
+ # If String or Symbol is passed then adds the id to it.
157
+ #
158
+ # The method is widely used for MS Azure XMLs because MS requires XML
159
+ # tags to appear in a predefined order. Grrr... ;)
160
+ #
161
+ # @param [String] key_name Usually a tag name.
162
+ #
163
+ # @return [String] A string containing the original one and the current ordering ID.
164
+ # if key_name was not set then it returns the next id value.
165
+ #
166
+ # @example
167
+ # Hash::_order('hahaha') #=> "hahaha{#1}"
168
+ # Hash::_order('hohoho') #=> "hohoho{#2}"
169
+ # Hash::_order #=> 3
170
+ #
171
+ # @example
172
+ # hash = {
173
+ # Hash::_order('foo') => 34,
174
+ # Hash::_order('boo') => 45,
175
+ # Hash::_order('zoo') => 53,
176
+ # Hash::_order('poo') => 10,
177
+ # Hash::_order('moo') => {
178
+ # Hash::_order('noo') => 101,
179
+ # Hash::_order('too') => 113,
180
+ # Hash::_order('koo') => 102,
181
+ # },
182
+ # Hash::_order('woo') => 03,
183
+ # Hash::_order('hoo') => 1
184
+ # }
185
+ # hash._to_xml(:indent => ' ') #=>
186
+ # <boo>45</boo>
187
+ # <zoo>53</zoo>
188
+ # <poo>10</poo>
189
+ # <moo>
190
+ # <noo>101</noo>
191
+ # <too>113</too>
192
+ # <koo>102</koo>
193
+ # </moo>
194
+ # <woo>3</woo>
195
+ # <hoo>1</hoo>
196
+ #
197
+ def self._order(key_name=nil)
198
+ @_next_ordered_key_id ||= 0
199
+ @_next_ordered_key_id += 1
200
+ if key_name
201
+ fail(RuntimeError.new('String or Symbol is expected')) unless key_name.is_a?(String) || key_name.is_a?(Symbol)
202
+ result = "#{key_name}{##{@_next_ordered_key_id}}"
203
+ result = result.to_sym if key_name.is_a?(Symbol)
204
+ result
205
+ else
206
+ @_next_ordered_key_id
207
+ end
208
+ end
209
+
210
+ # Sorts the keys accordingly to their order definition (if Hash::_order was used).
211
+ def _xml_sort_keys # :nodoc:
212
+ keys.sort do |key1, key2|
213
+ key1idx = key1.to_s[RIGHTXMLSUPPORT_SORTORDERREGEXP] && $2 && $2.to_i
214
+ key2idx = key2.to_s[RIGHTXMLSUPPORT_SORTORDERREGEXP] && $2 && $2.to_i
215
+ if key1idx && key2idx then key1idx <=> key2idx
216
+ elsif key1idx then -1
217
+ elsif key2idx then 1
218
+ else 0
219
+ end
220
+ end
221
+ end
222
+
223
+ # Builds the final XML tag text.
224
+ def _xml_finalize_tag(tag_name, tag_attributes, tag_text, tag_elements, opts) # :nodoc:
225
+ next_opts = opts.merge(:level => opts[:level] + 1)
226
+ case
227
+ when tag_elements.empty? && tag_text.empty? then "<#{tag_name}#{tag_attributes}/>"._xml_align(opts)
228
+ when tag_elements.empty? then "<#{tag_name}#{tag_attributes}>#{tag_text}</#{tag_name}>"._xml_align(opts)
229
+ else "<#{tag_name}#{tag_attributes}>"._xml_align(opts) +
230
+ tag_text._xml_align(next_opts) +
231
+ tag_elements +
232
+ "</#{tag_name}>"._xml_align(opts)
233
+ end
234
+ end
235
+
236
+ # Returns an XML-representation if the hash object.
237
+ #
238
+ # @param [Hash] opts A set of options.
239
+ # @option opts [Boolean] :escape The flag.
240
+ # @option opts [Boolean] :indent The indentation string (is blank by default).
241
+ # @option opts [Boolean] :crfl The CR/LF string (is blank by default).
242
+ #
243
+ # @return [String]
244
+ #
245
+ # @example
246
+ # ({ 'a' => [ 1, { :c => 'd' } ] })._to_xml #=>
247
+ # "<a>1</a><a><c>d</c></a>"
248
+ #
249
+ # @example
250
+ # { 'screen' => {
251
+ # '@width' => 1080,
252
+ # '@hight' => 720,
253
+ # '@@text' => 'HD',
254
+ # 'color' => {
255
+ # '@max-colors' => 65535,
256
+ # '@dinamic-resolution' => '1:1000000',
257
+ # '@@text' => '<"PAL">',
258
+ # 'brightness' => {
259
+ # 'bright' => true
260
+ # }
261
+ # }
262
+ # }
263
+ # }._to_xml(:indent => ' ',
264
+ # :escape => true) #=>
265
+ # <screen width="1080" hight="720">
266
+ # HD
267
+ # <color max-colors="65535" dinamic-resolution="1:1000000">
268
+ # &lt;&quot;PAL&quot;&gt;
269
+ # <brightness>
270
+ # <bright>true</bright>
271
+ # </brightness>
272
+ # </color>
273
+ # </screen>
274
+ #
275
+ def _to_xml(opts={})
276
+ result = ''
277
+ opts = _xml_get_opts(opts)
278
+ next_opts = opts.merge(:level => opts[:level] + 1)
279
+ _xml_sort_keys.each do |tag_name|
280
+ value = self[tag_name]
281
+ tag_name = tag_name.to_s.sub(RIGHTXMLSUPPORT_SORTORDERREGEXP, '')
282
+ if value.is_a?(Hash)
283
+ tag_attributes = ''; tag_elements = ''; tag_text = ''
284
+ value._xml_sort_keys.each do |item|
285
+ item_value = value[item]
286
+ item = item.to_s.sub(RIGHTXMLSUPPORT_SORTORDERREGEXP, '')
287
+ case
288
+ when item == '@@text' then tag_text << item_value._xml_conditional_escape(opts)
289
+ when item[/^@[^@]/] then tag_attributes << %Q{ #{item[1..-1]}="#{item_value._xml_conditional_escape(opts)}"}
290
+ else tag_elements << { item => item_value }._to_xml(next_opts)
291
+ end
292
+ end
293
+ result << _xml_finalize_tag(tag_name, tag_attributes, tag_text, tag_elements, opts)
294
+ elsif value.is_a?(Array)
295
+ value.each do |item|
296
+ item = { tag_name => item } if item.is_a?(Array)
297
+ result << { tag_name => item }._to_xml(opts)
298
+ end
299
+ else
300
+ result << _xml_finalize_tag(tag_name, '', value.to_s, '', opts)
301
+ end
302
+ end
303
+ result
304
+ end
305
+
306
+ end