XSLT: Pull duplicate namespace declarations up towards root node

Published:

Sometimes XML becomes a bit weird with namespace declarations all over the place. This XSLT cleans that up. Stumbled upon it in a StackOverflow answer I don't find anymore so put here so I know where to find it in the future.

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

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

  <xsl:template match="*">
    <xsl:copy copy-namespaces="no">
      <xsl:for-each-group group-by="local-name()" select="descendant-or-self::*/namespace::*">
        <xsl:copy-of select="."/>
      </xsl:for-each-group>
      <xsl:apply-templates select="@* , node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Example

Input

<soap:Envelope
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
>
  <soap:Header>
    <wsa:MessageID soap:mustUnderstand="0">
      uuid:7fa12310-5db4-11e3-ae24-a3c913f2629d
    </wsa:MessageID>
    <wsa:To soap:mustUnderstand="0">
      http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous/
    </wsa:To>
  </soap:Header>
  <soap:Body>
    <ns1:getTicket xmlns:ns1="http://api.example.com/some-webservice">
      <cus:msisdn
        xmlns:cus="http://api.example.com/some-webservice"
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
      >
        00000000
      </cus:msisdn>
      <cus:ticket
        xmlns:cus="http://api.example.com/some-webservice"
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
      >
        171
      </cus:ticket>
    </ns1:getTicket>
  </soap:Body>
</soap:Envelope>

Output

<soap:Envelope
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:ns1="http://api.example.com/some-webservice"
  xmlns:cus="http://api.example.com/some-webservice"
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
>
  <soap:Header>
    <wsa:MessageID soap:mustUnderstand="0">
      uuid:7fa12310-5db4-11e3-ae24-a3c913f2629d
    </wsa:MessageID>
    <wsa:To soap:mustUnderstand="0">
      http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous/
    </wsa:To>
  </soap:Header>
  <soap:Body>
    <ns1:getTicket>
      <cus:msisdn>00000000</cus:msisdn>
      <cus:ticket>171</cus:ticket>
    </ns1:getTicket>
  </soap:Body>
</soap:Envelope>

If anyone know how to adjust it to also merge the duplicated prefixes ns1 and cus, do let me know. 🙂