【问题标题】::has CSS pseudo class in Nokogiri: 在 Nokogiri 中有 CSS 伪类
【发布时间】:2012-07-30 09:41:20
【问题描述】:

我正在寻找Nokogiri 中的伪类:has。 它应该像 jQuery 的 has selector 一样工作。

例如:

<li><h1><a href="dfd">ex1</a></h1><span class="string">sdfsdf</span></li>
<li><h1><a href="dsfsdf">ex2</a></h1><span class="string"></span></li>
<li><h1><a href="sdfd">ex3</a></h1></li>

CSS 选择器应该只返回第一个链接,即具有非空span.string 兄弟的链接。

在 jQuery 中这个选择器效果很好:

$('li:has(span.string:not(:empty))>h1>a')

但不是在 Nokogiri:

Nokogiri::HTML(html_source).css('li:has(span.string:not(:empty))>h1>a')

:not:empty 效果很好,但不是 :has


  1. Nokogiri 中是否有任何关于 CSS 选择器的文档?
  2. 也许有人可以编写一个自定义的:has 伪类?这是example 如何编写:regexp 选择器。
  3. 我可以选择使用 XPath。如何为li:has(span.string:not(:empty))&gt;h1&gt;a 编写 XPath?

【问题讨论】:

  • :has pseudo 是一个 jQuery 扩展,所以我猜 Nokogiri 不支持它,因为它不是任何标准的一部分。
  • 好的,所以我的最后 3 个问题是合适的
  • 鉴于您提供的 HTML,//li[span[@class="string"][count(node()) &gt; 0]]/h1/a 会返回包含 ex1 内容的节点(第一个 a)。
  • @MisterJack,nokogiri 支持:has 选择器,但它无法正常工作,请查看my answer below。感谢 xpath 示例:)

标签: jquery css ruby-on-rails ruby nokogiri


【解决方案1】:

好的,我找到了一个可能对某人有用的解决方案。

自定义伪类:custom_has:

class MyCustomSelectors
  def custom_has node_set, selector
      node_set.find_all { |node| node.css(selector).present? }
  end
end

#usage:
doc.css('li:custom_has(span.string:not(:empty))>h1>a',MyCustomSelectors.new)

为什么我声明 :custom_has 而不仅仅是 :has?因为已经声明过了。 在 Nokogiri 存储库中,tests 用于 :has 选择器,但它们不起作用。我reported this issue给作者。

【讨论】:

    【解决方案2】:

    Nokogiri 允许在同一对象上链接 .css().xpath() 调用。因此,任何时候您想使用:has,只需结束当前的.css() 调用并添加.xpath(..)(父选择器)即可。您甚至可以从您的xpath() 停止的地方开始另一个.css() 呼叫来恢复您的选择!

    示例:

    这是来自维基百科的一些 HTML:

    <tr>
        <th scope="row" style="text-align:left;">
            Origin
        </th>
        <td>
            <a href="/wiki/Edinburgh" title="Edinburgh">Edinburgh</a>
            <a href="/wiki/Scotland" title="Scotland">Scotland</a>
        </td>
    </tr>
    <tr>
        <th scope="row" style="text-align:left;">
            <a href="/wiki/Music_genre" title="Music genre">Genres</a>
        </th>
        <td>
            <a href="/wiki/Electronica" title="Electronica">Electronica</a>
            <a href="/wiki/Intelligent_dance_music" title="Intelligent dance music">IDM</a>
            <a href="/wiki/Ambient_music" title="Ambient music">ambient</a>
            <a href="/wiki/Downtempo" title="Downtempo">downtempo</a>
            <a href="/wiki/Trip_hop" title="Trip hop">trip hop</a>
        </td>
    </tr>
    <tr>
        <th scope="row" style="text-align:left;">
            <a href="/wiki/Record_label" title="Record label">Labels</a>
        </th>
        <td>
            <a href="/wiki/Warp_(record_label)" title="Warp (record label)">Warp</a>
            <a href="/wiki/Skam_Records" title="Skam Records">Skam</a>
            <a href="/wiki/Music70" title="Music70">Music70</a>
        </td>
    </tr>
    

    假设您要选择包含href="/Music_genre" 链接的&lt;th&gt; 之后的第一个&lt;td&gt; 内的所有&lt;a&gt; 元素。

    @artistPage.css("table th > a[href='/wiki/Music_genre']").xpath("..").css("+ td a")
    

    这将返回每个流派列表的所有&lt;a&gt;

    现在,让我们获取所有&lt;a&gt; 的内部文本并将它们放入一个数组中。

    @genreLinks = @artistPage.css("table th > a[href='/wiki/Music_genre']").xpath("..").css("+ td a")
    @genres = []
    @genreLinks.each do |genreLink|
      @genres.push(genreLink.text)
    end
    

    【讨论】:

      【解决方案3】:

      problem with Nokogiri's current implementation of :has() 是它创建的 XPath 要求内容是直接子代,而不是任何后代:

      puts Nokogiri::CSS.xpath_for( "a:has(b)" )
      #=> "//a[b]"
      #=> Should output "//a[.//b]" to be correct
      

      要使这个 XPath 与 jQuery 的功能相匹配,您需要允许 span 成为后代元素。例如:

      require 'nokogiri'
      d = Nokogiri.XML('<r><a/><a><b><c/></b></a></r>')
      d.at_css('a:has(b)')    #=> #<Nokogiri::XML::Element:0x14dd608 name="a" children=[#<Nokogiri::XML::Element:0x14dd3e0 name="b" children=[#<Nokogiri::XML::Element:0x14dd20c name="c">]>]>
      d.at_css('a:has(c)')    #=> nil
      d.at_xpath('//a[.//c]') #=> #<Nokogiri::XML::Element:0x14dd608 name="a" children=[#<Nokogiri::XML::Element:0x14dd3e0 name="b" children=[#<Nokogiri::XML::Element:0x14dd20c name="c">]>]>
      

      对于您的具体情况,这里是完整的“损坏”XPath:

      puts Nokogiri::CSS.xpath_for( "li:has(span.string:not(:empty)) > h1 > a" )
      #=> //li[span[contains(concat(' ', @class, ' '), ' string ') and not(not(node()))]]/h1/a
      

      这里是固定的:

      # Adding just the .//
      //li[.//span[contains(concat(' ', @class, ' '), ' string ') and not(not(node()))]]/h1/a
      
      # Simplified to assume only one CSS class is present on the span
      //li[.//span[@class='string' and not(not(node()))]]/h1/a
      
      # Assuming that `not(:empty)` really meant "Has some text in it"
      //li[.//span[@class='string' and text()]]/h1/a
      
      # ..or maybe you really wanted "Has some text anywhere underneath"
      //li[.//span[@class='string' and .//text()]]/h1/a
      
      # ..or maybe you really wanted "Has at least one element child"
      //li[.//span[@class='string' and *]]/h1/a
      

      【讨论】:

        【解决方案4】:

        Nokogiri 没有:has 选择器,这是关于它所做 作用的文档: http://ruby.bastardsbook.com/chapters/html-parsing/#h-2-2

        【讨论】:

        • 在您的链接中,只是一些如何使用它的示例,而不是 nokogiri css 选择器文档。
        • 这是最具描述性和解释性的,如果您想了解我的意思,请查看Nokogiri's Documentation
        猜你喜欢
        • 2011-05-13
        • 1970-01-01
        • 2017-08-08
        • 1970-01-01
        • 1970-01-01
        • 2011-12-25
        • 2011-09-21
        • 2012-12-21
        相关资源
        最近更新 更多