首页 文章

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

提问于
浏览
3

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

例如,给定这个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"]

但是,我可以't figure out how to say "select the following sibling node, regardless of element or textness, and see if that'不是文本或 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"和"a3"的结果是错误的,让我感到困惑 . 它正确地找到了前面的 <a> ,但是后来没有正确地验证它的第一个后续兄弟不是文本(应该允许"a2"找到"a1"),或者它只是空格(这应该阻止"a3"找到"a2" .


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

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

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

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

1 回答

  • 5

    Use

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

    XSLT - based verification:

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

    When this transformation is applied on the provided XML document:

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

    the XPath expression is evaluated and the nodes that are selected by this evaluation, are copied to the output

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

    Update

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

    The problem is here

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

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

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

    Solution

    Replace the above with

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

    XSLT - based verification

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

    Now this transformation produces exactly the wanted result:

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

相关问题