From 817a5e075baf2ef36636105d8aeec5d304269db9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Nov 2014 22:59:38 +0100 Subject: [PATCH] Wrap predicate AST nodes _around_ other nodes. This means that "foo[1]" uses this AST: (predicate (test nil "foo") (int 1)) Instead of this AST: (test nil "foo" (int 1)) This makes it easier for the XPath evaluator to process predicates correctly. --- lib/oga/xpath/evaluator.rb | 59 +++++++++++++++--------- lib/oga/xpath/parser.y | 12 +++-- spec/oga/xpath/parser/calls_spec.rb | 27 +++++------ spec/oga/xpath/parser/predicates_spec.rb | 26 ++++------- spec/oga/xpath/parser/variable_spec.rb | 6 +-- 5 files changed, 68 insertions(+), 62 deletions(-) diff --git a/lib/oga/xpath/evaluator.rb b/lib/oga/xpath/evaluator.rb index b0402ce..b782a7d 100644 --- a/lib/oga/xpath/evaluator.rb +++ b/lib/oga/xpath/evaluator.rb @@ -163,45 +163,58 @@ module Oga end ## - # Processes a node test and optionally a predicate. + # Processes a node test. # # @param [AST::Node] ast_node # @param [Oga::XML::NodeSet] context # @return [Oga::XML::NodeSet] # def on_test(ast_node, context) - nodes = XML::NodeSet.new - predicate = ast_node.children[2] - xpath_index = 1 + nodes = XML::NodeSet.new context.each do |xml_node| - next unless node_matches?(xml_node, ast_node) + nodes << xml_node if node_matches?(xml_node, ast_node) + end - if predicate - retval = with_node_set(context) do - process(predicate, XML::NodeSet.new([xml_node])) + return nodes + end + + ## + # Processes a predicate. + # + # @param [AST::Node] ast_node + # @param [Oga::XML::NodeSet] context + # @return [Oga::XML::NodeSet] + # + def on_predicate(ast_node, context) + test, predicate = *ast_node + + initial_nodes = process(test, context) + final_nodes = XML::NodeSet.new + xpath_index = 1 + + initial_nodes.each do |xml_node| + retval = with_node_set(initial_nodes) do + process(predicate, XML::NodeSet.new([xml_node])) + end + + # Numeric values are used as node set indexes. + if retval.is_a?(Numeric) + final_nodes << xml_node if retval.to_i == xpath_index + + # Node sets, strings, booleans, etc + elsif retval + if retval.respond_to?(:empty?) and retval.empty? + next end - # Numeric values are used as node set indexes. - if retval.is_a?(Numeric) - nodes << xml_node if retval.to_i == xpath_index - - # Node sets, strings, booleans, etc - elsif retval - if retval.respond_to?(:empty?) and retval.empty? - next - end - - nodes << xml_node - end - else - nodes << xml_node + final_nodes << xml_node end xpath_index += 1 end - return nodes + return final_nodes end ## diff --git a/lib/oga/xpath/parser.y b/lib/oga/xpath/parser.y index c210251..0a5a9cc 100644 --- a/lib/oga/xpath/parser.y +++ b/lib/oga/xpath/parser.y @@ -40,9 +40,8 @@ rule ; expression_members - : node_test_as_axis + : expression_with_predicate | operator - | axis | string | number | call @@ -51,9 +50,14 @@ rule | variable ; - path_member + expression_with_predicate : node_test_as_axis + | node_test_as_axis predicate { s(:predicate, val[0], val[1]) } | axis + | axis predicate { s(:predicate, val[0], val[1]) } + + path_member + : expression_with_predicate | call ; @@ -83,7 +87,7 @@ rule node_test : node_name { s(:test, *val[0]) } - | node_name predicate { s(:test, *val[0], val[1]) } + #| node_name predicate { s(:predicate, s(:test, *val[0]), val[1]) } | type_test { val[0] } ; diff --git a/spec/oga/xpath/parser/calls_spec.rb b/spec/oga/xpath/parser/calls_spec.rb index 9739d37..de16a4e 100644 --- a/spec/oga/xpath/parser/calls_spec.rb +++ b/spec/oga/xpath/parser/calls_spec.rb @@ -43,17 +43,12 @@ describe Oga::XPath::Parser do parse_xpath('div[@class="foo"]/bar()').should == s( :path, s( - :axis, - 'child', + :predicate, + s(:axis, 'child', s(:test, nil, 'div')), s( - :test, - nil, - 'div', - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'foo') - ) + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'foo') ) ), s(:call, 'bar') @@ -64,14 +59,14 @@ describe Oga::XPath::Parser do parse_xpath('A[@x]/B[@x]/bar()').should == s( :path, s( - :axis, - 'child', - s(:test, nil, 'A', s(:axis, 'attribute', s(:test, nil, 'x'))) + :predicate, + s(:axis, 'child', s(:test, nil, 'A')), + s(:axis, 'attribute', s(:test, nil, 'x')) ), s( - :axis, - 'child', - s(:test, nil, 'B', s(:axis, 'attribute', s(:test, nil, 'x'))) + :predicate, + s(:axis, 'child', s(:test, nil, 'B')), + s(:axis, 'attribute', s(:test, nil, 'x')) ), s(:call, 'bar') ) diff --git a/spec/oga/xpath/parser/predicates_spec.rb b/spec/oga/xpath/parser/predicates_spec.rb index f6caa0a..2b15e18 100644 --- a/spec/oga/xpath/parser/predicates_spec.rb +++ b/spec/oga/xpath/parser/predicates_spec.rb @@ -4,30 +4,24 @@ describe Oga::XPath::Parser do context 'predicates' do example 'parse a single predicate' do parse_xpath('foo[@class="bar"]').should == s( - :axis, - 'child', + :predicate, + s(:axis, 'child', s(:test, nil, 'foo')), s( - :test, - nil, - 'foo', - s(:eq, s(:axis, 'attribute', s(:test, nil, 'class')), s(:string, 'bar')) + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'bar') ) ) end example 'parse a predicate using the or operator' do parse_xpath('foo[@x="bar" or @x="baz"]').should == s( - :axis, - 'child', + :predicate, + s(:axis, 'child', s(:test, nil, 'foo')), s( - :test, - nil, - 'foo', - s( - :or, - s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'bar')), - s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'baz')), - ) + :or, + s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'bar')), + s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'baz')), ) ) end diff --git a/spec/oga/xpath/parser/variable_spec.rb b/spec/oga/xpath/parser/variable_spec.rb index 6935d35..dc7ba0d 100644 --- a/spec/oga/xpath/parser/variable_spec.rb +++ b/spec/oga/xpath/parser/variable_spec.rb @@ -8,9 +8,9 @@ describe Oga::XPath::Parser do example 'parse a variable reference in a predicate' do parse_xpath('foo[$bar]').should == s( - :axis, - 'child', - s(:test, nil, 'foo', s(:var, 'bar')) + :predicate, + s(:axis, 'child', s(:test, nil, 'foo')), + s(:var, 'bar') ) end end