xslt: the context item is absent in Function - xslt

my xml-pool is like
<POOL>
<CUSTOMER>
<GROUP_ID>
2
</GROUP_ID>
</CUSTOMER>
</POOL>
I have a call for my Function, that looks like
<xsl:value-of select="nck:serviceNo(' ',' ')"/>
and my function is:
<xsl:function name="nck:serviceNo" as="xs:string">
<xsl:param name="spacer1" as="xs:string"/>
<xsl:param name="spacer2" as="xs:string"/>
<xsl:variable name="num">
1010<xsl:value-of select="$spacer1" />2020 3030<xsl:value-of select="$spacer2" />
<xsl:choose>
<xsl:when test="/POOL/CUSTOMER/GROUP_ID = 2" >
10
</xsl:when>
<xsl:otherwise>
0
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:sequence select="$num"/>
</xsl:function>
My Error is:
Type error at char 1 in expression in xsl:when/#test on line 31
column 60 of test.xsl: XPDY0002 Leading '/' selects
nothing: the context item is absent Errors were reported during
stylesheet compilation
I need a workarround for supplying the pool in the function, can anyone help

Add a parameter to pass in a node e.g. <xsl:param name="group-id"/> and pass in e.g. <xsl:value-of select="nck:serviceNo(' ',' ', /POOL/CUSTOMER/GROUP_ID)"/> where you then ckeck <xsl:when test="$group-id = 2"> or declare a global param or variable e.g. <xsl:param name="main-root" select="/"/> and use e.g. <xsl:when test="$main-root/POOL/CUSTOMER/GROUP_ID = 2">.

Related

Passing node and field dynamically to xslt function

I am trying to make something to act as a null check decimal fields for use in xslt transformations. I was thinking I could also make similar for other data types as I need. I made this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/CR_Question"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:this="urn:this-stylesheet"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:function name="this:NullCheckField" as="xsd:string">
<xsl:param name="node" as="xsd:string"/>
<xsl:param name="fieldName" as="xsd:string"/>
<xsl:variable name="variableName">
<xsl:choose>
<xsl:when test="not(exists(concat($node, '/', $fieldName)))">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="x">
<xsl:value-of select="concat($node, '/', $fieldName)"/>
</xsl:variable>
<xsl:variable name="y">
<xsl:value-of select="$x"/>
</xsl:variable>
<xsl:value-of select="format-number(number($y), '#.00')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$variableName"/>
</xsl:function>
<xsl:output method="text" indent="yes"/>
<xsl:template match="wd:Report_Entry">
<xsl:value-of select="this:NullCheckField('wd:Report_Data/wd:Report_Entry','wd:deduction_amount')"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
and i tried calling it with this line
<wd:Report_Data xmlns:wd="urn:com.workday.report/CR_Question">
<wd:Report_Entry>
<wd:Employee_ID>1555111</wd:Employee_ID>
<wd:Check_Date>2022-04-28</wd:Check_Date>
<wd:NegDeductIndc>0</wd:NegDeductIndc>
<wd:Deduction_YTD>0</wd:Deduction_YTD>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Employee_ID>1555222</wd:Employee_ID>
<wd:Check_Date>2022-04-28</wd:Check_Date>
<wd:NegDeductIndc>0</wd:NegDeductIndc>
<wd:Deduction_YTD>0</wd:Deduction_YTD>
</wd:Report_Entry>
</wd:Report_Data>
When I run this, the test should fail and use the hard coded 0 since the node is not there, but instead of dropping into
<xsl:when test="not(exists(concat($node, '/', $fieldName)))">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
It keeps dropping into the and failing out. I am not sure of two things.
The first is why is my exist check not working correctly and falling into the
Other question - am I able to pass the node names and concat them like this?
Edit: Updated function as of now
<!-- param type 1 = decimal -->
<!-- param type 2 = integer -->
<!-- param type 3 = string -->
<xsl:function name="this:NullCheckField" as="xsd:string">
<xsl:param name="node"/>
<xsl:param name="type" as="xsd:integer"/>
<xsl:variable name="variableName">
<xsl:choose>
<xsl:when test="not(exists($node))">
<xsl:choose>
<xsl:when test="$type = 1">
<xsl:value-of select="format-number(0, '#.00')"/>
</xsl:when>
<xsl:when test="$type = 2">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:otherwise>
<xsl:text/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="x">
<xsl:value-of select="$node[0]"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$type = 1">
<xsl:value-of select="format-number(number($x), '#.00')"/>
</xsl:when>
<xsl:when test="$type = 2">
<xsl:value-of select="number($x)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$variableName"/>
</xsl:function>
The problem I am trying to resolve is my integration will not always produce the empty nodes. Sometimes there is a missing node and I have been doing checks like this
<xsl:choose>
<xsl:when test="not(exists(wd:DPDEEYDEDS))">
<xsl:value-of select="' '"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="wd:DPDEEYDEDS"/>
</xsl:otherwise>
</xsl:choose>
Some of these are decimal, some are integer, some are spaces. I'm trying to do a dynamic null check. The function will be called, the field node evaluated, if it is not a missing or empty node then it formats the value, otherwise return a default 0|0.00|' '.
Unless you want to move to XSLT with xsl:evaluate support the whole idea of passing in strings with XPath fragments is nonsense.
If, in the context of template match="wd:Report_Entry", you want to pass that Report_Entry element to a function use . e.g. this:my-function(., 'deduction_amount'). The first function argument should be of type node() or element(), certainly you don't want to atomize the node to a string.
Then you could check e.g. test="$node/*[local-name() = $fieldName]" to check for the existence of a child element with local name $fieldName.
I don't see any need to work with element names as as strings, you can simply pass in e.g. this:my-function(wd:deduction_amount), with the type of the paramter being node()? or node()* and then do any exists check on that, if needed.
The whole construction of nested, untyped variables is unnecessarily complicated and inefficient.
I don't even see the need for the function, doing e.g. format-number((wd:deduction_amount, 0)[1], '#.00') inside the template suffices.

Need to format the date as per the input xml file

Need to change the date format from 123121(mmddyy) to 12/31/2021(mm/dd/yyyy)
Input XML I'm having:
<section>
<Plans>
<Date>123121</Date>
</Plans>
</section>
<p>date: <keyword keyref="Cost:Date:date4"/></p>
XSL I have tried:
<xsl:template match="keyword[contains(#keyref, 'Cost:Date:date4')]">
<xsl:param name="section" as="element()" tunnel="yes">
<empty/>
</xsl:param>
<keyword keyref="Cost:Date:date4">
<xsl:call-template name="format_variable">
<xsl:with-param name="cur_keyref" select="#keyref"/>
<xsl:with-param name="cur_value"
select="$section//Plans/Date"/>
<xsl:with-param name="cur_format" select="'date4'"/>
</xsl:call-template>
</keyword>
</xsl:template>
<xsl:template name="format_variable">
<xsl:param name="cur_keyref" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_value" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_format" as="xs:string">MISSING</xsl:param>
<xsl:choose>
<xsl:when test="$cur_format = 'date4'">
<xsl:value-of select="format-dateTime($cur_value, '[M01]/[D01]/[Y0001]')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="concat('MISSING_FORMAT_', $cur_keyref, '_', $cur_value, '_[', $cur_format, ']')"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
When I'm trying the above code its showing the below error:
Invalid dateTime value "123121" (Too short)
Please somebody help me out get the 12/31/2021(mm/dd/yyyy) value on the output.
You are trying to use the format-dateTime() function on something that is not a valid dateTime.
What can't you do simply:
<xsl:value-of select="replace(Date, '(.{2})(.{2})(.{2})', '$1/$2/20$3')"/>
Note that this is assuming all your dates will be in the 21st century. Otherwise you would need additional logic to identify the century.

Sum value of output of template in Apache FOP

I am using Apache FOP to generate a PDF document, and to display a certain value I have to iterate over a number of nodes to determine a total price value, then sum that value. So far I have a function that iterates over an array and then retrieves the intended value, but the issue occurs when I try to sum the results.
<xsl:function name="foo:buildTotalValue">
<xsl:param name="items" />
<xsl:variable name="totals">
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(exsl:node-set($totals))" />
</xsl:function>
<xsl:template name="getTotalPriceNode">
<xsl:param name="itemParam" />
<xsl:choose>
<xsl:when test="$itemParam/Recurrance = 'OnceOff'">
<xsl:value-of select="$itemParam/TotalValue" />
</xsl:when>
<xsl:when test="$itemParam/Recurrance = 'Monthly'">
<xsl:value-of select="$itemParam/TotalValue * $itemParam/Months"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
I'm hoping that when I pass in foo:buildTotalValue with entries like this:
<Charges>
<Charge>
<Recurrance>OnceOff</Recurrance>
<TotalValue>50.00</TotalValue>
</Charge>
<Charge>
<Recurrance>Monthly</Recurrance>
<TotalValue>10.00</TotalValue>
<Months>6</Months>
</Charge>
</Charges>
would return with the value 110.00, but instead I get the error:
Cannot convert string "50.0060.00" to double
I've tried adding a <value> or something in the templates and then using that as a selector for the exsl:node-set function but it doesn't seem to make a difference.
AFAICT, the problem with your function is that it builds a concatenated string of values returned by the called template, instead of a tree of nodes that can be converted into a node-set and summed.
Try changing:
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
to:
<xsl:for-each select="$items/charge">
<total>
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</total>
</xsl:for-each>
and:
<xsl:value-of select="sum(exsl:node-set($totals))" />
to:
<xsl:value-of select="sum(exsl:node-set($totals)/total)" />
Untested, because (see comment to your question).
I ended up using the suggestion from Martin from the comment - the xpath 2+ expression along the line of:
sum(Charge[Recurrance = 'OnceOff']/TotalValue | Charge[Recurrance = 'Monthly']/(TotalValue * Months))
which was able to achieve what I needed without the use of functions / templates / node-set (And in a lot less code)

Using fn:random-number-generator to produce random numbers more than once

I try to write a simple function to provide me with a random letter each time i call it but I have difficulties combining my idea with the concept of a functional programing approach. Some help along the way would be appreciated!
The code I have looks like:
<xd:doc>
<xd:desc>Provides one random letter, if the type is provided it returns a letter of thet type</xd:desc>
<xd:param name="type">The type of letter to return, one of (A,a,B,b)</xd:param>
</xd:doc>
<xsl:function name="gdpr:randomLetter" as="xs:string">
<xsl:param name="type" as="xs:string"></xsl:param>
<xsl:choose>
<xsl:when test="$type = 'A'">
<xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
<xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
<xsl:value-of select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'a'">
<xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
<xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
<xsl:value-of select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'B'">
<xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
<xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
<xsl:value-of select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'b'">
<xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
<xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
<xsl:value-of select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 52)[1]"/>
<xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
<xsl:value-of select="$letters[$randomNumber]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
Your question encapsulates the problem:
I try to write a simple function to provide me with a random letter
each time i call it
But a function that produces different results on different invocations (with the same arguments) is not a true ("pure") function.
One way out of this is to exploit the fact that XSLT already has "impure" functions of a kind: a function that creates a new node returns a different node each time, and you can expose this by using generate-id(). So you could write
<xsl:function name="my:random" as="xs:double">
<xsl:variable name="dummy"><a/></xsl:variable>
<xsl:sequence select="fn:random-number-generator(generate-id($dummy))?permute(1 to 10)"/>
</xsl:function>
The only problem with this is that you're right on the boundaries of what's well-defined in the spec and the optimizer might not let you get away with such tricks. It's much better, if you can, to find some way of passing a different argument to the function each time it is called: for example, a sequence number, or generate-id() applied to the input node you are currently processing.
In the context of XSLT 3, I think one way to have a "new" random-number-generator for every node you need it is to define an accumulator:
<xsl:accumulator name="rng" as="map(xs:string, item())" initial-value="random-number-generator(current-dateTime())">
<xsl:accumulator-rule match="foo[#type]" select="$value?next()"/>
</xsl:accumulator>
So that way you could implement your function as
<xsl:function name="gdpr:randomLetter" as="item()*">
<xsl:param name="type" as="xs:string"/>
<xsl:param name="rng" as="map(xs:string, item())"/>
<xsl:choose>
<xsl:when test="$type = 'A'">
<xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
<xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
<xsl:sequence select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'a'">
<xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
<xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
<xsl:sequence select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'B'">
<xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
<xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
<xsl:sequence select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:when test="$type = 'b'">
<xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
<xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
<xsl:sequence select="$letters[$randomNumber]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="randomNumber" select="$rng?permute(1 to 52)[1]"/>
<xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
<xsl:sequence select="$letters[$randomNumber]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
and then call it with e.g.
<xsl:template match="foo[#type]">
<xsl:copy>
<xsl:value-of select="gdpr:randomLetter(#type, accumulator-before('rng'))"/>
</xsl:copy>
</xsl:template>
and make sure you use
<xsl:mode on-no-match="shallow-copy" use-accumulators="rng"/>
The problem is that fn:random-number-generator function is deterministic. The specs itself explain that:
Both forms of the function are ·deterministic·: calling the function
twice with the same arguments, within a single ·execution scope·,
produces the same results.
You need to use properly the function under the key next contained in the resulted map from calling the random-number-generator function. Like the spec said:
The entry with key "next" is a zero-arity function that can be called
to return another random number generator.
For the sake of completeness, I came up with this solution but it only works for small pieces of text due to the depth of recursion.
On a sidenote- I realized my solution was waste of time as I use exist-db that does not include random-number-generator in it's XSLT implementation.
<xsl:function name="gdpr:rngRecurseStart">
<xsl:param name="text"></xsl:param>
<xsl:variable name="chars" select="functx:chars($text)"/>
<xsl:copy-of select="gdpr:rngRecurse($chars,random-number-generator(current-dateTime()),'')"></xsl:copy-of>
</xsl:function>
<xsl:function name="gdpr:rngRecurse">
<xsl:param name="chars"></xsl:param>
<xsl:param name="rngGenerator"></xsl:param>
<xsl:param name="newText"></xsl:param>
<xsl:variable name="curentchar" select="$chars[1]"></xsl:variable>
<xsl:variable name="newRngGenerator" select="$rngGenerator?next()"/>
<xsl:choose>
<xsl:when test="count($chars) >1">
<xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
<xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
<xsl:copy-of select="gdpr:rngRecurse(subsequence($chars,2),$newRngGenerator,$resultText)"></xsl:copy-of>
</xsl:when>
<xsl:when test="count($chars) =1">
<xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
<xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
<xsl:copy-of select="$resultText"></xsl:copy-of>
</xsl:when>
<xsl:otherwise><xsl:copy-of select="$newText"></xsl:copy-of></xsl:otherwise>
</xsl:choose>
</xsl:function>

Test param value type in XSL

I am getting this from LibXSLT:
XSLTProcessor::transformToXml(): Invalid type
XSLTProcessor::transformToXml(): xmlXPathCompiledEval: 1 objects left on the stack.
I am passing a param which can either have a string value or a nodeset. I am trying to test whether it contains a certain substring and in that case assign that value to another parameter.
The calling template:
<xsl:call-template name="img">
<xsl:with-param name="upload" select="'url.com/image.jpg'"/>
<xsl:with-param name="w" select="200"/>
<xsl:with-param name="h" select="200"/>
</xsl:call-template>
The called template:
<xsl:template name="img" match="*" mode="w">
<xsl:param name="upload" select="."/>
<xsl:param name="JITexternal">
<xsl:choose>
<xsl:when test="
not($upload/meta) and (contains($upload, '.jpg')
or
contains($upload, '.png'))
">
<xsl:value-of select="$upload"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:param>
</xsl:template>
While I am not sure what is tripping LibXSLT, I think is that fact that when I run those tests and the value is a string it throws the type error above.
But most importantly, is there a good way to test the type of a param's value?
UPDATE: the full XSL script on GitHub
While not exactly type checking, I found that converting the result tree fragment to a string() before running my test did prevent LibXSLT to bomb out:
<xsl:param name="JITexternal">
<xsl:choose>
<xsl:when test="starts-with(string($upload), 'http://')">
<xsl:value-of select="$upload"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:param>