Question

I'm starting to use XSLT to temporarily support the current 1.0 version of our webservice while clients transition to 1.1, converting old calls to the new format.

For such change, I need to change the namespace, include a node and rename another. I'm new to XSLT, but after some googling I came up with a working solution, however the workings of it and the output looks messy, and I'm not sure how much fail-safe it is. I'd like some advice to enhance it, making it cleaner and more maintainable (version 1.2 will require way more transforming).

Input XML example (my SOAP client uses qualified elements):

  <ns10:testRequest xmlns:ns10="namespace/1.0">
     <ns10:a>
        <ns10:b1>
           <ns10:c>cccc</ns10:c>
           <ns10:d>dddd</ns10:d>
           <ns10:e>eeee</ns10:e>
        </ns10:b1>
        <ns10:b2 attr="value">
           <ns10:f>false</ns10:f>
           <ns10:g>2014-03-01</ns10:g>
           <ns10:h>true</ns10:h>
        </ns10:b2>
        <ns10:b3>
        </ns10:b3>
     </ns10:a>
  </ns10:testRequest>

XSLT (with relevant comments):

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

    <xsl:param name="newnsParam">namespace/1.1</xsl:param>

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

    <!-- rename 'g' to 'newName' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g">
        <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below -->
        <xsl:element name="newName" namespace="{$newnsParam}">
            <xsl:apply-templates select="@*|node()" />
        </xsl:element>
    </xsl:template>

    <!-- add 'newElement' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1">
        <!-- same as above, must set as newns now, and this one won't be qualified (why?) -->
        <xsl:element name="b1" namespace="{$newnsParam}">
            <xsl:apply-templates select="@*|node()" />
            <xsl:element name="newElement" namespace="{$newnsParam}">NEW_VAL</xsl:element>
        </xsl:element>
    </xsl:template>

    <!-- change namespace, but it makes every node redefine the namespace -->
    <xsl:template match="@oldns:*">
        <xsl:attribute name="newns:{local-name()}" namespace="{$newnsParam}">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:template>
    <xsl:template match="oldns:*">
        <xsl:element name="newns:{local-name()}" namespace="{$newnsParam}">
            <xsl:apply-templates select="node()|@*"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Output XML:

  <newns:testRequest xmlns:newns="namespace/1.1">
     <newns:a xmlns:newns="namespace/1.1">
        <newns:b1 xmlns:newns="namespace/1.1">
           <newns:c xmlns:newns="namespace/1.1">cccc</newns:c>
           <newns:d xmlns:newns="namespace/1.1">dddd</newns:d>
           <newns:e xmlns:newns="namespace/1.1">eeee</newns:e>
        </newns:b1>
        <newns:b2 xmlns="namespace/1.1" attr="value" xmlns:newns="namespace/1.1">
           <newns:f xmlns="namespace/1.1" xmlns:newns="namespace/1.1">false</newns:f>
           <newns:newName xmlns="namespace/1.1" xmlns:newns="namespace/1.1">2014-03-01</newns:newName>
           <newns:h xmlns="namespace/1.1" xmlns:newns="namespace/1.1">true</newns:h>
           <newElement xmlns="namespace/1.1" xmlns:newns="namespace/1.1">NEW_VAL</newElement>
        </newns:b2>
        <newns:b3 xmlns:newns="namespace/1.1">
        </newns:b3>
     </newns:a>
  </newns:testRequest>

As seen, it is quite confuse with all those unecessary namespace definitions, plus 'newElement' is oddly unqualified. We log the calls for debugging and auditing purposes, and such verbosity is undesirable.

Expected XML (or something like it):

  <newns:testRequest xmlns:newns="namespace/1.1">
     <newns:a>
        <newns:b1>
           <newns:c>cccc</newns:c>
           <newns:d>dddd</newns:d>
           <newns:e>eeee</newns:e>
        </newns:b1>
        <newns:b2 attr="value">
           <newns:f>false</newns:f>
           <newns:newName>2014-03-01</newns:newName>
           <newns:h>true</newns:h>
           <newns:newElement>NEW_VAL</newns:newElement>
        </newns:b2>
        <newns:b3>
        </newns:b3>
     </newns:a>
  </newns:testRequest>

It is important that the XSLT be robust to handle different input XML, like qualified and unqualified elements (it accepts both with the current XSLT).

Any help is appreciated.

Environment: Java 1.6.0_14, Spring 3.1.3 web application with Spring WS 2.1.0, JAXB 2.2.6 and internal JRE Apache Xalan


EDIT 1: hr_117's suggestion gave this exception:

javax.xml.transform.TransformerException: java.lang.RuntimeException: Namespace for prefix 'newns' has not been declared.
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:717) ~[na:1.6.0_14]
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313) ~[na:1.6.0_14]
    at org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor.transformMessage(PayloadTransformingInterceptor.java:118) ~[spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor.handleRequest(PayloadTransformingInterceptor.java:92) ~[spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.server.endpoint.interceptor.DelegatingSmartEndpointInterceptor.handleRequest(DelegatingSmartEndpointInterceptor.java:78) ~[spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:224) [spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:173) [spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:88) [spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:59) [spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.ws.transport.http.MessageDispatcherServlet.doService(MessageDispatcherServlet.java:221) [spring-ws-core-2.1.0.RELEASE.jar:na]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882) [org.springframework.web.servlet-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789) [org.springframework.web.servlet-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) [javax.servlet_1.0.0.0_2-5.jar:2.5]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [javax.servlet_1.0.0.0_2-5.jar:2.5]
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3591) [weblogic.jar:10.3.2.0]
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321) [com.bea.core.weblogic.security.identity_1.1.2.0.jar:1.1.2.0]
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121) [com.bea.core.weblogic.security.wls_1.0.0.0_5-2-0-0.jar:5.2.0.0]
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2202) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2108) [weblogic.jar:10.3.2.0]
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1432) [weblogic.jar:10.3.2.0]
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201) [com.bea.core.weblogic.workmanager_1.7.0.0.jar:1.7.0.0]
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:173) [com.bea.core.weblogic.workmanager_1.7.0.0.jar:1.7.0.0]
Caused by: java.lang.RuntimeException: Namespace for prefix 'newns' has not been declared.
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1518) ~[na:1.6.0_14]
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.runTimeError(BasisLibrary.java:1522) ~[na:1.6.0_14]
    at com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.startXslElement(BasisLibrary.java:1408) ~[na:1.6.0_14]
    at transformation_1_0_to_1_1.template$dot$4() ~[na:na]
    at transformation_1_0_to_1_1.applyTemplates() ~[na:na]
    at transformation_1_0_to_1_1.applyTemplates() ~[na:na]
    at transformation_1_0_to_1_1.transform() ~[na:na]
    at com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(AbstractTranslet.java:602) ~[na:1.6.0_14]
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:710) ~[na:1.6.0_14]
    ... 25 common frames omitted

EDIR 2: As a test, I replaced Xalan with Saxon, and the output was nice like Michael posted. However, it's a large jar for our already large application, plus it conflicted with other parts of our application, so it's more of an instability factor. I'd like to just keep the focus on Xalan and make it work right.

Was it helpful?

Solution

I'm rather surprised that Xalan should be producing all those redundant namespace declarations. Here is Saxon's output for comparison:

<?xml version="1.0" encoding="UTF-8"?><newns:testRequest xmlns:newns="namespace/1.1">
     <newns:a>
        <b1 xmlns="namespace/1.1">
           <newns:c>cccc</newns:c>
           <newns:d>dddd</newns:d>
           <newns:e>eeee</newns:e>
        <newElement>NEW_VAL</newElement></b1>
        <newns:b2 attr="value">
           <newns:f>false</newns:f>
           <newns:g>2014-03-01</newns:g>
           <newns:h>true</newns:h>
        </newns:b2>
        <newns:b3>
        </newns:b3>
     </newns:a>
  </newns:testRequest>

If you want to make the dominant namespace newns be the default namespace, to reduce clutter further, you can change <xsl:copy> to <xsl:element name="{local-name()}" namespace="{namespace-uri()}">. This produces the output:

<?xml version="1.0" encoding="UTF-8"?>
<testRequest xmlns="namespace/1.1">
     <a>
        <b1>
           <c>cccc</c>
           <d>dddd</d>
           <e>eeee</e>
        <newElement>NEW_VAL</newElement></b1>
        <b2 attr="value">
           <f>false</f>
           <g>2014-03-01</g>
           <h>true</h>
        </b2>
        <b3>
        </b3>
     </a>
  </testRequest>

Here's the amended stylesheet:

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

    <xsl:param name="newnsParam">namespace/1.1</xsl:param>

    <!-- copy all document -->
    <xsl:template match="*">
        <xsl:element name="{local-name()}" namespace="{namespace-uri()}">
            <xsl:apply-templates select="*" />
        </xsl:element>
    </xsl:template>

    <xsl:template match="@*">
        <xsl:copy-of select="."/>
    </xsl:template>

    <!-- rename 'g' to 'newName' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g">
        <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below -->
        <xsl:element name="newName" namespace="{$newnsParam}">
            <xsl:apply-templates select="@*|node()" />
        </xsl:element>
    </xsl:template>

    <!-- add 'newElement' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1">
        <!-- same as above, must set as newns now, and this one won't be qualified (why?) -->
        <xsl:element name="b1" namespace="{$newnsParam}">
            <xsl:apply-templates select="@*|node()" />
            <xsl:element name="newElement" namespace="{$newnsParam}">NEW_VAL</xsl:element>
        </xsl:element>
    </xsl:template>

    <!-- change namespace, but it makes every node redefine the namespace -->
    <xsl:template match="@oldns:*">
        <xsl:attribute name="newns:{local-name()}" namespace="{$newnsParam}">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:template>

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

</xsl:stylesheet>

OTHER TIPS

I'm not sure if this works for you because I'm using a different xlst processor (xsltproc). Also my output with your original xslt does not completely look like your output.

So anyway:
I have mainly removed the namespace attribute from xlst and add two times the namespace prefix "newns".

Try this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:oldns="namespace/1.0"
    xmlns:newns="namespace/1.1"
    version="1.0">

    <xsl:param name="newnsParam">namespace/1.1</xsl:param>

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

    <!-- rename 'g' to 'newName' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1/oldns:g">
        <!-- if I don't set it with newns now, it will remain as oldns even after namespace change below -->
        <xsl:element name="newns:newName">
            <xsl:apply-templates select="@*|node()" />
        </xsl:element>
    </xsl:template>

    <!-- add 'newElement' -->
    <xsl:template match="/oldns:testRequest/oldns:a/oldns:b1">
        <!-- same as above, must set as newns now, and this one won't be qualified (why?) -->
        <xsl:element name="newns:b1" >
            <xsl:apply-templates select="@*|node()" />
            <xsl:element name="newns:newElement">NEW_VAL</xsl:element>
        </xsl:element>
    </xsl:template>

    <!-- change namespace, but it makes every node redefine the namespace -->
    <xsl:template match="@oldns:*">
        <xsl:attribute name="newns:{local-name()}">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:template>
    <xsl:template match="oldns:*">
        <xsl:element name="newns:{local-name()}">
            <xsl:apply-templates select="node()|@*"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Which generates the following output (at least with xsltproc):

<?xml version="1.0"?>
<newns:testRequest xmlns:newns="namespace/1.1">
     <newns:a>
        <newns:b1>
           <newns:c>cccc</newns:c>
           <newns:d>dddd</newns:d>
           <newns:e>eeee</newns:e>
        <newns:newElement>NEW_VAL</newns:newElement></newns:b1>
        <newns:b2 attr="value">
           <newns:f>false</newns:f>
           <newns:g>2014-03-01</newns:g>
           <newns:h>true</newns:h>
        </newns:b2>
        <newns:b3>
        </newns:b3>
     </newns:a>
  </newns:testRequest>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top