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
@@ -17,98 +17,119 @@ rootelt=d.root
17
17
  XML::XXPath.new("/bar/baz[@key='work']").first(rootelt,:ensure_created=>true)
18
18
  => <baz key='work'> ... </>
19
19
  d.write($stdout,2)
20
- <foo>
21
- <bar>
22
- <baz key='work'>Java</baz>
23
- <baz key='play'>Ruby</baz>
24
- </bar>
25
- </foo>
26
-
27
- ### no change (path existed before)
20
+
21
+ <foo>
22
+ <bar>
23
+ <baz key='work'>
24
+ Java
25
+ </baz>
26
+ <baz key='play'>
27
+ Ruby
28
+ </baz>
29
+ </bar>
30
+ </foo>### no change (path existed before)
28
31
 
29
32
 
30
33
  XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
31
34
  => <baz key='42'/>
32
35
  d.write($stdout,2)
33
- <foo>
34
- <bar>
35
- <baz key='work'>Java</baz>
36
- <baz key='play'>Ruby</baz>
36
+
37
+ <foo>
38
+ <bar>
39
+ <baz key='work'>
40
+ Java
41
+ </baz>
42
+ <baz key='play'>
43
+ Ruby
44
+ </baz>
37
45
  <baz key='42'/>
38
- </bar>
39
- </foo>
40
-
41
- ### path was added
46
+ </bar>
47
+ </foo>### path was added
42
48
 
43
49
  XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
44
50
  => <baz key='42'/>
45
51
  d.write($stdout,2)
46
- <foo>
47
- <bar>
48
- <baz key='work'>Java</baz>
49
- <baz key='play'>Ruby</baz>
52
+
53
+ <foo>
54
+ <bar>
55
+ <baz key='work'>
56
+ Java
57
+ </baz>
58
+ <baz key='play'>
59
+ Ruby
60
+ </baz>
50
61
  <baz key='42'/>
51
- </bar>
52
- </foo>
53
-
54
- ### no change this time
62
+ </bar>
63
+ </foo>### no change this time
55
64
 
56
65
  XML::XXPath.new("/bar/baz[@key2='hello']").first(rootelt,:ensure_created=>true)
57
- => <baz key2='hello' key='work'> ... </>
66
+ => <baz key='work' key2='hello'> ... </>
58
67
  d.write($stdout,2)
59
- <foo>
60
- <bar>
61
- <baz key2='hello' key='work'>Java</baz>
62
- <baz key='play'>Ruby</baz>
68
+
69
+ <foo>
70
+ <bar>
71
+ <baz key='work' key2='hello'>
72
+ Java
73
+ </baz>
74
+ <baz key='play'>
75
+ Ruby
76
+ </baz>
63
77
  <baz key='42'/>
64
- </bar>
65
- </foo>
66
-
67
- ### this fit in the 1st "baz" element since
78
+ </bar>
79
+ </foo>### this fit in the 1st "baz" element since
68
80
  ### there was no "key2" attribute there before.
69
81
 
70
82
  XML::XXPath.new("/bar/baz[2]").first(rootelt,:ensure_created=>true)
71
83
  => <baz key='play'> ... </>
72
84
  d.write($stdout,2)
73
- <foo>
74
- <bar>
75
- <baz key2='hello' key='work'>Java</baz>
76
- <baz key='play'>Ruby</baz>
85
+
86
+ <foo>
87
+ <bar>
88
+ <baz key='work' key2='hello'>
89
+ Java
90
+ </baz>
91
+ <baz key='play'>
92
+ Ruby
93
+ </baz>
77
94
  <baz key='42'/>
78
- </bar>
79
- </foo>
80
-
81
- ### no change
95
+ </bar>
96
+ </foo>### no change
82
97
 
83
98
  XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
84
- => #<XML::XXPath::Accessors::Attribute:0x3223b0 @parent=<baz haha='[unset]'/>, @name="haha">
99
+ => #<XML::XXPath::Accessors::Attribute:0x7f4876522658 @name="haha", @parent=<baz haha='[unset]'/>>
85
100
  d.write($stdout,2)
86
- <foo>
87
- <bar>
88
- <baz key2='hello' key='work'>Java</baz>
89
- <baz key='play'>Ruby</baz>
101
+
102
+ <foo>
103
+ <bar>
104
+ <baz key='work' key2='hello'>
105
+ Java
106
+ </baz>
107
+ <baz key='play'>
108
+ Ruby
109
+ </baz>
90
110
  <baz key='42'/>
91
- <baz/>
92
- <baz/>
93
- <baz haha='[unset]'/>
94
- </bar>
95
- </foo>
96
-
97
- ### for there to be a 6th "baz" element, there must be 1st..5th "baz" elements
111
+ <baz/>
112
+ <baz/>
113
+ <baz haha='[unset]'/>
114
+ </bar>
115
+ </foo>### for there to be a 6th "baz" element, there must be 1st..5th "baz" elements
98
116
 
99
117
  XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
100
- => #<XML::XXPath::Accessors::Attribute:0x31f830 @parent=<baz haha='[unset]'/>, @name="haha">
118
+ => #<XML::XXPath::Accessors::Attribute:0x7f4876516bc8 @name="haha", @parent=<baz haha='[unset]'/>>
101
119
  d.write($stdout,2)
102
- <foo>
103
- <bar>
104
- <baz key2='hello' key='work'>Java</baz>
105
- <baz key='play'>Ruby</baz>
120
+
121
+ <foo>
122
+ <bar>
123
+ <baz key='work' key2='hello'>
124
+ Java
125
+ </baz>
126
+ <baz key='play'>
127
+ Ruby
128
+ </baz>
106
129
  <baz key='42'/>
107
- <baz/>
108
- <baz/>
109
- <baz haha='[unset]'/>
110
- </bar>
111
- </foo>
112
-
113
- ### no change this time
130
+ <baz/>
131
+ <baz/>
132
+ <baz haha='[unset]'/>
133
+ </bar>
134
+ </foo>### no change this time
114
135
 
@@ -24,14 +24,13 @@ newelt = XML::XXPath.new("bar/*").first(rootelt, :ensure_created=>true)
24
24
  => </>
25
25
 
26
26
  d.write($stdout,2)
27
- <foo>
28
- <bar>
29
- </>
30
- </bar>
31
- <bar/>
32
- </foo>
33
-
34
-
27
+
28
+ <foo>
29
+ <bar>
30
+ </>
31
+ </bar>
32
+ <bar/>
33
+ </foo>
35
34
  ### a new "unspecified" element was created
36
35
  newelt.unspecified?
37
36
  => true
@@ -43,14 +42,15 @@ newelt.unspecified?
43
42
  => false
44
43
 
45
44
  d.write($stdout,2)
46
- <foo>
47
- <bar>
48
- <new-one>hello!</new-one>
49
- </bar>
50
- <bar/>
51
- </foo>
52
-
53
-
45
+
46
+ <foo>
47
+ <bar>
48
+ <new-one>
49
+ hello!
50
+ </new-one>
51
+ </bar>
52
+ <bar/>
53
+ </foo>
54
54
  ### you could also set unspecified to false explicitly, as in:
55
55
  newelt.unspecified=true
56
56
 
@@ -44,7 +44,7 @@ path2.all(d)
44
44
  ## "first" raises XML::XXPathError in such cases...
45
45
  path2.first(d)
46
46
  XML::XXPathError: path not found: /foo/bar[2]/baz[4]
47
- from ../lib/xml/../xml/xxpath.rb:130:in `first'
47
+ from ../lib/xml/xxpath.rb:75:in `first'
48
48
 
49
49
  ##...unless we allow nil returns
50
50
  path2.first(d,:allow_nil=>true)
data/install.rb CHANGED
@@ -26,6 +26,7 @@ end
26
26
  files = %w-
27
27
  xml/mapping.rb
28
28
  xml/xxpath.rb
29
+ xml/xxpath_methods.rb
29
30
  xml/mapping/base.rb
30
31
  xml/mapping/standard_nodes.rb
31
32
  xml/mapping/version.rb
@@ -1,14 +1,16 @@
1
1
  # xml-mapping -- bidirectional Ruby-XML mapper
2
- # Copyright (C) 2004,2005 Olaf Klischat
2
+ # Copyright (C) 2004-2006 Olaf Klischat
3
3
 
4
4
  $:.unshift(File.dirname(__FILE__)+"/..")
5
5
 
6
6
  require 'xml/mapping/base'
7
7
  require 'xml/mapping/standard_nodes'
8
8
 
9
+ XML::Mapping.add_node_class XML::Mapping::Node
9
10
  XML::Mapping.add_node_class XML::Mapping::TextNode
10
11
  XML::Mapping.add_node_class XML::Mapping::NumericNode
11
12
  XML::Mapping.add_node_class XML::Mapping::ObjectNode
12
13
  XML::Mapping.add_node_class XML::Mapping::BooleanNode
13
14
  XML::Mapping.add_node_class XML::Mapping::ArrayNode
14
15
  XML::Mapping.add_node_class XML::Mapping::HashNode
16
+ XML::Mapping.add_node_class XML::Mapping::ChoiceNode
@@ -1,5 +1,5 @@
1
1
  # xml-mapping -- bidirectional Ruby-XML mapper
2
- # Copyright (C) 2004,2005 Olaf Klischat
2
+ # Copyright (C) 2004-2006 Olaf Klischat
3
3
 
4
4
  require 'rexml/document'
5
5
  require "xml/xxpath"
@@ -60,6 +60,14 @@ module XML
60
60
  # Including XML::Mapping also adds all methods of
61
61
  # XML::Mapping::ClassMethods to your class (as class methods).
62
62
  #
63
+ # It is recommended that if your class does not have required
64
+ # +initialize+ method arguments. The XML loader attempts to create a
65
+ # new object using the +new+ method. If this fails because the
66
+ # initializer expects an argument, then the loader calls +allocate+
67
+ # instead. +allocate+ bypasses the initializer. If your class must
68
+ # have initializer arguments, then you should verify that bypassing
69
+ # the initializer is acceptable.
70
+ #
63
71
  # As you may have noticed from the example, the node factory methods
64
72
  # generally use XPath expressions to specify locations in the mapped
65
73
  # XML document. To make this work, XML::Mapping relies on
@@ -70,45 +78,96 @@ module XML
70
78
  # elements/documents in memory.
71
79
  module Mapping
72
80
 
73
- # can't really use class variables for these because they must be
81
+ # defined mapping classes for a given root elt name and mapping
82
+ # name (nested map from root element name to mapping name to array
83
+ # of classes)
84
+ #
85
+ # can't really use a class variable for this because it must be
74
86
  # shared by all class methods mixed into classes by including
75
87
  # Mapping. See
76
88
  # http://user.cs.tu-berlin.de/~klischat/mydocs/ruby/mixin_class_methods_global_state.txt.html
77
89
  # for a more detailed discussion.
78
- Classes_w_default_rootelt_names = {} #:nodoc:
79
- Classes_w_nondefault_rootelt_names = {} #:nodoc:
90
+ Classes_by_rootelt_names = {} #:nodoc:
91
+ class << Classes_by_rootelt_names
92
+ def create_classes_for rootelt_name, mapping
93
+ (self[rootelt_name] ||= {})[mapping] ||= []
94
+ end
95
+ def classes_for rootelt_name, mapping
96
+ (self[rootelt_name] || {})[mapping] || []
97
+ end
98
+ def remove_class rootelt_name, mapping, clazz
99
+ classes_for(rootelt_name, mapping).delete clazz
100
+ end
101
+ def ensure_exists rootelt_name, mapping, clazz
102
+ clazzes = create_classes_for(rootelt_name, mapping)
103
+ clazzes << clazz unless clazzes.include? clazz
104
+ end
105
+ end
106
+
80
107
 
81
108
  def self.append_features(base) #:nodoc:
82
109
  super
83
110
  base.extend(ClassMethods)
84
- Classes_w_default_rootelt_names[base.default_root_element_name] = base
111
+ Classes_by_rootelt_names.create_classes_for(base.default_root_element_name, :_default) << base
112
+ base.initializing_xml_mapping
85
113
  end
86
114
 
87
-
88
- # Finds the mapping class corresponding to the given XML root
89
- # element name. This is the inverse operation to
115
+ # Finds a mapping class corresponding to the given XML root
116
+ # element name and mapping name. There may be more than one such class --
117
+ # in that case, the most recently defined one is returned
118
+ #
119
+ # This is the inverse operation to
90
120
  # <class>.root_element_name (see
91
121
  # XML::Mapping::ClassMethods.root_element_name).
92
- def self.class_for_root_elt_name(name)
122
+ def self.class_for_root_elt_name(name, options={:mapping=>:_default})
93
123
  # TODO: implement Hash read-only instead of this
94
124
  # interface
95
- Classes_w_nondefault_rootelt_names[name] ||
96
- Classes_w_default_rootelt_names[name]
125
+ Classes_by_rootelt_names.classes_for(name, options[:mapping])[-1]
97
126
  end
98
127
 
128
+ # Finds a mapping class and mapping name corresponding to the
129
+ # given XML root element name. There may be more than one
130
+ # (class,mapping) tuple for a given root element name -- in that
131
+ # case, one of them is selected arbitrarily.
132
+ #
133
+ # returns [class,mapping]
134
+ def self.class_and_mapping_for_root_elt_name(name)
135
+ (Classes_by_rootelt_names[name] || {}).each_pair{|mapping,classes| return [classes[0],mapping] }
136
+ nil
137
+ end
99
138
 
100
- def initialize_xml_mapping #:nodoc:
101
- self.class.all_xml_mapping_nodes.each do |node|
102
- node.obj_initializing(self)
139
+ # Xml-mapping-specific initializer.
140
+ #
141
+ # This will be called when a new instance is being initialized
142
+ # from an XML source, as well as after calling _class_._new_(args)
143
+ # (for the latter case to work, you'll have to make sure you call
144
+ # the inherited _initialize_ method)
145
+ #
146
+ # The :mapping keyword argument gives the mapping the instance is
147
+ # being initialized with. This is non-nil only when the instance
148
+ # is being initialized from an XML source (:mapping will contain
149
+ # the :mapping argument passed (explicitly or implicitly) to the
150
+ # load_from_... method).
151
+ #
152
+ # When the instance is being initialized because _class_._new_ was
153
+ # called, the :mapping argument is set to nil to show that the
154
+ # object is being initialized with respect to no specific mapping.
155
+ #
156
+ # The default implementation of this method calls obj_initializing
157
+ # on all nodes. You may overwrite this method to do your own
158
+ # initialization stuff; make sure to call +super+ in that case.
159
+ def initialize_xml_mapping(options={:mapping=>nil})
160
+ self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
161
+ node.obj_initializing(self,options[:mapping])
103
162
  end
104
163
  end
105
164
 
106
- # private :initialize_xml_mapping
107
-
108
- # Initializer. Calls obj_initializing(self) on all nodes. You
109
- # should call this using +super+ in your mapping classes to
110
- # inherit this behaviour.
165
+ # Initializer. Called (by Class#new) after _self_ was created
166
+ # using _new_.
167
+ #
168
+ # XML::Mapping's implementation calls #initialize_xml_mapping.
111
169
  def initialize(*args)
170
+ super(*args)
112
171
  initialize_xml_mapping
113
172
  end
114
173
 
@@ -119,19 +178,21 @@ module XML
119
178
  # object's class are processed (i.e. have their
120
179
  # #xml_to_obj method called) in the order of their definition
121
180
  # inside the class, then #post_load is called.
122
- def fill_from_xml(xml)
123
- pre_load(xml)
124
- self.class.all_xml_mapping_nodes.each do |node|
181
+ def fill_from_xml(xml, options={:mapping=>:_default})
182
+ raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") \
183
+ unless self.class.xml_mapping_nodes_hash.has_key?(options[:mapping])
184
+ pre_load xml, :mapping=>options[:mapping]
185
+ self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
125
186
  node.xml_to_obj self, xml
126
187
  end
127
- post_load
188
+ post_load :mapping=>options[:mapping]
128
189
  end
129
190
 
130
191
  # This method is called immediately before _self_ is filled from
131
192
  # an xml source. _xml_ is the source REXML::Element.
132
193
  #
133
194
  # The default implementation of this method is empty.
134
- def pre_load(xml)
195
+ def pre_load(xml, options={:mapping=>:_default})
135
196
  end
136
197
 
137
198
 
@@ -143,7 +204,7 @@ module XML
143
204
  # exception to abandon the whole loading process.
144
205
  #
145
206
  # The default implementation of this method is empty.
146
- def post_load
207
+ def post_load(options={:mapping=>:_default})
147
208
  end
148
209
 
149
210
 
@@ -152,8 +213,8 @@ module XML
152
213
  # (i.e. have their
153
214
  # #obj_to_xml method called) in the order of their definition
154
215
  # inside the class.
155
- def fill_into_xml(xml)
156
- self.class.all_xml_mapping_nodes.each do |node|
216
+ def fill_into_xml(xml, options={:mapping=>:_default})
217
+ self.class.all_xml_mapping_nodes(:mapping=>options[:mapping]).each do |node|
157
218
  node.obj_to_xml self,xml
158
219
  end
159
220
  end
@@ -163,10 +224,10 @@ module XML
163
224
  #
164
225
  # This method calls #pre_save, then #fill_into_xml, then
165
226
  # #post_save.
166
- def save_to_xml
167
- xml = pre_save
168
- fill_into_xml(xml)
169
- post_save(xml)
227
+ def save_to_xml(options={:mapping=>:_default})
228
+ xml = pre_save :mapping=>options[:mapping]
229
+ fill_into_xml xml, :mapping=>options[:mapping]
230
+ post_save xml, :mapping=>options[:mapping]
170
231
  xml
171
232
  end
172
233
 
@@ -181,47 +242,271 @@ module XML
181
242
  # class name, with capital letters converted to lowercase and
182
243
  # preceded by a dash, e.g. "MySampleClass" becomes
183
244
  # "my-sample-class".
184
- def pre_save
185
- REXML::Element.new(self.class.root_element_name)
245
+ def pre_save(options={:mapping=>:_default})
246
+ REXML::Element.new(self.class.root_element_name(:mapping=>options[:mapping]))
186
247
  end
187
248
 
188
249
  # This method is called immediately after _self_'s state has been
189
250
  # filled into an XML element.
190
251
  #
191
252
  # The default implementation does nothing.
192
- def post_save(xml)
253
+ def post_save(xml, options={:mapping=>:_default})
193
254
  end
194
255
 
195
256
 
196
257
  # Save _self_'s state as XML into the file named _filename_.
197
258
  # The XML is obtained by calling #save_to_xml.
198
- def save_to_file(filename)
199
- xml = save_to_xml
259
+ def save_to_file(filename, options={:mapping=>:_default})
260
+ xml = save_to_xml :mapping=>options[:mapping]
200
261
  File.open(filename,"w") do |f|
201
- xml.write(f,2)
262
+ REXML::Formatters::Transitive.new(2,false).write(xml,f)
263
+ end
264
+ end
265
+
266
+
267
+ # The instance methods of this module are automatically added as
268
+ # class methods to a class that includes XML::Mapping.
269
+ module ClassMethods
270
+ #ClassMethods = Module.new do # this is the alternative -- but see above for peculiarities
271
+
272
+ # all nodes of this class, in the order of their definition,
273
+ # hashed by mapping (hash mapping => array of nodes)
274
+ def xml_mapping_nodes_hash #:nodoc:
275
+ @xml_mapping_nodes ||= {}
276
+ end
277
+
278
+ # called on a class when it is being made a mapping class
279
+ # (i.e. immediately after XML::Mapping was included in it)
280
+ def initializing_xml_mapping #:nodoc:
281
+ @default_mapping = :_default
282
+ end
283
+
284
+ # Make _mapping_ the mapping to be used by default in future
285
+ # node declarations in this class. The default can be
286
+ # overwritten on a per-node basis by passing a :mapping option
287
+ # parameter to the node factory method
288
+ #
289
+ # The initial default mapping in a mapping class is :_default
290
+ def use_mapping mapping
291
+ @default_mapping = mapping
292
+ xml_mapping_nodes_hash[mapping] ||= [] # create empty mapping node list if
293
+ # there wasn't one before so future calls
294
+ # to load/save_xml etc. w/ this mapping don't raise
295
+ end
296
+
297
+ # return the current default mapping (:_default initially, or
298
+ # the value set with the latest call to use_mapping)
299
+ def default_mapping
300
+ @default_mapping
301
+ end
302
+
303
+ # Add getter and setter methods for a new attribute named _name_
304
+ # to this class. This is a convenience method intended to be
305
+ # called from Node class initializers.
306
+ def add_accessor(name)
307
+ name = name.id2name if name.kind_of? Symbol
308
+ unless self.instance_methods.include?(name)
309
+ self.module_eval <<-EOS
310
+ attr_reader :#{name}
311
+ EOS
312
+ end
313
+ unless self.instance_methods.include?("#{name}=")
314
+ self.module_eval <<-EOS
315
+ attr_writer :#{name}
316
+ EOS
317
+ end
318
+ end
319
+
320
+ # Create a new instance of this class from the XML contained in
321
+ # the file named _filename_. Calls load_from_xml internally.
322
+ def load_from_file(filename, options={:mapping=>:_default})
323
+ xml = REXML::Document.new(File.new(filename))
324
+ load_from_xml xml.root, :mapping=>options[:mapping]
325
+ end
326
+
327
+ # Create a new instance of this class from the XML contained in
328
+ # _xml_ (a REXML::Element).
329
+ #
330
+ # Allocates a new object, then calls fill_from_xml(_xml_) on
331
+ # it.
332
+ def load_from_xml(xml, options={:mapping=>:_default})
333
+ raise(MappingError, "undefined mapping: #{options[:mapping].inspect}") \
334
+ unless xml_mapping_nodes_hash.has_key?(options[:mapping])
335
+ # create the new object. It is recommended that the class
336
+ # have a no-argument initializer, so try new first. If that
337
+ # doesn't work, try allocate, which bypasses the initializer.
338
+ begin
339
+ obj = self.new
340
+ rescue ArgumentError # TODO: this may hide real errors.
341
+ # how to statically check whether
342
+ # self self.new accepts an empty
343
+ # argument list?
344
+ obj = self.allocate
345
+ end
346
+ obj.initialize_xml_mapping :mapping=>options[:mapping]
347
+ obj.fill_from_xml xml, :mapping=>options[:mapping]
348
+ obj
349
+ end
350
+
351
+
352
+ # array of all nodes defined in this class, in the order of
353
+ # their definition. Option :create specifies whether or not an
354
+ # empty array should be created and returned if there was none
355
+ # before (if not, an exception is raised). :mapping specifies
356
+ # the mapping the returned nodes must have been defined in; nil
357
+ # means return all nodes regardless of their mapping
358
+ def xml_mapping_nodes(options={:mapping=>nil,:create=>true})
359
+ unless options[:mapping]
360
+ return xml_mapping_nodes_hash.values.inject([]){|a1,a2|a1+a2}
361
+ end
362
+ options[:create] = true if options[:create].nil?
363
+ if options[:create]
364
+ xml_mapping_nodes_hash[options[:mapping]] ||= []
365
+ else
366
+ xml_mapping_nodes_hash[options[:mapping]] ||
367
+ raise(MappingError, "undefined mapping: #{options[:mapping].inspect}")
368
+ end
369
+ end
370
+
371
+
372
+ # enumeration of all nodes in effect when
373
+ # marshalling/unmarshalling this class, that is, nodes defined
374
+ # for this class as well as for its superclasses. The nodes are
375
+ # returned in the order of their definition, starting with the
376
+ # topmost superclass that has nodes defined. keyword arguments
377
+ # are the same as for #xml_mapping_nodes.
378
+ def all_xml_mapping_nodes(options={:mapping=>nil,:create=>true})
379
+ # TODO: we could return a dynamic Enumerable here, or cache
380
+ # the array...
381
+ result = []
382
+ if superclass and superclass.respond_to?(:all_xml_mapping_nodes)
383
+ result += superclass.all_xml_mapping_nodes options
384
+ end
385
+ result += xml_mapping_nodes options
202
386
  end
387
+
388
+
389
+ # The "root element name" of this class (combined getter/setter
390
+ # method).
391
+ #
392
+ # The root element name is the name of the root element of the
393
+ # XML tree returned by <this class>.#save_to_xml (or, more
394
+ # specifically, <this class>.#pre_save). By default, this method
395
+ # returns the #default_root_element_name; you may call this
396
+ # method with an argument to set the root element name to
397
+ # something other than the default. The option argument :mapping
398
+ # specifies the mapping the root element is/will be defined in,
399
+ # it defaults to the current default mapping (:_default
400
+ # initially, or the value set with the latest call to
401
+ # use_mapping)
402
+ def root_element_name(name=nil, options={:mapping=>@default_mapping})
403
+ if Hash===name # ugly...
404
+ options=name; name=nil
405
+ end
406
+ @root_element_names ||= {}
407
+ if name
408
+ Classes_by_rootelt_names.remove_class root_element_name, options[:mapping], self
409
+ @root_element_names[options[:mapping]] = name
410
+ Classes_by_rootelt_names.create_classes_for(name, options[:mapping]) << self
411
+ end
412
+ @root_element_names[options[:mapping]] || default_root_element_name
413
+ end
414
+
415
+ # The default root element name for this class. Equals the class
416
+ # name, with all parent module names stripped, and with capital
417
+ # letters converted to lowercase and preceded by a dash;
418
+ # e.g. "Foo::Bar::MySampleClass" becomes "my-sample-class".
419
+ def default_root_element_name
420
+ self.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"-"+$2.downcase}
421
+ end
422
+
203
423
  end
204
424
 
205
425
 
426
+
427
+ # "polymorphic" load function. Turns the XML tree _xml_ into an
428
+ # object, which is returned. The class of the object and the
429
+ # mapping to be used for unmarshalling are automatically
430
+ # determined from the root element name of _xml_ using
431
+ # XML::Mapping.class_for_root_elt_name. If :mapping is non-nil,
432
+ # only root element names defined in that mapping will be
433
+ # considered (default is to consider all classes)
434
+ def self.load_object_from_xml(xml,options={:mapping=>nil})
435
+ if mapping = options[:mapping]
436
+ c = class_for_root_elt_name xml.name, :mapping=>mapping
437
+ else
438
+ c,mapping = class_and_mapping_for_root_elt_name(xml.name)
439
+ end
440
+ unless c
441
+ raise MappingError, "no mapping class for root element name #{xml.name}, mapping #{mapping.inspect}"
442
+ end
443
+ c.load_from_xml xml, :mapping=>mapping
444
+ end
445
+
446
+ # Like load_object_from_xml, but loads from the XML file named by
447
+ # _filename_.
448
+ def self.load_object_from_file(filename,options={:mapping=>nil})
449
+ xml = REXML::Document.new(File.new(filename))
450
+ load_object_from_xml xml.root, options
451
+ end
452
+
453
+
454
+ # Registers the new node class _c_ (must be a descendant of Node)
455
+ # with the xml-mapping framework.
456
+ #
457
+ # A new "factory method" will automatically be added to
458
+ # ClassMethods (and therefore to all classes that include
459
+ # XML::Mapping from now on); so you can call it from the body of
460
+ # your mapping class definition in order to create nodes of type
461
+ # _c_. The name of the factory method is derived by "underscoring"
462
+ # the (unqualified) name of _c_;
463
+ # e.g. _c_==<tt>Foo::Bar::MyNiftyNode</tt> will result in the
464
+ # creation of a factory method named +my_nifty_node+. The
465
+ # generated factory method creates and returns a new instance of
466
+ # _c_. The list of argument to _c_.new consists of _self_
467
+ # (i.e. the mapping class the factory method was called from)
468
+ # followed by the arguments passed to the factory method. You
469
+ # should always use the factory methods to create instances of
470
+ # node classes; you should never need to call a node class's
471
+ # constructor directly.
472
+ #
473
+ # For a demonstration, see the calls to +text_node+, +array_node+
474
+ # etc. in the examples along with the corresponding node classes
475
+ # TextNode, ArrayNode etc. (these predefined node classes are in
476
+ # no way "special"; they're added using add_node_class in
477
+ # mapping.rb just like any custom node classes would be).
478
+ def self.add_node_class(c)
479
+ meth_name = c.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"_"+$2.downcase}
480
+ ClassMethods.module_eval <<-EOS
481
+ def #{meth_name}(*args)
482
+ #{c.name}.new(self,*args)
483
+ end
484
+ EOS
485
+ end
486
+
487
+
488
+ ###### core node classes
489
+
206
490
  # Abstract base class for all node types. As mentioned in the
207
491
  # documentation for XML::Mapping, node types must be registered
208
- # using add_node_class, and a corresponding "node factory method"
209
- # (e.g. "text_node") will then be added as a class method to your
210
- # mapping classes. The node factory method is called from the body
211
- # of the mapping classes as demonstrated in the examples. It
212
- # creates an instance of its corresponding node type (the list of
213
- # parameters to the node factory method, preceded by the owning
214
- # mapping class, will be passed to the constructor of the node
215
- # type) and adds it to its owning mapping class, so there is one
216
- # node object per node definition per mapping class. That node
217
- # object will handle all XML marshalling/unmarshalling for this
218
- # node, for all instances of the mapping class. For this purpose,
219
- # the marshalling and unmarshalling methods of a mapping class
220
- # instance (fill_into_xml and fill_from_xml, respectively)
221
- # will call obj_to_xml resp. xml_to_obj on all nodes of the
222
- # mapping class, in the order of their definition, passing the
223
- # REXML element the data is to be marshalled to/unmarshalled from
224
- # as well as the object the data is to be read from/filled into.
492
+ # using XML::Mapping.add_node_class, and a corresponding "node
493
+ # factory method" (e.g. "text_node") will then be added as a class
494
+ # method to your mapping classes. The node factory method is
495
+ # called from the body of the mapping classes as demonstrated in
496
+ # the examples. It creates an instance of its corresponding node
497
+ # type (the list of parameters to the node factory method,
498
+ # preceded by the owning mapping class, will be passed to the
499
+ # constructor of the node type) and adds it to its owning mapping
500
+ # class, so there is one node object per node definition per
501
+ # mapping class. That node object will handle all XML
502
+ # marshalling/unmarshalling for this node, for all instances of
503
+ # the mapping class. For this purpose, the marshalling and
504
+ # unmarshalling methods of a mapping class instance (fill_into_xml
505
+ # and fill_from_xml, respectively) will call obj_to_xml
506
+ # resp. xml_to_obj on all nodes of the mapping class, in the order
507
+ # of their definition, passing the REXML element the data is to be
508
+ # marshalled to/unmarshalled from as well as the object the data
509
+ # is to be read from/filled into.
225
510
  #
226
511
  # Node types that map some XML data to a single attribute of their
227
512
  # mapping class (that should be most of them) shouldn't be
@@ -230,14 +515,55 @@ module XML
230
515
  class Node
231
516
  # Intializer, to be called from descendant classes. _owner_ is
232
517
  # the mapping class this node is being defined in. It'll be
233
- # stored in _@owner_.
234
- def initialize(owner)
518
+ # stored in _@owner_. @options will be set to a (possibly empty)
519
+ # hash containing the option arguments passed to
520
+ # _initialize_. Options :mapping, :reader and :writer will be
521
+ # handled, subclasses may handle additional options. See the
522
+ # section on defining nodes in the README for details.
523
+ def initialize(owner,*args)
235
524
  @owner = owner
236
- owner.xml_mapping_nodes << self
525
+ if Hash===args[-1]
526
+ @options = args[-1]
527
+ args = args[0..-2]
528
+ else
529
+ @options={}
530
+ end
531
+ @mapping = @options[:mapping] || owner.default_mapping
532
+ owner.xml_mapping_nodes(:mapping=>@mapping) << self
533
+ XML::Mapping::Classes_by_rootelt_names.ensure_exists owner.root_element_name, @mapping, owner
534
+ if @options[:reader]
535
+ # override xml_to_obj in this instance with invocation of
536
+ # @options[:reader]
537
+ class << self
538
+ alias_method :default_xml_to_obj, :xml_to_obj
539
+ def xml_to_obj(obj,xml)
540
+ begin
541
+ @options[:reader].call(obj,xml)
542
+ rescue ArgumentError
543
+ @options[:reader].call(obj,xml,self.method(:default_xml_to_obj))
544
+ end
545
+ end
546
+ end
547
+ end
548
+ if @options[:writer]
549
+ # override obj_to_xml in this instance with invocation of
550
+ # @options[:writer]
551
+ class << self
552
+ alias_method :default_obj_to_xml, :obj_to_xml
553
+ def obj_to_xml(obj,xml)
554
+ begin
555
+ @options[:writer].call(obj,xml)
556
+ rescue ArgumentError
557
+ @options[:writer].call(obj,xml,self.method(:default_obj_to_xml))
558
+ end
559
+ end
560
+ end
561
+ end
562
+ args
237
563
  end
238
564
  # This is called by the XML unmarshalling machinery when the
239
565
  # state of an instance of this node's @owner is to be read from
240
- # an XML node. _obj_ is the instance, _xml_ is the element (a
566
+ # an XML tree. _obj_ is the instance, _xml_ is the tree (a
241
567
  # REXML::Element). The node must read "its" data from _xml_
242
568
  # (using XML::XXPath or any other means) and store it to the
243
569
  # corresponding parts (attributes etc.) of _obj_'s state.
@@ -246,32 +572,44 @@ module XML
246
572
  end
247
573
  # This is called by the XML unmarshalling machinery when the
248
574
  # state of an instance of this node's @owner is to be stored
249
- # into an XML node. _obj_ is the instance, _xml_ is the element
250
- # (a REXML::Element). The node must extract "its" data from
251
- # _obj_ and store it to the corresponding parts (sub-elements,
575
+ # into an XML tree. _obj_ is the instance, _xml_ is the tree (a
576
+ # REXML::Element). The node must extract "its" data from _obj_
577
+ # and store it to the corresponding parts (sub-elements,
252
578
  # attributes etc.) of _xml_ (using XML::XXPath or any other
253
579
  # means).
254
580
  def obj_to_xml(obj,xml)
255
581
  raise "abstract method called"
256
582
  end
257
- # Called when a new instance is being initialized. _obj_ is the
258
- # instance. You may set up initial values for the attributes
259
- # this node is responsible for here. Default implementation is
260
- # empty.
261
- def obj_initializing(obj)
583
+ # Called when a new instance of the mapping class this node
584
+ # belongs to is being initialized. _obj_ is the
585
+ # instance. _mapping_ is the mapping the initialization is
586
+ # happening with, if any: If the instance is being initialized
587
+ # as part of e.g. <tt>Class.load_from_file(name,
588
+ # :mapping=>:some_mapping</tt> or any other call that specifies
589
+ # a mapping, that mapping will be passed to this method. If the
590
+ # instance is being initialized normally with
591
+ # <tt>Class.new</tt>, _mapping_ is nil here.
592
+ #
593
+ # You may set up initial values for the attributes this node is
594
+ # responsible for here. Default implementation is empty.
595
+ def obj_initializing(obj,mapping)
596
+ end
597
+ # tell whether this node's data is present in _obj_ (when this
598
+ # method is called, _obj_ will be an instance of the mapping
599
+ # class this node was defined in). This method is currently used
600
+ # only by ChoiceNode when writing data back to XML. See
601
+ # ChoiceNode#obj_to_xml.
602
+ def is_present_in? obj
603
+ true
262
604
  end
263
605
  end
264
606
 
265
607
 
266
608
  # Base class for node types that map some XML data to a single
267
- # attribute of their mapping class. This class also introduces a
268
- # general "options" hash parameter which may be used to influence
269
- # the creation of nodes in numerous ways, e.g. by providing
270
- # default attribute values when there is no source data in the
271
- # mapped XML.
609
+ # attribute of their mapping class.
272
610
  #
273
- # All node types that come with xml-mapping inherit from
274
- # SingleAttributeNode.
611
+ # All node types that come with xml-mapping except one
612
+ # (ChoiceNode) inherit from SingleAttributeNode.
275
613
  class SingleAttributeNode < Node
276
614
  # Initializer. _owner_ is the owning mapping class (gets passed
277
615
  # to the superclass initializer and therefore put into
@@ -282,10 +620,8 @@ module XML
282
620
  # attribute of name attrname) is added to the mapping class
283
621
  # (using attr_accessor).
284
622
  #
285
- # If the last argument is a hash, it is assumed to be the
286
- # abovementioned "options hash", and is stored into
287
- # @options. Two entries -- :optional and :default_value -- in
288
- # the options hash are already processed in SingleAttributeNode:
623
+ # In the initializer, two option arguments -- :optional and
624
+ # :default_value -- are processed in SingleAttributeNode:
289
625
  #
290
626
  # Supplying :default_value=>_obj_ makes _obj_ the _default
291
627
  # value_ for this attribute. When unmarshalling (loading) an
@@ -296,43 +632,24 @@ module XML
296
632
  #
297
633
  # Providing just :optional=>true is equivalent to providing
298
634
  # :default_value=>nil.
299
- #
300
- # The remaining arguments are passed to initialize_impl, which
301
- # is the initializer subclasses should overwrite instead of
302
- # initialize.
303
- #
304
- # For example (TextNode is a subclass of SingleAttributeNote):
305
- #
306
- # class Address
307
- # include XML::Mapping
308
- # text_node :city, "city", :optional=>true, :default_value=>"Berlin"
309
- # end
310
- #
311
- # Here +Address+ is the _owner_, <tt>:city</tt> is the
312
- # _attrname_,
313
- # <tt>{:optional=>true,:default_value=>"Berlin"}</tt> is the
314
- # @options, and ["city"] is the argument list that'll be passed
315
- # to TextNode.initialize_impl. "city" is of course the XPath
316
- # expression locating the XML sub-element this text node refers
317
- # to; TextNode.initialize_impl stores it into @path.
318
- def initialize(owner,attrname,*args)
319
- super(owner)
320
- @attrname = attrname
321
- owner.add_accessor attrname
322
- if Hash===args[-1]
323
- @options = args[-1]
324
- args = args[0..-2]
325
- else
326
- @options={}
327
- end
635
+ def initialize(*args)
636
+ @attrname,*args = super(*args)
637
+ @owner.add_accessor @attrname
328
638
  if @options[:optional] and not(@options.has_key?(:default_value))
329
639
  @options[:default_value] = nil
330
640
  end
331
641
  initialize_impl(*args)
642
+ args
332
643
  end
333
- # Initializer to be implemented by subclasses.
644
+ # this method was retained for compatibility with xml-mapping 0.8.
645
+ #
646
+ # It used to be the initializer to be implemented by subclasses. The
647
+ # arguments (args) are those still unprocessed by
648
+ # SingleAttributeNode's initializer.
649
+ #
650
+ # In xml-mapping 0.9 and up, you should just override initialize() and
651
+ # call super.initialize. The returned array is the same args array.
334
652
  def initialize_impl(*args)
335
- raise "abstract method called"
336
653
  end
337
654
 
338
655
  # Exception that may be used by implementations of
@@ -356,15 +673,14 @@ module XML
356
673
  obj.send :"#{@attrname}=", @options[:default_value]
357
674
  end
358
675
  end
676
+ true
359
677
  end
360
678
 
361
- # (to be overridden by subclasses) Extract and return the
362
- # attribute's value from _xml_. In the example above, TextNode's
363
- # implementation would return the current value of the
364
- # sub-element named by @path (i.e., "city"). If the
365
- # implementation decides that the attribute value is "unset" in
366
- # _xml_, it should raise NoAttrValueSet in order to initiate
367
- # proper handling of possibly supplied :optional and
679
+ # (to be overridden by subclasses) Extract and return the value
680
+ # of the attribute this node is responsible for (@attrname) from
681
+ # _xml_. If the implementation decides that the attribute value
682
+ # is "unset" in _xml_, it should raise NoAttrValueSet in order
683
+ # to initiate proper handling of possibly supplied :optional and
368
684
  # :default_value options (you may use #default_when_xpath_err
369
685
  # for this purpose).
370
686
  def extract_attr_value(xml)
@@ -382,14 +698,17 @@ module XML
382
698
  end
383
699
  set_attr_value(xml, value)
384
700
  end
701
+ true
385
702
  end
386
- # (to be overridden by subclasses) Write _value_ into the
387
- # correct sub-nodes of _xml_.
703
+ # (to be overridden by subclasses) Write _value_, which is the
704
+ # current value of the attribute this node is responsible for
705
+ # (@attrname), into (the correct sub-nodes, attributes,
706
+ # whatever) of _xml_.
388
707
  def set_attr_value(xml, value)
389
708
  raise "abstract method called"
390
709
  end
391
- def obj_initializing(obj) # :nodoc:
392
- if @options.has_key? :default_value
710
+ def obj_initializing(obj,mapping) # :nodoc:
711
+ if @options.has_key?(:default_value) and (mapping==nil || mapping==@mapping)
393
712
  begin
394
713
  obj.send :"#{@attrname}=", @options[:default_value].clone
395
714
  rescue
@@ -410,160 +729,11 @@ module XML
410
729
  raise NoAttrValueSet, "Attribute #{@attrname} not set (XXPathError: #{err})"
411
730
  end
412
731
  end
413
- end
414
-
415
-
416
- # Registers the new node class _c_ (must be a descendant of Node)
417
- # with the xml-mapping framework.
418
- #
419
- # A new "factory method" will automatically be added to
420
- # ClassMethods (and therefore to all classes that include
421
- # XML::Mapping from now on); so you can call it from the body of
422
- # your mapping class definition in order to create nodes of type
423
- # _c_. The name of the factory method is derived by "underscoring"
424
- # the (unqualified) name of _c_;
425
- # e.g. _c_==<tt>Foo::Bar::MyNiftyNode</tt> will result in the
426
- # creation of a factory method named +my_nifty_node+. The
427
- # generated factory method creates and returns a new instance of
428
- # _c_. The list of argument to _c_.new consists of _self_
429
- # (i.e. the mapping class the factory method was called from)
430
- # followed by the arguments passed to the factory method. You
431
- # should always use the factory methods to create instances of
432
- # node classes; you should never need to call a node class's
433
- # constructor directly.
434
- #
435
- # For a demonstration, see the calls to +text_node+, +array_node+
436
- # etc. in the examples along with the corresponding node classes
437
- # TextNode, ArrayNode etc. (these predefined node classes are in
438
- # no way "special"; they're added using add_node_class in
439
- # mapping.rb just like any custom node classes would be).
440
- def self.add_node_class(c)
441
- meth_name = c.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"_"+$2.downcase}
442
- ClassMethods.module_eval <<-EOS
443
- def #{meth_name}(*args)
444
- #{c.name}.new(self,*args)
445
- end
446
- EOS
447
- end
448
-
449
-
450
- # The instance methods of this module are automatically added as
451
- # class methods to a class that includes XML::Mapping.
452
- module ClassMethods
453
- #ClassMethods = Module.new do # this is the alterbative -- but see above for peculiarities
454
-
455
- # Add getter and setter methods for a new attribute named _name_
456
- # to this class. This is a convenience method intended to be
457
- # called from Node class initializers.
458
- def add_accessor(name)
459
- name = name.id2name if name.kind_of? Symbol
460
- unless self.instance_methods.include?(name)
461
- self.module_eval <<-EOS
462
- attr_reader :#{name}
463
- EOS
464
- end
465
- unless self.instance_methods.include?("#{name}=")
466
- self.module_eval <<-EOS
467
- attr_writer :#{name}
468
- EOS
469
- end
470
- end
471
-
472
- # Create a new instance of this class from the XML contained in
473
- # the file named _filename_. Calls load_from_xml internally.
474
- def load_from_file(filename)
475
- xml = REXML::Document.new(File.new(filename))
476
- load_from_xml(xml.root)
477
- end
478
-
479
- # Create a new instance of this class from the XML contained in
480
- # _xml_ (a REXML::Element).
481
- #
482
- # Allocates a new object, then calls fill_from_xml(_xml_) on
483
- # it.
484
- def load_from_xml(xml)
485
- obj = self.allocate
486
- obj.initialize_xml_mapping
487
- obj.fill_from_xml(xml)
488
- obj
489
- end
490
-
491
-
492
- # array of all nodes types defined in this class, in the order
493
- # of their definition
494
- def xml_mapping_nodes
495
- @xml_mapping_nodes ||= []
496
- end
497
-
498
-
499
- # enumeration of all nodes types in effect when
500
- # marshalling/unmarshalling this class, that is, node types
501
- # defined for this class as well as for its superclasses. The
502
- # node types are returned in the order of their definition,
503
- # starting with the topmost superclass that has node types
504
- # defined.
505
- def all_xml_mapping_nodes
506
- # TODO: we could return a dynamic Enumerable here, or cache
507
- # the array...
508
- result = []
509
- if superclass and superclass.respond_to?(:all_xml_mapping_nodes)
510
- result += superclass.all_xml_mapping_nodes
511
- end
512
- result += xml_mapping_nodes
513
- end
514
-
515
-
516
- # The "root element name" of this class (combined getter/setter
517
- # method).
518
- #
519
- # The root element name is the name of the root element of the
520
- # XML tree returned by <this class>.#save_to_xml (or, more
521
- # specifically, <this class>.#pre_save). By default, this method
522
- # returns the #default_root_element_name; you may call this
523
- # method with an argument to set the root element name to
524
- # something other than the default.
525
- def root_element_name(name=nil)
526
- if name
527
- Classes_w_nondefault_rootelt_names.delete(root_element_name)
528
- Classes_w_default_rootelt_names.delete(root_element_name)
529
- Classes_w_default_rootelt_names.delete(name)
530
-
531
- @root_element_name = name
532
-
533
- Classes_w_nondefault_rootelt_names[name]=self
534
- end
535
- @root_element_name || default_root_element_name
536
- end
537
-
538
-
539
- # The default root element name for this class. Equals the class
540
- # name, with all parent module names stripped, and with capital
541
- # letters converted to lowercase and preceded by a dash;
542
- # e.g. "Foo::Bar::MySampleClass" becomes "my-sample-class".
543
- def default_root_element_name
544
- self.name.split('::')[-1].gsub(/^(.)/){$1.downcase}.gsub(/(.)([A-Z])/){$1+"-"+$2.downcase}
732
+ # (overridden) returns true if and only if the value of this
733
+ # node's attribute in _obj_ is non-nil.
734
+ def is_present_in? obj
735
+ nil != obj.send(:"#{@attrname}")
545
736
  end
546
-
547
- end
548
-
549
-
550
-
551
- # "polymorphic" load function. Turns the XML tree _xml_ into an
552
- # object, which is returned. The class of the object is
553
- # automatically determined from the root element name of _xml_
554
- # using XML::Mapping::class_for_root_elt_name.
555
- def self.load_object_from_xml(xml)
556
- unless c = class_for_root_elt_name(xml.name)
557
- raise MappingError, "no mapping class for root element name #{xml.name}"
558
- end
559
- c.load_from_xml(xml)
560
- end
561
-
562
- # Like load_object_from_xml, but loads from the XML file named by
563
- # _filename_.
564
- def self.load_object_from_file(filename)
565
- xml = REXML::Document.new(File.new(filename))
566
- load_object_from_xml(xml.root)
567
737
  end
568
738
 
569
739
  end