XSL - 如何从源xml中删除未使用的命名空间?

时间:2011-01-04 11:59:53

标签: xml xslt xml-namespaces

我有一个包含许多未使用的命名空间的xml,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<ns1:Envelope xmlns:ns1="http://www.a.com" xmlns:ns2="http://www.b.com" xmlns:ns3="http://www.c.com" xmlns:ns4="http://www.d.com">
    <ns1:Body>
        <ns2:a>
            <ns2:b>data1</ns2:b>
            <ns2:c>data2</ns2:c>
        </ns2:a>
    </ns1:Body>
</ns1:Envelope> 

我想删除未使用的命名空间,而不必在xslt中指定要删除/维护的命名空间。结果xml应为:

<?xml version="1.0" encoding="UTF-8"?>
<ns1:Envelope xmlns:ns1="http://www.a.com" xmlns:ns2="http://www.b.com">
    <ns1:Body>
        <ns2:a>
            <ns2:b>data1</ns2:b>
            <ns2:c>data2</ns2:c>
        </ns2:a>
    </ns1:Body>
</ns1:Envelope> 

我已经google了很多,但还没有找到解决这个问题的方法。有没有?

感谢。

PS:不是100%肯定,但我认为它应该适用于XSL 1.0。

3 个答案:

答案 0 :(得分:19)

与@ Martin-Honnen的答案不同,此解决方案可以产生完全理想的结果 - 必要的命名空间节点保留在它们所在的位置并且不会向下移动。

此外,此解决方案正确处理命名空间中的属性

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

 <xsl:template match="*">
  <xsl:element name="{name()}" namespace="{namespace-uri()}">
   <xsl:variable name="vtheElem" select="."/>

   <xsl:for-each select="namespace::*">
     <xsl:variable name="vPrefix" select="name()"/>

     <xsl:if test=
      "$vtheElem/descendant::*
              [(namespace-uri()=current()
             and 
              substring-before(name(),':') = $vPrefix)
             or
              @*[substring-before(name(),':') = $vPrefix]
              ]
      ">
      <xsl:copy-of select="."/>
     </xsl:if>
   </xsl:for-each>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文档(提供的带有添加的命名空间属性的XML文档):

<ns1:Envelope xmlns:ns1="http://www.a.com" xmlns:ns2="http://www.b.com" xmlns:ns3="http://www.c.com" xmlns:ns4="http://www.d.com">
    <ns1:Body ns2:x="1">
        <ns2:a>
            <ns2:b>data1</ns2:b>
            <ns2:c>data2</ns2:c>
        </ns2:a>
    </ns1:Body>
</ns1:Envelope>

产生了所需的正确结果

<ns1:Envelope xmlns:ns1="http://www.a.com" xmlns:ns2="http://www.b.com">
   <ns1:Body ns2:x="1">
      <ns2:a>
         <ns2:b>data1</ns2:b>
         <ns2:c>data2</ns2:c>
      </ns2:a>
   </ns1:Body>
</ns1:Envelope>

答案 1 :(得分:3)

好吧,如果你使用

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

  <xsl:template match="@* | text() | comment() | processing-instruction()">
    <xsl:copy/>
  </xsl:template>

  <xsl:template match="*">
    <xsl:element name="{name()}" namespace="{namespace-uri()}">
      <xsl:apply-templates select="@* | node()"/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

然后删除未使用的命名空间,但结果更像是

<ns1:Envelope xmlns:ns1="http://www.a.com">
    <ns1:Body>
        <ns2:a xmlns:ns2="http://www.b.com">
            <ns2:b>data1</ns2:b>
            <ns2:c>data2</ns2:c>
        </ns2:a>
    </ns1:Body>
</ns1:Envelope>

比你要求的要多。

答案 2 :(得分:1)

添加到Dimitre的答案中,如果应保留仅在属性中出现的名称空间,请添加以下条件:@*[contains(.,concat($vPrefix,':'))]

  <xsl:if test= "$vtheElem/descendant::* [namespace-uri() = current()     and
                   substring-before(name(),':') = $vPrefix or
                   @*[substring-before(name(),':') = $vPrefix] or
                   @*[contains(.,concat($vPrefix,':'))]
                  ]">

由于ns3,这将正确保留名称空间attrib="ns3:Header",如下例所示。

 <ns1:Envelope xmlns:ns1="http://www.a.com" xmlns:ns2="http://www.b.com" xmlns:ns3="http://www.c.com" xmlns:ns4="http://www.d.com">
    <ns1:Body ns2:x="1">
        <ns2:a>
            <ns2:b atrib="ns3:Header">data1</ns2:b>
            <ns2:c>data2</ns2:c>
        </ns2:a>
    </ns1:Body>
</ns1:Envelope>