使用XPath选择前面的元素,并在其中插入可选的纯空白文本节点



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

例如,给定这个XML文档…

<r>
  <a>a1</a><a>a2</a>
   b
  <a>a3</a>
    <a>a4</a>
  <b/>
  <a>a5</a>
</r>

…然后:

  • 对于"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(.)=""]]

…,但似乎没有效果。


这是我的测试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", ""]

"a2"one_answers"a3"的结果是什么是错误的,让我困惑。它可以正确地找到前面的<a>,但是不能正确地验证它的第一个兄弟不是文本(这应该允许"a2"找到"a1")或者它只是空白(这应该阻止"a3"找到"a2"。


Edit:这是我正在编写的XPath,以及我想要它做的事情:

  • preceding-sibling::*[1][name()="a"]… -找到前面的第一个元素,并确保它是<a>

    • [following-sibling::node()[1][…]] -确保以下第一个节点(在找到的<a>之前)匹配某些条件

      • not(text()) or normalize-space(.)="" -确保以下节点不是文本节点,或者它的规范化空间为空

使用:

/*/a/preceding-sibling::node()
       [not(self::text()[not(normalize-space())])]
            [1]
              [self::a]

基于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>

当对提供的XML文档应用此转换时:

<r>
  <a>a1</a><a>a2</a>
   b
  <a>a3</a>
    <a>a4</a>
  <b/>
  <a>a5</a>
</r>

对XPath表达式求值,并将该求值所选择的节点复制到输出:

<a>a1</a>
<a>a3</a>

:

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

问题在这里:

[not(text()) or normalize-space(.)='']

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

但是OP想要测试上下文节点是否是文本节点。

<<p> 解决方案/strong>:

:

代替
[not(self::text()) or normalize-space(.)='']

基于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>

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

<a>a1</a>
<a>a3</a>

最新更新