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:
parent
e559b4b89b
commit
1c301d40e2
|
@ -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
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue