diff --git a/lib/nokogiri/css/node.rb b/lib/nokogiri/css/node.rb index c54402660a1..a650b557040 100644 --- a/lib/nokogiri/css/node.rb +++ b/lib/nokogiri/css/node.rb @@ -1,6 +1,8 @@ module Nokogiri module CSS class Node + ALLOW_COMBINATOR_ON_SELF = [:DIRECT_ADJACENT_SELECTOR, :FOLLOWING_SELECTOR, :CHILD_SELECTOR] + # Get the type of this node attr_accessor :type # Get the value of this node @@ -21,6 +23,7 @@ def accept visitor # Convert this CSS node to xpath with +prefix+ using +visitor+ def to_xpath prefix = '//', visitor = XPathVisitor.new self.preprocess! + prefix = '.' if ALLOW_COMBINATOR_ON_SELF.include?(type) && value.first.nil? prefix + visitor.accept(self) end diff --git a/lib/nokogiri/css/parser.y b/lib/nokogiri/css/parser.y index 351cba83886..9037312dfcd 100644 --- a/lib/nokogiri/css/parser.y +++ b/lib/nokogiri/css/parser.y @@ -9,12 +9,13 @@ rule : selector COMMA simple_selector_1toN { result = [val.first, val.last].flatten } + | prefixless_combinator_selector { result = val.flatten } | simple_selector_1toN { result = val.flatten } ; combinator : PLUS { result = :DIRECT_ADJACENT_SELECTOR } | GREATER { result = :CHILD_SELECTOR } - | TILDE { result = :PRECEDING_SELECTOR } + | TILDE { result = :FOLLOWING_SELECTOR } | S { result = :DESCENDANT_SELECTOR } | DOUBLESLASH { result = :DESCENDANT_SELECTOR } | SLASH { result = :CHILD_SELECTOR } @@ -59,6 +60,11 @@ rule ) } ; + prefixless_combinator_selector + : combinator simple_selector_1toN { + result = Node.new(val.first, [nil, val.last]) + } + ; simple_selector_1toN : simple_selector combinator simple_selector_1toN { result = Node.new(val[1], [val.first, val.last]) diff --git a/lib/nokogiri/css/xpath_visitor.rb b/lib/nokogiri/css/xpath_visitor.rb index d2c800d082d..0aac67a9662 100644 --- a/lib/nokogiri/css/xpath_visitor.rb +++ b/lib/nokogiri/css/xpath_visitor.rb @@ -126,13 +126,13 @@ def visit_class_condition node { 'combinator' => ' and ', 'direct_adjacent_selector' => "/following-sibling::*[1]/self::", - 'preceding_selector' => "/following-sibling::", + 'following_selector' => "/following-sibling::", 'descendant_selector' => '//', 'child_selector' => '/', }.each do |k,v| class_eval %{ def visit_#{k} node - "\#{node.value.first.accept(self)}#{v}\#{node.value.last.accept(self)}" + "\#{node.value.first.accept(self) if node.value.first}#{v}\#{node.value.last.accept(self)}" end } end diff --git a/test/css/test_parser.rb b/test/css/test_parser.rb index 6c443c9656d..031e52cd7f9 100644 --- a/test/css/test_parser.rb +++ b/test/css/test_parser.rb @@ -214,6 +214,21 @@ def test_direct_preceding_selector @parser.parse("E + F G") end + def test_prefixless_child_selector + assert_xpath("./a", @parser.parse('>a')) + assert_xpath("./a//b/i", @parser.parse('>a b>i')) + end + + def test_prefixless_preceding_sibling_selector + assert_xpath("./following-sibling::a", @parser.parse('~a')) + assert_xpath("./following-sibling::a//b/following-sibling::i", @parser.parse('~a b~i')) + end + + def test_prefixless_direct_adjacent_selector + assert_xpath("./following-sibling::*[1]/self::a", @parser.parse('+a')) + assert_xpath("./following-sibling::*[1]/self::a/following-sibling::*[1]/self::b", @parser.parse('+a+b')) + end + def test_attribute assert_xpath "//h1[@a = 'Tender Lovemaking']", @parser.parse("h1[a='Tender Lovemaking']")