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

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?

Related

Process delimited text with xslt 1.0

I need to to process a delimited text with xslt that looks like:
abc#mail;#6896;#def#mail;#7467;#hij#mail
The output should be a mailto: link with all addresses and I need to throw away the numbers between them. I am working on Sharepoint, so can only use XSLT 1.0
Edit:
I found this question from about 7 years ago, that is almost the same:
"Regular expression"-style replace in XSLT 1.0
In my case, it doing exactly the opposite: keeping the numbers and throwing out the mail addresses. Can someone help me how to modify the code in the address?
update: as a workaround, I added the delimiter as prefix to my text, and the template works perfect.
This specifically looks for numbers rather than skipping every other item.
<xsl:template name="ExtractNumbers">
<xsl:param name="strInputString"/>
<xsl:variable name="strCurrent" select="substring-before($strInputString, ';#')" />
<xsl:variable name="strRemaining" select="substring-after($strInputString, ';#')" />
<xsl:if test="$strCurrent != ''">
<xsl:if test="string(number($strCurrent)) != 'NaN'">
<xsl:value-of select="$strCurrent" />
<xsl:if test="contains($strRemaining, ';#')">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:if>
<xsl:call-template name="ExtractNumbers">
<xsl:with-param name="strInputString" select="$strRemaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

Using XSL Choose to Substitute Empty String

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.

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>

How-to Test if Umbraco property is empty

I'm can't figure out how to test if a non-mandatory property is empty in my umbraco site, and currently my pages cause an XSLT parsing error if said property is empty. My current code is simple:
<xsl:variable name="media" select="umbraco.library:GetMedia(sectionImage, 0)" />
<xsl:if test="$media"> <!-- or $media != null -->
<xsl:variable name="url" select="$media/umbracoFile" />
<xsl:element name="img">
<xsl:attribute name="src"><xsl:value-of select="$url" />
</xsl:attribute>
</xsl:element>
</xsl:if>
I'm running Umbraco v6.0.6 and I've using the error checking solution as provided on the umbraco wiki, at http://our.umbraco.org/wiki/reference/umbracolibrary/getmedia
When I tried a similar style logic in C# I found that the test variable, $media, would have a value such as "umbraco.presentation.nodeFactory.Property." This filler content would wrongly bypass the if test, and then cause a break down.
This is happening on a variety of data types; media files, text strings, integers, etc.
Thank you for your time reading my post.
umbraco.library:GetMedia cannot return null, you may get an error back if no media was matched, in example
<error>No media is maching '123123'</error>
The thing is that your code works but you dont close the <xsl:attribute name="src"> as you should
<xsl:attribute name="src">
<xsl:value-of select="$url" />
</xsl:attribute>
If you for some reason really like to ensure that there is an image in there you should write a "test" and count values in the nodeTypeAlias
<xsl:variable name="media" select="umbraco.library:GetMedia(sectionImage, 0)" />
<xsl:if test="count($media[#nodeTypeAlias='Image']) > 0">
<xsl:variable name="url" select="$media/umbracoFile" />
<xsl:element name="img">
<xsl:attribute name="src">
<xsl:value-of select="$url" />
</xsl:attribute>
</xsl:element>
</xsl:if>
The xpath becomes a bit "odd" since you work with zero depth selection, if you fetch media with a greater depth you must adjust the count a bit
<xsl:variable name="media" select="umbraco.library:GetMedia(sectionImage, 0)" />
<xsl:if test="count($media/*[#nodeTypeAlias='Image']) > 0">
...
</xsl:if>
To test if a folder was selected simply check for folder instead
<xsl:variable name="media" select="umbraco.library:GetMedia(sectionImage, 0)" />
<xsl:if test="count($media[#nodeTypeAlias='Folder']) > 0">
...
</xsl:if>
Good luck
Okay so the initial IF condition has to do this:
<xsl:if test = "sectionImage != ''">
Which is how XSLT/Xpath checks if a XML tag is null. Then Eric Herlitz's solution can be used, or a dummy approach to just start assigning values without the secondary IF condition.

Get current nodes xpath

I need to get the xpath of current node for which i have written an xsl function
<func:function name="fn:getXpath">
<xsl:variable name="xpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat($xpath, name())" />
<xsl:if test="not(position()=last())">
<xsl:value-of select="concat('/', $xpath)" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$xpath" />
</func:function>
But when I run this, I'm getting the following error
file:///D:/test.xsl; Line #165; Column #63; Variable accessed before it is bound!
file:///D:/test.xsl; Line #165; Column #63; java.lang.NullPointerException
I'm using xalan 2.7.0. Please help.
In your example you are trying to use the variable in the definition itself, which is not valid.
It looks your intention is to try and modify the value of an existing value. However XSLT is a functional language, and as a result variables are immutable. This means you cannot change the value once defined.
In this case, you don't need to be so complicated. You can just remove the reference to the variable itself, and you will get the result you need
<func:function name="fn:getXpath">
<xsl:variable name="xpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="name()"/>
<xsl:if test="not(position()=last())">
<xsl:value-of select="'/'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$xpath" />
</func:function>
You are using the variable $xpath inside the definition of the variable itself:
<func:function name="fn:getXpath">
<xsl:variable name="xpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat($xpath, name())" /> <-------
<xsl:if test="not(position()=last())">
<xsl:value-of select="concat('/', $xpath)" /> <-------
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$xpath" />
</func:function>
The variable is not known at that point.