right_cloud_api_base 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.
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