Skip to content

Commit

Permalink
Merge pull request #886 from Mange/negative-nth
Browse files Browse the repository at this point in the history
Add support for an-b in nth selectors
  • Loading branch information
leejarvis committed Jun 14, 2013
2 parents 11eaf3d + 5e62dfa commit d24eb85
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 28 deletions.
26 changes: 17 additions & 9 deletions lib/nokogiri/css/parser.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 17 additions & 11 deletions lib/nokogiri/css/parser.y
Expand Up @@ -130,7 +130,7 @@ rule
| FUNCTION expr RPAREN {
result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
}
| FUNCTION an_plus_b RPAREN {
| FUNCTION nth RPAREN {
result = Node.new(:FUNCTION, [val.first.strip, val[1]].flatten)
}
| NOT expr RPAREN {
Expand All @@ -150,10 +150,10 @@ rule
{
if val[0] == 'even'
val = ["2","n","+","0"]
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
elsif val[0] == 'odd'
val = ["2","n","+","1"]
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
else
# This is not CSS standard. It allows us to support this:
# assert_xpath("//a[foo(., @href)]", @parser.parse('a:foo(@href)'))
Expand All @@ -163,33 +163,39 @@ rule
end
}
;
an_plus_b
nth
: NUMBER IDENT PLUS NUMBER # 5n+3 -5n+3
{
if val[1] == 'n'
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
else
raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
end
}
| IDENT PLUS NUMBER { # n+3, -n+3
if val[0] == 'n'
val.unshift("1")
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
elsif val[0] == '-n'
val[0] = 'n'
val.unshift("-1")
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
else
raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
end
}
| NUMBER IDENT # 5n, -5n
{
if val[1] == 'n'
| NUMBER IDENT { # 5n, -5n, 10n-1
n = val[1]
if n[0, 2] == 'n-'
val[1] = 'n'
val << "-"
# b is contained in n as n is the string "n-b"
val << n[2, n.size]
result = Node.new(:NTH, val)
elsif n == 'n'
val << "+"
val << "0"
result = Node.new(:AN_PLUS_B, val)
result = Node.new(:NTH, val)
else
raise Racc::ParseError, "parse error on IDENT '#{val[1]}'"
end
Expand Down
26 changes: 19 additions & 7 deletions lib/nokogiri/css/xpath_visitor.rb
Expand Up @@ -14,14 +14,14 @@ def visit_function node
when /^eq\(/
"position() = #{node.value[1]}"
when /^(nth|nth-of-type|nth-child)\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :AN_PLUS_B
an_plus_b(node.value[1])
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1])
else
"position() = #{node.value[1]}"
end
when /^(nth-last-child|nth-last-of-type)\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :AN_PLUS_B
an_plus_b(node.value[1], :last => true)
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1], :last => true)
else
index = node.value[1].to_i - 1
index == 0 ? "position() = last()" : "position() = last() - #{index}"
Expand Down Expand Up @@ -151,11 +151,10 @@ def accept node
end

private
def an_plus_b node, options={}
def nth node, options={}
raise ArgumentError, "expected an+b node to contain 4 tokens, but is #{node.value.inspect}" unless node.value.size == 4

a = node.value[0].to_i
b = node.value[3].to_i
a, b = read_a_and_positive_b node.value
position = options[:last] ? "(last()-position()+1)" : "position()"

if (b == 0)
Expand All @@ -166,6 +165,19 @@ def an_plus_b node, options={}
end
end

def read_a_and_positive_b values
op = values[2]
if op == "+"
a = values[0].to_i
b = values[3].to_i
elsif op == "-"
a = values[0].to_i
b = a - (values[3].to_i % a)
else
raise ArgumentError, "expected an+b node to have either + or - as the operator, but is #{op.inspect}"
end
[a, b]
end
end
end
end
4 changes: 4 additions & 0 deletions test/css/test_nthiness.rb
Expand Up @@ -74,6 +74,10 @@ def test_mnp3
assert_result_rows [1,2,3], @parser.search("table/tr:nth(-n+3)")
end

def test_4nm1
assert_result_rows [3,7,11], @parser.search("table/tr:nth(4n-1)")
end

def test_np3
assert_result_rows [3,4,5,6,7,8,9,10,11,12,13,14], @parser.search("table/tr:nth(n+3)")
end
Expand Down
2 changes: 1 addition & 1 deletion test/css/test_tokenizer.rb
Expand Up @@ -152,7 +152,7 @@ def test_scan_function_selector
], @scanner)
end

def test_scan_an_plus_b
def test_scan_nth
@scanner.scan('x:nth-child(5n+3)')
assert_tokens([ [:IDENT, 'x'],
[':', ':'],
Expand Down

0 comments on commit d24eb85

Please sign in to comment.