xml-mapping 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +10 -53
  3. data/README.md +57 -0
  4. data/Rakefile +75 -85
  5. data/TODO.txt +12 -2
  6. data/examples/README +4 -4
  7. data/examples/cleanup.rb +11 -0
  8. data/examples/company_usage.intout +7 -7
  9. data/examples/documents_folders_usage.intout +2 -2
  10. data/examples/order_signature_enhanced_usage.intout +3 -3
  11. data/examples/order_usage.intout +26 -26
  12. data/examples/person.intout +3 -3
  13. data/examples/person_mm.intout +2 -2
  14. data/examples/publication.intout +2 -2
  15. data/examples/reader.intout +1 -1
  16. data/examples/stringarray_usage.intout +1 -1
  17. data/examples/time_augm.intout +6 -6
  18. data/examples/xpath_create_new.intout +13 -13
  19. data/examples/xpath_ensure_created.intout +2 -2
  20. data/examples/xpath_usage.intout +1 -1
  21. data/lib/xml/mapping.rb +0 -2
  22. data/lib/xml/mapping/base.rb +24 -9
  23. data/lib/xml/mapping/version.rb +1 -1
  24. data/test/company.rb +43 -0
  25. data/test/documents_folders.rb +7 -0
  26. data/test/multiple_mappings_test.rb +3 -1
  27. data/test/xml_mapping_adv_test.rb +20 -20
  28. data/test/xml_mapping_test.rb +31 -2
  29. data/{README → user_manual.md} +916 -154
  30. data/user_manual_xxpath.md +677 -0
  31. metadata +100 -112
  32. data/ChangeLog +0 -189
  33. data/README_XPATH +0 -202
  34. data/examples/company_usage.intin.rb +0 -19
  35. data/examples/documents_folders_usage.intin.rb +0 -18
  36. data/examples/order_signature_enhanced_usage.intin.rb +0 -12
  37. data/examples/order_usage.intin.rb +0 -120
  38. data/examples/person.intin.rb +0 -44
  39. data/examples/person_mm.intin.rb +0 -119
  40. data/examples/publication.intin.rb +0 -44
  41. data/examples/reader.intin.rb +0 -33
  42. data/examples/stringarray_usage.intin.rb +0 -11
  43. data/examples/time_augm.intin.rb +0 -19
  44. data/examples/time_augm_loading.intin.rb +0 -44
  45. data/examples/time_node.intin.rb +0 -79
  46. data/examples/time_node_w_marshallers.intin.rb +0 -48
  47. data/examples/xpath_create_new.intin.rb +0 -85
  48. data/examples/xpath_docvsroot.intin.rb +0 -30
  49. data/examples/xpath_ensure_created.intin.rb +0 -62
  50. data/examples/xpath_pathological.intin.rb +0 -42
  51. data/examples/xpath_usage.intin.rb +0 -51
  52. data/install.rb +0 -41
  53. data/test/xxpath_benchmark.result1.txt +0 -17
@@ -0,0 +1,677 @@
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. For the time being, if you
12
+ want to use this XPath implementation stand-alone, you can just rip
13
+ the files `lib/xml/xxpath.rb`, `lib/xml/xxpath/steps.rb`, and
14
+ `lib/xml/xxpath_methods.rb` out of the xml-mapping distribution and
15
+ use them on their own (they do not depend on anything else).
16
+
17
+ xml-xxpath's XPath support is vastly incomplete (see below), but, in
18
+ addition to the normal reading/matching functionality found in other
19
+ XPath implementations (i.e. "find all elements in a given XML document
20
+ matching a given XPath expression"), xml-xxpath supports <i>write
21
+ access</i>. For example, when writing the XPath expression
22
+ `/foo/bar[3]/baz[@key='hiho']` to the XML document
23
+
24
+ <foo>
25
+ <bar>
26
+ <baz key='ab'>hello</baz>
27
+ <baz key='xy'>goodbye</baz>
28
+ </bar>
29
+ </foo>
30
+
31
+ , you'll get:
32
+
33
+ <foo>
34
+ <bar>
35
+ <baz key='ab'>hello</baz>
36
+ <baz key='xy'>goodbye</baz>
37
+ </bar>
38
+ <bar/>
39
+ <bar><baz key='hiho'/></bar>
40
+ </foo>
41
+
42
+ This feature is used by xml-mapping when writing (marshalling) Ruby
43
+ objects to XML, and is actually the reason why I couldn't just use any
44
+ of the existing XPath implementations, e.g. the one that comes with
45
+ REXML. Also, the whole xml-xxpath implementation is just 300 lines of
46
+ Ruby code, it is quite fast (paths are precompiled), and xml-xxpath
47
+ returns matched elements in the order they appeared in the source
48
+ document -- I've heard REXML::XPath doesn't do that :)
49
+
50
+ Some basic knowledge of XPath is helpful for reading this document.
51
+
52
+ At the moment, xml-xxpath understands XPath expressions of the form
53
+ [`/`]_pathelement_`/[/]`_pathelement_`/[/]`...,
54
+ where each _pathelement_ must be one of these:
55
+
56
+ - a simple element name _name_, e.g. `signature`
57
+
58
+ - an attribute name, @_attrname_, e.g. `@key`
59
+
60
+ - a combination of an element name and an attribute name and
61
+ -value, in the form `elt_name[@attr_name='attr_value']`
62
+
63
+ - an element name and an index, `elt_name[index]`
64
+
65
+ - the "match-all" path element, `*`
66
+
67
+ - .
68
+
69
+ - name1`|`name2`|`...
70
+
71
+ - `.[@key='xy'] / self::*[@key='xy']`
72
+
73
+ - `child::*[@key='xy']`
74
+
75
+ - `text()`
76
+
77
+
78
+
79
+ Xml-xxpath only supports relative paths at this time, i.e. XPath
80
+ expressions beginning with "/" or "//" will still only find nodes
81
+ below the node the expression is applied to (as if you had written
82
+ "./" or ".//", respectively).
83
+
84
+
85
+ ## Usage
86
+
87
+ Xml-xxpath defines the class XML::XXPath. An instance of that class
88
+ wraps an XPath expression, the string representation of which must be
89
+ supplied when constructing the instance. You then call instance
90
+ methods like _first_, _all_ or <i>create_new</i> on the instance,
91
+ supplying the REXML Element the XPath expression should be applied to,
92
+ and get the results, or, in the case of write access, the element is
93
+ updated in-place.
94
+
95
+
96
+ ### Read Access
97
+
98
+ require 'xml/xxpath'
99
+
100
+ d=REXML::Document.new <<EOS
101
+ <foo>
102
+ <bar>
103
+ <baz key="work">Java</baz>
104
+ <baz key="play">Ruby</baz>
105
+ </bar>
106
+ <bar>
107
+ <baz key="ab">hello</baz>
108
+ <baz key="play">scrabble</baz>
109
+ <baz key="xy">goodbye</baz>
110
+ </bar>
111
+ <more>
112
+ <baz key="play">poker</baz>
113
+ </more>
114
+ </foo>
115
+ EOS
116
+
117
+
118
+ ####read access
119
+ path=XML::XXPath.new("/foo/bar[2]/baz")
120
+
121
+ ## path.all(document) gives all elements matching path in document
122
+ path.all(d)
123
+ => [<baz key='ab'> ... </>, <baz key='play'> ... </>, <baz key='xy'> ... </>]
124
+
125
+ ## loop over them
126
+ path.each(d){|elt| puts elt.text}
127
+ hello
128
+ scrabble
129
+ goodbye
130
+ => [<baz key='ab'> ... </>, <baz key='play'> ... </>, <baz key='xy'> ... </>]
131
+
132
+ ## the first of those
133
+ path.first(d)
134
+ => <baz key='ab'> ... </>
135
+
136
+ ## no match here (only three "baz" elements)
137
+ path2=XML::XXPath.new("/foo/bar[2]/baz[4]")
138
+ path2.all(d)
139
+ => []
140
+
141
+ ## "first" raises XML::XXPathError in such cases...
142
+ path2.first(d)
143
+ XML::XXPathError: path not found: /foo/bar[2]/baz[4]
144
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:75:in `first'
145
+
146
+ ##...unless we allow nil returns
147
+ path2.first(d,:allow_nil=>true)
148
+ => nil
149
+
150
+ ##attribute nodes can also be returned
151
+ keysPath=XML::XXPath.new("/foo/*/*/@key")
152
+
153
+ keysPath.all(d).map{|attr|attr.text}
154
+ => ["work", "play", "ab", "play", "xy", "play"]
155
+
156
+ The objects supplied to the `all()`, `first()`, and
157
+ `each()` calls must be REXML element nodes, i.e. they must
158
+ support messages like `elements`, `attributes` etc
159
+ (instances of REXML::Element and its subclasses do this). The calls
160
+ return the found elements as instances of REXML::Element or
161
+ XML::XXPath::Accessors::Attribute. The latter is a wrapper around
162
+ attribute nodes that is largely call-compatible to
163
+ REXML::Element. This is so you can write things like
164
+ `path.each{|node|puts node.text}` without having to
165
+ special-case anything even if the path matches attributes, not just
166
+ elements.
167
+
168
+ As you can see, you can re-use path objects, applying them to
169
+ different XML elements at will. You should do this because the XPath
170
+ pattern is stored inside the XPath object in a pre-compiled form,
171
+ which makes it more efficient.
172
+
173
+ The path elements of the XPath pattern are applied to the
174
+ `.elements` collection of the passed XML element and its
175
+ sub-elements, starting with the first one. This is shown by the
176
+ following code:
177
+
178
+ require 'xml/xxpath'
179
+
180
+ d=REXML::Document.new <<EOS
181
+ <foo>
182
+ <bar x="hello">
183
+ <first>
184
+ <second>pingpong</second>
185
+ </first>
186
+ </bar>
187
+ <bar x="goodbye"/>
188
+ </foo>
189
+ EOS
190
+
191
+ XML::XXPath.new("/foo/bar").all(d)
192
+ => [<bar x='hello'> ... </>, <bar x='goodbye'/>]
193
+
194
+ XML::XXPath.new("/bar").all(d)
195
+ => []
196
+
197
+ XML::XXPath.new("/foo/bar").all(d.root)
198
+ => []
199
+
200
+ XML::XXPath.new("/bar").all(d.root)
201
+ => [<bar x='hello'> ... </>, <bar x='goodbye'/>]
202
+
203
+
204
+ firstelt = XML::XXPath.new("/foo/bar/first").first(d)
205
+ => <first> ... </>
206
+
207
+ XML::XXPath.new("/first/second").all(firstelt)
208
+ => []
209
+
210
+ XML::XXPath.new("/second").all(firstelt)
211
+ => [<second> ... </>]
212
+
213
+ A REXML +Document+ object is a REXML +Element+ object whose +elements+
214
+ collection consists only of a single member -- the document's root
215
+ node. The first path element of the XPath -- "foo" in the example --
216
+ is matched against that. That is why the path "/bar" in the example
217
+ doesn't match anything when matched against the document +d+ itself.
218
+
219
+ An ordinary REXML +Element+ object that represents a node somewhere
220
+ inside an XML tree has an +elements+ collection that consists of all
221
+ the element's direct sub-elements. That is why XPath patterns matched
222
+ against the +firstelt+ element in the example *must not* start with
223
+ "/first" (unless there is a child node that is also named "first").
224
+
225
+
226
+ ### Write Access
227
+
228
+ You may pass an `:ensure_created=>true` option argument to
229
+ _path_.first(_elt_) / _path_.all(_elt_) calls to make sure that _path_
230
+ exists inside the passed XML element _elt_. If it existed before,
231
+ nothing changes, and the call behaves just as it would without the
232
+ option argument. If the path didn't exist before, the XML element is
233
+ modified such that
234
+
235
+ - the path exists afterwards
236
+
237
+ - all paths that existed before still exist afterwards
238
+
239
+ - the modification is as small as possible (i.e. as few elements as
240
+ possible are added, additional attributes are added to existing
241
+ elements if possible etc.)
242
+
243
+ The created resp. previously existing, matching elements are returned.
244
+
245
+
246
+ Examples:
247
+
248
+ require 'xml/xxpath'
249
+
250
+ d=REXML::Document.new <<EOS
251
+ <foo>
252
+ <bar>
253
+ <baz key="work">Java</baz>
254
+ <baz key="play">Ruby</baz>
255
+ </bar>
256
+ </foo>
257
+ EOS
258
+
259
+
260
+ rootelt=d.root
261
+
262
+ #### ensuring that a specific path exists inside the document
263
+
264
+ XML::XXPath.new("/bar/baz[@key='work']").first(rootelt,:ensure_created=>true)
265
+ => <baz key='work'> ... </>
266
+ d.write($stdout,2)
267
+
268
+ <foo>
269
+ <bar>
270
+ <baz key='work'>
271
+ Java
272
+ </baz>
273
+ <baz key='play'>
274
+ Ruby
275
+ </baz>
276
+ </bar>
277
+ </foo>### no change (path existed before)
278
+
279
+
280
+ XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
281
+ => <baz key='42'/>
282
+ d.write($stdout,2)
283
+
284
+ <foo>
285
+ <bar>
286
+ <baz key='work'>
287
+ Java
288
+ </baz>
289
+ <baz key='play'>
290
+ Ruby
291
+ </baz>
292
+ <baz key='42'/>
293
+ </bar>
294
+ </foo>### path was added
295
+
296
+ XML::XXPath.new("/bar/baz[@key='42']").first(rootelt,:ensure_created=>true)
297
+ => <baz key='42'/>
298
+ d.write($stdout,2)
299
+
300
+ <foo>
301
+ <bar>
302
+ <baz key='work'>
303
+ Java
304
+ </baz>
305
+ <baz key='play'>
306
+ Ruby
307
+ </baz>
308
+ <baz key='42'/>
309
+ </bar>
310
+ </foo>### no change this time
311
+
312
+ XML::XXPath.new("/bar/baz[@key2='hello']").first(rootelt,:ensure_created=>true)
313
+ => <baz key='work' key2='hello'> ... </>
314
+ d.write($stdout,2)
315
+
316
+ <foo>
317
+ <bar>
318
+ <baz key='work' key2='hello'>
319
+ Java
320
+ </baz>
321
+ <baz key='play'>
322
+ Ruby
323
+ </baz>
324
+ <baz key='42'/>
325
+ </bar>
326
+ </foo>### this fit in the 1st "baz" element since
327
+ ### there was no "key2" attribute there before.
328
+
329
+ XML::XXPath.new("/bar/baz[2]").first(rootelt,:ensure_created=>true)
330
+ => <baz key='play'> ... </>
331
+ d.write($stdout,2)
332
+
333
+ <foo>
334
+ <bar>
335
+ <baz key='work' key2='hello'>
336
+ Java
337
+ </baz>
338
+ <baz key='play'>
339
+ Ruby
340
+ </baz>
341
+ <baz key='42'/>
342
+ </bar>
343
+ </foo>### no change
344
+
345
+ XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
346
+ => #<XML::XXPath::Accessors::Attribute:0x007fb014a51d08 @parent=<baz haha='[unset]'/>, @name="haha">
347
+ d.write($stdout,2)
348
+
349
+ <foo>
350
+ <bar>
351
+ <baz key='work' key2='hello'>
352
+ Java
353
+ </baz>
354
+ <baz key='play'>
355
+ Ruby
356
+ </baz>
357
+ <baz key='42'/>
358
+ <baz/>
359
+ <baz/>
360
+ <baz haha='[unset]'/>
361
+ </bar>
362
+ </foo>### for there to be a 6th "baz" element, there must be 1st..5th "baz" elements
363
+
364
+ XML::XXPath.new("/bar/baz[6]/@haha").first(rootelt,:ensure_created=>true)
365
+ => #<XML::XXPath::Accessors::Attribute:0x007fb014a479c0 @parent=<baz haha='[unset]'/>, @name="haha">
366
+ d.write($stdout,2)
367
+
368
+ <foo>
369
+ <bar>
370
+ <baz key='work' key2='hello'>
371
+ Java
372
+ </baz>
373
+ <baz key='play'>
374
+ Ruby
375
+ </baz>
376
+ <baz key='42'/>
377
+ <baz/>
378
+ <baz/>
379
+ <baz haha='[unset]'/>
380
+ </bar>
381
+ </foo>### no change this time
382
+
383
+
384
+
385
+ Alternatively, you may pass a `:create_new=>true` option
386
+ argument or call `create_new` (_path_`.create_new(`_elt_`)` is
387
+ equivalent to _path_`.first(`_elt_`,:create_new=>true)`). In that
388
+ case, a new node is created in _elt_ for each path element of _path_
389
+ (or an exception raised if that wasn't possible for any path element).
390
+
391
+ Examples:
392
+
393
+ require 'xml/xxpath'
394
+
395
+ d=REXML::Document.new <<EOS
396
+ <foo>
397
+ <bar>
398
+ <baz key="work">Java</baz>
399
+ <baz key="play">Ruby</baz>
400
+ </bar>
401
+ </foo>
402
+ EOS
403
+
404
+
405
+ rootelt=d.root
406
+
407
+ path1=XML::XXPath.new("/bar/baz[@key='work']")
408
+
409
+ path1.create_new(rootelt)
410
+ => <baz key='work'/>
411
+ d.write($stdout,2)
412
+
413
+ <foo>
414
+ <bar>
415
+ <baz key='work'>
416
+ Java
417
+ </baz>
418
+ <baz key='play'>
419
+ Ruby
420
+ </baz>
421
+ </bar>
422
+ <bar>
423
+ <baz key='work'/>
424
+ </bar>
425
+ </foo>### a new element is created for *each* path element, regardless of
426
+ ### what existed before. So a new "bar" element was added, with a new
427
+ ### "baz" element inside it
428
+
429
+ ### same call again...
430
+ path1.create_new(rootelt)
431
+ => <baz key='work'/>
432
+ d.write($stdout,2)
433
+
434
+ <foo>
435
+ <bar>
436
+ <baz key='work'>
437
+ Java
438
+ </baz>
439
+ <baz key='play'>
440
+ Ruby
441
+ </baz>
442
+ </bar>
443
+ <bar>
444
+ <baz key='work'/>
445
+ </bar>
446
+ <bar>
447
+ <baz key='work'/>
448
+ </bar>
449
+ </foo>### same procedure -- new elements added for each path element
450
+
451
+
452
+ ## get reference to 1st "baz" element
453
+ firstbazelt=XML::XXPath.new("/bar/baz").first(rootelt)
454
+ => <baz key='work'> ... </>
455
+
456
+ path2=XML::XXPath.new("@key2")
457
+
458
+ path2.create_new(firstbazelt)
459
+ => #<XML::XXPath::Accessors::Attribute:0x007fb014e37210 @parent=<baz key='work' key2='[unset]'> ... </>, @name="key2">
460
+ d.write($stdout,2)
461
+
462
+ <foo>
463
+ <bar>
464
+ <baz key='work' key2='[unset]'>
465
+ Java
466
+ </baz>
467
+ <baz key='play'>
468
+ Ruby
469
+ </baz>
470
+ </bar>
471
+ <bar>
472
+ <baz key='work'/>
473
+ </bar>
474
+ <bar>
475
+ <baz key='work'/>
476
+ </bar>
477
+ </foo>### ok, new attribute node added
478
+
479
+ ### same call again...
480
+ path2.create_new(firstbazelt)
481
+ XML::XXPathError: XPath (@key2): create_new and attribute already exists
482
+ from /home/olaf/xml-mapping/lib/xml/xxpath/steps.rb:215:in `create_on'
483
+ from /home/olaf/xml-mapping/lib/xml/xxpath/steps.rb:80:in `block in creator'
484
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:91:in `call'
485
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:91:in `all'
486
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:70:in `first'
487
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:112:in `create_new'
488
+ ### can't create that path anew again -- an element can't have more
489
+ ### than one attribute with the same name
490
+
491
+ ### the document hasn't changed
492
+ d.write($stdout,2)
493
+
494
+ <foo>
495
+ <bar>
496
+ <baz key='work' key2='[unset]'>
497
+ Java
498
+ </baz>
499
+ <baz key='play'>
500
+ Ruby
501
+ </baz>
502
+ </bar>
503
+ <bar>
504
+ <baz key='work'/>
505
+ </bar>
506
+ <bar>
507
+ <baz key='work'/>
508
+ </bar>
509
+ </foo>
510
+
511
+
512
+ ### create_new the same path as in the ensure_created example
513
+ baz6elt=XML::XXPath.new("/bar/baz[6]").create_new(rootelt)
514
+ => <baz/>
515
+ d.write($stdout,2)
516
+
517
+ <foo>
518
+ <bar>
519
+ <baz key='work' key2='[unset]'>
520
+ Java
521
+ </baz>
522
+ <baz key='play'>
523
+ Ruby
524
+ </baz>
525
+ </bar>
526
+ <bar>
527
+ <baz key='work'/>
528
+ </bar>
529
+ <bar>
530
+ <baz key='work'/>
531
+ </bar>
532
+ <bar>
533
+ <baz/>
534
+ <baz/>
535
+ <baz/>
536
+ <baz/>
537
+ <baz/>
538
+ <baz/>
539
+ </bar>
540
+ </foo>### ok, new "bar" element and 6th "baz" element inside it created
541
+
542
+
543
+ XML::XXPath.new("baz[6]").create_new(baz6elt.parent)
544
+ XML::XXPathError: XPath: baz[6]: create_new and element already exists
545
+ from /home/olaf/xml-mapping/lib/xml/xxpath/steps.rb:167:in `create_on'
546
+ from /home/olaf/xml-mapping/lib/xml/xxpath/steps.rb:80:in `block in creator'
547
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:91:in `call'
548
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:91:in `all'
549
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:70:in `first'
550
+ from /home/olaf/xml-mapping/lib/xml/xxpath.rb:112:in `create_new'
551
+ ### yep, baz[6] already existed and thus couldn't be created once
552
+ ### again
553
+
554
+ ### but of course...
555
+ XML::XXPath.new("/bar/baz[6]").create_new(rootelt)
556
+ => <baz/>
557
+ d.write($stdout,2)
558
+
559
+ <foo>
560
+ <bar>
561
+ <baz key='work' key2='[unset]'>
562
+ Java
563
+ </baz>
564
+ <baz key='play'>
565
+ Ruby
566
+ </baz>
567
+ </bar>
568
+ <bar>
569
+ <baz key='work'/>
570
+ </bar>
571
+ <bar>
572
+ <baz key='work'/>
573
+ </bar>
574
+ <bar>
575
+ <baz/>
576
+ <baz/>
577
+ <baz/>
578
+ <baz/>
579
+ <baz/>
580
+ <baz/>
581
+ </bar>
582
+ <bar>
583
+ <baz/>
584
+ <baz/>
585
+ <baz/>
586
+ <baz/>
587
+ <baz/>
588
+ <baz/>
589
+ </bar>
590
+ </foo>### this works because *all* path elements are newly created
591
+
592
+
593
+ This feature is used in xml-mapping by node types like
594
+ XML::Mapping::ArrayNode, which must create a new instance of the
595
+ "per-array element path" for each element of the array to be stored in
596
+ an XML tree.
597
+
598
+
599
+ ### Pathological Cases
600
+
601
+ What is created when the Path "*" is to be created inside an empty XML
602
+ element? The name of the element to be created isn't known, but still
603
+ some element must be created. The answer is that xml-xxpath creates a
604
+ special "unspecified" element whose name must be set by the caller
605
+ afterwards:
606
+
607
+ require 'xml/xxpath'
608
+
609
+ d=REXML::Document.new <<EOS
610
+ <foo>
611
+ <bar/>
612
+ <bar/>
613
+ </foo>
614
+ EOS
615
+
616
+
617
+ rootelt=d.root
618
+
619
+
620
+ XML::XXPath.new("*").all(rootelt)
621
+ => [<bar/>, <bar/>]
622
+ ### ok
623
+
624
+ XML::XXPath.new("bar/*").first(rootelt, :allow_nil=>true)
625
+ => nil
626
+ ### ok, nothing there
627
+
628
+ ### the same call with :ensure_created=>true
629
+ newelt = XML::XXPath.new("bar/*").first(rootelt, :ensure_created=>true)
630
+ => </>
631
+
632
+ d.write($stdout,2)
633
+
634
+ <foo>
635
+ <bar>
636
+ </>
637
+ </bar>
638
+ <bar/>
639
+ </foo>
640
+ ### a new "unspecified" element was created
641
+ newelt.unspecified?
642
+ => true
643
+
644
+ ### we must modify it to "specify" it
645
+ newelt.name="new-one"
646
+ newelt.text="hello!"
647
+ newelt.unspecified?
648
+ => false
649
+
650
+ d.write($stdout,2)
651
+
652
+ <foo>
653
+ <bar>
654
+ <new-one>
655
+ hello!
656
+ </new-one>
657
+ </bar>
658
+ <bar/>
659
+ </foo>
660
+ ### you could also set unspecified to false explicitly, as in:
661
+ newelt.unspecified=true
662
+
663
+
664
+ The "newelt" object in the last example is an ordinary
665
+ REXML::Element. xml-xxpath mixes the "unspecified" attribute into that
666
+ class, as well as into the XML::XXPath::Accessors::Attribute class
667
+ mentioned above.
668
+
669
+
670
+ ## Implentation notes
671
+
672
+ `doc/xpath_impl_notes.txt` contains some documentation on the
673
+ implementation of xml-xxpath.
674
+
675
+ ## License
676
+
677
+ Ruby's.