4

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?

valentinas
  • 4,277
  • 1
  • 20
  • 27
Jim Maivald
  • 522
  • 6
  • 26
  • 3
    Learn to use template rules in your stylesheet, rather than a monolithic for-each. Then it will be very easy to refine the rules to cover special cases such as an element being empty. – Michael Kay Jun 05 '12 at 08:18

1 Answers1

4
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.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • @Demitre Thanks for the answer. I figured that was the problem, but I haven't used templates for this type of XML. Would CHOOSE >> WHEN >> OTHERWISE work at all? Or, does that need the same XML in every node? – Jim Maivald Jun 05 '12 at 13:09
  • @Demitre. I'm not familiar with the variable names you created here: Are these random names? Or do they have meaning? It looks like the select is an entity... – Jim Maivald Jun 05 '12 at 13:14
  • @JimMaivald: [Re: Would CHOOSE >> WHEN >> OTHERWISE work at all?] The answer is the same if you ask for C/C#/C++/Java etc. : "Would `if(...) then ... else ...` work at all?" – Dimitre Novatchev Jun 05 '12 at 13:21
  • @JimMaivald: I use the name `$vNL` to mean (and have the value of) "new-line-character". You have probably seen the abbreviations NL and CR that mean, respectively "new line" and "carriage return". I also start every variable name I define, with the character `v`, to avoid errors when I miss the starting `$` character for a variable reference and there happens to be an element with the same name in the source XML document -- this can result in a very difficult to detect, non-reported by the XSLT processor issue. – Dimitre Novatchev Jun 05 '12 at 13:26
  • thanks for all your help. One last question: this current XML does not have empty elements, the elements are just not exported. But, would your suggestion still work if the XML had elements like – Jim Maivald Jun 07 '12 at 19:47
  • @JimMaivald: In this case you'll have to replace in the cuddent code the corresponding `` with ``. There are many questions with good answers about "empty elements" -- just have a look at them. – Dimitre Novatchev Jun 07 '12 at 20:54
  • I have tried to use your suggestion, but I am getting the following error message: "No character data is allowed between top-level elements" I can't see any obvious errors. Do you have any ideas? – Jim Maivald Jun 07 '12 at 21:43
  • It seems when you copy code from the site it contains hidden characters that produce XSLT errors. You have to remove any whitespace from the posted code and then run it. It worked fine after I did that. Thanks – Jim Maivald Jun 07 '12 at 22:01
  • @JimMaivald: Yes copying SO code from the browser is sometimes tricky and indentation is usually lost. – Dimitre Novatchev Jun 07 '12 at 23:15
  • We are importing the XML into a print page layout so that the XML will appear exactly as it does in the transformed file. I need to put several of the data elements on the same line, such as and There is a subnode containing two data elements and that must appear on the same line. Can you suggest a template construction would create this result? I've been trying several different constructions but I can't get them to appear on one line with a colon between them. – Jim Maivald Jun 08 '12 at 19:56
  • @JimMaivald: I greatly recommend that you ask this as a new, separate question. – Dimitre Novatchev Jun 08 '12 at 20:42