diff --git a/lib/nokogiri/css/parser.rb b/lib/nokogiri/css/parser.rb index 3e7f6211e4..ffc6e96178 100644 --- a/lib/nokogiri/css/parser.rb +++ b/lib/nokogiri/css/parser.rb @@ -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 ] @@ -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 diff --git a/lib/nokogiri/css/parser.y b/lib/nokogiri/css/parser.y index 69a26e8332..af1ba6bad3 100644 --- a/lib/nokogiri/css/parser.y +++ b/lib/nokogiri/css/parser.y @@ -22,7 +22,7 @@ rule ; xpath_attribute_name: - '@' IDENT { result = val.join } + '@' IDENT { result = val[1] } ; xpath_attribute: @@ -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 { @@ -164,7 +164,7 @@ rule result = val end } - | xpath_attribute_name + | xpath_attribute ; nth: diff --git a/lib/nokogiri/css/xpath_visitor.rb b/lib/nokogiri/css/xpath_visitor.rb index 8ac7188c5c..2a7ab074db 100644 --- a/lib/nokogiri/css/xpath_visitor.rb +++ b/lib/nokogiri/css/xpath_visitor.rb @@ -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 @@ -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) @@ -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) diff --git a/test/css/test_parser.rb b/test/css/test_parser.rb index 9c3175002a..4f5ab0fa6c 100644 --- a/test/css/test_parser.rb +++ b/test/css/test_parser.rb @@ -24,7 +24,8 @@ class TestNokogiri < Nokogiri::TestCase [:CONDITIONAL_SELECTOR, [:ELEMENT_NAME], [:PSEUDO_CLASS, - [:FUNCTION],],], ast.to_type + [:FUNCTION],],], + ast.to_type ) end @@ -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 diff --git a/test/css/test_xpath_visitor.rb b/test/css/test_xpath_visitor.rb index dd1e5dada1..d4cb4b1923 100644 --- a/test/css/test_xpath_visitor.rb +++ b/test/css/test_xpath_visitor.rb @@ -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')"))