Parse bare XPath node tests as child axes.

When parsing a bare node test such as "A" this is now parsed as following:

    (axis "child" (test nil "A"))

Instead of this:

    (test nil "A")

According to the XPath specification both are identical and this simplifies some
of the code in the XPath evaluator.
This commit is contained in:
Yorick Peterse 2014-07-28 00:34:26 +02:00
parent 1916799fef
commit 23de57a3a0
8 changed files with 179 additions and 88 deletions

View File

@ -34,7 +34,7 @@ rule
; ;
expression expression
: node_test : node_test_as_axis
| operator | operator
| axis | axis
| string | string
@ -45,7 +45,7 @@ rule
; ;
path_member path_member
: node_test : node_test_as_axis
| axis | axis
| call | call
; ;
@ -66,6 +66,13 @@ rule
| T_SLASH path_member { s(:absolute_path, val[1]) } | T_SLASH path_member { s(:absolute_path, val[1]) }
; ;
# Whenever a bare test is used (e.g. just "A") this actually means "child::A".
# Handling this on lexer level requires a look-behind/look-ahead while solving
# this on evaluator level produces inconsistent/confusing code.
node_test_as_axis
: node_test { s(:axis, 'child', val[0]) }
;
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(:test, *val[0], val[1]) }

View File

@ -106,7 +106,7 @@ describe Oga::XPath::Parser do
parse_xpath('//A').should == s( parse_xpath('//A').should == s(
:absolute_path, :absolute_path,
s(:axis, 'descendant-or-self', s(:call, 'node')), s(:axis, 'descendant-or-self', s(:call, 'node')),
s(:test, nil, 'A') s(:axis, 'child', s(:test, nil, 'A'))
) )
end end

View File

@ -10,7 +10,7 @@ describe Oga::XPath::Parser do
parse_xpath('count(/foo)').should == s( parse_xpath('count(/foo)').should == s(
:call, :call,
'count', 'count',
s(:absolute_path, s(:test, nil, 'foo')) s(:absolute_path, s(:axis, 'child', s(:test, nil, 'foo')))
) )
end end
@ -18,7 +18,7 @@ describe Oga::XPath::Parser do
parse_xpath('count(/foo, "bar")').should == s( parse_xpath('count(/foo, "bar")').should == s(
:call, :call,
'count', 'count',
s(:absolute_path, s(:test, nil, 'foo')), s(:absolute_path, s(:axis, 'child', s(:test, nil, 'foo'))),
s(:string, 'bar') s(:string, 'bar')
) )
end end
@ -26,7 +26,7 @@ describe Oga::XPath::Parser do
example 'parse a relative path with a function call' do example 'parse a relative path with a function call' do
parse_xpath('foo/bar()').should == s( parse_xpath('foo/bar()').should == s(
:path, :path,
s(:test, nil, 'foo'), s(:axis, 'child', s(:test, nil, 'foo')),
s(:call, 'bar') s(:call, 'bar')
) )
end end
@ -34,13 +34,17 @@ describe Oga::XPath::Parser do
example 'parse an absolute path with a function call' do example 'parse an absolute path with a function call' do
parse_xpath('/foo/bar()').should == s( parse_xpath('/foo/bar()').should == s(
:absolute_path, :absolute_path,
s(:test, nil, 'foo'), s(:call, 'bar') s(:axis, 'child', s(:test, nil, 'foo')),
s(:call, 'bar')
) )
end end
example 'parse a predicate followed by a function call' do example 'parse a predicate followed by a function call' do
parse_xpath('div[@class="foo"]/bar()').should == s( parse_xpath('div[@class="foo"]/bar()').should == s(
:path, :path,
s(
:axis,
'child',
s( s(
:test, :test,
nil, nil,
@ -50,6 +54,7 @@ describe Oga::XPath::Parser do
s(:axis, 'attribute', s(:test, nil, 'class')), s(:axis, 'attribute', s(:test, nil, 'class')),
s(:string, 'foo') s(:string, 'foo')
) )
)
), ),
s(:call, 'bar') s(:call, 'bar')
) )
@ -58,8 +63,16 @@ describe Oga::XPath::Parser do
example 'parse two predicates followed by a function call' do example 'parse two predicates followed by a function call' do
parse_xpath('A[@x]/B[@x]/bar()').should == s( parse_xpath('A[@x]/B[@x]/bar()').should == s(
:path, :path,
s(:test, nil, 'A', s(:axis, 'attribute', s(:test, nil, 'x'))), s(
s(:test, nil, 'B', s(:axis, 'attribute', s(:test, nil, 'x'))), :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')))
),
s(:call, 'bar') s(:call, 'bar')
) )
end end

View File

@ -5,104 +5,156 @@ describe Oga::XPath::Parser do
example 'parse "A or B or C"' do example 'parse "A or B or C"' do
parse_xpath('A or B or C').should == s( parse_xpath('A or B or C').should == s(
:or, :or,
s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :or,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A and B and C"' do example 'parse "A and B and C"' do
parse_xpath('A and B and C').should == s( parse_xpath('A and B and C').should == s(
:and, :and,
s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :and,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A = B = C"' do example 'parse "A = B = C"' do
parse_xpath('A = B = C').should == s( parse_xpath('A = B = C').should == s(
:eq, :eq,
s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :eq,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A != B != C"' do example 'parse "A != B != C"' do
parse_xpath('A != B != C').should == s( parse_xpath('A != B != C').should == s(
:neq, :neq,
s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :neq,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A <= B <= C"' do example 'parse "A <= B <= C"' do
parse_xpath('A <= B <= C').should == s( parse_xpath('A <= B <= C').should == s(
:lte, :lte,
s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :lte,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A < B < C"' do example 'parse "A < B < C"' do
parse_xpath('A < B < C').should == s( parse_xpath('A < B < C').should == s(
:lt, :lt,
s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :lt,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A >= B >= C"' do example 'parse "A >= B >= C"' do
parse_xpath('A >= B >= C').should == s( parse_xpath('A >= B >= C').should == s(
:gte, :gte,
s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :gte,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A > B > C"' do example 'parse "A > B > C"' do
parse_xpath('A > B > C').should == s( parse_xpath('A > B > C').should == s(
:gt, :gt,
s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :gt,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
example 'parse "A or B and C"' do example 'parse "A or B and C"' do
parse_xpath('A or B and C').should == s( parse_xpath('A or B and C').should == s(
:or, :or,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:and, s(:test, nil, 'B'), s(:test, nil, 'C')) s(
:and,
s(:axis, 'child', s(:test, nil, 'B')),
s(:axis, 'child', s(:test, nil, 'C'))
)
) )
end end
example 'parse "A and B = C"' do example 'parse "A and B = C"' do
parse_xpath('A and B = C').should == s( parse_xpath('A and B = C').should == s(
:and, :and,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:eq, s(:test, nil, 'B'), s(:test, nil, 'C')) s(
:eq,
s(:axis, 'child', s(:test, nil, 'B')),
s(:axis, 'child', s(:test, nil, 'C'))
)
) )
end end
example 'parse "A = B < C"' do example 'parse "A = B < C"' do
parse_xpath('A = B < C').should == s( parse_xpath('A = B < C').should == s(
:eq, :eq,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:lt, s(:test, nil, 'B'), s(:test, nil, 'C')) s(
:lt,
s(:axis, 'child', s(:test, nil, 'B')),
s(:axis, 'child', s(:test, nil, 'C'))
)
) )
end end
example 'parse "A < B | C"' do example 'parse "A < B | C"' do
parse_xpath('A < B | C').should == s( parse_xpath('A < B | C').should == s(
:lt, :lt,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:pipe, s(:test, nil, 'B'), s(:test, nil, 'C')) s(
:pipe,
s(:axis, 'child', s(:test, nil, 'B')),
s(:axis, 'child', s(:test, nil, 'C'))
)
) )
end end
example 'parse "A > B or C"' do example 'parse "A > B or C"' do
parse_xpath('A > B or C').should == s( parse_xpath('A > B or C').should == s(
:or, :or,
s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:test, nil, 'C') :gt,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(:axis, 'child', s(:test, nil, 'C'))
) )
end end
end end

View File

@ -5,120 +5,128 @@ describe Oga::XPath::Parser do
example 'parse the pipe operator' do example 'parse the pipe operator' do
parse_xpath('A | B').should == s( parse_xpath('A | B').should == s(
:pipe, :pipe,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the pipe operator using two paths' do example 'parse the pipe operator using two paths' do
parse_xpath('A/B | C/D').should == s( parse_xpath('A/B | C/D').should == s(
:pipe, :pipe,
s(:path, s(:test, nil, 'A'), s(:test, nil, 'B')), s(
s(:path, s(:test, nil, 'C'), s(:test, nil, 'D')) :path,
s(:axis, 'child', s(:test, nil, 'A')),
s(:axis, 'child', s(:test, nil, 'B'))
),
s(
:path,
s(:axis, 'child', s(:test, nil, 'C')),
s(:axis, 'child', s(:test, nil, 'D'))
)
) )
end end
example 'parse the and operator' do example 'parse the and operator' do
parse_xpath('A and B').should == s( parse_xpath('A and B').should == s(
:and, :and,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the or operator' do example 'parse the or operator' do
parse_xpath('A or B').should == s( parse_xpath('A or B').should == s(
:or, :or,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the plus operator' do example 'parse the plus operator' do
parse_xpath('A + B').should == s( parse_xpath('A + B').should == s(
:add, :add,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the div operator' do example 'parse the div operator' do
parse_xpath('A div B').should == s( parse_xpath('A div B').should == s(
:div, :div,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the mod operator' do example 'parse the mod operator' do
parse_xpath('A mod B').should == s( parse_xpath('A mod B').should == s(
:mod, :mod,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the equals operator' do example 'parse the equals operator' do
parse_xpath('A = B').should == s( parse_xpath('A = B').should == s(
:eq, :eq,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the not-equals operator' do example 'parse the not-equals operator' do
parse_xpath('A != B').should == s( parse_xpath('A != B').should == s(
:neq, :neq,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the lower-than operator' do example 'parse the lower-than operator' do
parse_xpath('A < B').should == s( parse_xpath('A < B').should == s(
:lt, :lt,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the greater-than operator' do example 'parse the greater-than operator' do
parse_xpath('A > B').should == s( parse_xpath('A > B').should == s(
:gt, :gt,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the lower-or-equal operator' do example 'parse the lower-or-equal operator' do
parse_xpath('A <= B').should == s( parse_xpath('A <= B').should == s(
:lte, :lte,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the greater-or-equal operator' do example 'parse the greater-or-equal operator' do
parse_xpath('A >= B').should == s( parse_xpath('A >= B').should == s(
:gte, :gte,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the mul operator' do example 'parse the mul operator' do
parse_xpath('A * B').should == s( parse_xpath('A * B').should == s(
:mul, :mul,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
example 'parse the subtraction operator' do example 'parse the subtraction operator' do
parse_xpath('A - B').should == s( parse_xpath('A - B').should == s(
:sub, :sub,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
end end

View File

@ -3,18 +3,21 @@ require 'spec_helper'
describe Oga::XPath::Parser do describe Oga::XPath::Parser do
context 'paths' do context 'paths' do
example 'parse an absolute path' do example 'parse an absolute path' do
parse_xpath('/A').should == s(:absolute_path, s(:test, nil, 'A')) parse_xpath('/A').should == s(
:absolute_path,
s(:axis, 'child', s(:test, nil, 'A'))
)
end end
example 'parse a relative path' do example 'parse a relative path' do
parse_xpath('A').should == s(:test, nil, 'A') parse_xpath('A').should == s(:axis, 'child', s(:test, nil, 'A'))
end end
example 'parse an expression using two paths' do example 'parse an expression using two paths' do
parse_xpath('/A/B').should == s( parse_xpath('/A/B').should == s(
:absolute_path, :absolute_path,
s(:test, nil, 'A'), s(:axis, 'child', s(:test, nil, 'A')),
s(:test, nil, 'B') s(:axis, 'child', s(:test, nil, 'B'))
) )
end end
end end

View File

@ -4,15 +4,22 @@ 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,
'child',
s(
:test, :test,
nil, nil,
'foo', 'foo',
s(:eq, s(:axis, 'attribute', s(:test, nil, 'class')), 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,
'child',
s(
:test, :test,
nil, nil,
'foo', 'foo',
@ -22,6 +29,7 @@ describe Oga::XPath::Parser do
s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'baz')), s(:eq, s(:axis, 'attribute', s(:test, nil, 'x')), s(:string, 'baz')),
) )
) )
)
end end
end end
end end

View File

@ -3,15 +3,15 @@ require 'spec_helper'
describe Oga::XPath::Parser do describe Oga::XPath::Parser do
context 'wildcards' do context 'wildcards' do
example 'parse a wildcard name test' do example 'parse a wildcard name test' do
parse_xpath('*').should == s(:test, nil, '*') parse_xpath('*').should == s(:axis, 'child', s(:test, nil, '*'))
end end
example 'parse a wildcard namespace test' do example 'parse a wildcard namespace test' do
parse_xpath('*:A').should == s(:test, '*', 'A') parse_xpath('*:A').should == s(:axis, 'child', s(:test, '*', 'A'))
end end
example 'parse a wildcard namespace and name test' do example 'parse a wildcard namespace and name test' do
parse_xpath('*:*').should == s(:test, '*', '*') parse_xpath('*:*').should == s(:axis, 'child', s(:test, '*', '*'))
end end
end end
end end