diff --git a/lib/oga/xpath/parser.y b/lib/oga/xpath/parser.y index b0f80aa..5b1c496 100644 --- a/lib/oga/xpath/parser.y +++ b/lib/oga/xpath/parser.y @@ -29,24 +29,10 @@ preclow rule xpath - : paths { val[0] } + : expression { val[0] } | /* none */ { nil } ; - paths - : T_SLASH path { s(:absolute, val[1]) } - | path { val[0] } - ; - - path - : expressions { s(:path, *val[0]) } - ; - - expressions - : expression { val } - | expression T_SLASH expressions { [val[0], *val[2]] } - ; - expression : node_test | operator @@ -54,6 +40,30 @@ rule | string | number | call + | path + | absolute_path + ; + + path_member + : node_test + | axis + | call + ; + + path_members + : path_member T_SLASH path_member { [val[0], val[2]] } + | path_member T_SLASH path_members { [val[0], *val[2]] } + ; + + # A and A/B + path + : path_members { s(:path, *val[0]) } + ; + + # /A and /A/B + absolute_path + : T_SLASH path_members { s(:absolute_path, *val[1]) } + | T_SLASH path_member { s(:absolute_path, val[1]) } ; node_test @@ -70,24 +80,24 @@ rule ; predicate - : T_LBRACK paths T_RBRACK { val[1] } + : T_LBRACK expression T_RBRACK { val[1] } ; operator - : paths T_PIPE paths { s(:pipe, val[0], val[2]) } - | paths T_AND paths { s(:and, val[0], val[2]) } - | paths T_OR paths { s(:or, val[0], val[2]) } - | paths T_ADD paths { s(:add, val[0], val[2]) } - | paths T_DIV paths { s(:div, val[0], val[2]) } - | paths T_MOD paths { s(:mod, val[0], val[2]) } - | paths T_EQ paths { s(:eq, val[0], val[2]) } - | paths T_NEQ paths { s(:neq, val[0], val[2]) } - | paths T_LT paths { s(:lt, val[0], val[2]) } - | paths T_GT paths { s(:gt, val[0], val[2]) } - | paths T_LTE paths { s(:lte, val[0], val[2]) } - | paths T_GTE paths { s(:gte, val[0], val[2]) } - | paths T_MUL paths { s(:mul, val[0], val[2]) } - | paths T_SUB paths { s(:sub, val[0], val[2]) } + : expression T_PIPE expression { s(:pipe, val[0], val[2]) } + | expression T_AND expression { s(:and, val[0], val[2]) } + | expression T_OR expression { s(:or, val[0], val[2]) } + | expression T_ADD expression { s(:add, val[0], val[2]) } + | expression T_DIV expression { s(:div, val[0], val[2]) } + | expression T_MOD expression { s(:mod, val[0], val[2]) } + | expression T_EQ expression { s(:eq, val[0], val[2]) } + | expression T_NEQ expression { s(:neq, val[0], val[2]) } + | expression T_LT expression { s(:lt, val[0], val[2]) } + | expression T_GT expression { s(:gt, val[0], val[2]) } + | expression T_LTE expression { s(:lte, val[0], val[2]) } + | expression T_GTE expression { s(:gte, val[0], val[2]) } + | expression T_MUL expression { s(:mul, val[0], val[2]) } + | expression T_SUB expression { s(:sub, val[0], val[2]) } ; axis @@ -106,8 +116,8 @@ rule ; call_args - : paths { val } - | paths T_COMMA call_args { val[2].unshift(val[0]) } + : expression { val } + | expression T_COMMA call_args { val[2].unshift(val[0]) } ; string diff --git a/spec/oga/xpath/parser/axes_spec.rb b/spec/oga/xpath/parser/axes_spec.rb index 9dbf444..9cd43f9 100644 --- a/spec/oga/xpath/parser/axes_spec.rb +++ b/spec/oga/xpath/parser/axes_spec.rb @@ -4,92 +4,92 @@ describe Oga::XPath::Parser do context 'full axes' do example 'parse the ancestor axis' do parse_xpath('/ancestor::A').should == s( - :absolute, - s(:path, s(:axis, 'ancestor', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'ancestor', s(:test, nil, 'A')) ) end example 'parse the ancestor-or-self axis' do parse_xpath('/ancestor-or-self::A').should == s( - :absolute, - s(:path, s(:axis, 'ancestor-or-self', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'ancestor-or-self', s(:test, nil, 'A')) ) end example 'parse the attribute axis' do parse_xpath('/attribute::A').should == s( - :absolute, - s(:path, s(:axis, 'attribute', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'attribute', s(:test, nil, 'A')) ) end example 'parse the child axis' do parse_xpath('/child::A').should == s( - :absolute, - s(:path, s(:axis, 'child', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'child', s(:test, nil, 'A')) ) end example 'parse the descendant axis' do parse_xpath('/descendant::A').should == s( - :absolute, - s(:path, s(:axis, 'descendant', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'descendant', s(:test, nil, 'A')) ) end example 'parse the descendant-or-self axis' do parse_xpath('/descendant-or-self::A').should == s( - :absolute, - s(:path, s(:axis, 'descendant-or-self', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'descendant-or-self', s(:test, nil, 'A')) ) end example 'parse the following axis' do parse_xpath('/following::A').should == s( - :absolute, - s(:path, s(:axis, 'following', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'following', s(:test, nil, 'A')) ) end example 'parse the following-sibling axis' do parse_xpath('/following-sibling::A').should == s( - :absolute, - s(:path, s(:axis, 'following-sibling', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'following-sibling', s(:test, nil, 'A')) ) end example 'parse the namespace axis' do parse_xpath('/namespace::A').should == s( - :absolute, - s(:path, s(:axis, 'namespace', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'namespace', s(:test, nil, 'A')) ) end example 'parse the parent axis' do parse_xpath('/parent::A').should == s( - :absolute, - s(:path, s(:axis, 'parent', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'parent', s(:test, nil, 'A')) ) end example 'parse the preceding axis' do parse_xpath('/preceding::A').should == s( - :absolute, - s(:path, s(:axis, 'preceding', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'preceding', s(:test, nil, 'A')) ) end example 'parse the preceding-sibling axis' do parse_xpath('/preceding-sibling::A').should == s( - :absolute, - s(:path, s(:axis, 'preceding-sibling', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'preceding-sibling', s(:test, nil, 'A')) ) end example 'parse the self axis' do parse_xpath('/self::A').should == s( - :absolute, - s(:path, s(:axis, 'self', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'self', s(:test, nil, 'A')) ) end end @@ -97,30 +97,24 @@ describe Oga::XPath::Parser do context 'short axes' do example 'parse the @attribute axis' do parse_xpath('/@A').should == s( - :absolute, - s(:path, s(:axis, 'attribute', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'attribute', s(:test, nil, 'A')) ) end example 'parse the // axis' do parse_xpath('//A').should == s( - :absolute, - s(:path, s(:axis, 'descendant-or-self', s(:test, nil, 'A'))) + :absolute_path, + s(:axis, 'descendant-or-self', s(:test, nil, 'A')) ) end example 'parse the .. axis' do - parse_xpath('/..').should == s( - :absolute, - s(:path, s(:axis, 'parent')) - ) + parse_xpath('/..').should == s(:absolute_path, s(:axis, 'parent')) end example 'parse the . axis' do - parse_xpath('/.').should == s( - :absolute, - s(:path, s(:axis, 'self')) - ) + parse_xpath('/.').should == s(:absolute_path, s(:axis, 'self')) end end end diff --git a/spec/oga/xpath/parser/calls_spec.rb b/spec/oga/xpath/parser/calls_spec.rb index 45a5baa..5631411 100644 --- a/spec/oga/xpath/parser/calls_spec.rb +++ b/spec/oga/xpath/parser/calls_spec.rb @@ -3,25 +3,23 @@ require 'spec_helper' describe Oga::XPath::Parser do context 'function calls' do example 'parse a function call without arguments' do - parse_xpath('count()').should == s(:path, s(:call, 'count')) + parse_xpath('count()').should == s(:call, 'count') end example 'parse a function call with a single argument' do parse_xpath('count(/foo)').should == s( - :path, - s(:call, 'count', s(:absolute, s(:path, s(:test, nil, 'foo')))) + :call, + 'count', + s(:absolute_path, s(:test, nil, 'foo')) ) end example 'parse a function call with two arguments' do parse_xpath('count(/foo, "bar")').should == s( - :path, - s( - :call, - 'count', - s(:absolute, s(:path, s(:test, nil, 'foo'))), - s(:path, s(:string, 'bar')) - ) + :call, + 'count', + s(:absolute_path, s(:test, nil, 'foo')), + s(:string, 'bar') ) end @@ -29,14 +27,14 @@ describe Oga::XPath::Parser do parse_xpath('foo/bar()').should == s( :path, s(:test, nil, 'foo'), - s(:path, s(:call, 'bar')) + s(:call, 'bar') ) end example 'parse an absolute path with a function call' do parse_xpath('/foo/bar()').should == s( - :absolute, - s(:path, s(:test, nil, 'foo'), s(:path, s(:call, 'bar'))) + :absolute_path, + s(:test, nil, 'foo'), s(:call, 'bar') ) end @@ -48,36 +46,21 @@ describe Oga::XPath::Parser do nil, 'div', s( - :path, - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'foo') - ) + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'foo') ) ), - s(:path, s(:call, 'bar')) + s(:call, 'bar') ) end example 'parse two predicates followed by a function call' do - parse_xpath('A[@class]/B[@class]/bar()').should == s( + parse_xpath('A[@x]/B[@x]/bar()').should == s( :path, - s( - :test, - nil, - 'A', - s(:path, s(:axis, 'attribute', s(:test, nil, 'class')))), - s( - :path, - s( - :test, - nil, - 'B', - s(:path, s(:axis, 'attribute', s(:test, nil, 'class'))) - ), - s(:path, s(:call, 'bar')) - ) + s(:test, nil, 'A', s(:axis, 'attribute', s(:test, nil, 'x'))), + s(:test, nil, 'B', s(:axis, 'attribute', s(:test, nil, 'x'))), + s(:call, 'bar') ) end end diff --git a/spec/oga/xpath/parser/operator_precedence_spec.rb b/spec/oga/xpath/parser/operator_precedence_spec.rb index abed008..1cfa69d 100644 --- a/spec/oga/xpath/parser/operator_precedence_spec.rb +++ b/spec/oga/xpath/parser/operator_precedence_spec.rb @@ -4,144 +4,105 @@ describe Oga::XPath::Parser do context 'operator precedence' do example 'parse "A or B or C"' do parse_xpath('A or B or C').should == s( - :path, - s( - :or, - s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :or, + s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A and B and C"' do parse_xpath('A and B and C').should == s( - :path, - s( - :and, - s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :and, + s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A = B = C"' do parse_xpath('A = B = C').should == s( - :path, - s( - :eq, - s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :eq, + s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A != B != C"' do parse_xpath('A != B != C').should == s( - :path, - s( - :neq, - s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :neq, + s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A <= B <= C"' do parse_xpath('A <= B <= C').should == s( - :path, - s( - :lte, - s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :lte, + s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A < B < C"' do parse_xpath('A < B < C').should == s( - :path, - s( - :lt, - s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :lt, + s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A >= B >= C"' do parse_xpath('A >= B >= C').should == s( - :path, - s( - :gte, - s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :gte, + s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A > B > C"' do parse_xpath('A > B > C').should == s( - :path, - s( - :gt, - s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :gt, + s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end example 'parse "A or B and C"' do parse_xpath('A or B and C').should == s( - :path, - s( - :or, - s(:test, nil, 'A'), - s(:and, s(:test, nil, 'B'), s(:test, nil, 'C')) - ) + :or, + s(:test, nil, 'A'), + s(:and, s(:test, nil, 'B'), s(:test, nil, 'C')) ) end example 'parse "A and B = C"' do parse_xpath('A and B = C').should == s( - :path, - s( - :and, - s(:test, nil, 'A'), - s(:eq, s(:test, nil, 'B'), s(:test, nil, 'C')) - ) + :and, + s(:test, nil, 'A'), + s(:eq, s(:test, nil, 'B'), s(:test, nil, 'C')) ) end example 'parse "A = B < C"' do parse_xpath('A = B < C').should == s( - :path, - s( - :eq, - s(:test, nil, 'A'), - s(:lt, s(:test, nil, 'B'), s(:test, nil, 'C')) - ) + :eq, + s(:test, nil, 'A'), + s(:lt, s(:test, nil, 'B'), s(:test, nil, 'C')) ) end example 'parse "A < B | C"' do parse_xpath('A < B | C').should == s( - :path, - s( - :lt, - s(:test, nil, 'A'), - s(:pipe, s(:test, nil, 'B'), s(:test, nil, 'C')) - ) + :lt, + s(:test, nil, 'A'), + s(:pipe, s(:test, nil, 'B'), s(:test, nil, 'C')) ) end example 'parse "A > B or C"' do parse_xpath('A > B or C').should == s( - :path, - s( - :or, - s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), - s(:test, nil, 'C') - ) + :or, + s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') ) end end diff --git a/spec/oga/xpath/parser/operators_spec.rb b/spec/oga/xpath/parser/operators_spec.rb index 4ec17bd..0548dbc 100644 --- a/spec/oga/xpath/parser/operators_spec.rb +++ b/spec/oga/xpath/parser/operators_spec.rb @@ -4,99 +4,121 @@ describe Oga::XPath::Parser do context 'operators' do example 'parse the pipe operator' do parse_xpath('A | B').should == s( - :path, - s(:pipe, s(:test, nil, 'A'), s(:test, nil, 'B')) + :pipe, + s(:test, nil, 'A'), + s(:test, nil, 'B') + ) + end + + example 'parse the pipe operator using two paths' do + parse_xpath('A/B | C/D').should == s( + :pipe, + s(:path, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:path, s(:test, nil, 'C'), s(:test, nil, 'D')) ) end example 'parse the and operator' do parse_xpath('A and B').should == s( - :path, - s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')) + :and, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the or operator' do parse_xpath('A or B').should == s( - :path, - s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')) + :or, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the plus operator' do parse_xpath('A + B').should == s( - :path, - s(:add, s(:test, nil, 'A'), s(:test, nil, 'B')) + :add, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the div operator' do parse_xpath('A div B').should == s( - :path, - s(:div, s(:test, nil, 'A'), s(:test, nil, 'B')) + :div, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the mod operator' do parse_xpath('A mod B').should == s( - :path, - s(:mod, s(:test, nil, 'A'), s(:test, nil, 'B')) + :mod, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the equals operator' do parse_xpath('A = B').should == s( - :path, - s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')) + :eq, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the not-equals operator' do parse_xpath('A != B').should == s( - :path, - s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')) + :neq, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the lower-than operator' do parse_xpath('A < B').should == s( - :path, - s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')) + :lt, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the greater-than operator' do parse_xpath('A > B').should == s( - :path, - s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')) + :gt, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the lower-or-equal operator' do parse_xpath('A <= B').should == s( - :path, - s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')) + :lte, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the greater-or-equal operator' do parse_xpath('A >= B').should == s( - :path, - s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')) + :gte, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the mul operator' do parse_xpath('A * B').should == s( - :path, - s(:mul, s(:test, nil, 'A'), s(:test, nil, 'B')) + :mul, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end example 'parse the subtraction operator' do parse_xpath('A - B').should == s( - :path, - s(:sub, s(:test, nil, 'A'), s(:test, nil, 'B')) + :sub, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end end diff --git a/spec/oga/xpath/parser/paths_spec.rb b/spec/oga/xpath/parser/paths_spec.rb index 7fe992f..f10ff26 100644 --- a/spec/oga/xpath/parser/paths_spec.rb +++ b/spec/oga/xpath/parser/paths_spec.rb @@ -3,17 +3,18 @@ require 'spec_helper' describe Oga::XPath::Parser do context 'paths' do example 'parse an absolute path' do - parse_xpath('/A').should == s(:absolute, s(:path, s(:test, nil, 'A'))) + parse_xpath('/A').should == s(:absolute_path, s(:test, nil, 'A')) end example 'parse a relative path' do - parse_xpath('A').should == s(:path, s(:test, nil, 'A')) + parse_xpath('A').should == s(:test, nil, 'A') end example 'parse an expression using two paths' do parse_xpath('/A/B').should == s( - :absolute, - s(:path, s(:test, nil, 'A'), s(:path, s(:test, nil, 'B'))) + :absolute_path, + s(:test, nil, 'A'), + s(:test, nil, 'B') ) end end diff --git a/spec/oga/xpath/parser/predicates_spec.rb b/spec/oga/xpath/parser/predicates_spec.rb index 1e0edf2..41acfce 100644 --- a/spec/oga/xpath/parser/predicates_spec.rb +++ b/spec/oga/xpath/parser/predicates_spec.rb @@ -4,46 +4,22 @@ describe Oga::XPath::Parser do context 'predicates' do example 'parse a single predicate' do parse_xpath('foo[@class="bar"]').should == s( - :path, - s( - :test, - nil, - 'foo', - s( - :path, - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'bar') - ) - ) - ) + :test, + nil, + 'foo', + s(:eq, s(:axis, 'attribute', s(:test, nil, 'class')), s(:string, 'bar')) ) end example 'parse a predicate using the or operator' do - parse_xpath('foo[@class="bar" or @class="baz"]').should == s( - :path, + parse_xpath('foo[@x="bar" or @x="baz"]').should == s( + :test, + nil, + 'foo', s( - :test, - nil, - 'foo', - s( - :path, - s( - :or, - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'bar') - ), - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'baz') - ) - ) - ) + :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 diff --git a/spec/oga/xpath/parser/wildcard_spec.rb b/spec/oga/xpath/parser/wildcard_spec.rb index 8d0de40..a007e1d 100644 --- a/spec/oga/xpath/parser/wildcard_spec.rb +++ b/spec/oga/xpath/parser/wildcard_spec.rb @@ -3,15 +3,15 @@ require 'spec_helper' describe Oga::XPath::Parser do context 'wildcards' do example 'parse a wildcard name test' do - parse_xpath('*').should == s(:path, s(:test, nil, '*')) + parse_xpath('*').should == s(:test, nil, '*') end example 'parse a wildcard namespace test' do - parse_xpath('*:A').should == s(:path, s(:test, '*', 'A')) + parse_xpath('*:A').should == s(:test, '*', 'A') end example 'parse a wildcard namespace and name test' do - parse_xpath('*:*').should == s(:path, s(:test, '*', '*')) + parse_xpath('*:*').should == s(:test, '*', '*') end end end