diff --git a/lib/oga/xpath/parser.y b/lib/oga/xpath/parser.y index 184d70c..74bde1e 100644 --- a/lib/oga/xpath/parser.y +++ b/lib/oga/xpath/parser.y @@ -3,30 +3,74 @@ # class Oga::XPath::Parser -token T_AXIS T_COLON T_COMMA T_FLOAT T_INT T_IDENT T_OP T_STAR +token T_AXIS T_COLON T_COMMA T_FLOAT T_INT T_IDENT T_STAR token T_LBRACK T_RBRACK T_LPAREN T_RPAREN T_SLASH T_STRING +token T_PIPE T_AND T_OR T_ADD T_DIV T_MOD T_EQ T_NEQ T_LT T_GT T_LTE T_GTE options no_result_var +prechigh + left T_EQ + right T_OR +preclow + rule - expressions - : expressions_ { s(:xpath, *val[0]) } - | /* none */ { s(:xpath) } + xpath + : expressions { s(:xpath, *val[0]) } + | /* none */ { s(:xpath) } ; - expressions_ - : expressions_ expression { val[0] << val[1] } - | expression { val } + expressions + : expressions expression { val[0] << val[1] } + | expression { val } ; expression - : node_test + : path + | node_test + | operator + | axis + | string + | number + ; + + path + : T_SLASH node_test { s(:path, val[1]) } ; node_test - : T_IDENT { s(:node, nil, val[0]) } - | T_IDENT T_COLON T_IDENT { s(:node, val[0], val[2]) } + : node_name { s(:node_test, val[0]) } + | node_name predicate { s(:node_test, val[0], *val[1]) } ; + + node_name + # foo + : T_IDENT { s(:name, nil, val[0]) } + + # foo:bar + | T_IDENT T_COLON T_IDENT { s(:name, val[0], val[1]) } + ; + + predicate + : T_LBRACK expressions T_RBRACK { val[1] } + ; + + operator + : expression T_EQ expression { s(:eq, val[0], val[2]) } + | expression T_OR expression { s(:or, val[0], val[2]) } + ; + + axis + : T_AXIS T_IDENT { s(:axis, val[0], val[1]) } + ; + + string + : T_STRING { s(:string, val[0]) } + ; + + number + : T_INT { s(:int, val[0]) } + | T_FLOAT { s(:float, val[0]) } end ---- inner diff --git a/spec/oga/xpath/parser/axes_spec.rb b/spec/oga/xpath/parser/axes_spec.rb new file mode 100644 index 0000000..ae9bbc5 --- /dev/null +++ b/spec/oga/xpath/parser/axes_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Oga::XPath::Parser do + context 'axes' do + example 'parse an absolute path' do + parse_xpath('/foo').should == s( + :xpath, + s(:path, s(:node_test, s(:name, nil, 'foo'))) + ) + end + + example 'parse a relative path' do + parse_xpath('foo').should == s(:xpath, s(:node_test, s(:name, nil, 'foo'))) + end + + example 'parse an expression using two paths' do + parse_xpath('/foo/bar').should == s( + :xpath, + s(:path, s(:node_test, s(:name, nil, 'foo'))), + s(:path, s(:node_test, s(:name, nil, 'bar'))) + ) + end + end +end diff --git a/spec/oga/xpath/parser/predicates_spec.rb b/spec/oga/xpath/parser/predicates_spec.rb new file mode 100644 index 0000000..d4a346a --- /dev/null +++ b/spec/oga/xpath/parser/predicates_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Oga::XPath::Parser do + context 'predicates' do + example 'parse a single predicate' do + parse_xpath('/foo[@class="bar"]').should == s( + :xpath, + s( + :path, + s( + :node_test, + s(:name, nil, 'foo'), + s(:eq, s(:axis, 'attribute', 'class'), s(:string, 'bar')) + ) + ) + ) + end + + example 'parse a predicate using the or operator' do + parse_xpath('/foo[@class="bar" or @class="baz"]').should == s( + :xpath, + s( + :path, + s( + :node_test, + s(:name, nil, 'foo'), + s( + :or, + s(:eq, s(:axis, 'attribute', 'class'), s(:string, 'bar')), + s(:eq, s(:axis, 'attribute', 'class'), s(:string, 'baz')) + ) + ) + ) + ) + end + end +end diff --git a/spec/oga/xpath/parser_spec.rb b/spec/oga/xpath/parser_spec.rb deleted file mode 100644 index 0d3567e..0000000 --- a/spec/oga/xpath/parser_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe Oga::XPath::Parser do - pending 'Write me!' -end diff --git a/spec/support/parsing.rb b/spec/support/parsing.rb index 411fbcb..c17e77c 100644 --- a/spec/support/parsing.rb +++ b/spec/support/parsing.rb @@ -5,10 +5,11 @@ module Oga # # @param [Symbol] type # @param [Array] cihldren - # @return [Oga::AST::Node] + # @return [AST::Node] # def s(type, *children) - return Oga::AST::Node.new(type, children) + # TODO: add support for CSS AST nodes. + return Oga::XPath::Node.new(type, children) end ## @@ -32,6 +33,16 @@ module Oga return Oga::XPath::Lexer.new(input).lex end + ## + # Parses an XPath expression. + # + # @param [String] input + # @return [Oga::XPath::Node] + # + def parse_xpath(input) + return Oga::XPath::Parser.new(input).parse + end + ## # Parses the given XML and returns an AST. #