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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/doc/css_selectors.md +1 -1
  3. data/lib/liboga.jar +0 -0
  4. data/lib/oga.rb +6 -1
  5. data/lib/oga/blacklist.rb +0 -10
  6. data/lib/oga/css/lexer.rb +530 -255
  7. data/lib/oga/css/parser.rb +232 -230
  8. data/lib/oga/entity_decoder.rb +0 -4
  9. data/lib/oga/html/entities.rb +0 -4
  10. data/lib/oga/html/parser.rb +0 -4
  11. data/lib/oga/html/sax_parser.rb +0 -4
  12. data/lib/oga/lru.rb +0 -26
  13. data/lib/oga/oga.rb +0 -8
  14. data/lib/oga/ruby/generator.rb +225 -0
  15. data/lib/oga/ruby/node.rb +189 -0
  16. data/lib/oga/version.rb +1 -1
  17. data/lib/oga/whitelist.rb +0 -6
  18. data/lib/oga/xml/attribute.rb +13 -20
  19. data/lib/oga/xml/cdata.rb +0 -4
  20. data/lib/oga/xml/character_node.rb +0 -8
  21. data/lib/oga/xml/comment.rb +0 -4
  22. data/lib/oga/xml/default_namespace.rb +0 -2
  23. data/lib/oga/xml/doctype.rb +0 -8
  24. data/lib/oga/xml/document.rb +10 -14
  25. data/lib/oga/xml/element.rb +1 -52
  26. data/lib/oga/xml/entities.rb +0 -26
  27. data/lib/oga/xml/expanded_name.rb +12 -0
  28. data/lib/oga/xml/html_void_elements.rb +0 -2
  29. data/lib/oga/xml/lexer.rb +0 -86
  30. data/lib/oga/xml/namespace.rb +0 -10
  31. data/lib/oga/xml/node.rb +18 -34
  32. data/lib/oga/xml/node_set.rb +0 -50
  33. data/lib/oga/xml/parser.rb +13 -50
  34. data/lib/oga/xml/processing_instruction.rb +0 -8
  35. data/lib/oga/xml/pull_parser.rb +0 -18
  36. data/lib/oga/xml/querying.rb +58 -19
  37. data/lib/oga/xml/sax_parser.rb +0 -18
  38. data/lib/oga/xml/text.rb +0 -12
  39. data/lib/oga/xml/traversal.rb +0 -4
  40. data/lib/oga/xml/xml_declaration.rb +0 -8
  41. data/lib/oga/xpath/compiler.rb +1568 -0
  42. data/lib/oga/xpath/conversion.rb +102 -0
  43. data/lib/oga/xpath/lexer.rb +1844 -1238
  44. data/lib/oga/xpath/parser.rb +182 -153
  45. metadata +7 -3
  46. data/lib/oga/xpath/evaluator.rb +0 -1800
@@ -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
@@ -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
 
@@ -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