根据属性删除空元素和后代

时间:2014-07-30 00:29:38

标签: xml xslt

我正在尝试创建一个XSLT,它将删除满足以下任一条件的任何元素的所有元素和所有后代:

  1. 元素为空(无文字,仅限空格)且属性。
  2. 元素为空,并且有一个或多个属于 all 为空的属性(无 文字,仅限空格。)
  3. 换句话说:唯一应该保留的元素是那些为空那些空白并且至少有一个属性的元素空。

    此外,XSLT必须是通用的/动态的,足以处理任何XML给定的XML输入并根据上述期望产生结果。

    我目前正在使用以下XSLT:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">
        <xsl:output method="xml" omit-xml-declaration="no" indent="no" />
        <xsl:strip-space elements="*" />
    
        <xsl:template match="/">
            <xsl:apply-templates select="*" />
        </xsl:template>
    
        <xsl:template match="*">
            <xsl:if test="(normalize-space(.)) or (normalize-space(.//@*))">
                <xsl:copy>
                    <xsl:element name="name()">
                        <xsl:copy-of select="@*" />
                        <xsl:apply-templates />
                    </xsl:element>
                </xsl:copy>
            </xsl:if>
        </xsl:template>
    
    </xsl:stylesheet>
    

    转换以下XML:

    <?xml version="1.0" encoding="UTF-8"?>
    <Parents>
        <Parent></Parent>
        <Parent test=""></Parent>
        <Parent test="A"></Parent>
        <Parent>Parent 1</Parent>
        <Parent>
            <Child test=""></Child>
        </Parent>
        <Parent>
            <Child test="A"></Child>
        </Parent>
        <Parent>
            <Child>Child 1</Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild test=""></GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild test="A"></GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>GrandChild 1</GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild test=""></GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild test="A"></GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild>GreatGrandChild 1</GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
    </Parents>
    

    我期望上面的XSLT将转换给定的XML:

    <?xml version="1.0" encoding="UTF-8"?>
    <Parents>
        <Parent test="A"></Parent>
        <Parent>Parent 1</Parent>
        <Parent>
            <Child test="A"></Child>
        </Parent>
        <Parent>
            <Child>Child 1</Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild test="A"></GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>GrandChild 1</GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild test="A"></GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild>GreatGrandChild 1</GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
    </Parents>
    

    然而,它在转换后产生以下不希望的结果:

    <?xml version="1.0" encoding="UTF-8"?>
    <Parents>
        <Parent test="A" />
        <Parent>Parent 1</Parent>
        <Parent>
            <Child>Child 1</Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>GrandChild 1</GrandChild>
            </Child>
        </Parent>
        <Parent>
            <Child>
                <GrandChild>
                    <GreatGrandChild>GreatGrandChild 1</GreatGrandChild>
                </GrandChild>
            </Child>
        </Parent>
    </Parents>
    

    乍一看,似乎只有顶层节点及其直接子节点被测试表达式的"(normalize-space(.)) or (normalize-space(.//@*))"条件正确处理。但是,这个块来自输入XML

        <Parent>
            <Child test="A"></Child>
        </Parent>
    

    也被从输出中过滤掉了。

    我为这个实现尝试了很多变化,这是我最接近达到预期结果的。


    我希望我已经清楚地提供了足够的细节来描述我想要实现的目标。如有必要,我很乐意根据要求提供更多细节。

1 个答案:

答案 0 :(得分:1)

优秀而清晰的问题,以及你所追求的明确的例子,很高兴看到你已经尝试过了。

您的代码几乎没有发生任何事情: - 您显示的输出与运行样式表时获得的输出不匹配,因为: - 您首先对每个元素进行浅层复制(xsl:copy)然后手动执行(xsl:element name()),从而复制每个元素 - 在我编辑之前,样式表无效,即name="name()"是非法的,应该是name="{name()}"。 - 您正在使用xsl:ifnormalize-space对所有基础节点的进行测试,这是其所有文本内容的串联。这不是你想要的,你也应该测试孩子,或者只测试文本节点(见下文) - 你已经使用xsl:strip-space对空间进行了规范化,除非你真的想要忽略仅空白属性(不在你的请求中),你应该使用它,否则就没有必要。

这是您使用输入XML获得的实际输出,这显然也不是您想要的:

<?xml version="1.0" encoding="UTF-8"?>
<Parents>
   <Parents>
      <Parent>
         <Parent test="A"/>
      </Parent>
      <Parent>
         <Parent>Parent 1</Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child test="A"/>
            </Child>
         </Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child>Child 1</Child>
            </Child>
         </Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child>
                  <GrandChild>
                     <GrandChild test="A"/>
                  </GrandChild>
               </Child>
            </Child>
         </Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child>
                  <GrandChild>
                     <GrandChild>GrandChild 1</GrandChild>
                  </GrandChild>
               </Child>
            </Child>
         </Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child>
                  <GrandChild>
                     <GrandChild>
                        <GreatGrandChild>
                           <GreatGrandChild test="A"/>
                        </GreatGrandChild>
                     </GrandChild>
                  </GrandChild>
               </Child>
            </Child>
         </Parent>
      </Parent>
      <Parent>
         <Parent>
            <Child>
               <Child>
                  <GrandChild>
                     <GrandChild>
                        <GreatGrandChild>
                           <GreatGrandChild>GreatGrandChild 1</GreatGrandChild>
                        </GreatGrandChild>
                     </GrandChild>
                  </GrandChild>
               </Child>
            </Child>
         </Parent>
      </Parent>
   </Parents>
</Parents>

这是您可以使用的解决方案,它使用修改后的复制惯用法,然后覆盖那些您不想复制的元素和其他节点。这是典型的XSLT方式&#34;这样做,正如您所看到的,不需要任何xsl:if分支指令。处理器为您完成工作;)。

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

    <xsl:template match="node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*" />
        </xsl:copy>
    </xsl:template>

    <!-- remove empty elements, or empty elements with empty attribs -->
    <xsl:template match="*[not(*)][not(text())][normalize-space(@*) = '']" />

</xsl:stylesheet>

样式表复制evertything(各种节点,还有注释和处理指令),这是第一个模板。第二个模板,故意为空,只删除适合其过滤器的所有内容。这是有效的,因为在XSLT中,更具体的模板比不太具体的模板具有更高的优先级。

输出目前如下,它与您的输出不完全匹配,但 符合我的描述:

<?xml version="1.0" encoding="UTF-8"?>
<Parents>
   <Parent test="A"/>
   <Parent>Parent 1</Parent>
   <Parent/>
   <Parent>
      <Child test="A"/>
   </Parent>
   <Parent>
      <Child>Child 1</Child>
   </Parent>
   <Parent>
      <Child/>
   </Parent>
   <Parent>
      <Child>
         <GrandChild test="A"/>
      </Child>
   </Parent>
   <Parent>
      <Child>
         <GrandChild>GrandChild 1</GrandChild>
      </Child>
   </Parent>
   <Parent>
      <Child>
         <GrandChild/>
      </Child>
   </Parent>
   <Parent>
      <Child>
         <GrandChild>
            <GreatGrandChild test="A"/>
         </GrandChild>
      </Child>
   </Parent>
   <Parent>
      <Child>
         <GrandChild>
            <GreatGrandChild>GreatGrandChild 1</GreatGrandChild>
         </GrandChild>
      </Child>
   </Parent>
</Parents>

更新:通过重新阅读您的要求,您确实提到了后代。所以回想起来,我认为你的意思是:

  • 如果元素的任何子元素包含非空白文本
  • ,则保留该元素
  • 如果元素的任何子元素包含非空白属性值
  • ,则保留该元素
  • 删除其余

如果这是正确的,您可以通过检查不存在的后代文本节点并组合所有后代属性节点来实现这一点。用以下内容替换上面的过滤行:

<xsl:template match="*[not(.//text())][normalize-space(.//@*) = '']" />

这将准确地为您提供您在所选输出中的输出(并且更​​接近您的尝试已经显示的内容)。