xml-mapping 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +64 -3
- data/README +871 -173
- data/README_XPATH +40 -13
- data/Rakefile +37 -26
- data/TODO.txt +39 -8
- data/examples/README +5 -0
- data/examples/company_usage.intout +34 -22
- data/examples/documents_folders.rb +31 -0
- data/examples/documents_folders.xml +16 -0
- data/examples/documents_folders_usage.intin.rb +18 -0
- data/examples/documents_folders_usage.intout +46 -0
- data/examples/order_signature_enhanced_usage.intout +21 -11
- data/examples/order_usage.intin.rb +52 -5
- data/examples/order_usage.intout +154 -80
- data/examples/person.intin.rb +44 -0
- data/examples/person.intout +27 -0
- data/examples/person_mm.intin.rb +119 -0
- data/examples/person_mm.intout +114 -0
- data/examples/publication.intin.rb +44 -0
- data/examples/publication.intout +20 -0
- data/examples/reader.intin.rb +33 -0
- data/examples/reader.intout +19 -0
- data/examples/stringarray.rb +5 -0
- data/examples/stringarray.xml +10 -0
- data/examples/stringarray_usage.intin.rb +11 -0
- data/examples/stringarray_usage.intout +31 -0
- data/examples/time_augm.intout +19 -7
- data/examples/time_augm_loading.intin.rb +44 -0
- data/examples/time_augm_loading.intout +12 -0
- data/examples/time_node.intin.rb +79 -0
- data/examples/time_node.rb +3 -2
- data/examples/time_node_w_marshallers.intin.rb +48 -0
- data/examples/time_node_w_marshallers.intout +25 -0
- data/examples/time_node_w_marshallers.xml +9 -0
- data/examples/xpath_create_new.intout +132 -114
- data/examples/xpath_ensure_created.intout +86 -65
- data/examples/xpath_pathological.intout +16 -16
- data/examples/xpath_usage.intout +1 -1
- data/install.rb +1 -0
- data/lib/xml/mapping.rb +3 -1
- data/lib/xml/mapping/base.rb +442 -272
- data/lib/xml/mapping/core_classes_mapping.rb +32 -0
- data/lib/xml/mapping/standard_nodes.rb +176 -86
- data/lib/xml/mapping/version.rb +2 -2
- data/lib/xml/rexml_ext.rb +186 -0
- data/lib/xml/xxpath.rb +28 -265
- data/lib/xml/xxpath/steps.rb +345 -0
- data/lib/xml/xxpath_methods.rb +96 -0
- data/test/all_tests.rb +4 -1
- data/test/benchmark_fixtures.rb +14 -0
- data/test/{multiple_mappings.rb → bookmarks.rb} +0 -0
- data/test/company.rb +47 -0
- data/test/documents_folders.rb +11 -1
- data/test/examples_test.rb +29 -0
- data/test/fixtures/benchmark.xml +77 -0
- data/test/fixtures/company1.xml +9 -0
- data/test/fixtures/documents_folders.xml +0 -8
- data/test/fixtures/documents_folders2.xml +13 -19
- data/test/fixtures/triangle_m1.xml +17 -0
- data/test/fixtures/triangle_m2.xml +19 -0
- data/test/inheritance_test.rb +50 -0
- data/test/multiple_mappings_test.rb +155 -0
- data/test/rexml_xpath_benchmark.rb +29 -0
- data/test/triangle_mm.rb +57 -0
- data/test/xml_mapping_adv_test.rb +36 -1
- data/test/xml_mapping_test.rb +136 -7
- data/test/xpath_test.rb +154 -0
- data/test/xxpath_benchmark.rb +36 -0
- data/test/xxpath_benchmark.result1.txt +17 -0
- data/test/xxpath_methods_test.rb +61 -0
- 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.
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
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
|
-
|
23
|
-
|
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
|
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, @
|
71
|
-
#
|
72
|
-
#
|
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
|
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 @
|
95
|
-
@
|
96
|
-
value.fill_into_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 @
|
100
|
-
@
|
101
|
-
@options[:class].load_from_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 @
|
107
|
-
@
|
108
|
-
value.fill_into_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 @
|
116
|
-
@
|
117
|
-
XML::Mapping.load_object_from_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
|
144
|
-
super
|
145
|
-
|
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
|
-
@
|
166
|
+
@unmarshaller.call(default_when_xpath_err{@path.first(xml)})
|
149
167
|
end
|
150
168
|
def set_attr_value(xml, value) # :nodoc:
|
151
|
-
@
|
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
|
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
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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 << @
|
279
|
+
result << @unmarshaller.call(elt)
|
260
280
|
end
|
261
281
|
result
|
262
282
|
end
|
263
283
|
def set_attr_value(xml, value) # :nodoc:
|
264
|
-
|
265
|
-
|
266
|
-
@
|
267
|
-
|
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
|
300
|
-
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
def
|
307
|
-
super
|
308
|
-
if path3
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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 = @
|
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
|
-
|
333
|
-
|
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
|
-
@
|
355
|
+
@marshaller.call(elt,v)
|
336
356
|
@key_path.first(elt,:ensure_created=>true).text = k
|
337
|
-
|
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
|
|
data/lib/xml/mapping/version.rb
CHANGED
@@ -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
|