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
data/lib/oga/xml/sax_parser.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Oga
|
2
2
|
module XML
|
3
|
-
##
|
4
3
|
# The SaxParser class provides the basic interface for writing custom SAX
|
5
4
|
# parsers. All callback methods defined in {Oga::XML::Parser} are delegated
|
6
5
|
# to a dedicated handler class.
|
@@ -66,12 +65,9 @@ module Oga
|
|
66
65
|
# attribute names (optionally prefixed by their namespace) and their values.
|
67
66
|
# You can overwrite `on_attribute` to control individual attributes and
|
68
67
|
# `on_attributes` to control the final set.
|
69
|
-
#
|
70
68
|
class SaxParser < Parser
|
71
|
-
##
|
72
69
|
# @param [Object] handler The SAX handler to delegate callbacks to.
|
73
70
|
# @see [Oga::XML::Parser#initialize]
|
74
|
-
#
|
75
71
|
def initialize(handler, *args)
|
76
72
|
@handler = handler
|
77
73
|
|
@@ -89,38 +85,32 @@ module Oga
|
|
89
85
|
EOF
|
90
86
|
end
|
91
87
|
|
92
|
-
##
|
93
88
|
# Manually overwrite `on_element` so we can ensure that `after_element`
|
94
89
|
# always receives the namespace and name.
|
95
90
|
#
|
96
91
|
# @see [Oga::XML::Parser#on_element]
|
97
92
|
# @return [Array]
|
98
|
-
#
|
99
93
|
def on_element(namespace, name, attrs = [])
|
100
94
|
run_callback(:on_element, namespace, name, attrs)
|
101
95
|
|
102
96
|
[namespace, name]
|
103
97
|
end
|
104
98
|
|
105
|
-
##
|
106
99
|
# Manually overwrite `after_element` so it can take a namespace and name.
|
107
100
|
# This differs a bit from the regular `after_element` which only takes an
|
108
101
|
# {Oga::XML::Element} instance.
|
109
102
|
#
|
110
103
|
# @param [Array] namespace_with_name
|
111
|
-
#
|
112
104
|
def after_element(namespace_with_name)
|
113
105
|
run_callback(:after_element, *namespace_with_name)
|
114
106
|
|
115
107
|
return
|
116
108
|
end
|
117
109
|
|
118
|
-
##
|
119
110
|
# Manually overwrite this method since for this one we _do_ want the
|
120
111
|
# return value so it can be passed to `on_element`.
|
121
112
|
#
|
122
113
|
# @see [Oga::XML::Parser#on_attribute]
|
123
|
-
#
|
124
114
|
def on_attribute(name, ns = nil, value = nil)
|
125
115
|
if @handler.respond_to?(:on_attribute)
|
126
116
|
return run_callback(:on_attribute, name, ns, value)
|
@@ -135,12 +125,10 @@ module Oga
|
|
135
125
|
{key => value}
|
136
126
|
end
|
137
127
|
|
138
|
-
##
|
139
128
|
# Merges the attributes together into a Hash.
|
140
129
|
#
|
141
130
|
# @param [Array] attrs
|
142
131
|
# @return [Hash]
|
143
|
-
#
|
144
132
|
def on_attributes(attrs)
|
145
133
|
if @handler.respond_to?(:on_attributes)
|
146
134
|
return run_callback(:on_attributes, attrs)
|
@@ -156,9 +144,7 @@ module Oga
|
|
156
144
|
merged
|
157
145
|
end
|
158
146
|
|
159
|
-
##
|
160
147
|
# @param [String] text
|
161
|
-
#
|
162
148
|
def on_text(text)
|
163
149
|
if @handler.respond_to?(:on_text)
|
164
150
|
unless inside_literal_html?
|
@@ -173,17 +159,13 @@ module Oga
|
|
173
159
|
|
174
160
|
private
|
175
161
|
|
176
|
-
##
|
177
162
|
# @return [TrueClass|FalseClass]
|
178
|
-
#
|
179
163
|
def inside_literal_html?
|
180
164
|
@lexer.html_script? || @lexer.html_style?
|
181
165
|
end
|
182
166
|
|
183
|
-
##
|
184
167
|
# @param [Symbol] method
|
185
168
|
# @param [Array] args
|
186
|
-
#
|
187
169
|
def run_callback(method, *args)
|
188
170
|
@handler.send(method, *args) if @handler.respond_to?(method)
|
189
171
|
end
|
data/lib/oga/xml/text.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
module Oga
|
2
2
|
module XML
|
3
|
-
##
|
4
3
|
# Class containing information about a single text node. Text nodes don't
|
5
4
|
# have any children, attributes and the likes; just text.
|
6
|
-
#
|
7
5
|
class Text < CharacterNode
|
8
6
|
def initialize(*args)
|
9
7
|
super
|
@@ -11,20 +9,16 @@ module Oga
|
|
11
9
|
@decoded = false
|
12
10
|
end
|
13
11
|
|
14
|
-
##
|
15
12
|
# @param [String] value
|
16
|
-
#
|
17
13
|
def text=(value)
|
18
14
|
@decoded = false
|
19
15
|
@text = value
|
20
16
|
end
|
21
17
|
|
22
|
-
##
|
23
18
|
# Returns the text as a String. Upon the first call any XML/HTML entities
|
24
19
|
# are decoded.
|
25
20
|
#
|
26
21
|
# @return [String]
|
27
|
-
#
|
28
22
|
def text
|
29
23
|
if decode_entities?
|
30
24
|
@text = EntityDecoder.try_decode(@text, html?)
|
@@ -34,9 +28,7 @@ module Oga
|
|
34
28
|
@text
|
35
29
|
end
|
36
30
|
|
37
|
-
##
|
38
31
|
# @see [Oga::XML::CharacterNode#to_xml]
|
39
|
-
#
|
40
32
|
def to_xml
|
41
33
|
return super if inside_literal_html?
|
42
34
|
|
@@ -45,16 +37,12 @@ module Oga
|
|
45
37
|
|
46
38
|
private
|
47
39
|
|
48
|
-
##
|
49
40
|
# @return [TrueClass|FalseClass]
|
50
|
-
#
|
51
41
|
def decode_entities?
|
52
42
|
!@decoded && !inside_literal_html?
|
53
43
|
end
|
54
44
|
|
55
|
-
##
|
56
45
|
# @return [TrueClass|FalseClass]
|
57
|
-
#
|
58
46
|
def inside_literal_html?
|
59
47
|
node = parent
|
60
48
|
|
data/lib/oga/xml/traversal.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
module Oga
|
2
2
|
module XML
|
3
|
-
##
|
4
3
|
# Module that provides methods to traverse DOM trees.
|
5
|
-
#
|
6
4
|
module Traversal
|
7
|
-
##
|
8
5
|
# Traverses through the node and yields every child node to the supplied
|
9
6
|
# block.
|
10
7
|
#
|
@@ -29,7 +26,6 @@ module Oga
|
|
29
26
|
# end
|
30
27
|
#
|
31
28
|
# @yieldparam [Oga::XML::Node] The current node.
|
32
|
-
#
|
33
29
|
def each_node
|
34
30
|
visit = children.to_a.reverse
|
35
31
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module Oga
|
2
2
|
module XML
|
3
|
-
##
|
4
3
|
# Class containing information about an XML declaration tag.
|
5
|
-
#
|
6
4
|
class XmlDeclaration
|
7
5
|
# @return [String]
|
8
6
|
attr_accessor :version
|
@@ -14,24 +12,20 @@ module Oga
|
|
14
12
|
# @return [String]
|
15
13
|
attr_accessor :standalone
|
16
14
|
|
17
|
-
##
|
18
15
|
# @param [Hash] options
|
19
16
|
#
|
20
17
|
# @option options [String] :version
|
21
18
|
# @option options [String] :encoding
|
22
19
|
# @option options [String] :standalone
|
23
|
-
#
|
24
20
|
def initialize(options = {})
|
25
21
|
@version = options[:version] || '1.0'
|
26
22
|
@encoding = options[:encoding] || 'UTF-8'
|
27
23
|
@standalone = options[:standalone]
|
28
24
|
end
|
29
25
|
|
30
|
-
##
|
31
26
|
# Converts the declaration tag to XML.
|
32
27
|
#
|
33
28
|
# @return [String]
|
34
|
-
#
|
35
29
|
def to_xml
|
36
30
|
pairs = []
|
37
31
|
|
@@ -44,9 +38,7 @@ module Oga
|
|
44
38
|
"<?xml #{pairs.join(' ')} ?>"
|
45
39
|
end
|
46
40
|
|
47
|
-
##
|
48
41
|
# @return [String]
|
49
|
-
#
|
50
42
|
def inspect
|
51
43
|
segments = []
|
52
44
|
|
@@ -0,0 +1,1568 @@
|
|
1
|
+
module Oga
|
2
|
+
module XPath
|
3
|
+
# Compiling of XPath ASTs into Ruby code.
|
4
|
+
#
|
5
|
+
# The Compiler class can be used to turn an XPath AST into Ruby source code
|
6
|
+
# that can be executed to match XML nodes in a given input document/element.
|
7
|
+
# Compiled source code is cached per expression, removing the need for
|
8
|
+
# recompiling the same expression over and over again.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
class Compiler
|
12
|
+
# @return [Oga::LRU]
|
13
|
+
CACHE = LRU.new
|
14
|
+
|
15
|
+
# Wildcard for node names/namespace prefixes.
|
16
|
+
STAR = '*'
|
17
|
+
|
18
|
+
# Node types that require a NodeSet to push nodes into.
|
19
|
+
RETURN_NODESET = [:path, :absolute_path, :axis, :predicate]
|
20
|
+
|
21
|
+
# Hash containing all operator callbacks, the conversion methods and the
|
22
|
+
# Ruby methods to use.
|
23
|
+
OPERATORS = {
|
24
|
+
:on_add => [:to_float, :+],
|
25
|
+
:on_sub => [:to_float, :-],
|
26
|
+
:on_div => [:to_float, :/],
|
27
|
+
:on_gt => [:to_float, :>],
|
28
|
+
:on_gte => [:to_float, :>=],
|
29
|
+
:on_lt => [:to_float, :<],
|
30
|
+
:on_lte => [:to_float, :<=],
|
31
|
+
:on_mul => [:to_float, :*],
|
32
|
+
:on_mod => [:to_float, :%],
|
33
|
+
:on_and => [:to_boolean, :and],
|
34
|
+
:on_or => [:to_boolean, :or]
|
35
|
+
}
|
36
|
+
|
37
|
+
# Compiles and caches an AST.
|
38
|
+
#
|
39
|
+
# @see [#compile]
|
40
|
+
def self.compile_with_cache(ast)
|
41
|
+
CACHE.get_or_set(ast) { new.compile(ast) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
reset
|
46
|
+
end
|
47
|
+
|
48
|
+
# Resets the internal state.
|
49
|
+
def reset
|
50
|
+
@literal_id = 0
|
51
|
+
|
52
|
+
@predicate_nodesets = []
|
53
|
+
@predicate_indexes = []
|
54
|
+
end
|
55
|
+
|
56
|
+
# Compiles an XPath AST into a Ruby Proc.
|
57
|
+
#
|
58
|
+
# @param [AST::Node] ast
|
59
|
+
# @return [Proc]
|
60
|
+
def compile(ast)
|
61
|
+
document = literal(:node)
|
62
|
+
matched = matched_literal
|
63
|
+
|
64
|
+
if return_nodeset?(ast)
|
65
|
+
ruby_ast = process(ast, document) { |node| matched.push(node) }
|
66
|
+
else
|
67
|
+
ruby_ast = process(ast, document)
|
68
|
+
end
|
69
|
+
|
70
|
+
vars = variables_literal.assign(self.nil)
|
71
|
+
|
72
|
+
proc_ast = literal(:lambda).add_block(document, vars) do
|
73
|
+
input_assign = original_input_literal.assign(document)
|
74
|
+
|
75
|
+
if return_nodeset?(ast)
|
76
|
+
body = matched.assign(literal(XML::NodeSet).new)
|
77
|
+
.followed_by(ruby_ast)
|
78
|
+
.followed_by(matched)
|
79
|
+
else
|
80
|
+
body = ruby_ast
|
81
|
+
end
|
82
|
+
|
83
|
+
input_assign.followed_by(body)
|
84
|
+
end
|
85
|
+
|
86
|
+
generator = Ruby::Generator.new
|
87
|
+
source = generator.process(proc_ast)
|
88
|
+
|
89
|
+
eval(source)
|
90
|
+
ensure
|
91
|
+
reset
|
92
|
+
end
|
93
|
+
|
94
|
+
# Processes a single XPath AST node.
|
95
|
+
#
|
96
|
+
# @param [AST::Node] ast
|
97
|
+
# @param [Oga::Ruby::Node] input
|
98
|
+
# @return [Oga::Ruby::Node]
|
99
|
+
def process(ast, input, &block)
|
100
|
+
send("on_#{ast.type}", ast, input, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @param [AST::Node] ast
|
104
|
+
# @param [Oga::Ruby::Node] input
|
105
|
+
# @return [Oga::Ruby::Node]
|
106
|
+
def on_absolute_path(ast, input, &block)
|
107
|
+
if ast.children.empty?
|
108
|
+
matched_literal.push(input.root_node)
|
109
|
+
else
|
110
|
+
process(ast.children[0], input.root_node, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Dispatches the processing of axes to dedicated methods. This works
|
115
|
+
# similar to {#process} except the handler names are "on_axis_X" with "X"
|
116
|
+
# being the axis name.
|
117
|
+
#
|
118
|
+
# @param [AST::Node] ast
|
119
|
+
# @param [Oga::Ruby::Node] input
|
120
|
+
# @return [Oga::Ruby::Node]
|
121
|
+
def on_axis(ast, input, &block)
|
122
|
+
name, test, following = *ast
|
123
|
+
|
124
|
+
handler = name.gsub('-', '_')
|
125
|
+
|
126
|
+
send(:"on_axis_#{handler}", test, input) do |matched|
|
127
|
+
process_following_or_yield(following, matched, &block)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [AST::Node] ast
|
132
|
+
# @param [Oga::Ruby::Node] input
|
133
|
+
# @return [Oga::Ruby::Node]
|
134
|
+
def on_axis_child(ast, input)
|
135
|
+
child = unique_literal(:child)
|
136
|
+
|
137
|
+
document_or_node(input).if_true do
|
138
|
+
input.children.each.add_block(child) do
|
139
|
+
process(ast, child).if_true { yield child }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [AST::Node] ast
|
145
|
+
# @param [Oga::Ruby::Node] input
|
146
|
+
# @return [Oga::Ruby::Node]
|
147
|
+
def on_axis_attribute(ast, input)
|
148
|
+
input.is_a?(XML::Element).if_true do
|
149
|
+
attribute = unique_literal(:attribute)
|
150
|
+
|
151
|
+
input.attributes.each.add_block(attribute) do
|
152
|
+
name_match = match_name_and_namespace(ast, attribute)
|
153
|
+
|
154
|
+
if name_match
|
155
|
+
name_match.if_true { yield attribute }
|
156
|
+
else
|
157
|
+
yield attribute
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# @param [AST::Node] ast
|
164
|
+
# @param [Oga::Ruby::Node] input
|
165
|
+
# @return [Oga::Ruby::Node]
|
166
|
+
def on_axis_ancestor_or_self(ast, input)
|
167
|
+
parent = unique_literal(:parent)
|
168
|
+
|
169
|
+
process(ast, input).and(input.is_a?(XML::Node))
|
170
|
+
.if_true { yield input }
|
171
|
+
.followed_by do
|
172
|
+
attribute_or_node(input).if_true do
|
173
|
+
input.each_ancestor.add_block(parent) do
|
174
|
+
process(ast, parent).if_true { yield parent }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# @param [AST::Node] ast
|
181
|
+
# @param [Oga::Ruby::Node] input
|
182
|
+
# @return [Oga::Ruby::Node]
|
183
|
+
def on_axis_ancestor(ast, input)
|
184
|
+
parent = unique_literal(:parent)
|
185
|
+
|
186
|
+
attribute_or_node(input).if_true do
|
187
|
+
input.each_ancestor.add_block(parent) do
|
188
|
+
process(ast, parent).if_true { yield parent }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# @param [AST::Node] ast
|
194
|
+
# @param [Oga::Ruby::Node] input
|
195
|
+
# @return [Oga::Ruby::Node]
|
196
|
+
def on_axis_descendant_or_self(ast, input)
|
197
|
+
node = unique_literal(:descendant)
|
198
|
+
|
199
|
+
document_or_node(input).if_true do
|
200
|
+
process(ast, input)
|
201
|
+
.if_true { yield input }
|
202
|
+
.followed_by do
|
203
|
+
input.each_node.add_block(node) do
|
204
|
+
process(ast, node).if_true { yield node }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# @param [AST::Node] ast
|
211
|
+
# @param [Oga::Ruby::Node] input
|
212
|
+
# @return [Oga::Ruby::Node]
|
213
|
+
def on_axis_descendant(ast, input)
|
214
|
+
node = unique_literal(:descendant)
|
215
|
+
|
216
|
+
document_or_node(input).if_true do
|
217
|
+
input.each_node.add_block(node) do
|
218
|
+
process(ast, node).if_true { yield node }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# @param [AST::Node] ast
|
224
|
+
# @param [Oga::Ruby::Node] input
|
225
|
+
# @return [Oga::Ruby::Node]
|
226
|
+
def on_axis_parent(ast, input)
|
227
|
+
parent = unique_literal(:parent)
|
228
|
+
|
229
|
+
attribute_or_node(input).if_true do
|
230
|
+
parent.assign(input.parent).followed_by do
|
231
|
+
process(ast, parent).if_true { yield parent }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# @param [AST::Node] ast
|
237
|
+
# @param [Oga::Ruby::Node] input
|
238
|
+
# @return [Oga::Ruby::Node]
|
239
|
+
def on_axis_self(ast, input)
|
240
|
+
process(ast, input).if_true { yield input }
|
241
|
+
end
|
242
|
+
|
243
|
+
# @param [AST::Node] ast
|
244
|
+
# @param [Oga::Ruby::Node] input
|
245
|
+
# @return [Oga::Ruby::Node]
|
246
|
+
def on_axis_following_sibling(ast, input)
|
247
|
+
orig_input = original_input_literal
|
248
|
+
doc_node = literal(:doc_node)
|
249
|
+
check = literal(:check)
|
250
|
+
parent = literal(:parent)
|
251
|
+
root = literal(:root)
|
252
|
+
|
253
|
+
orig_input.is_a?(XML::Node)
|
254
|
+
.if_true { root.assign(orig_input.parent) }
|
255
|
+
.else { root.assign(orig_input) }
|
256
|
+
.followed_by do
|
257
|
+
input.is_a?(XML::Node).and(input.parent)
|
258
|
+
.if_true { parent.assign(input.parent) }
|
259
|
+
.else { parent.assign(self.nil) }
|
260
|
+
end
|
261
|
+
.followed_by(check.assign(self.false))
|
262
|
+
.followed_by do
|
263
|
+
document_or_node(root).if_true do
|
264
|
+
root.each_node.add_block(doc_node) do
|
265
|
+
doc_node.eq(input)
|
266
|
+
.if_true do
|
267
|
+
check.assign(self.true)
|
268
|
+
.followed_by(throw_message(:skip_children))
|
269
|
+
end
|
270
|
+
.followed_by do
|
271
|
+
check.not.or(parent != doc_node.parent).if_true do
|
272
|
+
send_message(:next)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
.followed_by do
|
276
|
+
process(ast, doc_node).if_true { yield doc_node }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# @param [AST::Node] ast
|
284
|
+
# @param [Oga::Ruby::Node] input
|
285
|
+
# @return [Oga::Ruby::Node]
|
286
|
+
def on_axis_following(ast, input)
|
287
|
+
orig_input = original_input_literal
|
288
|
+
doc_node = literal(:doc_node)
|
289
|
+
check = literal(:check)
|
290
|
+
root = literal(:root)
|
291
|
+
|
292
|
+
orig_input.is_a?(XML::Node)
|
293
|
+
.if_true { root.assign(orig_input.root_node) }
|
294
|
+
.else { root.assign(orig_input) }
|
295
|
+
.followed_by(check.assign(self.false))
|
296
|
+
.followed_by do
|
297
|
+
document_or_node(root).if_true do
|
298
|
+
root.each_node.add_block(doc_node) do
|
299
|
+
doc_node.eq(input)
|
300
|
+
.if_true do
|
301
|
+
check.assign(self.true)
|
302
|
+
.followed_by(throw_message(:skip_children))
|
303
|
+
end
|
304
|
+
.followed_by do
|
305
|
+
check.if_false { send_message(:next) }
|
306
|
+
end
|
307
|
+
.followed_by do
|
308
|
+
process(ast, doc_node).if_true { yield doc_node }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# @param [AST::Node] ast
|
316
|
+
# @param [Oga::Ruby::Node] input
|
317
|
+
# @return [Oga::Ruby::Node]
|
318
|
+
def on_axis_namespace(ast, input)
|
319
|
+
underscore = literal(:_)
|
320
|
+
node = unique_literal(:namespace)
|
321
|
+
|
322
|
+
name = string(ast.children[1])
|
323
|
+
star = string(STAR)
|
324
|
+
|
325
|
+
input.is_a?(XML::Element).if_true do
|
326
|
+
input.available_namespaces.each.add_block(underscore, node) do
|
327
|
+
node.name.eq(name).or(name.eq(star)).if_true { yield node }
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# @param [AST::Node] ast
|
333
|
+
# @param [Oga::Ruby::Node] input
|
334
|
+
# @return [Oga::Ruby::Node]
|
335
|
+
def on_axis_preceding(ast, input)
|
336
|
+
root = literal(:root)
|
337
|
+
doc_node = literal(:doc_node)
|
338
|
+
|
339
|
+
input.is_a?(XML::Node).if_true do
|
340
|
+
root.assign(input.root_node)
|
341
|
+
.followed_by do
|
342
|
+
document_or_node(root).if_true do
|
343
|
+
root.each_node.add_block(doc_node) do
|
344
|
+
doc_node.eq(input)
|
345
|
+
.if_true { self.break }
|
346
|
+
.followed_by do
|
347
|
+
process(ast, doc_node).if_true { yield doc_node }
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# @param [AST::Node] ast
|
356
|
+
# @param [Oga::Ruby::Node] input
|
357
|
+
# @return [Oga::Ruby::Node]
|
358
|
+
def on_axis_preceding_sibling(ast, input)
|
359
|
+
orig_input = original_input_literal
|
360
|
+
check = literal(:check)
|
361
|
+
root = literal(:root)
|
362
|
+
parent = literal(:parent)
|
363
|
+
doc_node = literal(:doc_node)
|
364
|
+
|
365
|
+
orig_input.is_a?(XML::Node)
|
366
|
+
.if_true { root.assign(orig_input.parent) }
|
367
|
+
.else { root.assign(orig_input) }
|
368
|
+
.followed_by(check.assign(self.false))
|
369
|
+
.followed_by do
|
370
|
+
input.is_a?(XML::Node).and(input.parent)
|
371
|
+
.if_true { parent.assign(input.parent) }
|
372
|
+
.else { parent.assign(self.nil) }
|
373
|
+
end
|
374
|
+
.followed_by do
|
375
|
+
document_or_node(root).if_true do
|
376
|
+
root.each_node.add_block(doc_node) do
|
377
|
+
doc_node.eq(input)
|
378
|
+
.if_true { self.break }
|
379
|
+
.followed_by do
|
380
|
+
doc_node.parent.eq(parent).if_true do
|
381
|
+
process(ast, doc_node).if_true { yield doc_node }
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# @param [AST::Node] ast
|
390
|
+
# @param [Oga::Ruby::Node] input
|
391
|
+
# @return [Oga::Ruby::Node]
|
392
|
+
def on_predicate(ast, input, &block)
|
393
|
+
test, predicate, following = *ast
|
394
|
+
|
395
|
+
index_var = unique_literal(:index)
|
396
|
+
|
397
|
+
if number?(predicate)
|
398
|
+
method = :on_predicate_index
|
399
|
+
elsif has_call_node?(predicate, 'last')
|
400
|
+
method = :on_predicate_temporary
|
401
|
+
else
|
402
|
+
method = :on_predicate_direct
|
403
|
+
end
|
404
|
+
|
405
|
+
@predicate_indexes << index_var
|
406
|
+
|
407
|
+
ast = index_var.assign(literal(1)).followed_by do
|
408
|
+
send(method, input, test, predicate) do |matched|
|
409
|
+
process_following_or_yield(following, matched, &block)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
@predicate_indexes.pop
|
414
|
+
|
415
|
+
ast
|
416
|
+
end
|
417
|
+
|
418
|
+
# Processes a predicate that requires a temporary NodeSet.
|
419
|
+
#
|
420
|
+
# @param [Oga::Ruby::Node] input
|
421
|
+
# @param [AST::Node] test
|
422
|
+
# @param [AST::Node] predicate
|
423
|
+
# @return [Oga::Ruby::Node]
|
424
|
+
def on_predicate_temporary(input, test, predicate)
|
425
|
+
temp_set = unique_literal(:temp_set)
|
426
|
+
pred_node = unique_literal(:pred_node)
|
427
|
+
pred_var = unique_literal(:pred_var)
|
428
|
+
conversion = literal(Conversion)
|
429
|
+
|
430
|
+
index_var = predicate_index
|
431
|
+
index_step = literal(1)
|
432
|
+
|
433
|
+
@predicate_nodesets << temp_set
|
434
|
+
|
435
|
+
ast = temp_set.assign(literal(XML::NodeSet).new)
|
436
|
+
.followed_by do
|
437
|
+
process(test, input) { |node| temp_set << node }
|
438
|
+
end
|
439
|
+
.followed_by do
|
440
|
+
temp_set.each.add_block(pred_node) do
|
441
|
+
pred_ast = process(predicate, pred_node)
|
442
|
+
|
443
|
+
pred_var.assign(pred_ast)
|
444
|
+
.followed_by do
|
445
|
+
pred_var.is_a?(Numeric).if_true do
|
446
|
+
pred_var.assign(pred_var.to_i.eq(index_var))
|
447
|
+
end
|
448
|
+
end
|
449
|
+
.followed_by do
|
450
|
+
conversion.to_boolean(pred_var).if_true { yield pred_node }
|
451
|
+
end
|
452
|
+
.followed_by do
|
453
|
+
index_var.assign(index_var + index_step)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
@predicate_nodesets.pop
|
459
|
+
|
460
|
+
ast
|
461
|
+
end
|
462
|
+
|
463
|
+
# Processes a predicate that doesn't require temporary NodeSet.
|
464
|
+
#
|
465
|
+
# @param [Oga::Ruby::Node] input
|
466
|
+
# @param [AST::Node] test
|
467
|
+
# @param [AST::Node] predicate
|
468
|
+
# @return [Oga::Ruby::Node]
|
469
|
+
def on_predicate_direct(input, test, predicate)
|
470
|
+
pred_var = unique_literal(:pred_var)
|
471
|
+
index_var = predicate_index
|
472
|
+
index_step = literal(1)
|
473
|
+
conversion = literal(Conversion)
|
474
|
+
|
475
|
+
process(test, input) do |matched_test_node|
|
476
|
+
if return_nodeset?(predicate)
|
477
|
+
pred_ast = catch_message(:predicate_matched) do
|
478
|
+
process(predicate, matched_test_node) do
|
479
|
+
throw_message(:predicate_matched, self.true)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
else
|
483
|
+
pred_ast = process(predicate, matched_test_node)
|
484
|
+
end
|
485
|
+
|
486
|
+
pred_var.assign(pred_ast)
|
487
|
+
.followed_by do
|
488
|
+
pred_var.is_a?(Numeric).if_true do
|
489
|
+
pred_var.assign(pred_var.to_i.eq(index_var))
|
490
|
+
end
|
491
|
+
end
|
492
|
+
.followed_by do
|
493
|
+
conversion.to_boolean(pred_var).if_true do
|
494
|
+
yield matched_test_node
|
495
|
+
end
|
496
|
+
end
|
497
|
+
.followed_by do
|
498
|
+
index_var.assign(index_var + index_step)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Processes a predicate that uses a literal index.
|
504
|
+
#
|
505
|
+
# @param [Oga::Ruby::Node] input
|
506
|
+
# @param [AST::Node] test
|
507
|
+
# @param [AST::Node] predicate
|
508
|
+
# @return [Oga::Ruby::Node]
|
509
|
+
def on_predicate_index(input, test, predicate)
|
510
|
+
index_var = predicate_index
|
511
|
+
index_step = literal(1)
|
512
|
+
|
513
|
+
index = process(predicate, input).to_i
|
514
|
+
|
515
|
+
process(test, input) do |matched_test_node|
|
516
|
+
index_var.eq(index)
|
517
|
+
.if_true do
|
518
|
+
yield matched_test_node
|
519
|
+
end
|
520
|
+
.followed_by do
|
521
|
+
index_var.assign(index_var + index_step)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# @param [AST::Node] ast
|
527
|
+
# @param [Oga::Ruby::Node] input
|
528
|
+
# @return [Oga::Ruby::Node]
|
529
|
+
def on_test(ast, input)
|
530
|
+
condition = element_or_attribute(input)
|
531
|
+
name_match = match_name_and_namespace(ast, input)
|
532
|
+
|
533
|
+
name_match ? condition.and(name_match) : condition
|
534
|
+
end
|
535
|
+
|
536
|
+
# Processes the `=` operator.
|
537
|
+
#
|
538
|
+
# @see [#operator]
|
539
|
+
def on_eq(ast, input, &block)
|
540
|
+
conv = literal(Conversion)
|
541
|
+
|
542
|
+
operator(ast, input) do |left, right|
|
543
|
+
mass_assign([left, right], conv.to_compatible_types(left, right))
|
544
|
+
.followed_by do
|
545
|
+
operation = left.eq(right)
|
546
|
+
|
547
|
+
block ? operation.if_true(&block) : operation
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# Processes the `!=` operator.
|
553
|
+
#
|
554
|
+
# @see [#operator]
|
555
|
+
def on_neq(ast, input, &block)
|
556
|
+
conv = literal(Conversion)
|
557
|
+
|
558
|
+
operator(ast, input) do |left, right|
|
559
|
+
mass_assign([left, right], conv.to_compatible_types(left, right))
|
560
|
+
.followed_by do
|
561
|
+
operation = left != right
|
562
|
+
|
563
|
+
block ? operation.if_true(&block) : operation
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
OPERATORS.each do |callback, (conv_method, ruby_method)|
|
569
|
+
define_method(callback) do |ast, input, &block|
|
570
|
+
conversion = literal(XPath::Conversion)
|
571
|
+
|
572
|
+
operator(ast, input) do |left, right|
|
573
|
+
lval = conversion.__send__(conv_method, left)
|
574
|
+
rval = conversion.__send__(conv_method, right)
|
575
|
+
operation = lval.__send__(ruby_method, rval)
|
576
|
+
|
577
|
+
block ? conversion.to_boolean(operation).if_true(&block) : operation
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
# Processes the `|` operator.
|
583
|
+
#
|
584
|
+
# @see [#operator]
|
585
|
+
def on_pipe(ast, input, &block)
|
586
|
+
left, right = *ast
|
587
|
+
|
588
|
+
union = unique_literal(:union)
|
589
|
+
conversion = literal(Conversion)
|
590
|
+
|
591
|
+
union.assign(literal(XML::NodeSet).new)
|
592
|
+
.followed_by(process(left, input) { |node| union << node })
|
593
|
+
.followed_by(process(right, input) { |node| union << node })
|
594
|
+
.followed_by do
|
595
|
+
# block present means we're in a predicate
|
596
|
+
block ? conversion.to_boolean(union).if_true(&block) : union
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
# @param [AST::Node] ast
|
601
|
+
# @return [Oga::Ruby::Node]
|
602
|
+
def on_string(ast, *)
|
603
|
+
string(ast.children[0])
|
604
|
+
end
|
605
|
+
|
606
|
+
# @param [AST::Node] ast
|
607
|
+
# @return [Oga::Ruby::Node]
|
608
|
+
def on_int(ast, *)
|
609
|
+
literal(ast.children[0].to_f.to_s)
|
610
|
+
end
|
611
|
+
|
612
|
+
# @param [AST::Node] ast
|
613
|
+
# @return [Oga::Ruby::Node]
|
614
|
+
def on_float(ast, *)
|
615
|
+
literal(ast.children[0].to_s)
|
616
|
+
end
|
617
|
+
|
618
|
+
# @param [AST::Node] ast
|
619
|
+
# @return [Oga::Ruby::Node]
|
620
|
+
def on_var(ast, *)
|
621
|
+
name = ast.children[0]
|
622
|
+
|
623
|
+
variables_literal.and(variables_literal[string(name)])
|
624
|
+
.or(send_message(:raise, string("Undefined XPath variable: #{name}")))
|
625
|
+
end
|
626
|
+
|
627
|
+
# Delegates function calls to specific handlers.
|
628
|
+
#
|
629
|
+
# @param [AST::Node] ast
|
630
|
+
# @param [Oga::Ruby::Node] input
|
631
|
+
# @return [Oga::Ruby::Node]
|
632
|
+
def on_call(ast, input, &block)
|
633
|
+
name, *args = *ast
|
634
|
+
|
635
|
+
handler = name.gsub('-', '_')
|
636
|
+
|
637
|
+
send(:"on_call_#{handler}", input, *args, &block)
|
638
|
+
end
|
639
|
+
|
640
|
+
# @return [Oga::Ruby::Node]
|
641
|
+
def on_call_true(*)
|
642
|
+
block_given? ? yield : self.true
|
643
|
+
end
|
644
|
+
|
645
|
+
# @return [Oga::Ruby::Node]
|
646
|
+
def on_call_false(*)
|
647
|
+
self.false
|
648
|
+
end
|
649
|
+
|
650
|
+
# @param [Oga::Ruby::Node] input
|
651
|
+
# @param [AST::Node] arg
|
652
|
+
# @return [Oga::Ruby::Node]
|
653
|
+
def on_call_boolean(input, arg)
|
654
|
+
arg_ast = try_match_first_node(arg, input)
|
655
|
+
call_arg = unique_literal(:call_arg)
|
656
|
+
conversion = literal(Conversion)
|
657
|
+
|
658
|
+
call_arg.assign(arg_ast).followed_by do
|
659
|
+
converted = conversion.to_boolean(call_arg)
|
660
|
+
|
661
|
+
block_given? ? converted.if_true { yield } : converted
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
# @param [Oga::Ruby::Node] input
|
666
|
+
# @param [AST::Node] arg
|
667
|
+
# @return [Oga::Ruby::Node]
|
668
|
+
def on_call_ceiling(input, arg)
|
669
|
+
arg_ast = try_match_first_node(arg, input)
|
670
|
+
call_arg = unique_literal(:call_arg)
|
671
|
+
conversion = literal(Conversion)
|
672
|
+
|
673
|
+
call_arg.assign(arg_ast)
|
674
|
+
.followed_by do
|
675
|
+
call_arg.assign(conversion.to_float(call_arg))
|
676
|
+
end
|
677
|
+
.followed_by do
|
678
|
+
call_arg.nan?
|
679
|
+
.if_true { call_arg }
|
680
|
+
.else { block_given? ? yield : call_arg.ceil.to_f }
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
# @param [Oga::Ruby::Node] input
|
685
|
+
# @param [AST::Node] arg
|
686
|
+
# @return [Oga::Ruby::Node]
|
687
|
+
def on_call_floor(input, arg)
|
688
|
+
arg_ast = try_match_first_node(arg, input)
|
689
|
+
call_arg = unique_literal(:call_arg)
|
690
|
+
conversion = literal(Conversion)
|
691
|
+
|
692
|
+
call_arg.assign(arg_ast)
|
693
|
+
.followed_by do
|
694
|
+
call_arg.assign(conversion.to_float(call_arg))
|
695
|
+
end
|
696
|
+
.followed_by do
|
697
|
+
call_arg.nan?
|
698
|
+
.if_true { call_arg }
|
699
|
+
.else { block_given? ? yield : call_arg.floor.to_f }
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
# @param [Oga::Ruby::Node] input
|
704
|
+
# @param [AST::Node] arg
|
705
|
+
# @return [Oga::Ruby::Node]
|
706
|
+
def on_call_round(input, arg)
|
707
|
+
arg_ast = try_match_first_node(arg, input)
|
708
|
+
call_arg = unique_literal(:call_arg)
|
709
|
+
conversion = literal(Conversion)
|
710
|
+
|
711
|
+
call_arg.assign(arg_ast)
|
712
|
+
.followed_by do
|
713
|
+
call_arg.assign(conversion.to_float(call_arg))
|
714
|
+
end
|
715
|
+
.followed_by do
|
716
|
+
call_arg.nan?
|
717
|
+
.if_true { call_arg }
|
718
|
+
.else { block_given? ? yield : call_arg.round.to_f }
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
# @param [Oga::Ruby::Node] input
|
723
|
+
# @param [Array<AST::Node>] args
|
724
|
+
# @return [Oga::Ruby::Node]
|
725
|
+
def on_call_concat(input, *args)
|
726
|
+
conversion = literal(Conversion)
|
727
|
+
assigns = []
|
728
|
+
conversions = []
|
729
|
+
|
730
|
+
args.each do |arg|
|
731
|
+
arg_var = unique_literal(:concat_arg)
|
732
|
+
arg_ast = try_match_first_node(arg, input)
|
733
|
+
|
734
|
+
assigns << arg_var.assign(arg_ast)
|
735
|
+
conversions << conversion.to_string(arg_var)
|
736
|
+
end
|
737
|
+
|
738
|
+
concatted = assigns.inject(:followed_by)
|
739
|
+
.followed_by(conversions.inject(:+))
|
740
|
+
|
741
|
+
block_given? ? concatted.empty?.if_false { yield } : concatted
|
742
|
+
end
|
743
|
+
|
744
|
+
# @param [Oga::Ruby::Node] input
|
745
|
+
# @param [AST::Node] haystack
|
746
|
+
# @param [AST::Node] needle
|
747
|
+
# @return [Oga::Ruby::Node]
|
748
|
+
def on_call_contains(input, haystack, needle)
|
749
|
+
haystack_lit = unique_literal(:haystack)
|
750
|
+
needle_lit = unique_literal(:needle)
|
751
|
+
conversion = literal(Conversion)
|
752
|
+
|
753
|
+
haystack_lit.assign(try_match_first_node(haystack, input))
|
754
|
+
.followed_by do
|
755
|
+
needle_lit.assign(try_match_first_node(needle, input))
|
756
|
+
end
|
757
|
+
.followed_by do
|
758
|
+
converted = conversion.to_string(haystack_lit)
|
759
|
+
.include?(conversion.to_string(needle_lit))
|
760
|
+
|
761
|
+
block_given? ? converted.if_true { yield } : converted
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
# @param [Oga::Ruby::Node] input
|
766
|
+
# @param [AST::Node] arg
|
767
|
+
# @return [Oga::Ruby::Node]
|
768
|
+
def on_call_count(input, arg)
|
769
|
+
count = unique_literal(:count)
|
770
|
+
|
771
|
+
unless return_nodeset?(arg)
|
772
|
+
raise TypeError, 'count() can only operate on NodeSet instances'
|
773
|
+
end
|
774
|
+
|
775
|
+
count.assign(literal(0.0))
|
776
|
+
.followed_by do
|
777
|
+
process(arg, input) { count.assign(count + literal(1)) }
|
778
|
+
end
|
779
|
+
.followed_by do
|
780
|
+
block_given? ? count.zero?.if_false { yield } : count
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
# Processes the `id()` function call.
|
785
|
+
#
|
786
|
+
# The XPath specification states that this function's behaviour should be
|
787
|
+
# controlled by a DTD. If a DTD were to specify that the ID attribute for
|
788
|
+
# a certain element would be "foo" then this function should use said
|
789
|
+
# attribute.
|
790
|
+
#
|
791
|
+
# Oga does not support DTD parsing/evaluation and as such always uses the
|
792
|
+
# "id" attribute.
|
793
|
+
#
|
794
|
+
# This function searches the entire document for a matching node,
|
795
|
+
# regardless of the current position.
|
796
|
+
#
|
797
|
+
# @param [Oga::Ruby::Node] input
|
798
|
+
# @param [AST::Node] arg
|
799
|
+
# @return [Oga::Ruby::Node]
|
800
|
+
def on_call_id(input, arg)
|
801
|
+
orig_input = original_input_literal
|
802
|
+
node = unique_literal(:node)
|
803
|
+
ids_var = unique_literal('ids')
|
804
|
+
matched = unique_literal('id_matched')
|
805
|
+
id_str_var = unique_literal('id_string')
|
806
|
+
attr_var = unique_literal('attr')
|
807
|
+
|
808
|
+
matched.assign(literal(XML::NodeSet).new)
|
809
|
+
.followed_by do
|
810
|
+
# When using some sort of path we'll want the text of all matched
|
811
|
+
# nodes.
|
812
|
+
if return_nodeset?(arg)
|
813
|
+
ids_var.assign(literal(:[])).followed_by do
|
814
|
+
process(arg, input) { |element| ids_var << element.text }
|
815
|
+
end
|
816
|
+
|
817
|
+
# For everything else we'll cast the value to a string and split it
|
818
|
+
# on every space.
|
819
|
+
else
|
820
|
+
conversion = literal(Conversion).to_string(ids_var)
|
821
|
+
.split(string(' '))
|
822
|
+
|
823
|
+
ids_var.assign(process(arg, input))
|
824
|
+
.followed_by(ids_var.assign(conversion))
|
825
|
+
end
|
826
|
+
end
|
827
|
+
.followed_by do
|
828
|
+
id_str_var.assign(string('id'))
|
829
|
+
end
|
830
|
+
.followed_by do
|
831
|
+
orig_input.each_node.add_block(node) do
|
832
|
+
node.is_a?(XML::Element).if_true do
|
833
|
+
attr_var.assign(node.attribute(id_str_var)).followed_by do
|
834
|
+
attr_var.and(ids_var.include?(attr_var.value))
|
835
|
+
.if_true { block_given? ? yield : matched << node }
|
836
|
+
end
|
837
|
+
end
|
838
|
+
end
|
839
|
+
end
|
840
|
+
.followed_by(matched)
|
841
|
+
end
|
842
|
+
|
843
|
+
# @param [Oga::Ruby::Node] input
|
844
|
+
# @param [AST::Node] arg
|
845
|
+
# @return [Oga::Ruby::Node]
|
846
|
+
def on_call_lang(input, arg)
|
847
|
+
lang_var = unique_literal('lang')
|
848
|
+
node = unique_literal('node')
|
849
|
+
found = unique_literal('found')
|
850
|
+
xml_lang = unique_literal('xml_lang')
|
851
|
+
matched = unique_literal('matched')
|
852
|
+
|
853
|
+
conversion = literal(Conversion)
|
854
|
+
|
855
|
+
ast = lang_var.assign(try_match_first_node(arg, input))
|
856
|
+
.followed_by do
|
857
|
+
lang_var.assign(conversion.to_string(lang_var))
|
858
|
+
end
|
859
|
+
.followed_by do
|
860
|
+
matched.assign(self.false)
|
861
|
+
end
|
862
|
+
.followed_by do
|
863
|
+
node.assign(input)
|
864
|
+
end
|
865
|
+
.followed_by do
|
866
|
+
xml_lang.assign(string('xml:lang'))
|
867
|
+
end
|
868
|
+
.followed_by do
|
869
|
+
node.respond_to?(symbol(:attribute)).while_true do
|
870
|
+
found.assign(node.get(xml_lang))
|
871
|
+
.followed_by do
|
872
|
+
found.if_true do
|
873
|
+
found.eq(lang_var)
|
874
|
+
.if_true do
|
875
|
+
if block_given?
|
876
|
+
yield
|
877
|
+
else
|
878
|
+
matched.assign(self.true).followed_by(self.break)
|
879
|
+
end
|
880
|
+
end
|
881
|
+
.else { self.break }
|
882
|
+
end
|
883
|
+
end
|
884
|
+
.followed_by(node.assign(node.parent))
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
block_given? ? ast : ast.followed_by(matched)
|
889
|
+
end
|
890
|
+
|
891
|
+
# @param [Oga::Ruby::Node] input
|
892
|
+
# @param [AST::Node] arg
|
893
|
+
# @return [Oga::Ruby::Node]
|
894
|
+
def on_call_local_name(input, arg = nil)
|
895
|
+
argument_or_first_node(input, arg) do |arg_var|
|
896
|
+
arg_var
|
897
|
+
.if_true do
|
898
|
+
ensure_element_or_attribute(arg_var)
|
899
|
+
.followed_by { block_given? ? yield : arg_var.name }
|
900
|
+
end
|
901
|
+
.else { string('') }
|
902
|
+
end
|
903
|
+
end
|
904
|
+
|
905
|
+
# @param [Oga::Ruby::Node] input
|
906
|
+
# @param [AST::Node] arg
|
907
|
+
# @return [Oga::Ruby::Node]
|
908
|
+
def on_call_name(input, arg = nil)
|
909
|
+
argument_or_first_node(input, arg) do |arg_var|
|
910
|
+
arg_var
|
911
|
+
.if_true do
|
912
|
+
ensure_element_or_attribute(arg_var)
|
913
|
+
.followed_by { block_given? ? yield : arg_var.expanded_name }
|
914
|
+
end
|
915
|
+
.else { string('') }
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
# @param [Oga::Ruby::Node] input
|
920
|
+
# @param [AST::Node] arg
|
921
|
+
# @return [Oga::Ruby::Node]
|
922
|
+
def on_call_namespace_uri(input, arg = nil)
|
923
|
+
default = string('')
|
924
|
+
|
925
|
+
argument_or_first_node(input, arg) do |arg_var|
|
926
|
+
arg_var
|
927
|
+
.if_true do
|
928
|
+
ensure_element_or_attribute(arg_var).followed_by do
|
929
|
+
arg_var.namespace
|
930
|
+
.if_true { block_given? ? yield : arg_var.namespace.uri }
|
931
|
+
.else { default } # no yield so predicates aren't matched
|
932
|
+
end
|
933
|
+
end
|
934
|
+
.else { default }
|
935
|
+
end
|
936
|
+
end
|
937
|
+
|
938
|
+
# @param [Oga::Ruby::Node] input
|
939
|
+
# @param [AST::Node] arg
|
940
|
+
# @return [Oga::Ruby::Node]
|
941
|
+
def on_call_normalize_space(input, arg = nil)
|
942
|
+
conversion = literal(Conversion)
|
943
|
+
norm_var = unique_literal(:normalized)
|
944
|
+
|
945
|
+
find = literal('/\s+/')
|
946
|
+
replace = string(' ')
|
947
|
+
|
948
|
+
argument_or_first_node(input, arg) do |arg_var|
|
949
|
+
norm_var
|
950
|
+
.assign(conversion.to_string(arg_var).strip.gsub(find, replace))
|
951
|
+
.followed_by do
|
952
|
+
norm_var.empty?
|
953
|
+
.if_true { string('') }
|
954
|
+
.else { block_given? ? yield : norm_var }
|
955
|
+
end
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# @param [Oga::Ruby::Node] input
|
960
|
+
# @param [AST::Node] arg
|
961
|
+
# @return [Oga::Ruby::Node]
|
962
|
+
def on_call_not(input, arg)
|
963
|
+
arg_ast = try_match_first_node(arg, input)
|
964
|
+
call_arg = unique_literal(:call_arg)
|
965
|
+
conversion = literal(Conversion)
|
966
|
+
|
967
|
+
call_arg.assign(arg_ast).followed_by do
|
968
|
+
converted = conversion.to_boolean(call_arg).not
|
969
|
+
|
970
|
+
block_given? ? converted.if_true { yield } : converted
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
# @param [Oga::Ruby::Node] input
|
975
|
+
# @param [AST::Node] arg
|
976
|
+
# @return [Oga::Ruby::Node]
|
977
|
+
def on_call_number(input, arg = nil)
|
978
|
+
convert_var = unique_literal(:convert)
|
979
|
+
conversion = literal(Conversion)
|
980
|
+
|
981
|
+
argument_or_first_node(input, arg) do |arg_var|
|
982
|
+
convert_var.assign(conversion.to_float(arg_var)).followed_by do
|
983
|
+
if block_given?
|
984
|
+
convert_var.zero?.if_false { yield }
|
985
|
+
else
|
986
|
+
convert_var
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
# @param [Oga::Ruby::Node] input
|
993
|
+
# @param [AST::Node] haystack
|
994
|
+
# @param [AST::Node] needle
|
995
|
+
# @return [Oga::Ruby::Node]
|
996
|
+
def on_call_starts_with(input, haystack, needle)
|
997
|
+
haystack_var = unique_literal(:haystack)
|
998
|
+
needle_var = unique_literal(:needle)
|
999
|
+
conversion = literal(Conversion)
|
1000
|
+
|
1001
|
+
haystack_var.assign(try_match_first_node(haystack, input))
|
1002
|
+
.followed_by do
|
1003
|
+
needle_var.assign(try_match_first_node(needle, input))
|
1004
|
+
end
|
1005
|
+
.followed_by do
|
1006
|
+
haystack_var.assign(conversion.to_string(haystack_var))
|
1007
|
+
.followed_by do
|
1008
|
+
needle_var.assign(conversion.to_string(needle_var))
|
1009
|
+
end
|
1010
|
+
.followed_by do
|
1011
|
+
equal = needle_var.empty?
|
1012
|
+
.or(haystack_var.start_with?(needle_var))
|
1013
|
+
|
1014
|
+
block_given? ? equal.if_true { yield } : equal
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
# @param [Oga::Ruby::Node] input
|
1020
|
+
# @param [AST::Node] arg
|
1021
|
+
# @return [Oga::Ruby::Node]
|
1022
|
+
def on_call_string_length(input, arg = nil)
|
1023
|
+
convert_var = unique_literal(:convert)
|
1024
|
+
conversion = literal(Conversion)
|
1025
|
+
|
1026
|
+
argument_or_first_node(input, arg) do |arg_var|
|
1027
|
+
convert_var.assign(conversion.to_string(arg_var).length)
|
1028
|
+
.followed_by do
|
1029
|
+
if block_given?
|
1030
|
+
convert_var.zero?.if_false { yield }
|
1031
|
+
else
|
1032
|
+
convert_var.to_f
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
# @param [Oga::Ruby::Node] input
|
1039
|
+
# @param [AST::Node] arg
|
1040
|
+
# @return [Oga::Ruby::Node]
|
1041
|
+
def on_call_string(input, arg = nil)
|
1042
|
+
convert_var = unique_literal(:convert)
|
1043
|
+
conversion = literal(Conversion)
|
1044
|
+
|
1045
|
+
argument_or_first_node(input, arg) do |arg_var|
|
1046
|
+
convert_var.assign(conversion.to_string(arg_var))
|
1047
|
+
.followed_by do
|
1048
|
+
if block_given?
|
1049
|
+
convert_var.empty?.if_false { yield }
|
1050
|
+
else
|
1051
|
+
convert_var
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
# @param [Oga::Ruby::Node] input
|
1058
|
+
# @param [AST::Node] haystack
|
1059
|
+
# @param [AST::Node] needle
|
1060
|
+
# @return [Oga::Ruby::Node]
|
1061
|
+
def on_call_substring_before(input, haystack, needle)
|
1062
|
+
haystack_var = unique_literal(:haystack)
|
1063
|
+
needle_var = unique_literal(:needle)
|
1064
|
+
conversion = literal(Conversion)
|
1065
|
+
|
1066
|
+
before = unique_literal(:before)
|
1067
|
+
sep = unique_literal(:sep)
|
1068
|
+
after = unique_literal(:after)
|
1069
|
+
|
1070
|
+
haystack_var.assign(try_match_first_node(haystack, input))
|
1071
|
+
.followed_by do
|
1072
|
+
needle_var.assign(try_match_first_node(needle, input))
|
1073
|
+
end
|
1074
|
+
.followed_by do
|
1075
|
+
converted = conversion.to_string(haystack_var)
|
1076
|
+
.partition(conversion.to_string(needle_var))
|
1077
|
+
|
1078
|
+
mass_assign([before, sep, after], converted).followed_by do
|
1079
|
+
sep.empty?
|
1080
|
+
.if_true { sep }
|
1081
|
+
.else { block_given? ? yield : before }
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
# @param [Oga::Ruby::Node] input
|
1087
|
+
# @param [AST::Node] haystack
|
1088
|
+
# @param [AST::Node] needle
|
1089
|
+
# @return [Oga::Ruby::Node]
|
1090
|
+
def on_call_substring_after(input, haystack, needle)
|
1091
|
+
haystack_var = unique_literal(:haystack)
|
1092
|
+
needle_var = unique_literal(:needle)
|
1093
|
+
conversion = literal(Conversion)
|
1094
|
+
|
1095
|
+
before = unique_literal(:before)
|
1096
|
+
sep = unique_literal(:sep)
|
1097
|
+
after = unique_literal(:after)
|
1098
|
+
|
1099
|
+
haystack_var.assign(try_match_first_node(haystack, input))
|
1100
|
+
.followed_by do
|
1101
|
+
needle_var.assign(try_match_first_node(needle, input))
|
1102
|
+
end
|
1103
|
+
.followed_by do
|
1104
|
+
converted = conversion.to_string(haystack_var)
|
1105
|
+
.partition(conversion.to_string(needle_var))
|
1106
|
+
|
1107
|
+
mass_assign([before, sep, after], converted).followed_by do
|
1108
|
+
sep.empty?
|
1109
|
+
.if_true { sep }
|
1110
|
+
.else { block_given? ? yield : after }
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
# @param [Oga::Ruby::Node] input
|
1116
|
+
# @param [AST::Node] haystack
|
1117
|
+
# @param [AST::Node] start
|
1118
|
+
# @param [AST::Node] length
|
1119
|
+
# @return [Oga::Ruby::Node]
|
1120
|
+
def on_call_substring(input, haystack, start, length = nil)
|
1121
|
+
haystack_var = unique_literal(:haystack)
|
1122
|
+
start_var = unique_literal(:start)
|
1123
|
+
stop_var = unique_literal(:stop)
|
1124
|
+
length_var = unique_literal(:length)
|
1125
|
+
conversion = literal(Conversion)
|
1126
|
+
|
1127
|
+
haystack_var.assign(try_match_first_node(haystack, input))
|
1128
|
+
.followed_by do
|
1129
|
+
start_var.assign(try_match_first_node(start, input))
|
1130
|
+
.followed_by do
|
1131
|
+
start_var.assign(start_var - literal(1))
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
.followed_by do
|
1135
|
+
if length
|
1136
|
+
length_var.assign(try_match_first_node(length, input))
|
1137
|
+
.followed_by do
|
1138
|
+
length_int = conversion.to_float(length_var)
|
1139
|
+
.to_i - literal(1)
|
1140
|
+
|
1141
|
+
stop_var.assign(start_var + length_int)
|
1142
|
+
end
|
1143
|
+
else
|
1144
|
+
stop_var.assign(literal(-1))
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
.followed_by do
|
1148
|
+
substring = conversion
|
1149
|
+
.to_string(haystack_var)[range(start_var, stop_var)]
|
1150
|
+
|
1151
|
+
block_given? ? substring.empty?.if_false { yield } : substring
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
# @param [Oga::Ruby::Node] input
|
1156
|
+
# @param [AST::Node] arg
|
1157
|
+
# @return [Oga::Ruby::Node]
|
1158
|
+
def on_call_sum(input, arg)
|
1159
|
+
unless return_nodeset?(arg)
|
1160
|
+
raise TypeError, 'sum() can only operate on a path, axis or predicate'
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
sum_var = unique_literal(:sum)
|
1164
|
+
conversion = literal(Conversion)
|
1165
|
+
|
1166
|
+
sum_var.assign(literal(0.0))
|
1167
|
+
.followed_by do
|
1168
|
+
process(arg, input) do |matched_node|
|
1169
|
+
sum_var.assign(sum_var + conversion.to_float(matched_node.text))
|
1170
|
+
end
|
1171
|
+
end
|
1172
|
+
.followed_by do
|
1173
|
+
block_given? ? sum_var.zero?.if_false { yield } : sum_var
|
1174
|
+
end
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
# @param [Oga::Ruby::Node] input
|
1178
|
+
# @param [AST::Node] source
|
1179
|
+
# @param [AST::Node] find
|
1180
|
+
# @param [AST::Node] replace
|
1181
|
+
# @return [Oga::Ruby::Node]
|
1182
|
+
def on_call_translate(input, source, find, replace)
|
1183
|
+
source_var = unique_literal(:source)
|
1184
|
+
find_var = unique_literal(:find)
|
1185
|
+
replace_var = unique_literal(:replace)
|
1186
|
+
replaced_var = unique_literal(:replaced)
|
1187
|
+
conversion = literal(Conversion)
|
1188
|
+
|
1189
|
+
char = unique_literal(:char)
|
1190
|
+
index = unique_literal(:index)
|
1191
|
+
|
1192
|
+
source_var.assign(try_match_first_node(source, input))
|
1193
|
+
.followed_by do
|
1194
|
+
replaced_var.assign(conversion.to_string(source_var))
|
1195
|
+
end
|
1196
|
+
.followed_by do
|
1197
|
+
find_var.assign(try_match_first_node(find, input))
|
1198
|
+
end
|
1199
|
+
.followed_by do
|
1200
|
+
find_var.assign(conversion.to_string(find_var).chars.to_array)
|
1201
|
+
end
|
1202
|
+
.followed_by do
|
1203
|
+
replace_var.assign(try_match_first_node(replace, input))
|
1204
|
+
end
|
1205
|
+
.followed_by do
|
1206
|
+
replace_var.assign(conversion.to_string(replace_var).chars.to_array)
|
1207
|
+
end
|
1208
|
+
.followed_by do
|
1209
|
+
find_var.each_with_index.add_block(char, index) do
|
1210
|
+
replace_with = replace_var[index]
|
1211
|
+
.if_true { replace_var[index] }
|
1212
|
+
.else { string('') }
|
1213
|
+
|
1214
|
+
replaced_var.assign(replaced_var.gsub(char, replace_with))
|
1215
|
+
end
|
1216
|
+
end
|
1217
|
+
.followed_by do
|
1218
|
+
replaced_var
|
1219
|
+
end
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# @return [Oga::Ruby::Node]
|
1223
|
+
def on_call_last(*)
|
1224
|
+
set = predicate_nodeset
|
1225
|
+
|
1226
|
+
unless set
|
1227
|
+
raise 'last() can only be used in a predicate'
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
set.length.to_f
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
# @return [Oga::Ruby::Node]
|
1234
|
+
def on_call_position(*)
|
1235
|
+
index = predicate_index
|
1236
|
+
|
1237
|
+
unless index
|
1238
|
+
raise 'position() can only be used in a predicate'
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
index.to_f
|
1242
|
+
end
|
1243
|
+
|
1244
|
+
# Delegates type tests to specific handlers.
|
1245
|
+
#
|
1246
|
+
# @param [AST::Node] ast
|
1247
|
+
# @param [Oga::Ruby::Node] input
|
1248
|
+
# @return [Oga::Ruby::Node]
|
1249
|
+
def on_type_test(ast, input, &block)
|
1250
|
+
name, following = *ast
|
1251
|
+
|
1252
|
+
handler = name.gsub('-', '_')
|
1253
|
+
|
1254
|
+
send(:"on_type_test_#{handler}", input) do |matched|
|
1255
|
+
process_following_or_yield(following, matched, &block)
|
1256
|
+
end
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
# @param [Oga::Ruby::Node] input
|
1260
|
+
# @return [Oga::Ruby::Node]
|
1261
|
+
def on_type_test_comment(input)
|
1262
|
+
input.is_a?(XML::Comment)
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
# @param [Oga::Ruby::Node] input
|
1266
|
+
# @return [Oga::Ruby::Node]
|
1267
|
+
def on_type_test_text(input)
|
1268
|
+
input.is_a?(XML::Text)
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
# @param [Oga::Ruby::Node] input
|
1272
|
+
# @return [Oga::Ruby::Node]
|
1273
|
+
def on_type_test_processing_instruction(input)
|
1274
|
+
input.is_a?(XML::ProcessingInstruction)
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
# @param [Oga::Ruby::Node] input
|
1278
|
+
# @return [Oga::Ruby::Node]
|
1279
|
+
def on_type_test_node(input)
|
1280
|
+
document_or_node(input).or(input.is_a?(XML::Attribute))
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
# @param [#to_s] value
|
1284
|
+
# @return [Oga::Ruby::Node]
|
1285
|
+
def literal(value)
|
1286
|
+
Ruby::Node.new(:lit, [value.to_s])
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
# @param [Oga::Ruby::Node] start
|
1290
|
+
# @param [Oga::Ruby::Node] stop
|
1291
|
+
# @return [Oga::Ruby::Node]
|
1292
|
+
def range(start, stop)
|
1293
|
+
Ruby::Node.new(:range, [start, stop])
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
# @param [String] name
|
1297
|
+
# @return [Oga::Ruby::Node]
|
1298
|
+
def unique_literal(name)
|
1299
|
+
new_id = @literal_id += 1
|
1300
|
+
|
1301
|
+
literal("#{name}#{new_id}")
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
# @param [#to_s] value
|
1305
|
+
# @return [Oga::Ruby::Node]
|
1306
|
+
def string(value)
|
1307
|
+
Ruby::Node.new(:string, [value.to_s])
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
# @param [String] value
|
1311
|
+
# @return [Oga::Ruby::Node]
|
1312
|
+
def symbol(value)
|
1313
|
+
Ruby::Node.new(:symbol, [value.to_sym])
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
# @param [String] name
|
1317
|
+
# @param [Array] args
|
1318
|
+
# @return [Oga::Ruby::Node]
|
1319
|
+
def send_message(name, *args)
|
1320
|
+
Ruby::Node.new(:send, [nil, name.to_s, *args])
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
# @param [Class] klass
|
1324
|
+
# @param [String] message
|
1325
|
+
# @return [Oga::Ruby::Node]
|
1326
|
+
def raise_message(klass, message)
|
1327
|
+
send_message(:raise, literal(klass), string(message))
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
# @return [Oga::Ruby::Node]
|
1331
|
+
def nil
|
1332
|
+
@nil ||= literal(:nil)
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
# @return [Oga::Ruby::Node]
|
1336
|
+
def true
|
1337
|
+
@true ||= literal(:true)
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# @return [Oga::Ruby::Node]
|
1341
|
+
def false
|
1342
|
+
@false ||= literal(:false)
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
# @param [Oga::Ruby::Node] node
|
1346
|
+
# @return [Oga::Ruby::Node]
|
1347
|
+
def element_or_attribute(node)
|
1348
|
+
node.is_a?(XML::Element).or(node.is_a?(XML::Attribute))
|
1349
|
+
end
|
1350
|
+
|
1351
|
+
# @param [Oga::Ruby::Node] node
|
1352
|
+
# @return [Oga::Ruby::Node]
|
1353
|
+
def attribute_or_node(node)
|
1354
|
+
node.is_a?(XML::Attribute).or(node.is_a?(XML::Node))
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
# @param [Oga::Ruby::Node] node
|
1358
|
+
# @return [Oga::Ruby::Node]
|
1359
|
+
def document_or_node(node)
|
1360
|
+
node.is_a?(XML::Document).or(node.is_a?(XML::Node))
|
1361
|
+
end
|
1362
|
+
|
1363
|
+
# @param [AST::Node] ast
|
1364
|
+
# @param [Oga::Ruby::Node] input
|
1365
|
+
# @return [Oga::Ruby::Node]
|
1366
|
+
def match_name_and_namespace(ast, input)
|
1367
|
+
ns, name = *ast
|
1368
|
+
|
1369
|
+
condition = nil
|
1370
|
+
name_str = string(name)
|
1371
|
+
zero = literal(0)
|
1372
|
+
|
1373
|
+
if name != STAR
|
1374
|
+
condition = input.name.eq(name_str)
|
1375
|
+
.or(input.name.casecmp(name_str).eq(zero))
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
if ns and ns != STAR
|
1379
|
+
ns_match = input.namespace_name.eq(string(ns))
|
1380
|
+
condition = condition ? condition.and(ns_match) : ns_match
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
condition
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
# Returns an AST matching the first node of a node set.
|
1387
|
+
#
|
1388
|
+
# @param [Oga::Ruby::Node] ast
|
1389
|
+
# @param [Oga::Ruby::Node] input
|
1390
|
+
# @return [Oga::Ruby::Node]
|
1391
|
+
def match_first_node(ast, input)
|
1392
|
+
catch_message(:value) do
|
1393
|
+
process(ast, input) do |node|
|
1394
|
+
throw_message(:value, node)
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
# Tries to match the first node in a set, otherwise processes it as usual.
|
1400
|
+
#
|
1401
|
+
# @see [#match_first_node]
|
1402
|
+
def try_match_first_node(ast, input, optimize_first = true)
|
1403
|
+
if return_nodeset?(ast) and optimize_first
|
1404
|
+
match_first_node(ast, input)
|
1405
|
+
else
|
1406
|
+
process(ast, input)
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
# @param [Oga::Ruby::Node] input
|
1411
|
+
# @return [Oga::Ruby::Node]
|
1412
|
+
def ensure_element_or_attribute(input)
|
1413
|
+
element_or_attribute(input).if_false do
|
1414
|
+
raise_message(TypeError, 'argument is not an Element or Attribute')
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
# @param [Oga::Ruby::Node] input
|
1419
|
+
# @param [AST::Ruby] arg
|
1420
|
+
# @return [Oga::Ruby::Node]
|
1421
|
+
def argument_or_first_node(input, arg = nil)
|
1422
|
+
arg_ast = arg ? try_match_first_node(arg, input) : input
|
1423
|
+
arg_var = unique_literal(:argument_or_first_node)
|
1424
|
+
|
1425
|
+
arg_var.assign(arg_ast).followed_by { yield arg_var }
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
# Generates the code for an operator.
|
1429
|
+
#
|
1430
|
+
# The generated code is optimized so that expressions such as `a/b = c`
|
1431
|
+
# only match the first node in both arms instead of matching all available
|
1432
|
+
# nodes first. Because numeric operators only ever operates on the first
|
1433
|
+
# node in a set we can simply ditch the rest, possibly speeding things up
|
1434
|
+
# quite a bit. This only works if one of the arms is:
|
1435
|
+
#
|
1436
|
+
# * a path
|
1437
|
+
# * an axis
|
1438
|
+
# * a predicate
|
1439
|
+
#
|
1440
|
+
# Everything else is processed the usual (and possibly slower) way.
|
1441
|
+
#
|
1442
|
+
# @param [AST::Node] ast
|
1443
|
+
# @param [Oga::Ruby::Node] input
|
1444
|
+
# @param [TrueClass|FalseClass] optimize_first
|
1445
|
+
# @return [Oga::Ruby::Node]
|
1446
|
+
def operator(ast, input, optimize_first = true)
|
1447
|
+
left, right = *ast
|
1448
|
+
|
1449
|
+
left_var = unique_literal(:op_left)
|
1450
|
+
right_var = unique_literal(:op_right)
|
1451
|
+
|
1452
|
+
left_ast = try_match_first_node(left, input, optimize_first)
|
1453
|
+
right_ast = try_match_first_node(right, input, optimize_first)
|
1454
|
+
|
1455
|
+
left_var.assign(left_ast)
|
1456
|
+
.followed_by(right_var.assign(right_ast))
|
1457
|
+
.followed_by { yield left_var, right_var }
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
# @return [Oga::Ruby::Node]
|
1461
|
+
def matched_literal
|
1462
|
+
literal(:matched)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
# @return [Oga::Ruby::Node]
|
1466
|
+
def original_input_literal
|
1467
|
+
literal(:original_input)
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
# @return [Oga::Ruby::Node]
|
1471
|
+
def variables_literal
|
1472
|
+
literal(:variables)
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
# @param [AST::Node] ast
|
1476
|
+
# @return [Oga::Ruby::Node]
|
1477
|
+
def to_int(ast)
|
1478
|
+
literal(ast.children[0].to_i.to_s)
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
# @param [Array] vars The variables to assign.
|
1482
|
+
# @param [Oga::Ruby::Node] value
|
1483
|
+
# @return [Oga::Ruby::Node]
|
1484
|
+
def mass_assign(vars, value)
|
1485
|
+
Ruby::Node.new(:massign, [vars, value])
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
# @param [AST::Node] ast
|
1489
|
+
# @return [TrueClass|FalseClass]
|
1490
|
+
def number?(ast)
|
1491
|
+
ast.type == :int || ast.type == :float
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
# @param [AST::Node] ast
|
1495
|
+
# @return [TrueClass|FalseClass]
|
1496
|
+
def string?(ast)
|
1497
|
+
ast.type == :string
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
# @param [Symbol] name
|
1501
|
+
# @return [Oga::Ruby::Node]
|
1502
|
+
def catch_message(name)
|
1503
|
+
send_message(:catch, symbol(name)).add_block do
|
1504
|
+
# Ensure that the "catch" only returns a value when "throw" is
|
1505
|
+
# actually invoked.
|
1506
|
+
yield.followed_by(self.nil)
|
1507
|
+
end
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
# @param [Symbol] name
|
1511
|
+
# @param [Array] args
|
1512
|
+
# @return [Oga::Ruby::Node]
|
1513
|
+
def throw_message(name, *args)
|
1514
|
+
send_message(:throw, symbol(name), *args)
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
# @return [Oga::Ruby::Node]
|
1518
|
+
def break
|
1519
|
+
send_message(:break)
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
# @param [AST::Node] ast
|
1523
|
+
# @return [TrueClass|FalseClass]
|
1524
|
+
def return_nodeset?(ast)
|
1525
|
+
RETURN_NODESET.include?(ast.type)
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
# @param [AST::Node] ast
|
1529
|
+
# @return [TrueClass|FalseClass]
|
1530
|
+
def has_call_node?(ast, name)
|
1531
|
+
visit = [ast]
|
1532
|
+
|
1533
|
+
until visit.empty?
|
1534
|
+
current = visit.pop
|
1535
|
+
|
1536
|
+
return true if current.type == :call && current.children[0] == name
|
1537
|
+
|
1538
|
+
current.children.each do |child|
|
1539
|
+
visit << child if child.is_a?(AST::Node)
|
1540
|
+
end
|
1541
|
+
end
|
1542
|
+
|
1543
|
+
false
|
1544
|
+
end
|
1545
|
+
|
1546
|
+
# @return [Oga::Ruby::Node]
|
1547
|
+
def predicate_index
|
1548
|
+
@predicate_indexes.last
|
1549
|
+
end
|
1550
|
+
|
1551
|
+
# @return [Oga::Ruby::Node]
|
1552
|
+
def predicate_nodeset
|
1553
|
+
@predicate_nodesets.last
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
# @param [AST::Node] following
|
1557
|
+
# @param [Oga::Ruby::Node] matched
|
1558
|
+
# @return [Oga::Ruby::Node]
|
1559
|
+
def process_following_or_yield(following, matched, &block)
|
1560
|
+
if following
|
1561
|
+
process(following, matched, &block)
|
1562
|
+
else
|
1563
|
+
yield matched
|
1564
|
+
end
|
1565
|
+
end
|
1566
|
+
end # Compiler
|
1567
|
+
end # XPath
|
1568
|
+
end # Oga
|