From 0128dc50ae35bde86689e5c0a4ea17e047571fa6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 14 Nov 2014 01:23:42 +0100 Subject: [PATCH] Fixed CSS evaluation of :first-of-type The old XPath "position() = 1" would work in Nokogiri due to the way they retrieve descendants. In Oga however this would simply always return the first node. To fix this Oga now counts the amount of preceding siblings that match the same full name. --- lib/oga/css/parser.y | 6 +++- .../pseudo_classes/first_of_type_spec.rb | 29 ++++++++++--------- .../pseudo_classes/first_of_type_spec.rb | 8 ++++- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index b7f5e8f..09e5b56 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -515,7 +515,11 @@ end # @return [AST::Node] # def on_pseudo_class_first_of_type - return s(:eq, s(:call, 'position'), s(:int, 1)) + return s( + :eq, + s(:call, 'count', s(:axis, 'preceding-sibling', s(:call, 'name'))), + s(:int, 0) + ) end ## diff --git a/spec/oga/css/evaluator/pseudo_classes/first_of_type_spec.rb b/spec/oga/css/evaluator/pseudo_classes/first_of_type_spec.rb index 8315926..e9bcdde 100644 --- a/spec/oga/css/evaluator/pseudo_classes/first_of_type_spec.rb +++ b/spec/oga/css/evaluator/pseudo_classes/first_of_type_spec.rb @@ -3,22 +3,25 @@ require 'spec_helper' describe 'CSS selector evaluation' do context ':first-of-type pseudo class' do before do - @document = parse('') + @document = parse(<<-EOF) +
+
foo
+
+
+
bar
+
baz
+
+
+
+ EOF - @a1 = @document.children[0].children[0] - @b1 = @document.children[0].children[1] + @dt1 = @document.at_xpath('dl/dt') + @dt2 = @document.at_xpath('dl/dd/dl/dt') end - example 'return a node set containing the first node' do - evaluate_css(@document, 'root :first-of-type').should == node_set(@a1) - end - - example 'return a node set containing the first node with a node test' do - evaluate_css(@document, 'root a:first-of-type').should == node_set(@a1) - end - - example 'return a node set containing the first node' do - evaluate_css(@document, 'root b:first-of-type').should == node_set(@b1) + example 'return a node set containing all
nodes' do + evaluate_css(@document, 'dl dt:first-of-type') + .should == node_set(@dt1, @dt2) end end end diff --git a/spec/oga/css/parser/pseudo_classes/first_of_type_spec.rb b/spec/oga/css/parser/pseudo_classes/first_of_type_spec.rb index 5e903a8..ae88987 100644 --- a/spec/oga/css/parser/pseudo_classes/first_of_type_spec.rb +++ b/spec/oga/css/parser/pseudo_classes/first_of_type_spec.rb @@ -4,7 +4,13 @@ describe Oga::CSS::Parser do context ':first-of-type pseudo class' do example 'parse the :first-of-type pseudo class' do parse_css(':first-of-type').should == parse_xpath( - 'descendant::*[position() = 1]' + 'descendant::*[count(preceding-sibling::name()) = 0]' + ) + end + + example 'parse the a:first-of-type pseudo class' do + parse_css('a:first-of-type').should == parse_xpath( + 'descendant::a[count(preceding-sibling::name()) = 0]' ) end end