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.
This commit is contained in:
Yorick Peterse 2014-11-14 01:23:42 +01:00
parent e3a26c5d15
commit 0128dc50ae
3 changed files with 28 additions and 15 deletions

View File

@ -515,7 +515,11 @@ end
# @return [AST::Node] # @return [AST::Node]
# #
def on_pseudo_class_first_of_type 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 end
## ##

View File

@ -3,22 +3,25 @@ require 'spec_helper'
describe 'CSS selector evaluation' do describe 'CSS selector evaluation' do
context ':first-of-type pseudo class' do context ':first-of-type pseudo class' do
before do before do
@document = parse('<root><a /><b /></root>') @document = parse(<<-EOF)
<dl>
<dt>foo</dt>
<dd>
<dl>
<dt>bar</dt>
<dd>baz</dd>
</dl>
</dd>
</dl>
EOF
@a1 = @document.children[0].children[0] @dt1 = @document.at_xpath('dl/dt')
@b1 = @document.children[0].children[1] @dt2 = @document.at_xpath('dl/dd/dl/dt')
end end
example 'return a node set containing the first node' do example 'return a node set containing all <dt> nodes' do
evaluate_css(@document, 'root :first-of-type').should == node_set(@a1) evaluate_css(@document, 'dl dt:first-of-type')
end .should == node_set(@dt1, @dt2)
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 <b> node' do
evaluate_css(@document, 'root b:first-of-type').should == node_set(@b1)
end end
end end
end end

View File

@ -4,7 +4,13 @@ describe Oga::CSS::Parser do
context ':first-of-type pseudo class' do context ':first-of-type pseudo class' do
example 'parse the :first-of-type pseudo class' do example 'parse the :first-of-type pseudo class' do
parse_css(':first-of-type').should == parse_xpath( 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
end end