xml-mapping 0.8.1 → 0.9.1
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.
- data/ChangeLog +64 -3
- data/README +871 -173
- data/README_XPATH +40 -13
- data/Rakefile +37 -26
- data/TODO.txt +39 -8
- data/examples/README +5 -0
- data/examples/company_usage.intout +34 -22
- data/examples/documents_folders.rb +31 -0
- data/examples/documents_folders.xml +16 -0
- data/examples/documents_folders_usage.intin.rb +18 -0
- data/examples/documents_folders_usage.intout +46 -0
- data/examples/order_signature_enhanced_usage.intout +21 -11
- data/examples/order_usage.intin.rb +52 -5
- data/examples/order_usage.intout +154 -80
- data/examples/person.intin.rb +44 -0
- data/examples/person.intout +27 -0
- data/examples/person_mm.intin.rb +119 -0
- data/examples/person_mm.intout +114 -0
- data/examples/publication.intin.rb +44 -0
- data/examples/publication.intout +20 -0
- data/examples/reader.intin.rb +33 -0
- data/examples/reader.intout +19 -0
- data/examples/stringarray.rb +5 -0
- data/examples/stringarray.xml +10 -0
- data/examples/stringarray_usage.intin.rb +11 -0
- data/examples/stringarray_usage.intout +31 -0
- data/examples/time_augm.intout +19 -7
- data/examples/time_augm_loading.intin.rb +44 -0
- data/examples/time_augm_loading.intout +12 -0
- data/examples/time_node.intin.rb +79 -0
- data/examples/time_node.rb +3 -2
- data/examples/time_node_w_marshallers.intin.rb +48 -0
- data/examples/time_node_w_marshallers.intout +25 -0
- data/examples/time_node_w_marshallers.xml +9 -0
- data/examples/xpath_create_new.intout +132 -114
- data/examples/xpath_ensure_created.intout +86 -65
- data/examples/xpath_pathological.intout +16 -16
- data/examples/xpath_usage.intout +1 -1
- data/install.rb +1 -0
- data/lib/xml/mapping.rb +3 -1
- data/lib/xml/mapping/base.rb +442 -272
- data/lib/xml/mapping/core_classes_mapping.rb +32 -0
- data/lib/xml/mapping/standard_nodes.rb +176 -86
- data/lib/xml/mapping/version.rb +2 -2
- data/lib/xml/rexml_ext.rb +186 -0
- data/lib/xml/xxpath.rb +28 -265
- data/lib/xml/xxpath/steps.rb +345 -0
- data/lib/xml/xxpath_methods.rb +96 -0
- data/test/all_tests.rb +4 -1
- data/test/benchmark_fixtures.rb +14 -0
- data/test/{multiple_mappings.rb → bookmarks.rb} +0 -0
- data/test/company.rb +47 -0
- data/test/documents_folders.rb +11 -1
- data/test/examples_test.rb +29 -0
- data/test/fixtures/benchmark.xml +77 -0
- data/test/fixtures/company1.xml +9 -0
- data/test/fixtures/documents_folders.xml +0 -8
- data/test/fixtures/documents_folders2.xml +13 -19
- data/test/fixtures/triangle_m1.xml +17 -0
- data/test/fixtures/triangle_m2.xml +19 -0
- data/test/inheritance_test.rb +50 -0
- data/test/multiple_mappings_test.rb +155 -0
- data/test/rexml_xpath_benchmark.rb +29 -0
- data/test/triangle_mm.rb +57 -0
- data/test/xml_mapping_adv_test.rb +36 -1
- data/test/xml_mapping_test.rb +136 -7
- data/test/xpath_test.rb +154 -0
- data/test/xxpath_benchmark.rb +36 -0
- data/test/xxpath_benchmark.result1.txt +17 -0
- data/test/xxpath_methods_test.rb +61 -0
- metadata +139 -90
data/lib/xml/xxpath.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
#
|
2
|
-
# Copyright (C) 2004
|
1
|
+
# xxpath -- XPath implementation for Ruby, including write access
|
2
|
+
# Copyright (C) 2004-2006 Olaf Klischat
|
3
3
|
|
4
4
|
require 'rexml/document'
|
5
|
+
require 'xml/rexml_ext'
|
6
|
+
require 'xml/xxpath/steps'
|
5
7
|
|
6
8
|
module XML
|
7
9
|
|
@@ -19,91 +21,34 @@ module XML
|
|
19
21
|
def initialize(xpathstr)
|
20
22
|
@xpathstr = xpathstr # for error messages
|
21
23
|
|
22
|
-
|
24
|
+
# TODO: write a real XPath parser sometime
|
23
25
|
|
24
|
-
|
25
|
-
# maybe: build & create the procs using eval
|
26
|
+
xpathstr='/'+xpathstr if xpathstr[0] != ?/
|
26
27
|
|
27
28
|
@creator_procs = [ proc{|node,create_new| node} ]
|
28
29
|
@reader_proc = proc {|nodes| nodes}
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
@creator_procs << curr_creator = proc {|node,create_new|
|
52
|
-
prev_creator.call(Accessors.create_subnode_by_name_and_index(node,create_new,
|
53
|
-
name,index),
|
54
|
-
create_new)
|
55
|
-
}
|
56
|
-
@reader_proc = proc {|nodes|
|
57
|
-
next_nodes = Accessors.subnodes_by_name_and_index(nodes,
|
58
|
-
name,index)
|
59
|
-
if (next_nodes == [])
|
60
|
-
throw :not_found, [nodes,curr_creator]
|
61
|
-
else
|
62
|
-
prev_reader.call(next_nodes)
|
63
|
-
end
|
64
|
-
}
|
65
|
-
when /^@(.*)$/
|
66
|
-
name = $1
|
67
|
-
@creator_procs << curr_creator = proc {|node,create_new|
|
68
|
-
prev_creator.call(Accessors.create_subnode_by_attr_name(node,create_new,name),
|
69
|
-
create_new)
|
70
|
-
}
|
71
|
-
@reader_proc = proc {|nodes|
|
72
|
-
next_nodes = Accessors.subnodes_by_attr_name(nodes,name)
|
73
|
-
if (next_nodes == [])
|
74
|
-
throw :not_found, [nodes,curr_creator]
|
75
|
-
else
|
76
|
-
prev_reader.call(next_nodes)
|
77
|
-
end
|
78
|
-
}
|
79
|
-
when '*'
|
80
|
-
@creator_procs << curr_creator = proc {|node,create_new|
|
81
|
-
prev_creator.call(Accessors.create_subnode_by_all(node,create_new),
|
82
|
-
create_new)
|
83
|
-
}
|
84
|
-
@reader_proc = proc {|nodes|
|
85
|
-
next_nodes = Accessors.subnodes_by_all(nodes)
|
86
|
-
if (next_nodes == [])
|
87
|
-
throw :not_found, [nodes,curr_creator]
|
88
|
-
else
|
89
|
-
prev_reader.call(next_nodes)
|
90
|
-
end
|
91
|
-
}
|
92
|
-
else
|
93
|
-
name = part
|
94
|
-
@creator_procs << curr_creator = proc {|node,create_new|
|
95
|
-
prev_creator.call(Accessors.create_subnode_by_name(node,create_new,name),
|
96
|
-
create_new)
|
97
|
-
}
|
98
|
-
@reader_proc = proc {|nodes|
|
99
|
-
next_nodes = Accessors.subnodes_by_name(nodes,name)
|
100
|
-
if (next_nodes == [])
|
101
|
-
throw :not_found, [nodes,curr_creator]
|
102
|
-
else
|
103
|
-
prev_reader.call(next_nodes)
|
104
|
-
end
|
105
|
-
}
|
106
|
-
end
|
30
|
+
|
31
|
+
part=nil; part_expected=true
|
32
|
+
xpathstr.split(/(\/+)/)[1..-1].reverse.each do |x|
|
33
|
+
if part_expected
|
34
|
+
part=x
|
35
|
+
part_expected = false
|
36
|
+
next
|
37
|
+
end
|
38
|
+
part_expected = true
|
39
|
+
axis = case x
|
40
|
+
when '/'
|
41
|
+
:child
|
42
|
+
when '//'
|
43
|
+
:descendant
|
44
|
+
else
|
45
|
+
raise XXPathError, "XPath (#{xpathstr}): unknown axis: #{x}"
|
46
|
+
end
|
47
|
+
axis=:self if axis==:child and (part[0]==?. or part=~/^self::/) # yuck
|
48
|
+
|
49
|
+
step = Step.compile(axis,part)
|
50
|
+
@creator_procs << step.creator(@creator_procs[-1])
|
51
|
+
@reader_proc = step.reader(@reader_proc, @creator_procs[-1])
|
107
52
|
end
|
108
53
|
end
|
109
54
|
|
@@ -167,188 +112,6 @@ module XML
|
|
167
112
|
first(base_node,:create_new=>true)
|
168
113
|
end
|
169
114
|
|
170
|
-
|
171
|
-
module Accessors #:nodoc:
|
172
|
-
|
173
|
-
# we need a boolean "unspecified?" attribute for XML nodes --
|
174
|
-
# paths like "*" oder (somewhen) "foo|bar" create "unspecified"
|
175
|
-
# nodes that the user must then "specify" by setting their text
|
176
|
-
# etc. (or manually setting unspecified=false)
|
177
|
-
#
|
178
|
-
# This is mixed into the REXML::Element and
|
179
|
-
# XML::XXPath::Accessors::Attribute classes.
|
180
|
-
module UnspecifiednessSupport
|
181
|
-
|
182
|
-
def unspecified?
|
183
|
-
@xml_xpath_unspecified ||= false
|
184
|
-
end
|
185
|
-
|
186
|
-
def unspecified=(x)
|
187
|
-
@xml_xpath_unspecified = x
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.included(mod)
|
191
|
-
mod.module_eval <<-EOS
|
192
|
-
alias_method :_text_orig, :text
|
193
|
-
alias_method :_textis_orig, :text=
|
194
|
-
def text
|
195
|
-
# we're suffering from the "fragile base class"
|
196
|
-
# phenomenon here -- we don't know whether the
|
197
|
-
# implementation of the class we get mixed into always
|
198
|
-
# calls text (instead of just accessing @text or so)
|
199
|
-
if unspecified?
|
200
|
-
"[UNSPECIFIED]"
|
201
|
-
else
|
202
|
-
_text_orig
|
203
|
-
end
|
204
|
-
end
|
205
|
-
def text=(x)
|
206
|
-
_textis_orig(x)
|
207
|
-
self.unspecified=false
|
208
|
-
end
|
209
|
-
|
210
|
-
alias_method :_nameis_orig, :name=
|
211
|
-
def name=(x)
|
212
|
-
_nameis_orig(x)
|
213
|
-
self.unspecified=false
|
214
|
-
end
|
215
|
-
EOS
|
216
|
-
end
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
class REXML::Element #:nodoc:
|
221
|
-
include UnspecifiednessSupport
|
222
|
-
end
|
223
|
-
|
224
|
-
# attribute node, half-way compatible
|
225
|
-
# with REXML's Element.
|
226
|
-
# REXML doesn't provide one...
|
227
|
-
#
|
228
|
-
# The all/first calls return instances of this class if they
|
229
|
-
# matched an attribute node.
|
230
|
-
class Attribute
|
231
|
-
attr_reader :parent, :name
|
232
|
-
attr_writer :name
|
233
|
-
|
234
|
-
def initialize(parent,name)
|
235
|
-
@parent,@name = parent,name
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.new(parent,name,create)
|
239
|
-
if parent.attributes[name]
|
240
|
-
super(parent,name)
|
241
|
-
else
|
242
|
-
if create
|
243
|
-
parent.attributes[name] = "[unset]"
|
244
|
-
super(parent,name)
|
245
|
-
else
|
246
|
-
nil
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
# the value of the attribute.
|
252
|
-
def text
|
253
|
-
parent.attributes[@name]
|
254
|
-
end
|
255
|
-
|
256
|
-
def text=(x)
|
257
|
-
parent.attributes[@name] = x
|
258
|
-
end
|
259
|
-
|
260
|
-
def ==(other)
|
261
|
-
other.kind_of?(Attribute) and other.parent==parent and other.name==name
|
262
|
-
end
|
263
|
-
|
264
|
-
include UnspecifiednessSupport
|
265
|
-
end
|
266
|
-
|
267
|
-
# read accessors
|
268
|
-
|
269
|
-
for things in %w{name name_and_attr name_and_index attr_name all} do
|
270
|
-
self.module_eval <<-EOS
|
271
|
-
def self.subnodes_by_#{things}(nodes, *args)
|
272
|
-
nodes.map{|node| subnodes_by_#{things}_singlesrc(node,*args)}.flatten
|
273
|
-
end
|
274
|
-
EOS
|
275
|
-
end
|
276
|
-
|
277
|
-
def self.subnodes_by_name_singlesrc(node,name)
|
278
|
-
node.elements.select{|elt| elt.name==name}
|
279
|
-
end
|
280
|
-
|
281
|
-
def self.subnodes_by_name_and_attr_singlesrc(node,name,attr_name,attr_value)
|
282
|
-
node.elements.select{|elt| elt.name==name and elt.attributes[attr_name]==attr_value}
|
283
|
-
end
|
284
|
-
|
285
|
-
def self.subnodes_by_name_and_index_singlesrc(node,name,index)
|
286
|
-
index-=1
|
287
|
-
byname=subnodes_by_name_singlesrc(node,name)
|
288
|
-
if index>=byname.size
|
289
|
-
[]
|
290
|
-
else
|
291
|
-
[byname[index]]
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def self.subnodes_by_attr_name_singlesrc(node,name)
|
296
|
-
attr=Attribute.new(node,name,false)
|
297
|
-
if attr then [attr] else [] end
|
298
|
-
end
|
299
|
-
|
300
|
-
def self.subnodes_by_all_singlesrc(node)
|
301
|
-
node.elements.to_a
|
302
|
-
end
|
303
|
-
|
304
|
-
|
305
|
-
# write accessors
|
306
|
-
|
307
|
-
# precondition: unless create_new, we know that a node with
|
308
|
-
# exactly the requested attributes doesn't exist yet (else we
|
309
|
-
# wouldn't have been called)
|
310
|
-
def self.create_subnode_by_name(node,create_new,name)
|
311
|
-
node.elements.add name
|
312
|
-
end
|
313
|
-
|
314
|
-
def self.create_subnode_by_name_and_attr(node,create_new,name,attr_name,attr_value)
|
315
|
-
if create_new
|
316
|
-
newnode = node.elements.add(name)
|
317
|
-
else
|
318
|
-
newnode = subnodes_by_name_singlesrc(node,name)[0]
|
319
|
-
if not(newnode) or newnode.attributes[attr_name]
|
320
|
-
newnode = node.elements.add(name)
|
321
|
-
end
|
322
|
-
end
|
323
|
-
newnode.attributes[attr_name]=attr_value
|
324
|
-
newnode
|
325
|
-
end
|
326
|
-
|
327
|
-
def self.create_subnode_by_name_and_index(node,create_new,name,index)
|
328
|
-
name_matches = subnodes_by_name_singlesrc(node,name)
|
329
|
-
if create_new and (name_matches.size >= index)
|
330
|
-
raise XXPathError, "XPath (#{@xpathstr}): #{name}[#{index}]: create_new and element already exists"
|
331
|
-
end
|
332
|
-
newnode = name_matches[0]
|
333
|
-
(index-name_matches.size).times do
|
334
|
-
newnode = node.elements.add name
|
335
|
-
end
|
336
|
-
newnode
|
337
|
-
end
|
338
|
-
|
339
|
-
def self.create_subnode_by_attr_name(node,create_new,name)
|
340
|
-
if create_new and node.attributes[name]
|
341
|
-
raise XXPathError, "XPath (#{@xpathstr}): @#{name}: create_new and attribute already exists"
|
342
|
-
end
|
343
|
-
Attribute.new(node,name,true)
|
344
|
-
end
|
345
|
-
|
346
|
-
def self.create_subnode_by_all(node,create_new)
|
347
|
-
node = node.elements.add
|
348
|
-
node.unspecified = true
|
349
|
-
node
|
350
|
-
end
|
351
|
-
end
|
352
115
|
end
|
353
116
|
|
354
117
|
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
# xxpath -- XPath implementation for Ruby, including write access
|
2
|
+
# Copyright (C) 2004-2006 Olaf Klischat
|
3
|
+
|
4
|
+
module XML
|
5
|
+
class XXPath
|
6
|
+
|
7
|
+
# base class for XPath "steps". Steps contain an "axis" (e.g.
|
8
|
+
# "/", "//", i.e. the "child" resp. "descendants" axis), and
|
9
|
+
# a "node matcher" like "foo" or "@bar" or "foo[@bar='baz']", i.e. they
|
10
|
+
# match some XML nodes and don't match others (e.g. "foo[@bar='baz']"
|
11
|
+
# macthes all XML element nodes named "foo" that contain an attribute
|
12
|
+
# with name "bar" and value "baz").
|
13
|
+
#
|
14
|
+
# Steps can find out whether they match a given XML node
|
15
|
+
# (Step#matches?(node)), and they know how to create a matchingnode
|
16
|
+
# on a given base node (Step#create_on(node,create_new)).
|
17
|
+
class Step #:nodoc:
|
18
|
+
def self.inherited(subclass)
|
19
|
+
(@subclasses||=[]) << subclass
|
20
|
+
end
|
21
|
+
|
22
|
+
# create and return an instance of the right Step subclass for
|
23
|
+
# axis _axis_ (:child or :descendant atm.) and node matcher _string_
|
24
|
+
def self.compile axis, string
|
25
|
+
(@subclasses||=[]).each do |sc|
|
26
|
+
obj = sc.compile axis, string
|
27
|
+
return obj if obj
|
28
|
+
end
|
29
|
+
raise XXPathError, "can't compile XPath step: #{string}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize axis
|
33
|
+
@axis = axis
|
34
|
+
end
|
35
|
+
|
36
|
+
# return a proc that takes a list of nodes, finds all sub-nodes
|
37
|
+
# that are reachable from one of those nodes via _self_'s axis
|
38
|
+
# and match (see below) _self_, and calls _prev_reader_ on them
|
39
|
+
# (and returns the result). When the proc doesn't find any such
|
40
|
+
# nodes, it throws <tt>:not_found,
|
41
|
+
# [nodes,creator_from_here]</tt>.
|
42
|
+
#
|
43
|
+
# Needed for compiling whole XPath expressions for reading.
|
44
|
+
#
|
45
|
+
# <tt>Step</tt> itself provides a generic default implementation
|
46
|
+
# which checks whether _self_ matches a given node by calling
|
47
|
+
# self.matches?(node). Subclasses must either implement such a
|
48
|
+
# _matches?_ method or override _reader_ to provide more
|
49
|
+
# specialized implementations for better performance.
|
50
|
+
def reader(prev_reader,creator_from_here)
|
51
|
+
proc {|nodes|
|
52
|
+
next_nodes = []
|
53
|
+
nodes.each do |node|
|
54
|
+
if node.respond_to? :each_on_axis
|
55
|
+
node.each_on_axis(@axis) do |subnode|
|
56
|
+
next_nodes << subnode if self.matches?(subnode)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
if (next_nodes.empty?)
|
61
|
+
throw :not_found, [nodes,creator_from_here]
|
62
|
+
else
|
63
|
+
prev_reader.call(next_nodes)
|
64
|
+
end
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# return a proc that takes a node, creates a sub-node matching
|
69
|
+
# _self_ on it, and then calls _prev_creator_ on that and
|
70
|
+
# returns the result.
|
71
|
+
#
|
72
|
+
# Needed for compiling whole XPath expressions for writing.
|
73
|
+
#
|
74
|
+
# <tt>Step</tt> itself provides a generic default
|
75
|
+
# implementation, subclasses may provide specialized
|
76
|
+
# implementations for better performance.
|
77
|
+
def creator(prev_creator)
|
78
|
+
if @axis==:child or @axis==:self
|
79
|
+
proc {|node,create_new|
|
80
|
+
prev_creator.call(self.create_on(node,create_new),
|
81
|
+
create_new)
|
82
|
+
}
|
83
|
+
else
|
84
|
+
proc {|node,create_new|
|
85
|
+
raise XXPathError, "can't create axis: #{@axis}"
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
class AttrStep < Step #:nodoc:
|
93
|
+
def self.compile axis, string
|
94
|
+
/^(?:\.|self::\*)\[@(.*?)='(.*?)'\]$/ === string or return nil
|
95
|
+
self.new axis,$1,$2
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize(axis,attr_name,attr_value)
|
99
|
+
super(axis)
|
100
|
+
@attr_name,@attr_value = attr_name,attr_value
|
101
|
+
end
|
102
|
+
|
103
|
+
def matches? node
|
104
|
+
node.is_a?(REXML::Element) and node.attributes[@attr_name]==@attr_value
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_on(node,create_new)
|
108
|
+
if create_new
|
109
|
+
raise XXPathError, "XPath: .[@'#{@attr_name}'='#{@attr_value}']: create_new but context node already exists"
|
110
|
+
end
|
111
|
+
# TODO: raise if node.attributes[@attr_name] already exists?
|
112
|
+
node.attributes[@attr_name]=@attr_value
|
113
|
+
node
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
class NameAndAttrStep < Step #:nodoc:
|
119
|
+
def self.compile axis, string
|
120
|
+
/^(.*?)\[@(.*?)='(.*?)'\]$/ === string or return nil
|
121
|
+
self.new axis,$1,$2,$3
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize(axis,name,attr_name,attr_value)
|
125
|
+
super(axis)
|
126
|
+
@name,@attr_name,@attr_value = name,attr_name,attr_value
|
127
|
+
end
|
128
|
+
|
129
|
+
def matches? node
|
130
|
+
node.is_a?(REXML::Element) and node.name==@name and node.attributes[@attr_name]==@attr_value
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_on(node,create_new)
|
134
|
+
if create_new
|
135
|
+
newnode = node.elements.add(@name)
|
136
|
+
else
|
137
|
+
newnode = node.elements.select{|elt| elt.name==@name and not(elt.attributes[@attr_name])}[0]
|
138
|
+
if not(newnode)
|
139
|
+
newnode = node.elements.add(@name)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
newnode.attributes[@attr_name]=@attr_value
|
143
|
+
newnode
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
class NameAndIndexStep < Step #:nodoc:
|
149
|
+
def self.compile axis, string
|
150
|
+
/^(.*?)\[(\d+)\]$/ === string or return nil
|
151
|
+
self.new axis,$1,$2.to_i
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize(axis,name,index)
|
155
|
+
super(axis)
|
156
|
+
@name,@index = name,index
|
157
|
+
end
|
158
|
+
|
159
|
+
def matches? node
|
160
|
+
raise XXPathError, "can't use #{@name}[#{@index}] on root node" if node.parent.nil?
|
161
|
+
node == node.parent.elements.select{|elt| elt.name==@name}[@index-1]
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_on(node,create_new)
|
165
|
+
name_matches = node.elements.select{|elt| elt.name==@name}
|
166
|
+
if create_new and (name_matches.size >= @index)
|
167
|
+
raise XXPathError, "XPath: #{@name}[#{@index}]: create_new and element already exists"
|
168
|
+
end
|
169
|
+
newnode = name_matches[0]
|
170
|
+
(@index-name_matches.size).times do
|
171
|
+
newnode = node.elements.add @name
|
172
|
+
end
|
173
|
+
newnode
|
174
|
+
end
|
175
|
+
|
176
|
+
def reader(prev_reader,creator_from_here)
|
177
|
+
if @axis==:child
|
178
|
+
index = @index - 1
|
179
|
+
proc {|nodes|
|
180
|
+
next_nodes = []
|
181
|
+
nodes.each do |node|
|
182
|
+
byname=node.elements.select{|elt| elt.name==@name}
|
183
|
+
next_nodes << byname[index] if index<byname.size
|
184
|
+
end
|
185
|
+
if (next_nodes.empty?)
|
186
|
+
throw :not_found, [nodes,creator_from_here]
|
187
|
+
else
|
188
|
+
prev_reader.call(next_nodes)
|
189
|
+
end
|
190
|
+
}
|
191
|
+
else
|
192
|
+
super(prev_reader,creator_from_here)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
class AttrNameStep < Step #:nodoc:
|
199
|
+
def self.compile axis, string
|
200
|
+
/^@(.*)$/ === string or return nil
|
201
|
+
self.new axis,$1
|
202
|
+
end
|
203
|
+
|
204
|
+
def initialize(axis,attr_name)
|
205
|
+
super(axis)
|
206
|
+
@attr_name = attr_name
|
207
|
+
end
|
208
|
+
|
209
|
+
def matches? node
|
210
|
+
node.class==XML::XXPath::Accessors::Attribute and node.name==@attr_name
|
211
|
+
end
|
212
|
+
|
213
|
+
def create_on(node,create_new)
|
214
|
+
if create_new and node.attributes[@attr_name]
|
215
|
+
raise XXPathError, "XPath (@#{@attr_name}): create_new and attribute already exists"
|
216
|
+
end
|
217
|
+
XML::XXPath::Accessors::Attribute.new(node,@attr_name,true)
|
218
|
+
end
|
219
|
+
|
220
|
+
def reader(prev_reader,creator_from_here)
|
221
|
+
if @axis==:child
|
222
|
+
proc {|nodes|
|
223
|
+
next_nodes = []
|
224
|
+
nodes.each do |node|
|
225
|
+
attr=XML::XXPath::Accessors::Attribute.new(node,@attr_name,false)
|
226
|
+
next_nodes << attr if attr
|
227
|
+
end
|
228
|
+
if (next_nodes.empty?)
|
229
|
+
throw :not_found, [nodes,creator_from_here]
|
230
|
+
else
|
231
|
+
prev_reader.call(next_nodes)
|
232
|
+
end
|
233
|
+
}
|
234
|
+
else
|
235
|
+
super(prev_reader,creator_from_here)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
class AllElementsStep < Step #:nodoc:
|
242
|
+
def self.compile axis, string
|
243
|
+
'*'==string or return nil
|
244
|
+
self.new axis
|
245
|
+
end
|
246
|
+
|
247
|
+
def matches? node
|
248
|
+
node.is_a? REXML::Element
|
249
|
+
end
|
250
|
+
|
251
|
+
def create_on(node,create_new)
|
252
|
+
newnode = node.elements.add
|
253
|
+
newnode.unspecified = true
|
254
|
+
newnode
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
class ThisNodeStep < Step #:nodoc:
|
260
|
+
def self.compile axis, string
|
261
|
+
'.'==string or return nil
|
262
|
+
self.new axis
|
263
|
+
end
|
264
|
+
|
265
|
+
def matches? node
|
266
|
+
true
|
267
|
+
end
|
268
|
+
|
269
|
+
def create_on(node,create_new)
|
270
|
+
if create_new
|
271
|
+
raise XXPathError, "XPath: .: create_new and attribute already exists"
|
272
|
+
end
|
273
|
+
node
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
class AlternativeNamesStep < Step #:nodoc:
|
279
|
+
def self.compile axis, string
|
280
|
+
if string=~/\|/
|
281
|
+
self.new axis, string.split('|')
|
282
|
+
else
|
283
|
+
nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def initialize(axis,names)
|
288
|
+
super(axis)
|
289
|
+
@names = names
|
290
|
+
end
|
291
|
+
|
292
|
+
def matches? node
|
293
|
+
node.is_a?(REXML::Element) and @names.inject(false){|prev,name| prev or node.name==name}
|
294
|
+
end
|
295
|
+
|
296
|
+
def create_on(node,create_new)
|
297
|
+
newnode = node.elements.add
|
298
|
+
newnode.unspecified = true
|
299
|
+
newnode
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
class TextNodesStep < Step #:nodoc:
|
305
|
+
def self.compile axis, string
|
306
|
+
'text()' == string or return nil
|
307
|
+
self.new axis
|
308
|
+
end
|
309
|
+
|
310
|
+
def matches? node
|
311
|
+
node.is_a? REXML::Text
|
312
|
+
end
|
313
|
+
|
314
|
+
def create_on(node,create_new)
|
315
|
+
node.add(REXML::Text.new(""))
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
class REXML::Text
|
320
|
+
# call-compatibility w/ REXML::Element
|
321
|
+
alias_method :text, :value
|
322
|
+
alias_method :text=, :value=
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
class NameStep < Step #:nodoc:
|
327
|
+
def self.compile axis, string
|
328
|
+
self.new axis,string
|
329
|
+
end
|
330
|
+
|
331
|
+
def initialize(axis,name)
|
332
|
+
super(axis)
|
333
|
+
@name = name
|
334
|
+
end
|
335
|
+
|
336
|
+
def matches? node
|
337
|
+
node.is_a?(REXML::Element) and node.name==@name
|
338
|
+
end
|
339
|
+
|
340
|
+
def create_on(node,create_new)
|
341
|
+
node.elements.add @name
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|