Is xsl:sequence always non-empty? - xslt

I don't understand output from this stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="root/sub"/>
</xsl:template>
<xsl:template match="sub">
<xsl:variable name="seq">
<xsl:sequence select="*" />
</xsl:variable>
<xsl:message>
<xsl:value-of select="#id" />
<xsl:text>: </xsl:text>
<xsl:value-of select="count($seq)" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>
when applied to following XML:
<root>
<sub id="empty" />
<sub id="one"><one/></sub>
<sub id="two"><one/><one/></sub>
<sub id="three"><one/><one/><one/></sub>
</root>
Output, written by xsl:message element, is:
empty: 1
one: 1
two: 1
three: 1
I expected this one instead:
empty: 0
one: 1
two: 2
three: 3
Why does count($seq) always return 1 in this case? How would you change variable definition, so that I can later test it for emptiness? (Simple <xsl:variable name='seq' select='*' /> would return expected answer, but is not an option ... I want to change between variable in this template, and test it for emptiness later).

Let me try to answer the "why" part of your question.
If you write the following statement:
<xsl:variable name="x" select="*" />
the variable $x contains the sequence of the child nodes of the current node. There's no implicit parent node in $x, because you use select. Now consider the following:
<xsl:variable name="x">
<content />
<content />
</xsl:variable>
where variable $x contains a sequence of one node: the parent node of content. Here, count($x) will always give you 1. To get the amount of content elements, you need to count the children of the $x implicit root node: count($x/content).
As a rule of thumb, you can remember that: if xsl:variable is itself an empty element with a select attribute, the result set will be assigned to the variable. If you use xsl:variable without the select attribute, you always create an implicit parent with the variable, and the contents of the variable are the children underneath it.
The same applies for your example with xsl:sequence as a child to xsl:variable. By having a non-empty xsl:variable you create an implicit parent for the sequence, which is why you always get 1 if you count the variable itself.
If you need both: a bundle of statements inside xsl:variable, but the behavior of the select attribute, you can workaround this by using the following:
<xsl:variable name="x" select="$y/*" />
<xsl:variable name="y">
<xsl:sequence select="foo" />
</xsl:variable>
which will now yield the expected amount for count($x), but whether or not that's really beneficial or makes your code clearer is arguable.

It works as you might expect if you either change the variable declaration to:
<xsl:variable name="seq" select="*"/>
or declare the type of the variable using 'as' attribute:
<xsl:variable name="seq" as="item()*">
<xsl:sequence select="*" />
</xsl:variable>
Not specifying any type information often leeds to surprising results in XSLT 2.0. If you are using Saxon, you can output how Saxon interprets the stylesheet using the explain extension attribute:
<xsl:template match="sub" saxon:explain="yes" xmlns:saxon="http://saxon.sf.net/">
<xsl:variable name="seq">
<xsl:sequence select="*" />
</xsl:variable>
<xsl:message>
<xsl:value-of select="#id" />
<xsl:text>: </xsl:text>
<xsl:value-of select="count($seq)" />
</xsl:message>
</xsl:template>
As you can see, Saxon constructs a document-node out of the sequence:
Optimized expression tree for template at line 6 in :
let $seq[refCount=1] as document-node() :=
document-constructor
child::element()
return
message

You are selecting the sub nodes, and then counting each node - so it'll always be 1. You need to count the children, for example:
<xsl:value-of select="count($seq/*)" />
will give you the output you're expecting.

Related

Attributes as arguments return empty values in xsl:contains() in XSLT

The variable $authors is declared as follows:
<listAuthor>
<author catalogNumber="26A.18.1" fullName="Pjotr Domanski"/>
<author catalogNumber="26A.19.1" fullName="Hermine Rex"/>
<author catalogNumber="26A.19.2" fullName="Christoferus Hohenzell"/>
</listAuthor>
Further, the following XML input is given:
<h1:Block Type="obj">
<h1:Field Type="5000" Value="77772001" />
<h1:Field Type="5209" Value="26A.19.1 : Lazy Tennis"/>
</h1:Block>
The expected output is:
<h1:Block Type="obj">
<h1:Field Type="5000" Value="77772001" />
<h1:Field Type="5209" Value="26A.19.1 : Lazy Tennis"/>
<h1:Field Type="9904" Value="Hermine Rex"/>
</h1:Block>
Here is a snippet of my transformation:
<xsl:template match="h1:Block">
<h1:Block Type="obj">
<xsl:copy-of select="child::*"/>
<h1:Field Type="9904">
<xsl:attribute name="Value"
select="$authors/listAuthor/author[contains (h1:Field[#Type='5209']/#Value, #catalogNumber)]/#fullName"/>
</h1:Field>
</h1:Block>
</xsl:template>
The name of the author should be put in h1:Field[#Type='9904']/#Value, when #catalogNumber in $authors/author is contained in h1:Field[#Type='5209']. Unfortunately, the attribute #Value remains empty.
I found the following workaround by introducing a variable:
<xsl:template match="h1:Block">
<h1:Block Type="obj">
<xsl:copy-of select="child::*"/>
<xsl:variable name="value5209" select="h1:Field[#Type='5209']/#Value"/>
<h1:Field Type="9904">
<xsl:attribute name="Value"
select="$authors/listAuthor/author[contains ($value5209, #catalogNumber)]/#fullName"/>
</h1:Field>
</h1:Block>
</xsl:template>
In this case, it works fine. Does anyone have any idea why it doesn't work in the first case? I use Saxon Saxon-HE 9.6.
Inside the predicate the context node is the author element which does not have a h1:Field, so change $authors/listAuthor/author[contains (h1:Field[#Type='5209']/#Value, #catalogNumber)]/#fullName to $authors/listAuthor/author[contains (current()/h1:Field[#Type='5209']/#Value, #catalogNumber)]/#fullName to select the context node of the template.

how to do a dynamic boolean checking in xslt

From my root template for unique account value, call goes to trans template were in the input xml I will have multiple nodes, my requirement is that once the call goes from root template to trans template, if a match of accountId found in between multiple elements, account details template is called only once, irrespective of other match found. I need a solution for above requirement.
input sample :
<elements><accountId>1</accountId></elements>
<elements><accountId>1</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>2</accountId></elements>
<elements><accountId>3</accountId></elements>
The below line should be wrapped under some code so its called only once
<xsl:call-template name="Account_details" />
Below is my complete code for xsl
<xsl:template match="/">
<xsl:variable name="unique-accounts" select="//*/*/*/accountId/text()[generate-id()=generate-id(key('account-by-id', .)[1])]"/>
<xsl:for-each select="$unique-accounts">
<xsl:variable name="currentValue" select="current()"/>
<xsl:apply-templates select="//trans">
<xsl:with-param name="passCurrentValue" select="$currentValue"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="trans">
<xsl:param name="passCurrentValue" />
<xsl:variable name="booleanValue" select="true()"/>
<xsl:for-each select="elements">
<xsl:if test="$passCurrentValue=/*/*/accountId">
<xsl:if test="$booleanValue">
<xsl:call-template name="Account_details" />
<xsl:variable name="booleanValue" select="false()"></xsl:variable>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="Account_details">
.............
</xsl:template>
With the code
<xsl:template match="/">
<xsl:variable name="booleanCheck" select="true"></xsl:variable>
the variable booleanCheck is a node-set (XSLT 1.0) or sequence of elements named true below the document node /. So unless your XML input has a root element named true the value is an empty node-set respectively empty sequence. If you want to select a boolean value then use <xsl:variable name="booleanValue" select="true()"/>. See http://www.w3.org/TR/xpath/#function-true. Then you can test <xsl:if test="$booleanValue">. That should explain how to use boolean values, whether that fits into your context I am not sure.

XSLT - How to refer to a current node value using xsl:choose?

I try to create a variable, which I can use in a later template:
<xsl:variable name="fc">
<xsl:choose>
<xsl:when test="self::node()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Unfortunately it does not work.
<xsl:template match="element1">
<h1><font color="{$fc}"><xsl:value-of select="self::node()"/></font></h1>
</xsl:template>
What am I doing wrong?
Here is the extensive code:
XML:
<root
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com scheme.xsd" xmlns="http://www.test.com" xmlns:tst="http://www.test.com">
<elementA>
<elementB tst:name="name">
<elementC tst:name="name">
<element1> Test1 </element1>
<element2> Test2 </element2>
</elementC >
</elementB>
</elementA>
</root>
All the elements are qualified and part of the namespace "http://www.test.com".
XSLT:
<xsl:template match="/">
<html>
<body><xsl:apply-templates select="tst:root/tst:elementA/tst:elementB/tst:elementC/tst:element1"/>
</body>
</html>
</xsl:template>
<xsl:variable name="var_fc">
<xsl:choose>
<xsl:when test="local-name(.)='tst:element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="tst:element1">
<h2><font color="{$var_fc}"><xsl:value-of select="self::node()"/></font></h2>
</xsl:template>
As a result, element1 should turn gray, but it always turn red.
You can't use a variable for this, as the content of an xsl:variable is evaluated just once at definition time, whereas you want to evaluate some logic every time the variable is referenced, in the current context at the point of reference.
Instead you need a template, either a named one:
<xsl:template name="fc">
<xsl:choose>
<xsl:when test="local-name()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:template>
or (better) a pair of matching templates with a mode, to let the template matcher do the work:
<!-- match any node whose local name is "element1" -->
<xsl:template mode="fc" match="node()[local-name() = 'element1']">gray</xsl:template>
<!-- match any other node -->
<xsl:template mode="fc" match="node()">red</xsl:template>
When you want to use this logic:
<h1>
<font>
<xsl:attribute name="color">
<xsl:apply-templates select="." mode="fc" />
</xsl:attribute>
Seeing as you have the tst prefix mapped in your stylesheet you could check the name directly instead of using the local-name() predicate:
<xsl:template mode="fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="fc" match="node()">red</xsl:template>
XSLT variables are designed not to be changeable. Actually they could be named constants. If your variable fc is created global, it will use the root element for choose. You have to use choose in the actual template to be tested against the current element. If you want to have "red" and "gray" defined only once, create two variables with just that text content and use these instead the plain text in the choose.
Maybe it is a typo:
<xsl:when test=self::node()='element1'">gray</xsl:when>
should be:
<xsl:when test="self::node()='element1'">gray</xsl:when>
there is a missing quote.
I think instead of test="self::node()='element1'" you want test="self::element1" or test="local-name(.) = 'element1'".
A couple of other errors in your code:
(1) self::node() = 'element1'
tests whether the content of the element is "element1", not whether its name is "element1"
(2) local-name(.)='tst:element1'
will never be true because the local name of a node never contains a colon.
Experienced users would often write this code using template rules:
<xsl:template mode="var_fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="var_fc" match="*">red</xsl:template>
and then
<xsl:apply-templates select="." mode="var_fc"/>

xsl : Incrementing a global variable

I know the fact that variables in XSL are immutable. But in my scenario, I want to increment a global variable and use the value later. Is there any other way by which this can be done? I have added enough comments in the code to make my question more clear. Please note I am using XSLT 1.0
<xsl:variable name="counter">0</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="Condition_ABC">
<xsl:variable name="returnValue"> <!--returnValue is the return value from the named template GetValue -->
<xsl:call-template name="GetValue"/>
</xsl:variable>
<xsl:if test="not($returnValue= '')">
<!-- If the returned value is not null
Here I want to add the increment logic, something like this
$counter = $counter+ 1 -->
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="some_pattern">
<Attribute>
<!-- the final 'counter' value from above xsl block needs to be outputted here -->
<xsl:value-of select="$counter"/>
</Attribute>
</xsl:template>

XSLT for-each counter - how to access data

for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>