class Oga::XPath::Compiler

Compiling of XPath ASTs into Ruby code.

The Compiler class can be used to turn an XPath AST into Ruby source code that can be executed to match XML nodes in a given input document/element. Compiled source code is cached per expression, removing the need for recompiling the same expression over and over again.

@private

Constants

CACHE

@return [Oga::LRU]

CONTEXT

Context for compiled Procs. As compiled Procs do not mutate the enclosing environment we can just re-use the same instance without synchronization.

OPERATORS

Hash containing all operator callbacks, the conversion methods and the Ruby methods to use.

RETURN_NODESET

Node types that require a NodeSet to push nodes into.

STAR

Wildcard for node names/namespace prefixes.

Public Class Methods

compile_with_cache(ast, namespaces: nil) click to toggle source

Compiles and caches an AST.

@see [#compile]

# File lib/oga/xpath/compiler.rb, line 45
def self.compile_with_cache(ast, namespaces: nil)
  cache_key = namespaces ? [ast, namespaces] : ast
  CACHE.get_or_set(cache_key) { new(namespaces: namespaces).compile(ast) }
end
new(namespaces: nil) click to toggle source

@param [Hash] namespaces

# File lib/oga/xpath/compiler.rb, line 51
def initialize(namespaces: nil)
  reset

  @namespaces = namespaces
end

Public Instance Methods

argument_or_first_node(input, arg = nil) { |arg_var| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Ruby] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1450
def argument_or_first_node(input, arg = nil)
  arg_ast = arg ? try_match_first_node(arg, input) : input
  arg_var = unique_literal(:argument_or_first_node)

  arg_var.assign(arg_ast).followed_by { yield arg_var }
end
attribute_or_node(node) click to toggle source

@param [Oga::Ruby::Node] node @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1366
def attribute_or_node(node)
  node.is_a?(XML::Attribute).or(node.is_a?(XML::Node))
end
break() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1547
def break
  send_message(:break)
end
catch_message(name) { || ... } click to toggle source

@param [Symbol] name @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1531
def catch_message(name)
  send_message(:catch, symbol(name)).add_block do
    # Ensure that the "catch" only returns a value when "throw" is
    # actually invoked.
    yield.followed_by(self.nil)
  end
end
compile(ast) click to toggle source

Compiles an XPath AST into a Ruby Proc.

@param [AST::Node] ast @return [Proc]

# File lib/oga/xpath/compiler.rb, line 69
def compile(ast)
  document = literal(:node)
  matched  = matched_literal

  if return_nodeset?(ast)
    ruby_ast = process(ast, document) { |node| matched.push(node) }
  else
    ruby_ast = process(ast, document)
  end

  vars = variables_literal.assign(self.nil)

  proc_ast = literal(:lambda).add_block(document, vars) do
    input_assign = original_input_literal.assign(document)

    if return_nodeset?(ast)
      body = matched.assign(literal(XML::NodeSet).new)
        .followed_by(ruby_ast)
        .followed_by(matched)
    else
      body = ruby_ast
    end

    input_assign.followed_by(body)
  end

  generator = Ruby::Generator.new
  source    = generator.process(proc_ast)

  CONTEXT.evaluate(source)
ensure
  reset
end
document_or_node(node) click to toggle source

@param [Oga::Ruby::Node] node @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1372
def document_or_node(node)
  node.is_a?(XML::Document).or(node.is_a?(XML::Node))
end
element_or_attribute(node) click to toggle source

@param [Oga::Ruby::Node] node @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1360
def element_or_attribute(node)
  node.is_a?(XML::Element).or(node.is_a?(XML::Attribute))
end
ensure_element_or_attribute(input) click to toggle source

@param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1441
def ensure_element_or_attribute(input)
  element_or_attribute(input).if_false do
    raise_message(TypeError, 'argument is not an Element or Attribute')
  end
end
false() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1354
def false
  @false ||= literal(:false)
end
has_call_node?(ast, name) click to toggle source

@param [AST::Node] ast @return [TrueClass|FalseClass]

# File lib/oga/xpath/compiler.rb, line 1559
def has_call_node?(ast, name)
  visit = [ast]

  until visit.empty?
    current = visit.pop

    return true if current.type == :call && current.children[0] == name

    current.children.each do |child|
      visit << child if child.is_a?(AST::Node)
    end
  end

  false
end
literal(value) click to toggle source

@param [#to_s] value @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1298
def literal(value)
  Ruby::Node.new(:lit, [value.to_s])
end
mass_assign(vars, value) click to toggle source

@param [Array] vars The variables to assign. @param [Oga::Ruby::Node] value @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1513
def mass_assign(vars, value)
  Ruby::Node.new(:massign, [vars, value])
end
match_first_node(ast, input) click to toggle source

Returns an AST matching the first node of a node set.

@param [Oga::Ruby::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1420
def match_first_node(ast, input)
  catch_message(:value) do
    process(ast, input) do |node|
      throw_message(:value, node)
    end
  end
end
match_name_and_namespace(ast, input) click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1379
def match_name_and_namespace(ast, input)
  ns, name = *ast

  condition = nil
  name_str  = string(name)
  zero      = literal(0)

  if name != STAR
    condition = input.name.eq(name_str)
      .or(input.name.casecmp(name_str).eq(zero))
  end

  if ns and ns != STAR
    if @namespaces
      ns_uri = @namespaces[ns]
      ns_match =
        if ns_uri
          input.namespace.and(input.namespace.uri.eq(string(ns_uri)))
        else
          self.false
        end
    else
      ns_match =
        if ns == XML::Element::XMLNS_PREFIX
          input
        else
          input.namespace_name.eq(string(ns))
        end
    end

    condition = condition ? condition.and(ns_match) : ns_match
  end

  condition
end
matched_literal() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1490
def matched_literal
  literal(:matched)
end
nil() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1344
def nil
  @nil ||= literal(:nil)
end
number?(ast) click to toggle source

@param [AST::Node] ast @return [TrueClass|FalseClass]

# File lib/oga/xpath/compiler.rb, line 1519
def number?(ast)
  ast.type == :int || ast.type == :float
end
on_absolute_path(ast, input, &block) click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 115
def on_absolute_path(ast, input, &block)
  if ast.children.empty?
    matched_literal.push(input.root_node)
  else
    process(ast.children[0], input.root_node, &block)
  end
end
on_axis(ast, input, &block) click to toggle source

Dispatches the processing of axes to dedicated methods. This works similar to {#process} except the handler names are “on_axis_X” with “X” being the axis name.

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 130
def on_axis(ast, input, &block)
  name, test, following = *ast

  handler = name.gsub('-', '_')

  send(:"on_axis_#{handler}", test, input) do |matched|
    process_following_or_yield(following, matched, &block)
  end
end
on_axis_ancestor(ast, input) { |parent| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 192
def on_axis_ancestor(ast, input)
  parent = unique_literal(:parent)

  attribute_or_node(input).if_true do
    input.each_ancestor.add_block(parent) do
      process(ast, parent).if_true { yield parent }
    end
  end
end
on_axis_ancestor_or_self(ast, input) { |input| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 175
def on_axis_ancestor_or_self(ast, input)
  parent = unique_literal(:parent)

  process(ast, input).and(input.is_a?(XML::Node))
    .if_true { yield input }
    .followed_by do
      attribute_or_node(input).if_true do
        input.each_ancestor.add_block(parent) do
          process(ast, parent).if_true { yield parent }
        end
      end
    end
end
on_axis_attribute(ast, input) { |attribute| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 156
def on_axis_attribute(ast, input)
  input.is_a?(XML::Element).if_true do
    attribute = unique_literal(:attribute)

    input.attributes.each.add_block(attribute) do
      name_match = match_name_and_namespace(ast, attribute)

      if name_match
        name_match.if_true { yield attribute }
      else
        yield attribute
      end
    end
  end
end
on_axis_child(ast, input) { |child| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 143
def on_axis_child(ast, input)
  child = unique_literal(:child)

  document_or_node(input).if_true do
    input.children.each.add_block(child) do
      process(ast, child).if_true { yield child }
    end
  end
end
on_axis_descendant(ast, input) { |node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 222
def on_axis_descendant(ast, input)
  node = unique_literal(:descendant)

  document_or_node(input).if_true do
    input.each_node.add_block(node) do
      process(ast, node).if_true { yield node }
    end
  end
end
on_axis_descendant_or_self(ast, input) { |input| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 205
def on_axis_descendant_or_self(ast, input)
  node = unique_literal(:descendant)

  document_or_node(input).if_true do
    process(ast, input)
      .if_true { yield input }
      .followed_by do
        input.each_node.add_block(node) do
          process(ast, node).if_true { yield node }
        end
      end
  end
end
on_axis_following(ast, input) { |doc_node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 295
def on_axis_following(ast, input)
  orig_input = original_input_literal
  doc_node   = literal(:doc_node)
  check      = literal(:check)
  root       = literal(:root)

  orig_input.is_a?(XML::Node)
    .if_true { root.assign(orig_input.root_node) }
    .else    { root.assign(orig_input) }
    .followed_by(check.assign(self.false))
    .followed_by do
      document_or_node(root).if_true do
        root.each_node.add_block(doc_node) do
          doc_node.eq(input)
            .if_true do
              check.assign(self.true)
                .followed_by(throw_message(:skip_children))
            end
            .followed_by do
              check.if_false { send_message(:next) }
            end
            .followed_by do
              process(ast, doc_node).if_true { yield doc_node }
            end
        end
      end
    end
end
on_axis_following_sibling(ast, input) { |doc_node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 255
def on_axis_following_sibling(ast, input)
  orig_input = original_input_literal
  doc_node   = literal(:doc_node)
  check      = literal(:check)
  parent     = literal(:parent)
  root       = literal(:root)

  orig_input.is_a?(XML::Node)
    .if_true { root.assign(orig_input.parent) }
    .else    { root.assign(orig_input) }
    .followed_by do
      input.is_a?(XML::Node).and(input.parent)
        .if_true { parent.assign(input.parent) }
        .else    { parent.assign(self.nil) }
    end
    .followed_by(check.assign(self.false))
    .followed_by do
      document_or_node(root).if_true do
        root.each_node.add_block(doc_node) do
          doc_node.eq(input)
            .if_true do
              check.assign(self.true)
                .followed_by(throw_message(:skip_children))
            end
            .followed_by do
              check.not.or(parent != doc_node.parent).if_true do
                send_message(:next)
              end
            end
            .followed_by do
              process(ast, doc_node).if_true { yield doc_node }
            end
        end
      end
    end
end
on_axis_namespace(ast, input) { |node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 327
def on_axis_namespace(ast, input)
  underscore = literal(:_)
  node       = unique_literal(:namespace)

  name = string(ast.children[1])
  star = string(STAR)

  input.is_a?(XML::Element).if_true do
    input.available_namespaces.each.add_block(underscore, node) do
      node.name.eq(name).or(name.eq(star)).if_true { yield node }
    end
  end
end
on_axis_parent(ast, input) { |parent| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 235
def on_axis_parent(ast, input)
  parent = unique_literal(:parent)

  attribute_or_node(input).if_true do
    parent.assign(input.parent).followed_by do
      process(ast, parent).if_true { yield parent }
    end
  end
end
on_axis_preceding(ast, input) { |doc_node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 344
def on_axis_preceding(ast, input)
  root     = literal(:root)
  doc_node = literal(:doc_node)

  input.is_a?(XML::Node).if_true do
    root.assign(input.root_node)
      .followed_by do
        document_or_node(root).if_true do
          root.each_node.add_block(doc_node) do
            doc_node.eq(input)
              .if_true { self.break }
              .followed_by do
                process(ast, doc_node).if_true { yield doc_node }
              end
          end
        end
      end
  end
end
on_axis_preceding_sibling(ast, input) { |doc_node| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 367
def on_axis_preceding_sibling(ast, input)
  orig_input = original_input_literal
  check      = literal(:check)
  root       = literal(:root)
  parent     = literal(:parent)
  doc_node   = literal(:doc_node)

  orig_input.is_a?(XML::Node)
    .if_true { root.assign(orig_input.parent) }
    .else    { root.assign(orig_input) }
    .followed_by(check.assign(self.false))
    .followed_by do
      input.is_a?(XML::Node).and(input.parent)
        .if_true { parent.assign(input.parent) }
        .else    { parent.assign(self.nil) }
    end
    .followed_by do
      document_or_node(root).if_true do
        root.each_node.add_block(doc_node) do
          doc_node.eq(input)
            .if_true { self.break }
            .followed_by do
              doc_node.parent.eq(parent).if_true do
                process(ast, doc_node).if_true { yield doc_node }
              end
            end
        end
      end
    end
end
on_axis_self(ast, input) { |input| ... } click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 248
def on_axis_self(ast, input)
  process(ast, input).if_true { yield input }
end
on_call(ast, input, &block) click to toggle source

Delegates function calls to specific handlers.

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 645
def on_call(ast, input, &block)
  name, *args = *ast

  handler = name.gsub('-', '_')

  send(:"on_call_#{handler}", input, *args, &block)
end
on_call_boolean(input, arg) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 666
def on_call_boolean(input, arg)
  arg_ast    = try_match_first_node(arg, input)
  call_arg   = unique_literal(:call_arg)
  conversion = literal(Conversion)

  call_arg.assign(arg_ast).followed_by do
    converted = conversion.to_boolean(call_arg)

    block_given? ? converted.if_true { yield } : converted
  end
end
on_call_ceiling(input, arg) { |: ceil.to_f| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 681
def on_call_ceiling(input, arg)
  arg_ast    = try_match_first_node(arg, input)
  call_arg   = unique_literal(:call_arg)
  conversion = literal(Conversion)

  call_arg.assign(arg_ast)
    .followed_by do
      call_arg.assign(conversion.to_float(call_arg))
    end
    .followed_by do
      call_arg.nan?
        .if_true { call_arg }
        .else    { block_given? ? yield : call_arg.ceil.to_f }
    end
end
on_call_concat(input, *args) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [Array<AST::Node>] args @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 738
def on_call_concat(input, *args)
  conversion  = literal(Conversion)
  assigns     = []
  conversions = []

  args.each do |arg|
    arg_var = unique_literal(:concat_arg)
    arg_ast = try_match_first_node(arg, input)

    assigns     << arg_var.assign(arg_ast)
    conversions << conversion.to_string(arg_var)
  end

  concatted = assigns.inject(:followed_by)
    .followed_by(conversions.inject(:+))

  block_given? ? concatted.empty?.if_false { yield } : concatted
end
on_call_contains(input, haystack, needle) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] haystack @param [AST::Node] needle @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 761
def on_call_contains(input, haystack, needle)
  haystack_lit = unique_literal(:haystack)
  needle_lit   = unique_literal(:needle)
  conversion   = literal(Conversion)

  haystack_lit.assign(try_match_first_node(haystack, input))
    .followed_by do
      needle_lit.assign(try_match_first_node(needle, input))
    end
    .followed_by do
      converted = conversion.to_string(haystack_lit)
        .include?(conversion.to_string(needle_lit))

      block_given? ? converted.if_true { yield } : converted
    end
end
on_call_count(input, arg) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 781
def on_call_count(input, arg)
  count = unique_literal(:count)

  unless return_nodeset?(arg)
    raise TypeError, 'count() can only operate on NodeSet instances'
  end

  count.assign(literal(0.0))
    .followed_by do
      process(arg, input) { count.assign(count + literal(1)) }
    end
    .followed_by do
      block_given? ? count.zero?.if_false { yield } : count
    end
end
on_call_false(*) click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 659
def on_call_false(*)
  self.false
end
on_call_floor(input, arg) { |: floor.to_f| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 700
def on_call_floor(input, arg)
  arg_ast    = try_match_first_node(arg, input)
  call_arg   = unique_literal(:call_arg)
  conversion = literal(Conversion)

  call_arg.assign(arg_ast)
    .followed_by do
      call_arg.assign(conversion.to_float(call_arg))
    end
    .followed_by do
      call_arg.nan?
        .if_true { call_arg }
        .else    { block_given? ? yield : call_arg.floor.to_f }
    end
end
on_call_id(input, arg) { |: matched << node| ... } click to toggle source

Processes the ‘id()` function call.

The XPath specification states that this function’s behaviour should be controlled by a DTD. If a DTD were to specify that the ID attribute for a certain element would be “foo” then this function should use said attribute.

Oga does not support DTD parsing/evaluation and as such always uses the “id” attribute.

This function searches the entire document for a matching node, regardless of the current position.

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 813
def on_call_id(input, arg)
  orig_input = original_input_literal
  node       = unique_literal(:node)
  ids_var    = unique_literal('ids')
  matched    = unique_literal('id_matched')
  id_str_var = unique_literal('id_string')
  attr_var   = unique_literal('attr')

  matched.assign(literal(XML::NodeSet).new)
    .followed_by do
      # When using some sort of path we'll want the text of all matched
      # nodes.
      if return_nodeset?(arg)
        ids_var.assign(literal(:[])).followed_by do
          process(arg, input) { |element| ids_var << element.text }
        end

      # For everything else we'll cast the value to a string and split it
      # on every space.
      else
        conversion = literal(Conversion).to_string(ids_var)
          .split(string(' '))

        ids_var.assign(process(arg, input))
          .followed_by(ids_var.assign(conversion))
      end
    end
    .followed_by do
      id_str_var.assign(string('id'))
    end
    .followed_by do
      orig_input.each_node.add_block(node) do
        node.is_a?(XML::Element).if_true do
          attr_var.assign(node.attribute(id_str_var)).followed_by do
            attr_var.and(ids_var.include?(attr_var.value))
              .if_true { block_given? ? yield : matched << node }
          end
        end
      end
    end
    .followed_by(matched)
end
on_call_lang(input, arg) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 859
def on_call_lang(input, arg)
  lang_var = unique_literal('lang')
  node     = unique_literal('node')
  found    = unique_literal('found')
  xml_lang = unique_literal('xml_lang')
  matched  = unique_literal('matched')

  conversion = literal(Conversion)

  ast = lang_var.assign(try_match_first_node(arg, input))
    .followed_by do
      lang_var.assign(conversion.to_string(lang_var))
    end
    .followed_by do
      matched.assign(self.false)
    end
    .followed_by do
      node.assign(input)
    end
    .followed_by do
      xml_lang.assign(string('xml:lang'))
    end
    .followed_by do
      node.respond_to?(symbol(:attribute)).while_true do
        found.assign(node.get(xml_lang))
          .followed_by do
            found.if_true do
              found.eq(lang_var)
                .if_true do
                  if block_given?
                    yield
                  else
                    matched.assign(self.true).followed_by(self.break)
                  end
                end
                .else { self.break }
            end
          end
          .followed_by(node.assign(node.parent))
      end
    end

  block_given? ? ast : ast.followed_by(matched)
end
on_call_last(*) click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1236
def on_call_last(*)
  set = predicate_nodeset

  unless set
    raise 'last() can only be used in a predicate'
  end

  set.length.to_f
end
on_call_local_name(input, arg = nil) { |: name| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 907
def on_call_local_name(input, arg = nil)
  argument_or_first_node(input, arg) do |arg_var|
    arg_var
      .if_true do
        ensure_element_or_attribute(arg_var)
          .followed_by { block_given? ? yield : arg_var.name }
      end
      .else { string('') }
  end
end
on_call_name(input, arg = nil) { |: expanded_name| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 921
def on_call_name(input, arg = nil)
  argument_or_first_node(input, arg) do |arg_var|
    arg_var
      .if_true do
        ensure_element_or_attribute(arg_var)
          .followed_by { block_given? ? yield : arg_var.expanded_name }
      end
      .else { string('') }
  end
end
on_call_namespace_uri(input, arg = nil) { |: namespace.uri| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 935
def on_call_namespace_uri(input, arg = nil)
  default = string('')

  argument_or_first_node(input, arg) do |arg_var|
    arg_var
      .if_true do
        ensure_element_or_attribute(arg_var).followed_by do
          arg_var.namespace
            .if_true { block_given? ? yield : arg_var.namespace.uri }
            .else    { default } # no yield so predicates aren't matched
        end
      end
      .else { default }
  end
end
on_call_normalize_space(input, arg = nil) { |: norm_var| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 954
def on_call_normalize_space(input, arg = nil)
  conversion = literal(Conversion)
  norm_var   = unique_literal(:normalized)

  find    = literal('/\s+/')
  replace = string(' ')

  argument_or_first_node(input, arg) do |arg_var|
    norm_var
      .assign(conversion.to_string(arg_var).strip.gsub(find, replace))
      .followed_by do
        norm_var.empty?
          .if_true { string('') }
          .else    { block_given? ? yield : norm_var }
      end
  end
end
on_call_not(input, arg) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 975
def on_call_not(input, arg)
  arg_ast    = try_match_first_node(arg, input)
  call_arg   = unique_literal(:call_arg)
  conversion = literal(Conversion)

  call_arg.assign(arg_ast).followed_by do
    converted = conversion.to_boolean(call_arg).not

    block_given? ? converted.if_true { yield } : converted
  end
end
on_call_number(input, arg = nil) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 990
def on_call_number(input, arg = nil)
  convert_var = unique_literal(:convert)
  conversion  = literal(Conversion)

  argument_or_first_node(input, arg) do |arg_var|
    convert_var.assign(conversion.to_float(arg_var)).followed_by do
      if block_given?
        convert_var.zero?.if_false { yield }
      else
        convert_var
      end
    end
  end
end
on_call_position(*) click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1247
def on_call_position(*)
  index = predicate_index

  unless index
    raise 'position() can only be used in a predicate'
  end

  index.to_f
end
on_call_round(input, arg) { |: round.to_f| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 719
def on_call_round(input, arg)
  arg_ast    = try_match_first_node(arg, input)
  call_arg   = unique_literal(:call_arg)
  conversion = literal(Conversion)

  call_arg.assign(arg_ast)
    .followed_by do
      call_arg.assign(conversion.to_float(call_arg))
    end
    .followed_by do
      call_arg.nan?
        .if_true { call_arg }
        .else    { block_given? ? yield : call_arg.round.to_f }
    end
end
on_call_starts_with(input, haystack, needle) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] haystack @param [AST::Node] needle @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1009
def on_call_starts_with(input, haystack, needle)
  haystack_var = unique_literal(:haystack)
  needle_var   = unique_literal(:needle)
  conversion   = literal(Conversion)

  haystack_var.assign(try_match_first_node(haystack, input))
    .followed_by do
      needle_var.assign(try_match_first_node(needle, input))
    end
    .followed_by do
      haystack_var.assign(conversion.to_string(haystack_var))
        .followed_by do
          needle_var.assign(conversion.to_string(needle_var))
        end
        .followed_by do
          equal = needle_var.empty?
            .or(haystack_var.start_with?(needle_var))

          block_given? ? equal.if_true { yield } : equal
        end
    end
end
on_call_string(input, arg = nil) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1054
def on_call_string(input, arg = nil)
  convert_var = unique_literal(:convert)
  conversion  = literal(Conversion)

  argument_or_first_node(input, arg) do |arg_var|
    convert_var.assign(conversion.to_string(arg_var))
      .followed_by do
        if block_given?
          convert_var.empty?.if_false { yield }
        else
          convert_var
        end
      end
  end
end
on_call_string_length(input, arg = nil) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1035
def on_call_string_length(input, arg = nil)
  convert_var = unique_literal(:convert)
  conversion  = literal(Conversion)

  argument_or_first_node(input, arg) do |arg_var|
    convert_var.assign(conversion.to_string(arg_var).length)
      .followed_by do
        if block_given?
          convert_var.zero?.if_false { yield }
        else
          convert_var.to_f
        end
      end
  end
end
on_call_substring(input, haystack, start, length = nil) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] haystack @param [AST::Node] start @param [AST::Node] length @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1133
def on_call_substring(input, haystack, start, length = nil)
  haystack_var = unique_literal(:haystack)
  start_var    = unique_literal(:start)
  stop_var     = unique_literal(:stop)
  length_var   = unique_literal(:length)
  conversion   = literal(Conversion)

  haystack_var.assign(try_match_first_node(haystack, input))
    .followed_by do
      start_var.assign(try_match_first_node(start, input))
        .followed_by do
          start_var.assign(start_var - literal(1))
        end
    end
    .followed_by do
      if length
        length_var.assign(try_match_first_node(length, input))
          .followed_by do
            length_int = conversion.to_float(length_var)
              .to_i - literal(1)

            stop_var.assign(start_var + length_int)
          end
      else
        stop_var.assign(literal(-1))
      end
    end
    .followed_by do
      substring = conversion
        .to_string(haystack_var)[range(start_var, stop_var)]

      block_given? ? substring.empty?.if_false { yield } : substring
    end
end
on_call_substring_after(input, haystack, needle) { |: after| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] haystack @param [AST::Node] needle @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1103
def on_call_substring_after(input, haystack, needle)
  haystack_var = unique_literal(:haystack)
  needle_var   = unique_literal(:needle)
  conversion   = literal(Conversion)

  before = unique_literal(:before)
  sep    = unique_literal(:sep)
  after  = unique_literal(:after)

  haystack_var.assign(try_match_first_node(haystack, input))
    .followed_by do
      needle_var.assign(try_match_first_node(needle, input))
    end
    .followed_by do
      converted = conversion.to_string(haystack_var)
        .partition(conversion.to_string(needle_var))

      mass_assign([before, sep, after], converted).followed_by do
        sep.empty?
          .if_true { sep }
          .else    { block_given? ? yield : after }
      end
    end
end
on_call_substring_before(input, haystack, needle) { |: before| ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] haystack @param [AST::Node] needle @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1074
def on_call_substring_before(input, haystack, needle)
  haystack_var = unique_literal(:haystack)
  needle_var   = unique_literal(:needle)
  conversion   = literal(Conversion)

  before = unique_literal(:before)
  sep    = unique_literal(:sep)
  after  = unique_literal(:after)

  haystack_var.assign(try_match_first_node(haystack, input))
    .followed_by do
      needle_var.assign(try_match_first_node(needle, input))
    end
    .followed_by do
      converted = conversion.to_string(haystack_var)
        .partition(conversion.to_string(needle_var))

      mass_assign([before, sep, after], converted).followed_by do
        sep.empty?
          .if_true { sep }
          .else    { block_given? ? yield : before }
      end
    end
end
on_call_sum(input, arg) { || ... } click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] arg @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1171
def on_call_sum(input, arg)
  unless return_nodeset?(arg)
    raise TypeError, 'sum() can only operate on a path, axis or predicate'
  end

  sum_var    = unique_literal(:sum)
  conversion = literal(Conversion)

  sum_var.assign(literal(0.0))
    .followed_by do
      process(arg, input) do |matched_node|
        sum_var.assign(sum_var + conversion.to_float(matched_node.text))
      end
    end
    .followed_by do
      block_given? ? sum_var.zero?.if_false { yield } : sum_var
    end
end
on_call_translate(input, source, find, replace) click to toggle source

@param [Oga::Ruby::Node] input @param [AST::Node] source @param [AST::Node] find @param [AST::Node] replace @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1195
def on_call_translate(input, source, find, replace)
  source_var   = unique_literal(:source)
  find_var     = unique_literal(:find)
  replace_var  = unique_literal(:replace)
  replaced_var = unique_literal(:replaced)
  conversion   = literal(Conversion)

  char  = unique_literal(:char)
  index = unique_literal(:index)

  source_var.assign(try_match_first_node(source, input))
    .followed_by do
      replaced_var.assign(conversion.to_string(source_var))
    end
    .followed_by do
      find_var.assign(try_match_first_node(find, input))
    end
    .followed_by do
      find_var.assign(conversion.to_string(find_var).chars.to_array)
    end
    .followed_by do
      replace_var.assign(try_match_first_node(replace, input))
    end
    .followed_by do
      replace_var.assign(conversion.to_string(replace_var).chars.to_array)
    end
    .followed_by do
      find_var.each_with_index.add_block(char, index) do
        replace_with = replace_var[index]
          .if_true { replace_var[index] }
          .else    { string('') }

        replaced_var.assign(replaced_var.gsub(char, replace_with))
      end
    end
    .followed_by do
      replaced_var
    end
end
on_call_true(*) { |: true| ... } click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 654
def on_call_true(*)
  block_given? ? yield : self.true
end
on_eq(ast, input, &block) click to toggle source

Processes the ‘=` operator.

@see [#operator]

# File lib/oga/xpath/compiler.rb, line 548
def on_eq(ast, input, &block)
  conv = literal(Conversion)

  operator(ast, input) do |left, right|
    mass_assign([left, right], conv.to_compatible_types(left, right))
      .followed_by do
        operation = left.eq(right)

        block ? operation.if_true(&block) : operation
      end
  end
end
on_float(ast, *) click to toggle source

@param [AST::Node] ast @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 627
def on_float(ast, *)
  literal(ast.children[0].to_s)
end
on_int(ast, *) click to toggle source

@param [AST::Node] ast @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 621
def on_int(ast, *)
  literal(ast.children[0].to_f.to_s)
end
on_neq(ast, input, &block) click to toggle source

Processes the ‘!=` operator.

@see [#operator]

# File lib/oga/xpath/compiler.rb, line 564
def on_neq(ast, input, &block)
  conv = literal(Conversion)

  operator(ast, input) do |left, right|
    mass_assign([left, right], conv.to_compatible_types(left, right))
      .followed_by do
        operation = left != right

        block ? operation.if_true(&block) : operation
      end
  end
end
on_pipe(ast, input, &block) click to toggle source

Processes the ‘|` operator.

@see [#operator]

# File lib/oga/xpath/compiler.rb, line 594
def on_pipe(ast, input, &block)
  left, right = *ast

  union = unique_literal(:union)

  # Expressions such as "a | b | c"
  if left.type == :pipe
    union.assign(process(left, input))
      .followed_by(process(right, input) { |node| union << node })
      .followed_by(union)
  # Expressions such as "a | b"
  else
    union.assign(literal(XML::NodeSet).new)
      .followed_by(process(left, input) { |node| union << node })
      .followed_by(process(right, input) { |node| union << node })
      .followed_by(union)
  end
end
on_predicate(ast, input, &block) click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 401
def on_predicate(ast, input, &block)
  test, predicate, following = *ast

  index_var = unique_literal(:index)

  if number?(predicate)
    method = :on_predicate_index
  elsif has_call_node?(predicate, 'last')
    method = :on_predicate_temporary
  else
    method = :on_predicate_direct
  end

  @predicate_indexes << index_var

  ast = index_var.assign(literal(1)).followed_by do
    send(method, input, test, predicate) do |matched|
      process_following_or_yield(following, matched, &block)
    end
  end

  @predicate_indexes.pop

  ast
end
on_predicate_direct(input, test, predicate) { |matched_test_node| ... } click to toggle source

Processes a predicate that doesn’t require temporary NodeSet.

@param [Oga::Ruby::Node] input @param [AST::Node] test @param [AST::Node] predicate @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 478
def on_predicate_direct(input, test, predicate)
  pred_var   = unique_literal(:pred_var)
  index_var  = predicate_index
  index_step = literal(1)
  conversion = literal(Conversion)

  process(test, input) do |matched_test_node|
    if return_nodeset?(predicate)
      pred_ast = catch_message(:predicate_matched) do
        process(predicate, matched_test_node) do
          throw_message(:predicate_matched, self.true)
        end
      end
    else
      pred_ast = process(predicate, matched_test_node)
    end

    pred_var.assign(pred_ast)
      .followed_by do
        pred_var.is_a?(Numeric).if_true do
          pred_var.assign(pred_var.to_i.eq(index_var))
        end
      end
      .followed_by do
        conversion.to_boolean(pred_var).if_true do
          yield matched_test_node
        end
      end
      .followed_by do
        index_var.assign(index_var + index_step)
      end
  end
end
on_predicate_index(input, test, predicate) { |matched_test_node| ... } click to toggle source

Processes a predicate that uses a literal index.

@param [Oga::Ruby::Node] input @param [AST::Node] test @param [AST::Node] predicate @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 518
def on_predicate_index(input, test, predicate)
  index_var  = predicate_index
  index_step = literal(1)

  index = process(predicate, input).to_i

  process(test, input) do |matched_test_node|
    index_var.eq(index)
      .if_true do
        yield matched_test_node
      end
      .followed_by do
        index_var.assign(index_var + index_step)
      end
  end
end
on_predicate_temporary(input, test, predicate) { |pred_node| ... } click to toggle source

Processes a predicate that requires a temporary NodeSet.

@param [Oga::Ruby::Node] input @param [AST::Node] test @param [AST::Node] predicate @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 433
def on_predicate_temporary(input, test, predicate)
  temp_set   = unique_literal(:temp_set)
  pred_node  = unique_literal(:pred_node)
  pred_var   = unique_literal(:pred_var)
  conversion = literal(Conversion)

  index_var  = predicate_index
  index_step = literal(1)

  @predicate_nodesets << temp_set

  ast = temp_set.assign(literal(XML::NodeSet).new)
    .followed_by do
      process(test, input) { |node| temp_set << node }
    end
    .followed_by do
      temp_set.each.add_block(pred_node) do
        pred_ast = process(predicate, pred_node)

        pred_var.assign(pred_ast)
          .followed_by do
            pred_var.is_a?(Numeric).if_true do
              pred_var.assign(pred_var.to_i.eq(index_var))
            end
          end
          .followed_by do
            conversion.to_boolean(pred_var).if_true { yield pred_node }
          end
          .followed_by do
            index_var.assign(index_var + index_step)
          end
      end
    end

  @predicate_nodesets.pop

  ast
end
on_string(ast, *) click to toggle source

@param [AST::Node] ast @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 615
def on_string(ast, *)
  string(ast.children[0])
end
on_test(ast, input) click to toggle source

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 538
def on_test(ast, input)
  condition  = element_or_attribute(input)
  name_match = match_name_and_namespace(ast, input)

  name_match ? condition.and(name_match) : condition
end
on_type_test(ast, input, &block) click to toggle source

Delegates type tests to specific handlers.

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1262
def on_type_test(ast, input, &block)
  name, following = *ast

  handler = name.gsub('-', '_')

  send(:"on_type_test_#{handler}", input) do |matched|
    process_following_or_yield(following, matched, &block)
  end
end
on_type_test_comment(input) click to toggle source

@param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1274
def on_type_test_comment(input)
  input.is_a?(XML::Comment)
end
on_type_test_node(input) click to toggle source

@param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1292
def on_type_test_node(input)
  document_or_node(input).or(input.is_a?(XML::Attribute))
end
on_type_test_processing_instruction(input) click to toggle source

@param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1286
def on_type_test_processing_instruction(input)
  input.is_a?(XML::ProcessingInstruction)
end
on_type_test_text(input) click to toggle source

@param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1280
def on_type_test_text(input)
  input.is_a?(XML::Text)
end
on_var(ast, *) click to toggle source

@param [AST::Node] ast @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 633
def on_var(ast, *)
  name = ast.children[0]

  variables_literal.and(variables_literal[string(name)])
    .or(send_message(:raise, string("Undefined XPath variable: #{name}")))
end
operator(ast, input, optimize_first = true) { |left_var, right_var| ... } click to toggle source

Generates the code for an operator.

The generated code is optimized so that expressions such as ‘a/b = c` only match the first node in both arms instead of matching all available nodes first. Because numeric operators only ever operates on the first node in a set we can simply ditch the rest, possibly speeding things up quite a bit. This only works if one of the arms is:

  • a path

  • an axis

  • a predicate

Everything else is processed the usual (and possibly slower) way.

@param [AST::Node] ast @param [Oga::Ruby::Node] input @param [TrueClass|FalseClass] optimize_first @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1475
def operator(ast, input, optimize_first = true)
  left, right = *ast

  left_var  = unique_literal(:op_left)
  right_var = unique_literal(:op_right)

  left_ast  = try_match_first_node(left, input, optimize_first)
  right_ast = try_match_first_node(right, input, optimize_first)

  left_var.assign(left_ast)
    .followed_by(right_var.assign(right_ast))
    .followed_by { yield left_var, right_var }
end
original_input_literal() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1495
def original_input_literal
  literal(:original_input)
end
predicate_index() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1576
def predicate_index
  @predicate_indexes.last
end
predicate_nodeset() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1581
def predicate_nodeset
  @predicate_nodesets.last
end
process(ast, input, &block) click to toggle source

Processes a single XPath AST node.

@param [AST::Node] ast @param [Oga::Ruby::Node] input @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 108
def process(ast, input, &block)
  send("on_#{ast.type}", ast, input, &block)
end
process_following_or_yield(following, matched) { |matched| ... } click to toggle source

@param [AST::Node] following @param [Oga::Ruby::Node] matched @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1588
def process_following_or_yield(following, matched, &block)
  if following
    process(following, matched, &block)
  else
    yield matched
  end
end
raise_message(klass, message) click to toggle source

@param [Class] klass @param [String] message @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1339
def raise_message(klass, message)
  send_message(:raise, literal(klass), string(message))
end
range(start, stop) click to toggle source

@param [Oga::Ruby::Node] start @param [Oga::Ruby::Node] stop @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1305
def range(start, stop)
  Ruby::Node.new(:range, [start, stop])
end
reset() click to toggle source

Resets the internal state.

# File lib/oga/xpath/compiler.rb, line 58
def reset
  @literal_id = 0

  @predicate_nodesets = []
  @predicate_indexes  = []
end
return_nodeset?(ast) click to toggle source

@param [AST::Node] ast @return [TrueClass|FalseClass]

# File lib/oga/xpath/compiler.rb, line 1553
def return_nodeset?(ast)
  RETURN_NODESET.include?(ast.type)
end
send_message(name, *args) click to toggle source

@param [String] name @param [Array] args @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1332
def send_message(name, *args)
  Ruby::Node.new(:send, [nil, name.to_s, *args])
end
string(value) click to toggle source

@param [#to_s] value @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1319
def string(value)
  Ruby::Node.new(:string, [value.to_s])
end
string?(ast) click to toggle source

@param [AST::Node] ast @return [TrueClass|FalseClass]

# File lib/oga/xpath/compiler.rb, line 1525
def string?(ast)
  ast.type == :string
end
symbol(value) click to toggle source

@param [String] value @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1325
def symbol(value)
  Ruby::Node.new(:symbol, [value.to_sym])
end
throw_message(name, *args) click to toggle source

@param [Symbol] name @param [Array] args @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1542
def throw_message(name, *args)
  send_message(:throw, symbol(name), *args)
end
to_int(ast) click to toggle source

@param [AST::Node] ast @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1506
def to_int(ast)
  literal(ast.children[0].to_i.to_s)
end
true() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1349
def true
  @true ||= literal(:true)
end
try_match_first_node(ast, input, optimize_first = true) click to toggle source

Tries to match the first node in a set, otherwise processes it as usual.

@see [#match_first_node]

# File lib/oga/xpath/compiler.rb, line 1431
def try_match_first_node(ast, input, optimize_first = true)
  if return_nodeset?(ast) and optimize_first
    match_first_node(ast, input)
  else
    process(ast, input)
  end
end
unique_literal(name) click to toggle source

@param [String] name @return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1311
def unique_literal(name)
  new_id = @literal_id += 1

  literal("#{name}#{new_id}")
end
variables_literal() click to toggle source

@return [Oga::Ruby::Node]

# File lib/oga/xpath/compiler.rb, line 1500
def variables_literal
  literal(:variables)
end