diff --git a/lib/oga/xpath/parser.rll b/lib/oga/xpath/parser.rll index 430f3d7..8af5252 100644 --- a/lib/oga/xpath/parser.rll +++ b/lib/oga/xpath/parser.rll @@ -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 } ; diff --git a/spec/oga/xpath/parser/axes_spec.rb b/spec/oga/xpath/parser/axes_spec.rb index 611c92b..8468b83 100644 --- a/spec/oga/xpath/parser/axes_spec.rb +++ b/spec/oga/xpath/parser/axes_spec.rb @@ -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 diff --git a/spec/oga/xpath/parser/calls_spec.rb b/spec/oga/xpath/parser/calls_spec.rb index 81356c7..b94549a 100644 --- a/spec/oga/xpath/parser/calls_spec.rb +++ b/spec/oga/xpath/parser/calls_spec.rb @@ -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 diff --git a/spec/oga/xpath/parser/node_type_spec.rb b/spec/oga/xpath/parser/node_type_spec.rb deleted file mode 100644 index 0c440a6..0000000 --- a/spec/oga/xpath/parser/node_type_spec.rb +++ /dev/null @@ -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 diff --git a/spec/oga/xpath/parser/operators_spec.rb b/spec/oga/xpath/parser/operators_spec.rb index 9442f53..02c1f39 100644 --- a/spec/oga/xpath/parser/operators_spec.rb +++ b/spec/oga/xpath/parser/operators_spec.rb @@ -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')) ) ) diff --git a/spec/oga/xpath/parser/paths_spec.rb b/spec/oga/xpath/parser/paths_spec.rb index 12a79f4..02bafd8 100644 --- a/spec/oga/xpath/parser/paths_spec.rb +++ b/spec/oga/xpath/parser/paths_spec.rb @@ -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 diff --git a/spec/oga/xpath/parser/type_tests_spec.rb b/spec/oga/xpath/parser/type_tests_spec.rb new file mode 100644 index 0000000..61ff344 --- /dev/null +++ b/spec/oga/xpath/parser/type_tests_spec.rb @@ -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