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
|
||||
= relative_path
|
||||
= path_step_or_axis
|
||||
| absolute_path
|
||||
| string
|
||||
| number
|
||||
|
@ -105,22 +105,16 @@ expression_member
|
|||
| 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
|
||||
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
|
||||
|
@ -135,6 +129,7 @@ path_step
|
|||
type = val[1][0]
|
||||
args = val[1][1]
|
||||
pred = val[1][2]
|
||||
more = val[1][3]
|
||||
|
||||
if type.equal?(:test)
|
||||
# Whenever a bare test is used (e.g. just "A") this actually means
|
||||
|
@ -152,15 +147,38 @@ path_step
|
|||
node = s(:predicate, node, pred)
|
||||
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
|
||||
}
|
||||
| type_test { s(:axis, 'child', val[0]) }
|
||||
;
|
||||
|
||||
path_step_follow
|
||||
= T_LPAREN call_args T_RPAREN { [:call, val[1]] }
|
||||
| T_COLON T_IDENT predicate? { [:test, val[1], val[2]] }
|
||||
| predicate? { [:test, nil, val[0]] }
|
||||
= T_LPAREN call_args T_RPAREN { [:call, val[1]] }
|
||||
| T_COLON T_IDENT predicate? path_step_more? { [:test, val[1], val[2], val[3]] }
|
||||
| predicate? path_step_more? { [:test, nil, val[0], val[1]] }
|
||||
;
|
||||
|
||||
path_step_more
|
||||
= T_SLASH path_step_or_axis { val[1] }
|
||||
;
|
||||
|
||||
predicate
|
||||
|
@ -194,14 +212,19 @@ call_args_follow
|
|||
|
||||
# child::foo, descendant-or-self::foo, etc
|
||||
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]
|
||||
ret = s(:predicate, ret, val[2])
|
||||
end
|
||||
|
||||
if more
|
||||
ret = ret.updated(nil, ret.children + [more])
|
||||
end
|
||||
|
||||
ret
|
||||
}
|
||||
;
|
||||
|
|
|
@ -112,8 +112,12 @@ describe Oga::XPath::Parser do
|
|||
it 'parses the // axis' do
|
||||
parse_xpath('//A').should == s(
|
||||
:absolute_path,
|
||||
s(:axis, 'descendant-or-self', s(:type_test, 'node')),
|
||||
s(:axis, 'child', s(:test, nil, 'A'))
|
||||
s(
|
||||
:axis,
|
||||
'descendant-or-self',
|
||||
s(:type_test, 'node'),
|
||||
s(:axis, 'child', s(:test, nil, 'A'))
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -24,32 +24,25 @@ describe Oga::XPath::Parser do
|
|||
end
|
||||
|
||||
it 'parses a relative path with a function call' do
|
||||
parse_xpath('foo/bar()').should == s(
|
||||
:path,
|
||||
s(:axis, 'child', s(:test, nil, 'foo')),
|
||||
s(:call, 'bar')
|
||||
)
|
||||
parse_xpath('foo/bar()').should ==
|
||||
s(:axis, 'child', s(:test, nil, 'foo'), s(:call, 'bar'))
|
||||
end
|
||||
|
||||
it 'parses an absolute path with a function call' do
|
||||
parse_xpath('/foo/bar()').should == s(
|
||||
:absolute_path,
|
||||
s(:axis, 'child', s(:test, nil, 'foo')),
|
||||
s(:call, 'bar')
|
||||
s(:axis, 'child', s(:test, nil, 'foo'), s(:call, 'bar'))
|
||||
)
|
||||
end
|
||||
|
||||
it 'parses a predicate followed by a function call' do
|
||||
parse_xpath('div[@class="foo"]/bar()').should == s(
|
||||
:path,
|
||||
:predicate,
|
||||
s(:axis, 'child', s(:test, nil, 'div')),
|
||||
s(
|
||||
:predicate,
|
||||
s(:axis, 'child', 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')
|
||||
)
|
||||
|
@ -57,18 +50,15 @@ describe Oga::XPath::Parser do
|
|||
|
||||
it 'parses two predicates followed by a function call' do
|
||||
parse_xpath('A[@x]/B[@x]/bar()').should == s(
|
||||
:path,
|
||||
s(
|
||||
:predicate,
|
||||
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(
|
||||
:predicate,
|
||||
s(:axis, 'child', s(:test, nil, 'B')),
|
||||
s(:axis, 'attribute', s(:test, nil, 'x'))
|
||||
),
|
||||
s(:call, 'bar')
|
||||
s(:axis, 'attribute', s(:test, nil, 'x')),
|
||||
s(:call, 'bar')
|
||||
)
|
||||
)
|
||||
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(
|
||||
:pipe,
|
||||
s(
|
||||
:path,
|
||||
s(:axis, 'child', s(:test, nil, 'A')),
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'A'),
|
||||
s(:axis, 'child', s(:test, nil, 'B'))
|
||||
),
|
||||
s(
|
||||
:path,
|
||||
s(:axis, 'child', s(:test, nil, 'C')),
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'C'),
|
||||
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
|
||||
parse_xpath('A/B').should == s(
|
||||
:path,
|
||||
s(:axis, 'child', s(:test, nil, 'A')),
|
||||
s(:axis, 'child', s(:test, nil, 'B')),
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'A'),
|
||||
s(:axis, 'child', s(:test, nil, 'B'))
|
||||
)
|
||||
end
|
||||
|
||||
it 'parses a relative path using three steps' do
|
||||
parse_xpath('A/B/C').should == s(
|
||||
:path,
|
||||
s(:axis, 'child', s(:test, nil, 'A')),
|
||||
s(:axis, 'child', s(:test, nil, 'B')),
|
||||
s(:axis, 'child', s(:test, nil, 'C')),
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'A'),
|
||||
s(
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'B'),
|
||||
s(:axis, 'child', s(:test, nil, 'C'))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'parses an expression using two paths' do
|
||||
parse_xpath('/A/B').should == s(
|
||||
:absolute_path,
|
||||
s(:axis, 'child', s(:test, nil, 'A')),
|
||||
s(:axis, 'child', s(:test, nil, 'B'))
|
||||
s(
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'A'),
|
||||
s(:axis, 'child', s(:test, nil, 'B'))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'parses an expression using three paths' do
|
||||
parse_xpath('/A/B/C').should == s(
|
||||
:absolute_path,
|
||||
s(:axis, 'child', s(:test, nil, 'A')),
|
||||
s(:axis, 'child', s(:test, nil, 'B')),
|
||||
s(:axis, 'child', s(:test, nil, 'C'))
|
||||
s(
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'A'),
|
||||
s(
|
||||
:axis,
|
||||
'child',
|
||||
s(:test, nil, 'B'),
|
||||
s(:axis, 'child', s(:test, nil, 'C'))
|
||||
)
|
||||
)
|
||||
)
|
||||
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