Excluding XML empty nodes with XSLT

xml, xslt

Solution

Here's the problem: not all XML fields are being used in every record. So, when the XSLT adds the boilerplate text it appears even in the records that don't include the XML elements.

This is what templates are for -- not using templates in XSLT is like not using classes in an OO programming language.

This simple transformation (notice that not a single conditional instruction has been used):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes" />
    <xsl:strip-space elements="*"/>

    <xsl:variable name="vNL" select="'&#xA;'"/>
    <xsl:template match="/">
        <root>
            <story>
              <xsl:apply-templates select="root/story/CL"/>
            </story>
        </root>
    </xsl:template>

    <xsl:template match="CL">
      <xsl:apply-templates select="CityDescription"/>
      <xsl:apply-templates select="BK"/>
    </xsl:template>

    <xsl:template match="CityDescription | CompanyName | address">
        <xsl:element name="{name()}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="BK">
      <xsl:value-of select="$vNL"/>
      <xsl:apply-templates select="CompanyName"/>
      <xsl:apply-templates select="address"/>
      <xsl:apply-templates select="HoldingCo"/>
      <xsl:apply-templates select="TotalAssets"/>
      <xsl:apply-templates select="TotalLiabilities"/>
      <xsl:apply-templates select="TotalDeposits"/>
      <xsl:apply-templates select="EquityCapital"/>
    </xsl:template>

    <xsl:template match="HoldingCo">
        <HoldingCo>
            <xsl:text>Holding Co: </xsl:text>
            <xsl:value-of select="."/>
        </HoldingCo>
    </xsl:template>

    <xsl:template match="TotalAssets">
        <TotalAssets>
            <xsl:text>Total Assets: </xsl:text>
            <xsl:value-of select="."/>
        </TotalAssets>
    </xsl:template>

    <xsl:template match="TotalLiabilities">
        <TotalLiabilities>
            <xsl:text>Total Liabilities: </xsl:text>
            <xsl:value-of select="."/>
        </TotalLiabilities>
    </xsl:template>

    <xsl:template match="TotalDeposits">
        <TotalDeposits>
            <xsl:text>Total Deposits: </xsl:text>
            <xsl:value-of select="."/>
        </TotalDeposits>
    </xsl:template>

    <xsl:template match="EquityCapital">
        <EquityCapital>
            <xsl:text>Total Assets: </xsl:text>
            <xsl:value-of select="."/>
        </EquityCapital>
    </xsl:template>

    <xsl:template match="text()"/>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <story>
        <CL>
            <CityDescription>City One</CityDescription>
            <BK>
                <CompanyName>Corporate Name</CompanyName>
                <address>123 Main St</address>
                <HoldingCo>Company Name</HoldingCo>
                <TotalAssets>128,319,000</TotalAssets>
                <TotalLiabilities>117,059,000</TotalLiabilities>
                <TotalDeposits>89,847,000</TotalDeposits>
                <EquityCapital>11,260,000</EquityCapital>
            </BK>
            <BK>
                <CompanyName>Smaller Company</CompanyName>
                <address>123 Central St</address>
            </BK>
        </CL>
        <CL>
            <CityDescription>City Two</CityDescription>
            <BK>
                <CompanyName>Corporate Name Three</CompanyName>
                <address>123 High St</address>
                <HoldingCo>Company Name</HoldingCo>
                <TotalAssets>128,319,000</TotalAssets>
                <TotalLiabilities>117,059,000</TotalLiabilities>
                <TotalDeposits>89,847,000</TotalDeposits>
                <EquityCapital>11,260,000</EquityCapital>
            </BK>
            <BK>
                <CompanyName>Smaller Company Four</CompanyName>
                <address>123 Jones St</address>
            </BK>
        </CL>
    </story>
</root>

produces the wanted, correct result:

<root>
   <story>
      <CityDescription>City One</CityDescription>

      <CompanyName>Corporate Name</CompanyName>
      <address>123 Main St</address>
      <HoldingCo>Holding Co: Company Name</HoldingCo>
      <TotalAssets>Total Assets: 128,319,000</TotalAssets>
      <TotalLiabilities>Total Liabilities: 117,059,000</TotalLiabilities>
      <TotalDeposits>Total Deposits: 89,847,000</TotalDeposits>
      <EquityCapital>Total Assets: 11,260,000</EquityCapital>

      <CompanyName>Smaller Company</CompanyName>
      <address>123 Central St</address>
      <CityDescription>City Two</CityDescription>

      <CompanyName>Corporate Name Three</CompanyName>
      <address>123 High St</address>
      <HoldingCo>Holding Co: Company Name</HoldingCo>
      <TotalAssets>Total Assets: 128,319,000</TotalAssets>
      <TotalLiabilities>Total Liabilities: 117,059,000</TotalLiabilities>
      <TotalDeposits>Total Deposits: 89,847,000</TotalDeposits>
      <EquityCapital>Total Assets: 11,260,000</EquityCapital>

      <CompanyName>Smaller Company Four</CompanyName>
      <address>123 Jones St</address>
   </story>
</root>

As the child elements of `BK` are processed in document order, the matching template can be simplified to just:

<xsl:template match="BK">
    <xsl:value-of select="$vNL"/>
    <xsl:apply-templates/>
</xsl:template>

The same is valid for the template matching `CL` -- it can be replaced by:

<xsl:template match="CL">
    <xsl:apply-templates/>
</xsl:template>

Finally, this template can be completely removed, because it copies exactly the XSLT built-in template that matches any element.

Thus, the transformation after these refactorings is:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes" />
    <xsl:strip-space elements="*"/>

    <xsl:variable name="vNL" select="'&#xA;'"/>
    <xsl:template match="/">
        <root>
            <story>
              <xsl:apply-templates select="root/story/CL"/>
            </story>
        </root>
    </xsl:template>

    <xsl:template match="CityDescription | CompanyName | address">
        <xsl:element name="{name()}">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="BK">
        <xsl:value-of select="$vNL"/>
        <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="HoldingCo">
        <HoldingCo>
            <xsl:text>Holding Co: </xsl:text>
            <xsl:value-of select="."/>
        </HoldingCo>
    </xsl:template>

    <xsl:template match="TotalAssets">
        <TotalAssets>
            <xsl:text>Total Assets: </xsl:text>
            <xsl:value-of select="."/>
        </TotalAssets>
    </xsl:template>

    <xsl:template match="TotalLiabilities">
        <TotalLiabilities>
            <xsl:text>Total Liabilities: </xsl:text>
            <xsl:value-of select="."/>
        </TotalLiabilities>
    </xsl:template>

    <xsl:template match="TotalDeposits">
        <TotalDeposits>
            <xsl:text>Total Deposits: </xsl:text>
            <xsl:value-of select="."/>
        </TotalDeposits>
    </xsl:template>

    <xsl:template match="EquityCapital">
        <EquityCapital>
            <xsl:text>Total Assets: </xsl:text>
            <xsl:value-of select="."/>
        </EquityCapital>
    </xsl:template>

    <xsl:template match="text()"/>
</xsl:stylesheet>

Explanation:

The instruction:

<xsl:apply-templates select="someChildName"/>

only applies templates (performs processing) if a `someChildName` child node exists.

Problem

I have some very simple XML. I am recreating some XML and adding some boilerplate text into the final XML file to import into InDesign when I'm done. Here's the problem: not all XML fields are being used in every record. So, when the XSLT adds the boilerplate text it appears even in the records that don't include the XML elements. I tried using the choose >> when >> otherwise to look for the element, then use the element if is there, or ignore the boilerplate and insert NOTHING if the element is not in the record. Here is some sample XML data: ``` <?xml version="1.0" encoding="UTF-8"?> <root> <story> <CL> <CityDescription>City One</CityDescription> <BK> <CompanyName>Corporate Name</CompanyName> <address>123 Main St</address> <HoldingCo>Company Name</HoldingCo> <TotalAssets>128,319,000</TotalAssets> <TotalLiabilities>117,059,000</TotalLiabilities> <TotalDeposits>89,847,000</TotalDeposits> <EquityCapital>11,260,000</EquityCapital> </BK> <BK> <CompanyName>Smaller Company</CompanyName> <address>123 Central St</address> </BK> </CL> <CL> <CityDescription>City Two</CityDescription> <BK> <CompanyName>Corporate Name Three</CompanyName> <address>123 High St</address> <HoldingCo>Company Name</HoldingCo> <TotalAssets>128,319,000</TotalAssets> <TotalLiabilities>117,059,000</TotalLiabilities> <TotalDeposits>89,847,000</TotalDeposits> <EquityCapital>11,260,000</EquityCapital> </BK> <BK> <CompanyName>Smaller Company Four</CompanyName> <address>123 Jones St</address> </BK> </CL> </story> </root> ``` Here is the XSLT that I was trying to use, but it added "Holding Co:" and "Total Assets:" even to the records that did not contain elements: ``` <?xml version="1.0" encoding="UTF-8"?><!-- DWXMLSource="Testing.xml" --> <!DOCTYPE xsl:stylesheet [ ]> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="XML" /> <xsl:template match="/"> <root> <story> <xsl:for-each select="root/story/CL"> <CityDescription><xsl:value-of select="CityDescription"/></CityDescription><xsl:text> </xsl:text> <xsl:for-each select="BK"> <CompanyName><xsl:value-of select="CompanyName"/></CompanyName><xsl:text> </xsl:text> <address><xsl:value-of select="address"/></address><xsl:text> </xsl:text> <HoldingCo><xsl:text>Holding Co: </xsl:text><xsl:value-of select="HoldingCo"/></HoldingCo><xsl:text> </xsl:text> <TotalAssets><xsl:text>Total Assets: </xsl:text><xsl:value-of select="TotalAssets"/></TotalAssets><xsl:text> </xsl:text> <TotalLiabilities><xsl:text>Total Liabilities: </xsl:text><xsl:value-of select="TotalLiabilities"/></TotalLiabilities><xsl:text> </xsl:text> <TotalDeposits><xsl:text>Total Deposits: </xsl:text><xsl:value-of select="TotalDeposits"/></TotalDeposits><xsl:text> </xsl:text> <EquityCapital><xsl:text>Total Assets: </xsl:text><xsl:value-of select="EquityCapital"/></EquityCapital><xsl:text> </xsl:text> </xsl:for-each> </xsl:for-each> </story> </root> </xsl:template> </xsl:stylesheet> ``` I tried using Choose >> When >> Otherwise to basically ignore the elements , et al when they do not appear in the data, but my output show nothing but the "otherwise" content. Any suggestions?

Original source

Related problems