查找除子节目外的所有后代text()节点

时间:2012-05-25 20:32:42

标签: ruby xml xpath nokogiri

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

例如,给定对下面#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

2 个答案:

答案 0 :(得分:3)

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

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

这将选择完全相同的想要的文本节点。

答案 1 :(得分:1)

使用:

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