Replace (path) nodes with nested nodes
This changes the XPath AST so that every segment in a path (e.g. foo/bar) is parsed as a child node of the node that precedes it. For example, take the following expression: foo/bar This used to be parsed into the following AST: (path (axis "child" (test nil "foo")) (axis "child" (test nil "bar"))) This is now parsed into the following AST: (axis "child" (test nil "foo") (axis "child" (test nil "bar"))) This new AST is much easier to deal with in the XPath::Compiler class, especially when trying to ensure that each segment operates on the correct input. This commit also fixes parsing of type tests with predicates, such as: comment()[10] This used to throw a parser error.
This commit is contained in:
parent
866044f94f
commit
365a9e9fa9
|
@ -97,7 +97,7 @@ union_expr_follow
|
||||||
;
|
;
|
||||||
|
|
||||||
expression_member
|
expression_member
|
||||||
= relative_path
|
= path_step_or_axis
|
||||||
| absolute_path
|
| absolute_path
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
|
@ -105,22 +105,16 @@ expression_member
|
||||||
| T_LPAREN expression T_RPAREN { val[1] }
|
| T_LPAREN expression T_RPAREN { val[1] }
|
||||||
;
|
;
|
||||||
|
|
||||||
# A, A/B, etc
|
|
||||||
relative_path
|
|
||||||
= path_steps { val[0].length > 1 ? s(:path, *val[0]) : val[0][0] }
|
|
||||||
;
|
|
||||||
|
|
||||||
path_steps
|
|
||||||
= path_step_or_axis path_steps_follow* { [val[0], *val[1]] }
|
|
||||||
;
|
|
||||||
|
|
||||||
path_steps_follow
|
|
||||||
= T_SLASH path_step_or_axis { val[1] }
|
|
||||||
;
|
|
||||||
|
|
||||||
# /A, /A/B, etc
|
# /A, /A/B, etc
|
||||||
absolute_path
|
absolute_path
|
||||||
= T_SLASH path_steps? { s(:absolute_path, *val[1]) }
|
= T_SLASH path_step_or_axis?
|
||||||
|
{
|
||||||
|
if val[1]
|
||||||
|
s(:absolute_path, val[1])
|
||||||
|
else
|
||||||
|
s(:absolute_path)
|
||||||
|
end
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
path_step_or_axis
|
path_step_or_axis
|
||||||
|
@ -135,6 +129,7 @@ path_step
|
||||||
type = val[1][0]
|
type = val[1][0]
|
||||||
args = val[1][1]
|
args = val[1][1]
|
||||||
pred = val[1][2]
|
pred = val[1][2]
|
||||||
|
more = val[1][3]
|
||||||
|
|
||||||
if type.equal?(:test)
|
if type.equal?(:test)
|
||||||
# Whenever a bare test is used (e.g. just "A") this actually means
|
# Whenever a bare test is used (e.g. just "A") this actually means
|
||||||
|
@ -152,15 +147,38 @@ path_step
|
||||||
node = s(:predicate, node, pred)
|
node = s(:predicate, node, pred)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if more
|
||||||
|
node = node.updated(nil, node.children + [more])
|
||||||
|
end
|
||||||
|
|
||||||
|
node
|
||||||
|
}
|
||||||
|
| type_test predicate? path_step_more?
|
||||||
|
{
|
||||||
|
pred = val[1]
|
||||||
|
more = val[2]
|
||||||
|
node = s(:axis, 'child', val[0])
|
||||||
|
|
||||||
|
if pred
|
||||||
|
node = s(:predicate, node, pred)
|
||||||
|
end
|
||||||
|
|
||||||
|
if more
|
||||||
|
node = node.updated(nil, node.children + [more])
|
||||||
|
end
|
||||||
|
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
| type_test { s(:axis, 'child', val[0]) }
|
|
||||||
;
|
;
|
||||||
|
|
||||||
path_step_follow
|
path_step_follow
|
||||||
= T_LPAREN call_args T_RPAREN { [:call, val[1]] }
|
= T_LPAREN call_args T_RPAREN { [:call, val[1]] }
|
||||||
| T_COLON T_IDENT predicate? { [:test, val[1], val[2]] }
|
| T_COLON T_IDENT predicate? path_step_more? { [:test, val[1], val[2], val[3]] }
|
||||||
| predicate? { [:test, nil, val[0]] }
|
| predicate? path_step_more? { [:test, nil, val[0], val[1]] }
|
||||||
|
;
|
||||||
|
|
||||||
|
path_step_more
|
||||||
|
= T_SLASH path_step_or_axis { val[1] }
|
||||||
;
|
;
|
||||||
|
|
||||||
predicate
|
predicate
|
||||||
|
@ -194,14 +212,19 @@ call_args_follow
|
||||||
|
|
||||||
# child::foo, descendant-or-self::foo, etc
|
# child::foo, descendant-or-self::foo, etc
|
||||||
axis
|
axis
|
||||||
= T_AXIS axis_value predicate?
|
= T_AXIS axis_value predicate? path_step_more?
|
||||||
{
|
{
|
||||||
ret = s(:axis, val[0], val[1])
|
ret = s(:axis, val[0], val[1])
|
||||||
|
more = val[3]
|
||||||
|
|
||||||
if val[2]
|
if val[2]
|
||||||
ret = s(:predicate, ret, val[2])
|
ret = s(:predicate, ret, val[2])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if more
|
||||||
|
ret = ret.updated(nil, ret.children + [more])
|
||||||
|
end
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
|
@ -112,8 +112,12 @@ describe Oga::XPath::Parser do
|
||||||
it 'parses the // axis' do
|
it 'parses the // axis' do
|
||||||
parse_xpath('//A').should == s(
|
parse_xpath('//A').should == s(
|
||||||
:absolute_path,
|
:absolute_path,
|
||||||
s(:axis, 'descendant-or-self', s(:type_test, 'node')),
|
s(
|
||||||
s(:axis, 'child', s(:test, nil, 'A'))
|
:axis,
|
||||||
|
'descendant-or-self',
|
||||||
|
s(:type_test, 'node'),
|
||||||
|
s(:axis, 'child', s(:test, nil, 'A'))
|
||||||
|
),
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -24,32 +24,25 @@ describe Oga::XPath::Parser do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses a relative path with a function call' do
|
it 'parses a relative path with a function call' do
|
||||||
parse_xpath('foo/bar()').should == s(
|
parse_xpath('foo/bar()').should ==
|
||||||
:path,
|
s(:axis, 'child', s(:test, nil, 'foo'), s(:call, 'bar'))
|
||||||
s(:axis, 'child', s(:test, nil, 'foo')),
|
|
||||||
s(:call, 'bar')
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses an absolute path with a function call' do
|
it 'parses 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(:axis, 'child', s(:test, nil, 'foo')),
|
s(:axis, 'child', s(:test, nil, 'foo'), s(:call, 'bar'))
|
||||||
s(:call, 'bar')
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses a predicate followed by a function call' do
|
it 'parses 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,
|
:predicate,
|
||||||
|
s(:axis, 'child', s(:test, nil, 'div')),
|
||||||
s(
|
s(
|
||||||
:predicate,
|
:eq,
|
||||||
s(:axis, 'child', s(:test, nil, 'div')),
|
s(:axis, 'attribute', s(:test, nil, 'class')),
|
||||||
s(
|
s(:string, 'foo')
|
||||||
:eq,
|
|
||||||
s(:axis, 'attribute', s(:test, nil, 'class')),
|
|
||||||
s(:string, 'foo')
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
s(:call, 'bar')
|
s(:call, 'bar')
|
||||||
)
|
)
|
||||||
|
@ -57,18 +50,15 @@ describe Oga::XPath::Parser do
|
||||||
|
|
||||||
it 'parses two predicates followed by a function call' do
|
it 'parses 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,
|
:predicate,
|
||||||
s(
|
s(:axis, 'child', s(:test, nil, 'A')),
|
||||||
:predicate,
|
s(:axis, 'attribute', s(:test, nil, 'x')),
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
|
||||||
s(:axis, 'attribute', s(:test, nil, 'x'))
|
|
||||||
),
|
|
||||||
s(
|
s(
|
||||||
:predicate,
|
:predicate,
|
||||||
s(:axis, 'child', s(:test, nil, 'B')),
|
s(:axis, 'child', s(:test, nil, 'B')),
|
||||||
s(:axis, 'attribute', s(:test, nil, 'x'))
|
s(:axis, 'attribute', s(:test, nil, 'x')),
|
||||||
),
|
s(:call, 'bar')
|
||||||
s(:call, 'bar')
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
describe Oga::XPath::Parser do
|
|
||||||
describe 'node types' do
|
|
||||||
it 'parses the "node" type' do
|
|
||||||
parse_xpath('node()').should == s(:axis, 'child', s(:type_test, 'node'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'parses the "comment" type' do
|
|
||||||
parse_xpath('comment()')
|
|
||||||
.should == s(:axis, 'child', s(:type_test, 'comment'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'parses the "text" type' do
|
|
||||||
parse_xpath('text()').should == s(:axis, 'child', s(:type_test, 'text'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'parses the "processing-instruction" type' do
|
|
||||||
parse_xpath('processing-instruction()')
|
|
||||||
.should == s(:axis, 'child', s(:type_test, 'processing-instruction'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -14,13 +14,15 @@ describe Oga::XPath::Parser do
|
||||||
parse_xpath('A/B | C/D').should == s(
|
parse_xpath('A/B | C/D').should == s(
|
||||||
:pipe,
|
:pipe,
|
||||||
s(
|
s(
|
||||||
:path,
|
:axis,
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
'child',
|
||||||
|
s(:test, nil, 'A'),
|
||||||
s(:axis, 'child', s(:test, nil, 'B'))
|
s(:axis, 'child', s(:test, nil, 'B'))
|
||||||
),
|
),
|
||||||
s(
|
s(
|
||||||
:path,
|
:axis,
|
||||||
s(:axis, 'child', s(:test, nil, 'C')),
|
'child',
|
||||||
|
s(:test, nil, 'C'),
|
||||||
s(:axis, 'child', s(:test, nil, 'D'))
|
s(:axis, 'child', s(:test, nil, 'D'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,35 +15,53 @@ describe Oga::XPath::Parser do
|
||||||
|
|
||||||
it 'parses a relative path using two steps' do
|
it 'parses a relative path using two steps' do
|
||||||
parse_xpath('A/B').should == s(
|
parse_xpath('A/B').should == s(
|
||||||
:path,
|
:axis,
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
'child',
|
||||||
s(:axis, 'child', s(:test, nil, 'B')),
|
s(:test, nil, 'A'),
|
||||||
|
s(:axis, 'child', s(:test, nil, 'B'))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses a relative path using three steps' do
|
it 'parses a relative path using three steps' do
|
||||||
parse_xpath('A/B/C').should == s(
|
parse_xpath('A/B/C').should == s(
|
||||||
:path,
|
:axis,
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
'child',
|
||||||
s(:axis, 'child', s(:test, nil, 'B')),
|
s(:test, nil, 'A'),
|
||||||
s(:axis, 'child', s(:test, nil, 'C')),
|
s(
|
||||||
|
:axis,
|
||||||
|
'child',
|
||||||
|
s(:test, nil, 'B'),
|
||||||
|
s(:axis, 'child', s(:test, nil, 'C'))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses an expression using two paths' do
|
it 'parses an expression using two paths' do
|
||||||
parse_xpath('/A/B').should == s(
|
parse_xpath('/A/B').should == s(
|
||||||
:absolute_path,
|
:absolute_path,
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
s(
|
||||||
s(:axis, 'child', s(:test, nil, 'B'))
|
:axis,
|
||||||
|
'child',
|
||||||
|
s(:test, nil, 'A'),
|
||||||
|
s(:axis, 'child', s(:test, nil, 'B'))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses an expression using three paths' do
|
it 'parses an expression using three paths' do
|
||||||
parse_xpath('/A/B/C').should == s(
|
parse_xpath('/A/B/C').should == s(
|
||||||
:absolute_path,
|
:absolute_path,
|
||||||
s(:axis, 'child', s(:test, nil, 'A')),
|
s(
|
||||||
s(:axis, 'child', s(:test, nil, 'B')),
|
:axis,
|
||||||
s(:axis, 'child', s(:test, nil, 'C'))
|
'child',
|
||||||
|
s(:test, nil, 'A'),
|
||||||
|
s(
|
||||||
|
:axis,
|
||||||
|
'child',
|
||||||
|
s(:test, nil, 'B'),
|
||||||
|
s(:axis, 'child', s(:test, nil, 'C'))
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Oga::XPath::Parser do
|
||||||
|
describe 'node() type test' do
|
||||||
|
it 'parses the standalone type test' do
|
||||||
|
parse_xpath('node()').should == s(:axis, 'child', s(:type_test, 'node'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses the type test in a predicate' do
|
||||||
|
parse_xpath('node()[1]').should == s(
|
||||||
|
:predicate,
|
||||||
|
s(:axis, 'child', s(:type_test, 'node')),
|
||||||
|
s(:int, 1)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'comment() type test' do
|
||||||
|
it 'parses the standalone type test' do
|
||||||
|
parse_xpath('comment()').should == s(:axis, 'child', s(:type_test, 'comment'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses the type test in a predicate' do
|
||||||
|
parse_xpath('comment()[1]').should == s(
|
||||||
|
:predicate,
|
||||||
|
s(:axis, 'child', s(:type_test, 'comment')),
|
||||||
|
s(:int, 1)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'text() type test' do
|
||||||
|
it 'parses the standalone type test' do
|
||||||
|
parse_xpath('text()').should == s(:axis, 'child', s(:type_test, 'text'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses the type test in a predicate' do
|
||||||
|
parse_xpath('text()[1]').should == s(
|
||||||
|
:predicate,
|
||||||
|
s(:axis, 'child', s(:type_test, 'text')),
|
||||||
|
s(:int, 1)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'processing-instruction() type test' do
|
||||||
|
it 'parses the standalone type test' do
|
||||||
|
parse_xpath('processing-instruction()').should ==
|
||||||
|
s(:axis, 'child', s(:type_test, 'processing-instruction'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'parses the type test in a predicate' do
|
||||||
|
parse_xpath('processing-instruction()[1]').should == s(
|
||||||
|
:predicate,
|
||||||
|
s(:axis, 'child', s(:type_test, 'processing-instruction')),
|
||||||
|
s(:int, 1)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue