diff --git a/lib/oga/css/lexer.rl b/lib/oga/css/lexer.rl index aab61bc..b98d860 100644 --- a/lib/oga/css/lexer.rl +++ b/lib/oga/css/lexer.rl @@ -147,7 +147,7 @@ module Oga # Identifiers are used for element and attribute names. Identifiers have # to start with a letter. - identifier = [a-zA-Z*]+ [a-zA-Z\-_0-9]*; + identifier = '*' | [a-zA-Z]+ [a-zA-Z\-_0-9]*; action emit_identifier { emit(:T_IDENT, ts, te) @@ -184,6 +184,23 @@ module Oga add_token(:T_INT, value) } + # Strings + # + # Strings can be single or double quoted. They are mainly used for + # attribute values. + # + dquote = '"'; + squote = "'"; + + string_dquote = (dquote ^dquote* dquote); + string_squote = (squote ^squote* squote); + + string = string_dquote | string_squote; + + action emit_string { + emit(:T_STRING, ts + 1, te - 1) + } + # Nth numbers # # These numbers are in the form of 2n+1 and are used for @@ -236,6 +253,7 @@ module Oga nth_integer => emit_nth_integer; nth_identifier => emit_nth_identifier; integer => emit_integer; + string => emit_string; any; *|; diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index 2db64bd..f724dfc 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -14,6 +14,7 @@ rule expression : path | node_test + | node_operators ; path_member @@ -46,7 +47,39 @@ rule ; predicate - : T_LBRACK expression T_RBRACK { val[1] } + : T_LBRACK predicate_members T_RBRACK { val[1] } + ; + + predicate_members + : expression { val[0] } + | operator { val[0] } + ; + + operator + : op_members T_EQ op_members { s(:eq, val[0], val[2]) } + | op_members T_SPACE_IN op_members { s(:space_in, val[0], val[2]) } + | op_members T_STARTS_WITH op_members { s(:starts_with, val[0], val[2]) } + | op_members T_ENDS_WITH op_members { s(:ends_with, val[0], val[2]) } + | op_members T_IN op_members { s(:in, val[0], val[2]) } + | op_members T_HYPHEN_IN op_members { s(:hyphen_in, val[0],val[2]) } + ; + + node_operators + : node_test T_CHILD node_test { s(:child, val[0], val[2]) } + | node_test T_FOLLOWING node_test { s(:following, val[0], val[2]) } + | node_test T_FOLLOWING_DIRECT node_test + { + s(:following_direct, val[0], val[2]) + } + ; + + op_members + : node_test + | string + ; + + string + : T_STRING { s(:string, val[0]) } ; end diff --git a/spec/oga/css/lexer/operators_spec.rb b/spec/oga/css/lexer/operators_spec.rb index 36130bf..b197181 100644 --- a/spec/oga/css/lexer/operators_spec.rb +++ b/spec/oga/css/lexer/operators_spec.rb @@ -22,6 +22,10 @@ describe Oga::CSS::Lexer do lex_css('*=').should == [[:T_IN, nil]] end + example 'lex an identifier followed by the *= operator' do + lex_css('foo *=').should == [[:T_IDENT, 'foo'], [:T_IN, nil]] + end + example 'lex the |= operator' do lex_css('|=').should == [[:T_HYPHEN_IN, nil]] end diff --git a/spec/oga/css/lexer/strings_spec.rb b/spec/oga/css/lexer/strings_spec.rb new file mode 100644 index 0000000..7eb0f3f --- /dev/null +++ b/spec/oga/css/lexer/strings_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe Oga::CSS::Lexer do + context 'strings' do + example 'lex a single quoted string' do + lex_css("'foo'").should == [[:T_STRING, 'foo']] + end + + example 'lex a double quoted string' do + lex_css('"foo"').should == [[:T_STRING, 'foo']] + end + end +end diff --git a/spec/oga/css/parser/operators_spec.rb b/spec/oga/css/parser/operators_spec.rb new file mode 100644 index 0000000..e33e042 --- /dev/null +++ b/spec/oga/css/parser/operators_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe Oga::CSS::Parser do + context 'operators' do + example 'parse the = operator' do + parse_css('x[a="b"]').should == s( + :test, + nil, + 'x', + s(:eq, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the ~= operator' do + parse_css('x[a~="b"]').should == s( + :test, + nil, + 'x', + s(:space_in, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the ^= operator' do + parse_css('x[a^="b"]').should == s( + :test, + nil, + 'x', + s(:starts_with, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the $= operator' do + parse_css('x[a$="b"]').should == s( + :test, + nil, + 'x', + s(:ends_with, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the *= operator' do + parse_css('x[a*="b"]').should == s( + :test, + nil, + 'x', + s(:in, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the |= operator' do + parse_css('x[a|="b"]').should == s( + :test, + nil, + 'x', + s(:hyphen_in, s(:test, nil, 'a'), s(:string, 'b')) + ) + end + + example 'parse the > operator' do + parse_css('x > y').should == s( + :child, + s(:test, nil, 'x'), + s(:test, nil, 'y') + ) + end + + example 'parse the + operator' do + parse_css('x + y').should == s( + :following_direct, + s(:test, nil, 'x'), + s(:test, nil, 'y') + ) + end + + example 'parse the ~ operator' do + parse_css('x ~ y').should == s( + :following, + s(:test, nil, 'x'), + s(:test, nil, 'y') + ) + end + end +end