XSLT Logic Overwrite a temporary variable - xslt

How we can achieve below logic.
The UniqueID is the temporary variable.PALLET_NUMBER is coming from input.
if PALLET_NUMBER!=NULL then
UniqueID=substring (PALLET_NUMBER, 10)
if PALLET_NUMBER =NULL then
UniqueID=substring (CARTON_NUMBER, 7)
we can get the value of UniqueID from the above two conditions.These things happen in iteration loop.How we can overwrite the UniqueID temporary variable.
bacause later There is a condition we need to put like
<foreach>
If previous UniqueID != current UniqueID then
<Some code>
<IF>
</foreach>

You cannot overwrite variable in xslt.
You may want to look up xslt extension functions to achieve your goal.

As Treemonkey, says, it's not possible to overwrite variables, but you can achieve something like you describe using recursion:
Suppose you had something like this:
<xsl:for-each select="my/node/isNamed/something" />
You could do this instead:
<xsl:variable name="items" select="my/node/isNamed/something" />
<xsl:apply-templates select="$items[1]">
<xsl:with-param name="remainder" select="$items[position() > 1" />
</xsl:apply-templates>
<!-- Separate template -->
<xsl:template match="something">
<xsl:param name="remainder" />
<xsl:param name="lastId" />
<xsl:variable name="uniqueId" select="..." />
<!-- Contents of the xsl:for-each -->
<xsl:apply-templates select="$remainder[1]">
<xsl:with-param name="remainder" select="$remainder[position() > 1]" />
<xsl:with-param name="lastId" select="$uniqueId" />
</xsl:apply-templates>
</xsl:template>

Related

Function in XSLT

I am using the following formula at a lot of places inside an xsl file.
<xsl:value-of select="format-number($Value div 1000000, '##.##')" />
Is there anyway I can create a function so I can keep the logic at one place and reuse it as per below example?
Example:
<xsl:value-of select="ConvertToMillionAndFormatNumber($Value)" />
There are no custom functions in XSLT 1.0 (unless your processor happens to support them as an extension), but you can use a named template:
<xsl:template name="ConvertToMillionAndFormatNumber">
<xsl:param name="Value" />
<xsl:value-of select="format-number($Value div 1000000, '##.##')" />
</xsl:template>
and call it as:
<xsl:call-template name="ConvertToMillionAndFormatNumber">
<xsl:with-param name="Value" select="your-value-here"/>
</xsl:call-template>

How to conditionally filter out children in XSLT

I currently have something similar to the following XML:
<div class="newsFeed">
<div class="newsItem"><news position="3"/></div>
<categoryFilter dayFilter="4">
<div class="newsItem"><news position="2"/></div>
</categoryFilter>
</div>
I need to copy the XML, and output the nth news item on the node. Futhermore, I need to be able to filter that news. For this example, lets construct my news as follows:
<xsl:variable name="news">
<xsl:for-each select="1 to 30">
<item>
<day><xsl:value-of select=". mod 4" /></day>
<content>Content: <xsl:value-of select="." /></content>
</item>
</xsl:for-each>
</xsl:variable>
I actually use document() and use the for-each to sort it, but I'm trying to keep it succinct. This would mean that my output XML would be something like the following:
<div class="newsFeed">
<div class="newsItem">Content: 3</div>
<div class="newsItem">Content: 8</div>
</div>
The reason the second one is 8 is because the categoryFilter filters out every <item> where the day isn't 4 (which happens to be the 4th, 8th, 12th, and so on), and then we select the second one.
The XSLT to produce the above is as follows:
XSLT:
<xsl:template match="news">
<xsl:param name="items" select="$news" />
<xsl:variable name="position" select="#position" />
<xsl:copy-of select="$items/item[position()=$position]/content" />
</xsl:template>
<xsl:template match="categoryFilter">
<xsl:param name="items" select="$news" />
<xsl:variable name="day" select="#dayFilter" />
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates>
<xsl:with-param name="items" select="$filteredItems">
</xsl:apply-templates>
</xsl:template>
My problem lies with the <for-each>. It seems silly that I have to use a for-each to filter out the <item> nodes, but I can't find a better way. Simply doing a <xsl:variable select="$items/item[day=$day]"> changes the structure, and makes it so that the <xsl:template match="news"> doesn't work.
Is there a way to filter out child nodes without using a for-each? I am using <xsl:stylesheet version="3.0">
Instead of doing this...
<xsl:variable name="filteredItems">
<xsl:for-each select="$items/item[day=$day]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
... you could use a sequence. This will select the actual items, as opposed to create copies of them
<xsl:variable name="filteredItems">
<xsl:sequence select="$items/item[day=$day]" />
</xsl:variable>
This was, doing $filteredItems/item will still work.
Alternatively, you could take the opposite approach, and do away with the need to specify /item in all the expressions.
First, define your news variable like so:
<xsl:variable name="news" as="element()*">
This means you can write the expression that uses it like so:
<xsl:copy-of select="$news[position()=$position]/content" />
And similarly for filteredItems....
<xsl:variable name="filteredItems" select="$items[day=$day]" />

xslt - compare local and global variable

how can I make a comparation of two value which one inside for-each tag and a variable outside for-each tag like example below
<xsl:variable name="uid" >
<xsl:value-of select="/ctas/output/uid" />
</xsl:variable>
<xsl:for-each select="/ctas/user/row">
<xsl:variable name="user_uid" select="user_uid" />
<xsl:if test="$user_uid ≠ $uid" >
//do something
</xsl:if>
</xsl:for-each>
The usual way is to write
<xsl:variable name="uid" >
<xsl:value-of select="/ctas/output/uid" />
</xsl:variable>
simply as
<xsl:variable name="uid" select="/ctas/output/uid" />
as that selects the node(s) in the input and then to use the variable in a predicate, as in
<xsl:for-each select="/ctas/user/row[not(user_uid = $uid)]">...</xsl:for-each>
For efficiency such cross-references are often optimized with a key:
<xsl:key name="uid" match="ctas/output" use="uid"/>
and then
<xsl:for-each select="/ctas/user/row[not(key('uid', user_uid))]">...</xsl:for-each>

XSLT: xsl:function won't work

The following works for me:
<xsl:variable name="core" select="document('CoreMain_v1.4.0.xsd')" />
<xsl:variable name="AcRec" select="document('AcademicRecord_v1.3.0.xsd')" />
<xsl:template match="xs:element">
<xsl:variable name="prefix" select="substring-before(#type, ':')" />
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
But I use the same logic to handle the lookup of elements in the current or other documents based on the prefix, matching the node name in numerous places within the stylesheet. So, after changing the stylesheet version to 2.0, I tried:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
<xsl:function name="my:lookup">
<xsl:param name="attribute" />
<!-- parse the attribute for the prefix & name values -->
<xsl:variable name="prefix" select="substring-before($attribute, ':')" />
<xsl:variable name="name" select="substring-after($attribute, ':')" />
<!-- Switch statement based on the prefix value -->
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:function>
In my reading, I have only found examples of functions that return text - none call templates. I have the impression that an xsl:function should always return text/output...
After more investigation, it is entering the my:lookup function and the variables (prefix & name) are getting populated. So it does enter the xsl:choose statement, and the hits the appropriate when test. The issue appears to be with the apply-templates - value-of is displaying the child values; copy-of does as well, which I think is odd (shouldn't the output include the xml element declarations?). Why would there be a difference if code that works in a template declaration is moved to an xsl:function?
It's been a while since I did any serious XSLT, but IIRC your problem is not in the function, but in your template:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
The value-of statement won't inline the result tree returned by your function. Instead, it's going to try and reduce that result tree down into some kind of string, and inline that instead. This is why you're seeing the child values and not the elements themselves.
To inline the result tree returned by your function, you'll need to use some templates to copy the result tree into place.
So, your main template will need to change to this:
<xsl:template match="xs:element">
<xsl:apply-templates select="my:lookup(#type)" />
</xsl:template>
and you'll need some templates to do the recursive call. A quick google found a good discussion of the identity template that should do what you need.
(Please forgive any syntax errors, as I said, it's been a while ...)

Optional parameters when calling an XSL template

Is there way to call an XSL template with optional parameters?
For example:
<xsl:call-template name="test">
<xsl:with-param name="foo" select="'fooValue'" />
<xsl:with-param name="bar" select="'barValue'" />
</xsl:call-template>
And the resulting template definition:
<xsl:template name="foo">
<xsl:param name="foo" select="$foo" />
<xsl:param name="bar" select="$bar" />
<xsl:param name="baz" select="$baz" />
...possibly more params...
</xsl:template>
This code will gives me an error "Expression error: variable 'baz' not found." Is it possible to leave out the "baz" declaration?
Thank you,
Henry
You're using the xsl:param syntax wrong.
Do this instead:
<xsl:template name="foo">
<xsl:param name="foo" />
<xsl:param name="bar" />
<xsl:param name="baz" select="DEFAULT_VALUE" />
...possibly more params...
</xsl:template>
Param takes the value of the parameter passed using the xsl:with-param that matches the name of the xsl:param statement. If none is provided it takes the value of the select attribute full XPath.
More details can be found on W3School's entry on param.
Personally, I prefer doing the following:
<xsl:call-template name="test">
<xsl:with-param name="foo">
<xsl:text>fooValue</xsl:text>
</xsl:with-param>
I like using explicitly with text so that I can use XPath on my XSL to do searches. It has come in handy many times when doing analysis on an XSL I didn't write or didn't remember.
The value in the select part of the param element will be used if you don't pass a parameter.
You are getting an error because the variable or parameter $baz does not exist yet. It would have to be defined at the top level for it to work in your example, which is not what you wanted anyway.
Also if you are passing a literal value to a template then you should pass it like this.
<xsl:call-template name="test">
<xsl:with-param name="foo">fooValue</xsl:with-param>
Please do not use <xsl:param .../> if you do not need it to increase readability.
This works great:
<xsl:template name="inner">
<xsl:value-of select="$message" />
</xsl:template>
<xsl:template name="outer">
<xsl:call-template name="inner">
<xsl:with-param name="message" select="'Welcome'" />
</xsl:call-template>
</xsl:template>