Home > Back-end >  XSLT conditional namespace and element
XSLT conditional namespace and element

Time:08-02

I've working a XSLT and XML generation based on some rules. Now, I want to make a slight modification to XSLT file, the idea is as follows:

XSLT

  • bill:Receipt root node must contain the attribute xmlns:payment="http://www.website.io/Payments2" only if payments/payment exist.
  • At the same time, <bill:Addendum> must contain <payment:Payments Version="2.0"> element only if if payments/payment exist.

As in the following example:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <bill:Receipt xmlns:bill="http://www.website.io/bll/4"
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd"
                        ReceiptType="{@ReceiptType}">
            <xsl:if test="payments/payment">
                <xsl:attribute name="xmlns:payment">
                    <xsl:value-of select="http://www.website.io/Payments2"/>
                </xsl:attribute>
            </xsl:if>
            <bill:Addendum>
                <xsl:if test="payments/payment">
                    <payment:Payments Version="2.0">
                        <payment:Totals>
                            <xsl:attribute name="PaymentsTotal">
                                <xsl:value-of select="payments/totals/@PaymentsTotal"></xsl:value-of>
                            </xsl:attribute>
                        </payment:Totals>
                    </payment:Payments>
                </xsl:if>
            </bill:Addendum>
        </bill:Receipt>
    </xsl:template>
</xsl:stylesheet>

Problem with this configuration is that I'm getting the error: The prefix "payment" for element "payment:Payments" is not bound. because the namespace is conditional.

I know that I can do something like this <payment:Payments Version="2.0" xmlns:payment="http://www.website.io/Payments2"> (add the namespace to child element) but that implies my namespace won't be part of my root element bill:Receipt which is one of my requirements.

Is there another way to do it? Thanks!

-- EDIT

Thanks for the clarifications. Here are two different inputs and outputs expected, based on the XSLT.

Is there some example to take a look? Thanks. I'm still getting use to XSLT thing.

Input 1

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bill ReceiptType="I">
    <payments/>
</bill>

Output 1: No payment namespace and no payment element

<?xml version="1.0" encoding="UTF-8"?>
<bill:Receipt xmlns:bill="http://www.website.io/bll/4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd" ReceiptType="I">
    <bill:Addendum></bill:Addendum>
</bill:Receipt>

Input 2

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bill ReceiptType="I">
    <payments>
        <payment>...</payment>
        <totals PaymentsTotal="150.0"></totals>
    </payments>
</bill>

Output 2: payment namespace and payment element

<?xml version="1.0" encoding="UTF-8"?>
<bill:Receipt xmlns:bill="http://www.website.io/bll/4" xmlns:payment="http://www.website.io/Payments2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd" ReceiptType="I">
    <bill:Addendum>
        <payment:Payments Version="2.0">
            <payment:Totals PaymentsTotal="150.0" />
        </payment:Payments>
    </bill:Addendum>
</bill:Receipt>

CodePudding user response:

In XSLT, namespaces are not attributes. Generally, you don't need to create namespace declaration: if you create your elements (and attributes) in the right namespace, the namespace declarations will be produced automatically.

If you really need to produce a namespace declaration on the root element, when it's not actually used in the name of the element or any of its attributes, then in XSLT 2.0 you can achieve that using the xsl:namespace instruction. In 1.0 it's more convoluted.

You may find this post helpful: How can I dynamically set the default namespace declaration of an XSLT transformation's output XML?

CodePudding user response:

The simple solution would be to do:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:bill="http://www.website.io/bll/4" 
xmlns:payment="http://www.website.io/Payments2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/bill">
    <bill:Receipt 
    xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd" 
    ReceiptType="{@ReceiptType}">
        <bill:Addendum>
            <xsl:if test="payments/payment">
                <payment:Payments Version="2.0">
                    <payment:Totals PaymentsTotal="{payments/totals/@PaymentsTotal}"/>
                </payment:Payments>
            </xsl:if>
        </bill:Addendum>
    </bill:Receipt>
</xsl:template>

</xsl:stylesheet>

This would produce a redundant namespace declaration in the case of input 1 - but that should cause no problems if the target application employs a conformant parser.

Since you say that in the case of input 2 you must have the namespace declaration on the root element, we must assume that the target application's parser does NOT conform to standard. To accommodate such limitation, you need to convolute your code with some inelegant workaround - for example:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:bill="http://www.website.io/bll/4" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/bill">
    <xsl:choose>
        <xsl:when test="payments/payment">
            <bill:Receipt 
            xmlns:payment="http://www.website.io/Payments2"
            xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd" 
            ReceiptType="{@ReceiptType}">
                <bill:Addendum>
                    <payment:Payments Version="2.0">
                        <payment:Totals PaymentsTotal="{payments/totals/@PaymentsTotal}"/>
                    </payment:Payments>
                </bill:Addendum>
            </bill:Receipt>
        </xsl:when>
        <xsl:otherwise>
            <bill:Receipt 
            xsi:schemaLocation="http://www.website.io/bll/4 http://www.website.io/site/bll/4/bllv40.xsd" 
            ReceiptType="{@ReceiptType}">
                <bill:Addendum/>
            </bill:Receipt>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Do note that the XSLT processor may decide to put the namespace declaration/s anywhere that is in-scope for the relevant nodes. I have tested the above with several processors, but your processor may behave differently. Ultimately, the best solution would be to have a conformant parser at the target application and avoid this or some other elaborate workaround.

  • Related