diff --git a/lib/oga/css/lexer.rl b/lib/oga/css/lexer.rl index fc8c50d..dde2b7e 100644 --- a/lib/oga/css/lexer.rl +++ b/lib/oga/css/lexer.rl @@ -230,6 +230,8 @@ module Oga pseudo_args := |* whitespace; + hash | dot | colon; + # NOTE: the priorities here are put in place to ensure that rules such # as `nth` take precedence over `identifier`. The highest number has # the highest priority. diff --git a/lib/oga/css/parser.rll b/lib/oga/css/parser.rll index ccffba8..bf978fd 100644 --- a/lib/oga/css/parser.rll +++ b/lib/oga/css/parser.rll @@ -497,6 +497,24 @@ even s(:call, 'not', s(:axis, 'child', s(:type_test, 'node'))) end + # Generates the AST for the `:not` selector. + # + # @param [AST::Node] arg + # @return [AST::Node] + def on_pseudo_class_not(arg) + # Unpacks (axis "descendant" (test nil "x")) into just (test nil "x") as + # in this case we want to wrap the (test) node in a (axis "self") node. + if arg.type == :axis + arg = s(:axis, 'self', arg.children[1]) + + # Unpack (predicate (eq ...)) into just (eq) + elsif arg.type == :predicate + arg = arg.children[1] + end + + s(:call, 'not', arg) + end + # Generates the AST for the `=` operator. # # @param [AST::Node] attr diff --git a/spec/oga/css/lexer/pseudo_classes_spec.rb b/spec/oga/css/lexer/pseudo_classes_spec.rb index fac944d..c307fe0 100644 --- a/spec/oga/css/lexer/pseudo_classes_spec.rb +++ b/spec/oga/css/lexer/pseudo_classes_spec.rb @@ -126,5 +126,16 @@ describe Oga::CSS::Lexer do [:T_RPAREN, nil] ] end + + it 'lexes the :not(#foo) pseudo class' do + lex_css(':not(#foo)').should == [ + [:T_COLON, nil], + [:T_IDENT, 'not'], + [:T_LPAREN, nil], + [:T_HASH, nil], + [:T_IDENT, 'foo'], + [:T_RPAREN, nil] + ] + end end end diff --git a/spec/oga/css/parser/pseudo_classes/not_spec.rb b/spec/oga/css/parser/pseudo_classes/not_spec.rb new file mode 100644 index 0000000..79871fb --- /dev/null +++ b/spec/oga/css/parser/pseudo_classes/not_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Oga::CSS::Parser do + describe ':not pseudo class' do + it 'parses the :not(x) pseudo class' do + parse_css(':not(x)').should == parse_xpath('descendant::*[not(self::x)]') + end + + it 'parses the x:not(y) pseudo class' do + parse_css('x:not(y)').should == parse_xpath('descendant::x[not(self::y)]') + end + + it 'parses the x:not(#foo) pseudo class' do + parse_css('x:not(#foo)').should == + parse_xpath('descendant::x[not(@id="foo")]') + end + end +end