xml-mapping 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.