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:
parent
9c661e1e60
commit
a398af19fe
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue