actionpack 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/CHANGELOG +517 -15
  2. data/MIT-LICENSE +1 -1
  3. data/README +18 -20
  4. data/Rakefile +7 -4
  5. data/examples/address_book_controller.rb +3 -3
  6. data/examples/blog_controller.cgi +3 -3
  7. data/examples/debate_controller.cgi +5 -5
  8. data/lib/action_controller.rb +2 -2
  9. data/lib/action_controller/assertions.rb +73 -311
  10. data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
  11. data/lib/action_controller/assertions/dom_assertions.rb +25 -0
  12. data/lib/action_controller/assertions/model_assertions.rb +12 -0
  13. data/lib/action_controller/assertions/response_assertions.rb +140 -0
  14. data/lib/action_controller/assertions/routing_assertions.rb +82 -0
  15. data/lib/action_controller/assertions/selector_assertions.rb +571 -0
  16. data/lib/action_controller/assertions/tag_assertions.rb +117 -0
  17. data/lib/action_controller/base.rb +334 -163
  18. data/lib/action_controller/benchmarking.rb +3 -6
  19. data/lib/action_controller/caching.rb +83 -22
  20. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
  21. data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
  22. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
  23. data/lib/action_controller/cgi_process.rb +50 -27
  24. data/lib/action_controller/components.rb +21 -25
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
  27. data/lib/action_controller/filters.rb +448 -225
  28. data/lib/action_controller/flash.rb +24 -20
  29. data/lib/action_controller/helpers.rb +2 -5
  30. data/lib/action_controller/integration.rb +40 -16
  31. data/lib/action_controller/layout.rb +11 -8
  32. data/lib/action_controller/macros/auto_complete.rb +3 -2
  33. data/lib/action_controller/macros/in_place_editing.rb +3 -2
  34. data/lib/action_controller/mime_responds.rb +41 -29
  35. data/lib/action_controller/mime_type.rb +68 -10
  36. data/lib/action_controller/pagination.rb +4 -3
  37. data/lib/action_controller/request.rb +22 -14
  38. data/lib/action_controller/rescue.rb +25 -22
  39. data/lib/action_controller/resources.rb +302 -0
  40. data/lib/action_controller/response.rb +20 -2
  41. data/lib/action_controller/response.rb.rej +17 -0
  42. data/lib/action_controller/routing.rb +1165 -567
  43. data/lib/action_controller/scaffolding.rb +30 -31
  44. data/lib/action_controller/session/active_record_store.rb +2 -0
  45. data/lib/action_controller/session/drb_store.rb +4 -0
  46. data/lib/action_controller/session/mem_cache_store.rb +4 -0
  47. data/lib/action_controller/session_management.rb +6 -9
  48. data/lib/action_controller/status_codes.rb +89 -0
  49. data/lib/action_controller/streaming.rb +6 -15
  50. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
  51. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
  52. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
  53. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
  54. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  55. data/lib/action_controller/test_process.rb +52 -30
  56. data/lib/action_controller/url_rewriter.rb +63 -29
  57. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
  58. data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
  59. data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
  60. data/lib/action_controller/verification.rb +22 -11
  61. data/lib/action_pack.rb +1 -1
  62. data/lib/action_pack/version.rb +2 -2
  63. data/lib/action_view.rb +1 -1
  64. data/lib/action_view/base.rb +46 -43
  65. data/lib/action_view/compiled_templates.rb +1 -1
  66. data/lib/action_view/helpers/active_record_helper.rb +54 -17
  67. data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
  68. data/lib/action_view/helpers/capture_helper.rb +1 -1
  69. data/lib/action_view/helpers/date_helper.rb +258 -136
  70. data/lib/action_view/helpers/debug_helper.rb +1 -1
  71. data/lib/action_view/helpers/deprecated_helper.rb +34 -0
  72. data/lib/action_view/helpers/form_helper.rb +75 -35
  73. data/lib/action_view/helpers/form_options_helper.rb +7 -5
  74. data/lib/action_view/helpers/form_tag_helper.rb +44 -6
  75. data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
  76. data/lib/action_view/helpers/javascript_helper.rb +71 -10
  77. data/lib/action_view/helpers/javascripts/controls.js +41 -23
  78. data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
  79. data/lib/action_view/helpers/javascripts/effects.js +293 -163
  80. data/lib/action_view/helpers/javascripts/prototype.js +897 -389
  81. data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
  82. data/lib/action_view/helpers/number_helper.rb +111 -65
  83. data/lib/action_view/helpers/prototype_helper.rb +84 -109
  84. data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
  85. data/lib/action_view/helpers/tag_helper.rb +69 -16
  86. data/lib/action_view/helpers/text_helper.rb +149 -112
  87. data/lib/action_view/helpers/url_helper.rb +200 -107
  88. data/lib/action_view/template_error.rb +66 -42
  89. data/test/abstract_unit.rb +4 -2
  90. data/test/active_record_unit.rb +84 -56
  91. data/test/activerecord/active_record_assertions_test.rb +26 -18
  92. data/test/activerecord/active_record_store_test.rb +4 -36
  93. data/test/activerecord/pagination_test.rb +1 -6
  94. data/test/controller/action_pack_assertions_test.rb +230 -113
  95. data/test/controller/addresses_render_test.rb +2 -6
  96. data/test/controller/assert_select_test.rb +576 -0
  97. data/test/controller/base_test.rb +73 -3
  98. data/test/controller/caching_test.rb +228 -0
  99. data/test/controller/capture_test.rb +12 -10
  100. data/test/controller/cgi_test.rb +89 -12
  101. data/test/controller/components_test.rb +24 -2
  102. data/test/controller/content_type_test.rb +139 -0
  103. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  104. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  105. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  106. data/test/controller/cookie_test.rb +33 -25
  107. data/test/controller/deprecated_instance_variables_test.rb +48 -0
  108. data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
  109. data/test/controller/fake_controllers.rb +0 -1
  110. data/test/controller/filters_test.rb +301 -16
  111. data/test/controller/flash_test.rb +19 -2
  112. data/test/controller/helper_test.rb +2 -2
  113. data/test/controller/integration_test.rb +154 -0
  114. data/test/controller/layout_test.rb +115 -1
  115. data/test/controller/mime_responds_test.rb +94 -0
  116. data/test/controller/mime_type_test.rb +9 -0
  117. data/test/controller/new_render_test.rb +161 -11
  118. data/test/controller/raw_post_test.rb +52 -15
  119. data/test/controller/redirect_test.rb +27 -14
  120. data/test/controller/render_test.rb +76 -29
  121. data/test/controller/request_test.rb +55 -4
  122. data/test/controller/resources_test.rb +274 -0
  123. data/test/controller/routing_test.rb +1533 -824
  124. data/test/controller/selector_test.rb +628 -0
  125. data/test/controller/send_file_test.rb +9 -1
  126. data/test/controller/session_management_test.rb +51 -0
  127. data/test/controller/test_test.rb +113 -29
  128. data/test/controller/url_rewriter_test.rb +86 -17
  129. data/test/controller/verification_test.rb +19 -17
  130. data/test/controller/webservice_test.rb +0 -7
  131. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  132. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  133. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  134. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  135. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
  136. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
  137. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
  138. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
  139. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
  140. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
  141. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
  142. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
  143. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
  144. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
  145. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
  146. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
  147. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
  148. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
  149. data/test/fixtures/multipart/binary_file +0 -0
  150. data/test/fixtures/public/javascripts/application.js +1 -0
  151. data/test/fixtures/test/_hello.rxml +1 -0
  152. data/test/fixtures/test/hello_world_container.rxml +3 -0
  153. data/test/fixtures/topic.rb +2 -2
  154. data/test/template/active_record_helper_test.rb +83 -12
  155. data/test/template/asset_tag_helper_test.rb +75 -95
  156. data/test/template/compiled_templates_test.rb +1 -0
  157. data/test/template/date_helper_test.rb +873 -181
  158. data/test/template/deprecated_helper_test.rb +36 -0
  159. data/test/template/deprecated_instance_variables_test.rb +43 -0
  160. data/test/template/form_helper_test.rb +77 -1
  161. data/test/template/form_options_helper_test.rb +4 -0
  162. data/test/template/form_tag_helper_test.rb +66 -2
  163. data/test/template/java_script_macros_helper_test.rb +4 -1
  164. data/test/template/javascript_helper_test.rb +29 -0
  165. data/test/template/number_helper_test.rb +63 -27
  166. data/test/template/prototype_helper_test.rb +77 -34
  167. data/test/template/tag_helper_test.rb +34 -6
  168. data/test/template/text_helper_test.rb +69 -34
  169. data/test/template/url_helper_test.rb +168 -16
  170. data/test/testing_sandbox.rb +7 -22
  171. metadata +66 -20
  172. data/filler.txt +0 -50
  173. data/lib/action_controller/code_generation.rb +0 -235
  174. data/lib/action_controller/vendor/xml_simple.rb +0 -1019
  175. data/test/controller/caching_filestore.rb +0 -74
  176. data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
  177. data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
  178. data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
  179. data/test/fixtures/dont_load.rb +0 -3
@@ -1,1019 +0,0 @@
1
- # = XmlSimple
2
- #
3
- # Author:: Maik Schmidt <contact@maik-schmidt.de>
4
- # Copyright:: Copyright (c) 2003 Maik Schmidt
5
- # License:: Distributes under the same terms as Ruby.
6
- #
7
- require 'rexml/document'
8
-
9
- # Easy API to maintain XML (especially configuration files).
10
- class XmlSimple #:nodoc:
11
- include REXML
12
-
13
- @@VERSION = '1.0.2'
14
-
15
- # A simple cache for XML documents that were already transformed
16
- # by xml_in.
17
- class Cache #:nodoc:
18
- # Creates and initializes a new Cache object.
19
- def initialize
20
- @mem_share_cache = {}
21
- @mem_copy_cache = {}
22
- end
23
-
24
- # Saves a data structure into a file.
25
- #
26
- # data::
27
- # Data structure to be saved.
28
- # filename::
29
- # Name of the file belonging to the data structure.
30
- def save_storable(data, filename)
31
- cache_file = get_cache_filename(filename)
32
- File.open(cache_file, "w+") { |f| Marshal.dump(data, f) }
33
- end
34
-
35
- # Restores a data structure from a file. If restoring the data
36
- # structure failed for any reason, nil will be returned.
37
- #
38
- # filename::
39
- # Name of the file belonging to the data structure.
40
- def restore_storable(filename)
41
- cache_file = get_cache_filename(filename)
42
- return nil unless File::exist?(cache_file)
43
- return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i
44
- data = nil
45
- File.open(cache_file) { |f| data = Marshal.load(f) }
46
- data
47
- end
48
-
49
- # Saves a data structure in a shared memory cache.
50
- #
51
- # data::
52
- # Data structure to be saved.
53
- # filename::
54
- # Name of the file belonging to the data structure.
55
- def save_mem_share(data, filename)
56
- @mem_share_cache[filename] = [Time::now.to_i, data]
57
- end
58
-
59
- # Restores a data structure from a shared memory cache. You
60
- # should consider these elements as "read only". If restoring
61
- # the data structure failed for any reason, nil will be
62
- # returned.
63
- #
64
- # filename::
65
- # Name of the file belonging to the data structure.
66
- def restore_mem_share(filename)
67
- get_from_memory_cache(filename, @mem_share_cache)
68
- end
69
-
70
- # Copies a data structure to a memory cache.
71
- #
72
- # data::
73
- # Data structure to be copied.
74
- # filename::
75
- # Name of the file belonging to the data structure.
76
- def save_mem_copy(data, filename)
77
- @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)]
78
- end
79
-
80
- # Restores a data structure from a memory cache. If restoring
81
- # the data structure failed for any reason, nil will be
82
- # returned.
83
- #
84
- # filename::
85
- # Name of the file belonging to the data structure.
86
- def restore_mem_copy(filename)
87
- data = get_from_memory_cache(filename, @mem_share_cache)
88
- data = Marshal.load(data) unless data.nil?
89
- data
90
- end
91
-
92
- private
93
-
94
- # Returns the "cache filename" belonging to a filename, i.e.
95
- # the extension '.xml' in the original filename will be replaced
96
- # by '.stor'. If filename does not have this extension, '.stor'
97
- # will be appended.
98
- #
99
- # filename::
100
- # Filename to get "cache filename" for.
101
- def get_cache_filename(filename)
102
- filename.sub(/(\.xml)?$/, '.stor')
103
- end
104
-
105
- # Returns a cache entry from a memory cache belonging to a
106
- # certain filename. If no entry could be found for any reason,
107
- # nil will be returned.
108
- #
109
- # filename::
110
- # Name of the file the cache entry belongs to.
111
- # cache::
112
- # Memory cache to get entry from.
113
- def get_from_memory_cache(filename, cache)
114
- return nil unless cache[filename]
115
- return nil unless cache[filename][0] > File::mtime(filename).to_i
116
- return cache[filename][1]
117
- end
118
- end
119
-
120
- # Create a "global" cache.
121
- @@cache = Cache.new
122
-
123
- # Creates and intializes a new XmlSimple object.
124
- #
125
- # defaults::
126
- # Default values for options.
127
- def initialize(defaults = nil)
128
- unless defaults.nil? || defaults.instance_of?(Hash)
129
- raise ArgumentError, "Options have to be a Hash."
130
- end
131
- @default_options = normalize_option_names(defaults, KNOWN_OPTIONS['in'] & KNOWN_OPTIONS['out'])
132
- @options = Hash.new
133
- @_var_values = nil
134
- end
135
-
136
- # Converts an XML document in the same way as the Perl module XML::Simple.
137
- #
138
- # string::
139
- # XML source. Could be one of the following:
140
- #
141
- # - nil: Tries to load and parse '<scriptname>.xml'.
142
- # - filename: Tries to load and parse filename.
143
- # - IO object: Reads from object until EOF is detected and parses result.
144
- # - XML string: Parses string.
145
- #
146
- # options::
147
- # Options to be used.
148
- def xml_in(string = nil, options = nil)
149
- handle_options('in', options)
150
-
151
- # If no XML string or filename was supplied look for scriptname.xml.
152
- if string.nil?
153
- string = File::basename($0)
154
- string.sub!(/\.[^.]+$/, '')
155
- string += '.xml'
156
-
157
- directory = File::dirname($0)
158
- @options['searchpath'].unshift(directory) unless directory.nil?
159
- end
160
-
161
- if string.instance_of?(String)
162
- if string =~ /<.*?>/m
163
- @doc = parse(string)
164
- elsif string == '-'
165
- @doc = parse($stdin.readlines.to_s)
166
- else
167
- filename = find_xml_file(string, @options['searchpath'])
168
-
169
- if @options.has_key?('cache')
170
- @options['cache'].each { |scheme|
171
- case(scheme)
172
- when 'storable'
173
- content = @@cache.restore_storable(filename)
174
- when 'mem_share'
175
- content = @@cache.restore_mem_share(filename)
176
- when 'mem_copy'
177
- content = @@cache.restore_mem_copy(filename)
178
- else
179
- raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
180
- end
181
- return content if content
182
- }
183
- end
184
-
185
- @doc = load_xml_file(filename)
186
- end
187
- elsif string.kind_of?(IO)
188
- @doc = parse(string.readlines.to_s)
189
- else
190
- raise ArgumentError, "Could not parse object of type: <#{string.type}>."
191
- end
192
-
193
- result = collapse(@doc.root)
194
- result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result
195
- put_into_cache(result, filename)
196
- result
197
- end
198
-
199
- # This is the functional version of the instance method xml_in.
200
- def XmlSimple.xml_in(string = nil, options = nil)
201
- xml_simple = XmlSimple.new
202
- xml_simple.xml_in(string, options)
203
- end
204
-
205
- # Converts a data structure into an XML document.
206
- #
207
- # ref::
208
- # Reference to data structure to be converted into XML.
209
- # options::
210
- # Options to be used.
211
- def xml_out(ref, options = nil)
212
- handle_options('out', options)
213
- if ref.instance_of?(Array)
214
- ref = { @options['anonymoustag'] => ref }
215
- end
216
-
217
- if @options['keeproot']
218
- keys = ref.keys
219
- if keys.size == 1
220
- ref = ref[keys[0]]
221
- @options['rootname'] = keys[0]
222
- end
223
- elsif @options['rootname'] == ''
224
- if ref.instance_of?(Hash)
225
- refsave = ref
226
- ref = {}
227
- refsave.each { |key, value|
228
- if !scalar(value)
229
- ref[key] = value
230
- else
231
- ref[key] = [ value.to_s ]
232
- end
233
- }
234
- end
235
- end
236
-
237
- @ancestors = []
238
- xml = value_to_xml(ref, @options['rootname'], '')
239
- @ancestors = nil
240
-
241
- if @options['xmldeclaration']
242
- xml = @options['xmldeclaration'] + "\n" + xml
243
- end
244
-
245
- if @options.has_key?('outputfile')
246
- if @options['outputfile'].kind_of?(IO)
247
- return @options['outputfile'].write(xml)
248
- else
249
- File.open(@options['outputfile'], "w") { |file| file.write(xml) }
250
- end
251
- end
252
- xml
253
- end
254
-
255
- # This is the functional version of the instance method xml_out.
256
- def XmlSimple.xml_out(hash, options = nil)
257
- xml_simple = XmlSimple.new
258
- xml_simple.xml_out(hash, options)
259
- end
260
-
261
- private
262
-
263
- # Declare options that are valid for xml_in and xml_out.
264
- KNOWN_OPTIONS = {
265
- 'in' => %w(
266
- keyattr keeproot forcecontent contentkey noattr
267
- searchpath forcearray suppressempty anonymoustag
268
- cache grouptags normalisespace normalizespace
269
- variables varattr
270
- ),
271
- 'out' => %w(
272
- keyattr keeproot contentkey noattr rootname
273
- xmldeclaration outputfile noescape suppressempty
274
- anonymoustag indent grouptags noindent
275
- )
276
- }
277
-
278
- # Define some reasonable defaults.
279
- DEF_KEY_ATTRIBUTES = []
280
- DEF_ROOT_NAME = 'opt'
281
- DEF_CONTENT_KEY = 'content'
282
- DEF_XML_DECLARATION = "<?xml version='1.0' standalone='yes'?>"
283
- DEF_ANONYMOUS_TAG = 'anon'
284
- DEF_FORCE_ARRAY = true
285
- DEF_INDENTATION = ' '
286
-
287
- # Normalizes option names in a hash, i.e., turns all
288
- # characters to lower case and removes all underscores.
289
- # Additionally, this method checks, if an unknown option
290
- # was used and raises an according exception.
291
- #
292
- # options::
293
- # Hash to be normalized.
294
- # known_options::
295
- # List of known options.
296
- def normalize_option_names(options, known_options)
297
- return nil if options.nil?
298
- result = Hash.new
299
- options.each { |key, value|
300
- lkey = key.downcase
301
- lkey.gsub!(/_/, '')
302
- if !known_options.member?(lkey)
303
- raise ArgumentError, "Unrecognised option: #{lkey}."
304
- end
305
- result[lkey] = value
306
- }
307
- result
308
- end
309
-
310
- # Merges a set of options with the default options.
311
- #
312
- # direction::
313
- # 'in': If options should be handled for xml_in.
314
- # 'out': If options should be handled for xml_out.
315
- # options::
316
- # Options to be merged with the default options.
317
- def handle_options(direction, options)
318
- @options = options || Hash.new
319
-
320
- raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash)
321
-
322
- unless KNOWN_OPTIONS.has_key?(direction)
323
- raise ArgumentError, "Unknown direction: <#{direction}>."
324
- end
325
-
326
- known_options = KNOWN_OPTIONS[direction]
327
- @options = normalize_option_names(@options, known_options)
328
-
329
- unless @default_options.nil?
330
- known_options.each { |option|
331
- unless @options.has_key?(option)
332
- if @default_options.has_key?(option)
333
- @options[option] = @default_options[option]
334
- end
335
- end
336
- }
337
- end
338
-
339
- unless @options.has_key?('noattr')
340
- @options['noattr'] = false
341
- end
342
-
343
- if @options.has_key?('rootname')
344
- @options['rootname'] = '' if @options['rootname'].nil?
345
- else
346
- @options['rootname'] = DEF_ROOT_NAME
347
- end
348
-
349
- if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true
350
- @options['xmldeclaration'] = DEF_XML_DECLARATION
351
- end
352
-
353
- if @options.has_key?('contentkey')
354
- if @options['contentkey'] =~ /^-(.*)$/
355
- @options['contentkey'] = $1
356
- @options['collapseagain'] = true
357
- end
358
- else
359
- @options['contentkey'] = DEF_CONTENT_KEY
360
- end
361
-
362
- unless @options.has_key?('normalisespace')
363
- @options['normalisespace'] = @options['normalizespace']
364
- end
365
- @options['normalisespace'] = 0 if @options['normalisespace'].nil?
366
-
367
- if @options.has_key?('searchpath')
368
- unless @options['searchpath'].instance_of?(Array)
369
- @options['searchpath'] = [ @options['searchpath'] ]
370
- end
371
- else
372
- @options['searchpath'] = []
373
- end
374
-
375
- if @options.has_key?('cache') && scalar(@options['cache'])
376
- @options['cache'] = [ @options['cache'] ]
377
- end
378
-
379
- @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag')
380
-
381
- if !@options.has_key?('indent') || @options['indent'].nil?
382
- @options['indent'] = DEF_INDENTATION
383
- end
384
-
385
- @options['indent'] = '' if @options.has_key?('noindent')
386
-
387
- # Special cleanup for 'keyattr' which could be an array or
388
- # a hash or left to default to array.
389
- if @options.has_key?('keyattr')
390
- if !scalar(@options['keyattr'])
391
- # Convert keyattr => { elem => '+attr' }
392
- # to keyattr => { elem => ['attr', '+'] }
393
- if @options['keyattr'].instance_of?(Hash)
394
- @options['keyattr'].each { |key, value|
395
- if value =~ /^([-+])?(.*)$/
396
- @options['keyattr'][key] = [$2, $1 ? $1 : '']
397
- end
398
- }
399
- elsif !@options['keyattr'].instance_of?(Array)
400
- raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
401
- end
402
- else
403
- @options['keyattr'] = [ @options['keyattr'] ]
404
- end
405
- else
406
- @options['keyattr'] = DEF_KEY_ATTRIBUTES
407
- end
408
-
409
- if @options.has_key?('forcearray')
410
- if @options['forcearray'].instance_of?(Regexp)
411
- @options['forcearray'] = [ @options['forcearray'] ]
412
- end
413
-
414
- if @options['forcearray'].instance_of?(Array)
415
- force_list = @options['forcearray']
416
- unless force_list.empty?
417
- @options['forcearray'] = {}
418
- force_list.each { |tag|
419
- if tag.instance_of?(Regexp)
420
- unless @options['forcearray']['_regex'].instance_of?(Array)
421
- @options['forcearray']['_regex'] = []
422
- end
423
- @options['forcearray']['_regex'] << tag
424
- else
425
- @options['forcearray'][tag] = true
426
- end
427
- }
428
- else
429
- @options['forcearray'] = false
430
- end
431
- else
432
- @options['forcearray'] = @options['forcearray'] ? true : false
433
- end
434
- else
435
- @options['forcearray'] = DEF_FORCE_ARRAY
436
- end
437
-
438
- if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash)
439
- raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
440
- end
441
-
442
- if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash)
443
- raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
444
- end
445
-
446
- if @options.has_key?('variables')
447
- @_var_values = @options['variables']
448
- elsif @options.has_key?('varattr')
449
- @_var_values = {}
450
- end
451
- end
452
-
453
- # Actually converts an XML document element into a data structure.
454
- #
455
- # element::
456
- # The document element to be collapsed.
457
- def collapse(element)
458
- result = @options['noattr'] ? {} : get_attributes(element)
459
-
460
- if @options['normalisespace'] == 2
461
- result.each { |k, v| result[k] = normalise_space(v) }
462
- end
463
-
464
- if element.has_elements?
465
- element.each_element { |child|
466
- value = collapse(child)
467
- if empty(value) && (element.attributes.empty? || @options['noattr'])
468
- next if @options.has_key?('suppressempty') && @options['suppressempty'] == true
469
- end
470
- result = merge(result, child.name, value)
471
- }
472
- if has_mixed_content?(element)
473
- # normalisespace?
474
- content = element.texts.map { |x| x.to_s }
475
- content = content[0] if content.size == 1
476
- result[@options['contentkey']] = content
477
- end
478
- elsif element.has_text? # i.e. it has only text.
479
- return collapse_text_node(result, element)
480
- end
481
-
482
- # Turn Arrays into Hashes if key fields present.
483
- count = fold_arrays(result)
484
-
485
- # Disintermediate grouped tags.
486
- if @options.has_key?('grouptags')
487
- result.each { |key, value|
488
- next unless (value.instance_of?(Hash) && (value.size == 1))
489
- child_key, child_value = value.to_a[0]
490
- if @options['grouptags'][key] == child_key
491
- result[key] = child_value
492
- end
493
- }
494
- end
495
-
496
- # Fold Hases containing a single anonymous Array up into just the Array.
497
- if count == 1
498
- anonymoustag = @options['anonymoustag']
499
- if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array)
500
- return result[anonymoustag]
501
- end
502
- end
503
-
504
- if result.empty? && @options.has_key?('suppressempty')
505
- return @options['suppressempty'] == '' ? '' : nil
506
- end
507
-
508
- result
509
- end
510
-
511
- # Collapses a text node and merges it with an existing Hash, if
512
- # possible.
513
- # Thanks to Curtis Schofield for reporting a subtle bug.
514
- #
515
- # hash::
516
- # Hash to merge text node value with, if possible.
517
- # element::
518
- # Text node to be collapsed.
519
- def collapse_text_node(hash, element)
520
- value = node_to_text(element)
521
- if empty(value) && !element.has_attributes?
522
- return {}
523
- end
524
-
525
- if element.has_attributes? && !@options['noattr']
526
- return merge(hash, @options['contentkey'], value)
527
- else
528
- if @options['forcecontent']
529
- return merge(hash, @options['contentkey'], value)
530
- else
531
- return value
532
- end
533
- end
534
- end
535
-
536
- # Folds all arrays in a Hash.
537
- #
538
- # hash::
539
- # Hash to be folded.
540
- def fold_arrays(hash)
541
- fold_amount = 0
542
- keyattr = @options['keyattr']
543
- if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash))
544
- hash.each { |key, value|
545
- if value.instance_of?(Array)
546
- if keyattr.instance_of?(Array)
547
- hash[key] = fold_array(value)
548
- else
549
- hash[key] = fold_array_by_name(key, value)
550
- end
551
- fold_amount += 1
552
- end
553
- }
554
- end
555
- fold_amount
556
- end
557
-
558
- # Folds an Array to a Hash, if possible. Folding happens
559
- # according to the content of keyattr, which has to be
560
- # an array.
561
- #
562
- # array::
563
- # Array to be folded.
564
- def fold_array(array)
565
- hash = Hash.new
566
- array.each { |x|
567
- return array unless x.instance_of?(Hash)
568
- key_matched = false
569
- @options['keyattr'].each { |key|
570
- if x.has_key?(key)
571
- key_matched = true
572
- value = x[key]
573
- return array if value.instance_of?(Hash) || value.instance_of?(Array)
574
- value = normalise_space(value) if @options['normalisespace'] == 1
575
- x.delete(key)
576
- hash[value] = x
577
- break
578
- end
579
- }
580
- return array unless key_matched
581
- }
582
- hash = collapse_content(hash) if @options['collapseagain']
583
- hash
584
- end
585
-
586
- # Folds an Array to a Hash, if possible. Folding happens
587
- # according to the content of keyattr, which has to be
588
- # a Hash.
589
- #
590
- # name::
591
- # Name of the attribute to be folded upon.
592
- # array::
593
- # Array to be folded.
594
- def fold_array_by_name(name, array)
595
- return array unless @options['keyattr'].has_key?(name)
596
- key, flag = @options['keyattr'][name]
597
-
598
- hash = Hash.new
599
- array.each { |x|
600
- if x.instance_of?(Hash) && x.has_key?(key)
601
- value = x[key]
602
- return array if value.instance_of?(Hash) || value.instance_of?(Array)
603
- value = normalise_space(value) if @options['normalisespace'] == 1
604
- hash[value] = x
605
- hash[value]["-#{key}"] = hash[value][key] if flag == '-'
606
- hash[value].delete(key) unless flag == '+'
607
- else
608
- $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
609
- return array
610
- end
611
- }
612
- hash = collapse_content(hash) if @options['collapseagain']
613
- hash
614
- end
615
-
616
- # Tries to collapse a Hash even more ;-)
617
- #
618
- # hash::
619
- # Hash to be collapsed again.
620
- def collapse_content(hash)
621
- content_key = @options['contentkey']
622
- hash.each_value { |value|
623
- return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key)
624
- hash.each_key { |key| hash[key] = hash[key][content_key] }
625
- }
626
- hash
627
- end
628
-
629
- # Adds a new key/value pair to an existing Hash. If the key to be added
630
- # does already exist and the existing value associated with key is not
631
- # an Array, it will be converted into an Array. Then the new value is
632
- # appended to that Array.
633
- #
634
- # hash::
635
- # Hash to add key/value pair to.
636
- # key::
637
- # Key to be added.
638
- # value::
639
- # Value to be associated with key.
640
- def merge(hash, key, value)
641
- if value.instance_of?(String)
642
- value = normalise_space(value) if @options['normalisespace'] == 2
643
-
644
- # do variable substitutions
645
- unless @_var_values.nil? || @_var_values.empty?
646
- value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
647
- end
648
-
649
- # look for variable definitions
650
- if @options.has_key?('varattr')
651
- varattr = @options['varattr']
652
- if hash.has_key?(varattr)
653
- set_var(hash[varattr], value)
654
- end
655
- end
656
- end
657
- if hash.has_key?(key)
658
- if hash[key].instance_of?(Array)
659
- hash[key] << value
660
- else
661
- hash[key] = [ hash[key], value ]
662
- end
663
- elsif value.instance_of?(Array) # Handle anonymous arrays.
664
- hash[key] = [ value ]
665
- else
666
- if force_array?(key)
667
- hash[key] = [ value ]
668
- else
669
- hash[key] = value
670
- end
671
- end
672
- hash
673
- end
674
-
675
- # Checks, if the 'forcearray' option has to be used for
676
- # a certain key.
677
- def force_array?(key)
678
- return false if key == @options['contentkey']
679
- return true if @options['forcearray'] == true
680
- forcearray = @options['forcearray']
681
- if forcearray.instance_of?(Hash)
682
- return true if forcearray.has_key?(key)
683
- return false unless forcearray.has_key?('_regex')
684
- forcearray['_regex'].each { |x| return true if key =~ x }
685
- end
686
- return false
687
- end
688
-
689
- # Converts the attributes array of a document node into a Hash.
690
- # Returns an empty Hash, if node has no attributes.
691
- #
692
- # node::
693
- # Document node to extract attributes from.
694
- def get_attributes(node)
695
- attributes = {}
696
- node.attributes.each { |n,v| attributes[n] = v }
697
- attributes
698
- end
699
-
700
- # Determines, if a document element has mixed content.
701
- #
702
- # element::
703
- # Document element to be checked.
704
- def has_mixed_content?(element)
705
- if element.has_text? && element.has_elements?
706
- return true if element.texts.join('') !~ /^\s*$/s
707
- end
708
- false
709
- end
710
-
711
- # Called when a variable definition is encountered in the XML.
712
- # A variable definition looks like
713
- # <element attrname="name">value</element>
714
- # where attrname matches the varattr setting.
715
- def set_var(name, value)
716
- @_var_values[name] = value
717
- end
718
-
719
- # Called during variable substitution to get the value for the
720
- # named variable.
721
- def get_var(name)
722
- if @_var_values.has_key?(name)
723
- return @_var_values[name]
724
- else
725
- return "${#{name}}"
726
- end
727
- end
728
-
729
- # Recurses through a data structure building up and returning an
730
- # XML representation of that structure as a string.
731
- #
732
- # ref::
733
- # Reference to the data structure to be encoded.
734
- # name::
735
- # The XML tag name to be used for this item.
736
- # indent::
737
- # A string of spaces for use as the current indent level.
738
- def value_to_xml(ref, name, indent)
739
- named = !name.nil? && name != ''
740
- nl = @options.has_key?('noindent') ? '' : "\n"
741
-
742
- if !scalar(ref)
743
- if @ancestors.member?(ref)
744
- raise ArgumentError, "Circular data structures not supported!"
745
- end
746
- @ancestors << ref
747
- else
748
- if named
749
- return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '</', name, '>', nl].join('')
750
- else
751
- return ref.to_s + nl
752
- end
753
- end
754
-
755
- # Unfold hash to array if possible.
756
- if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
757
- ref = hash_to_array(name, ref)
758
- end
759
-
760
- result = []
761
- if ref.instance_of?(Hash)
762
- # Reintermediate grouped values if applicable.
763
- if @options.has_key?('grouptags')
764
- ref.each { |key, value|
765
- if @options['grouptags'].has_key?(key)
766
- ref[key] = { @options['grouptags'][key] => value }
767
- end
768
- }
769
- end
770
-
771
- nested = []
772
- text_content = nil
773
- if named
774
- result << indent << '<' << name
775
- end
776
-
777
- if !ref.empty?
778
- ref.each { |key, value|
779
- next if !key.nil? && key[0, 1] == '-'
780
- if value.nil?
781
- unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
782
- raise ArgumentError, "Use of uninitialized value!"
783
- end
784
- value = {}
785
- end
786
-
787
- if !scalar(value) || @options['noattr']
788
- nested << value_to_xml(value, key, indent + @options['indent'])
789
- else
790
- value = value.to_s
791
- value = escape_value(value) unless @options['noescape']
792
- if key == @options['contentkey']
793
- text_content = value
794
- else
795
- result << ' ' << key << '="' << value << '"'
796
- end
797
- end
798
- }
799
- else
800
- text_content = ''
801
- end
802
-
803
- if !nested.empty? || !text_content.nil?
804
- if named
805
- result << '>'
806
- if !text_content.nil?
807
- result << text_content
808
- nested[0].sub!(/^\s+/, '') if !nested.empty?
809
- else
810
- result << nl
811
- end
812
- if !nested.empty?
813
- result << nested << indent
814
- end
815
- result << '</' << name << '>' << nl
816
- else
817
- result << nested
818
- end
819
- else
820
- result << ' />' << nl
821
- end
822
- elsif ref.instance_of?(Array)
823
- ref.each { |value|
824
- if scalar(value)
825
- result << indent << '<' << name << '>'
826
- result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
827
- result << '</' << name << '>' << nl
828
- elsif value.instance_of?(Hash)
829
- result << value_to_xml(value, name, indent)
830
- else
831
- result << indent << '<' << name << '>' << nl
832
- result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent'])
833
- result << indent << '</' << name << '>' << nl
834
- end
835
- }
836
- else
837
- # Probably, this is obsolete.
838
- raise ArgumentError, "Can't encode a value of type: #{ref.type}."
839
- end
840
- @ancestors.pop if !scalar(ref)
841
- result.join('')
842
- end
843
-
844
- # Checks, if a certain value is a "scalar" value. Whatever
845
- # that will be in Ruby ... ;-)
846
- #
847
- # value::
848
- # Value to be checked.
849
- def scalar(value)
850
- return false if value.instance_of?(Hash) || value.instance_of?(Array)
851
- return true
852
- end
853
-
854
- # Attempts to unfold a hash of hashes into an array of hashes. Returns
855
- # a reference to th array on success or the original hash, if unfolding
856
- # is not possible.
857
- #
858
- # parent::
859
- #
860
- # hashref::
861
- # Reference to the hash to be unfolded.
862
- def hash_to_array(parent, hashref)
863
- arrayref = []
864
- hashref.each { |key, value|
865
- return hashref unless value.instance_of?(Hash)
866
-
867
- if @options['keyattr'].instance_of?(Hash)
868
- return hashref unless @options['keyattr'].has_key?(parent)
869
- arrayref << { @options['keyattr'][parent][0] => key }.update(value)
870
- else
871
- arrayref << { @options['keyattr'][0] => key }.update(value)
872
- end
873
- }
874
- arrayref
875
- end
876
-
877
- # Replaces XML markup characters by their external entities.
878
- #
879
- # data::
880
- # The string to be escaped.
881
- def escape_value(data)
882
- return data if data.nil? || data == ''
883
- result = data.dup
884
- result.gsub!('&', '&amp;')
885
- result.gsub!('<', '&lt;')
886
- result.gsub!('>', '&gt;')
887
- result.gsub!('"', '&quot;')
888
- result.gsub!("'", '&apos;')
889
- result
890
- end
891
-
892
- # Removes leading and trailing whitespace and sequences of
893
- # whitespaces from a string.
894
- #
895
- # text::
896
- # String to be normalised.
897
- def normalise_space(text)
898
- text.sub!(/^\s+/, '')
899
- text.sub!(/\s+$/, '')
900
- text.gsub!(/\s\s+/, ' ')
901
- text
902
- end
903
-
904
- # Checks, if an object is nil, an empty String or an empty Hash.
905
- # Thanks to Norbert Gawor for a bugfix.
906
- #
907
- # value::
908
- # Value to be checked for emptyness.
909
- def empty(value)
910
- case value
911
- when Hash
912
- return value.empty?
913
- when String
914
- return value !~ /\S/m
915
- else
916
- return value.nil?
917
- end
918
- end
919
-
920
- # Converts a document node into a String.
921
- # If the node could not be converted into a String
922
- # for any reason, default will be returned.
923
- #
924
- # node::
925
- # Document node to be converted.
926
- # default::
927
- # Value to be returned, if node could not be converted.
928
- def node_to_text(node, default = nil)
929
- if node.instance_of?(Element)
930
- return node.texts.join('')
931
- elsif node.instance_of?(Attribute)
932
- return node.value.nil? ? default : node.value.strip
933
- elsif node.instance_of?(Text)
934
- return node.to_s.strip
935
- else
936
- return default
937
- end
938
- end
939
-
940
- # Parses an XML string and returns the according document.
941
- #
942
- # xml_string::
943
- # XML string to be parsed.
944
- #
945
- # The following exception may be raised:
946
- #
947
- # REXML::ParseException::
948
- # If the specified file is not wellformed.
949
- def parse(xml_string)
950
- Document.new(xml_string)
951
- end
952
-
953
- # Searches in a list of paths for a certain file. Returns
954
- # the full path to the file, if it could be found. Otherwise,
955
- # an exception will be raised.
956
- #
957
- # filename::
958
- # Name of the file to search for.
959
- # searchpath::
960
- # List of paths to search in.
961
- def find_xml_file(file, searchpath)
962
- filename = File::basename(file)
963
-
964
- if filename != file
965
- return file if File::file?(file)
966
- else
967
- searchpath.each { |path|
968
- full_path = File::join(path, filename)
969
- return full_path if File::file?(full_path)
970
- }
971
- end
972
-
973
- if searchpath.empty?
974
- return file if File::file?(file)
975
- raise ArgumentError, "File does not exist: #{file}."
976
- end
977
- raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
978
- end
979
-
980
- # Loads and parses an XML configuration file.
981
- #
982
- # filename::
983
- # Name of the configuration file to be loaded.
984
- #
985
- # The following exceptions may be raised:
986
- #
987
- # Errno::ENOENT::
988
- # If the specified file does not exist.
989
- # REXML::ParseException::
990
- # If the specified file is not wellformed.
991
- def load_xml_file(filename)
992
- parse(File.readlines(filename).to_s)
993
- end
994
-
995
- # Caches the data belonging to a certain file.
996
- #
997
- # data::
998
- # Data to be cached.
999
- # filename::
1000
- # Name of file the data was read from.
1001
- def put_into_cache(data, filename)
1002
- if @options.has_key?('cache')
1003
- @options['cache'].each { |scheme|
1004
- case(scheme)
1005
- when 'storable'
1006
- @@cache.save_storable(data, filename)
1007
- when 'mem_share'
1008
- @@cache.save_mem_share(data, filename)
1009
- when 'mem_copy'
1010
- @@cache.save_mem_copy(data, filename)
1011
- else
1012
- raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
1013
- end
1014
- }
1015
- end
1016
- end
1017
- end
1018
-
1019
- # vim:sw=2