From b46481557785b15d405d10bbe905f26fa548c01c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Sat, 15 Nov 2014 18:27:15 +0100 Subject: [PATCH] Fixed AST generation for nth-(first|last)-of-type. --- lib/oga/css/parser.y | 48 +------ .../pseudo_classes/nth_last_of_type_spec.rb | 120 ++++++++---------- .../parser/pseudo_classes/nth_of_type_spec.rb | 119 ++++++++--------- 3 files changed, 108 insertions(+), 179 deletions(-) diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index 2d270e2..e8d5675 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -424,10 +424,11 @@ end ## # @param [String] count_axis # @param [AST::Node] arg + # @param [AST::Node] count_test # @return [AST::Node] # - def generate_nth_child(count_axis, arg) - count_call = s(:call, 'count', s(:axis, count_axis, s(:test, nil, '*'))) + def generate_nth_child(count_axis, arg, count_test = s(:test, nil, '*')) + count_call = s(:call, 'count', s(:axis, count_axis, count_test)) # literal 2, 4, etc if int_node?(arg) @@ -462,9 +463,7 @@ end # @return [AST::Node] # def on_pseudo_class_nth_of_type(arg) - position_node = s(:call, 'position') - - return generate_nth_of_type(arg, position_node) + return generate_nth_child('preceding-sibling', arg, current_element) end ## @@ -474,44 +473,7 @@ end # @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_node, arg) - else - step, offset = *arg - compare = step_comparison(step) - - # 2n+2, 2n-4, etc - if offset - mod_val = step_modulo_value(step) - node = s( - :and, - 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_node, step), s(:int, 0)) - end - end - - return node + return generate_nth_child('following-sibling', arg, current_element) 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 index 15d3d28..f2024dd 100644 --- 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 @@ -2,109 +2,93 @@ require 'spec_helper' describe Oga::CSS::Parser do context ':nth-last-of-type pseudo class' do + example 'parse the x:nth-last-of-type(1) pseudo class' do + parse_css('x:nth-last-of-type(1)').should == parse_xpath( + 'descendant::x[count(following-sibling::x) = 0]' + ) + end + example 'parse the :nth-last-of-type(1) pseudo class' do parse_css(':nth-last-of-type(1)').should == parse_xpath( - 'descendant::*[(last() - position() + 1) = 1]' + 'descendant::*[count(following-sibling::*) = 0]' ) end - example 'parse the :nth-last-of-type(2n) pseudo class' do - parse_css(':nth-last-of-type(2n)').should == parse_xpath( - 'descendant::*[((last() - position() + 1) mod 2) = 0]' + example 'parse the :nth-last-of-type(2) pseudo class' do + parse_css(':nth-last-of-type(2)').should == parse_xpath( + 'descendant::*[count(following-sibling::*) = 1]' ) end - example 'parse the :nth-last-of-type(3n) pseudo class' do - parse_css(':nth-last-of-type(3n)').should == parse_xpath( - 'descendant::*[((last() - position() + 1) mod 3) = 0]' + example 'parse the x:nth-last-of-type(even) pseudo class' do + parse_css('x:nth-last-of-type(even)').should == parse_xpath( + 'descendant::x[((count(following-sibling::x) + 1) 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::*[((last() - position() + 1) >= 5) ' \ - 'and ((((last() - position() + 1) - 5) mod 2) = 0)]' + example 'parse the x:nth-last-of-type(odd) pseudo class' do + parse_css('x:nth-last-of-type(odd)').should == parse_xpath( + 'descendant::x[(count(following-sibling::x) + 1) >= 1 ' \ + 'and (((count(following-sibling::x) + 1) - 1) 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::*[((last() - position() + 1) >= 5) ' \ - 'and ((((last() - position() + 1) - 5) mod 3) = 0)]' + example 'parse the x:nth-last-of-type(n) pseudo class' do + parse_css('x:nth-last-of-type(n)').should == parse_xpath( + 'descendant::x[((count(following-sibling::x) + 1) mod 1) = 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::*[((last() - position() + 1) >= 1) ' \ - 'and ((((last() - position() + 1) - 1) mod 2) = 0)]' + example 'parse the x:nth-last-of-type(-n) pseudo class' do + parse_css('x:nth-last-of-type(-n)').should == parse_xpath( + 'descendant::x[((count(following-sibling::x) + 1) mod 1) = 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::*[((last() - position() + 1) >= 2) ' \ - 'and ((((last() - position() + 1) - 2) mod 2) = 0)]' + example 'parse the x:nth-last-of-type(-n+6) pseudo class' do + parse_css('x:nth-last-of-type(-n+6)').should == parse_xpath( + 'descendant::x[((count(following-sibling::x) + 1) <= 6) ' \ + 'and (((count(following-sibling::x) + 1) - 6) mod 1) = 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::*[((last() - position() + 1) <= 5) ' \ - 'and (((last() - position() + 1) - 5) mod 2) = 0]' + example 'parse the x:nth-last-of-type(n+5) pseudo class' do + parse_css('x:nth-last-of-type(n+5)').should == parse_xpath( + 'descendant::x[(count(following-sibling::x) + 1) >= 5 ' \ + 'and (((count(following-sibling::x) + 1) - 5) mod 1) = 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::*[((last() - position() + 1) <= -1) ' \ - 'and (((last() - position() + 1) - -1) mod 2) = 0]' + example 'parse the x:nth-last-of-type(2n) pseudo class' do + parse_css('x:nth-last-of-type(2n)') + .should == parse_css('x:nth-last-of-type(even)') + end + + example 'parse the x:nth-last-of-type(2n+1) pseudo class' do + parse_css('x:nth-last-of-type(2n+1)').should == parse_xpath( + 'descendant::x[(count(following-sibling::x) + 1) >= 1 ' \ + 'and (((count(following-sibling::x) + 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::*[((last() - position() + 1) <= -2) ' \ - 'and (((last() - position() + 1) - -2) mod 2) = 0]' + example 'parse the x:nth-last-of-type(3n+1) pseudo class' do + parse_css('x:nth-last-of-type(3n+1)').should == parse_xpath( + 'descendant::x[(count(following-sibling::x) + 1) >= 1 ' \ + 'and (((count(following-sibling::x) + 1) - 1) mod 3) = 0]' ) end - example 'parse the :nth-last-of-type(even) pseudo class' do - parse_css(':nth-last-of-type(even)').should == parse_xpath( - 'descendant::*[((last() - position() + 1) mod 2) = 0]' + example 'parse the x:nth-last-of-type(2n-6) pseudo class' do + parse_css('x:nth-last-of-type(2n-6)').should == parse_xpath( + 'descendant::x[(count(following-sibling::x) + 1) >= 2 ' \ + 'and (((count(following-sibling::x) + 1) - 2) 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::*[((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::*[((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::*[(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::*[((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::*[((last() - position() + 1) <= 5) ' \ - 'and (((last() - position() + 1) - 5) mod 1) = 0]' + example 'parse the x:nth-last-of-type(-2n+6) pseudo class' do + parse_css('x:nth-last-of-type(-2n+6)').should == parse_xpath( + 'descendant::x[((count(following-sibling::x) + 1) <= 6) ' \ + 'and (((count(following-sibling::x) + 1) - 6) mod 2) = 0]' ) end end diff --git a/spec/oga/css/parser/pseudo_classes/nth_of_type_spec.rb b/spec/oga/css/parser/pseudo_classes/nth_of_type_spec.rb index f6238af..d816c80 100644 --- a/spec/oga/css/parser/pseudo_classes/nth_of_type_spec.rb +++ b/spec/oga/css/parser/pseudo_classes/nth_of_type_spec.rb @@ -2,109 +2,92 @@ require 'spec_helper' describe Oga::CSS::Parser do context ':nth-of-type pseudo class' do + example 'parse the x:nth-of-type(1) pseudo class' do + parse_css('x:nth-of-type(1)').should == parse_xpath( + 'descendant::x[count(preceding-sibling::x) = 0]' + ) + end + example 'parse the :nth-of-type(1) pseudo class' do parse_css(':nth-of-type(1)').should == parse_xpath( - 'descendant::*[position() = 1]' + 'descendant::*[count(preceding-sibling::*) = 0]' ) end - example 'parse the :nth-of-type(2n) pseudo class' do - parse_css(':nth-of-type(2n)').should == parse_xpath( - 'descendant::*[(position() mod 2) = 0]' + example 'parse the :nth-of-type(2) pseudo class' do + parse_css(':nth-of-type(2)').should == parse_xpath( + 'descendant::*[count(preceding-sibling::*) = 1]' ) end - example 'parse the :nth-of-type(3n) pseudo class' do - parse_css(':nth-of-type(3n)').should == parse_xpath( - 'descendant::*[(position() mod 3) = 0]' + example 'parse the x:nth-of-type(even) pseudo class' do + parse_css('x:nth-of-type(even)').should == parse_xpath( + 'descendant::x[((count(preceding-sibling::x) + 1) mod 2) = 0]' ) end - example 'parse the :nth-of-type(2n+5) pseudo class' do - parse_css(':nth-of-type(2n+5)').should == parse_xpath( - 'descendant::*[(position() >= 5) ' \ - 'and (((position() - 5) mod 2) = 0)]' + example 'parse the x:nth-of-type(odd) pseudo class' do + parse_css('x:nth-of-type(odd)').should == parse_xpath( + 'descendant::x[(count(preceding-sibling::x) + 1) >= 1 ' \ + 'and (((count(preceding-sibling::x) + 1) - 1) mod 2) = 0]' ) end - example 'parse the :nth-of-type(3n+5) pseudo class' do - parse_css(':nth-of-type(3n+5)').should == parse_xpath( - 'descendant::*[(position() >= 5) ' \ - 'and (((position() - 5) mod 3) = 0)]' + example 'parse the x:nth-of-type(n) pseudo class' do + parse_css('x:nth-of-type(n)').should == parse_xpath( + 'descendant::x[((count(preceding-sibling::x) + 1) mod 1) = 0]' ) end - example 'parse the :nth-of-type(2n-5) pseudo class' do - parse_css(':nth-of-type(2n-5)').should == parse_xpath( - 'descendant::*[(position() >= 1) ' \ - 'and (((position() - 1) mod 2) = 0)]' + example 'parse the x:nth-of-type(-n) pseudo class' do + parse_css('x:nth-of-type(-n)').should == parse_xpath( + 'descendant::x[((count(preceding-sibling::x) + 1) mod 1) = 0]' ) end - example 'parse the :nth-of-type(2n-6) pseudo class' do - parse_css(':nth-of-type(2n-6)').should == parse_xpath( - 'descendant::*[(position() >= 2) ' \ - 'and (((position() - 2) mod 2) = 0)]' + example 'parse the x:nth-of-type(-n+6) pseudo class' do + parse_css('x:nth-of-type(-n+6)').should == parse_xpath( + 'descendant::x[((count(preceding-sibling::x) + 1) <= 6) ' \ + 'and (((count(preceding-sibling::x) + 1) - 6) mod 1) = 0]' ) end - example 'parse the :nth-of-type(-2n+5) pseudo class' do - parse_css(':nth-of-type(-2n+5)').should == parse_xpath( - 'descendant::*[(position() <= 5) ' \ - 'and ((position() - 5) mod 2) = 0]' + example 'parse the x:nth-of-type(n+5) pseudo class' do + parse_css('x:nth-of-type(n+5)').should == parse_xpath( + 'descendant::x[(count(preceding-sibling::x) + 1) >= 5 ' \ + 'and (((count(preceding-sibling::x) + 1) - 5) mod 1) = 0]' ) end - example 'parse the :nth-of-type(-2n-5) pseudo class' do - parse_css(':nth-of-type(-2n-5)').should == parse_xpath( - 'descendant::*[(position() <= -1) ' \ - 'and ((position() - -1) mod 2) = 0]' + example 'parse the x:nth-of-type(2n) pseudo class' do + parse_css('x:nth-of-type(2n)').should == parse_css('x:nth-of-type(even)') + end + + example 'parse the x:nth-of-type(2n+1) pseudo class' do + parse_css('x:nth-of-type(2n+1)').should == parse_xpath( + 'descendant::x[(count(preceding-sibling::x) + 1) >= 1 ' \ + 'and (((count(preceding-sibling::x) + 1) - 1) mod 2) = 0]' ) end - example 'parse the :nth-of-type(-2n-6) pseudo class' do - parse_css(':nth-of-type(-2n-6)').should == parse_xpath( - 'descendant::*[(position() <= -2) ' \ - 'and ((position() - -2) mod 2) = 0]' + example 'parse the x:nth-of-type(3n+1) pseudo class' do + parse_css('x:nth-of-type(3n+1)').should == parse_xpath( + 'descendant::x[(count(preceding-sibling::x) + 1) >= 1 ' \ + 'and (((count(preceding-sibling::x) + 1) - 1) mod 3) = 0]' ) end - example 'parse the :nth-of-type(even) pseudo class' do - parse_css(':nth-of-type(even)').should == parse_xpath( - 'descendant::*[(position() mod 2) = 0]' + example 'parse the x:nth-of-type(2n-6) pseudo class' do + parse_css('x:nth-of-type(2n-6)').should == parse_xpath( + 'descendant::x[(count(preceding-sibling::x) + 1) >= 2 ' \ + 'and (((count(preceding-sibling::x) + 1) - 2) mod 2) = 0]' ) end - example 'parse the :nth-of-type(odd) pseudo class' do - parse_css(':nth-of-type(odd)').should == parse_xpath( - 'descendant::*[(position() >= 1) ' \ - 'and (((position() - 1) mod 2) = 0)]' - ) - end - - example 'parse the :nth-of-type(n) pseudo class' do - parse_css(':nth-of-type(n)').should == parse_xpath( - 'descendant::*[(position() mod 1) =0]' - ) - end - - example 'parse the :nth-of-type(n+5) pseudo class' do - parse_css(':nth-of-type(n+5)').should == parse_xpath( - 'descendant::*[position() >= 5 ' \ - 'and ((position() - 5) mod 1) = 0]' - ) - end - - example 'parse the :nth-of-type(-n) pseudo class' do - parse_css(':nth-of-type(-n)').should == parse_xpath( - 'descendant::*[(position() mod 1) = 0]' - ) - end - - example 'parse the :nth-of-type(-n+5) pseudo class' do - parse_css(':nth-of-type(-n+5)').should == parse_xpath( - 'descendant::*[(position() <= 5) ' \ - 'and ((position() - 5) mod 1) = 0]' + example 'parse the x:nth-of-type(-2n+6) pseudo class' do + parse_css('x:nth-of-type(-2n+6)').should == parse_xpath( + 'descendant::x[((count(preceding-sibling::x) + 1) <= 6) ' \ + 'and (((count(preceding-sibling::x) + 1) - 6) mod 2) = 0]' ) end end