XPath选择前面的元素与可选的插入空白文本节点

Phr*_*ogz 3 ruby xpath nokogiri

给定一个元素作为上下文我想选择前面的兄弟元素并检查它是否具有特定的名称.需要注意的是,如果存在具有非空白内容的插入文本节点,我不想选择它.

例如,给定这个XML文档......

<r>
  <a>a1</a><a>a2</a>
   b
  <a>a3</a>
    <a>a4</a>
  <b/>
  <a>a5</a>
</r>
Run Code Online (Sandbox Code Playgroud)

…然后:

  • 对于"a1",应该没有匹配(<a>紧接在它之前没有兄弟元素)
  • 对于"a2",则应匹配"a1"(没有插入文本节点)
  • 对于"a3",应该没有匹配(存在具有非空白内容的插入文本节点)
  • 对于"a4",则应匹配"a3"(插入的文本节点仅为空格)
  • 对于"a5",应该没有匹配(前一个兄弟元素不是<a>).

我可以检查,如果前一兄弟是<a>preceding-sibling::*[1][name()="a"]

但是,我无法弄清楚如何说"选择以下兄弟节点,无论元素或文本,看看是不是文本或normalize-space(.)="".我最好的猜测是这样的:

preceding-sibling::*[1][name()="a"][following-sibling::node()[1][not(text()) or normalize-space(.)=""]]
Run Code Online (Sandbox Code Playgroud)

......但似乎没有效果.


这是我的测试Ruby文件:

require 'nokogiri'

xpath = 'preceding-sibling::*[1][name()="a"][following-sibling::node()[1][not(text()) or normalize-space(.)=""]]'
fragment = Nokogiri::XML.fragment '<a>a1</a><a>a2</a> b <a>a3</a> <a>a4</a> <b/> <a>a5</a>'    

fragment.css('a').each{ |a| p [a.text,a.xpath(xpath).to_s] }
#=> ["a1", ""]
#=> ["a2", ""]
#=> ["a3", "<a>a2</a>"]
#=> ["a4", "<a>a3</a>"]
#=> ["a5", ""]
Run Code Online (Sandbox Code Playgroud)

"a2"和"a3"的结果是错误的,让我感到困惑.它<a>正确地找到了前面的内容,但是后来没有正确地验证第一个后续兄弟是不是文本(应该允许"a2"找到"a1")还是只有空格(这应该防止"a3")找到"a2".


编辑:这是我写的XPath,以及我打算做的事情:

  • preceding-sibling::*[1][name()="a"]…- 找到第一个前面的元素,并确保它是一个<a>.这看似按预期工作.

    • [following-sibling::node()[1][…]]- 确保第一个后续节点(前面找到的节点<a>)符合某些条件

      • not(text()) or normalize-space(.)="" - 确保此后续节点不是文本节点,或者其标准化空间为空

Dim*_*hev 5

用途:

/*/a/preceding-sibling::node()
       [not(self::text()[not(normalize-space())])]
            [1]
              [self::a]
Run Code Online (Sandbox Code Playgroud)

基于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=
       "/*/a
          /preceding-sibling::node()
                      [not(self::text()[not(normalize-space())])]
                                        [1]
                                         [self::a]
    "/>
 </xsl:template>
</xsl:stylesheet>
Run Code Online (Sandbox Code Playgroud)

在提供的XML文档上应用此转换时:

<r>
  <a>a1</a><a>a2</a>
   b
  <a>a3</a>
    <a>a4</a>
  <b/>
  <a>a5</a>
</r>
Run Code Online (Sandbox Code Playgroud)

评估XPath表达式,并将此评估选择的节点复制到输出:

<a>a1</a>
<a>a3</a>
Run Code Online (Sandbox Code Playgroud)

更新:

问题中的XPath表达式有什么问题?

问题出在这里:

[not(text()) or normalize-space(.)='']
Run Code Online (Sandbox Code Playgroud)

这测试上下文节点是否没有文本节点节点.

但是OP希望测试上下文节点是否是文本节点.

方案:

将以上内容替换为:

[not(self::text()) or normalize-space(.)='']
Run Code Online (Sandbox Code Playgroud)

基于XSLT的验证:

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

 <xsl:template match="/*/a">
     <xsl:copy-of select=
     "preceding-sibling::*[1]
                      [name()='a']
                         [following-sibling::node()[1]
                                    [not(self::text()) or normalize-space(.)='']
                       ]"/>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>
Run Code Online (Sandbox Code Playgroud)

现在这个转换产生了完全想要的结果:

<a>a1</a>
<a>a3</a>
Run Code Online (Sandbox Code Playgroud)