Search code examples
xmlxsltxslt-1.0xslt-2.0xslt-grouping

How to not copy an attribute in the output of XSLT 2.0


I want to transform the one XML message into other. My input message currently contains some empty elements with attributes @nil=ture value. What I want is that these elements should be created empty but without the nill attribute. Please see below my current progess:

Input XML:

<?xml version="1.0" encoding="UTF-8"?>
    <collection>
        <row>
            <nr>A00</nr>
            <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
        </row>
        <row>
            <nr>A01</nr>
            <type>mash</type>
        </row>
    </collection>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:template match="//*[local-name()='collection']">
        <jsonArray>
            <xsl:text disable-output-escaping="yes">&lt;?xml-multiple?&gt;</xsl:text>
            <xsl:for-each select="//*[local-name()='row']">
                <jsonObject>
                     <xsl:copy-of select="node() except @nil" />
                </jsonObject>
            </xsl:for-each>
        </jsonArray>
    </xsl:template>
</xsl:stylesheet>

Current Output:

<?xml version="1.0" encoding="UTF-8"?>
<jsonArray>
   <?xml-multiple?>
   <jsonObject>
      <nr>A00</nr>
      <type xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />
   </jsonObject>
   <jsonObject>
      <nr>A01</nr>
      <type>mash</type>
   </jsonObject>
</jsonArray>

Expected Output:

<?xml version="1.0" encoding="UTF-8"?>
<jsonArray>
   <?xml-multiple?>
   <jsonObject>
      <nr>A00</nr>
      <type/>
   </jsonObject>
   <jsonObject>
      <nr>A01</nr>
      <type>mash</type>
   </jsonObject>
</jsonArray>

Solution

  • When you do <xsl:copy-of select="node() except @nil" /> you are copying the child elements of the current row, which will copy them without changes. The except @nil won't do what you expect because it will be looking for attributes called @nil on the current row element (and the attribute you are looking for is @xsi:nil anyway.

    Instead replace xsl:copy-of with xsl:apply-templates and add the identity template to your XSLT (with a slight tweak to remove namespace declarations).

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

    Then, you just need a template to ignore xsl:type

        <xsl:template match="@xsi:nil" />
    

    Try this XSLT

    <xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        exclude-result-prefixes="xsi">
    
        <xsl:template match="@*|node()">
            <xsl:copy copy-namespaces="no">
                <xsl:apply-templates select="@*|node()" />
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="@xsi:nil" />
    
        <xsl:template match="//*[local-name()='collection']">
            <jsonArray>
                <xsl:processing-instruction name="xml-multiple" />
                <xsl:for-each select="//*[local-name()='row']">
                    <jsonObject>
                         <xsl:apply-templates select="@*|node()" />
                    </jsonObject>
                </xsl:for-each>
            </jsonArray>
        </xsl:template>
    </xsl:stylesheet>
    

    (Do note you should really use xsl:processing-instruction to create processing instructions).