0

I am using XLST 1.0 and want to Transform XML to add 'nil' attributes to empty elements. I am finding that the namespace is being added to each matching element e.g my output looks like: <age xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />

I know this is valid but I'd rather it was added to my top-node. I came across this answer: How can I add namespaces to the root element of my XML using XSLT?

However I have multiple possible root nodes, so I thought I could do something like this:

  <xsl:template match="animals | people | things">
    <xsl:element name="{name()}">
      <xsl:attribute name="xmlns:xsi">http://www.w3.org/2001/XMLSchema-instance</xsl:attribute>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

However I get an error from Visual Studio "prexix xmlns not defined" and I'm not sure what to do about this.

Here is my total XLST file (it won't paste into SO for some reason) which tries to do a few things:

  1. Transform different types of animal into a single type
  2. Add the namespace to the root node
  3. Add xsi:nil = true to empty elements (note they must have no children not just no text, or my top-level node gets transformed)
Community
  • 1
  • 1
Mr. Boy
  • 60,845
  • 93
  • 320
  • 589

1 Answers1

0

First, a namespace declaration is not an attribute, and cannot be created using the xsl:attribute instruction.

You can use xsl:copy-of to insert a namespace declaration "manually" at a desired location, for example:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="/*">
    <xsl:copy>
        <xsl:copy-of select="document('')/xsl:stylesheet/namespace::xsi"/>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*[not(node())]">
    <xsl:copy>
        <xsl:attribute name="xsi:nil">true</xsl:attribute>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

However, the results are rather processor- dependent. Xalan, for example, will ignore this instruction and repeat the declaration at every empty node it outputs as before. In general, you have very little to no control over how your XSLT processor serializes the output.

Another option is to actually use the namespace declaration at the root level, say:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

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

<xsl:template match="/*">
    <xsl:copy>
        <xsl:attribute name="xsi:nil">false</xsl:attribute>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*[not(node())]">
    <xsl:copy>
        <xsl:attribute name="xsi:nil">true</xsl:attribute>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

This worked with all the processors I have tested with (YMMV).

Of course, the best option is to do nothing, since as you have noted, the difference is purely cosmetic.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51