每个XSL参数按名称删除元素和/或属性

时间:2012-02-21 22:06:33

标签: xml xslt xslt-1.0 xslt-2.0

以下是通过名称(在本例中为“removeMe”)从XML文件中删除不需要的元素和属性的工作:

<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="node() | @*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node() | @*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="removeMe"/>
</xsl:stylesheet>

问题是它不区分元素和属性,名称是硬编码的,它只能采用一个名称。 如何重写以使用下面的几个输入参数来删除一个或多个特定元素和/或属性?

<xsl:param name="removeElementsNamed"/>
<xsl:param name="removeAttributesNamed"/>

所需的结果是能够移除 一个或多个 元素和/或 一个或多个 < strong>属性,同时仍然区分元素和属性(换句话说,应该可以删除所有“时间”元素 没有也删除所有“时间” “属性)。

虽然本轮我需要XSLT 1.0,但接受和其他答案的XSLT 2.0解决方案可能对其他人有用。

4 个答案:

答案 0 :(得分:22)

此转化

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

 <xsl:param name="removeElementsNamed" select="'x'"/>

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

 <xsl:template match="*">
  <xsl:if test="not(name() = $removeElementsNamed)">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

应用于任何XML文档时,请说明

<t>
    <a>
        <b/>
        <x/>
    </a>
    <c/>
    <x/>
    <d/>
</t>

生成所需的正确结果 - 源XML文档的副本,其中任何名称为$removeElementsNamed参数值的元素的出现都将被删除:< / p>

<t>
   <a>
      <b/>
   </a>
   <c/>
   <d/>
</t>

请注意 In XSLT 1.0 it is syntactically illegal to have a variable or parameter reference inside a template match pattern 。这就是为什么@JanThomä和@treeMonkey的解决方案都会引起任何符合XSLT 1.0标准的处理器的错误。

更新:这是一个更复杂的解决方案,它允许删除以管道分隔的元素名称列表,并将其传递给转换:

<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:param name="removeElementsNamed" select="'|x|c|'"/>

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

 <xsl:template match="*">
  <xsl:if test=
   "not(contains($removeElementsNamed,
                 concat('|',name(),'|' )
                 )
        )
   ">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

当应用于同一XML文档(上图)时,转换再次生成所需的正确输出 - 源XML文档,其中包含名称在$removeElementsNamed参数中指定的所有元素 - 已删除

<t>
   <a>
      <b/>
   </a>
   <d/>
</t>

Update2 :与 Update1 中的转换相同,但是使用XSLT 2.0编写:

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

 <xsl:param name="removeElementsNamed" select="'|x|c|'"/>

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

 <xsl:template match=
 "*[name() = tokenize($removeElementsNamed, '\|')]"/>
</xsl:stylesheet>

更新:OP已添加要求,也可以删除具有某些特定名称的所有属性。

以下是稍微修改过的转换以适应这一新要求:

<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:param name="removeElementsNamed" select="'x'"/>
     <xsl:param name="removeAttributesNamed" select="'n'"/>

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

     <xsl:template match="*">
      <xsl:if test="not(name() = $removeElementsNamed)">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>

     <xsl:template match="@*">
      <xsl:if test="not(name() = $removeAttributesNamed)">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

将此转换应用于下面的XML文档(之前使用的那个,但添加了一些属性):

<t>
    <a>
        <b m="1" n="2"/>
        <x/>
    </a>
    <c/>
    <x/>
    <d n="3"/>
</t>

生成了需要的正确结果(名为x的所有元素和名为n的所有属性都被删除了):

<t>
   <a>
      <b m="1"/>
   </a>
   <c/>
   <d/>
</t>

UPDATE2 :正如OP再次请求的那样,我们现在实现了传递管道分隔的名称列表的功能,以删除具有这些名称的元素,并分别用管道分隔的名称列表删除具有以下名称的属性:

<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:param name="removeElementsNamed" select="'|c|x|'"/>
     <xsl:param name="removeAttributesNamed" select="'|n|p|'"/>

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

     <xsl:template match="*">
      <xsl:if test=
      "not(contains($removeElementsNamed,
                    concat('|', name(), '|')
                    )
           )
      ">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>

     <xsl:template match="@*">
      <xsl:if test=
      "not(contains($removeAttributesNamed,
                    concat('|', name(), '|')
                    )
           )
       ">
       <xsl:call-template name="identity"/>
      </xsl:if>
     </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文档

<t>
    <a p="0">
        <b m="1" n="2"/>
        <x/>
    </a>
    <c/>
    <x/>
    <d n="3"/>
</t>

生成所需的正确结果(名称为cx的元素以及名称为np的属性将被删除) :

<t>
   <a>
      <b m="1"/>
   </a>
   <d/>
</t>

答案 1 :(得分:3)

如果您可以使用2.0,这是一个XSLT 2.0选项。元素名称可以以逗号,制表符,管道或空格分隔的方式传递。

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

  <xsl:param name="removeElementsNamed" select="'bar,baz'"/>  

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

  <xsl:template match="*[name()=tokenize($removeElementsNamed,'[\|, \t]')]"/>  

</xsl:stylesheet>

答案 2 :(得分:0)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output omit-xml-declaration="yes" indent="yes"/>
   <xsl:param name="removeMe"/>

   <xsl:template match="node() | @*">
      <xsl:if test="not(name(.)=$removeMe)">
        <xsl:copy>
           <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
      </xsl:if>
   </xsl:template>   


</xsl:stylesheet>

答案 3 :(得分:-1)

这是一些hacky,但它可能会给你一般的想法:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="removeElementsNamed"/>

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

<xsl:template match="*[contains($removeElementsNamed, concat(',',name(),','))]"/>

您需要指定要删除的元素名称作为逗号分隔列表,以逗号开头并以逗号结尾,例如值“,foo,bar,baz”将删除所有名为foo bar或baz的元素。如果您没有任何元素是其他元素的部分名称,您可以将其简化为:

<xsl:template match="*[contains($removeElementsNamed,name())]"/>

但是,如果你有像

这样的XML
<foo>
  <bar>..<bar>
  <barbara>..</barbara>
<foo>

并使用“bar”作为参数,这将删除bar和barbara标签,因此第一种方法更安全。