oga 1.2.3-java → 1.3.0-java
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 +4 -4
- data/doc/css_selectors.md +1 -1
- data/lib/liboga.jar +0 -0
- data/lib/oga.rb +6 -1
- data/lib/oga/blacklist.rb +0 -10
- data/lib/oga/css/lexer.rb +530 -255
- data/lib/oga/css/parser.rb +232 -230
- data/lib/oga/entity_decoder.rb +0 -4
- data/lib/oga/html/entities.rb +0 -4
- data/lib/oga/html/parser.rb +0 -4
- data/lib/oga/html/sax_parser.rb +0 -4
- data/lib/oga/lru.rb +0 -26
- data/lib/oga/oga.rb +0 -8
- data/lib/oga/ruby/generator.rb +225 -0
- data/lib/oga/ruby/node.rb +189 -0
- data/lib/oga/version.rb +1 -1
- data/lib/oga/whitelist.rb +0 -6
- data/lib/oga/xml/attribute.rb +13 -20
- data/lib/oga/xml/cdata.rb +0 -4
- data/lib/oga/xml/character_node.rb +0 -8
- data/lib/oga/xml/comment.rb +0 -4
- data/lib/oga/xml/default_namespace.rb +0 -2
- data/lib/oga/xml/doctype.rb +0 -8
- data/lib/oga/xml/document.rb +10 -14
- data/lib/oga/xml/element.rb +1 -52
- data/lib/oga/xml/entities.rb +0 -26
- data/lib/oga/xml/expanded_name.rb +12 -0
- data/lib/oga/xml/html_void_elements.rb +0 -2
- data/lib/oga/xml/lexer.rb +0 -86
- data/lib/oga/xml/namespace.rb +0 -10
- data/lib/oga/xml/node.rb +18 -34
- data/lib/oga/xml/node_set.rb +0 -50
- data/lib/oga/xml/parser.rb +13 -50
- data/lib/oga/xml/processing_instruction.rb +0 -8
- data/lib/oga/xml/pull_parser.rb +0 -18
- data/lib/oga/xml/querying.rb +58 -19
- data/lib/oga/xml/sax_parser.rb +0 -18
- data/lib/oga/xml/text.rb +0 -12
- data/lib/oga/xml/traversal.rb +0 -4
- data/lib/oga/xml/xml_declaration.rb +0 -8
- data/lib/oga/xpath/compiler.rb +1568 -0
- data/lib/oga/xpath/conversion.rb +102 -0
- data/lib/oga/xpath/lexer.rb +1844 -1238
- data/lib/oga/xpath/parser.rb +182 -153
- metadata +7 -3
- data/lib/oga/xpath/evaluator.rb +0 -1800
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oga
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Yorick Peterse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -171,6 +171,8 @@ files:
|
|
171
171
|
- lib/oga/html/sax_parser.rb
|
172
172
|
- lib/oga/lru.rb
|
173
173
|
- lib/oga/oga.rb
|
174
|
+
- lib/oga/ruby/generator.rb
|
175
|
+
- lib/oga/ruby/node.rb
|
174
176
|
- lib/oga/version.rb
|
175
177
|
- lib/oga/whitelist.rb
|
176
178
|
- lib/oga/xml/attribute.rb
|
@@ -182,6 +184,7 @@ files:
|
|
182
184
|
- lib/oga/xml/document.rb
|
183
185
|
- lib/oga/xml/element.rb
|
184
186
|
- lib/oga/xml/entities.rb
|
187
|
+
- lib/oga/xml/expanded_name.rb
|
185
188
|
- lib/oga/xml/html_void_elements.rb
|
186
189
|
- lib/oga/xml/lexer.rb
|
187
190
|
- lib/oga/xml/namespace.rb
|
@@ -195,7 +198,8 @@ files:
|
|
195
198
|
- lib/oga/xml/text.rb
|
196
199
|
- lib/oga/xml/traversal.rb
|
197
200
|
- lib/oga/xml/xml_declaration.rb
|
198
|
-
- lib/oga/xpath/
|
201
|
+
- lib/oga/xpath/compiler.rb
|
202
|
+
- lib/oga/xpath/conversion.rb
|
199
203
|
- lib/oga/xpath/lexer.rb
|
200
204
|
- lib/oga/xpath/parser.rb
|
201
205
|
- oga.gemspec
|
data/lib/oga/xpath/evaluator.rb
DELETED
@@ -1,1800 +0,0 @@
|
|
1
|
-
module Oga
|
2
|
-
module XPath
|
3
|
-
##
|
4
|
-
# The Evaluator class evaluates XPath expressions, either as a String or an
|
5
|
-
# AST of `AST::Node` instances.
|
6
|
-
#
|
7
|
-
# ## Thread Safety
|
8
|
-
#
|
9
|
-
# This class is not thread-safe, you can not share the same instance between
|
10
|
-
# multiple threads. This is due to the use of an internal stack (see below
|
11
|
-
# for more information). It is however perfectly fine to use multiple
|
12
|
-
# separated instances as this class does not use a thread global state.
|
13
|
-
#
|
14
|
-
# ## Node Set Stack
|
15
|
-
#
|
16
|
-
# This class uses an internal stack of XML node sets. This stack is used for
|
17
|
-
# functions that require access to the set of nodes a predicate belongs to.
|
18
|
-
# An example of such a function is `position()`.
|
19
|
-
#
|
20
|
-
# An alternative would be to pass the node sets a predicate belongs to as an
|
21
|
-
# extra argument to the various `on_*` methods. The problematic part of
|
22
|
-
# this approach is that it requires every method to take and pass along the
|
23
|
-
# argument. It's far too easy to make mistakes in such a setup and as such
|
24
|
-
# I've chosen to use an internal stack instead.
|
25
|
-
#
|
26
|
-
# See {#with_node_set} and {#current_node_set} for more information.
|
27
|
-
#
|
28
|
-
# ## Set Indices
|
29
|
-
#
|
30
|
-
# XPath node sets start at index 1 instead of index 0. In other words, if
|
31
|
-
# you want to access the first node in a set you have to use index 1, not 0.
|
32
|
-
# Certain methods such as {#on_call_last} and {#on_call_position} take care
|
33
|
-
# of converting indices from Ruby to XPath.
|
34
|
-
#
|
35
|
-
# ## Number Types
|
36
|
-
#
|
37
|
-
# The XPath specification states that all numbers produced by an expression
|
38
|
-
# should be returned as double-precision 64bit IEEE 754 floating point
|
39
|
-
# numbers. For example, the return value of `position()` should be a float
|
40
|
-
# (e.g. "1.0", not "1").
|
41
|
-
#
|
42
|
-
# Oga takes care internally of converting numbers to integers and/or floats
|
43
|
-
# where needed. The output types however will always be floats.
|
44
|
-
#
|
45
|
-
# For more information on the specification, see
|
46
|
-
# <http://www.w3.org/TR/xpath/#numbers>.
|
47
|
-
#
|
48
|
-
# ## Variables
|
49
|
-
#
|
50
|
-
# The evaluator supports the binding of custom variables in the
|
51
|
-
# {#initialize} method. Variables can be bound by passing in a Hash with the
|
52
|
-
# keys set to the variable names (minus the `$` sign) and their values to
|
53
|
-
# the variable values. The keys of the variables Hash *must* be Strings.
|
54
|
-
#
|
55
|
-
# A basic example:
|
56
|
-
#
|
57
|
-
# evaluator = Evaluator.new(document, 'number' => 10)
|
58
|
-
#
|
59
|
-
# evaluator.evaluate('$number') # => 10
|
60
|
-
#
|
61
|
-
# @api private
|
62
|
-
#
|
63
|
-
class Evaluator
|
64
|
-
# Wildcard for node names/namespace prefixes.
|
65
|
-
STAR = '*'
|
66
|
-
|
67
|
-
##
|
68
|
-
# @param [Oga::XML::Document|Oga::XML::Node] document
|
69
|
-
# @param [Hash] variables Hash containing variables to expose to the XPath
|
70
|
-
# expressions.
|
71
|
-
#
|
72
|
-
def initialize(document, variables = {})
|
73
|
-
@document = document
|
74
|
-
@variables = variables
|
75
|
-
@node_sets = []
|
76
|
-
end
|
77
|
-
|
78
|
-
##
|
79
|
-
# Evaluates an XPath expression as a String.
|
80
|
-
#
|
81
|
-
# @example
|
82
|
-
# evaluator = Oga::XPath::Evaluator.new(document)
|
83
|
-
#
|
84
|
-
# evaluator.evaluate('//a')
|
85
|
-
#
|
86
|
-
# @param [String] string An XPath expression as a String.
|
87
|
-
# @return [Mixed]
|
88
|
-
#
|
89
|
-
def evaluate(string)
|
90
|
-
ast = Parser.parse_with_cache(string)
|
91
|
-
|
92
|
-
evaluate_ast(ast)
|
93
|
-
end
|
94
|
-
|
95
|
-
##
|
96
|
-
# Evaluates a pre-parsed XPath expression.
|
97
|
-
#
|
98
|
-
# @param [AST::Node] ast
|
99
|
-
# @return [Mixed]
|
100
|
-
#
|
101
|
-
def evaluate_ast(ast)
|
102
|
-
context = XML::NodeSet.new([@document])
|
103
|
-
|
104
|
-
process(ast, context)
|
105
|
-
end
|
106
|
-
|
107
|
-
##
|
108
|
-
# Processes an XPath node by dispatching it and the given context to a
|
109
|
-
# dedicated handler method. Handler methods are called "on_X" where "X" is
|
110
|
-
# the node type.
|
111
|
-
#
|
112
|
-
# @param [AST::Node] ast_node The XPath AST node to process.
|
113
|
-
#
|
114
|
-
# @param [Oga::XML::NodeSet] context The context (a set of nodes) to
|
115
|
-
# evaluate an expression in.
|
116
|
-
#
|
117
|
-
# @return [Oga::XML::NodeSet]
|
118
|
-
#
|
119
|
-
def process(ast_node, context)
|
120
|
-
handler = "on_#{ast_node.type}"
|
121
|
-
|
122
|
-
send(handler, ast_node, context)
|
123
|
-
end
|
124
|
-
|
125
|
-
##
|
126
|
-
# Processes an absolute XPath expression such as `/foo`.
|
127
|
-
#
|
128
|
-
# @param [AST::Node] ast_node
|
129
|
-
# @param [Oga::XML::NodeSet] context
|
130
|
-
# @return [Oga::XML::NodeSet]
|
131
|
-
#
|
132
|
-
def on_absolute_path(ast_node, context)
|
133
|
-
if @document.respond_to?(:root_node)
|
134
|
-
context = XML::NodeSet.new([@document.root_node])
|
135
|
-
else
|
136
|
-
context = XML::NodeSet.new([@document])
|
137
|
-
end
|
138
|
-
|
139
|
-
# If the expression is just "/" we'll just return the current context.
|
140
|
-
ast_node.children.empty? ? context : on_path(ast_node, context)
|
141
|
-
end
|
142
|
-
|
143
|
-
##
|
144
|
-
# Processes a relative XPath expression such as `foo`.
|
145
|
-
#
|
146
|
-
# Paths are evaluated using a "short-circuit" mechanism similar to Ruby's
|
147
|
-
# `&&` / `and` operator. Whenever a path results in an empty node set the
|
148
|
-
# evaluation is aborted immediately.
|
149
|
-
#
|
150
|
-
# @param [AST::Node] ast_node
|
151
|
-
# @param [Oga::XML::NodeSet] context
|
152
|
-
# @return [Oga::XML::NodeSet]
|
153
|
-
#
|
154
|
-
def on_path(ast_node, context)
|
155
|
-
nodes = XML::NodeSet.new
|
156
|
-
|
157
|
-
ast_node.children.each do |test|
|
158
|
-
nodes = process(test, context)
|
159
|
-
|
160
|
-
if nodes.empty?
|
161
|
-
break
|
162
|
-
else
|
163
|
-
context = nodes
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
nodes
|
168
|
-
end
|
169
|
-
|
170
|
-
##
|
171
|
-
# Processes a node test.
|
172
|
-
#
|
173
|
-
# @param [AST::Node] ast_node
|
174
|
-
# @param [Oga::XML::NodeSet] context
|
175
|
-
# @return [Oga::XML::NodeSet]
|
176
|
-
#
|
177
|
-
def on_test(ast_node, context)
|
178
|
-
nodes = XML::NodeSet.new
|
179
|
-
|
180
|
-
context.each do |xml_node|
|
181
|
-
nodes << xml_node if node_matches?(xml_node, ast_node)
|
182
|
-
end
|
183
|
-
|
184
|
-
nodes
|
185
|
-
end
|
186
|
-
|
187
|
-
##
|
188
|
-
# Processes a predicate.
|
189
|
-
#
|
190
|
-
# @param [AST::Node] ast_node
|
191
|
-
# @param [Oga::XML::NodeSet] context
|
192
|
-
# @return [Oga::XML::NodeSet]
|
193
|
-
#
|
194
|
-
def on_predicate(ast_node, context)
|
195
|
-
test, predicate = *ast_node.children
|
196
|
-
final_nodes = XML::NodeSet.new
|
197
|
-
|
198
|
-
context.each do |context_node|
|
199
|
-
initial_nodes = process(test, XML::NodeSet.new([context_node]))
|
200
|
-
xpath_index = 1
|
201
|
-
|
202
|
-
initial_nodes.each do |xml_node|
|
203
|
-
retval = with_node_set(initial_nodes) do
|
204
|
-
process(predicate, XML::NodeSet.new([xml_node]))
|
205
|
-
end
|
206
|
-
|
207
|
-
# Numeric values are used as node set indexes.
|
208
|
-
if retval.is_a?(Numeric)
|
209
|
-
final_nodes << xml_node if retval.to_i == xpath_index
|
210
|
-
|
211
|
-
# Node sets, strings, booleans, etc
|
212
|
-
elsif retval
|
213
|
-
if retval.respond_to?(:empty?) and retval.empty?
|
214
|
-
next
|
215
|
-
end
|
216
|
-
|
217
|
-
final_nodes << xml_node
|
218
|
-
end
|
219
|
-
|
220
|
-
xpath_index += 1
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
final_nodes
|
225
|
-
end
|
226
|
-
|
227
|
-
##
|
228
|
-
# Dispatches the processing of axes to dedicated methods. This works
|
229
|
-
# similar to {#process} except the handler names are "on_axis_X" with "X"
|
230
|
-
# being the axis name.
|
231
|
-
#
|
232
|
-
# @param [AST::Node] ast_node
|
233
|
-
# @param [Oga::XML::NodeSet] context
|
234
|
-
# @return [Oga::XML::NodeSet]
|
235
|
-
#
|
236
|
-
def on_axis(ast_node, context)
|
237
|
-
name, test = *ast_node.children
|
238
|
-
|
239
|
-
handler = name.gsub('-', '_')
|
240
|
-
|
241
|
-
send("on_axis_#{handler}", test, context)
|
242
|
-
end
|
243
|
-
|
244
|
-
##
|
245
|
-
# Processes the `ancestor` axis. This axis walks through the entire
|
246
|
-
# ancestor chain until a matching node is found.
|
247
|
-
#
|
248
|
-
# Evaluation happens using a "short-circuit" mechanism. The moment a
|
249
|
-
# matching node is found it is returned immediately.
|
250
|
-
#
|
251
|
-
# @param [AST::Node] ast_node
|
252
|
-
# @param [Oga::XML::NodeSet] context
|
253
|
-
# @return [Oga::XML::NodeSet]
|
254
|
-
#
|
255
|
-
def on_axis_ancestor(ast_node, context)
|
256
|
-
nodes = XML::NodeSet.new
|
257
|
-
|
258
|
-
context.each do |xml_node|
|
259
|
-
while has_parent?(xml_node)
|
260
|
-
xml_node = xml_node.parent
|
261
|
-
|
262
|
-
if node_matches?(xml_node, ast_node)
|
263
|
-
nodes << xml_node
|
264
|
-
break
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
nodes
|
270
|
-
end
|
271
|
-
|
272
|
-
##
|
273
|
-
# Processes the `ancestor-or-self` axis.
|
274
|
-
#
|
275
|
-
# @see [#on_axis_ancestor]
|
276
|
-
#
|
277
|
-
def on_axis_ancestor_or_self(ast_node, context)
|
278
|
-
nodes = XML::NodeSet.new
|
279
|
-
|
280
|
-
context.each do |xml_node|
|
281
|
-
while has_parent?(xml_node)
|
282
|
-
if node_matches?(xml_node, ast_node)
|
283
|
-
nodes << xml_node
|
284
|
-
break
|
285
|
-
end
|
286
|
-
|
287
|
-
xml_node = xml_node.parent
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
nodes
|
292
|
-
end
|
293
|
-
|
294
|
-
##
|
295
|
-
# Processes the `attribute` axis. The node test is performed against all
|
296
|
-
# the attributes of the nodes in the current context.
|
297
|
-
#
|
298
|
-
# Evaluation of the nodes continues until the node set has been exhausted
|
299
|
-
# (unlike some other methods which return the moment they find a matching
|
300
|
-
# node).
|
301
|
-
#
|
302
|
-
# @param [AST::Node] ast_node
|
303
|
-
# @param [Oga::XML::NodeSet] context
|
304
|
-
# @return [Oga::XML::NodeSet]
|
305
|
-
#
|
306
|
-
def on_axis_attribute(ast_node, context)
|
307
|
-
nodes = XML::NodeSet.new
|
308
|
-
|
309
|
-
context.each do |xml_node|
|
310
|
-
next unless xml_node.is_a?(XML::Element)
|
311
|
-
|
312
|
-
nodes += on_test(ast_node, xml_node.attributes)
|
313
|
-
end
|
314
|
-
|
315
|
-
nodes
|
316
|
-
end
|
317
|
-
|
318
|
-
##
|
319
|
-
# Evaluates the `child` axis. This axis simply takes all the child nodes
|
320
|
-
# of the current context nodes.
|
321
|
-
#
|
322
|
-
# @param [AST::Node] ast_node
|
323
|
-
# @param [Oga::XML::NodeSet] context
|
324
|
-
# @return [Oga::XML::NodeSet]
|
325
|
-
#
|
326
|
-
def on_axis_child(ast_node, context)
|
327
|
-
process(ast_node, child_nodes(context))
|
328
|
-
end
|
329
|
-
|
330
|
-
##
|
331
|
-
# Evaluates the `descendant` axis. This method processes child nodes until
|
332
|
-
# the very end of the tree, no "short-circuiting" mechanism is used.
|
333
|
-
#
|
334
|
-
# @param [AST::Node] ast_node
|
335
|
-
# @param [Oga::XML::NodeSet] context
|
336
|
-
# @return [Oga::XML::NodeSet]
|
337
|
-
#
|
338
|
-
def on_axis_descendant(ast_node, context)
|
339
|
-
nodes = XML::NodeSet.new
|
340
|
-
|
341
|
-
context.each do |context_node|
|
342
|
-
context_node.each_node do |node|
|
343
|
-
nodes.concat(process(ast_node, XML::NodeSet.new([node])))
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
nodes
|
348
|
-
end
|
349
|
-
|
350
|
-
##
|
351
|
-
# Evaluates the `descendant-or-self` axis.
|
352
|
-
#
|
353
|
-
# @param [AST::Node] ast_node
|
354
|
-
# @param [Oga::XML::NodeSet] context
|
355
|
-
# @return [Oga::XML::NodeSet]
|
356
|
-
#
|
357
|
-
def on_axis_descendant_or_self(ast_node, context)
|
358
|
-
nodes = on_test(ast_node, context)
|
359
|
-
|
360
|
-
nodes.concat(on_axis_descendant(ast_node, context))
|
361
|
-
|
362
|
-
nodes
|
363
|
-
end
|
364
|
-
|
365
|
-
##
|
366
|
-
# Evaluates the `following` axis.
|
367
|
-
#
|
368
|
-
# @param [AST::Node] ast_node
|
369
|
-
# @param [Oga::XML::NodeSet] context
|
370
|
-
# @return [Oga::XML::NodeSet]
|
371
|
-
#
|
372
|
-
def on_axis_following(ast_node, context)
|
373
|
-
nodes = XML::NodeSet.new
|
374
|
-
root = root_node(@document)
|
375
|
-
|
376
|
-
context.each do |context_node|
|
377
|
-
check = false
|
378
|
-
|
379
|
-
root.each_node do |doc_node|
|
380
|
-
# Skip child nodes of the current context node, compare all
|
381
|
-
# following nodes.
|
382
|
-
if doc_node == context_node
|
383
|
-
check = true
|
384
|
-
throw :skip_children
|
385
|
-
end
|
386
|
-
|
387
|
-
next unless check
|
388
|
-
|
389
|
-
nodes << doc_node if node_matches?(doc_node, ast_node)
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
|
-
nodes
|
394
|
-
end
|
395
|
-
|
396
|
-
##
|
397
|
-
# Evaluates the `following-sibling` axis.
|
398
|
-
#
|
399
|
-
# @param [AST::Node] ast_node
|
400
|
-
# @param [Oga::XML::NodeSet] context
|
401
|
-
# @return [Oga::XML::NodeSet]
|
402
|
-
#
|
403
|
-
def on_axis_following_sibling(ast_node, context)
|
404
|
-
nodes = XML::NodeSet.new
|
405
|
-
root = parent_node(@document)
|
406
|
-
|
407
|
-
context.each do |context_node|
|
408
|
-
check = false
|
409
|
-
parent = has_parent?(context_node) ? context_node.parent : nil
|
410
|
-
|
411
|
-
root.each_node do |doc_node|
|
412
|
-
# Skip child nodes of the current context node, compare all
|
413
|
-
# following nodes.
|
414
|
-
if doc_node == context_node
|
415
|
-
check = true
|
416
|
-
throw :skip_children
|
417
|
-
end
|
418
|
-
|
419
|
-
if !check or parent != doc_node.parent
|
420
|
-
next
|
421
|
-
end
|
422
|
-
|
423
|
-
if node_matches?(doc_node, ast_node)
|
424
|
-
nodes << doc_node
|
425
|
-
|
426
|
-
throw :skip_children
|
427
|
-
end
|
428
|
-
end
|
429
|
-
end
|
430
|
-
|
431
|
-
nodes
|
432
|
-
end
|
433
|
-
|
434
|
-
##
|
435
|
-
# Evaluates the `parent` axis.
|
436
|
-
#
|
437
|
-
# @param [AST::Node] ast_node
|
438
|
-
# @param [Oga::XML::NodeSet] context
|
439
|
-
# @return [Oga::XML::NodeSet]
|
440
|
-
#
|
441
|
-
def on_axis_parent(ast_node, context)
|
442
|
-
nodes = XML::NodeSet.new
|
443
|
-
|
444
|
-
context.each do |context_node|
|
445
|
-
next unless has_parent?(context_node)
|
446
|
-
|
447
|
-
parent = context_node.parent
|
448
|
-
|
449
|
-
nodes << parent if node_matches?(parent, ast_node)
|
450
|
-
end
|
451
|
-
|
452
|
-
nodes
|
453
|
-
end
|
454
|
-
|
455
|
-
##
|
456
|
-
# Evaluates the `preceding` axis.
|
457
|
-
#
|
458
|
-
# @param [AST::Node] ast_node
|
459
|
-
# @param [Oga::XML::NodeSet] context
|
460
|
-
# @return [Oga::XML::NodeSet]
|
461
|
-
#
|
462
|
-
def on_axis_preceding(ast_node, context)
|
463
|
-
nodes = XML::NodeSet.new
|
464
|
-
root = root_node(@document)
|
465
|
-
|
466
|
-
context.each do |context_node|
|
467
|
-
check = true
|
468
|
-
|
469
|
-
root.each_node do |doc_node|
|
470
|
-
# Test everything *until* we hit the current context node.
|
471
|
-
if doc_node == context_node
|
472
|
-
break
|
473
|
-
elsif node_matches?(doc_node, ast_node)
|
474
|
-
nodes << doc_node
|
475
|
-
end
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
nodes
|
480
|
-
end
|
481
|
-
|
482
|
-
##
|
483
|
-
# Evaluates the `preceding-sibling` axis.
|
484
|
-
#
|
485
|
-
# @param [AST::Node] ast_node
|
486
|
-
# @param [Oga::XML::NodeSet] context
|
487
|
-
# @return [Oga::XML::NodeSet]
|
488
|
-
#
|
489
|
-
def on_axis_preceding_sibling(ast_node, context)
|
490
|
-
nodes = XML::NodeSet.new
|
491
|
-
root = parent_node(@document)
|
492
|
-
|
493
|
-
context.each do |context_node|
|
494
|
-
check = true
|
495
|
-
parent = has_parent?(context_node) ? context_node.parent : nil
|
496
|
-
|
497
|
-
root.each_node do |doc_node|
|
498
|
-
# Test everything *until* we hit the current context node.
|
499
|
-
if doc_node == context_node
|
500
|
-
break
|
501
|
-
elsif doc_node.parent == parent and node_matches?(doc_node, ast_node)
|
502
|
-
nodes << doc_node
|
503
|
-
end
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
nodes
|
508
|
-
end
|
509
|
-
|
510
|
-
##
|
511
|
-
# Evaluates the `self` axis.
|
512
|
-
#
|
513
|
-
# @param [AST::Node] ast_node
|
514
|
-
# @param [Oga::XML::NodeSet] context
|
515
|
-
# @return [Oga::XML::NodeSet]
|
516
|
-
#
|
517
|
-
def on_axis_self(ast_node, context)
|
518
|
-
nodes = XML::NodeSet.new
|
519
|
-
|
520
|
-
context.each do |context_node|
|
521
|
-
nodes << context_node if node_matches?(context_node, ast_node)
|
522
|
-
end
|
523
|
-
|
524
|
-
nodes
|
525
|
-
end
|
526
|
-
|
527
|
-
##
|
528
|
-
# Evaluates the `namespace` axis.
|
529
|
-
#
|
530
|
-
# @param [AST::Node] ast_node
|
531
|
-
# @param [Oga::XML::NodeSet] context
|
532
|
-
# @return [Oga::XML::NodeSet]
|
533
|
-
#
|
534
|
-
def on_axis_namespace(ast_node, context)
|
535
|
-
nodes = XML::NodeSet.new
|
536
|
-
name = ast_node.children[1]
|
537
|
-
|
538
|
-
context.each do |context_node|
|
539
|
-
next unless context_node.respond_to?(:available_namespaces)
|
540
|
-
|
541
|
-
context_node.available_namespaces.each do |_, namespace|
|
542
|
-
if namespace.name == name or name == STAR
|
543
|
-
nodes << namespace
|
544
|
-
end
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
nodes
|
549
|
-
end
|
550
|
-
|
551
|
-
##
|
552
|
-
# Dispatches node type matching to dedicated handlers.
|
553
|
-
#
|
554
|
-
# @param [AST::Node] ast_node
|
555
|
-
# @param [Oga::XML::NodeSet] context
|
556
|
-
# @return [Oga::XML::NodeSet]
|
557
|
-
#
|
558
|
-
def on_type_test(ast_node, context)
|
559
|
-
name, test = *ast_node.children
|
560
|
-
|
561
|
-
handler = name.gsub('-', '_')
|
562
|
-
|
563
|
-
send("on_type_test_#{handler}", test, context)
|
564
|
-
end
|
565
|
-
|
566
|
-
##
|
567
|
-
# Processes the `node` type matcher. This matcher matches all node types.
|
568
|
-
#
|
569
|
-
# @param [AST::Node] ast_node
|
570
|
-
# @param [Oga::XML::NodeSet] context
|
571
|
-
# @return [Oga::XML::NodeSet]
|
572
|
-
#
|
573
|
-
def on_type_test_node(ast_node, context)
|
574
|
-
nodes = XML::NodeSet.new
|
575
|
-
|
576
|
-
context.each do |node|
|
577
|
-
if node.is_a?(XML::Node) or node.is_a?(XML::Document)
|
578
|
-
nodes << node
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
nodes
|
583
|
-
end
|
584
|
-
|
585
|
-
##
|
586
|
-
# Processes the `text()` type test. This matches only text nodes.
|
587
|
-
#
|
588
|
-
# @param [AST::Node] ast_node
|
589
|
-
# @param [Oga::XML::NodeSet] context
|
590
|
-
# @return [Oga::XML::NodeSet]
|
591
|
-
#
|
592
|
-
def on_type_test_text(ast_node, context)
|
593
|
-
nodes = XML::NodeSet.new
|
594
|
-
|
595
|
-
context.each do |node|
|
596
|
-
nodes << node if node.is_a?(XML::Text)
|
597
|
-
end
|
598
|
-
|
599
|
-
nodes
|
600
|
-
end
|
601
|
-
|
602
|
-
##
|
603
|
-
# Processes the `comment()` type test. This matches only comment nodes.
|
604
|
-
#
|
605
|
-
# @param [AST::Node] ast_node
|
606
|
-
# @param [Oga::XML::NodeSet] context
|
607
|
-
# @return [Oga::XML::NodeSet]
|
608
|
-
#
|
609
|
-
def on_type_test_comment(ast_node, context)
|
610
|
-
nodes = XML::NodeSet.new
|
611
|
-
|
612
|
-
context.each do |node|
|
613
|
-
nodes << node if node.is_a?(XML::Comment)
|
614
|
-
end
|
615
|
-
|
616
|
-
nodes
|
617
|
-
end
|
618
|
-
|
619
|
-
##
|
620
|
-
# Processes the `processing-instruction()` type test. This matches only
|
621
|
-
# processing-instruction nodes.
|
622
|
-
#
|
623
|
-
# @param [AST::Node] ast_node
|
624
|
-
# @param [Oga::XML::NodeSet] context
|
625
|
-
# @return [Oga::XML::NodeSet]
|
626
|
-
#
|
627
|
-
def on_type_test_processing_instruction(ast_node, context)
|
628
|
-
nodes = XML::NodeSet.new
|
629
|
-
|
630
|
-
context.each do |node|
|
631
|
-
nodes << node if node.is_a?(XML::ProcessingInstruction)
|
632
|
-
end
|
633
|
-
|
634
|
-
nodes
|
635
|
-
end
|
636
|
-
|
637
|
-
##
|
638
|
-
# Processes the pipe (`|`) operator. This operator creates a union of two
|
639
|
-
# sets.
|
640
|
-
#
|
641
|
-
# @param [AST::Node] ast_node
|
642
|
-
# @param [Oga::XML::NodeSet] context
|
643
|
-
# @return [Oga::XML::NodeSet]
|
644
|
-
#
|
645
|
-
def on_pipe(ast_node, context)
|
646
|
-
left, right = *ast_node.children
|
647
|
-
|
648
|
-
process(left, context) + process(right, context)
|
649
|
-
end
|
650
|
-
|
651
|
-
##
|
652
|
-
# Processes the `and` operator.
|
653
|
-
#
|
654
|
-
# This operator returns true if both the left and right expression
|
655
|
-
# evaluate to `true`. If the first expression evaluates to `false` the
|
656
|
-
# right expression is ignored.
|
657
|
-
#
|
658
|
-
# @param [AST::Node] ast_node
|
659
|
-
# @param [Oga::XML::NodeSet] context
|
660
|
-
# @return [TrueClass|FalseClass]
|
661
|
-
#
|
662
|
-
def on_and(ast_node, context)
|
663
|
-
left, right = *ast_node.children
|
664
|
-
|
665
|
-
on_call_boolean(context, left) && on_call_boolean(context, right)
|
666
|
-
end
|
667
|
-
|
668
|
-
##
|
669
|
-
# Processes the `or` operator.
|
670
|
-
#
|
671
|
-
# This operator returns `true` if one of the expressions evaluates to
|
672
|
-
# true, otherwise false is returned. If the first expression evaluates to
|
673
|
-
# `true` the second expression is ignored.
|
674
|
-
#
|
675
|
-
# @param [AST::Node] ast_node
|
676
|
-
# @param [Oga::XML::NodeSet] context
|
677
|
-
# @return [TrueClass|FalseClass]
|
678
|
-
#
|
679
|
-
def on_or(ast_node, context)
|
680
|
-
left, right = *ast_node.children
|
681
|
-
|
682
|
-
on_call_boolean(context, left) || on_call_boolean(context, right)
|
683
|
-
end
|
684
|
-
|
685
|
-
##
|
686
|
-
# Processes the `+` operator.
|
687
|
-
#
|
688
|
-
# This operator converts the left and right expressions to numbers and
|
689
|
-
# adds them together.
|
690
|
-
#
|
691
|
-
# @param [AST::Node] ast_node
|
692
|
-
# @param [Oga::XML::NodeSet] context
|
693
|
-
# @return [Float]
|
694
|
-
#
|
695
|
-
def on_add(ast_node, context)
|
696
|
-
left, right = *ast_node.children
|
697
|
-
|
698
|
-
on_call_number(context, left) + on_call_number(context, right)
|
699
|
-
end
|
700
|
-
|
701
|
-
##
|
702
|
-
# Processes the `div` operator.
|
703
|
-
#
|
704
|
-
# This operator converts the left and right expressions to numbers and
|
705
|
-
# divides the left number with the right number.
|
706
|
-
#
|
707
|
-
# @param [AST::Node] ast_node
|
708
|
-
# @param [Oga::XML::NodeSet] context
|
709
|
-
# @return [Float]
|
710
|
-
#
|
711
|
-
def on_div(ast_node, context)
|
712
|
-
left, right = *ast_node.children
|
713
|
-
|
714
|
-
on_call_number(context, left) / on_call_number(context, right)
|
715
|
-
end
|
716
|
-
|
717
|
-
##
|
718
|
-
# Processes the `mod` operator.
|
719
|
-
#
|
720
|
-
# This operator converts the left and right expressions to numbers and
|
721
|
-
# returns the modulo of the two numbers.
|
722
|
-
#
|
723
|
-
# @param [AST::Node] ast_node
|
724
|
-
# @param [Oga::XML::NodeSet] context
|
725
|
-
# @return [Float]
|
726
|
-
#
|
727
|
-
def on_mod(ast_node, context)
|
728
|
-
left, right = *ast_node.children
|
729
|
-
|
730
|
-
on_call_number(context, left) % on_call_number(context, right)
|
731
|
-
end
|
732
|
-
|
733
|
-
##
|
734
|
-
# Processes the `*` operator.
|
735
|
-
#
|
736
|
-
# This operator converts the left and right expressions to numbers and
|
737
|
-
# multiplies the left number with the right number.
|
738
|
-
#
|
739
|
-
# @param [AST::Node] ast_node
|
740
|
-
# @param [Oga::XML::NodeSet] context
|
741
|
-
# @return [Float]
|
742
|
-
#
|
743
|
-
def on_mul(ast_node, context)
|
744
|
-
left, right = *ast_node.children
|
745
|
-
|
746
|
-
on_call_number(context, left) * on_call_number(context, right)
|
747
|
-
end
|
748
|
-
|
749
|
-
##
|
750
|
-
# Processes the `-` operator.
|
751
|
-
#
|
752
|
-
# This operator converts the left and right expressions to numbers and
|
753
|
-
# subtracts the right number of the left number.
|
754
|
-
#
|
755
|
-
# @param [AST::Node] ast_node
|
756
|
-
# @param [Oga::XML::NodeSet] context
|
757
|
-
# @return [Float]
|
758
|
-
#
|
759
|
-
def on_sub(ast_node, context)
|
760
|
-
left, right = *ast_node.children
|
761
|
-
|
762
|
-
on_call_number(context, left) - on_call_number(context, right)
|
763
|
-
end
|
764
|
-
|
765
|
-
##
|
766
|
-
# Processes the `=` operator.
|
767
|
-
#
|
768
|
-
# This operator evaluates the expression on the left and right and returns
|
769
|
-
# `true` if they are equal. This operator can be used to compare strings,
|
770
|
-
# numbers and node sets. When using node sets the text of the set is
|
771
|
-
# compared instead of the nodes themselves. That is, nodes with different
|
772
|
-
# names but the same text are considered to be equal.
|
773
|
-
#
|
774
|
-
# @param [AST::Node] ast_node
|
775
|
-
# @param [Oga::XML::NodeSet] context
|
776
|
-
# @return [TrueClass|FalseClass]
|
777
|
-
#
|
778
|
-
def on_eq(ast_node, context)
|
779
|
-
left = process(ast_node.children[0], context)
|
780
|
-
right = process(ast_node.children[1], context)
|
781
|
-
|
782
|
-
if left.is_a?(XML::NodeSet)
|
783
|
-
left = first_node_text(left)
|
784
|
-
end
|
785
|
-
|
786
|
-
if right.is_a?(XML::NodeSet)
|
787
|
-
right = first_node_text(right)
|
788
|
-
end
|
789
|
-
|
790
|
-
if left.is_a?(Numeric) and !right.is_a?(Numeric)
|
791
|
-
right = to_float(right)
|
792
|
-
end
|
793
|
-
|
794
|
-
if left.is_a?(String) and !right.is_a?(String)
|
795
|
-
right = to_string(right)
|
796
|
-
end
|
797
|
-
|
798
|
-
left == right
|
799
|
-
end
|
800
|
-
|
801
|
-
##
|
802
|
-
# Processes the `!=` operator.
|
803
|
-
#
|
804
|
-
# This operator does the exact opposite of the `=` operator. See {#on_eq}
|
805
|
-
# for more information.
|
806
|
-
#
|
807
|
-
# @see [#on_eq]
|
808
|
-
#
|
809
|
-
def on_neq(ast_node, context)
|
810
|
-
!on_eq(ast_node, context)
|
811
|
-
end
|
812
|
-
|
813
|
-
##
|
814
|
-
# Processes the `<` operator.
|
815
|
-
#
|
816
|
-
# This operator converts the left and right expression to a number and
|
817
|
-
# returns `true` if the first number is lower than the second number.
|
818
|
-
#
|
819
|
-
# @param [Oga::XML::Node] ast_node
|
820
|
-
# @param [Oga::XML::NodeSet] context
|
821
|
-
# @return [TrueClass|FalseClass]
|
822
|
-
#
|
823
|
-
def on_lt(ast_node, context)
|
824
|
-
left, right = *ast_node.children
|
825
|
-
|
826
|
-
on_call_number(context, left) < on_call_number(context, right)
|
827
|
-
end
|
828
|
-
|
829
|
-
##
|
830
|
-
# Processes the `>` operator.
|
831
|
-
#
|
832
|
-
# This operator converts the left and right expression to a number and
|
833
|
-
# returns `true` if the first number is greater than the second number.
|
834
|
-
#
|
835
|
-
# @param [Oga::XML::Node] ast_node
|
836
|
-
# @param [Oga::XML::NodeSet] context
|
837
|
-
# @return [TrueClass|FalseClass]
|
838
|
-
#
|
839
|
-
def on_gt(ast_node, context)
|
840
|
-
left, right = *ast_node.children
|
841
|
-
|
842
|
-
on_call_number(context, left) > on_call_number(context, right)
|
843
|
-
end
|
844
|
-
|
845
|
-
##
|
846
|
-
# Processes the `<=` operator.
|
847
|
-
#
|
848
|
-
# This operator converts the left and right expression to a number and
|
849
|
-
# returns `true` if the first number is lower-than or equal to the second
|
850
|
-
# number.
|
851
|
-
#
|
852
|
-
# @param [Oga::XML::Node] ast_node
|
853
|
-
# @param [Oga::XML::NodeSet] context
|
854
|
-
# @return [TrueClass|FalseClass]
|
855
|
-
#
|
856
|
-
def on_lte(ast_node, context)
|
857
|
-
left, right = *ast_node.children
|
858
|
-
|
859
|
-
on_call_number(context, left) <= on_call_number(context, right)
|
860
|
-
end
|
861
|
-
|
862
|
-
##
|
863
|
-
# Processes the `>=` operator.
|
864
|
-
#
|
865
|
-
# This operator converts the left and right expression to a number and
|
866
|
-
# returns `true` if the first number is greater-than or equal to the
|
867
|
-
# second number.
|
868
|
-
#
|
869
|
-
# @param [Oga::XML::Node] ast_node
|
870
|
-
# @param [Oga::XML::NodeSet] context
|
871
|
-
# @return [TrueClass|FalseClass]
|
872
|
-
#
|
873
|
-
def on_gte(ast_node, context)
|
874
|
-
left, right = *ast_node.children
|
875
|
-
|
876
|
-
on_call_number(context, left) >= on_call_number(context, right)
|
877
|
-
end
|
878
|
-
|
879
|
-
##
|
880
|
-
# Delegates function calls to specific handlers.
|
881
|
-
#
|
882
|
-
# Handler functions take two arguments:
|
883
|
-
#
|
884
|
-
# 1. The context node set
|
885
|
-
# 2. A variable list of XPath function arguments, passed as individual
|
886
|
-
# Ruby method arguments.
|
887
|
-
#
|
888
|
-
# @param [AST::Node] ast_node
|
889
|
-
# @param [Oga::XML::NodeSet] context
|
890
|
-
# @return [Oga::XML::NodeSet]
|
891
|
-
#
|
892
|
-
def on_call(ast_node, context)
|
893
|
-
name, *args = *ast_node.children
|
894
|
-
|
895
|
-
handler = name.gsub('-', '_')
|
896
|
-
|
897
|
-
send("on_call_#{handler}", context, *args)
|
898
|
-
end
|
899
|
-
|
900
|
-
##
|
901
|
-
# Processes the `last()` function call. This function call returns the
|
902
|
-
# index of the last node in the current set.
|
903
|
-
#
|
904
|
-
# @param [Oga::XML::NodeSet] context
|
905
|
-
# @return [Float]
|
906
|
-
#
|
907
|
-
def on_call_last(context)
|
908
|
-
# XPath uses indexes 1 to N instead of 0 to N.
|
909
|
-
current_node_set.length.to_f
|
910
|
-
end
|
911
|
-
|
912
|
-
##
|
913
|
-
# Processes the `position()` function call. This function returns the
|
914
|
-
# position of the current node in the current node set.
|
915
|
-
#
|
916
|
-
# @param [Oga::XML::NodeSet] context
|
917
|
-
# @return [Float]
|
918
|
-
#
|
919
|
-
def on_call_position(context)
|
920
|
-
index = current_node_set.index(context.first) + 1
|
921
|
-
|
922
|
-
index.to_f
|
923
|
-
end
|
924
|
-
|
925
|
-
##
|
926
|
-
# Processes the `count()` function call. This function counts the amount
|
927
|
-
# of nodes in `expression` and returns the result as a float.
|
928
|
-
#
|
929
|
-
# @param [Oga::XML::NodeSet] context
|
930
|
-
# @param [AST::Node] expression
|
931
|
-
# @return [Float]
|
932
|
-
#
|
933
|
-
def on_call_count(context, expression)
|
934
|
-
retval = process(expression, context)
|
935
|
-
|
936
|
-
unless retval.is_a?(XML::NodeSet)
|
937
|
-
raise TypeError, 'count() can only operate on NodeSet instances'
|
938
|
-
end
|
939
|
-
|
940
|
-
retval.length.to_f
|
941
|
-
end
|
942
|
-
|
943
|
-
##
|
944
|
-
# Processes the `id()` function call.
|
945
|
-
#
|
946
|
-
# The XPath specification states that this function's behaviour should be
|
947
|
-
# controlled by a DTD. If a DTD were to specify that the ID attribute for
|
948
|
-
# a certain element would be "foo" then this function should use said
|
949
|
-
# attribute.
|
950
|
-
#
|
951
|
-
# Oga does not support DTD parsing/evaluation and as such always uses the
|
952
|
-
# "id" attribute.
|
953
|
-
#
|
954
|
-
# This function searches the entire document for a matching node,
|
955
|
-
# regardless of the current position.
|
956
|
-
#
|
957
|
-
# @param [Oga::XML::NodeSet] context
|
958
|
-
# @param [AST::Node] expression
|
959
|
-
# @return [Oga::XML::NodeSet]
|
960
|
-
#
|
961
|
-
def on_call_id(context, expression)
|
962
|
-
id = process(expression, context)
|
963
|
-
nodes = XML::NodeSet.new
|
964
|
-
|
965
|
-
# Based on Nokogiri's/libxml behaviour it appears that when using a node
|
966
|
-
# set the text of the set is used as the ID.
|
967
|
-
id = id.is_a?(XML::NodeSet) ? id.text : id.to_s
|
968
|
-
ids = id.split(' ')
|
969
|
-
|
970
|
-
@document.each_node do |node|
|
971
|
-
next unless node.is_a?(XML::Element)
|
972
|
-
|
973
|
-
attr = node.attribute('id')
|
974
|
-
|
975
|
-
if attr and ids.include?(attr.value)
|
976
|
-
nodes << node
|
977
|
-
end
|
978
|
-
end
|
979
|
-
|
980
|
-
nodes
|
981
|
-
end
|
982
|
-
|
983
|
-
##
|
984
|
-
# Processes the `local-name()` function call.
|
985
|
-
#
|
986
|
-
# This function call returns the name of one of the following:
|
987
|
-
#
|
988
|
-
# * The current context node (if any)
|
989
|
-
# * The first node in the supplied node set
|
990
|
-
#
|
991
|
-
# @param [Oga::XML::NodeSet] context
|
992
|
-
# @param [AST::Node] expression
|
993
|
-
# @return [Oga::XML::NodeSet]
|
994
|
-
#
|
995
|
-
def on_call_local_name(context, expression = nil)
|
996
|
-
node = function_node(context, expression)
|
997
|
-
|
998
|
-
node.respond_to?(:name) ? node.name : ''
|
999
|
-
end
|
1000
|
-
|
1001
|
-
##
|
1002
|
-
# Processes the `name()` function call.
|
1003
|
-
#
|
1004
|
-
# This function call is similar to `local-name()` (see
|
1005
|
-
# {#on_call_local_name}) except that it includes the namespace name if
|
1006
|
-
# present.
|
1007
|
-
#
|
1008
|
-
# @param [Oga::XML::NodeSet] context
|
1009
|
-
# @param [AST::Node] expression
|
1010
|
-
# @return [Oga::XML::NodeSet]
|
1011
|
-
#
|
1012
|
-
def on_call_name(context, expression = nil)
|
1013
|
-
node = function_node(context, expression)
|
1014
|
-
|
1015
|
-
if node.respond_to?(:name) and node.respond_to?(:namespace)
|
1016
|
-
if node.namespace
|
1017
|
-
return "#{node.namespace.name}:#{node.name}"
|
1018
|
-
else
|
1019
|
-
return node.name
|
1020
|
-
end
|
1021
|
-
else
|
1022
|
-
return ''
|
1023
|
-
end
|
1024
|
-
end
|
1025
|
-
|
1026
|
-
##
|
1027
|
-
# Processes the `namespace-uri()` function call.
|
1028
|
-
#
|
1029
|
-
# This function call returns the namespace URI of one of the following:
|
1030
|
-
#
|
1031
|
-
# * The current context node (if any)
|
1032
|
-
# * The first node in the supplied node set
|
1033
|
-
#
|
1034
|
-
# @param [Oga::XML::NodeSet] context
|
1035
|
-
# @param [AST::Node] expression
|
1036
|
-
# @return [Oga::XML::NodeSet]
|
1037
|
-
#
|
1038
|
-
def on_call_namespace_uri(context, expression = nil)
|
1039
|
-
node = function_node(context, expression)
|
1040
|
-
|
1041
|
-
if node.respond_to?(:namespace) and node.namespace
|
1042
|
-
return node.namespace.uri
|
1043
|
-
else
|
1044
|
-
return ''
|
1045
|
-
end
|
1046
|
-
end
|
1047
|
-
|
1048
|
-
##
|
1049
|
-
# Evaluates the `string()` function call.
|
1050
|
-
#
|
1051
|
-
# This function call converts the given argument *or* the current context
|
1052
|
-
# node to a string. If a node set is given then only the first node is
|
1053
|
-
# converted to a string.
|
1054
|
-
#
|
1055
|
-
# @example
|
1056
|
-
# string(10) # => "10"
|
1057
|
-
#
|
1058
|
-
# @param [Oga::XML::NodeSet] context
|
1059
|
-
# @param [AST::Node] expression
|
1060
|
-
# @return [String]
|
1061
|
-
#
|
1062
|
-
def on_call_string(context, expression = nil)
|
1063
|
-
if expression
|
1064
|
-
convert = process(expression, context)
|
1065
|
-
|
1066
|
-
if convert.is_a?(XML::NodeSet)
|
1067
|
-
convert = convert[0]
|
1068
|
-
end
|
1069
|
-
else
|
1070
|
-
convert = context.first
|
1071
|
-
end
|
1072
|
-
|
1073
|
-
if convert.respond_to?(:text)
|
1074
|
-
return convert.text
|
1075
|
-
else
|
1076
|
-
return to_string(convert)
|
1077
|
-
end
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
##
|
1081
|
-
# Evaluates the `number()` function call.
|
1082
|
-
#
|
1083
|
-
# This function call converts its first argument *or* the current context
|
1084
|
-
# node to a number, similar to the `string()` function.
|
1085
|
-
#
|
1086
|
-
# @example
|
1087
|
-
# number("10") # => 10.0
|
1088
|
-
#
|
1089
|
-
# @see [#on_call_string]
|
1090
|
-
# @param [Oga::XML::NodeSet] context
|
1091
|
-
# @param [AST::Node] expression
|
1092
|
-
# @return [Float]
|
1093
|
-
#
|
1094
|
-
def on_call_number(context, expression = nil)
|
1095
|
-
convert = nil
|
1096
|
-
|
1097
|
-
if expression
|
1098
|
-
exp_retval = process(expression, context)
|
1099
|
-
|
1100
|
-
if exp_retval.is_a?(XML::NodeSet)
|
1101
|
-
convert = first_node_text(exp_retval)
|
1102
|
-
|
1103
|
-
elsif exp_retval == true
|
1104
|
-
convert = 1.0
|
1105
|
-
|
1106
|
-
elsif exp_retval == false
|
1107
|
-
convert = 0.0
|
1108
|
-
|
1109
|
-
elsif exp_retval
|
1110
|
-
convert = exp_retval
|
1111
|
-
end
|
1112
|
-
else
|
1113
|
-
convert = context.first.text
|
1114
|
-
end
|
1115
|
-
|
1116
|
-
to_float(convert)
|
1117
|
-
end
|
1118
|
-
|
1119
|
-
##
|
1120
|
-
# Processes the `concat()` function call.
|
1121
|
-
#
|
1122
|
-
# This function call converts its arguments to strings and concatenates
|
1123
|
-
# them. In case of node sets the text of the set is used.
|
1124
|
-
#
|
1125
|
-
# @param [Oga::XML::NodeSet] context
|
1126
|
-
# @param [AST::Node] first
|
1127
|
-
# @param [AST::Node] second
|
1128
|
-
# @param [Array<AST::Node>] rest
|
1129
|
-
#
|
1130
|
-
def on_call_concat(context, first, second, *rest)
|
1131
|
-
args = [first, second] + rest
|
1132
|
-
retval = ''
|
1133
|
-
|
1134
|
-
args.each do |arg|
|
1135
|
-
retval << on_call_string(context, arg)
|
1136
|
-
end
|
1137
|
-
|
1138
|
-
retval
|
1139
|
-
end
|
1140
|
-
|
1141
|
-
##
|
1142
|
-
# Processes the `starts-with()` function call.
|
1143
|
-
#
|
1144
|
-
# This function call returns `true` if the string in the 1st argument
|
1145
|
-
# starts with the string in the 2nd argument. Node sets can also be used.
|
1146
|
-
#
|
1147
|
-
# @example
|
1148
|
-
# starts-with("hello world", "hello") # => true
|
1149
|
-
#
|
1150
|
-
# @param [Oga::XML::NodeSet] context
|
1151
|
-
# @param [AST::Node] haystack The string to search.
|
1152
|
-
# @param [AST::Node] needle The string to search for.
|
1153
|
-
# @return [TrueClass|FalseClass]
|
1154
|
-
#
|
1155
|
-
def on_call_starts_with(context, haystack, needle)
|
1156
|
-
haystack_str = on_call_string(context, haystack)
|
1157
|
-
needle_str = on_call_string(context, needle)
|
1158
|
-
|
1159
|
-
# https://github.com/jruby/jruby/issues/1923
|
1160
|
-
needle_str.empty? || haystack_str.start_with?(needle_str)
|
1161
|
-
end
|
1162
|
-
|
1163
|
-
##
|
1164
|
-
# Processes the `contains()` function call.
|
1165
|
-
#
|
1166
|
-
# This function call returns `true` if the string in the 1st argument
|
1167
|
-
# contains the string in the 2nd argument. Node sets can also be used.
|
1168
|
-
#
|
1169
|
-
# @example
|
1170
|
-
# contains("hello world", "o w") # => true
|
1171
|
-
#
|
1172
|
-
# @param [Oga::XML::NodeSet] context
|
1173
|
-
# @param [AST::Node] haystack The string to search.
|
1174
|
-
# @param [AST::Node] needle The string to search for.
|
1175
|
-
# @return [String]
|
1176
|
-
#
|
1177
|
-
def on_call_contains(context, haystack, needle)
|
1178
|
-
haystack_str = on_call_string(context, haystack)
|
1179
|
-
needle_str = on_call_string(context, needle)
|
1180
|
-
|
1181
|
-
haystack_str.include?(needle_str)
|
1182
|
-
end
|
1183
|
-
|
1184
|
-
##
|
1185
|
-
# Processes the `substring-before()` function call.
|
1186
|
-
#
|
1187
|
-
# This function call returns the substring of the 1st argument that occurs
|
1188
|
-
# before the string given in the 2nd argument. For example:
|
1189
|
-
#
|
1190
|
-
# substring-before("2014-08-25", "-")
|
1191
|
-
#
|
1192
|
-
# This would return "2014" as it occurs before the first "-".
|
1193
|
-
#
|
1194
|
-
# @param [Oga::XML::NodeSet] context
|
1195
|
-
# @param [AST::Node] haystack The string to search.
|
1196
|
-
# @param [AST::Node] needle The string to search for.
|
1197
|
-
# @return [String]
|
1198
|
-
#
|
1199
|
-
def on_call_substring_before(context, haystack, needle)
|
1200
|
-
haystack_str = on_call_string(context, haystack)
|
1201
|
-
needle_str = on_call_string(context, needle)
|
1202
|
-
|
1203
|
-
before, sep, after = haystack_str.partition(needle_str)
|
1204
|
-
|
1205
|
-
sep.empty? ? sep : before
|
1206
|
-
end
|
1207
|
-
|
1208
|
-
##
|
1209
|
-
# Processes the `substring-after()` function call.
|
1210
|
-
#
|
1211
|
-
# This function call returns the substring of the 1st argument that occurs
|
1212
|
-
# after the string given in the 2nd argument. For example:
|
1213
|
-
#
|
1214
|
-
# substring-after("2014-08-25", "-")
|
1215
|
-
#
|
1216
|
-
# This would return "08-25" as it occurs after the first "-".
|
1217
|
-
#
|
1218
|
-
# @param [Oga::XML::NodeSet] context
|
1219
|
-
# @param [AST::Node] haystack The string to search.
|
1220
|
-
# @param [AST::Node] needle The string to search for.
|
1221
|
-
# @return [String]
|
1222
|
-
#
|
1223
|
-
def on_call_substring_after(context, haystack, needle)
|
1224
|
-
haystack_str = on_call_string(context, haystack)
|
1225
|
-
needle_str = on_call_string(context, needle)
|
1226
|
-
|
1227
|
-
before, sep, after = haystack_str.partition(needle_str)
|
1228
|
-
|
1229
|
-
sep.empty? ? sep : after
|
1230
|
-
end
|
1231
|
-
|
1232
|
-
##
|
1233
|
-
# Processes the `substring()` function call.
|
1234
|
-
#
|
1235
|
-
# This function call returns the substring of the 1st argument, starting
|
1236
|
-
# at the position given in the 2nd argument. If the third argument is
|
1237
|
-
# given it is used as the length for the substring, otherwise the string
|
1238
|
-
# is consumed until the end.
|
1239
|
-
#
|
1240
|
-
# XPath string indexes start from position 1, not position 0.
|
1241
|
-
#
|
1242
|
-
# @example Using a literal string
|
1243
|
-
# substring("foo", 2) # => "oo"
|
1244
|
-
#
|
1245
|
-
# @example Using a literal string with a custom length
|
1246
|
-
# substring("foo", 1, 2) # => "fo"
|
1247
|
-
#
|
1248
|
-
# @example Using a node set
|
1249
|
-
# substring(users/user/username, 5)
|
1250
|
-
#
|
1251
|
-
# @param [Oga::XML::NodeSet] context
|
1252
|
-
# @param [AST::Node] haystack
|
1253
|
-
# @param [AST::Node] start
|
1254
|
-
# @param [AST::Node] length
|
1255
|
-
# @return [String]
|
1256
|
-
#
|
1257
|
-
def on_call_substring(context, haystack, start, length = nil)
|
1258
|
-
haystack_str = on_call_string(context, haystack)
|
1259
|
-
start_index = on_call_number(context, start).to_i - 1
|
1260
|
-
|
1261
|
-
if length
|
1262
|
-
length_int = on_call_number(context, length).to_i - 1
|
1263
|
-
stop_index = start_index + length_int
|
1264
|
-
else
|
1265
|
-
stop_index = -1
|
1266
|
-
end
|
1267
|
-
|
1268
|
-
haystack_str[start_index..stop_index]
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
##
|
1272
|
-
# Processes the `string-length()` function.
|
1273
|
-
#
|
1274
|
-
# This function returns the length of the string given in the 1st argument
|
1275
|
-
# *or* the current context node. If the expression is not a string it's
|
1276
|
-
# converted to a string using the `string()` function.
|
1277
|
-
#
|
1278
|
-
# @see [#on_call_string]
|
1279
|
-
# @param [Oga::XML::NodeSet] context
|
1280
|
-
# @param [AST::Node] expression
|
1281
|
-
# @return [Float]
|
1282
|
-
#
|
1283
|
-
def on_call_string_length(context, expression = nil)
|
1284
|
-
on_call_string(context, expression).length.to_f
|
1285
|
-
end
|
1286
|
-
|
1287
|
-
##
|
1288
|
-
# Processes the `normalize-space()` function call.
|
1289
|
-
#
|
1290
|
-
# This function strips the 1st argument string *or* the current context
|
1291
|
-
# node of leading/trailing whitespace as well as replacing multiple
|
1292
|
-
# whitespace sequences with single spaces.
|
1293
|
-
#
|
1294
|
-
# @example
|
1295
|
-
# normalize-space(" fo o ") # => "fo o"
|
1296
|
-
#
|
1297
|
-
# @param [Oga::XML::NodeSet] context
|
1298
|
-
# @param [AST::Node] expression
|
1299
|
-
# @return [String]
|
1300
|
-
#
|
1301
|
-
def on_call_normalize_space(context, expression = nil)
|
1302
|
-
str = on_call_string(context, expression)
|
1303
|
-
|
1304
|
-
str.strip.gsub(/\s+/, ' ')
|
1305
|
-
end
|
1306
|
-
|
1307
|
-
##
|
1308
|
-
# Processes the `translate()` function call.
|
1309
|
-
#
|
1310
|
-
# This function takes the string of the 1st argument and replaces all
|
1311
|
-
# characters of the 2nd argument with those specified in the 3rd argument.
|
1312
|
-
#
|
1313
|
-
# @example
|
1314
|
-
# translate("bar", "abc", "ABC") # => "BAr"
|
1315
|
-
#
|
1316
|
-
# @param [Oga::XML::NodeSet] context
|
1317
|
-
# @param [AST::Node] input
|
1318
|
-
# @param [AST::Node] find
|
1319
|
-
# @param [AST::Node] replace
|
1320
|
-
# @return [String]
|
1321
|
-
#
|
1322
|
-
def on_call_translate(context, input, find, replace)
|
1323
|
-
input_str = on_call_string(context, input)
|
1324
|
-
find_chars = on_call_string(context, find).chars.to_a
|
1325
|
-
replace_chars = on_call_string(context, replace).chars.to_a
|
1326
|
-
replaced = input_str
|
1327
|
-
|
1328
|
-
find_chars.each_with_index do |char, index|
|
1329
|
-
replace_with = replace_chars[index] ? replace_chars[index] : ''
|
1330
|
-
replaced = replaced.gsub(char, replace_with)
|
1331
|
-
end
|
1332
|
-
|
1333
|
-
replaced
|
1334
|
-
end
|
1335
|
-
|
1336
|
-
##
|
1337
|
-
# Processes the `boolean()` function call.
|
1338
|
-
#
|
1339
|
-
# This function converts the 1st argument to a boolean.
|
1340
|
-
#
|
1341
|
-
# The boolean `true` is returned for the following:
|
1342
|
-
#
|
1343
|
-
# * A non empty string
|
1344
|
-
# * A non empty node set
|
1345
|
-
# * A non zero number, either positive or negative
|
1346
|
-
#
|
1347
|
-
# The boolean `false` is returned for all other cases.
|
1348
|
-
#
|
1349
|
-
# @param [Oga::XML::NodeSet] context
|
1350
|
-
# @param [AST::Node] expression
|
1351
|
-
# @return [TrueClass|FalseClass]
|
1352
|
-
#
|
1353
|
-
def on_call_boolean(context, expression)
|
1354
|
-
retval = process(expression, context)
|
1355
|
-
bool = false
|
1356
|
-
|
1357
|
-
if retval.is_a?(Numeric)
|
1358
|
-
bool = !retval.nan? && !retval.zero?
|
1359
|
-
elsif retval
|
1360
|
-
bool = !retval.respond_to?(:empty?) || !retval.empty?
|
1361
|
-
end
|
1362
|
-
|
1363
|
-
bool
|
1364
|
-
end
|
1365
|
-
|
1366
|
-
##
|
1367
|
-
# Processes the `not()` function call.
|
1368
|
-
#
|
1369
|
-
# This function converts the 1st argument to a boolean and returns the
|
1370
|
-
# opposite boolean value. For example, if the first argument results in
|
1371
|
-
# `true` then this function returns `false` instead.
|
1372
|
-
#
|
1373
|
-
# @param [Oga::XML::NodeSet] context
|
1374
|
-
# @param [AST::Node] expression
|
1375
|
-
# @return [TrueClass|FalseClass]
|
1376
|
-
#
|
1377
|
-
def on_call_not(context, expression)
|
1378
|
-
!on_call_boolean(context, expression)
|
1379
|
-
end
|
1380
|
-
|
1381
|
-
##
|
1382
|
-
# Processes the `true()` function call.
|
1383
|
-
#
|
1384
|
-
# This function simply returns the boolean `true`.
|
1385
|
-
#
|
1386
|
-
# @param [AST::NodeSet] context
|
1387
|
-
# @return [TrueClass]
|
1388
|
-
#
|
1389
|
-
def on_call_true(context)
|
1390
|
-
true
|
1391
|
-
end
|
1392
|
-
|
1393
|
-
##
|
1394
|
-
# Processes the `false()` function call.
|
1395
|
-
#
|
1396
|
-
# This function simply returns the boolean `false`.
|
1397
|
-
#
|
1398
|
-
# @param [AST::NodeSet] context
|
1399
|
-
# @return [FalseClass]
|
1400
|
-
#
|
1401
|
-
def on_call_false(context)
|
1402
|
-
false
|
1403
|
-
end
|
1404
|
-
|
1405
|
-
##
|
1406
|
-
# Processes the `lang()` function call.
|
1407
|
-
#
|
1408
|
-
# This function returns `true` if the current context node is in the given
|
1409
|
-
# language, `false` otherwise.
|
1410
|
-
#
|
1411
|
-
# The language is based on the value of the "xml:lang" attribute of either
|
1412
|
-
# the context node or an ancestor node (in case the context node has no
|
1413
|
-
# such attribute).
|
1414
|
-
#
|
1415
|
-
# @param [Oga::XML::NodeSet] context
|
1416
|
-
# @param [AST::Node] language
|
1417
|
-
# @return [TrueClass|FalseClass]
|
1418
|
-
#
|
1419
|
-
def on_call_lang(context, language)
|
1420
|
-
lang_str = on_call_string(context, language)
|
1421
|
-
node = context.first
|
1422
|
-
|
1423
|
-
while node.respond_to?(:attribute)
|
1424
|
-
found = node.attribute('xml:lang')
|
1425
|
-
|
1426
|
-
return found.value == lang_str if found
|
1427
|
-
|
1428
|
-
node = node.parent
|
1429
|
-
end
|
1430
|
-
|
1431
|
-
false
|
1432
|
-
end
|
1433
|
-
|
1434
|
-
##
|
1435
|
-
# Processes the `sum()` function call.
|
1436
|
-
#
|
1437
|
-
# This function call takes a node set, converts each node to a number and
|
1438
|
-
# then sums the values.
|
1439
|
-
#
|
1440
|
-
# As an example, take the following XML:
|
1441
|
-
#
|
1442
|
-
# <root>
|
1443
|
-
# <a>1</a>
|
1444
|
-
# <b>2</b>
|
1445
|
-
# </root>
|
1446
|
-
#
|
1447
|
-
# Using the expression `sum(root/*)` the return value would be `3.0`.
|
1448
|
-
#
|
1449
|
-
# @param [Oga::XML::NodeSet] context
|
1450
|
-
# @param [AST::Node] expression
|
1451
|
-
# @return [Float]
|
1452
|
-
#
|
1453
|
-
def on_call_sum(context, expression)
|
1454
|
-
nodes = process(expression, context)
|
1455
|
-
sum = 0.0
|
1456
|
-
|
1457
|
-
unless nodes.is_a?(XML::NodeSet)
|
1458
|
-
raise TypeError, 'sum() can only operate on NodeSet instances'
|
1459
|
-
end
|
1460
|
-
|
1461
|
-
nodes.each do |node|
|
1462
|
-
sum += node.text.to_f
|
1463
|
-
end
|
1464
|
-
|
1465
|
-
sum
|
1466
|
-
end
|
1467
|
-
|
1468
|
-
##
|
1469
|
-
# Processes the `floor()` function call.
|
1470
|
-
#
|
1471
|
-
# This function call rounds the 1st argument down to the closest integer,
|
1472
|
-
# and then returns that number as a float.
|
1473
|
-
#
|
1474
|
-
# @param [Oga::XML::NodeSet] context
|
1475
|
-
# @param [AST::Node] expression
|
1476
|
-
# @return [Float]
|
1477
|
-
#
|
1478
|
-
def on_call_floor(context, expression)
|
1479
|
-
number = on_call_number(context, expression)
|
1480
|
-
|
1481
|
-
number.nan? ? number : number.floor.to_f
|
1482
|
-
end
|
1483
|
-
|
1484
|
-
##
|
1485
|
-
# Processes the `ceiling()` function call.
|
1486
|
-
#
|
1487
|
-
# This function call rounds the 1st argument up to the closest integer,
|
1488
|
-
# and then returns that number as a float.
|
1489
|
-
#
|
1490
|
-
# @param [Oga::XML::NodeSet] context
|
1491
|
-
# @param [AST::Node] expression
|
1492
|
-
# @return [Float]
|
1493
|
-
#
|
1494
|
-
def on_call_ceiling(context, expression)
|
1495
|
-
number = on_call_number(context, expression)
|
1496
|
-
|
1497
|
-
number.nan? ? number : number.ceil.to_f
|
1498
|
-
end
|
1499
|
-
|
1500
|
-
##
|
1501
|
-
# Processes the `round()` function call.
|
1502
|
-
#
|
1503
|
-
# This function call rounds the 1st argument to the closest integer, and
|
1504
|
-
# then returns that number as a float.
|
1505
|
-
#
|
1506
|
-
# @param [Oga::XML::NodeSet] context
|
1507
|
-
# @param [AST::Node] expression
|
1508
|
-
# @return [Float]
|
1509
|
-
#
|
1510
|
-
def on_call_round(context, expression)
|
1511
|
-
number = on_call_number(context, expression)
|
1512
|
-
|
1513
|
-
number.nan? ? number : number.round.to_f
|
1514
|
-
end
|
1515
|
-
|
1516
|
-
##
|
1517
|
-
# Processes an `(int)` node.
|
1518
|
-
#
|
1519
|
-
# @param [AST::Node] ast_node
|
1520
|
-
# @param [Oga::XML::NodeSet] context
|
1521
|
-
# @return [Float]
|
1522
|
-
#
|
1523
|
-
def on_int(ast_node, context)
|
1524
|
-
ast_node.children[0].to_f
|
1525
|
-
end
|
1526
|
-
|
1527
|
-
##
|
1528
|
-
# Processes an `(float)` node.
|
1529
|
-
#
|
1530
|
-
# @param [AST::Node] ast_node
|
1531
|
-
# @param [Oga::XML::NodeSet] context
|
1532
|
-
# @return [Float]
|
1533
|
-
#
|
1534
|
-
def on_float(ast_node, context)
|
1535
|
-
ast_node.children[0]
|
1536
|
-
end
|
1537
|
-
|
1538
|
-
##
|
1539
|
-
# Processes a `(string)` node.
|
1540
|
-
#
|
1541
|
-
# @param [AST::Node] ast_node
|
1542
|
-
# @param [Oga::XML::NodeSet] context
|
1543
|
-
# @return [String]
|
1544
|
-
#
|
1545
|
-
def on_string(ast_node, context)
|
1546
|
-
ast_node.children[0]
|
1547
|
-
end
|
1548
|
-
|
1549
|
-
##
|
1550
|
-
# Processes a variable reference. If the variable is not defined an error
|
1551
|
-
# is raised.
|
1552
|
-
#
|
1553
|
-
# @param [AST::Node] ast_node
|
1554
|
-
# @param [Oga::XML::NodeSet] context
|
1555
|
-
# @return [Mixed]
|
1556
|
-
# @raise [RuntimeError]
|
1557
|
-
#
|
1558
|
-
def on_var(ast_node, context)
|
1559
|
-
name = ast_node.children[0]
|
1560
|
-
|
1561
|
-
if @variables.key?(name)
|
1562
|
-
return @variables[name]
|
1563
|
-
else
|
1564
|
-
raise "Undefined XPath variable: #{name}"
|
1565
|
-
end
|
1566
|
-
end
|
1567
|
-
|
1568
|
-
##
|
1569
|
-
# Returns the node for a function call. This node is either the first node
|
1570
|
-
# in the supplied node set, or the first node in the current context.
|
1571
|
-
#
|
1572
|
-
# @param [Oga::XML::NodeSet] context
|
1573
|
-
# @param [AST::Node] expression
|
1574
|
-
# @return [Oga::XML::Node]
|
1575
|
-
#
|
1576
|
-
def function_node(context, expression = nil)
|
1577
|
-
if expression
|
1578
|
-
node = process(expression, context)
|
1579
|
-
|
1580
|
-
if node.is_a?(XML::NodeSet)
|
1581
|
-
node = node.first
|
1582
|
-
else
|
1583
|
-
raise TypeError, 'only node sets can be used as arguments'
|
1584
|
-
end
|
1585
|
-
else
|
1586
|
-
node = context.first
|
1587
|
-
end
|
1588
|
-
|
1589
|
-
node
|
1590
|
-
end
|
1591
|
-
|
1592
|
-
##
|
1593
|
-
# Returns the text of the first node in the node set, or an empty string
|
1594
|
-
# if the node set is empty.
|
1595
|
-
#
|
1596
|
-
# @param [Oga::XML::NodeSet] set
|
1597
|
-
# @return [String]
|
1598
|
-
#
|
1599
|
-
def first_node_text(set)
|
1600
|
-
set[0].respond_to?(:text) ? set[0].text : ''
|
1601
|
-
end
|
1602
|
-
|
1603
|
-
##
|
1604
|
-
# Returns a node set containing all the child nodes of the given set of
|
1605
|
-
# nodes.
|
1606
|
-
#
|
1607
|
-
# @param [Oga::XML::NodeSet] nodes
|
1608
|
-
# @return [Oga::XML::NodeSet]
|
1609
|
-
#
|
1610
|
-
def child_nodes(nodes)
|
1611
|
-
children = XML::NodeSet.new
|
1612
|
-
|
1613
|
-
nodes.each do |xml_node|
|
1614
|
-
children.concat(xml_node.children)
|
1615
|
-
end
|
1616
|
-
|
1617
|
-
children
|
1618
|
-
end
|
1619
|
-
|
1620
|
-
##
|
1621
|
-
# Checks if a given {Oga::XML::Node} instance matches a `AST::Node`
|
1622
|
-
# instance.
|
1623
|
-
#
|
1624
|
-
# This method can use both "test" and "type-test" nodes. In case of
|
1625
|
-
# "type-test" nodes the procedure is as following:
|
1626
|
-
#
|
1627
|
-
# 1. Evaluate the expression
|
1628
|
-
# 2. If the return value is non empty return `true`, otherwise return
|
1629
|
-
# `false`
|
1630
|
-
#
|
1631
|
-
# For "test" nodes the procedure is as following instead:
|
1632
|
-
#
|
1633
|
-
# 1. Match the name
|
1634
|
-
# 2. Match the namespace
|
1635
|
-
#
|
1636
|
-
# For both the name and namespace a wildcard (`*`) can be used.
|
1637
|
-
#
|
1638
|
-
# @param [Oga::XML::Node] xml_node
|
1639
|
-
# @param [AST::Node] ast_node
|
1640
|
-
# @return [Oga::XML::NodeSet]
|
1641
|
-
#
|
1642
|
-
def node_matches?(xml_node, ast_node)
|
1643
|
-
ns, name = *ast_node.children
|
1644
|
-
|
1645
|
-
if ast_node.type.equal?(:type_test)
|
1646
|
-
return type_matches?(xml_node, ast_node)
|
1647
|
-
end
|
1648
|
-
|
1649
|
-
# If only the name is given and is a wildcard then we'll also want to
|
1650
|
-
# match the namespace as a wildcard.
|
1651
|
-
if !ns and name == STAR
|
1652
|
-
ns = STAR
|
1653
|
-
end
|
1654
|
-
|
1655
|
-
name_matches = name_matches?(xml_node, name)
|
1656
|
-
ns_matches = false
|
1657
|
-
|
1658
|
-
if ns
|
1659
|
-
ns_matches = namespace_matches?(xml_node, ns)
|
1660
|
-
|
1661
|
-
elsif name_matches and !xml_node.namespace
|
1662
|
-
ns_matches = true
|
1663
|
-
end
|
1664
|
-
|
1665
|
-
if !ns and !ns_matches
|
1666
|
-
ns_matches = xml_node.respond_to?(:default_namespace?) &&
|
1667
|
-
xml_node.default_namespace?
|
1668
|
-
end
|
1669
|
-
|
1670
|
-
name_matches && ns_matches
|
1671
|
-
end
|
1672
|
-
|
1673
|
-
##
|
1674
|
-
# @param [Oga::XML::Node] xml_node
|
1675
|
-
# @param [AST::Node] ast_node
|
1676
|
-
# @return [TrueClass|FalseClass]
|
1677
|
-
#
|
1678
|
-
def type_matches?(xml_node, ast_node)
|
1679
|
-
context = XML::NodeSet.new([xml_node])
|
1680
|
-
|
1681
|
-
process(ast_node, context).length > 0
|
1682
|
-
end
|
1683
|
-
|
1684
|
-
##
|
1685
|
-
# Returns `true` if the name of the XML node matches the given name *or*
|
1686
|
-
# matches a wildcard.
|
1687
|
-
#
|
1688
|
-
# @param [Oga::XML::Node] xml_node
|
1689
|
-
# @param [String] name
|
1690
|
-
#
|
1691
|
-
def name_matches?(xml_node, name)
|
1692
|
-
return false unless xml_node.respond_to?(:name)
|
1693
|
-
|
1694
|
-
return true if name == STAR
|
1695
|
-
|
1696
|
-
xml_node.name == name || xml_node.name.casecmp(name) == 0
|
1697
|
-
end
|
1698
|
-
|
1699
|
-
##
|
1700
|
-
# Returns `true` if the namespace of the XML node matches the given
|
1701
|
-
# namespace *or* matches a wildcard.
|
1702
|
-
#
|
1703
|
-
# @param [Oga::XML::Node] xml_node
|
1704
|
-
# @param [String] ns
|
1705
|
-
#
|
1706
|
-
def namespace_matches?(xml_node, ns)
|
1707
|
-
return false unless xml_node.respond_to?(:namespace)
|
1708
|
-
|
1709
|
-
return true if ns == STAR
|
1710
|
-
|
1711
|
-
xml_node.namespace && xml_node.namespace.name == ns
|
1712
|
-
end
|
1713
|
-
|
1714
|
-
##
|
1715
|
-
# @param [Oga::XML::Node] ast_node
|
1716
|
-
# @return [TrueClass|FalseClass]
|
1717
|
-
#
|
1718
|
-
def has_parent?(ast_node)
|
1719
|
-
ast_node.respond_to?(:parent) && !!ast_node.parent
|
1720
|
-
end
|
1721
|
-
|
1722
|
-
##
|
1723
|
-
# Converts the given value to a float. If the value can't be converted to
|
1724
|
-
# a float NaN is returned instead.
|
1725
|
-
#
|
1726
|
-
# @param [Mixed] value
|
1727
|
-
# @return [Float]
|
1728
|
-
#
|
1729
|
-
def to_float(value)
|
1730
|
-
return Float(value) rescue Float::NAN
|
1731
|
-
end
|
1732
|
-
|
1733
|
-
##
|
1734
|
-
# Converts the given value to a string according to the XPath string
|
1735
|
-
# conversion rules.
|
1736
|
-
#
|
1737
|
-
# @param [Mixed] value
|
1738
|
-
# @return [String]
|
1739
|
-
#
|
1740
|
-
def to_string(value)
|
1741
|
-
# If we have a number that has a zero decimal (e.g. 10.0) we want to
|
1742
|
-
# get rid of that decimal. For this we'll first convert the number to
|
1743
|
-
# an integer.
|
1744
|
-
if value.is_a?(Float) and value.modulo(1).zero?
|
1745
|
-
value = value.to_i
|
1746
|
-
end
|
1747
|
-
|
1748
|
-
value.to_s
|
1749
|
-
end
|
1750
|
-
|
1751
|
-
##
|
1752
|
-
# Stores the specified node set and yields the supplied block. The return
|
1753
|
-
# value of this method is whatever the block returned.
|
1754
|
-
#
|
1755
|
-
# @example
|
1756
|
-
# retval = with_node_set(context) do
|
1757
|
-
# process(....)
|
1758
|
-
# end
|
1759
|
-
#
|
1760
|
-
# @param [Oga::XML::NodeSet] nodes
|
1761
|
-
#
|
1762
|
-
def with_node_set(nodes)
|
1763
|
-
@node_sets << nodes
|
1764
|
-
|
1765
|
-
retval = yield
|
1766
|
-
|
1767
|
-
@node_sets.pop
|
1768
|
-
|
1769
|
-
retval
|
1770
|
-
end
|
1771
|
-
|
1772
|
-
##
|
1773
|
-
# @return [Oga::XML::NodeSet]
|
1774
|
-
#
|
1775
|
-
def current_node_set
|
1776
|
-
@node_sets.last
|
1777
|
-
end
|
1778
|
-
|
1779
|
-
##
|
1780
|
-
# Returns the root node of `node`, or `node` itself if its a Document.
|
1781
|
-
#
|
1782
|
-
# @param [Oga::XML::Node|Oga::XML::Document] node
|
1783
|
-
# @return [Oga::XML::Node|Oga::XML::Document]
|
1784
|
-
#
|
1785
|
-
def root_node(node)
|
1786
|
-
node.respond_to?(:root_node) ? node.root_node : node
|
1787
|
-
end
|
1788
|
-
|
1789
|
-
##
|
1790
|
-
# Returns the parent node of `node`, or `node` itself if its a Document.
|
1791
|
-
#
|
1792
|
-
# @param [Oga::XML::Node|Oga::XML::Document] node
|
1793
|
-
# @return [Oga::XML::Node|Oga::XML::Document]
|
1794
|
-
#
|
1795
|
-
def parent_node(node)
|
1796
|
-
node.respond_to?(:parent) ? node.parent : node
|
1797
|
-
end
|
1798
|
-
end # Evaluator
|
1799
|
-
end # XPath
|
1800
|
-
end # Oga
|