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.
- 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
data/ChangeLog
CHANGED
@@ -1,8 +1,40 @@
|
|
1
|
-
|
1
|
+
2006/12/26 Olaf Klischat
|
2
2
|
|
3
|
-
*
|
3
|
+
* when creating a new instance of a mapping class from an XML
|
4
|
+
input, call new if possible rather than allocate (patch by Fred
|
5
|
+
Loney)
|
4
6
|
|
5
|
-
|
7
|
+
2006/04/30 Olaf Klischat
|
8
|
+
|
9
|
+
* xml/xxpath: text() steps
|
10
|
+
|
11
|
+
2006/03/31 Olaf Klischat
|
12
|
+
|
13
|
+
* SubObjectBaseNode: store marshaller/unmarshaller in
|
14
|
+
@marshaller/@unmarshaller (general policy for node
|
15
|
+
implementations is to set @options to
|
16
|
+
originally supplied option arguments and never change it; then
|
17
|
+
store "extracted" information in additional @attributes)
|
18
|
+
|
19
|
+
2006/02/19 Olaf Klischat
|
20
|
+
|
21
|
+
* xml/xxpath: child::*[@attrname='attrvalue'] steps
|
22
|
+
|
23
|
+
2006/02/19 Olaf Klischat
|
24
|
+
|
25
|
+
* xml/xxpath: .[@attrname='attrvalue'] steps
|
26
|
+
|
27
|
+
2005/12/30 Olaf Klischat
|
28
|
+
|
29
|
+
* node initializers in node's initialize() method; initialize_impl
|
30
|
+
deprecated (but retained for backward compatibility)
|
31
|
+
|
32
|
+
2005/12/28 Olaf Klischat
|
33
|
+
|
34
|
+
* :reader/:writer options to node factory functions (for partially
|
35
|
+
or completely overriding the node's functionality)
|
36
|
+
|
37
|
+
2005/12/07 Olaf Klischat
|
6
38
|
|
7
39
|
* ChangeLog file
|
8
40
|
|
@@ -10,6 +42,35 @@
|
|
10
42
|
|
11
43
|
* bugfix: clone default values to avoid external modifications
|
12
44
|
|
45
|
+
2005/11/27 Olaf Klischat
|
46
|
+
|
47
|
+
* xml/xxpath: name1|name2|... steps
|
48
|
+
|
49
|
+
2005/11/19 Olaf Klischat
|
50
|
+
|
51
|
+
* support for String and numeric types in :class attributes
|
52
|
+
|
53
|
+
2005/11/16 Olaf Klischat
|
54
|
+
|
55
|
+
* choice_node
|
56
|
+
|
57
|
+
2005/11/05 Olaf Klischat
|
58
|
+
|
59
|
+
* xml/xxpath: descendants ("//") axis
|
60
|
+
|
61
|
+
2005/10/11 Olaf Klischat
|
62
|
+
|
63
|
+
* support for "." paths/path elements (map sub-objects to XML data
|
64
|
+
from the parent object's XML element)
|
65
|
+
|
66
|
+
2005/10/05 Olaf Klischat
|
67
|
+
|
68
|
+
* multiple distinct mappings per mapping class
|
69
|
+
|
70
|
+
2005/09/30 Olaf Klischat
|
71
|
+
|
72
|
+
* @options moved from SingleAttributeNode to Node
|
73
|
+
|
13
74
|
2005/07/07 Olaf Klischat
|
14
75
|
|
15
76
|
* release 0.8
|
data/README
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
= XML-MAPPING: XML-to-object (and back)
|
1
|
+
= XML-MAPPING: XML-to-object (and back) Mapper for Ruby, including XPath Interpreter
|
2
2
|
|
3
3
|
Xml-mapping is an easy to use, extensible library that allows you to
|
4
|
-
semi-automatically map Ruby objects to XML trees and vice versa.
|
5
|
-
easy to use and has a modular design that allows for easy extension of
|
6
|
-
its functionality.
|
4
|
+
semi-automatically map Ruby objects to XML trees and vice versa.
|
7
5
|
|
8
6
|
== Download
|
9
7
|
|
@@ -11,16 +9,30 @@ For downloading the latest version, CVS repository access etc. go to:
|
|
11
9
|
|
12
10
|
http://rubyforge.org/projects/xml-mapping/
|
13
11
|
|
14
|
-
==
|
12
|
+
== Contents of this Document
|
13
|
+
|
14
|
+
- {Example}[aref:example]
|
15
|
+
- {Single-attribute Nodes}[aref:sanodes]
|
16
|
+
- {Default Values}[aref:defaultvalues]
|
17
|
+
- {Single-attribute Nodes with Sub-objects}[aref:subobjnodes]
|
18
|
+
- {Attribute Handling Details, Augmenting Existing Classes}[aref:attrdefns]
|
19
|
+
- {Other Nodes}[aref:onodes]
|
20
|
+
- {choice_node}[aref:choice_node]
|
21
|
+
- {Readers/Writers}[aref:readerswriters]
|
22
|
+
- {Multiple Mappings per Class}[aref:mappings]
|
23
|
+
- {Defining your own Node Types}[aref:definingnodes]
|
24
|
+
- {XPath Interpreter}[aref:xpath]
|
25
|
+
|
26
|
+
== {Example}[a:example]
|
15
27
|
|
16
28
|
(example document stolen + extended from
|
17
29
|
http://www.castor.org/xml-mapping.html)
|
18
30
|
|
19
|
-
=== Input
|
31
|
+
=== Input Document:
|
20
32
|
|
21
33
|
:include: order.xml
|
22
34
|
|
23
|
-
=== Mapping
|
35
|
+
=== Mapping Class Declaration:
|
24
36
|
|
25
37
|
:include: order.rb
|
26
38
|
|
@@ -28,9 +40,6 @@ http://www.castor.org/xml-mapping.html)
|
|
28
40
|
|
29
41
|
:include: order_usage.intout
|
30
42
|
|
31
|
-
|
32
|
-
== Description
|
33
|
-
|
34
43
|
As shown in the example, you have to include XML::Mapping into a class
|
35
44
|
to turn it into a "mapping class". There are no other restrictions
|
36
45
|
imposed on mapping classes; you can add attributes and methods to
|
@@ -38,14 +47,17 @@ them, include additional modules in them, derive them from other
|
|
38
47
|
classes, derive other classes from them etc.pp.
|
39
48
|
|
40
49
|
An instance of a mapping class can be created from/converted into an
|
41
|
-
XML node
|
42
|
-
XML::Mapping#save_to_xml, XML::Mapping.load_from_file,
|
50
|
+
XML node with methods like XML::Mapping::ClassMethods.load_from_xml,
|
51
|
+
XML::Mapping#save_to_xml, XML::Mapping::ClassMethods.load_from_file,
|
43
52
|
XML::Mapping#save_to_file. Special class methods like "text_node",
|
44
|
-
"array_node" etc., called
|
45
|
-
the body of the class definition to define instance attributes
|
46
|
-
are automatically and bidirectionally mapped to subtrees of the
|
47
|
-
element an instance of the class is mapped to.
|
48
|
-
|
53
|
+
"array_node" etc., called *node* *factory* *methods*, may be called
|
54
|
+
from the body of the class definition to define instance attributes
|
55
|
+
that are automatically and bidirectionally mapped to subtrees of the
|
56
|
+
XML element an instance of the class is mapped to.
|
57
|
+
|
58
|
+
== {Single-attribute Nodes}[a:sanodes]
|
59
|
+
|
60
|
+
For example, in the definition
|
49
61
|
|
50
62
|
class Address
|
51
63
|
include XML::Mapping
|
@@ -59,45 +71,30 @@ definition
|
|
59
71
|
the first call to #text_node creates an attribute named "city" which
|
60
72
|
is mapped to the text of the XML child element defined by the XPath
|
61
73
|
expression "City" (xml-mapping includes an XPath interpreter that can
|
62
|
-
also be used seperately; see below). When you create an
|
63
|
-
+Address+ from an XML element (using
|
64
|
-
|
74
|
+
also be used seperately; see below[aref:xpath]). When you create an
|
75
|
+
instance of +Address+ from an XML element (using
|
76
|
+
Address.load_from_file(file_name) or
|
77
|
+
Address.load_from_xml(rexml_element)), that instance's "city"
|
65
78
|
attribute will be set to the text of the XML element's "City" child
|
66
|
-
element. When you convert an instance of Address into an XML
|
67
|
-
a sub-element "City" is added and
|
68
|
-
of the +city+ attribute. The other node types
|
69
|
-
array_node etc.) work analogously.
|
70
|
-
|
71
|
-
of
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
elements. When that happens, "Signed-By" names the common base element
|
86
|
-
for all those elements, and "Signature" is the path that will be
|
87
|
-
duplicated for each element. The input document in the example above
|
88
|
-
shows how this ends up looking.
|
89
|
-
|
90
|
-
Hash nodes work similarly, but they define hash-valued attributes
|
91
|
-
instead of array-valued ones.
|
92
|
-
|
93
|
-
Refer to the reference documentation for details about the node types
|
94
|
-
that are included in the xml-mapping library.
|
95
|
-
|
96
|
-
|
97
|
-
=== Default values
|
98
|
-
|
99
|
-
For each node you may define a _default value_ which will be set if
|
100
|
-
there was no value defined for the attribute in the XML source.
|
79
|
+
element. When you convert an instance of +Address+ into an XML
|
80
|
+
element, a sub-element "City" is added and its text is set to the
|
81
|
+
current value of the +city+ attribute. The other node types
|
82
|
+
(numeric_node, array_node etc.) work analogously. Generally said, when
|
83
|
+
an instance of the above +Address+ class is created from or converted
|
84
|
+
to an XML tree, each of the four nodes in the class maps some parts of
|
85
|
+
that XML tree to a single, specific attribute of the +Adress+
|
86
|
+
instance. The name of that attribute is given in the first argument to
|
87
|
+
the node factory method. Such a node is called a "single-attribute
|
88
|
+
node". All node types that come with xml-mapping except one
|
89
|
+
(+choice_node+, which I'll talk about below) are single-attribute
|
90
|
+
nodes.
|
91
|
+
|
92
|
+
|
93
|
+
=== {Default Values}[a:defaultvalues]
|
94
|
+
|
95
|
+
For each single-attribute node you may define a <i>default value</i>
|
96
|
+
which will be set if there was no value defined for the attribute in
|
97
|
+
the XML source.
|
101
98
|
|
102
99
|
From the example:
|
103
100
|
|
@@ -118,7 +115,7 @@ The semantics of default values are as follows:
|
|
118
115
|
(when defining your own initializer, you'll have to call the
|
119
116
|
inherited _initialize_ method in order to get this behaviour)
|
120
117
|
|
121
|
-
- when loading:
|
118
|
+
- when loading an instance from an XML document:
|
122
119
|
|
123
120
|
- attributes without default values that are not represented in the
|
124
121
|
XML raise an error
|
@@ -130,7 +127,7 @@ The semantics of default values are as follows:
|
|
130
127
|
in the XML
|
131
128
|
|
132
129
|
|
133
|
-
- when saving:
|
130
|
+
- when saving an instance to an XML document:
|
134
131
|
|
135
132
|
- unset attributes without default values raise an error
|
136
133
|
|
@@ -150,10 +147,246 @@ This implies that:
|
|
150
147
|
|
151
148
|
|
152
149
|
|
153
|
-
===
|
150
|
+
=== {Single-attribute Nodes with Sub-objects}[a:subobjnodes]
|
151
|
+
|
152
|
+
Single-attribute nodes of type +array_node+, +hash_node+, and
|
153
|
+
+object_node+ recursively map one or more subtrees of their XML to
|
154
|
+
sub-objects (e.g. array elements or hash values) of their
|
155
|
+
attribute. For example, with the line
|
156
|
+
|
157
|
+
array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
|
154
158
|
|
155
|
-
|
156
|
-
|
159
|
+
, an attribute named "signatures" is added to the surrounding class
|
160
|
+
(here: +Order+); the attribute will be an array whose elements
|
161
|
+
correspond to the XML sub-trees yielded by the XPath expression
|
162
|
+
"Signed-By/Signature" (relative to the tree corresponding to the
|
163
|
+
+Order+ instance). Each element will be of class +Signature+
|
164
|
+
(internally, each element is created from its corresponding XML
|
165
|
+
subtree by just calling
|
166
|
+
<tt>Signature.load_from_xml(the_subtree)</tt>). The reason why the
|
167
|
+
path "Signed-By/Signature" is provided in two arguments instead of
|
168
|
+
just one combined one becomes apparent when marshalling the array
|
169
|
+
(along with the surrounding +Order+ object) back into a sequence of
|
170
|
+
XML elements. When that happens, "Signed-By" names the common base
|
171
|
+
element for all those elements, and "Signature" is the path that will
|
172
|
+
be duplicated for each element. For example, when the +signatures+
|
173
|
+
attribute contains an array with 3 +Signature+ instances (let's call
|
174
|
+
them <tt>sig1</tt>, <tt>sig2</tt>, and <tt>sig3</tt>) in it, it will
|
175
|
+
be marshalled to an XML tree that looks like this:
|
176
|
+
|
177
|
+
<Signed-By>
|
178
|
+
<Signature>
|
179
|
+
[marshalled object sig1]
|
180
|
+
</Signature>
|
181
|
+
<Signature>
|
182
|
+
[marshalled object sig2]
|
183
|
+
</Signature>
|
184
|
+
<Signature>
|
185
|
+
[marshalled object sig3]
|
186
|
+
</Signature>
|
187
|
+
</Signed-By>
|
188
|
+
|
189
|
+
Internally, each +Signature+ instance is stored into its
|
190
|
+
<tt><Signature></tt> sub-element by calling
|
191
|
+
<tt>the_signature_instance.fill_into_xml(the_sub_element)</tt>. The
|
192
|
+
input document in the example above shows how this ends up looking.
|
193
|
+
|
194
|
+
<tt>hash_node</tt>s work similarly, but they define hash-valued attributes
|
195
|
+
instead of array-valued ones.
|
196
|
+
|
197
|
+
<tt>object_node</tt>s are the simplest of the three types of
|
198
|
+
single-attribute nodes with sub-objects. They just map a single given
|
199
|
+
subtree directly to their attribute value. See the example for
|
200
|
+
examples :)
|
201
|
+
|
202
|
+
The mentioned methods +load_from_xml+ and +fill_into_xml+ are the only
|
203
|
+
methods classes must implement in order to be usable in the
|
204
|
+
<tt>:class=></tt> keyword arguments to node factory methods. Mapping
|
205
|
+
classes (i.e. classes that <tt>include XML::Mapping</tt>)
|
206
|
+
automatically inherit those functions and can thus be readily used in
|
207
|
+
<tt>:class=></tt> arguments, as shown for the +Signature+ class in the
|
208
|
+
+array_node+ call above. In addition to that, xml-mapping adds those
|
209
|
+
methods to some of Ruby's core classes, namely +String+ and +Numeric+
|
210
|
+
(and thus +Float+, +Integer+, and +BigInt+). So you can also use
|
211
|
+
strings or numbers as sub-objects of attributes of +array_node+,
|
212
|
+
+hash_node+, or +object_node+ nodes. For example, say you have an XML
|
213
|
+
document like this one:
|
214
|
+
|
215
|
+
:include: stringarray.xml
|
216
|
+
|
217
|
+
, and you want to map all the names to a string array attribute
|
218
|
+
+names+, you could do it like this:
|
219
|
+
|
220
|
+
:include: stringarray.rb
|
221
|
+
|
222
|
+
usage:
|
223
|
+
|
224
|
+
:include: stringarray_usage.intout
|
225
|
+
|
226
|
+
As a side node, this feature actually makes +text_node+ and
|
227
|
+
+numeric_node+ special cases of +object_node+. For example,
|
228
|
+
<tt>text_node :attr, "path"</tt> is the same as <tt>object_node :attr,
|
229
|
+
"path", :class=>String</tt>.
|
230
|
+
|
231
|
+
|
232
|
+
==== Polymorphic Sub-objects, Marshallers/Unmarshallers
|
233
|
+
|
234
|
+
Besides the <tt>:class</tt> keyword argument, there are alternative
|
235
|
+
ways for a single-attribute node with sub-objects to specify the way
|
236
|
+
the sub-objects are created from/marshalled into their subtrees.
|
237
|
+
|
238
|
+
First, it's possible not to specify anything at all -- in that case,
|
239
|
+
the class of a sub-object will be automatically deduced from the root
|
240
|
+
element name of its subtree. This allows you to achieve a kind of
|
241
|
+
"polymorphic", late-bound way to decide about the sub-object's
|
242
|
+
class. The following example document contains a hierarchical,
|
243
|
+
recursive set of named "documents" and "folders", where folders hold a
|
244
|
+
set of entries, each of which may again be either a document or a
|
245
|
+
folder:
|
246
|
+
|
247
|
+
:include: documents_folders.xml
|
248
|
+
|
249
|
+
This can be mapped to Ruby like this:
|
250
|
+
|
251
|
+
:include: documents_folders.rb
|
252
|
+
|
253
|
+
Usage:
|
254
|
+
|
255
|
+
:include: documents_folders_usage.intout
|
256
|
+
|
257
|
+
As you see, the <tt>Folder#entries</tt> attribute is mapped via an
|
258
|
+
array_node that does not specify a <tt>:class</tt> or anything else to
|
259
|
+
govern the instantiation of the array's elements. This causes
|
260
|
+
xml-mapping to deduce the class of each array element from the root
|
261
|
+
element name of the corresponding XML tree. In this example, the root
|
262
|
+
element name is either "document" or "folder". The mapping between
|
263
|
+
root element names and class names is the one briefly described in
|
264
|
+
example[aref:example] at the beginning of this document -- the
|
265
|
+
unqualified class name is just converted to lower case and "dashed",
|
266
|
+
e.g. Foo::Bar::MyClass becomes "my-class"; and you may overwrite this
|
267
|
+
on a per-class basis by calling <tt>root_element_name
|
268
|
+
"the-new-name"</tt> in the class body. In our example, the root
|
269
|
+
element name "document" leads to an instantiation of class +Document+,
|
270
|
+
and the root element name "folder" leads to an instantiation of class
|
271
|
+
+Folder+.
|
272
|
+
|
273
|
+
Incidentally, the last example shows that you can readily derive
|
274
|
+
mapping classes from one another (as said before, you can also derive
|
275
|
+
mapping classes from other classes, include other modules into them
|
276
|
+
etc. at will). This works just like intuition thinks it should -- when
|
277
|
+
deriving one mapping class from another one, the list of nodes in
|
278
|
+
effect when loading/saving instances of the derived class will consist
|
279
|
+
of all nodes of that class and all superclasses, starting with the
|
280
|
+
topmost superclass that has nodes defined. There is one thing to take
|
281
|
+
care of though: When deriving mapping classes from one another, you
|
282
|
+
have to make sure to <tt>include XML::Mapping</tt> in each class. This
|
283
|
+
requirement exists purely due to ease-of-implementation
|
284
|
+
considerations; there are probably ways to do away with it, but the
|
285
|
+
inconvenience seemed not severe enough for me to bother (as
|
286
|
+
yet). Still, you might get "strange" errors if you forget to do it for
|
287
|
+
a class.
|
288
|
+
|
289
|
+
Besides the <tt>:class</tt> keyword argument and no argument, there is
|
290
|
+
a third way to specify the way the sub-objects are created
|
291
|
+
from/marshalled into their subtrees: <tt>:marshaller</tt> and/or
|
292
|
+
<tt>:unmarshaller</tt> keyword arguments. Here you pass procs in which
|
293
|
+
you just do all the work manually. So this is basically a "catch-all"
|
294
|
+
for cases where the other two alternatives are not appropriate for the
|
295
|
+
problem at hand. (*TODO*: Use other example?) Let's say we want to
|
296
|
+
extend the +Signature+ class from the initial example to include the
|
297
|
+
date on which the signature was created. We want the new XML
|
298
|
+
representation of such a signature to look like this:
|
299
|
+
|
300
|
+
:include: time_node_w_marshallers.xml
|
301
|
+
|
302
|
+
So, a new "signed-on" element was added that holds the day, month, and
|
303
|
+
year. In the +Signature+ instance in Ruby, we want the date to be
|
304
|
+
stored in an attribute named +signed_on+ of type +Time+ (that's Ruby's
|
305
|
+
built-in +Time+ class).
|
306
|
+
|
307
|
+
One could think of using +object_node+, but something like
|
308
|
+
<tt>object_node :signed_on, "signed-on", :class=>Time</tt> won't work
|
309
|
+
because +Time+ isn't a mapping class and doesn't define methods
|
310
|
+
+load_from_xml+ and +fill_into_xml+ (we could easily define those
|
311
|
+
though; we'll talk about that possibility here[aref:attrdefns] and
|
312
|
+
here[aref:definingnodes]). The fastest, most ad-hoc way to achieve
|
313
|
+
what we want are :marshaller and :unmarshaller keyword arguments, like
|
314
|
+
this:
|
315
|
+
|
316
|
+
:include: time_node_w_marshallers.intout
|
317
|
+
|
318
|
+
The <tt>:unmarshaller</tt> proc will be called whenever a +Signature+
|
319
|
+
instance is being read in from an XML source. The +xml+ argument
|
320
|
+
passed to the proc contains (as a REXML::Element instance) the XML
|
321
|
+
subtree corresponding to the node's attribute's sub-object currently
|
322
|
+
being read. In the case of our +object_node+, the sub-object is just
|
323
|
+
the node's attribute (+signed_on+) itself, and the subtree is the one
|
324
|
+
rooted at the <signed-on> element (if this were e.g. an +array_node+,
|
325
|
+
the <tt>:unmarshaller</tt> proc would be called once for each array
|
326
|
+
element, and +xml+ would hold the subtree corresponding to the
|
327
|
+
"current" array element). The proc is expected to extract the
|
328
|
+
sub-object's data from +xml+ and return the sub-object. So we have to
|
329
|
+
read the "year", "month", and "day" elements, construct a +Time+
|
330
|
+
instance from them and return that. One could just use the REXML API
|
331
|
+
to do that, but I've decided here to use the XPath interpreter that
|
332
|
+
comes with xml-mapping (xml/xxpath), and specifically the
|
333
|
+
'xml/xxpath_methods' utility library that adds methods like +first+ to
|
334
|
+
REMXML::Element. We call +first+ on +xml+ three times, passing XPath
|
335
|
+
expressions to extract the "year"/"month"/"day" sub-elements,
|
336
|
+
construct the +Time+ instance from that and return it. The XPath
|
337
|
+
library is explained in more detail below[aref:xpath].
|
338
|
+
|
339
|
+
The <tt>:marshaller</tt> proc will be called whenever a +Signature+
|
340
|
+
instance is being written into an XML tree. +xml+ is again the XML
|
341
|
+
subtree rooted at the <signed-on> element (it will still be empty when
|
342
|
+
this proc is called), and +value+ is the current value of the
|
343
|
+
sub-object (again, since this is an +object_node+, +value+ is the
|
344
|
+
node's attribute, i.e. the +Time+ instance). We have to fill +xml+
|
345
|
+
with the data from +value+ here. So we add three elements "year",
|
346
|
+
"month" and "day" and set their texts to the corresponding values from
|
347
|
+
+value+. The commented-out code shows an alternative implementation of
|
348
|
+
the same thing using the XPath interpreter.
|
349
|
+
|
350
|
+
It should be mentioned again that :marshaller/:unmarshaller procs are
|
351
|
+
possible with all single-attribute nodes with sub-objects, i.e. with
|
352
|
+
+object_node+, +array_node+, and +hash_node+. So, if you wanted to map
|
353
|
+
a whole array of date values, you could use +array_node+ with the same
|
354
|
+
:marshaller/:unmarshaller procs as above, for example:
|
355
|
+
|
356
|
+
array_node :birthdays, "birthdays", "birthday",
|
357
|
+
:unmarshaller=> <as above>,
|
358
|
+
:marshaller=> <as above>
|
359
|
+
|
360
|
+
You can see that :marshaller/:unmarshaller procs give you more
|
361
|
+
flexibility, but they also impose more work because you essentially
|
362
|
+
have to do all the work of marshalling/unmarshalling the sub-objects
|
363
|
+
yourself. If you find yourself copying and pasting
|
364
|
+
marshaller/unmarshaller procs all over the place, you should instead
|
365
|
+
define your own node type or mix the marshalling/unmarshalling
|
366
|
+
capabilities into the +Time+ class itself. This is explained
|
367
|
+
here[aref:attrdefns] and here[aref:definingnodes], and you'll see that
|
368
|
+
it's not really much more work than writing :marshaller and
|
369
|
+
:unmarshaller procs (you essentially just move the code from those
|
370
|
+
procs into your own node type resp. into the +Time+ class), so you
|
371
|
+
should not hesitate to do this.
|
372
|
+
|
373
|
+
Another thing worth mentioning is that you don't have to specify
|
374
|
+
*both* a :marshaller and an :unmarshaller simultaneously. You can as
|
375
|
+
well give only one of them, and in addition to that pass a
|
376
|
+
<tt>:class</tt> argument or no argument. When you do that, the
|
377
|
+
specified marshaller (or unmarshaller) will be used when marshalling
|
378
|
+
(resp. unmarshalling) the sub-objects, and the other passed argument
|
379
|
+
(<tt>:class</tt> or none) will be employed when unmarshalling
|
380
|
+
(resp. marshalling) the sub-objects. So, in effect, you can deactivate
|
381
|
+
or "short-cut" some part of the marshalling/unmarshalling
|
382
|
+
functionality of a node type while retaining another part.
|
383
|
+
|
384
|
+
|
385
|
+
|
386
|
+
=== {Attribute Handling Details, Augmenting Existing Classes}[a:attrdefns]
|
387
|
+
|
388
|
+
I'll shed some more light on how single-attribute nodes add mapped
|
389
|
+
attributes to Ruby classes. An attribute declaration like
|
157
390
|
|
158
391
|
text_node :city, "City"
|
159
392
|
|
@@ -188,32 +421,255 @@ declarations that declare XML mappings for the day, month etc. fields:
|
|
188
421
|
:include: time_augm.intout
|
189
422
|
|
190
423
|
Here XML mappings are defined for the existing fields +year+, +month+
|
191
|
-
etc. Xml-
|
424
|
+
etc. Xml-mapping noticed that the getter methods for those attributes
|
192
425
|
existed, so it didn't overwrite them. When calling +save_to_xml+ on a
|
193
426
|
+Time+ object, these methods are called and return the object's values
|
194
|
-
for those fields, which then get written to the output XML.
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
class
|
427
|
+
for those fields, which then get written to the output XML.
|
428
|
+
|
429
|
+
So you can convert +Time+ objects into XML trees. What about reading
|
430
|
+
them back in from XML? All XML reading operations go through
|
431
|
+
<tt><Class>.load_from_xml</tt>. The +load_from_xml+ class method
|
432
|
+
inherited from XML::Mapping (see
|
433
|
+
XML::Mapping::ClassMethods#load_from_xml) allocates a new instance of
|
434
|
+
the class (+Time+), then calls +fill_from_xml+
|
435
|
+
(i.e. XML::Mapping#fill_from_xml) on it. +fill_from_xml+ iterates over
|
436
|
+
all our nodes in the order of their definition. For each node, its
|
437
|
+
data (the <year>, or <month>, or <day> etc. element) is read from the
|
438
|
+
XML source and then written to the +Time+ instance via the respective
|
439
|
+
setter method (<tt>year=</tt>, <tt>month=</tt>, <tt>day=</tt>
|
440
|
+
etc.). These methods didn't exist in +Time+ before (+Time+ objects are
|
441
|
+
immutable), so xml-mapping defined its own, default setter methods
|
442
|
+
that just set <tt>@year</tt>, <tt>@month</tt> etc. This is of course
|
443
|
+
pretty useless because +Time+ objects don't hold their time in these
|
444
|
+
variables, so the setter methods don't really change the time of the
|
445
|
+
+Time+ object. So we have to redefine +load_from_xml+ for the +Time+
|
446
|
+
class:
|
447
|
+
|
448
|
+
:include: time_augm_loading.intout
|
449
|
+
|
450
|
+
|
451
|
+
== {Other Nodes}[a:onodes]
|
452
|
+
|
453
|
+
All nodes I've shown so far (node types text_node, numeric_node,
|
454
|
+
boolean_node, object_node, array_node, and hash_node) were
|
455
|
+
single-attribute nodes: The first parameter to the node factory method
|
456
|
+
of such a node is an attribute name, and the attribute of that name is
|
457
|
+
the only piece of the state of instances of the node's mapping class
|
458
|
+
that gets read/written by the node.
|
459
|
+
|
460
|
+
=== {choice_node}[a:choice_node]
|
461
|
+
|
462
|
+
There is one node type distributed with xml-mapping that is not a
|
463
|
+
single-attribute node: +choice_node+. A +choice_node+ allows you to
|
464
|
+
specify a sequence of pairs, each consisting of an XPath expression
|
465
|
+
and another node (any node is supported here, including other
|
466
|
+
choice_nodes). When reading in an XML source, the choice_node will
|
467
|
+
delegate the work to the first node in the sequence whose
|
468
|
+
corresponding XPath expression was matched in the XML. When writing an
|
469
|
+
object back to XML, the choice_node will delegate the work to the
|
470
|
+
first node whose data was "present" in the object (for
|
471
|
+
single-attribute nodes, the data is considered "present" if the node's
|
472
|
+
attribute is non-nil; for choice_nodes, the data is considered
|
473
|
+
"present" if at least one of the node's sub-nodes is "present").
|
474
|
+
|
475
|
+
As a (somewhat contrived) example, here's a mapping for +Publication+
|
476
|
+
objects that have either a single author (contained in an "author" XML
|
477
|
+
attribute) or several "contributors" (contained in a sequence of
|
478
|
+
"contr" XML elements):
|
479
|
+
|
480
|
+
:include: publication.intout
|
481
|
+
|
482
|
+
The symbols :if, :then, and :elsif (but not :else -- see below) in the
|
483
|
+
+choice_node+'s node factory method call are ignored; they may be
|
484
|
+
sprinkled across the argument list at will (preferably the way shown
|
485
|
+
above of course) to increase readability.
|
486
|
+
|
487
|
+
The rest of the arguments specify the mentioned sequence of XPath
|
488
|
+
expressions and corresponding nodes.
|
489
|
+
|
490
|
+
When reading a +Publication+ object from XML, the XPath expressions
|
491
|
+
from the +choice_node+ (<tt>@author</tt> and +contr+) will be matched
|
492
|
+
in sequence against the source XML tree until a match is found or the
|
493
|
+
end of the argument list is reached. If the end is reached, an
|
494
|
+
exception is raised. Otherwise, for the first XPath expression that
|
495
|
+
matched, the corresponding node will be invoked (i.e. used to read
|
496
|
+
actual data from the XML source into the +Person+ object). If you
|
497
|
+
specify :else, :default, or :otherwise in place of an XPath
|
498
|
+
expression, this is treated as an XPath expression that always
|
499
|
+
matches. So you can use :else (or :default or :otherwise) for a
|
500
|
+
"fallback" node that will be used if none of the other XPath
|
501
|
+
expressions matched (an example for this follows).
|
502
|
+
|
503
|
+
When writing a +Publication+ object back to XML, the first node in the
|
504
|
+
sequence whose data is "present" in the source object will be invoked
|
505
|
+
to write data from the object into the target XML tree (and the
|
506
|
+
corresponding XPath expression will be created in the XML tree if it
|
507
|
+
doesn't exist already). If there is no such node in the sequence, an
|
508
|
+
exception is raised. As said above, for single-attribute nodes, the
|
509
|
+
node's data is considered "present" if the node's attribute is
|
510
|
+
non-nil. So, if you write a +Publication+ object to XML, and either
|
511
|
+
the +author+ or the +contributors+ attribute of the object is set, it
|
512
|
+
will be written; if both attributes are nil, an exception will be
|
513
|
+
raised.
|
514
|
+
|
515
|
+
A frequent use case for choice_nodes will probably be object
|
516
|
+
attributes that may be represented in multiple alternative ways in
|
517
|
+
XML. As an example, consider "Person" objects where the name of the
|
518
|
+
person should be stored alternatively in a sub-element named +name+,
|
519
|
+
or an attribute named +name+, or in the text of the +person+ element
|
520
|
+
itself. You can achieve this with +choice_node+ like this:
|
521
|
+
|
522
|
+
:include: person.intout
|
523
|
+
|
524
|
+
Here all sub-nodes of the choice_nodes are single-attribute nodes
|
525
|
+
(text_nodes) with the same attribute (+name+). As you see, when
|
526
|
+
writing persons to XML, the name is always stored in a <name>
|
527
|
+
sub-element. Of course, this is because that alternative appears first
|
528
|
+
in the choice_node.
|
529
|
+
|
530
|
+
|
531
|
+
=== {Readers/Writers}[a:readerswriters]
|
532
|
+
|
533
|
+
Finally, _all_ nodes support keyword arguments :reader and :writer
|
534
|
+
which allow you to extend or completely override the reading and/or
|
535
|
+
writing functionality of the node with your own code. The :reader as
|
536
|
+
well as the :writer argument must be a proc that takes as its
|
537
|
+
arguments the Ruby object to be read/written (instance of the mapping
|
538
|
+
class the node belongs to) and the XML tree to be written to/read
|
539
|
+
from. An optional third argument may be specified -- it will receive a
|
540
|
+
proc that wraps the default reader/writer functionality of the
|
541
|
+
node.
|
542
|
+
|
543
|
+
The :reader proc is for reading (from the XML into the object), the
|
544
|
+
:writer proc is for writing (from the object into the XML).
|
545
|
+
|
546
|
+
Here's a (really contrived) example:
|
547
|
+
|
548
|
+
:include: reader.intout
|
549
|
+
|
550
|
+
So there's a "Foo" class with a text_node that would by default
|
551
|
+
(without the :reader and :writer proc) map the Ruby attribute "name"
|
552
|
+
to the XML attribute "name". The :reader proc is invoked when reading
|
553
|
+
from XML into a +Foo+ object. The +xml+ argument is the XML tree,
|
554
|
+
+obj+ is the object. +default_reader+ is the proc that wraps the
|
555
|
+
default reading functionality of the node. We invoke it at the
|
556
|
+
beginning. For this text_node, the default reading functionality is to
|
557
|
+
take the text of the "name" attribute of +xml+ and put it into the
|
558
|
+
+name+ attribute of +obj+. After that, we take the text of the "more"
|
559
|
+
attribute of +xml+ and append it to the +name+ attribute of +obj+. So
|
560
|
+
the XML tree <tt><foo name="Jim" more="XYZ"/></tt> is converted to a
|
561
|
+
+Foo+ object with +name+="JimXYZ".
|
562
|
+
|
563
|
+
In our :writer proc, we only take +obj+ (the +Foo+ object to be
|
564
|
+
written to XML) and +xml+ (the XML tree the stuff is to be written
|
565
|
+
to). Analogously to the :reader, we could take a proc that wraps the
|
566
|
+
default writing functionality of the node, but we don't do that
|
567
|
+
here--we completely override the writing functionality with our own
|
568
|
+
code, which just takes the +name+ attribute of the object and writes
|
569
|
+
"hi <the name> ho" to a +bar+ XML attribute in the XML tree (stupid
|
570
|
+
example, I know).
|
571
|
+
|
572
|
+
As a special convention, if you specify both a :reader and a :writer
|
573
|
+
for a node, and in both cases you do /not/ call the default behaviour,
|
574
|
+
then you should use the generic node type +node+, e.g.:
|
575
|
+
|
576
|
+
class SomeClass
|
577
|
+
include XML::Mapping
|
199
578
|
|
200
|
-
|
201
|
-
methods (<tt>year=</tt>, <tt>month=</tt> etc.) didn't exist in +Time+
|
202
|
-
(+Time+ objects are immutable), so xml-mapping defined its own setter
|
203
|
-
methods that just set <tt>@year</tt>, <tt>@month</tt> etc., which is
|
204
|
-
pretty useless for this case. So you can't really read +Time+ values
|
205
|
-
back from an XML representation in this example. For that to work,
|
206
|
-
you'd need functioning <tt>blah=(x)</tt> methods for each +blah+
|
207
|
-
attribute that you want to define an XML mapping for.
|
579
|
+
....
|
208
580
|
|
581
|
+
node :reader=>proc{|obj,xml| ...},
|
582
|
+
:writer=>proc{|obj,xml| ...}
|
583
|
+
end
|
209
584
|
|
210
|
-
|
585
|
+
(since you're completely replacing both the reading and the writing
|
586
|
+
functionality, you're effectively replacing all the functionality of
|
587
|
+
the node, so it would be pointless and confusing to use one of the
|
588
|
+
more "specific" node types)
|
589
|
+
|
590
|
+
As you see, the purpose of readers and writers is to make it possible
|
591
|
+
to augment or override a node's functionality arbitrarily, so there
|
592
|
+
shouldn't be anything that's absolutely impossible to achieve with
|
593
|
+
xml-mapping. However, if you use readers and writers without invoking
|
594
|
+
the default behaviour, you really do everything manually, so you're
|
595
|
+
not doing any less work than you would do if you weren't using
|
596
|
+
xml-mapping at all. So you'll probably use readers and/or writers for
|
597
|
+
those bits of your mapping semantics that can't be achieved with
|
598
|
+
xml-mapping's predefined node types (an alternative approach might be
|
599
|
+
to override the +post_load+ and/or +post_save+ instance methods on the
|
600
|
+
mapping class -- see the reference documentation).
|
601
|
+
|
602
|
+
An advice similar to the one given above for marshallers/unmarshallers
|
603
|
+
applies here as well: If you find yourself writing lots of readers and
|
604
|
+
writers that only differ in some easily parameterizable aspects, you
|
605
|
+
should think about defining your own node types. We talk about that
|
606
|
+
below[aref:definingnodes], and it generally just means that you move
|
607
|
+
the (sensibly parameterized) code from your readers/writers to your
|
608
|
+
node types.
|
609
|
+
|
610
|
+
|
611
|
+
== {Multiple Mappings per Class}[a:mappings]
|
612
|
+
|
613
|
+
Sometimes you might want to represent the same Ruby object in multiple
|
614
|
+
alternative ways in XML. For example, the name of a "Person" object
|
615
|
+
could be represented either in a "name" element or a "name" attribute.
|
616
|
+
|
617
|
+
xml-mapping supports this by allowing you to define multiple disjoint
|
618
|
+
"mappings" for a mapping class. A mapping is by convention identified
|
619
|
+
with a symbol, e.g. <tt>:my_mapping</tt>, <tt>:other_mapping</tt>
|
620
|
+
etc., and each mapping comprises a root element name and a set of node
|
621
|
+
definitions. In the body of a mapping class definition, you switch to
|
622
|
+
another mapping with <tt>use_mapping :the_mapping</tt>. All following
|
623
|
+
node declarations will be added to that mapping *unless* you specify
|
624
|
+
the option :mapping=>:another_mapping for a node declaration (all node
|
625
|
+
types support that option). The default mapping (the mapping used if
|
626
|
+
there was no previous +use_mapping+ in the class body) is named
|
627
|
+
<tt>:_default</tt>.
|
628
|
+
|
629
|
+
All the worker methods like <tt>load_from_xml/file</tt>,
|
630
|
+
<tt>save_to_xml/file</tt>, <tt>load_object_from_xml/file</tt> support
|
631
|
+
a <tt>:mapping</tt> keyword argument to specify the mapping, which
|
632
|
+
again defaults to <tt>:_default</tt>.
|
633
|
+
|
634
|
+
In the following example, we define two mappings (the default one and
|
635
|
+
a mapping named <tt>:other</tt>) for +Person+ objects with a name, an
|
636
|
+
age and an address:
|
637
|
+
|
638
|
+
:include: examples/person_mm.intout
|
639
|
+
|
640
|
+
In this example, each of the two mappings contains nodes that map the
|
641
|
+
same set of Ruby attributes (name, age and address). This is probably
|
642
|
+
what you want most of the time (since you're normally defining
|
643
|
+
multiple XML mappings for the same Ruby data), but it's not a
|
644
|
+
necessity at all. When a mapping class is defined, xml-mapping will
|
645
|
+
add all Ruby attributes from all mappings to it.
|
646
|
+
|
647
|
+
You may have noticed that the <tt>object_node</tt>s in the +Person+
|
648
|
+
class apply the mapping they were themselves defined in to their
|
649
|
+
sub-ordinated class (+Address+). This is the case for all
|
650
|
+
{Single-attribute Nodes with Sub-objects}[aref:subobjnodes]
|
651
|
+
(+object_node+, +array_node+ and +hash_node+) unless you explicitly
|
652
|
+
specify a different mapping for the sub-object(s) using the option
|
653
|
+
:sub_mapping, e.g.
|
654
|
+
|
655
|
+
object_node :address, "address", :class=>Address, :sub_mapping=>:other
|
656
|
+
|
657
|
+
|
658
|
+
|
659
|
+
== {Defining your own Node Types}[a:definingnodes]
|
211
660
|
|
212
661
|
It's easy to write additional node types and register them with the
|
213
|
-
xml-mapping library
|
214
|
-
|
215
|
-
|
216
|
-
|
662
|
+
xml-mapping library (the following node types come with xml-mapping:
|
663
|
+
+node+, +text_node+, +numeric_node+, +boolean_node+, +object_node+,
|
664
|
+
+array_node+, +hash_node+, +choice_node+).
|
665
|
+
|
666
|
+
I'll first show an example, then some more theoretical insight.
|
667
|
+
|
668
|
+
=== Example
|
669
|
+
|
670
|
+
Let's say we want to extend the +Signature+ class from the example to
|
671
|
+
include the time at which the signature was created. We want the new
|
672
|
+
XML representation of such a signature to look like this:
|
217
673
|
|
218
674
|
:include: order_signature_enhanced.xml
|
219
675
|
|
@@ -224,9 +680,9 @@ the mapping class declaration to look like this:
|
|
224
680
|
|
225
681
|
(i.e. a new "time_node" declaration was added).
|
226
682
|
|
227
|
-
We want this +
|
228
|
-
which holds the date value from the XML in an instance of
|
229
|
-
+Time+.
|
683
|
+
We want this +time_node+ call to define an attribute named +signed_on+
|
684
|
+
which holds the date value from the XML document in an instance of
|
685
|
+
class +Time+.
|
230
686
|
|
231
687
|
This node type can be defined with this piece of code:
|
232
688
|
|
@@ -237,126 +693,367 @@ library. The name of the node factory method ("time_node") is
|
|
237
693
|
automatically derived from the class name of the node type
|
238
694
|
("TimeNode").
|
239
695
|
|
240
|
-
There will be one instance of the node type per
|
241
|
-
mapping class instance). That
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
:default_value=>Time.now</tt>)
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
696
|
+
There will be one instance of the node type +TimeNode+ per +time_node+
|
697
|
+
declaration per mapping class (not per mapping class instance). That
|
698
|
+
instance (the "node" for short) will be created by the node factory
|
699
|
+
method (+time_node+); there's no need to instantiate the node type
|
700
|
+
directly. The +time_node+ method places the node into the mapping
|
701
|
+
class; the @owner attribute of the node is set to reference the
|
702
|
+
mapping class. The node factory method passes the mapping class the
|
703
|
+
node appears in (+Signature+), followed by its own arguments, to the
|
704
|
+
node's constructor. In the example, the +time_node+ method calls
|
705
|
+
<tt>TimeNode.new(Signature, :signed_on, "signed-on",
|
706
|
+
:default_value=>Time.now)</tt>). +new+ of course creates the node and
|
707
|
+
then delegates the arguments to our initializer +initialize+. We first
|
708
|
+
call the superclass's initializer, which strips off from the argument
|
709
|
+
list those arguments it handles itself, and returns the remaining
|
710
|
+
ones. In this case, the superclass XML::Mapping::SingleAttributeNode
|
711
|
+
handles the +Signature+, <tt>:signed_on</tt> and
|
712
|
+
<tt>:default_value=>Time.now</tt> arguments -- +Signature+ is stored
|
713
|
+
into <tt>@owner</tt>, <tt>:signed_on</tt> is stored into
|
714
|
+
<tt>@attrname</tt>, and <tt>{:default_value=>Time.now}</tt> is stored
|
715
|
+
into <tt>@options</tt>. The remaining argument list
|
716
|
+
<tt>["signed-on"]</tt> is returned; we capture the
|
717
|
+
<tt>"signed-on"</tt> string in _path_ (the rest of the argument list
|
718
|
+
(an empty array) we capture in _args_ for returning it at the end of
|
719
|
+
the initializer. This isn't strictly necessary, it's just a convention
|
720
|
+
that a node class initializer should always return those arguments it
|
721
|
+
didn't handle itself). We'll interpret _path_ as an XPath expression
|
257
722
|
that locates the time value relative to the parent mapping object's
|
258
723
|
XML tree (in this case, this would be the XML tree rooted at the
|
259
|
-
|
260
|
-
from). We'll later have to read/store the year, month, and
|
261
|
-
from <tt>path+"/year"</tt>, <tt>path+"/month"</tt>, and
|
724
|
+
<tt><Signature></tt> element, i.e. the tree the +Signature+ instance
|
725
|
+
was read from). We'll later have to read/store the year, month, and
|
726
|
+
day values from <tt>path+"/year"</tt>, <tt>path+"/month"</tt>, and
|
262
727
|
<tt>path+"/day"</tt>, respectively, so we create (and precompile)
|
263
728
|
three corresponding XPath expressions using XML::XXPath.new and store
|
264
729
|
them into member variables of the node. XML::XXPath is an XPath
|
265
730
|
implementation that is bundled with xml-mapping. It is very
|
266
731
|
incomplete, but it supports writing (not just reading) of XML nodes,
|
267
732
|
which is needed to support writing data back to XML. The XML::XXPath
|
268
|
-
library is explained in more detail below.
|
733
|
+
library is explained in more detail below[aref:xpath].
|
269
734
|
|
270
735
|
The +extract_attr_value+ method is called whenever an instance of the
|
271
|
-
class the node belongs to (+Signature+ in the example) is
|
272
|
-
created from an XML tree. The parameter _xml_ is that tree
|
273
|
-
this is the tree rooted at the
|
274
|
-
example). The method implementation is expected to extract the
|
275
|
-
attribute's value from _xml_ and return it, or raise
|
736
|
+
mapping class the node belongs to (+Signature+ in the example) is
|
737
|
+
being created from an XML tree. The parameter _xml_ is that tree
|
738
|
+
(again, this is the tree rooted at the <tt><Signature></tt> element in
|
739
|
+
this example). The method implementation is expected to extract the
|
740
|
+
single attribute's value from _xml_ and return it, or raise
|
276
741
|
XML::Mapping::SingleAttributeNode::NoAttrValueSet if the attribute was
|
277
|
-
"unset" in the XML (
|
278
|
-
was defined), or raise any
|
279
|
-
|
280
|
-
|
742
|
+
"unset" in the XML (this exception tells the framework that the
|
743
|
+
default value should be put in place if it was defined), or raise any
|
744
|
+
other exception to signal an error and abort the whole process. Our
|
745
|
+
superclass XML::Mapping::SingleAttributeNode will store the returned
|
746
|
+
single attribute's value into the <tt>signed_on</tt> attribute of the
|
747
|
+
+Signature+ instance being read in. In our implementation, we apply
|
748
|
+
the xpath expressions created during initialization to _xml_
|
281
749
|
(e.g. <tt>@y_path.first(xml)</tt>). An expression
|
282
750
|
_xpath_expr_.first(_xml_) returns (as a REXML element) the first
|
283
751
|
sub-element of _xml_ that matches _xpath_expr_, or raises
|
284
752
|
XML::XXPathError if there was no such element. We apply REXML's _text_
|
285
753
|
method to the returned element to get out the element's text, convert
|
286
754
|
it to integer, and supply it to the constructor of the +Time+ object
|
287
|
-
to be returned.
|
288
|
-
attributes, XML::XXPath methods like _first_ will return
|
289
|
-
nodes that behave similarly to
|
290
|
-
|
291
|
-
this
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
XML::Mapping::NoAttrValueSet is
|
298
|
-
(
|
299
|
-
|
300
|
-
|
301
|
-
|
755
|
+
to be returned. As a side note, if an XPath expression matches XML
|
756
|
+
attributes, XML::XXPath methods like _first_ will return
|
757
|
+
XML::XXPath::Accessors::Attribute nodes that behave similarly to
|
758
|
+
REXML::Element nodes, including support for messages like _name_ and
|
759
|
+
_text_, so this would've worked also if our XPath expressions had
|
760
|
+
referred to XML attributes, not elements. The +default_when_xpath_err+
|
761
|
+
thing calls the supplied block and returns its value, but maps the
|
762
|
+
exception XML::XXPathError to the mentioned
|
763
|
+
XML::Mapping::SingleAttributeNode::NoAttrValueSet (any other
|
764
|
+
exceptions fall through unchanged). As said above,
|
765
|
+
XML::Mapping::SingleAttributeNode::NoAttrValueSet is caught by the
|
766
|
+
framework (more precisely, by our superclass
|
767
|
+
XML::Mapping::SingleAttributeNode), and the default value is set if it
|
768
|
+
was provided. So you should just wrap +default_when_xpath_err+ around
|
769
|
+
any applications of XPath expressions whose non-presence in the XML
|
770
|
+
you want to be considered a non-presence of the attribute you're
|
302
771
|
trying to extract. (XML::XXPath is designed to know knothing about
|
303
772
|
XML::Mapping, so it doesn't raise
|
304
773
|
XML::Mapping::SingleAttributeNode::NoAttrValueSet directly)
|
305
774
|
|
306
775
|
The +set_attr_value+ method is called whenever an instance of the
|
307
|
-
class the node belongs to (+Signature+ in the example) is
|
308
|
-
into an XML tree. The _xml_ parameter is the XML tree (a
|
309
|
-
node; here this is again the tree rooted at the
|
310
|
-
element); _value_ is the current value of the
|
311
|
-
|
312
|
-
the
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
776
|
+
mapping class the node belongs to (+Signature+ in the example) is
|
777
|
+
being stored into an XML tree. The _xml_ parameter is the XML tree (a
|
778
|
+
REXML element node; here this is again the tree rooted at the
|
779
|
+
<tt><Signature></tt> element); _value_ is the current value of the
|
780
|
+
single attribute (in this example, the <tt>signed_on</tt> attribute of
|
781
|
+
the +Signature+ instance being stored). _xml_ will most probably be
|
782
|
+
"half-populated" by the time this method is called -- the framework
|
783
|
+
calls the +set_attr_value+ methods of all nodes of a mapping class in
|
784
|
+
the order of their definition, letting each node fill its "bit" into
|
785
|
+
_xml_. The method implementation is expected to write _value_ into
|
786
|
+
(the correct sub-elements of) _xml_, or raise an exception to signal
|
787
|
+
an error and abort the whole process. No default value handling is
|
788
|
+
done here; +set_attr_value+ won't be called at all if the attribute
|
789
|
+
had been set to its default value. In our implementation we grab the
|
790
|
+
year, month and day values from _value_ (which must be a +Time+), and
|
791
|
+
store it into the sub-elements of _xml_ identified by XPath
|
792
|
+
expressions <tt>@y_path</tt>, <tt>@m_path</tt> and <tt>@d_path</tt>,
|
793
|
+
respectively. We do this by calling XML::XXPath#first with an
|
794
|
+
additional parameter <tt>:ensure_created=>true</tt>. An expression
|
795
|
+
_xpath_expr_.first(_xml_,:ensure_created=>true) works just like
|
796
|
+
_xpath_expr_.first(_xml_) if _xpath_expr_ was already present in
|
797
|
+
_xml_. If it was not, it is created (preferably at the end of _xml_'s
|
798
|
+
list of sub-nodes), and returned. See below[aref:xpath] for a more
|
799
|
+
detailed documentation of the XPath interpreter.
|
329
800
|
|
330
801
|
=== Element order in created XML documents
|
331
802
|
|
332
|
-
As just said, XML::XXPath, when used to create new XML nodes,
|
333
|
-
appends those nodes to the end of the list of subnodes of
|
334
|
-
xpath expression was applied to. All xml-mapping nodes
|
335
|
-
xml-mapping use XML::XXPath when writing data to XML,
|
336
|
-
also append their data to the XML data written by
|
337
|
-
nodes are invoked in the order of their
|
338
|
-
generally, your output data will appear
|
339
|
-
same order in which the corresponding
|
340
|
-
appeared in the mapping class (unless you
|
341
|
-
foo[number] which explicitly dictate a
|
342
|
-
of XML nodes). For instance, in the
|
343
|
-
|
344
|
-
<tt>:
|
345
|
-
|
803
|
+
As just said, XML::XXPath, when used to create new XML nodes,
|
804
|
+
generally appends those nodes to the end of the list of subnodes of
|
805
|
+
the node the xpath expression was applied to. All xml-mapping nodes
|
806
|
+
that come with xml-mapping use XML::XXPath when writing data to XML,
|
807
|
+
and therefore also append their data to the XML data written by
|
808
|
+
preceding nodes (the nodes are invoked in the order of their
|
809
|
+
definition). This means that, generally, your output data will appear
|
810
|
+
in the XML document in the same order in which the corresponding
|
811
|
+
xml-mapping node definitions appeared in the mapping class (unless you
|
812
|
+
used XPath expressions like foo[number] which explicitly dictate a
|
813
|
+
fixed position in the sequence of XML nodes). For instance, in the
|
814
|
+
+Order+ class from the example at the beginning of this document, if
|
815
|
+
we put the <tt>:signatures</tt> node _before_ the <tt>:items</tt>
|
816
|
+
node, the <tt><Signed-By></tt> element will appear _before_ the
|
817
|
+
sequence of <tt><Item></tt> elements in the output XML.
|
818
|
+
|
819
|
+
|
820
|
+
The following is a more systematic overview of the basic node
|
821
|
+
types. The description is self-contained, so some information from the
|
822
|
+
previous section will be repeated.
|
823
|
+
|
824
|
+
=== Node Types Are Ruby Classes
|
825
|
+
|
826
|
+
A node type is implemented as a Ruby class derived from
|
827
|
+
XML::Mapping::Node or one of its subclasses.
|
828
|
+
|
829
|
+
The following node types (node classes) come with xml-mapping (they
|
830
|
+
all live in the XML::Mapping namespace, which I've left out here for
|
831
|
+
brevity):
|
832
|
+
|
833
|
+
Node
|
834
|
+
+-SingleAttributeNode
|
835
|
+
| +-SubObjectBaseNode
|
836
|
+
| | +-ObjectNode
|
837
|
+
| | +-ArrayNode
|
838
|
+
| | +-HashNode
|
839
|
+
| +-TextNode
|
840
|
+
| +-NumericNode
|
841
|
+
| +-BooleanNode
|
842
|
+
+-ChoiceNode
|
843
|
+
|
844
|
+
XML::Mapping::Node is the base class for all nodes,
|
845
|
+
XML::Mapping::SingleAttributeNode is the base class for
|
846
|
+
{single-attribute nodes}[aref:sanodes], and
|
847
|
+
XML::Mapping::SubObjectBaseNode is the base class for
|
848
|
+
{single-attribute nodes with
|
849
|
+
sub-objects}[aref:subobjnodes]. XML::Mapping::TextNode,
|
850
|
+
XML::Mapping::ArrayNode etc. are of course the +text_node+,
|
851
|
+
+array_node+ etc. we've talked about in this document. When you've
|
852
|
+
written a new node class, you register it with xml-mapping by calling
|
853
|
+
<tt>XML::Mapping.add_node_class MyNode</tt>. When you do that,
|
854
|
+
xml-mapping automatically defines the node factory method for your
|
855
|
+
class -- the method's name (e.g. +my_node+) is derived from the node's
|
856
|
+
class name (e.g. Foo::Bar::MyNode) by stripping all parent module
|
857
|
+
names, and then converting capital letters to lowercase and preceding
|
858
|
+
them with an underscore. In fact, this is just how all the predefined
|
859
|
+
node types are defined -- those node types are not "special"; they're
|
860
|
+
defined in the source file +xml/mapping/standard_nodes.rb+ and then
|
861
|
+
registered normally in +xml/mapping.rb+. The source code of the
|
862
|
+
built-in nodes is not very long or complicated; you may consider
|
863
|
+
reading it in addition to this text to gain a better understanding.
|
864
|
+
|
865
|
+
|
866
|
+
=== How Node Types Work
|
867
|
+
|
868
|
+
The xml-mapping core "operates" node types as follows:
|
869
|
+
|
870
|
+
|
871
|
+
==== Node Initialization
|
872
|
+
|
873
|
+
As said above, when a node class is registered with xml-mapping by
|
874
|
+
calling <tt>XML::Mapping.add_node_class TheNodeClass</tt>, xml-mapping
|
875
|
+
automatically generates the node factory method for that type. The
|
876
|
+
node factory method will effectively be defined as a class method of
|
877
|
+
the XML::Mapping module, which is why one can call it from the body of
|
878
|
+
a mapping class definition. The generated method will create a new
|
879
|
+
instance of the node class (a *node* for short) by calling _new_ on
|
880
|
+
the node class. The list of parameters to _new_ will consist of <i>the
|
881
|
+
mapping class, followed by all arguments that were passed to the node
|
882
|
+
factory method</i>. For example, when you have this node declaration:
|
883
|
+
|
884
|
+
class MyMappingClass
|
885
|
+
include XML::Mapping
|
886
|
+
|
887
|
+
my_node :foo, "bar", 42, :hi=>"ho"
|
888
|
+
end
|
889
|
+
|
890
|
+
, then the node factory method (+my_node+) calls
|
891
|
+
<tt>MyNode.new(MyMappingClass, :foo, "bar", 42, :hi=>"ho")</tt>.
|
346
892
|
|
893
|
+
_new_ of course creates the instance and calls _initialize_ on it. The
|
894
|
+
_initialize_ implementation will generally store the parameters into
|
895
|
+
some instance variables for later usage. As a convention, _initialize_
|
896
|
+
should always extract from the parameter list those parameters it
|
897
|
+
processes itself, process them, and return an array containing the
|
898
|
+
remaining (still unprocessed) parameters. Thus, an implementation of
|
899
|
+
_initialize_ follows this pattern:
|
900
|
+
|
901
|
+
def initialize(*args)
|
902
|
+
myparam1,myparam2,...,myparamx,*args = super(*args)
|
903
|
+
|
904
|
+
.... process the myparam1,myparam2,...,myparamx ....
|
905
|
+
|
906
|
+
# return still unprocessed args
|
907
|
+
args
|
908
|
+
end
|
909
|
+
|
910
|
+
(since the called superclass initializer is written the same way, the
|
911
|
+
parameter array returned by it will already be stripped of all
|
912
|
+
parameters that the superclass initializer (or any of its
|
913
|
+
superclasses's initializers) processed)
|
914
|
+
|
915
|
+
This technique is a simple way to "chain" the initializers of all
|
916
|
+
superclasses of a node class, starting with the topmost one (Node), so
|
917
|
+
that each initializer can easily find out and process the parameters
|
918
|
+
it is responsible for.
|
919
|
+
|
920
|
+
The base node class XML::Mapping::Node provides an _initialize_
|
921
|
+
implementation that, among other things (described below), adds _self_
|
922
|
+
(i.e. the created node) to the internal list of nodes held by the
|
923
|
+
mapping class, and sets the @owner attribute of _self_ to reference
|
924
|
+
the mapping class.
|
925
|
+
|
926
|
+
So, effectively there will be one instance of a node class (a node)
|
927
|
+
per node definition, and that instance lives in the mapping class the
|
928
|
+
node was defined in.
|
929
|
+
|
930
|
+
|
931
|
+
==== Node Operation during Marshalling and Unmarshalling
|
932
|
+
|
933
|
+
When an instance of a mapping class is created or filled from an XML
|
934
|
+
tree, xml-mapping will call +xml_to_obj+ on all nodes defined in that
|
935
|
+
mapping class in the {mapping}[aref:mappings] the node is defined in,
|
936
|
+
in the order of their definition. Two parameters will be passed: the
|
937
|
+
mapping class instance being created/filled, and the XML tree the
|
938
|
+
instance is being created/filled from. The implementation of
|
939
|
+
+xml_to_obj+ is expected to read whatever pieces of data it is
|
940
|
+
responsible for from the XML tree and put it into the appropriate
|
941
|
+
variables/attributes etc. of the instance.
|
942
|
+
|
943
|
+
When an instance of a mapping class is stored or filled into an XML
|
944
|
+
tree, xml-mapping will call +obj_to_xml+ on all nodes defined in that
|
945
|
+
mapping class in the {mapping}[aref:mappings] the node is defined in,
|
946
|
+
in the order of their definition, again passing as parameters the
|
947
|
+
mapping class instance being stored, and the XML tree the instance is
|
948
|
+
being stored/filled into. The implementation of +obj_to_xml+ is
|
949
|
+
expected to read whatever pieces of data it is responsible for from
|
950
|
+
the instance and put it into the appropriate XML elements/XML attr
|
951
|
+
etc. of the XML tree.
|
952
|
+
|
953
|
+
|
954
|
+
=== Basic Node Types Overview
|
955
|
+
|
956
|
+
The following is an overview of how initialization and
|
957
|
+
marshalling/unmarshalling is implemented in the node base classes
|
958
|
+
(Node, SingleAttributeNode, and SubObjectBaseNode).
|
959
|
+
|
960
|
+
TODO: summary table: member var name; introduced in class; meaning
|
961
|
+
|
962
|
+
==== Node
|
963
|
+
|
964
|
+
In _initialize_, the mapping class and the option arguments are
|
965
|
+
stripped from the argument list. The mapping class is stored in
|
966
|
+
@owner, the option arguments are stored (as a hash) in @options (the
|
967
|
+
hash will be empty if no options were given). The
|
968
|
+
{mapping}[aref:mappings] the node is defined in is determined
|
969
|
+
(:mapping option, last <tt>use_mapping</tt> or <tt>:_default</tt>) and
|
970
|
+
stored in @mapping. The node then stores itself in the list of nodes
|
971
|
+
of the mapping class belonging to the mapping
|
972
|
+
(<tt>@owner.xml_mapping_nodes(:mapping=>@mapping)</tt>; see
|
973
|
+
XML::Mapping::ClassMethods#xml_mapping_nodes). This list is the list
|
974
|
+
of nodes later used when marshalling/unmarshalling an instance of the
|
975
|
+
mapping class with respect to a given mapping. This means that node
|
976
|
+
implementors will not normally "see" anything of the mapping (they
|
977
|
+
don't need to access the @mapping variable) because the
|
978
|
+
marshalling/unmarshalling methods
|
979
|
+
(<tt>obj_to_xml</tt>/<tt>xml_to_obj</tt>) simply won't be called if
|
980
|
+
the node's mapping is not the same as the mapping the
|
981
|
+
marshalling/unmarshalling is happening with.
|
982
|
+
|
983
|
+
Furthermore, if :reader and/or :writer options were given,
|
984
|
+
<tt>xml_to_obj</tt> resp. <tt>obj_to_xml</tt> are transparently
|
985
|
+
overwritten on the node to delegate to the supplied :reader/:writer
|
986
|
+
procs.
|
987
|
+
|
988
|
+
The marshalling/unmarshalling methods
|
989
|
+
(<tt>obj_to_xml</tt>/<tt>xml_to_obj</tt>) are not implemented in
|
990
|
+
+Node+ (they just raise an exception).
|
991
|
+
|
992
|
+
|
993
|
+
==== SingleAttributeNode
|
994
|
+
|
995
|
+
In _initialize_, the attribute name is stripped from the argument list
|
996
|
+
and stored in @attrname, and an attribute of that name is added to the
|
997
|
+
mapping class the node belongs to.
|
998
|
+
|
999
|
+
During marshalling/unmarshalling of an object to/from XML,
|
1000
|
+
single-attribute nodes only read/write a single piece of the object's
|
1001
|
+
state: the single attribute (@attrname) the node handles. Because of
|
1002
|
+
this, the <tt>obj_to_xml</tt>/<tt>xml_to_obj</tt> implementations in
|
1003
|
+
SingleAttributeNode call two new methods introduced by
|
1004
|
+
SingleAttributeNode, which must be overwritten by subclasses:
|
1005
|
+
|
1006
|
+
extract_attr_value(xml)
|
1007
|
+
|
1008
|
+
set_attr_value(xml, value)
|
1009
|
+
|
1010
|
+
<tt>extract_attr_value(xml)</tt> is called by <tt>xml_to_obj</tt>
|
1011
|
+
during unmarshalling. _xml_ is the XML tree being read. The method
|
1012
|
+
must read the attribute's value from _xml_ and return
|
1013
|
+
it. <tt>xml_to_obj</tt> will set the attribute to that value.
|
1014
|
+
|
1015
|
+
<tt>set_attr_value(xml, value)</tt> is called by <tt>obj_to_xml</tt>
|
1016
|
+
during marshalling. _xml_ is the XML tree being written, _value_ is
|
1017
|
+
the current value of the attribute. The method must write _value_ into
|
1018
|
+
(the correct sub-elements/attributes) of _xml_.
|
1019
|
+
|
1020
|
+
SingleAttributeNode also handles the default value, if it was
|
1021
|
+
specified (via the :default_value option): When writing data to XML,
|
1022
|
+
<tt>set_attr_value(xml, value)</tt> won't be called if the attribute
|
1023
|
+
was set to the default value. When reading data from XML, the
|
1024
|
+
<tt>extract_attr_value(xml)</tt> implementation must raise a special
|
1025
|
+
exception, XML::Mapping::SingleAttributeNode::NoAttrValueSet, if it
|
1026
|
+
wants to indicate that the data was not present in the
|
1027
|
+
XML. SingleAttributeNode will catch this exception and put the default
|
1028
|
+
value, if it was defined, into the attribute.
|
1029
|
+
|
1030
|
+
|
1031
|
+
==== SubObjectBaseNode
|
1032
|
+
|
1033
|
+
The initializer will set up additional member variables @sub_mapping,
|
1034
|
+
@marshaller, and @unmarshaller.
|
1035
|
+
|
1036
|
+
@sub_mapping contains the mapping to be used when reading/writing the
|
1037
|
+
sub-objects (either specified with :sub_mapping, or, by default, the
|
1038
|
+
mapping the node itself was defined in).
|
1039
|
+
|
1040
|
+
@marshaller and @unmarshaller contain procs that encapsulate
|
1041
|
+
writing/reading of sub-objects to/from XML, as specified by the user
|
1042
|
+
with :class/:marshaller/:unmarshaller etc. options (the meaning of
|
1043
|
+
those different options was described {above}[aref:subobjnodes]). The
|
1044
|
+
procs are there to be called from <tt>extract_attr_value</tt> or
|
1045
|
+
<tt>set_attr_value</tt> whenever the need arises.
|
347
1046
|
|
348
1047
|
|
349
|
-
== XPath
|
1048
|
+
== {XPath Interpreter}[a:xpath]
|
350
1049
|
|
351
1050
|
XML::XXPath is an XPath parser. It is used in xml-mapping node type
|
352
|
-
definitions, but can just as well be utilized stand-alone (it does
|
353
|
-
|
354
|
-
will always be
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
supports write access. For example, if you create the path
|
359
|
-
"/foo/bar[3]/baz[@key='hiho']" in the XML document
|
1051
|
+
definitions, but can just as well be utilized stand-alone (it does not
|
1052
|
+
depend on xml-mapping). XML::XXPath is very incomplete and probably
|
1053
|
+
will always be, but it should be reasonably efficient (XPath
|
1054
|
+
expressions are precompiled), and, most importantly, it supports write
|
1055
|
+
access, which is needed for writing objects to XML. For example, if
|
1056
|
+
you create the path "/foo/bar[3]/baz[@key='hiho']" in the XML document
|
360
1057
|
|
361
1058
|
<foo>
|
362
1059
|
<bar>
|
@@ -378,7 +1075,8 @@ supports write access. For example, if you create the path
|
|
378
1075
|
</bar>
|
379
1076
|
</foo>
|
380
1077
|
|
381
|
-
XML::XXPath is explained in more detail in the reference documentation
|
1078
|
+
XML::XXPath is explained in more detail in the reference documentation
|
1079
|
+
and the README_XPATH file.
|
382
1080
|
|
383
1081
|
|
384
1082
|
== License
|