【问题标题】:Find all descendant text() nodes except in subsections查找除小节之外的所有后代 text() 节点
【发布时间】:2012-05-25 20:32:42
【问题描述】:

我的 XML 文档有任意嵌套的部分。鉴于对特定部分的引用,我需要在该部分中找到所有 TextNodes,不包括小节

例如,给定下面#a1节点的引用,我只需要找到“A1”和“A1”文本节点:

<root>
  <section id="a1">
    <b>A1 <c>A1</c></b>
    <b>A1 <c>A1</c></b>
    <section id="a1.1">
      <b>A1.1 <c>A1.1</c></b>
    </section>
    <section id="a1.2">
      <b>A1.2 <c>A1.2</c></b>
      <section id="a1.2.1">
        <b>A1.2.1</b>
      </section>
      <b>A1.2 <c>A1.2</c></b>
    </section>
  </section>
  <section id="a2">
    <b>A2 <c>A2</c></b>
  </section>
</root>

如不明显,以上为虚构数据。 id 属性在实际文档中可能不存在。

我现在想出的最好办法是找到该部分中的所有文本节点,然后使用 Ruby 减去我不想要的那些:

def own_text(node)
  node.xpath('.//text()') - node.xpath('.//section//text()')
end

doc = Nokogiri.XML(mydoc,&:noblanks)
p own_text(doc.at("#a1")).length #=> 4

我可以制作一个 XPath 1.0 表达式来直接查找这些节点吗?比如:

.//text()[ancestor::section = self] # self being the original context node

【问题讨论】:

    标签: ruby xml xpath nokogiri


    【解决方案1】:

    使用(对于id属性的字符串值为“a1”的部分):

       //section[@id='a1']
           //*[normalize-space(text()) and ancestor::section[1]/@id = 'a1']/text()
    

    基于 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="/">
         <xsl:copy-of select=
          "//section[@id='a1']
               //*[normalize-space(text()) and ancestor::section[1]/@id = 'a1']
         "/>
     </xsl:template>
    </xsl:stylesheet>
    

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

    <root>
        <section id="a1">
            <b>A1 
                <c>A1</c>
            </b>
            <b>A1 
                <c>A1</c>
            </b>
            <section id="a1.1">
                <b>A1.1 
                    <c>A1.1</c>
                </b>
            </section>
            <section id="a1.2">
                <b>A1.2 
                    <c>A1.2</c>
                </b>
                <section id="a1.2.1">
                    <b>A1.2.1</b>
                </section>
                <b>A1.2 
                    <c>A1.2</c>
                </b>
            </section>
        </section>
        <section id="a2">
            <b>A2 
                <c>A2</c>
            </b>
        </section>
    </root>
    

    它评估 XPath 表达式(只选择所需文本节点的父节点 - 为了获得清晰可见的结果)并将所选节点复制到输出

    <b>A1 
                <c>A1</c>
    </b>
    <c>A1</c>
    <b>A1 
                <c>A1</c>
    </b>
    <c>A1</c>
    

    更新:如果section 元素可以具有相同的id 属性(或根本没有id 属性),请使用:

           (//section)[1]
               //*[normalize-space(text())
               and
                  count(ancestor::section)
                 =
                   count((//section)[1]/ancestor::section) +1]/text()
    

    基于 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="/">
             <xsl:copy-of select=
              "(//section)[1]
                   //*[normalize-space(text())
                   and
                      count(ancestor::section)
                     =
                       count((//section)[1]/ancestor::section) +1]
             "/>
         </xsl:template>
    </xsl:stylesheet>
    

    转换结果(相同)

    <b>A1 
                <c>A1</c>
    </b>
    <c>A1</c>
    <b>A1 
                <c>A1</c>
    </b>
    <c>A1</c>
    

    这会选择完全相同的文本节点。

    【讨论】:

    • 不依赖id 属性你能做到这一点吗?那只是一个演示文档,用于清楚地说明和讨论这一点。想象一下嵌套的 &lt;section&gt; 元素没有区别属性。
    • 不错;我忘记了使用count(),但即使您开始使用它,我也无法弄清楚您将如何“存储”计数。这仍然不能直接在 Ruby/XPath 中工作(因为在启动新上下文时唯一节点是 .),但这似乎回答了通用 XPath 的问题。
    • @Phrogz:不客气。是的,对于未识别的初始上下文节点,无法使用单个 XPath 1.0 表达式来选择这些节点,除非它在由 XPath 处理器的主机设置的特殊执行上下文中执行。对于 XSLT 1.0,可以使用current() 和/或generate-id() 函数,因此这是可能的。如果您可以使用 XSLT 1.0,则可以选择特定节点,否则无法在 XSLT 上下文之外使用单个 XPath 表达式进行操作。当然,使用 XPath 2.0 更容易选择这些。
    【解决方案2】:

    用途:

    //text()[ancestor::section[1]/@id = 'a1']
    

    【讨论】:

    • 这只有在每个部分都有一个唯一的id 属性时才有效。在我上面的示例数据中恰好是这种情况,但不是一般的解决方案。 +1,但不接受。
    • @Phrogz:如果是这种情况,您需要在问题的文本中指定这一点。您还需要指定如何唯一选择特定的section,因为这是所需XPath 表达式的必要前缀。请参阅我的答案以获取不依赖于 id 唯一性的解决方案。
    • @Dimitre 任何部分都可以通过例如//section[27] 或(实际上对于我的情况)doc.xpath('//section').each{ |section| …use this specific section reference as an anchor for a new XPath expression… } 进行唯一选择
    猜你喜欢
    • 1970-01-01
    • 2015-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    相关资源
    最近更新 更多