for-each no results text - xpath

I have a for-each and when there is nothing output by it I would like to display some default text.
If I have...
<xsl:for-each select='xpath'> Some content for each matching element result. </xsl:for-each>
What I would like is:
<xsl:for-each select='xpath'>
<xsl:output-this-when-there-is-content> Some content for each matching element result. </xsl:output-this-when-there-is-content>
<xsl:no-results-output> Some content for when the for-each finds no matches. </xsl:no-results-output>
</xsl:for-each>
Can anyone tell me how to do this, please?
Thanks,
Matt

Assuming you have:
<xsl:for-each select="xpath"> ...
The you can do something like:
<xsl:choose>
<xsl:when test="xpath">
<xsl:for-each select="xpath"> ...
</xsl:when>
<xsl:otherwise>
<xsl:text>Some default text</xsl:text>
</xsl:otherwise>
</xsl:choose>
To avoid the double test of the XPath (and duplication) you could probably use an xsl:variable, something like the following (syntax may be a little wrong, but the rough idea should be right).
<xsl:choose>
<xsl:variable name="elems" select="xpath"/>
<xsl:when test="$elems">
<xsl:for-each select="$elems"> ...
</xsl:when>
<xsl:otherwise>
<xsl:text>Some default text</xsl:text>
</xsl:otherwise>
</xsl:choose>

To avoid the verbosity of the <xsl:choose> solution that Greg Beech proposed, you can do:
<xsl:variable name="elems" select="xpath"/>
<xsl:for-each select="$elems">
<!-- ... -->
</xsl:for-each>
<xsl:if test="not($elems)">
<xsl:text>Some default text</xsl:text>
</xsl:if>
The <xsl:variable> is for efficiency, it avoids doing the same query twice.
The <xsl:for-each> only runs if there are any nodes in $elems, the <xsl:if> only runs if there are not.

I would think sth like this would be good:
If (x!=null)
{
console.write("Default Text")}
else
{
foreach (var y in x)
{
Console.Writeline(y);
//...
}
}

Related

Unable to use apply-templates with node-set - XSLT

I am trying to get distinct values of shelf and add them to xml tree using node-set. I am trying to print the values of node-set using apply-templates. But I am doing something wrong. Kindly correct me. Thanks
<xsl:template name="form_list">
<xsl:for-each select="//library/section/#shelf[generate-id()= generate-id(key('distinct_shelfs',.)[1])]">
<xsl:variable name="shelfs">
<ml>
<m><xsl:value-of select="."/></m>
</ml>
</xsl:variable>
</xsl:for-each>
<xsl:variable name="Shelf_Set"><xsl:value-of select="exsl:node-set($shelfs)/ml"/></xsl:variable>
</xsl:template>
<xsl:call-template name="PrintShelfs"/>
<xsl:template name="PrintShelfs">
<xsl:apply-templates select="$Shelf_Set" mode="m1"/>
</xsl:template>
<xsl:template match="ml" mode="m1" >
<a><xsl:value-of select='.'/></a>
</xsl:template>
The xsl:value-of instruction flattens a node-set to a single text node. I suspect you want simply
<xsl:variable name="Shelf_Set" select="exsl:node-set($shelfs)/ml"/>

xsl <choose>: How to select a following-sibling with a certain child?

I have the following fragment of a dictionary entry:
<entry>
<form>
<orth></orth>
<orth></orth>
</form>
<form>
<note></note>
<orth></orth>
</form>
</entry>
With xsl <choose> I want to select <form> only when the following <form> has <note> as a child. I tried
<xsl:template match="tei:form">
<xsl:choose>
<xsl:when test="following-sibling::*[1][name()='form' and child='note']">
<xsl:apply-templates/><text>\ </text>
</xsl:when>
</xsl:choose>
</xsl:template>
But this does not work. How should I correctly address <form> with <note> as child?
Your question is confusing. xsl:choose does not select anything.
If you want to use xsl:choose in the context of form - IOW, you want to process all form elements and choose which code should be executed based on the existence of note in the immediately following sibling - try something like:
<xsl:template match="form">
<xsl:choose>
<xsl:when test="following-sibling::form[1]/note">
<!-- DO SOMETHING -->
</xsl:when>
<xsl:otherwise>
<!-- DO SOMETHING ELSE -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
In order to process only the form elements that satisfy the condition, try instead:
<xsl:template match="entry">
<!-- ... -->
<xsl:for-each select="form[following-sibling::form[1]/note]">
<!-- DO SOMETHING -->
</xsl:for-each>
<!-- ... -->
</xsl:template>
If you are going to apply templates to all of your form elements, then you can avoid the use of conditional instrucctions and just use patterns like:
<xsl:template match="form">
<!-- General case -->
</xsl:template>
<xsl:template match="form[following-sibling::form[1]/note]">
<!-- Particular case -->
</xsl:template>
Do note: these patterns have different default priority, thus the template to apply is perfectly determinded.

Saxon he 9.4 performance -xslt

I have following two templates in xslt :
<xsl:template name="calculateAbsoluteEntryNodeIndex">
<!-- current 'entry' node -->
<xsl:param name="entryNode"/>
<!-- current 'entry' node index (position) in xml tree-->
<xsl:param name="entryNodePosition"/>
<xsl:choose>
<!--if the current 'entry' node contains 'namest' attribute then its ('namest') value is treated as
the absolute index (of the current 'entry' node)-->
<xsl:when test="$entryNode/#namest">
<!--writing result-->
<xsl:value-of select="number($entryNode/#namest)"/>
<xsl:text>;</xsl:text>
<xsl:value-of select="number($entryNode/#nameend)"/>
</xsl:when>
<xsl:otherwise>
<!--getting last 'Nameend' attribute value-->
<xsl:variable name="lastNameEndValue">
<xsl:choose>
<!--check if exists any 'entry' node before the current 'entry' node (on the current 'row' level) having 'nameend' attribute defined
('entry' has to have index number less than $entryNodePosition) -->
<xsl:when test="$entryNode/preceding-sibling::entry[#nameend]">
<!--get 'named' attribute value of the last matched "entry" node and convert it to number -->
<xsl:value-of select="number(($entryNode/preceding-sibling::entry/#nameend)[last()])"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!--getting 'entry' node index of the matched 'Nameend' attribute -->
<xsl:variable name="lastNameendNodePosition">
<xsl:choose>
<!-- if lastNameEndValue != 0 -->
<xsl:when test="$lastNameEndValue != '0'">
<!-- calculate index of the 'entry' node matched in $lastNameEndValue selection =>it is done by counting all preceding siblings of the node matched in
$lastNameEndValue increased by 1-->
<xsl:value-of select="count(($entryNode/preceding-sibling::entry[#nameend])[last()]/preceding-sibling::entry) + 1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!--writing result-->
<xsl:value-of select="$entryNodePosition - $lastNameendNodePosition + $lastNameEndValue"/>
<xsl:text>;</xsl:text>
<xsl:value-of select="$entryNodePosition - $lastNameendNodePosition + $lastNameEndValue"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And
I runned some profiling that goes with saxon he 9.4.-TP:profile.html
template calculateAbsoluteEntryNodeIndex 25268028
Total time spent on this template is 174966.587ms.
Whole xslts is executing in Total time: 337196.696 milliseconds.
It seems that is having a problems with transfomration of big tables around 14 thousands lines of xml. Any idea what can be issue here.
Structure of table is.
<?xml version="1.0" encoding="UTF-8"?>
<table tabledef="excel">
<tgroup cols="1">
<colspec colname="1" colnum="1" colwidth="100%"/>
<thead>
<row>
<entry morerows="1">
<p>
Text
</p>
</entry>
</thead>
<tbody>
<row>
<entry align="left">
<p>1</p>
</entry>
</row>
</tbody>
</tgroup>
</table>
There's not really enough information here: for example, what's the typical number of sibling entry elements within a row? And how often is this template executed? I guess you are probably executing it once per entry, and it is obviously quadratic in the number of entries.
The repetition of the expression $entryNode/preceding-sibling::entry[#nameend] is obviously wasteful.
It's very hard to offer advice on whether there are other ways of writing this that would go faster, without knowing anything about what the code is actually doing. Perhaps using xsl:number for some of the counting would work better; it's very difficult to tell. Alternatively, instead of doing a "for-each" that processes each entry independently, consider doing sibling recursion that works forwards through the nodes, passing parameter information about nodes already processed so you don't have to search backwards to preceding-sibling nodes.

XPath joining multiple elements

I'm looking a way to join every two elements together using XPath2.0:
<item>
<element class='1'>el1</element>
<element class='2'>el2</element>
<break>break</break>
<element class='1'>el3</element>
<element class='2'>el4</element>
<break>break</break>
<element class='1'>el5</element>
<element class='2'>el6</element>
<break>break</break>
<element class='1'>el7</element>
<element class='2'>el8</element>
</item>
I am hoping the result will be like:
el1el2
el3el4
el5el6
el7el8
The are "breaks" between two meaningful elements, and there are also classes to help out, but still I cannot get it done.
Since I'm not familiar with XPath, this is what I can come up so far, and turned out to be wrong, since concatenate needs at least two arguments...
//item/concat(element[preceding-sibling::break | following-sibling::break])
//item/element[#class='1']/concat(., following-sibling::element[1])
You want your result sequence to contain one item for each of the class='1' elements, the value of that item being the concatenation of that element and its next sibling (the matching class='2').
I'm not sure if you're also open for an XSLT 1.0 solution but this works for me with your input xml:
<xsl:template match="/item">
<xsl:apply-templates select="element[1]|break"/>
</xsl:template>
<xsl:template match="element[1]">
<xsl:text>
</xsl:text>
<xsl:value-of select="."/>
<xsl:value-of select="following-sibling::*[1]"/>
</xsl:template>
<xsl:template match="break">
<xsl:text>
</xsl:text>
<xsl:value-of select="following-sibling::*[1]"/>
<xsl:value-of select="following-sibling::*[2]"/>
</xsl:template>
I have two templates that match either the start element or the break element. I use the following-sibling axis to get to the next two elements. The <xsl:text> elements are there to force a linebreak.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="item">
<xsl:for-each-group select="*" group-starting-with="break">
<xsl:if test="current-group()[1][self::break]">
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:value-of select="current-group()[self::element]" separator=""/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

Treating empty document fragments as empty results

What is the most clean way to treat a variable that contains an empty document fragment as an empty variable?
I have the following code
<xsl:variable name="embedded-examples" select="d:example"/>
<xsl:variable name="external-examples">
<xsl:call-template name="external-examples"/>
</xsl:variable>
<xsl:variable name="examples" select="$embedded-examples | $external-examples"/>
<xsl:if test="$examples">
<d:section>
<d:title>
<xsl:text>Examples</xsl:text>
</d:title>
<xsl:for-each select="$examples">
<xsl:copy-of select="."/>
</xsl:for-each>
</d:section>
</xsl:if>
And the template
<xsl:template name="external-examples">
<xsl:variable name="example-comments" select="$example-docs//comment()"/>
<xsl:for-each select="$example-comments">
<d:example>
<d:title><xsl:value-of select="normalize-space(.)"/></d:title>
</d:example>
</xsl:for-each>
</xsl:template>
The problem is that when the variable in the esternal-examples is empty the xsl:for-each loop is not run, but it produces an empty document fragment anyhow, making the test test="$examples" pass instead of fail.
What should I do in the template to make sure that when example-comments is empty the template returns nothing?
Use a predicate to check for child nodes:
<xsl:for-each select="$example-comments[count(child::node() ) > 0]">
...
</xsl:for-each>

Resources