xml-mapping 0.8.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|