选择相邻的兄弟元素,而不包括中间的非空白文本节点。

5

假设有以下标记:

<p>
  <code>foo</code><code>bar</code>
  <code>jim</code> and then <code>jam</code>
</p>

我需要选择前三个<code>元素,但不包括最后一个。逻辑是“选择所有具有前面或后面的兄弟元素element,这些兄弟元素也是code元素,除非它们之间存在一个或多个具有非空格内容的文本节点。”
考虑到我正在使用Nokogiri(使用libxml2),因此只能使用XPath 1.0表达式。
虽然期望使用巧妙的XPath表达式,但在Nokogiri文档上执行相同操作的Ruby代码/迭代也可以接受。
请注意,CSS 相邻兄弟选择器会忽略非元素节点,因此选择nokodoc.css('code + code')将错误地选择最后一个<code>块。
Nokogiri.XML('<r><a/><b/> and <c/></r>').css('* + *').map(&:name)
#=> ["b", "c"]

编辑:为了更清晰明了,增加了更多的测试用例:

<section><ul>
  <li>Go to <code>N</code> and
      then <code>Y</code><code>Y</code><code>Y</code>.
  </li>
  <li>If you see <code>N</code> or <code>N</code> then…</li>
</ul>
<p>Elsewhere there might be: <code>N</code></p>
<p><code>N</code> across parents.</p>
<p>Then: <code>Y</code> <code>Y</code><code>Y</code> and <code>N</code>.</p>
<p><code>N</code><br/><code>N</code> elements interrupt, too.</p>
</section>

所有的Y都应该被选中。没有N应该被选中。 <code>标签的内容仅用于指示哪些应该被选择:您不能使用内容来确定是否选择元素。 <code>出现的上下文元素是无关紧要的。它们可能出现在<li>中,也可能出现在<p>中,或者其他地方。
我想一次选择所有连续运行的<code>。在其中一个Y集合的中间有一个空格字符并不是错误。

“非空格”内容使得在xpath中处理这个问题相当棘手。 - MattH
@MattH 我想应该是这样。我几乎可以接受一个禁止任何非元素节点干涉的版本,但我相信当我需要匹配时,我已经看到过它们之间有一个空格的情况。 - Phrogz
在这种情况下,正则表达式可以吗? - Jwosty
@Jwosty 不需要;我已经有了一个 Nokogiri 页面的 DOM,正在操纵它。通过 to_s 回转和重新解析只是为了使用正则表达式操纵 HTML,这会让我感到非常恶心。 ;) - Phrogz
啊,好的。我猜那可能有点复杂... :P - Jwosty
3个回答

4

用途:

//code
     [preceding-sibling::node()[1][self::code]
    or
      preceding-sibling::node()[1]
         [self::text()[not(normalize-space())]]
     and
      preceding-sibling::node()[2][self::code]
    or
     following-sibling::node()[1][self::code]
    or
      following-sibling::node()[1]
         [self::text()[not(normalize-space())]]
     and
      following-sibling::node()[2][self::code]
     ]

XSLT基于验证:

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <xsl:template match="/">
      <xsl:copy-of select=
       "//code
             [preceding-sibling::node()[1][self::code]
            or
              preceding-sibling::node()[1]
                 [self::text()[not(normalize-space())]]
             and
              preceding-sibling::node()[2][self::code]
            or
             following-sibling::node()[1][self::code]
            or
              following-sibling::node()[1]
                 [self::text()[not(normalize-space())]]
             and
              following-sibling::node()[2][self::code]
             ]"/>
     </xsl:template>
</xsl:stylesheet>

当对提供的 XML 文档应用此转换时:
<section><ul>
      <li>Go to <code>N</code> and
          then <code>Y</code><code>Y</code><code>Y</code>.
      </li>
      <li>If you see <code>N</code> or <code>N</code> then…</li>
    </ul>
    <p>Elsewhere there might be: <code>N</code></p>
    <p><code>N</code> across parents.</p>
    <p>Then: <code>Y</code> <code>Y</code><code>Y</code> and <code>N</code>.</p>
    <p><code>N</code><br/><code>N</code> elements interrupt, too.</p>
</section>

该XPath表达式将被评估,并且所选节点将被复制到输出:

<code>Y</code>
<code>Y</code>
<code>Y</code>
<code>Y</code>
<code>Y</code>
<code>Y</code>

出于好奇,/*/code//code的点/好处是什么? - Phrogz
@Phrogz:许多XPath 1.0的实现在评估//someName时非常慢 - 它们遍历整个子树。当我们了解文档的结构时,我们可以指定所需元素的确切路径,这可能会快很多倍。 - Dimitre Novatchev
@Phrogz:我编辑了我的答案,现在表达式简单多了。 - Dimitre Novatchev
这也会错误地选择 <p><br/><code>oops</code></p>。 - pguardiario
@Phrogz:问题没有明确定义,这会导致混淆。根据您的解释,可以有多个“相邻”的code元素组。您是否希望选择所有组中的code元素? - Dimitre Novatchev
显示剩余6条评论

3
//code[
  (
    following-sibling::node()[1][self::code]
    or (
      following-sibling::node()[1][self::text() and normalize-space() = ""]
      and
      following-sibling::node()[2][self::code]
    )
  )
  or (
    preceding-sibling::node()[1][self::code]
    or (
      preceding-sibling::node()[1][self::text() and normalize-space() = ""]
      and
      preceding-sibling::node()[2][self::code]
    )
  )
]

认为这个可以满足你的需求,不过我不会声称你真的想使用它。

我假设文本节点总是合并在一起,因此不会出现两个相邻的文本节点,尽管我相信这通常是情况,但如果您事先进行DOM操作,则可能不是情况。我还假设在code元素之间不会有任何其他元素,或者如果有其他元素,则像非空白文本一样防止选择。


1UP:同意我认为OP想要的,并且比我一直在试探的xpath更加简洁。 - MattH

1

我认为这就是你想要的:

/p/code[not(preceding-sibling::text()[not(normalize-space(.)="")])]

这将错误地选择<p><br/><code>oops</code></p> - Phrogz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接