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.
This commit is contained in:
Yorick Peterse 2014-11-12 22:59:38 +01:00
parent 24350fa457
commit 817a5e075b
5 changed files with 68 additions and 62 deletions

View File

@ -163,45 +163,58 @@ module Oga
end end
## ##
# Processes a node test and optionally a predicate. # Processes a node test.
# #
# @param [AST::Node] ast_node # @param [AST::Node] ast_node
# @param [Oga::XML::NodeSet] context # @param [Oga::XML::NodeSet] context
# @return [Oga::XML::NodeSet] # @return [Oga::XML::NodeSet]
# #
def on_test(ast_node, context) def on_test(ast_node, context)
nodes = XML::NodeSet.new nodes = XML::NodeSet.new
predicate = ast_node.children[2]
xpath_index = 1
context.each do |xml_node| 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 return nodes
retval = with_node_set(context) do end
process(predicate, XML::NodeSet.new([xml_node]))
##
# 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 end
# Numeric values are used as node set indexes. final_nodes << xml_node
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
end end
xpath_index += 1 xpath_index += 1
end end
return nodes return final_nodes
end end
## ##

View File

@ -40,9 +40,8 @@ rule
; ;
expression_members expression_members
: node_test_as_axis : expression_with_predicate
| operator | operator
| axis
| string | string
| number | number
| call | call
@ -51,9 +50,14 @@ rule
| variable | variable
; ;
path_member expression_with_predicate
: node_test_as_axis : node_test_as_axis
| node_test_as_axis predicate { s(:predicate, val[0], val[1]) }
| axis | axis
| axis predicate { s(:predicate, val[0], val[1]) }
path_member
: expression_with_predicate
| call | call
; ;
@ -83,7 +87,7 @@ rule
node_test node_test
: node_name { s(:test, *val[0]) } : 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] } | type_test { val[0] }
; ;

View File

@ -43,17 +43,12 @@ describe Oga::XPath::Parser do
parse_xpath('div[@class="foo"]/bar()').should == s( parse_xpath('div[@class="foo"]/bar()').should == s(
:path, :path,
s( s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'div')),
s( s(
:test, :eq,
nil, s(:axis, 'attribute', s(:test, nil, 'class')),
'div', s(:string, 'foo')
s(
:eq,
s(:axis, 'attribute', s(:test, nil, 'class')),
s(:string, 'foo')
)
) )
), ),
s(:call, 'bar') s(:call, 'bar')
@ -64,14 +59,14 @@ describe Oga::XPath::Parser do
parse_xpath('A[@x]/B[@x]/bar()').should == s( parse_xpath('A[@x]/B[@x]/bar()').should == s(
:path, :path,
s( s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'A', s(:axis, 'attribute', s(:test, nil, 'x'))) s(:axis, 'attribute', s(:test, nil, 'x'))
), ),
s( s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'B')),
s(:test, nil, 'B', s(:axis, 'attribute', s(:test, nil, 'x'))) s(:axis, 'attribute', s(:test, nil, 'x'))
), ),
s(:call, 'bar') s(:call, 'bar')
) )

View File

@ -4,30 +4,24 @@ describe Oga::XPath::Parser do
context 'predicates' do context 'predicates' do
example 'parse a single predicate' do example 'parse a single predicate' do
parse_xpath('foo[@class="bar"]').should == s( parse_xpath('foo[@class="bar"]').should == s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'foo')),
s( s(
:test, :eq,
nil, s(:axis, 'attribute', s(:test, nil, 'class')),
'foo', s(:string, 'bar')
s(:eq, s(:axis, 'attribute', s(:test, nil, 'class')), s(:string, 'bar'))
) )
) )
end end
example 'parse a predicate using the or operator' do example 'parse a predicate using the or operator' do
parse_xpath('foo[@x="bar" or @x="baz"]').should == s( parse_xpath('foo[@x="bar" or @x="baz"]').should == s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'foo')),
s( s(
:test, :or,
nil, s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'bar')),
'foo', s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'baz')),
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')),
)
) )
) )
end end

View File

@ -8,9 +8,9 @@ describe Oga::XPath::Parser do
example 'parse a variable reference in a predicate' do example 'parse a variable reference in a predicate' do
parse_xpath('foo[$bar]').should == s( parse_xpath('foo[$bar]').should == s(
:axis, :predicate,
'child', s(:axis, 'child', s(:test, nil, 'foo')),
s(:test, nil, 'foo', s(:var, 'bar')) s(:var, 'bar')
) )
end end
end end