From 87f6b9c72382d8e5ae84e3e078f3118a821cefbf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 28 Oct 2014 00:21:11 +0100 Subject: [PATCH] Basic support for :nth-child() This already includes support for formulas such as 2n, odd, even and 2n+1. Negative formulas, just "n" and others are not yet supported. --- lib/oga/css/parser.y | 62 ++++++++++++++++++---- spec/oga/css/parser/pseudo_classes_spec.rb | 37 ++++++------- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index c59e321..c986e61 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -259,8 +259,19 @@ rule | selector ; + string + : T_STRING { s(:string, val[0]) } + ; + + integer + : T_INT { s(:int, val[0].to_i) } + ; + + # These AST nodes are _not_ the final AST nodes. Instead they are used by + # on_pseudo_class_nth_child() to determine what the final AST should be. + nth - : T_NTH { s(:nth) } + : T_NTH { s(:nth, s(:int, 1)) } | T_MINUS T_NTH { s(:nth) } | integer T_NTH { s(:nth, val[0]) } | integer T_NTH integer { s(:nth, val[0], val[2]) } @@ -273,14 +284,6 @@ rule even : T_EVEN { s(:nth, s(:int, 2)) } ; - - string - : T_STRING { s(:string, val[0]) } - ; - - integer - : T_INT { s(:int, val[0].to_i) } - ; end ---- inner @@ -347,4 +350,45 @@ end def on_pseudo_class_root return s(:call, 'not', s(:axis, 'parent', s(:test, nil, '*'))) end + + ## + # Generates the AST for the `nth-child` pseudo class. + # + # @param [AST::Node] arg + # @return [AST::Node] + # + def on_pseudo_class_nth_child(arg) + if arg.type == :int + node = s( + :eq, + s( + :call, + 'count', + s(:axis, 'preceding-sibling', s(:test, nil, '*')) + ), + s(:int, arg.children[0] - 1) + ) + else + step, offset = *arg + + before_count = s( + :add, + s(:call, 'count', s(:axis, 'preceding-sibling', s(:test, nil, '*'))), + s(:int, 1) + ) + + if offset + node = s( + :and, + s(:gte, before_count, offset), + s(:eq, s(:mod, s(:sub, before_count, offset), s(:int, 2)), s(:int, 0)) + ) + else + node = s(:mod, before_count, step) + end + end + + return node + end + # vim: set ft=racc: diff --git a/spec/oga/css/parser/pseudo_classes_spec.rb b/spec/oga/css/parser/pseudo_classes_spec.rb index 146a6de..fad1a4c 100644 --- a/spec/oga/css/parser/pseudo_classes_spec.rb +++ b/spec/oga/css/parser/pseudo_classes_spec.rb @@ -15,38 +15,33 @@ describe Oga::CSS::Parser do end example 'parse the x:nth-child(1) pseudo class' do - parse_css('x:nth-child(1)').should == s( - :pseudo, - s(:test, nil, 'x'), - 'nth-child', - s(:int, 1) + parse_css('x:nth-child(1)').should == parse_xpath( + 'descendant-or-self::x[count(preceding-sibling::*) = 0]' ) end example 'parse the :nth-child(1) pseudo class' do - parse_css(':nth-child(1)').should == s( - :pseudo, - nil, - 'nth-child', - s(:int, 1) + parse_css(':nth-child(1)').should == parse_xpath( + 'descendant-or-self::*[count(preceding-sibling::*) = 0]' ) end - example 'parse the x:nth-child(odd) pseudo class' do - parse_css('x:nth-child(odd)').should == s( - :pseudo, - s(:test, nil, 'x'), - 'nth-child', - s(:odd) + example 'parse the :nth-child(2) pseudo class' do + parse_css(':nth-child(2)').should == parse_xpath( + 'descendant-or-self::*[count(preceding-sibling::*) = 1]' ) end example 'parse the x:nth-child(even) pseudo class' do - parse_css('x:nth-child(even)').should == s( - :pseudo, - s(:test, nil, 'x'), - 'nth-child', - s(:even) + parse_css('x:nth-child(even)').should == parse_xpath( + 'descendant-or-self::x[(count(preceding-sibling::*) + 1) mod 2]' + ) + end + + example 'parse the x:nth-child(odd) pseudo class' do + parse_css('x:nth-child(odd)').should == parse_xpath( + 'descendant-or-self::x[(count(preceding-sibling::*) + 1) >= 1 ' \ + 'and (((count(preceding-sibling::*) + 1) - 1) mod 2) = 0]' ) end