xml-mapping 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/LICENSE +56 -0
  2. data/README +386 -0
  3. data/README_XPATH +175 -0
  4. data/Rakefile +214 -0
  5. data/TODO.txt +32 -0
  6. data/doc/xpath_impl_notes.txt +119 -0
  7. data/examples/company.rb +34 -0
  8. data/examples/company.xml +26 -0
  9. data/examples/company_usage.intin.rb +19 -0
  10. data/examples/company_usage.intout +39 -0
  11. data/examples/order.rb +61 -0
  12. data/examples/order.xml +54 -0
  13. data/examples/order_signature_enhanced.rb +7 -0
  14. data/examples/order_signature_enhanced.xml +9 -0
  15. data/examples/order_signature_enhanced_usage.intin.rb +12 -0
  16. data/examples/order_signature_enhanced_usage.intout +16 -0
  17. data/examples/order_usage.intin.rb +73 -0
  18. data/examples/order_usage.intout +147 -0
  19. data/examples/time_augm.intin.rb +19 -0
  20. data/examples/time_augm.intout +23 -0
  21. data/examples/time_node.rb +27 -0
  22. data/examples/xpath_create_new.intin.rb +85 -0
  23. data/examples/xpath_create_new.intout +181 -0
  24. data/examples/xpath_docvsroot.intin.rb +30 -0
  25. data/examples/xpath_docvsroot.intout +34 -0
  26. data/examples/xpath_ensure_created.intin.rb +62 -0
  27. data/examples/xpath_ensure_created.intout +114 -0
  28. data/examples/xpath_pathological.intin.rb +42 -0
  29. data/examples/xpath_pathological.intout +56 -0
  30. data/examples/xpath_usage.intin.rb +51 -0
  31. data/examples/xpath_usage.intout +57 -0
  32. data/install.rb +40 -0
  33. data/lib/xml/mapping.rb +14 -0
  34. data/lib/xml/mapping/base.rb +563 -0
  35. data/lib/xml/mapping/standard_nodes.rb +343 -0
  36. data/lib/xml/mapping/version.rb +8 -0
  37. data/lib/xml/xxpath.rb +354 -0
  38. data/test/all_tests.rb +6 -0
  39. data/test/company.rb +54 -0
  40. data/test/documents_folders.rb +33 -0
  41. data/test/fixtures/bookmarks1.xml +24 -0
  42. data/test/fixtures/company1.xml +85 -0
  43. data/test/fixtures/documents_folders.xml +71 -0
  44. data/test/fixtures/documents_folders2.xml +30 -0
  45. data/test/multiple_mappings.rb +80 -0
  46. data/test/tests_init.rb +2 -0
  47. data/test/xml_mapping_adv_test.rb +84 -0
  48. data/test/xml_mapping_test.rb +182 -0
  49. data/test/xpath_test.rb +273 -0
  50. metadata +96 -0
data/LICENSE ADDED
@@ -0,0 +1,56 @@
1
+ Xml-mapping is copyrighted free software by Olaf Klischat
2
+ <klischat@cs.tu-berlin.de>. You can redistribute it and/or modify it
3
+ under either the terms of the GPL, or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a) distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/README ADDED
@@ -0,0 +1,386 @@
1
+ = XML-MAPPING: XML-to-object (and back) mapper for Ruby, including XPath interpreter
2
+
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. It is
5
+ easy to use and has a modular design that allows for easy extension of
6
+ its functionality.
7
+
8
+ == Download
9
+
10
+ For downloading the latest version, CVS repository access etc. go to:
11
+
12
+ http://rubyforge.org/projects/xml-mapping/
13
+
14
+ == Example
15
+
16
+ (example document stolen + extended from
17
+ http://www.castor.org/xml-mapping.html)
18
+
19
+ === Input document:
20
+
21
+ :include: order.xml
22
+
23
+ === Mapping class declaration:
24
+
25
+ :include: order.rb
26
+
27
+ === Usage:
28
+
29
+ :include: order_usage.intout
30
+
31
+
32
+ == Description
33
+
34
+ As shown in the example, you have to include XML::Mapping into a class
35
+ to turn it into a "mapping class". There are no other restrictions
36
+ imposed on mapping classes; you can add attributes and methods to
37
+ them, include additional modules in them, derive them from other
38
+ classes, derive other classes from them etc.pp.
39
+
40
+ An instance of a mapping class can be created from/converted into an
41
+ XML node by means of instance methods like XML::Mapping.load_from_xml,
42
+ XML::Mapping#save_to_xml, XML::Mapping.load_from_file,
43
+ XML::Mapping#save_to_file. Special class methods like "text_node",
44
+ "array_node" etc., called "node factory methods", may be called from
45
+ the body of the class definition to define instance attributes that
46
+ are automatically and bidirectionally mapped to subtrees of the XML
47
+ element an instance of the class is mapped to. For example, in the
48
+ definition
49
+
50
+ class Address
51
+ include XML::Mapping
52
+
53
+ text_node :city, "City"
54
+ text_node :state, "State"
55
+ numeric_node :zip, "ZIP"
56
+ text_node :street, "Street"
57
+ end
58
+
59
+ the first call to #text_node creates an attribute named "city" which
60
+ is mapped to the text of the XML child element defined by the XPath
61
+ expression "City" (xml-mapping includes an XPath interpreter that can
62
+ also be used seperately; see below). When you create an instance of
63
+ +Address+ from an XML element (using Address.load_from_file(file_name)
64
+ or Address.load_from_xml(rexml_element)), that instance's "city"
65
+ 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 element,
67
+ a sub-element "City" is added and it text is set to the current value
68
+ of the +city+ attribute. The other node types (numeric_node,
69
+ array_node etc.) work analogously. The node types +object_node+,
70
+ +array_node+, and +hash_node+ recursively map sub-trees to instances
71
+ of mapping classes (as opposed to simple types like String
72
+ etc.). For example, with the line
73
+
74
+ array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
75
+
76
+ , an attribute named "signatures" is added to the surrounding class
77
+ (here: Order); the attribute will be an array whose elements
78
+ correspond to the XML elements yielded by the XPath
79
+ "Signed-By/Signature". Each element will be of class +Signature+ (each
80
+ array element is created from the corresponding XML element by just
81
+ calling <tt>Signature.load_from_xml(the_xml_element)</tt>). The reason
82
+ why the path "Signed-By/Signature" is provieded in two arguments
83
+ instead of just one combined one becomes apparent when marshalling the
84
+ array (along with the surrounding object) back into a sequence of XML
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.
101
+
102
+ From the example:
103
+
104
+ class Signature
105
+ include XML::Mapping
106
+
107
+ text_node :position, "Position", :default_value=>"Some Employee"
108
+ end
109
+
110
+ The semantics of default values are as follows:
111
+
112
+ - when creating a new instance from scratch:
113
+
114
+ - attributes with default values are set to their default values
115
+
116
+ - attributes without default values are left unset
117
+
118
+ (when defining your own initializer, you'll have to call the
119
+ inherited _initialize_ method in order to get this behaviour)
120
+
121
+ - when loading:
122
+
123
+ - attributes without default values that are not represented in the
124
+ XML raise an error
125
+
126
+ - attributes with default values that are not represented in the XML
127
+ are set to their default values
128
+
129
+ - all other attributes are set to their respective values as present
130
+ in the XML
131
+
132
+
133
+ - when saving:
134
+
135
+ - unset attributes without default values raise an error
136
+
137
+ - attributes with default values that are set to their default
138
+ values are not saved
139
+
140
+ - all other attributes are saved
141
+
142
+
143
+ This implies that:
144
+
145
+ - attributes that are set to their respective default values are not
146
+ represented in the XML
147
+
148
+ - attributes without default values must be set explicitly before
149
+ saving
150
+
151
+
152
+
153
+ === Attribute handling details, augmenting existing classes
154
+
155
+ I'll shed some more light on how xml-mapping adds mapped attributes to
156
+ Ruby classes. An attribute declaration like
157
+
158
+ text_node :city, "City"
159
+
160
+ maps some portion of the XML tree (here: the "City" sub-element) to an
161
+ attribute (here: "city") of the class whose body the declaration
162
+ appears in. When writing (marshalling) instances of the surrounding
163
+ class into an XML document, xml-mapping will read the attribute value
164
+ from the instance using the function named +city+; when reading
165
+ (unmarshalling) an instance from an XML document, xml-mapping will use
166
+ the one-parameter function <tt>city=</tt> to set the attribute in the
167
+ instance to the value read from the XML document.
168
+
169
+ If these functions don't exist at the time the node declaration is
170
+ executed, xml-mapping adds default implementations that simply
171
+ read/write the attribute value to instance variables that have the
172
+ same name as the attribute. For example, the +city+ attribute
173
+ declaration in the +Address+ class in the example added functions
174
+ +city+ and <tt>city=</tt> that read/write from/to the instance
175
+ variable <tt>@city</tt>.
176
+
177
+ If, however, these functions already exist prior to defining the
178
+ attributes, xml-mapping will leave them untouched, so your precious
179
+ self-written accessor methods that do whatever complicated internal
180
+ processing of the data won't be overwritten.
181
+
182
+ This means that you can not only create new mapping classes from
183
+ scratch, you can also take existing classes that contain some
184
+ "business logic" and "augment" them with xml-mapping capabilities. As
185
+ a simple example, let's augment Ruby's "Time" class with node
186
+ declarations that declare XML mappings for the day, month etc. fields:
187
+
188
+ :include: time_augm.intout
189
+
190
+ Here XML mappings are defined for the existing fields +year+, +month+
191
+ etc. Xml-apping noticed that the getter methods for those attributes
192
+ existed, so it didn't overwrite them. When calling +save_to_xml+ on a
193
+ +Time+ object, these methods are called and return the object's values
194
+ for those fields, which then get written to the output XML. Of course
195
+ you could also derive a new class from a pre-existing one and
196
+ implement the XML::Mapping stuff there, or even derive several such
197
+ classes in order to define more than one XML mapping for one existing
198
+ class.
199
+
200
+ It should be mentioned that in the +Time+ example above, the setter
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.
208
+
209
+
210
+ === Defining your own node types
211
+
212
+ It's easy to write additional node types and register them with the
213
+ xml-mapping library. Let's say we want to extend the +Signature+ class
214
+ from the example to include the time at which the signature was
215
+ created. We want the new XML representation of such a signature to
216
+ look like this:
217
+
218
+ :include: order_signature_enhanced.xml
219
+
220
+ (we only save year, month and day to make this example shorter), and
221
+ the mapping class declaration to look like this:
222
+
223
+ :include: order_signature_enhanced.rb
224
+
225
+ (i.e. a new "time_node" declaration was added).
226
+
227
+ We want this +signed_on+ call to define an attribute named +signed_on+
228
+ which holds the date value from the XML in an instance of class
229
+ +Time+.
230
+
231
+ This node type can be defined with this piece of code:
232
+
233
+ :include: time_node.rb
234
+
235
+ The last line registers the new node type with the xml-mapping
236
+ library. The name of the node factory method ("time_node") is
237
+ automatically derived from the class name of the node type
238
+ ("TimeNode").
239
+
240
+ There will be one instance of the node type per mapping class (not per
241
+ mapping class instance). That instance will be created by the node
242
+ factory method (+time_node+); there's no need to instantiate the node
243
+ type directly. Whenever an instance of the mapping class needs to be
244
+ marshalled/unmarshalled to/from XML, +set_attr_value+
245
+ resp. +extract_attr_value+ will be called on the node type instance
246
+ ("node" for short). The node factory method places the node into the
247
+ mapping class; the @owner attribute of the node is set to reference
248
+ the mapping class. The node factory method passes its arguments (in
249
+ the example, that would be <tt>:signed_on, "signed-on",
250
+ :default_value=>Time.now</tt>) to the node's initializer. TimeNode's
251
+ parent class XML::Mapping::SingleAttributeNode already handles the
252
+ <tt>:signed_on</tt> and <tt>:default_value=>Time.now</tt> arguments --
253
+ <tt>:signed_on</tt> is stored into <tt>@attrname</tt>, and the default
254
+ value declarations will be described in a moment. The remaining
255
+ argument <tt>"signed-on"</tt> gets passed to our +initialize_impl+
256
+ method as parameter _path_. We'll interpret it as an XPath expression
257
+ that locates the time value relative to the parent mapping object's
258
+ XML tree (in this case, this would be the XML tree rooted at the
259
+ +<Signature>+ element, i.e. the tree the +Signature+ instance was read
260
+ from). We'll later have to read/store the year, month, and day values
261
+ from <tt>path+"/year"</tt>, <tt>path+"/month"</tt>, and
262
+ <tt>path+"/day"</tt>, respectively, so we create (and precompile)
263
+ three corresponding XPath expressions using XML::XXPath.new and store
264
+ them into member variables of the node. XML::XXPath is an XPath
265
+ implementation that is bundled with xml-mapping. It is very
266
+ incomplete, but it supports writing (not just reading) of XML nodes,
267
+ which is needed to support writing data back to XML. The XML::XXPath
268
+ library is explained in more detail below.
269
+
270
+ The +extract_attr_value+ method is called whenever an instance of the
271
+ class the node belongs to (+Signature+ in the example) is being
272
+ created from an XML tree. The parameter _xml_ is that tree (again,
273
+ this is the tree rooted at the +<Signature>+ element in this
274
+ example). The method implementation is expected to extract the
275
+ attribute's value from _xml_ and return it, or raise
276
+ XML::Mapping::SingleAttributeNode::NoAttrValueSet if the attribute was
277
+ "unset" in the XML (so the default value should be put in place if it
278
+ was defined), or raise any other exception to signal an error and
279
+ abort the whole process. In our implementation, we apply the xpath
280
+ expressions created at initialization to _xml_
281
+ (e.g. <tt>@y_path.first(xml)</tt>). An expression
282
+ _xpath_expr_.first(_xml_) returns (as a REXML element) the first
283
+ sub-element of _xml_ that matches _xpath_expr_, or raises
284
+ XML::XXPathError if there was no such element. We apply REXML's _text_
285
+ method to the returned element to get out the element's text, convert
286
+ it to integer, and supply it to the constructor of the +Time+ object
287
+ to be returned. (as a side note, if an XPath expression matches XML
288
+ attributes, XML::XXPath methods like _first_ will return "Attribute"
289
+ nodes that behave similarly to REXML::Element nodes, including
290
+ messages like _name_ and _text_ (XML::XXPath extends REXML to support
291
+ this because REXML's Attribute class is too incompatible), so this
292
+ would've worked also if our XPath expressions named XML attributes,
293
+ not elements). The +default_when_xpath_err+ thing calls the supplied
294
+ block and returns its value, but maps the exception XML::XXPathError to
295
+ the mentioned XML::Mapping::SingleAttributeNode::NoAttrValueSet (any
296
+ other exceptions fall through unchanged). As said above,
297
+ XML::Mapping::NoAttrValueSet is then caught by our superclass
298
+ (XML::Mapping::SingleAttributeNode), and the default value is set if
299
+ it was provided. So you should just wrap +default_when_xpath_err+
300
+ around any applications of XPath expressions whose non-presence in the
301
+ XML you want to be considered a non-presence of the attribute you're
302
+ trying to extract. (XML::XXPath is designed to know knothing about
303
+ XML::Mapping, so it doesn't raise
304
+ XML::Mapping::SingleAttributeNode::NoAttrValueSet directly)
305
+
306
+ The +set_attr_value+ method is called whenever an instance of the
307
+ class the node belongs to (+Signature+ in the example) is being stored
308
+ into an XML tree. The _xml_ parameter is the XML tree (a REXML element
309
+ node; here this is again the tree rooted at the +<Signature>+
310
+ element); _value_ is the current value of the attribute. _xml_ will
311
+ most probably be "half-populated" by the time this method is called --
312
+ the framework calls the +set_attr_value+ methods of all nodes of a
313
+ mapping class in the order of their definition, letting each node fill
314
+ its "bit" into _xml_. The method implementation is expected to write
315
+ _value_ into (the correct sub-elements of) _xml_, or raise an
316
+ exception to signal an error and abort the whole process. No default
317
+ value handling is done here; +set_attr_value+ won't be called at all
318
+ if the attribute had been set to its default value. In our
319
+ implementation we grab the year, month and day values from _value_
320
+ (which must be a +Time+), and store it into the sub-elements of _xml_
321
+ identified by XPath expressions <tt>@y_path</tt>, <tt>@m_path</tt> and
322
+ <tt>@d_path</tt>, respectively. We do this by calling XML::XXPath#first
323
+ with an additional parameter <tt>:ensure_created=>true</tt>. An
324
+ expression _xpath_expr_.first(_xml_,:ensure_created=>true) works just
325
+ like _xpath_expr_.first(_xml_) if _xpath_expr_ was already present in
326
+ _xml_. If it was not, it is created (preferable at the end of _xml_'s
327
+ list of sub-nodes), and returned. See below for a more detailed
328
+ documentation of the XPath interpreter.
329
+
330
+ === Element order in created XML documents
331
+
332
+ As just said, XML::XXPath, when used to create new XML nodes, generally
333
+ appends those nodes to the end of the list of subnodes of the node the
334
+ xpath expression was applied to. All xml-mapping nodes that come with
335
+ xml-mapping use XML::XXPath when writing data to XML, and therefore
336
+ also append their data to the XML data written by preceding nodes (the
337
+ nodes are invoked in the order of their definition). This means that,
338
+ generally, your output data will appear in the XML document in the
339
+ same order in which the corresponding xml-mapping node definitions
340
+ appeared in the mapping class (unless you used XPath expressions like
341
+ foo[number] which explicitly dictate a fixed position in the sequence
342
+ of XML nodes). For instance, in the example from the beginning of this
343
+ document, if we put the <tt>:signatures</tt> node _before_ the
344
+ <tt>:items</tt> node, the <tt><Signed-By></tt> element will appear
345
+ _before_ the sequence of <tt><Item></tt> elements in the output XML.
346
+
347
+
348
+
349
+ == XPath interpreter
350
+
351
+ 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
+ not depend on xml-mapping). XML::XXPath is very incomplete and probably
354
+ will always be (it only supports path elements of types _elt_name_,
355
+ @_attr_name_, _elt_name_[@_attr_name_=_attr_value_],
356
+ _elt_name_[_index_], and *), but it should be reasonably efficient
357
+ (XPath expressions are precompiled), and, most importantly, it
358
+ supports write access. For example, if you create the path
359
+ "/foo/bar[3]/baz[@key='hiho']" in the XML document
360
+
361
+ <foo>
362
+ <bar>
363
+ <baz key="ab">hello</baz>
364
+ <baz key="xy">goodbye</baz>
365
+ </bar>
366
+ </foo>
367
+
368
+ , you'll get:
369
+
370
+ <foo>
371
+ <bar>
372
+ <baz key='ab'>hello</baz>
373
+ <baz key='xy'>goodbye</baz>
374
+ </bar>
375
+ <bar/>
376
+ <bar>
377
+ <baz key='hiho'/>
378
+ </bar>
379
+ </foo>
380
+
381
+ XML::XXPath is explained in more detail in the reference documentation.
382
+
383
+
384
+ == License
385
+
386
+ Ruby's.
@@ -0,0 +1,175 @@
1
+ = XML-XXPATH
2
+
3
+ == Overview, Motivation
4
+
5
+ Xml-xxpath is an (incomplete) XPath interpreter that is at the moment
6
+ bundled with xml-mapping. It is built on top of REXML. xml-mapping
7
+ uses xml-xxpath extensively for implementing its node types -- see the
8
+ README file and the reference documentation (and the source code) for
9
+ details. xml-xxpath, however, does not depend on xml-mapping at all,
10
+ and is useful in its own right -- maybe I'll later distribute it as a
11
+ seperate library instead of bundling it. xml-xxpath's XPath support is
12
+ vastly incomplete (see below), but, in addition to the normal
13
+ reading/matching functionality found in other XPath implementations
14
+ (i.e. "find all elements in a given XML document matching a given
15
+ XPath expression"), xml-xxpath supports <i>write access</i>. For
16
+ example, when writing the XPath expression
17
+ "/foo/bar[3]/baz[@key='hiho']" to the XML document
18
+
19
+ <foo>
20
+ <bar>
21
+ <baz key='ab'>hello</baz>
22
+ <baz key='xy'>goodbye</baz>
23
+ </bar>
24
+ </foo>
25
+
26
+ , you'll get:
27
+
28
+ <foo>
29
+ <bar>
30
+ <baz key='ab'>hello</baz>
31
+ <baz key='xy'>goodbye</baz>
32
+ </bar>
33
+ <bar/>
34
+ <bar><baz key='hiho'/></bar>
35
+ </foo>
36
+
37
+ This feature is used by xml-mapping when writing (marshalling) Ruby
38
+ objects to XML, and is actually the reason why I couldn't just use any
39
+ of the existing XPath implementations, e.g. the one that comes with
40
+ REXML. Also, the whole xml-xxpath implementation is just 300 lines of
41
+ Ruby code, it is quite fast (paths are precompiled), and xml-xxpath
42
+ returns matched elements in the order they appeared in the source
43
+ document -- I've heard REXML::XXPath doesn't do that :)
44
+
45
+ Some basic knowledge of XPath is helpful for reading this document (I
46
+ don't know very much either).
47
+
48
+ At the moment, xml-xxpath understands XPath expressions of the form
49
+ [<tt>/</tt>]_pathelement_<tt>/</tt>_pathelement_<tt>/</tt>..., where
50
+ each _pathelement_ must be one of these:
51
+
52
+ - a simple element name _name_, e.g. +signature+
53
+
54
+ - an attribute name, @_attr_name_, e.g. <tt>@key</tt>
55
+
56
+ - a combination of an element name and an attribute name and
57
+ -value, in the form _elt_name_[@_attr_name_='_attr_value_']
58
+
59
+ - an element name and an index, _elt_name_[_index_]
60
+
61
+ - the "match-all" path element, <tt>*</tt>
62
+
63
+
64
+ == Usage
65
+
66
+ Xml-xxpath defines the class XML::XXPath. An instance of that class
67
+ wraps an XPath expression, the string representation of which must be
68
+ supplied when constructing the instance. You then call instance
69
+ methods like _first_, _all_ or <i>create_new</i> on the instance,
70
+ supplying the REXML Element the XPath expression should be applied to,
71
+ and get the results, or, in the case of write access, the element is
72
+ updated in-place.
73
+
74
+
75
+ === Read Access
76
+
77
+ :include: xpath_usage.intout
78
+
79
+ The objects supplied to the <tt>all()</tt>, <tt>first()</tt>, and
80
+ <tt>each()</tt> calls must be REXML element nodes, i.e. they must
81
+ support messages like <tt>elements</tt>, <tt>attributes</tt> etc
82
+ (instances of REXML::Element and its subclasses do this). The calls
83
+ return the found elements as instances of REXML::Element or
84
+ XML::XXPath::Accessors::Attribute. The latter is a wrapper around
85
+ attribute nodes that is largely call-compatible to
86
+ REXML::Element. This is so you can write things like
87
+ <tt>path.each{|node|puts node.text}</tt> without having to
88
+ special-case anything even if the path matches attributes, not just
89
+ elements.
90
+
91
+ As you can see, you can re-use path objects, applying them to
92
+ different XML elements at will. You should do this because the XPath
93
+ pattern is stored inside the XPath object in a pre-compiled form,
94
+ which makes it more efficient.
95
+
96
+ The path elements of the XPath pattern are applied to the
97
+ <tt>.elements</tt> collection of the passed XML element and its
98
+ sub-elements, starting with the first one. This is shown by the
99
+ following code:
100
+
101
+ :include: xpath_docvsroot.intout
102
+
103
+ A REXML +Document+ object is a REXML +Element+ object whose +elements+
104
+ collection consists only of a single member -- the document's root
105
+ node. The first path element of the XPath -- "foo" in the example --
106
+ is matched against that. That is why the path "/bar" in the example
107
+ doesn't match anything when matched against the document +d+ itself.
108
+
109
+ An ordinary REXML +Element+ object that represents a node somewhere
110
+ inside an XML tree has an +elements+ collection that consists of all
111
+ the element's direct sub-elements. That is why XPath patterns matched
112
+ against the +firstelt+ element in the example *must not* start with
113
+ "/first" (unless there is a child node that is also named "first").
114
+
115
+
116
+ === Write Access
117
+
118
+ You may pass a <tt>:ensure_created=>true</tt> option argument to
119
+ _path_.first(_elt_)/_path_.all(_elt_) calls to make sure that _path_
120
+ exists inside the passed XML element _elt_. If it existed before,
121
+ nothing changes, and the call behaves just as it would without the
122
+ option argument. If the path didn't exist before, the XML element is
123
+ modified such that
124
+
125
+ - the path exists afterwards
126
+
127
+ - all paths that existed before still exist afterwards
128
+
129
+ - the modification is as small as possible (i.e. as few elements as
130
+ possible are added, additional attributes are added to existing
131
+ elements if possible etc.)
132
+
133
+ The created resp. previously existing, matching elements are returned.
134
+
135
+
136
+ Examples:
137
+
138
+ :include: xpath_ensure_created.intout
139
+
140
+
141
+ Alternatively, you may pass a <tt>:create_new=>true</tt> option
142
+ argument or call <tt>create_new</tt> (_path_.create_new(_elt_) is
143
+ equivalent to _path_.first(_elt_,:create_new=>true)). In that case, a
144
+ new node in created in _elt_ for each path element of _path_ (or an
145
+ exception raised if that wasn't possible for any path element).
146
+
147
+ Examples:
148
+
149
+ :include: xpath_create_new.intout
150
+
151
+
152
+ === Pathological Cases
153
+
154
+ What is created when the Path "*" is to be created inside an empty XML
155
+ element? The name of the element to be created isn't known, but still
156
+ some element must be created. The answer is that xml-xxpath creates a
157
+ special "unspecified" element whose name must be set by the caller
158
+ afterwards:
159
+
160
+ :include: xpath_pathological.intout
161
+
162
+ The "newelt" object in the last example is an ordinary
163
+ REXML::Element. xml-xxpath mixes the "unspecified" attribute into that
164
+ class, as well as into the XML::XXPath::Accessors::Attribute class
165
+ mentioned above.
166
+
167
+
168
+ == Implentation notes
169
+
170
+ <tt>doc/xpath_impl_notes.txt</tt> contains some documentation on the
171
+ implementation of xml-xxpath.
172
+
173
+ == License
174
+
175
+ Ruby's.