Using XSL Choose to Substitute Empty String - xslt

I'm trying to substitute an empty value in a csv file with a number.
Here's an example:
1111111,,11222
So I tried this:
<xsl:template match="/">
<xsl:apply-templates select="//tr" />
</xsl:template>
<xsl:template match="tr">
<document>
<content name="title">
<xsl:value-of select="td[1]/text()" />
</content>
<content name="loanID">
<xsl:value-of select="td[1]/text()" />
</content>
<content name="cNumber">
<xsl:variable name="score" select="td[2]/text()" />
<xsl:choose>
<xsl:when test="$score=''">
<xsl:value-of select="550" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="td[18]/text()" />
</xsl:otherwise>
</xsl:choose>
</content>
</document>
</xsl:template>
I constantly get a null value for the cNumber node when the value is empty in the row, and I'm expecting my code to substitute the empty value for '550'. What am I doing wrong? I checked this question here: and it seems like this should work. I'm using a special application for this but my guess is the fault lies with me.
Thanks

If the td element is empty, then td/text() returns an empty sequence/node-set, and when you compare an empty sequence to '', the result is false. This is one of the reasons that many people advise against using text(). You're not interested here in the text node, you are interested in the string value of the td element, and to get that you should use
<xsl:variable name="score" select="string(td[2])" />
Your other uses of text() are also incorrect, though you're only likely to see a problem if your XML input contains comments or processing instructions. But you should get out of this coding habit, and replace
<xsl:value-of select="td[1]/text()" />
by
<xsl:value-of select="td[1]" />
As a general rule, when you see /text() in an XPath expression it's usually wrong.

Related

Exclude first element of a certain type when doing apply-templates

This is my source XML:
<DEFINITION>
<DEFINEDTERM>criminal proceeding</DEFINEDTERM>
<TEXT> means a prosecution for an offence and includes –</TEXT>
<PARAGRAPH>
<TEXT>a proceeding for the committal of a person for trial or sentence for an offence; and</TEXT>
</PARAGRAPH>
<PARAGRAPH>
<TEXT>a proceeding relating to bail –</TEXT>
</PARAGRAPH>
<TEXT>but does not include a prosecution that is a prescribed taxation offence within the meaning of Part III of the Taxation Administration Act 1953 of the Commonwealth;</TEXT>
</DEFINITION>
This is my XSL:
<xsl:template name="DEFINITION" match="DEFINITION">
<xsl:element name="body">
<xsl:attribute name="break">before</xsl:attribute>
<xsl:element name="defn">
<xsl:attribute name="id" />
<xsl:attribute name="scope" />
<xsl:value-of select="DEFINEDTERM" />
</xsl:element>
<xsl:element name="text">
<xsl:value-of select="replace(TEXT[1],'–','--')" />
</xsl:element>
</xsl:element>
<xsl:apply-templates select="*[not(self::TEXT[1])]" />
</xsl:template>
As per my XSL, I want to do something with the DEFINEDTERM element and the TEXT element that immediately follows it.
Then I want to apply-templates to the rest of the elements, except for the DEFINEDTERM and TEXT element that have already been dealt with. Most importantly, I don't want to apply templates to the first TEXT element.
How do I achieve this, because my XSL above does not work.
I have other templates for TEXT and PARAGRAPH, but not DEFINEDTERM. I have <xsl:template match="*|#*" /> at the top of the XSL.
You did not post the expected result nor a minimal reproducible example, so I can only guess you want to do:
<xsl:template match="DEFINITION">
<body break="before">
<defn id="" scope="">
<xsl:value-of select="DEFINEDTERM" />
</defn>
<text>
<xsl:value-of select="replace(DEFINEDTERM/following-sibling::TEXT[1],'–','--')" />
</text>
</body>
<xsl:apply-templates select="* except (DEFINEDTERM | DEFINEDTERM/following-sibling::TEXT[1])" />
</xsl:template>
At least that's what I understand as:
I want to do something with the DEFINEDTERM element and the TEXT element that immediately follows it.
This is assuming you are using XSLT 2.0 or higher (otherwise you would not be able to use the replace() function).
--
P.S. You might want to make this a bit more efficient by defining DEFINEDTERM/following-sibling::TEXT[1] as a variable first, then referring to the variable instead.

Can I check if an XSLT parameter is "templatable" before running apply-templates?

I have a parameter in my XSLT which usually is a proper set of nodes that I apply templates on.
<apply-templates select="$conferences" />
However, sometimes something goes wrong and it comes in as a string. In this case I just want to skip applying templates. But what is the proper way of checking this? I could check that it's not a string of course, but how can I check that the parameter is... "templatable"?
<if test=" ? ">
<apply-templates select="$conferences" />
</if>
Since you're in XSLT 2.0 you can simply do
<xsl:if test="$conferences instance of node()*">
You can do:
<apply-templates select="$conferences/*" />
Which will only apply if there is an XML in it. Strings will not be applied.
If you want to do a condition up front, do something like:
<xsl:choose>
<xsl:when test="count($conferences/*) > 0"> <!-- it is XML -->
<xsl:apply-templates select="$conferences/*" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$conferences" /> <!-- it is not XML -->
</xsl:otherwise>
</xsl:choose>

Displaying xml nodes dynamically and applying style to specific nodes using recursion

Below is my xml file.
<xml>
<top>
<main>
<firstname>John</firstname>
<lastname>John</lastname>
<table></table>
<chapter>
<firstname>Alex</firstname>
<lastname>Robert</lastname>
<p>Sample text chap</p>
<figure name="f1.svg"></figure>
<chapter>
<firstname>Rebec</firstname>
<lastname></lastname>
<p>Sample text</p>
<figure name="f2.svg"></figure>
</chapter>
</chapter>
</main>
</top>
</xml>
Desired output:
<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg
Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime.
I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations.
This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown.
And how do I match only 'lastname' and make it bold?
I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes.
Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.
XSLT:
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>
<xsl:template name="FetchNodes">
<xsl:param name="endIndex" />
<xsl:param name="startIndex" />
<xsl:param name="context" />
<xsl:if test="$startIndex <= $endIndex">
<xsl:if test="$context[$startIndex][name() = 'table']"">
<xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
</xsl:if>
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$endIndex" />
<xsl:with-param name="startIndex" select="$startIndex + 1"/>
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="table">
<xsl:value-of select="node()" />
</xsl:template>
With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.
Can anybody suggest something?
The problem it displaying "f1.svg" instead of "f2.svg" is because of this line
<xsl:variable name="ImageName">
<xsl:value-of select="$root/*/chapter/figure/#name" />
</xsl:variable>
You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first #name attribute regardless of your context. It should look this this
<xsl:variable name="ImageName">
<xsl:value-of select="#name" />
</xsl:variable>
Or better still, like this
<xsl:variable name="ImageName" select="#name" />
Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example
<xsl:template match="figure" mode="figure">
As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:
<xsl:choose>
<xsl:when test="self::lastname">
<fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()" />
</xsl:otherwise>
</xsl:choose>
EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.
For example, to turn a chapter into an fo:block do this
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
To output the firstname in bold, do this
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
Try this XSLT as a starting point, and build on it
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="main">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
<xsl:template match="lastname"/>
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>

How do I render a comma delimited list using xsl:for-each

I am rendering a list of tickers to html via xslt and I would like for the list to be comma deliimited. Assuming I was going to use xsl:for-each...
<xsl:for-each select="/Tickers/Ticker">
<xsl:value-of select="TickerSymbol"/>,
</xsl:for-each>
What is the best way to get rid of the trailing comma? Is there something better than xsl:for-each?
<xsl:for-each select="/Tickers/Ticker">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="TickerSymbol"/>
</xsl:for-each>
In XSLT 2.0 you could do it (without a for-each) using the string-join function:
<xsl:value-of select="string-join(/Tickers/Ticker, ',')"/>
In XSLT 1.0, another alternative to using xsl:for-each would be to use xsl:apply-templates
<xsl:template match="/">
<!-- Output first element without a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()=1]" />
<!-- Output subsequent elements with a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()>1]">
<xsl:with-param name="separator">,</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Ticker">
<xsl:param name="separator" />
<xsl:value-of select="$separator" /><xsl:value-of select="TickerSymbol" />
</xsl:template>
I know you said xsl 2.0 is not an option and it has been a long time since the question was asked, but for all those searching for a posibility to do what you wanted to achieve:
There is an easier way in xsl 2.0 or higher
<xsl:value-of separator=", " select="/Tickers/Ticker/TickerSymbol" />
This will read your /Tickers/Ticker elements and insert ', ' as separator where needed
If there is an easier way to do this I am looking forward for advice
Regards Kevin

Keeping XSLT Code DRY with 'if' Tests and 'value-of' Selects

In XSLT, what is the preferred way to keep code DRY when it comes to 'if's?
At the moment I am doing this:
<xsl:if test="select/some/long/path">
<element>
<xsl:value-of select="select/some/long/path" />
</element>
</xsl:if>
I would prefer to only write "select/some/long/path" once.
I see your point. When the path is 200 chars long the code can get messy.
You could just add it to a variable
<xsl:variable name="path" select="select/some/long/path"/>
<xsl:if test="$path">
<xsl:value-of select="$path" />
</xsl:if>
Where is the difference between:
<xsl:if test="select/some/long/path">
<xsl:value-of select="select/some/long/path" />
</xsl:if>
and
<xsl:value-of select="select/some/long/path" />
? If it does not exist, value-of will output an empty string (i.e.: nothing). So why the test?