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 # The Evaluator class is used to evaluate an XPath expression in the
# context of a given document. # context of a given document.
# #
class Evaluator < AST::Processor class Evaluator
## ##
# @param [Oga::XML::Document|Oga::XML::Node] document # @param [Oga::XML::Document|Oga::XML::Node] document
# #
def initialize(document) def initialize(document)
@document = document @document = document
reset
end end
def process(node, cursor) def reset
return if node.nil? @context = @document.children
@stack = XML::NodeSet.new
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
end end
def evaluate(string) def evaluate(string)
ast = Parser.new(string).parse ast = Parser.new(string).parse
cursor = move_cursor(@document)
return process(ast, cursor) process(ast)
end
def on_absolute(node, cursor) nodes = @stack
if @document.is_a?(XML::Node)
cursor = move_cursor(@document.root_node)
else
cursor = move_cursor(@document)
end
return process_all(node.children, cursor) reset
end
def on_path(node, cursor)
test, children = *node
nodes = process(test, cursor)
unless nodes.empty?
nodes = process_all(children, nodes)
end
return nodes return nodes
end end
def on_test(node, cursor) def process(node)
ns, name = *node handler = "on_#{node.type}"
nodes = XML::NodeSet.new
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) next unless xml_node.is_a?(XML::Element)
if xml_node.name == name and xml_node.namespace == ns 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
end end
return nodes @stack = XML::NodeSet.new
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
end end
end # Evaluator end # Evaluator
end # XPath end # XPath