diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index a3c3601..97b9c6c 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -436,11 +436,36 @@ end # @return [AST::Node] # def on_pseudo_class_nth_of_type(arg) - position_call = s(:call, 'position') + position_node = s(:call, 'position') + return generate_nth_of_type(arg, position_node) + end + + ## + # Generates the AST for the `nth-last-of-type` pseudo class. + # + # @param [AST::Node] arg + # @return [AST::Node] + # + def on_pseudo_class_nth_last_of_type(arg) + position_node = s( + :add, + s(:sub, s(:call, 'last'), s(:call, 'position')), + s(:int, 1) + ) + + return generate_nth_of_type(arg, position_node) + end + + ## + # @param [AST::Node] arg + # @param [AST::Node] position_node + # @return [AST::Node] + # + def generate_nth_of_type(arg, position_node) # literal 2, 4, etc if int_node?(arg) - node = s(:eq, position_call, arg) + node = s(:eq, position_node, arg) else step, offset = *arg compare = step_comparison(step) @@ -450,13 +475,13 @@ end mod_val = step_modulo_value(step) node = s( :and, - s(compare, position_call, offset), - s(:eq, s(:mod, s(:sub, position_call, offset), mod_val), s(:int, 0)) + s(compare, position_node, offset), + s(:eq, s(:mod, s(:sub, position_node, offset), mod_val), s(:int, 0)) ) # 2n, n, -2n else - node = s(:eq, s(:mod, position_call, step), s(:int, 0)) + node = s(:eq, s(:mod, position_node, step), s(:int, 0)) end end diff --git a/spec/oga/css/parser/pseudo_classes/nth_last_of_type_spec.rb b/spec/oga/css/parser/pseudo_classes/nth_last_of_type_spec.rb new file mode 100644 index 0000000..5091acb --- /dev/null +++ b/spec/oga/css/parser/pseudo_classes/nth_last_of_type_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Oga::CSS::Parser do + context ':nth-last-of-type pseudo class' do + example 'parse the :nth-last-of-type(1) pseudo class' do + parse_css(':nth-last-of-type(1)').should == parse_xpath( + 'descendant-or-self::*[(last() - position() + 1) = 1]' + ) + end + + example 'parse the :nth-last-of-type(2n) pseudo class' do + parse_css(':nth-last-of-type(2n)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) mod 2) = 0]' + ) + end + + example 'parse the :nth-last-of-type(3n) pseudo class' do + parse_css(':nth-last-of-type(3n)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) mod 3) = 0]' + ) + end + + example 'parse the :nth-last-of-type(2n+5) pseudo class' do + parse_css(':nth-last-of-type(2n+5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) >= 5) ' \ + 'and ((((last() - position() + 1) - 5) mod 2) = 0)]' + ) + end + + example 'parse the :nth-last-of-type(3n+5) pseudo class' do + parse_css(':nth-last-of-type(3n+5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) >= 5) ' \ + 'and ((((last() - position() + 1) - 5) mod 3) = 0)]' + ) + end + + example 'parse the :nth-last-of-type(2n-5) pseudo class' do + parse_css(':nth-last-of-type(2n-5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) >= 1) ' \ + 'and ((((last() - position() + 1) - 1) mod 2) = 0)]' + ) + end + + example 'parse the :nth-last-of-type(2n-6) pseudo class' do + parse_css(':nth-last-of-type(2n-6)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) >= 2) ' \ + 'and ((((last() - position() + 1) - 2) mod 2) = 0)]' + ) + end + + example 'parse the :nth-last-of-type(-2n+5) pseudo class' do + parse_css(':nth-last-of-type(-2n+5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) <= 5) ' \ + 'and (((last() - position() + 1) - 5) mod 2) = 0]' + ) + end + + example 'parse the :nth-last-of-type(-2n-5) pseudo class' do + parse_css(':nth-last-of-type(-2n-5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) <= -1) ' \ + 'and (((last() - position() + 1) - -1) mod 2) = 0]' + ) + end + + example 'parse the :nth-last-of-type(-2n-6) pseudo class' do + parse_css(':nth-last-of-type(-2n-6)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) <= -2) ' \ + 'and (((last() - position() + 1) - -2) mod 2) = 0]' + ) + end + + example 'parse the :nth-last-of-type(even) pseudo class' do + parse_css(':nth-last-of-type(even)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) mod 2) = 0]' + ) + end + + example 'parse the :nth-last-of-type(odd) pseudo class' do + parse_css(':nth-last-of-type(odd)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) >= 1) ' \ + 'and ((((last() - position() + 1) - 1) mod 2) = 0)]' + ) + end + + example 'parse the :nth-last-of-type(n) pseudo class' do + parse_css(':nth-last-of-type(n)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) mod 1) = 0]' + ) + end + + example 'parse the :nth-last-of-type(n+5) pseudo class' do + parse_css(':nth-last-of-type(n+5)').should == parse_xpath( + 'descendant-or-self::*[(last() - position() + 1) >= 5 ' \ + 'and (((last() - position() + 1) - 5) mod 1) = 0]' + ) + end + + example 'parse the :nth-last-of-type(-n) pseudo class' do + parse_css(':nth-last-of-type(-n)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) mod 1) = 0]' + ) + end + + example 'parse the :nth-last-of-type(-n+5) pseudo class' do + parse_css(':nth-last-of-type(-n+5)').should == parse_xpath( + 'descendant-or-self::*[((last() - position() + 1) <= 5) ' \ + 'and (((last() - position() + 1) - 5) mod 1) = 0]' + ) + end + end +end