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.
- checksums.yaml +7 -0
- data/LICENSE +10 -53
- data/README.md +57 -0
- data/Rakefile +75 -85
- data/TODO.txt +12 -2
- data/examples/README +4 -4
- data/examples/cleanup.rb +11 -0
- data/examples/company_usage.intout +7 -7
- data/examples/documents_folders_usage.intout +2 -2
- data/examples/order_signature_enhanced_usage.intout +3 -3
- data/examples/order_usage.intout +26 -26
- data/examples/person.intout +3 -3
- data/examples/person_mm.intout +2 -2
- data/examples/publication.intout +2 -2
- data/examples/reader.intout +1 -1
- data/examples/stringarray_usage.intout +1 -1
- data/examples/time_augm.intout +6 -6
- data/examples/xpath_create_new.intout +13 -13
- data/examples/xpath_ensure_created.intout +2 -2
- data/examples/xpath_usage.intout +1 -1
- data/lib/xml/mapping.rb +0 -2
- data/lib/xml/mapping/base.rb +24 -9
- data/lib/xml/mapping/version.rb +1 -1
- data/test/company.rb +43 -0
- data/test/documents_folders.rb +7 -0
- data/test/multiple_mappings_test.rb +3 -1
- data/test/xml_mapping_adv_test.rb +20 -20
- data/test/xml_mapping_test.rb +31 -2
- data/{README → user_manual.md} +916 -154
- data/user_manual_xxpath.md +677 -0
- metadata +100 -112
- data/ChangeLog +0 -189
- data/README_XPATH +0 -202
- data/examples/company_usage.intin.rb +0 -19
- data/examples/documents_folders_usage.intin.rb +0 -18
- data/examples/order_signature_enhanced_usage.intin.rb +0 -12
- data/examples/order_usage.intin.rb +0 -120
- data/examples/person.intin.rb +0 -44
- data/examples/person_mm.intin.rb +0 -119
- data/examples/publication.intin.rb +0 -44
- data/examples/reader.intin.rb +0 -33
- data/examples/stringarray_usage.intin.rb +0 -11
- data/examples/time_augm.intin.rb +0 -19
- data/examples/time_augm_loading.intin.rb +0 -44
- data/examples/time_node.intin.rb +0 -79
- data/examples/time_node_w_marshallers.intin.rb +0 -48
- data/examples/xpath_create_new.intin.rb +0 -85
- data/examples/xpath_docvsroot.intin.rb +0 -30
- data/examples/xpath_ensure_created.intin.rb +0 -62
- data/examples/xpath_pathological.intin.rb +0 -42
- data/examples/xpath_usage.intin.rb +0 -51
- data/install.rb +0 -41
- 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.
|