Use a stack based XPath evaluator.

This evaluator is capable of processing very, very basic XPath expressions. It
does not yet know anything about axes, functions, operators, etc.
This commit is contained in:
Yorick Peterse 2014-07-08 23:57:21 +02:00
parent 9c661e1e60
commit a398af19fe
1 changed files with 45 additions and 62 deletions

View File

@ -4,92 +4,75 @@ module Oga
# The Evaluator class is used to evaluate an XPath expression in the
# context of a given document.
#
class Evaluator < AST::Processor
class Evaluator
##
# @param [Oga::XML::Document|Oga::XML::Node] document
#
def initialize(document)
@document = document
reset
end
def process(node, cursor)
return if node.nil?
node = node.to_ast
on_handler = :"on_#{node.type}"
if respond_to?(on_handler)
new_node = send(on_handler, node, cursor)
else
new_node = handler_missing(node)
end
node = new_node if new_node
return node
end
def process_all(nodes, cursor)
return nodes.to_a.map do |node|
process(node, cursor)
end
def reset
@context = @document.children
@stack = XML::NodeSet.new
end
def evaluate(string)
ast = Parser.new(string).parse
cursor = move_cursor(@document)
ast = Parser.new(string).parse
return process(ast, cursor)
end
process(ast)
def on_absolute(node, cursor)
if @document.is_a?(XML::Node)
cursor = move_cursor(@document.root_node)
else
cursor = move_cursor(@document)
end
nodes = @stack
return process_all(node.children, cursor)
end
def on_path(node, cursor)
test, children = *node
nodes = process(test, cursor)
unless nodes.empty?
nodes = process_all(children, nodes)
end
reset
return nodes
end
def on_test(node, cursor)
ns, name = *node
nodes = XML::NodeSet.new
def process(node)
handler = "on_#{node.type}"
cursor.each do |xml_node|
if respond_to?(handler)
send(handler, node)
end
end
def on_path(node)
test, children = *node
process(test)
if children and !@stack.empty?
swap_context
process(children)
end
end
def on_test(node)
ns, name = *node
@context.each do |xml_node|
next unless xml_node.is_a?(XML::Element)
if xml_node.name == name and xml_node.namespace == ns
nodes << xml_node
@stack << xml_node
end
end
end
def swap_context
@context = XML::NodeSet.new
@stack.each do |xml_node|
xml_node.children.each do |child|
@context << child
end
end
return nodes
end
def move_cursor(location)
if location.is_a?(XML::Document)
return location.children
elsif location.is_a?(XML::Node)
return XML::NodeSet.new([location])
else
return location
end
@stack = XML::NodeSet.new
end
end # Evaluator
end # XPath