Search code examples
xslt-2.0xml-namespaces

XSLT 2.0 flexible with default or different prefixed namespace of the same URI


I am new to XSLT and am stumbling on some boiler plate namespace handling.

I have the following xslt where the goal is to simply rename one element:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/" >
    <xsl:strip-space elements="*" />

    <!-- element template that copies over elements -->
    <xsl:template match="*">
        <xsl:element name="{name()}">
            <xsl:apply-templates select="@* | node()"/>
        </xsl:element>
    </xsl:template>

    <!-- attribute template to copy attributes over -->
    <xsl:template match="@*">
        <xsl:copy>
            <xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
        </xsl:copy>
    </xsl:template>

    <!-- "other" template to copy the rest of the nodes -->
    <xsl:template match="comment() | text() | processing-instruction()">
        <xsl:copy/>
    </xsl:template>

    <!-- Rename an element -->
    <xsl:template match="BOPPolicyQuoteInqRq/RqUID" >
        <xsl:element name="RqUUID">
            <xsl:apply-templates select="node()|@*"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

Transforming the following xml works as expected:

<ACORD xmlns="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
  <InsuranceSvcRq>
    <BOPPolicyQuoteInqRq>
      <RqUID>E2BA6308-62D5-43AC-B8C1-7616FDFE9C98</RqUID>    
    </BOPPolicyQuoteInqRq>
  </InsuranceSvcRq>
</ACORD>

However, this semantically equivalent xml fails:

<bloat:ACORD xmlns:bloat="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/">
  <bloat:InsuranceSvcRq>
    <bloat:BOPPolicyQuoteInqRq>
      <bloat:RqUID>E2BA6308-62D5-43AC-B8C1-7616FDFE9C98</bloat:RqUID>    
    </bloat:BOPPolicyQuoteInqRq>
  </bloat:InsuranceSvcRq>
</bloat:ACORD>

The error i receive is:

Caused by: net.sf.saxon.trans.XPathException: Undeclared prefix in element name: bloat
    at net.sf.saxon.expr.instruct.ComputedElement.getElementName(ComputedElement.java:429)
    at net.sf.saxon.expr.instruct.ElementCreator.processLeavingTail(ElementCreator.java:388)
    at net.sf.saxon.expr.instruct.ElementCreator.processLeavingTail(ElementCreator.java:371)
    at net.sf.saxon.expr.instruct.Template.applyLeavingTail(Template.java:239)
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:1056)
    at net.sf.saxon.trans.TextOnlyCopyRuleSet.process(TextOnlyCopyRuleSet.java:65)
    at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:1044)
    at net.sf.saxon.Controller.transformDocument(Controller.java:2088)
    at net.sf.saxon.Controller.transform(Controller.java:1911)
    at org.apache.camel.builder.xml.XsltBuilder.process(XsltBuilder.java:141)
    at org.apache.camel.impl.ProcessorEndpoint.onExchange(ProcessorEndpoint.java:103)
    at org.apache.camel.component.xslt.XsltEndpoint.onExchange(XsltEndpoint.java:121)
    at org.apache.camel.impl.ProcessorEndpoint$1.process(ProcessorEndpoint.java:71)
    at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
    at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)
    at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:460)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
    at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
    at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:62)
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
    at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:109)
    at org.apache.camel.processor.UnitOfWorkProducer.process(UnitOfWorkProducer.java:68)
    at org.apache.camel.impl.ProducerCache$2.doInProducer(ProducerCache.java:412)
    at org.apache.camel.impl.ProducerCache$2.doInProducer(ProducerCache.java:380)
    at org.apache.camel.impl.ProducerCache.doInProducer(ProducerCache.java:270)
    at org.apache.camel.impl.ProducerCache.sendExchange(ProducerCache.java:380)
    at org.apache.camel.impl.ProducerCache.send(ProducerCache.java:221)
    at org.apache.camel.impl.DefaultProducerTemplate.send(DefaultProducerTemplate.java:124)
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:137)
    ... 32 more

It appears even though these xmls are semantically equivalent as far as the xml spec would be concerned, the XSLT transformer is getting hung up because one declares a prefix and the other doesn't (i would also venture to say it will get hung up if one was prefixed with 'foo' and one with 'bar').

I am in a position where I can't force the client who's passing me xml to declare a specific prefix or namespace a certain way. I also can't guarantee they won't decide to utilize a different prefix alias tomorrow.

My understanding of declaring the xpath-default-namespace attribute was that it told the xslt transformer what namespace URI the entire document will be associated with regardless of whether it would be declared as a default prefix, a prefix with the alias 'bloat', or even a prefix with the alias 'rainbowunicorns'.

What precisely does the attribute xpath-default-namespace do and how can I can write a flexible XSLT that can gracefully handle any amount of semantically equivalent namespaces regardless of what flavor of namespace declaration the client decides?

Specs if relevant: Camel 2.16.2 Saxon-HE 9.5.1-8

Updated transform that works with both xmls (courtesy of Martin Honnen):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/" >
    <xsl:strip-space elements="*" />

    <!-- element template that copies over elements -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
    </xsl:template>

    <!-- "other" template to copy the rest of the nodes -->
    <xsl:template match="comment() | processing-instruction()">
        <xsl:copy/>
    </xsl:template>

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

</xsl:stylesheet>

Solution

  • Replace

    <xsl:template match="*">
        <xsl:element name="{name()}">
            <xsl:apply-templates select="@* | node()"/>
        </xsl:element>
    </xsl:template>
    
    <!-- attribute template to copy attributes over -->
    <xsl:template match="@*">
        <xsl:copy>
            <xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
        </xsl:copy>
    </xsl:template>
    

    by

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

    as that way you copy any namespaces in scope as well and then use

    <xsl:template match="BOPPolicyQuoteInqRq/RqUID" >
        <xsl:element name="{QName(namespace-uri(), if (prefix-from-QName(node-name(.))) then concat(prefix-from-QName(node-name(.)),':', 'RqUUID') else 'RqUUID')}" namespace="{namespace-uri()}">
            <xsl:apply-templates select="node()|@*"/>
        </xsl:element>
    </xsl:template>
    

    With some variables to keep the code readable the whole stylesheet becomes

    <xsl:transform
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="2.0"
        xpath-default-namespace="http://www.ACORD.org/standards/PC_Surety/ACORD1/xml/"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="xs">
    
        <xsl:template match="@* | node()">
            <xsl:copy>
                <xsl:apply-templates select="@* | node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="BOPPolicyQuoteInqRq/RqUID">
            <xsl:variable name="new-local-name" as="xs:string" select="'RqUUID'"/>
            <xsl:variable name="prefix" select="prefix-from-QName(node-name(.))"/>
            <xsl:variable name="new-name" as="xs:string" select="if ($prefix) then concat($prefix,':', $new-local-name) else $new-local-name"/>
            <xsl:element name="{QName(namespace-uri(), $new-name)}" namespace="{namespace-uri()}">
                <xsl:apply-templates select="node()|@*"/>
            </xsl:element>
        </xsl:template>
    
    </xsl:transform>