From 266c66569e6a59bcb58dac839b1221efc5db2703 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 8 Jul 2014 19:19:59 +0200 Subject: [PATCH] Removed internal state of XPath::Evaluator. Come to think of it it might actually be easier to implement the evaluator as an actual VM. That is, instead of directly running on the AST it runs on some flavour of bytecode. Alternatively it runs directly on the AST but behaves more like a (stack based) VM. This would most likely be easier than passing a cursor to every node processing method. --- lib/oga/xpath/evaluator.rb | 77 ++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/lib/oga/xpath/evaluator.rb b/lib/oga/xpath/evaluator.rb index b1ab513..1999ccf 100644 --- a/lib/oga/xpath/evaluator.rb +++ b/lib/oga/xpath/evaluator.rb @@ -10,42 +10,87 @@ module Oga # def initialize(document) @document = document - @cursor = @document end - def on_absolute(node) - if @cursor.is_a?(XML::Node) - @cursor = @cursor.root_node + 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 - return process_all(node.children) + node = new_node if new_node + + return node end - def on_path(node) + def process_all(nodes, cursor) + return nodes.to_a.map do |node| + process(node, cursor) + end + end + + def evaluate(string) + ast = Parser.new(string).parse + cursor = move_cursor(@document) + + return process(ast, cursor) + end + + def on_absolute(node, cursor) + if @document.is_a?(XML::Node) + cursor = move_cursor(@document.root_node) + else + cursor = move_cursor(@document) + end + + return process_all(node.children, cursor) + end + + def on_path(node, cursor) test, children = *node - current = process(test) + nodes = process(test, cursor) - if current - @cursor = current - current = process(children) + unless nodes.empty? + nodes = process_all(children, nodes) end - return current + return nodes end - def on_test(node) - nodes = [] + def on_test(node, cursor) ns, name = *node + nodes = XML::NodeSet.new - @cursor.children.each do |child| - if child.is_a?(XML::Element) and child.name == name and child.namespace == ns - nodes << child + cursor.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 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 + end end # Evaluator end # XPath end # Oga