1
votes

I am facing an xslt/xpath problem and hope someone could help, in a few words here is what I try to achieve.

I have to transform an XML document where some nodes may be missing, these missing nodes are mandatory in the final result. I have the set of mandatory node names available in an xsl:param.

The base document is:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TRANSFORM.xslt"?>
<BEGIN>
    <CLIENT>
        <NUMBER>0021732561</NUMBER>
        <NAME1>John</NAME1>
        <NAME2>Connor</NAME2>
    </CLIENT>

    <PRODUCTS>
        <PRODUCT_ID>12</PRODUCT_ID>
            <DESCRIPTION>blah blah</DESCRIPTION>
    </PRODUCTS>

    <PRODUCTS>
        <PRODUCT_ID>13</PRODUCT_ID>
            <DESCRIPTION>description ...</DESCRIPTION>
    </PRODUCTS>

    <OPTIONS>
            <OPTION_ID>1</OPTION_ID>
            <DESCRIPTION>blah blah blah ...</DESCRIPTION>
    </OPTIONS>

    <PROMOTIONS>
            <PROMOTION_ID>1</PROMOTION_ID>
            <DESCRIPTION>blah blah blah ...</DESCRIPTION>
    </PROMOTIONS>

</BEGIN>

Here is the stylesheet so far:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>

    <xsl:param name="mandatoryNodes" as="xs:string*" select=" 'PRODUCTS', 'OPTIONS', 'PROMOTIONS' "/>

    <xsl:template match="/">
        <xsl:apply-templates select="child::node()"/>
    </xsl:template>

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

    <xsl:template match="BEGIN">
        <xsl:element name="BEGIN">
            <xsl:for-each select="$mandatoryNodes">
                <!-- If there is no node with this name -->
                <xsl:if test="count(*[name() = 'current()']) = 0"> 
                    <xsl:element name="{current()}" />
                </xsl:if> 
            </xsl:for-each>
            <xsl:apply-templates select="child::node()"/>
        </xsl:element>
    </xsl:template>

</xsl:stylesheet>

I tried the transformation in XML Spy, the xsl:iftest failed saying that 'current item is PRODUCTS of type xs:string.

I've tried the same xsl:if outside of a for-each and it seems to work ... what am I missing ?

1

1 Answers

2
votes

Inside of <xsl:for-each select="$mandatoryNodes"> the context item is a string but you want to access the primary input document and its nodes so you need to store that document or the template's context node in a variable and use that e.g.

<xsl:template match="BEGIN">
    <xsl:variable name="this" select="."/>
    <xsl:element name="BEGIN">
        <xsl:for-each select="$mandatoryNodes">
            <!-- If there is no child node of `BEGIN` with this name -->
            <xsl:if test="count($this/*[name() = current()]) = 0"> 
                <xsl:element name="{current()}" />
            </xsl:if> 
        </xsl:for-each>
        <xsl:apply-templates select="child::node()"/>
    </xsl:element>
</xsl:template>