Skip to content

Commit

Permalink
refactor: CSS parser AST uses :ATTRIB_NAME nodes consistently
Browse files Browse the repository at this point in the history
All attribute references end up as an :ATTRIB_NAME node without the
"@" present, which simplifies the XPath visitor code.
  • Loading branch information
flavorjones committed Jan 12, 2022
1 parent a1c34b3 commit b5a50f8
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 37 deletions.
34 changes: 17 additions & 17 deletions lib/nokogiri/css/parser.rb
Expand Up @@ -121,32 +121,32 @@ def unescape_css_string(str)
-29, -31, -39, -82, -40, -41, -48, -42, -47 ]

racc_goto_table = [
58, 42, 13, 1, 46, 52, 15, 68, 37, 71,
55, 39, 15, 69, 41, 15, 73, 74, 75, 76,
77, 44, 68, 81, 90, 45, 54, 51, 69, 15,
59, nil, 70, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, 78, 79, nil, nil, 15,
15, nil, nil, 104, nil, nil, nil, nil, nil, nil,
58, 42, 13, 1, 46, 52, 19, 68, 37, 71,
41, 39, 19, 69, 44, 19, 73, 74, 75, 76,
77, 45, 68, 81, 90, 54, 51, 59, 69, 55,
nil, nil, 70, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, 78, 79, nil, nil, 19,
19, nil, nil, 104, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, 112,
nil, 114, 115, nil, 117 ]

racc_goto_check = [
20, 14, 2, 1, 5, 11, 6, 9, 2, 11,
7, 2, 6, 14, 10, 6, 14, 14, 14, 14,
14, 12, 9, 19, 19, 13, 17, 18, 14, 6,
21, nil, 1, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, 2, 2, nil, nil, 6,
6, nil, nil, 14, nil, nil, nil, nil, nil, nil,
20, 14, 2, 1, 5, 11, 7, 9, 2, 11,
10, 2, 7, 14, 12, 7, 14, 14, 14, 14,
14, 13, 9, 19, 19, 17, 18, 21, 14, 7,
nil, nil, 1, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, 2, 2, nil, nil, 7,
7, nil, nil, 14, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, 20,
nil, 20, 20, nil, 20 ]

racc_goto_pointer = [
nil, 3, -1, nil, nil, -16, 3, -16, nil, -21,
-2, -21, 4, 8, -15, nil, nil, 0, 1, -28,
-27, 3, nil, nil, nil, nil ]
nil, 3, -1, nil, nil, -16, nil, 3, nil, -21,
-6, -21, -3, 4, -15, nil, nil, -1, 0, -28,
-27, 0, nil, nil, nil, nil ]

racc_goto_default = [
nil, nil, nil, 2, 3, 9, 63, 19, 20, 16,
nil, nil, nil, 2, 3, 9, 15, 63, 20, 16,
nil, 17, 34, 33, 18, 32, 22, 24, nil, nil,
65, nil, 31, 35, 82, 67 ]

Expand Down Expand Up @@ -400,7 +400,7 @@ def _reduce_8(val, _values, result)
end

def _reduce_9(val, _values, result)
result = val.join
result = val[1]
result
end

Expand Down
6 changes: 3 additions & 3 deletions lib/nokogiri/css/parser.y
Expand Up @@ -22,7 +22,7 @@ rule
;

xpath_attribute_name:
'@' IDENT { result = val.join }
'@' IDENT { result = val[1] }
;

xpath_attribute:
Expand Down Expand Up @@ -149,7 +149,7 @@ rule
NUMBER COMMA expr { result = [val[0], val[2]] }
| STRING COMMA expr { result = [val[0], val[2]] }
| IDENT COMMA expr { result = [val[0], val[2]] }
| xpath_attribute_name COMMA expr { result = [val[0], val[2]] }
| xpath_attribute COMMA expr { result = [val[0], val[2]] }
| NUMBER
| STRING
| IDENT {
Expand All @@ -164,7 +164,7 @@ rule
result = val
end
}
| xpath_attribute_name
| xpath_attribute
;

nth:
Expand Down
22 changes: 8 additions & 14 deletions lib/nokogiri/css/xpath_visitor.rb
Expand Up @@ -128,8 +128,11 @@ def visit_function(node)
is_direct = node.value[1].value[0].nil? # e.g. "has(> a)", "has(~ a)", "has(+ a)"
".#{"//" unless is_direct}#{node.value[1].accept(self)}"
else
# non-standard. this looks like a function call.
args = ["."] + node.value[1..-1]
# xpath function call, let's marshal those arguments
args = ["."]
args += node.value[1..-1].map do |n|
n.is_a?(Nokogiri::CSS::Node) ? n.accept(self) : n
end
"#{node.value.first}#{args.join(",")})"
end
end
Expand All @@ -149,17 +152,8 @@ def visit_id(node)
end

def visit_attribute_condition(node)
attribute = if (node.value.first.type == :FUNCTION) || (node.value.first.value.first =~ /::/)
""
else
"@"
end
attribute += node.value.first.accept(self)

# non-standard. attributes starting with '@'
attribute.gsub!(/^@@/, "@")

return attribute unless node.value.length == 3
attribute = node.value.first.accept(self)
return attribute if node.value.length == 1

value = node.value.last
value = "'#{value}'" unless /^['"]/.match?(value)
Expand Down Expand Up @@ -267,7 +261,7 @@ def visit_element_name(node)
end

def visit_attrib_name(node)
node.value.first
"@#{node.value.first}"
end

def accept(node)
Expand Down
44 changes: 41 additions & 3 deletions test/css/test_parser.rb
Expand Up @@ -24,7 +24,8 @@ class TestNokogiri < Nokogiri::TestCase
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME],
[:PSEUDO_CLASS,
[:FUNCTION],],], ast.to_type
[:FUNCTION],],],
ast.to_type
)
end

Expand All @@ -34,16 +35,53 @@ class TestNokogiri < Nokogiri::TestCase
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME, ["a"]],
[:PSEUDO_CLASS,
[:FUNCTION, ["nth-child("], ["2"]],],], asts.first.to_a
[:FUNCTION, ["nth-child("], ["2"]],],],
asts.first.to_a
)
end

it "parses xpath attributes in conditional selectors" do
ast = parser.parse("a[@class~=bar]").first
assert_equal(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME, ["a"]],
[:ATTRIBUTE_CONDITION,
[:ATTRIB_NAME, ["class"]],
[:includes],
["bar"],],],
ast.to_a
)
end

it "parses xpath attributes" do
ast = parser.parse("a/@href").first
assert_equal(
[:CHILD_SELECTOR, [:ELEMENT_NAME, ["a"]], [:ATTRIB_NAME, ["@href"]]],
[:CHILD_SELECTOR, [:ELEMENT_NAME, ["a"]], [:ATTRIB_NAME, ["href"]]],
ast.to_a
)
end

it "parses xpath attributes passed to xpath functions" do
ast = parser.parse("a:foo(@href)").first
assert_equal(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME, ["a"]],
[:PSEUDO_CLASS,
[:FUNCTION, ["foo("],
[:ATTRIB_NAME, ["href"]],],],],
ast.to_a,
)

ast = parser.parse("a:foo(@href,@id)").first
assert_equal(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME, ["a"]],
[:PSEUDO_CLASS,
[:FUNCTION, ["foo("],
[:ATTRIB_NAME, ["href"]],
[:ATTRIB_NAME, ["id"]],],],],
ast.to_a,
)
end
end
end
2 changes: 2 additions & 0 deletions test/css/test_xpath_visitor.rb
Expand Up @@ -300,7 +300,9 @@ def assert_xpath(expecteds, asts)
assert_xpath("//a[active(.)]", parser.parse("a:active"))

assert_xpath("//a[foo(.,@href)]", parser.parse("a:foo(@href)"))
assert_xpath("//a[foo(.,@href,@id)]", parser.parse("a:foo(@href, @id)"))
assert_xpath("//a[foo(.,@a,b)]", parser.parse("a:foo(@a, b)"))
assert_xpath("//a[foo(.,a,@b)]", parser.parse("a:foo(a, @b)"))
assert_xpath("//a[foo(.,a,10)]", parser.parse("a:foo(a, 10)"))
assert_xpath("//a[foo(.,42)]", parser.parse("a:foo(42)"))
assert_xpath("//a[foo(.,'bar')]", parser.parse("a:foo('bar')"))
Expand Down

0 comments on commit b5a50f8

Please sign in to comment.