xml-mapping 0.8.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/ChangeLog +64 -3
  2. data/README +871 -173
  3. data/README_XPATH +40 -13
  4. data/Rakefile +37 -26
  5. data/TODO.txt +39 -8
  6. data/examples/README +5 -0
  7. data/examples/company_usage.intout +34 -22
  8. data/examples/documents_folders.rb +31 -0
  9. data/examples/documents_folders.xml +16 -0
  10. data/examples/documents_folders_usage.intin.rb +18 -0
  11. data/examples/documents_folders_usage.intout +46 -0
  12. data/examples/order_signature_enhanced_usage.intout +21 -11
  13. data/examples/order_usage.intin.rb +52 -5
  14. data/examples/order_usage.intout +154 -80
  15. data/examples/person.intin.rb +44 -0
  16. data/examples/person.intout +27 -0
  17. data/examples/person_mm.intin.rb +119 -0
  18. data/examples/person_mm.intout +114 -0
  19. data/examples/publication.intin.rb +44 -0
  20. data/examples/publication.intout +20 -0
  21. data/examples/reader.intin.rb +33 -0
  22. data/examples/reader.intout +19 -0
  23. data/examples/stringarray.rb +5 -0
  24. data/examples/stringarray.xml +10 -0
  25. data/examples/stringarray_usage.intin.rb +11 -0
  26. data/examples/stringarray_usage.intout +31 -0
  27. data/examples/time_augm.intout +19 -7
  28. data/examples/time_augm_loading.intin.rb +44 -0
  29. data/examples/time_augm_loading.intout +12 -0
  30. data/examples/time_node.intin.rb +79 -0
  31. data/examples/time_node.rb +3 -2
  32. data/examples/time_node_w_marshallers.intin.rb +48 -0
  33. data/examples/time_node_w_marshallers.intout +25 -0
  34. data/examples/time_node_w_marshallers.xml +9 -0
  35. data/examples/xpath_create_new.intout +132 -114
  36. data/examples/xpath_ensure_created.intout +86 -65
  37. data/examples/xpath_pathological.intout +16 -16
  38. data/examples/xpath_usage.intout +1 -1
  39. data/install.rb +1 -0
  40. data/lib/xml/mapping.rb +3 -1
  41. data/lib/xml/mapping/base.rb +442 -272
  42. data/lib/xml/mapping/core_classes_mapping.rb +32 -0
  43. data/lib/xml/mapping/standard_nodes.rb +176 -86
  44. data/lib/xml/mapping/version.rb +2 -2
  45. data/lib/xml/rexml_ext.rb +186 -0
  46. data/lib/xml/xxpath.rb +28 -265
  47. data/lib/xml/xxpath/steps.rb +345 -0
  48. data/lib/xml/xxpath_methods.rb +96 -0
  49. data/test/all_tests.rb +4 -1
  50. data/test/benchmark_fixtures.rb +14 -0
  51. data/test/{multiple_mappings.rb → bookmarks.rb} +0 -0
  52. data/test/company.rb +47 -0
  53. data/test/documents_folders.rb +11 -1
  54. data/test/examples_test.rb +29 -0
  55. data/test/fixtures/benchmark.xml +77 -0
  56. data/test/fixtures/company1.xml +9 -0
  57. data/test/fixtures/documents_folders.xml +0 -8
  58. data/test/fixtures/documents_folders2.xml +13 -19
  59. data/test/fixtures/triangle_m1.xml +17 -0
  60. data/test/fixtures/triangle_m2.xml +19 -0
  61. data/test/inheritance_test.rb +50 -0
  62. data/test/multiple_mappings_test.rb +155 -0
  63. data/test/rexml_xpath_benchmark.rb +29 -0
  64. data/test/triangle_mm.rb +57 -0
  65. data/test/xml_mapping_adv_test.rb +36 -1
  66. data/test/xml_mapping_test.rb +136 -7
  67. data/test/xpath_test.rb +154 -0
  68. data/test/xxpath_benchmark.rb +36 -0
  69. data/test/xxpath_benchmark.result1.txt +17 -0
  70. data/test/xxpath_methods_test.rb +61 -0
  71. metadata +139 -90
@@ -0,0 +1,32 @@
1
+ class String
2
+ def self.load_from_xml(xml, options={:mapping=>:_default})
3
+ xml.text
4
+ end
5
+
6
+ def fill_into_xml(xml, options={:mapping=>:_default})
7
+ xml.text = self
8
+ end
9
+
10
+ def text
11
+ self
12
+ end
13
+ end
14
+
15
+
16
+ class Numeric
17
+ def self.load_from_xml(xml, options={:mapping=>:_default})
18
+ begin
19
+ Integer(xml.text)
20
+ rescue ArgumentError
21
+ Float(xml.text)
22
+ end
23
+ end
24
+
25
+ def fill_into_xml(xml, options={:mapping=>:_default})
26
+ xml.text = self.to_s
27
+ end
28
+
29
+ def text
30
+ self.to_s
31
+ end
32
+ end
@@ -9,21 +9,21 @@ module XML
9
9
  #
10
10
  # text_node :_attrname_, _path_ [, :default_value=>_obj_]
11
11
  # [, :optional=>true]
12
+ # [, :mapping=>_m_]
12
13
  #
13
14
  # Node that maps an XML node's text (the element's first child
14
15
  # text node resp. the attribute's value) to a (string) attribute
15
- # of the mapped object. Since TextNode inherits from
16
- # SingleAttributeNode, the first argument to the node factory
17
- # function is the attribute name (as a symbol). Handling of
18
- # <tt>:default_value</tt> and <tt>:optional</tt> option arguments
19
- # (if given) is also provided by the superclass -- see there for
20
- # details.
16
+ # of the mapped object. _path_ (an XPath expression) locates the
17
+ # XML node, _attrname_ (a symbol) names the
18
+ # attribute. <tt>:default_value</tt> is the default value,
19
+ # :optional=>true is equivalent to :default_value=>nil (see
20
+ # superclass documentation for details). <tt>_m_</tt> is the
21
+ # mapping; it defaults to the current default mapping
21
22
  class TextNode < SingleAttributeNode
22
- # Initializer. _path_ (a string, the 2nd argument to the node
23
- # factory function) is the XPath expression that locates the
24
- # mapped node in the XML.
25
- def initialize_impl(path)
23
+ def initialize(*args)
24
+ path,*args = super(*args)
26
25
  @path = XML::XXPath.new(path)
26
+ args
27
27
  end
28
28
  def extract_attr_value(xml) # :nodoc:
29
29
  default_when_xpath_err{ @path.first(xml).text }
@@ -37,13 +37,16 @@ module XML
37
37
  #
38
38
  # numeric_node :_attrname_, _path_ [, :default_value=>_obj_]
39
39
  # [, :optional=>true]
40
+ # [, :mapping=>_m_]
40
41
  #
41
42
  # Like TextNode, but interprets the XML node's text as a number
42
43
  # (Integer or Float, depending on the nodes's text) and maps it to
43
44
  # an Integer or Float attribute.
44
45
  class NumericNode < SingleAttributeNode
45
- def initialize_impl(path)
46
+ def initialize(*args)
47
+ path,*args = super(*args)
46
48
  @path = XML::XXPath.new(path)
49
+ args
47
50
  end
48
51
  def extract_attr_value(xml) # :nodoc:
49
52
  txt = default_when_xpath_err{ @path.first(xml).text }
@@ -67,10 +70,9 @@ module XML
67
70
  class SubObjectBaseNode < SingleAttributeNode
68
71
  # processes the keyword arguments :class, :marshaller, and
69
72
  # :unmarshaller (_args_ is ignored). When this initiaizer
70
- # returns, @options[:marshaller] and @options[:unmarshaller] are
71
- # set to procs that marshal/unmarshal a Ruby object to/from an
72
- # XML tree according to the keyword arguments that were passed
73
- # to the initializer:
73
+ # returns, @marshaller and @unmarshaller are set to procs that
74
+ # marshal/unmarshal a Ruby object to/from an XML tree according
75
+ # to the keyword arguments that were passed to the initializer:
74
76
  #
75
77
  # You either supply a :class argument with a class implementing
76
78
  # XML::Mapping -- in that case, the subtree will be mapped to an
@@ -89,37 +91,50 @@ module XML
89
91
  #
90
92
  # If both :class and :marshaller/:unmarshaller arguments are
91
93
  # supplied, the latter take precedence.
92
- def initialize_impl(*args)
94
+ def initialize(*args)
95
+ args = super(*args)
96
+
97
+ @sub_mapping = @options[:sub_mapping] || @mapping
98
+ @marshaller, @unmarshaller = @options[:marshaller], @options[:unmarshaller]
99
+
93
100
  if @options[:class]
94
- unless @options[:marshaller]
95
- @options[:marshaller] = proc {|xml,value|
96
- value.fill_into_xml(xml)
101
+ unless @marshaller
102
+ @marshaller = proc {|xml,value|
103
+ value.fill_into_xml xml, :mapping=>@sub_mapping
104
+ if xml.unspecified?
105
+ xml.name = value.class.root_element_name :mapping=>@sub_mapping
106
+ xml.unspecified = false
107
+ end
97
108
  }
98
109
  end
99
- unless @options[:unmarshaller]
100
- @options[:unmarshaller] = proc {|xml|
101
- @options[:class].load_from_xml(xml)
110
+ unless @unmarshaller
111
+ @unmarshaller = proc {|xml|
112
+ @options[:class].load_from_xml xml, :mapping=>@sub_mapping
102
113
  }
103
114
  end
104
115
  end
105
116
 
106
- unless @options[:marshaller]
107
- @options[:marshaller] = proc {|xml,value|
108
- value.fill_into_xml(xml)
117
+ unless @marshaller
118
+ @marshaller = proc {|xml,value|
119
+ value.fill_into_xml xml, :mapping=>@sub_mapping
109
120
  if xml.unspecified?
110
- xml.name = value.class.root_element_name
121
+ xml.name = value.class.root_element_name :mapping=>@sub_mapping
111
122
  xml.unspecified = false
112
123
  end
113
124
  }
114
125
  end
115
- unless @options[:unmarshaller]
116
- @options[:unmarshaller] = proc {|xml|
117
- XML::Mapping.load_object_from_xml(xml)
126
+ unless @unmarshaller
127
+ @unmarshaller = proc {|xml|
128
+ XML::Mapping.load_object_from_xml xml, :mapping=>@sub_mapping
118
129
  }
119
130
  end
131
+
132
+ args
120
133
  end
121
134
  end
122
135
 
136
+ require 'xml/mapping/core_classes_mapping'
137
+
123
138
  # Node factory function synopsis:
124
139
  #
125
140
  # object_node :_attrname_, _path_ [, :default_value=>_obj_]
@@ -127,6 +142,8 @@ module XML
127
142
  # [, :class=>_c_]
128
143
  # [, :marshaller=>_proc_]
129
144
  # [, :unmarshaller=>_proc_]
145
+ # [, :mapping=>_m_]
146
+ # [, :sub_mapping=>_sm_]
130
147
  #
131
148
  # Node that maps a subtree in the source XML to a Ruby
132
149
  # object. :_attrname_ and _path_ are again the attribute name
@@ -140,15 +157,16 @@ module XML
140
157
  class ObjectNode < SubObjectBaseNode
141
158
  # Initializer. _path_ (a string denoting an XPath expression) is
142
159
  # the location of the subtree.
143
- def initialize_impl(path)
144
- super
145
- @path = XML::XXPath.new(path)
160
+ def initialize(*args)
161
+ path,*args = super(*args)
162
+ @path = XML::XXPath.new(path)
163
+ args
146
164
  end
147
165
  def extract_attr_value(xml) # :nodoc:
148
- @options[:unmarshaller].call(default_when_xpath_err{@path.first(xml)})
166
+ @unmarshaller.call(default_when_xpath_err{@path.first(xml)})
149
167
  end
150
168
  def set_attr_value(xml, value) # :nodoc:
151
- @options[:marshaller].call(@path.first(xml,:ensure_created=>true), value)
169
+ @marshaller.call(@path.first(xml,:ensure_created=>true), value)
152
170
  end
153
171
  end
154
172
 
@@ -157,6 +175,7 @@ module XML
157
175
  # boolean_node :_attrname_, _path_,
158
176
  # _true_value_, _false_value_ [, :default_value=>_obj_]
159
177
  # [, :optional=>true]
178
+ # [, :mapping=>_m_]
160
179
  #
161
180
  # Node that maps an XML node's text (the element name resp. the
162
181
  # attribute value) to a boolean attribute of the mapped
@@ -168,9 +187,11 @@ module XML
168
187
  # represent the +false+ boolean value.
169
188
  class BooleanNode < SingleAttributeNode
170
189
  # Initializer.
171
- def initialize_impl(path,true_value,false_value)
190
+ def initialize(*args)
191
+ path,true_value,false_value,*args = super(*args)
172
192
  @path = XML::XXPath.new(path)
173
193
  @true_value = true_value; @false_value = false_value
194
+ args
174
195
  end
175
196
  def extract_attr_value(xml) # :nodoc:
176
197
  default_when_xpath_err{ @path.first(xml).text==@true_value }
@@ -188,6 +209,8 @@ module XML
188
209
  # [, :class=>_c_]
189
210
  # [, :marshaller=>_proc_]
190
211
  # [, :unmarshaller=>_proc_]
212
+ # [, :mapping=>_m_]
213
+ # [, :sub_mapping=>_sm_]
191
214
  #
192
215
  # -or-
193
216
  #
@@ -233,38 +256,35 @@ module XML
233
256
  # </bar>
234
257
  # </foo>
235
258
  class ArrayNode < SubObjectBaseNode
236
- # Initializer, delegates to do_initialize. Called with keyword
237
- # arguments and either 1 or 2 paths; the hindmost path argument
238
- # passed is delegated to _per_arrelement_path_; the preceding
239
- # path argument (if present, "" by default) is delegated to
240
- # _base_path_.
241
- def initialize_impl(path,path2=nil)
242
- super
243
- if path2
244
- do_initialize(path,path2)
245
- else
246
- do_initialize("",path)
247
- end
248
- end
249
- # "Real" initializer.
250
- def do_initialize(base_path,per_arrelement_path)
251
- per_arrelement_path=per_arrelement_path[1..-1] if per_arrelement_path[0]==?/
252
- @base_path = XML::XXPath.new(base_path)
253
- @per_arrelement_path = XML::XXPath.new(per_arrelement_path)
254
- @reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path)
259
+ # Initializer. Called with keyword arguments and either 1 or 2
260
+ # paths; the hindmost path argument passed is delegated to
261
+ # _per_arrelement_path_; the preceding path argument (if
262
+ # present, "" by default) is delegated to _base_path_.
263
+ def initialize(*args)
264
+ path,path2,*args = super(*args)
265
+ base_path,per_arrelement_path = if path2
266
+ [path,path2]
267
+ else
268
+ [".",path]
269
+ end
270
+ per_arrelement_path=per_arrelement_path[1..-1] if per_arrelement_path[0]==?/
271
+ @base_path = XML::XXPath.new(base_path)
272
+ @per_arrelement_path = XML::XXPath.new(per_arrelement_path)
273
+ @reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path)
274
+ args
255
275
  end
256
276
  def extract_attr_value(xml) # :nodoc:
257
277
  result = []
258
278
  default_when_xpath_err{@reader_path.all(xml)}.each do |elt|
259
- result << @options[:unmarshaller].call(elt)
279
+ result << @unmarshaller.call(elt)
260
280
  end
261
281
  result
262
282
  end
263
283
  def set_attr_value(xml, value) # :nodoc:
264
- base_elt = @base_path.first(xml,:ensure_created=>true)
265
- value.each do |arr_elt|
266
- @options[:marshaller].call(@per_arrelement_path.create_new(base_elt), arr_elt)
267
- end
284
+ base_elt = @base_path.first(xml,:ensure_created=>true)
285
+ value.each do |arr_elt|
286
+ @marshaller.call(@per_arrelement_path.create_new(base_elt), arr_elt)
287
+ end
268
288
  end
269
289
  end
270
290
 
@@ -277,6 +297,8 @@ module XML
277
297
  # [, :class=>_c_]
278
298
  # [, :marshaller=>_proc_]
279
299
  # [, :unmarshaller=>_proc_]
300
+ # [, :mapping=>_m_]
301
+ # [, :sub_mapping=>_sm_]
280
302
  #
281
303
  # - or -
282
304
  #
@@ -296,45 +318,113 @@ module XML
296
318
  # to such a node, key_path_ names the node whose text becomes the
297
319
  # associated hash key.
298
320
  class HashNode < SubObjectBaseNode
299
- # Initializer, delegates to do_initialize. Called with keyword
300
- # arguments and either 2 or 3 paths; the hindmost path argument
301
- # passed is delegated to _key_path_, the preceding path argument
302
- # is delegated to _per_arrelement_path_, the path preceding that
303
- # argument (if present, "" by default) is delegated to
304
- # _base_path_. The meaning of the keyword arguments is the same
305
- # as for ObjectNode.
306
- def initialize_impl(path1,path2,path3=nil)
307
- super
308
- if path3
309
- do_initialize(path1,path2,path3)
310
- else
311
- do_initialize("",path1,path2)
312
- end
313
- end
314
- # "Real" initializer.
315
- def do_initialize(base_path,per_hashelement_path,key_path)
316
- per_hashelement_path=per_hashelement_path[1..-1] if per_hashelement_path[0]==?/
317
- @base_path = XML::XXPath.new(base_path)
318
- @per_hashelement_path = XML::XXPath.new(per_hashelement_path)
319
- @key_path = XML::XXPath.new(key_path)
320
- @reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path)
321
+ # Initializer. Called with keyword arguments and either 2 or 3
322
+ # paths; the hindmost path argument passed is delegated to
323
+ # _key_path_, the preceding path argument is delegated to
324
+ # _per_arrelement_path_, the path preceding that argument (if
325
+ # present, "" by default) is delegated to _base_path_. The
326
+ # meaning of the keyword arguments is the same as for
327
+ # ObjectNode.
328
+ def initialize(*args)
329
+ path1,path2,path3,*args = super(*args)
330
+ base_path,per_hashelement_path,key_path = if path3
331
+ [path1,path2,path3]
332
+ else
333
+ ["",path1,path2]
334
+ end
335
+ per_hashelement_path=per_hashelement_path[1..-1] if per_hashelement_path[0]==?/
336
+ @base_path = XML::XXPath.new(base_path)
337
+ @per_hashelement_path = XML::XXPath.new(per_hashelement_path)
338
+ @key_path = XML::XXPath.new(key_path)
339
+ @reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path)
340
+ args
321
341
  end
322
342
  def extract_attr_value(xml) # :nodoc:
323
343
  result = {}
324
344
  default_when_xpath_err{@reader_path.all(xml)}.each do |elt|
325
345
  key = @key_path.first(elt).text
326
- value = @options[:unmarshaller].call(elt)
346
+ value = @unmarshaller.call(elt)
327
347
  result[key] = value
328
348
  end
329
349
  result
330
350
  end
331
351
  def set_attr_value(xml, value) # :nodoc:
332
- base_elt = @base_path.first(xml,:ensure_created=>true)
333
- value.each_pair do |k,v|
352
+ base_elt = @base_path.first(xml,:ensure_created=>true)
353
+ value.each_pair do |k,v|
334
354
  elt = @per_hashelement_path.create_new(base_elt)
335
- @options[:marshaller].call(elt,v)
355
+ @marshaller.call(elt,v)
336
356
  @key_path.first(elt,:ensure_created=>true).text = k
337
- end
357
+ end
358
+ end
359
+ end
360
+
361
+
362
+ class ChoiceNode < Node
363
+
364
+ def initialize(*args)
365
+ args = super(*args)
366
+ @choices = []
367
+ path=nil
368
+ args.each do |arg|
369
+ next if [:if,:then,:elsif].include? arg
370
+ if path.nil?
371
+ path = (if [:else,:default,:otherwise].include? arg
372
+ :else
373
+ else
374
+ XML::XXPath.new arg
375
+ end)
376
+ else
377
+ raise XML::MappingError, "node expected, found: #{arg.inspect}" unless Node===arg
378
+ @choices << [path,arg]
379
+
380
+ # undo what the node factory fcn did -- ugly ugly! would
381
+ # need some way to lazy-evaluate arg (a proc would be
382
+ # simple but ugly for the user), and then use some
383
+ # mechanism (a flag with dynamic scope probably) to tell
384
+ # the node factory fcn not to add the node to the
385
+ # xml_mapping_nodes
386
+ @owner.xml_mapping_nodes(:mapping=>@mapping).delete arg
387
+ path=nil
388
+ end
389
+ end
390
+
391
+ raise XML::MappingError, "node missing at end of argument list" unless path.nil?
392
+ raise XML::MappingError, "no choices were supplied" if @choices.empty?
393
+
394
+ []
395
+ end
396
+
397
+ def xml_to_obj(obj,xml)
398
+ @choices.each do |path,node|
399
+ if path==:else or not(path.all(xml).empty?)
400
+ node.xml_to_obj(obj,xml)
401
+ return true
402
+ end
403
+ end
404
+ raise XML::MappingError, "xml_to_obj: no choice matched in: #{xml}"
405
+ end
406
+
407
+ def obj_to_xml(obj,xml)
408
+ @choices.each do |path,node|
409
+ if node.is_present_in? obj
410
+ node.obj_to_xml(obj,xml)
411
+ path.first(xml, :ensure_created=>true)
412
+ return true
413
+ end
414
+ end
415
+ # @choices[0][1].obj_to_xml(obj,xml)
416
+ raise XML::MappingError, "obj_to_xml: no choice present in object: #{obj.inspect}"
417
+ end
418
+
419
+ def obj_initializing(obj,mapping)
420
+ @choices[0][1].obj_initializing(obj,mapping)
421
+ end
422
+
423
+ # (overridden) true if at least one of our nodes is_present_in?
424
+ # obj.
425
+ def is_present_in? obj
426
+ # TODO: use Enumerable#any?
427
+ @choices.inject(false){|prev,(path,node)| prev or node.is_present_in?(obj)}
338
428
  end
339
429
  end
340
430
 
@@ -1,8 +1,8 @@
1
1
  # xml-mapping -- bidirectional Ruby-XML mapper
2
- # Copyright (C) 2004,2005 Olaf Klischat
2
+ # Copyright (C) 2004-2010 Olaf Klischat
3
3
 
4
4
  module XML
5
5
  module Mapping
6
- VERSION = '0.8.1'
6
+ VERSION = '0.9.1'
7
7
  end
8
8
  end
@@ -0,0 +1,186 @@
1
+ # xxpath -- XPath implementation for Ruby, including write access
2
+ # Copyright (C) 2004-2010 Olaf Klischat
3
+
4
+ require 'rexml/document'
5
+
6
+ module XML
7
+
8
+ class XXPath
9
+ module Accessors #:nodoc:
10
+
11
+ # we need a boolean "unspecified?" attribute for XML nodes --
12
+ # paths like "*" oder (somewhen) "foo|bar" create "unspecified"
13
+ # nodes that the user must then "specify" by setting their text
14
+ # etc. (or manually setting unspecified=false)
15
+ #
16
+ # This is mixed into the REXML::Element and
17
+ # XML::XXPath::Accessors::Attribute classes.
18
+ module UnspecifiednessSupport
19
+
20
+ def unspecified?
21
+ @xml_xpath_unspecified ||= false
22
+ end
23
+
24
+ def unspecified=(x)
25
+ @xml_xpath_unspecified = x
26
+ end
27
+
28
+ def self.append_features(base)
29
+ return if base.included_modules.include? self # avoid aliasing methods more than once
30
+ # (would lead to infinite recursion)
31
+ super
32
+ base.module_eval <<-EOS
33
+ alias_method :_text_orig, :text
34
+ alias_method :_textis_orig, :text=
35
+ def text
36
+ # we're suffering from the "fragile base class"
37
+ # phenomenon here -- we don't know whether the
38
+ # implementation of the class we get mixed into always
39
+ # calls text (instead of just accessing @text or so)
40
+ if unspecified?
41
+ "[UNSPECIFIED]"
42
+ else
43
+ _text_orig
44
+ end
45
+ end
46
+ def text=(x)
47
+ _textis_orig(x)
48
+ self.unspecified=false
49
+ end
50
+
51
+ alias_method :_nameis_orig, :name=
52
+ def name=(x)
53
+ _nameis_orig(x)
54
+ self.unspecified=false
55
+ end
56
+ EOS
57
+ end
58
+
59
+ end
60
+
61
+ class REXML::Element #:nodoc:
62
+ include UnspecifiednessSupport
63
+ end
64
+
65
+ # attribute node, more or less call-compatible with REXML's
66
+ # Element. REXML's Attribute class doesn't provide this...
67
+ #
68
+ # The all/first calls return instances of this class if they
69
+ # matched an attribute node.
70
+ class Attribute
71
+ attr_reader :parent, :name
72
+ attr_writer :name
73
+
74
+ def initialize(parent,name)
75
+ @parent,@name = parent,name
76
+ end
77
+
78
+ def self.new(parent,name,create)
79
+ if parent.attributes[name]
80
+ super(parent,name)
81
+ else
82
+ if create
83
+ parent.attributes[name] = "[unset]"
84
+ super(parent,name)
85
+ else
86
+ nil
87
+ end
88
+ end
89
+ end
90
+
91
+ # the value of the attribute.
92
+ def text
93
+ parent.attributes[@name]
94
+ end
95
+
96
+ def text=(x)
97
+ parent.attributes[@name] = x
98
+ end
99
+
100
+ def ==(other)
101
+ other.kind_of?(Attribute) and other.parent==parent and other.name==name
102
+ end
103
+
104
+ include UnspecifiednessSupport
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+
113
+
114
+
115
+
116
+
117
+ class REXML::Parent
118
+ def each_on_axis_child
119
+ if respond_to? :attributes
120
+ attributes.each_key do |name|
121
+ yield XML::XXPath::Accessors::Attribute.new(self, name, false)
122
+ end
123
+ end
124
+ each_child do |c|
125
+ yield c
126
+ end
127
+ end
128
+
129
+ def each_on_axis_descendant(&block)
130
+ each_on_axis_child do |c|
131
+ block.call c
132
+ if REXML::Parent===c
133
+ c.each_on_axis_descendant(&block)
134
+ end
135
+ end
136
+ end
137
+
138
+ def each_on_axis_self
139
+ yield self
140
+ end
141
+
142
+ def each_on_axis(axis, &block)
143
+ send :"each_on_axis_#{axis}", &block
144
+ end
145
+ end
146
+
147
+
148
+ ## hotfix for REXML bug #128 -- see http://trac.germane-software.com/rexml/ticket/128
149
+ # a working Element#write is required by several tests and
150
+ # documentation code snippets
151
+ begin
152
+ # temporarily suppress warnings
153
+ class <<Kernel
154
+ alias_method :old_warn, :warn
155
+ def warn(msg)
156
+ end
157
+ end
158
+ begin
159
+ # detect bug
160
+ REXML::Element.new.write("",2)
161
+ ensure
162
+ # unsuppress
163
+ class <<Kernel
164
+ alias_method :warn, :old_warn
165
+ end
166
+ end
167
+ rescue NameError
168
+ # bug is present -- fix it. I use Element#write in numerous tests and rdoc
169
+ # inline code snippets. TODO: switch to REXML::Formatters there sometime.
170
+ class REXML::Element
171
+ def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
172
+ Kernel.warn("#{self.class.name}.write is deprecated. See REXML::Formatters")
173
+ formatter = if indent > -1
174
+ if transitive
175
+ require "rexml/formatters/transitive"
176
+ REXML::Formatters::Transitive.new( indent, ie_hack )
177
+ else
178
+ REXML::Formatters::Pretty.new( indent, ie_hack )
179
+ end
180
+ else
181
+ REXML::Formatters::Default.new( ie_hack )
182
+ end
183
+ formatter.write( self, output )
184
+ end
185
+ end
186
+ end