Properly fixed AST for first-of-type/last-of-type

This requires keeping track of the current element being processed. This in turn
allows the usage of count() + preceding-sibling/following-sibling.
This commit is contained in:
Yorick Peterse 2014-11-15 17:58:56 +01:00
parent e559b4b89b
commit 1c301d40e2
3 changed files with 54 additions and 9 deletions

View File

@ -43,7 +43,7 @@ rule
# .foo, :bar, etc # .foo, :bar, etc
: predicates : predicates
{ {
s(:predicate, s(:axis, 'descendant', s(:test, nil, '*')), val[0]) s(:predicate, s(:axis, 'descendant', on_test(nil, '*')), val[0])
} }
# foo # foo
@ -82,7 +82,7 @@ rule
[ [
s( s(
:predicate, :predicate,
s(:axis, 'following-sibling', s(:test, nil, '*')), s(:axis, 'following-sibling', on_test(nil, '*')),
s(:int, 1) s(:int, 1)
), ),
s(:axis, 'self', val[1]) s(:axis, 'self', val[1])
@ -97,7 +97,7 @@ rule
node_test node_test
# foo # foo
: node_name { s(:test, *val[0]) } : node_name { on_test(*val[0]) }
; ;
node_name node_name
@ -130,7 +130,7 @@ rule
; ;
attribute attribute
: node_name { s(:axis, 'attribute', s(:test, *val[0])) } : node_name { s(:axis, 'attribute', on_test(*val[0])) }
; ;
# The AST of these operators is mostly based on what # The AST of these operators is mostly based on what
@ -313,6 +313,13 @@ end
@lexer = Lexer.new(data) @lexer = Lexer.new(data)
end end
##
# Resets the internal state of the parser.
#
def reset
@current_element = nil
end
## ##
# @param [Symbol] type # @param [Symbol] type
# @param [Array] children # @param [Array] children
@ -335,6 +342,15 @@ end
yield [false, false] yield [false, false]
end end
##
# Returns the node test for the current element.
#
# @return [AST::Node]
#
def current_element
return @current_element ||= s(:test, nil, '*')
end
## ##
# Parses the input and returns the corresponding AST. # Parses the input and returns the corresponding AST.
# #
@ -345,11 +361,26 @@ end
# @return [AST::Node] # @return [AST::Node]
# #
def parse def parse
reset
ast = yyparse(self, :yield_next_token) ast = yyparse(self, :yield_next_token)
return ast return ast
end end
##
# Generates the AST for a node test.
#
# @param [String] namespace
# @param [String] name
# @return [AST::Node]
#
def on_test(namespace, name)
@current_element = s(:test, namespace, name)
return @current_element
end
## ##
# @param [String] name # @param [String] name
# @param [AST::Node] arg # @param [AST::Node] arg
@ -515,7 +546,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', current_element)),
s(:int, 0)
)
end end
## ##
@ -524,7 +559,11 @@ end
# @return [AST::Node] # @return [AST::Node]
# #
def on_pseudo_class_last_of_type def on_pseudo_class_last_of_type
return s(:eq, s(:call, 'position'), s(:call, 'last')) return s(
:eq,
s(:call, 'count', s(:axis, 'following-sibling', current_element)),
s(:int, 0)
)
end end
## ##

View File

@ -4,13 +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::*) = 0]'
) )
end end
example 'parse the a:first-of-type pseudo class' do example 'parse the a:first-of-type pseudo class' do
parse_css('a:first-of-type').should == parse_xpath( parse_css('a:first-of-type').should == parse_xpath(
'descendant::a[position() = 1]' 'descendant::a[count(preceding-sibling::a) = 0]'
) )
end end
end end

View File

@ -4,7 +4,13 @@ describe Oga::CSS::Parser do
context ':last-of-type pseudo class' do context ':last-of-type pseudo class' do
example 'parse the :last-of-type pseudo class' do example 'parse the :last-of-type pseudo class' do
parse_css(':last-of-type').should == parse_xpath( parse_css(':last-of-type').should == parse_xpath(
'descendant::*[position() = last()]' 'descendant::*[count(following-sibling::*) = 0]'
)
end
example 'parse the a:last-of-type pseudo class' do
parse_css('a:last-of-type').should == parse_xpath(
'descendant::a[count(following-sibling::a) = 0]'
) )
end end
end end