I'm trying to access a specific element in an array depending on the value of the current date in an XML file.
For example, in the XML
<CurrentMonth>5</CurrentMonth>
Then, in the XSLT - this is set as a variable as
<xsl:variable name="current-month">
xsl:value-of select="//CurrentMonth" />
</xsl:variable>
I also have declared an array of the "Month names" as
<xsl:variable name="array" as="element()*">
<Item>Jan</Item>
<Item>Feb</Item>
<Item>Mar</Item>
<Item>Apr</Item>
<Item>May</Item>
<Item>Jun</Item>
<Item>Jul</Item>
<Item>Aug</Item>
<Item>Sept</Item>
<Item>Oct</Item>
<Item>Nov</Item>
<Item>Dec</Item>
</xsl:variable>
Is it possible in XSLT to return the name of the month (e.g. "Jan") by using a variable as an index for the array?
Example :
<xsl:value-of select="$array[$current-month]">
The code above is throwing me
[FATAL]: Error checking type of the expression 'filter-expr(variable-ref(array/result-tree)
Thanks in advance.
You have several syntax errors:
<xsl:variable name="current-month">
xsl:value-of select="//CurrentMonth" />
</xsl:variable>
needs to be:
<xsl:variable name="current-month">
<xsl:value-of select="//CurrentMonth" />
</xsl:variable>
or preferably:
<xsl:variable name="current-month" select="//CurrentMonth" />
Next you have:
<xsl:value-of select="$array[$current-month]">
which needs to be closed:
<xsl:value-of select="$array[$current-month]"/>
and, in case you are using the first form of defining the variable, it needs to be:
<xsl:value-of select="$array[number($current-month)]">
Define the variable as <xsl:variable name="current-month" select="xs:integer(//CurrentMonth)"/>, then you can use $array[$current-month] (although you index a sequence and not an array). With your code you need $array[position() = $current-month].
A minimal but complete stylesheet that runs fine for me with Saxon 9.6.0.7 HE is
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="array" as="element()*">
<Item>Jan</Item>
<Item>Feb</Item>
<Item>Mar</Item>
<Item>Apr</Item>
<Item>May</Item>
<Item>Jun</Item>
<Item>Jul</Item>
<Item>Aug</Item>
<Item>Sept</Item>
<Item>Oct</Item>
<Item>Nov</Item>
<Item>Dec</Item>
</xsl:variable>
<xsl:variable name="current-month" select="xs:integer(//CurrentMonth)"/>
<xsl:template match="/">
<xsl:value-of select="$array[$current-month]"/>
</xsl:template>
</xsl:stylesheet>
and outputs May when run against the input <CurrentMonth>5</CurrentMonth>.
Related
In apache camel, I am using xslt to create xml request. I want to generate random ID and pass it as RequestID as one of attribute. Is there any way to generate numeric ID in xslt.
<RequestID>12345</RequestID>
There are few conditions like number should contain only numeric value and no alphabets.
It should be maximum 8 digit.
FXSL (https://github.com/dnovatchev/FXSL-XSLT2) has pure XSLT 2 support for random numbers, I am struggling to find the right way to ensure the 8 digits but at least
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
expand-text="yes">
<xsl:import href="https://github.com/dnovatchev/FXSL-XSLT2/raw/master/Tests/test-random.xsl"/>
<xsl:variable name="random-number" as="xs:double">
<xsl:call-template name="randomSequence">
<xsl:with-param name="pLength" select="1"/>
<xsl:with-param name="pSeed" select="(current-dateTime() - xs:dateTime('1970-01-01T00:00:00')) div xs:dayTimeDuration('PT1S')"/>
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="100"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<RequestID>
<xsl:value-of select="format-number($random-number, '00000001')"/>
</RequestID>
</xsl:template>
</xsl:stylesheet>
gives a random number on each invocation. I am sure Dimitre will come along later to post a more informed answer on how to use his library for your case.
I have a function as below
'
`<xsl:function name="type:iterateICinDef">
<xsl:param name="pElem" as="element()"/>
<xsl:param name="parentSpc"/>
<xsl:param name="sequence" as="xs:integer"/>
<xsl:param name="cardinality"/>
<xsl:param name="required"/>
<xsl:param name="refNameSpc"/>
<xsl:param name="type"/>
<xsl:param name="flag" as="xs:integer"/>
<xsl:param name="hierParent"/>
<xsl:variable name="refName" select="replace($refNameSpc,'_spc',' ')"/>
<xsl:variable name="parent" select="replace($parentSpc,'_spc',' ')"/>
<xsl:if test="not ($pElem/_u0024ref or $pElem/items/_u0024ref)">
<Repository_spcIC>
<xsl:attribute name="Name">
<xsl:value-of select="$refName"></xsl:value-of>
</xsl:attribute>
</Repository_spcIC>
</xsl:if>
</xsl:function>`
In the above the "<xsl:attribute name="Name"> Name should be unique and should not have duplicate for another Repository_spcIC and hence i want to avoid the duplicate by forming a array of names and lookup in the array if the name already exist, if it does exist then i need to put a suffix "__D1" to the name to make the name unique. If there is repeat of the same name more than twice then the serial no will be incremented to make it unique like "__D2", "__D3" and so on. If the name is unique then i need add that name to the array so that the next call to the function will lookup the array to know if the name already exist to decide to suffix the name or not
This above function are called from another function like below
`<xsl:for-each select="/*/definitions/element()">
<!-- what this loop does is goes one level up, which is definitions
and then iterate thru all nodes under it i.e each object to match
def1 or def2 got earlier-->
<xsl:if test="name(.)=$def1 or name(.)=$def2">
<xsl:variable name="cardinality">
<xsl:if test="required">
<xsl:for-each select="required">
<countNo>
<xsl:if test="(position() > 1)">
<xsl:text>,</xsl:text>
</xsl:if>
</countNo>
<xsl:value-of select="text()"/>
</xsl:for-each>
</xsl:if>
</xsl:variable>
<xsl:sequence select="type:iterateICinDef((.),if($flag=1)
then $refName else if($pElem/comments) then $pElem/comments
/text()
else $pElem/name(.),$sequence + 1, if($type='array') then 'Zero
or More' else 'Zero or One',$cardinality,$referredName,
$type,1,$refName)"/>
</xsl:if>
</xsl:for-each>`
Arrays, like everything else in the XDM data model, are immutable. This means that you can never change an existing array "in situ", you can only create a new array by making changes to an existing array.
You've expressed your needs in terms of procedural pseudo-code, and XSLT isn't a procedural language, so it's quite hard to reverse engineer your code to work out what the real requirement is. It's much better to explain your requirements by describing the input and the output of the process, rather than as a sequence of actions to get from one to the other.
The very phrase "one at a time" in your question rings alarm bells. In a declarative, functional language like XSLT there is no notion of time, no concept of things happening one after the other.
It's also not clear that you need XDM arrays here. I suspect that XDM sequences are going to be easier to work with. Arrays are a 3.0 bolt-on to the language (introduced primarily to support JSON), while sequences have been there since 2.0 and are much more deeply integrated into the processing model. For example, XSLT's xsl:for-each instruction can be used to process each individual item in a sequence, but there is no equivalent instruction to process each member of an array.
I could explain the detailed syntax for setting up a sequence or array that's empty, or one that contains a collection of strings, but I'm not at all sure this would be helpful. It would be much better if you could explain your requirements in functional terms (what's the input, what's the output, and how do they relate?), and that will probably be much easier to translate into XSLT code.
== UPDATE ==
In your update to the question, you've explained that you want to write a function that generates unique values. To achieve this, you're proposing that the function has a memory of previous calls so that it can avoid generating the same value twice. So the function needs to modify something external (specifically, its memory of previous calls): it is a function with side effects, often called an "impure function", and this is strongly discouraged, or impossible, in pure functional programming languages.
There are various workarounds to this problem, of which the most common are perhaps:
Generate a value that's a function of the thing you are currently processing in the input, for example by using position() or generate-id() or xsl:number.
Generate a value that's effectively random and that has an extremely low probability of repeating (for example, something based on a current timestamp). That's how UUIDs generally work; a value that is only likely to come up once every million years is unique enough for most practical purposes.
Write a function that delivers a generator, where a generator has two properties: a value that you can use this time, and a new generator that you can call to get further values. That's the way the built-in function fn:random-number-generator() works. This is the way functional programming enthusiasts would do it, but it takes a bit of getting used to if you're new to functional programming.
If you need unique names as you described you could recalculate the names in a previous step. The code shows how this could be accomplished:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="data">
<data>
<item name="A"/>
<item name="A"/>
<item name="B"/>
<item name="A"/>
<item name="C"/>
<item name="C1"/>
<item name="C1"/>
<item name="C11"/>
<item name="C1"/>
<item name="C11"/>
<item name="C12"/>
<item name="D"/>
<item name="A"/>
</data>
</xsl:variable>
<xsl:template name="makeunique">
<xsl:param name="data"/>
<xsl:if test="not($data/data/item[#replaced = 'true']) and $data/data/item/#replaced">
<data>
<xsl:for-each select="$data/data/item">
<item name="{#name}" origname="{#origname}"/>
</xsl:for-each>
</data>
</xsl:if>
<xsl:if test="$data/data/item[#replaced = 'true'] or not($data/data/item/#replaced)">
<xsl:variable name="newData">
<data>
<xsl:for-each select="$data/data/item">
<xsl:variable name="name">
<xsl:if test="./preceding-sibling::item[#name = current()/#name]">
<xsl:value-of select="concat(current()/#name,./preceding-sibling::item[#name = current()/#name]/last()+1)"/>
</xsl:if>
<xsl:if test="not(./preceding-sibling::item[#name = current()/#name]) and not(./following-sibling::item[#name = current()/#name])">
<xsl:value-of select="#name"/>
</xsl:if>
<xsl:if test="not(./preceding-sibling::item[#name = current()/#name]) and (./following-sibling::item[#name = current()/#name])">
<xsl:value-of select="concat(current()/#name,'1')"/>
</xsl:if>
</xsl:variable>
<xsl:variable name="origname">
<xsl:if test="not(current()/#origname)">
<xsl:value-of select="current()/#name"/>
</xsl:if>
<xsl:if test="current()/#origname">
<xsl:value-of select="current()/#origname"/>
</xsl:if>
</xsl:variable>
<item name="{$name}" origname="{$origname}" replaced="{not($name = current()/#name)}"/>
</xsl:for-each>
</data>
</xsl:variable>
<xsl:call-template name ="makeunique">
<xsl:with-param name="data" select="$newData"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="test">
<xsl:call-template name ="makeunique">
<xsl:with-param name="data" select="$data"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<item name="A1" origname="A"/>
<item name="A2" origname="A"/>
<item name="B" origname="B"/>
<item name="A3" origname="A"/>
<item name="C" origname="C"/>
<item name="C11" origname="C1"/>
<item name="C121" origname="C1"/>
<item name="C111" origname="C11"/>
<item name="C13" origname="C1"/>
<item name="C112" origname="C11"/>
<item name="C122" origname="C12"/>
<item name="D" origname="D"/>
<item name="A4" origname="A"/>
</data>
BTW: This solution is more for XSLT enthusiasts. I would prefer using a function with side effects here.
I'm trying to split a delimited string in XSLT 1.0 using a recursive template.
My string is dynamic, and it sometimes has a #^# delimiter and sometimes it doesn't. I want to check if the string contains an exact value of 'SMB30DAYSFREE' and print some text if it does. I can't use the contains operator because sometimes other values share the the same text. Example: 'INVSMB30DAYSFREE'.
A few examples of what the string could be:
SMB30DAYSFREE#^#ENT30DAYSFREE#^#CLI60DAYSFREE
INVSMB30DAYSFREE#^#ENT30DAYSFREE#^#CLI60DAYSFREE
ENT30DAYSFREE#^#CLI60DAYSFREE#^#SMB30DAYSFREE
SMB30DAYSFREE
Below is what I have so far but it isn't working. Any advice? Thanks!
<!--call the tokenizeString template-->
<xsl:template match="*">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="/transaction/data_xml/document/appliedPromotion_quote"/><!--delimited list of values--><!--delimited list of values-->
<xsl:with-param name="delimiter" select="'#^#'"/>
</xsl:call-template>
<xsl:/template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<!-- get everything in front of the first delimiter -->
<xsl:variable name="promo" select="substring-before($list,$delimiter)"/>
<xsl:if test="$promo = SMB30DAYSFREE">
<fo:block>
SMB30DAYSFREE
</fo:block>
</xsl:if>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<fo:block>
SMB30DAYSFREE
</fo:block>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The question is rather confusing. It seems like you want to test if the string contains a token with the exact value of SMB30DAYSFREE. If this is correct, you could make your test simply:
contains(concat('#^#', $string, '#^#'), '#^#SMB30DAYSFREE#^#')
and do away with the tokenizing.
I have many similar operations, for example, check that one tag is sum of two other tags.
To write it once, I do next:
<xsl:variable name="psumArray" select="//AAA/SUM1 | //AAA/SUM2"/>
<xsl:variable name="psummandArray1" select="//AAA/A1 | //AAA/A2"/>
<xsl:variable name="psummandArray2" select="//AAA/B1 | //AAA/B2"/>
<xsl:for-each select="$psumArray">
<xsl:variable name="temppos" select="position()"></xsl:variable>
<xsl:if test=" format-number((text()), '#.##') != format-number(number($psummandArray1[$temppos]/text()) + number($psummandArray2[$temppos]/text())), '#.##')">
<ERROR>error!</ERROR>
</xsl:if>
</xsl:for-each>
And I can put to my array variables any number of "selects". But I found that each array sorts it's result by name. And order of members in array is not defined by sequence that I wrote, but defined by select text comparation.
I.e.:
<xsl:variable name="psummandArray2" select="//AAA/B3 | //AAA/B2"/>
Will become as:
<xsl:variable name="psummandArray2" select="//AAA/B2 | //AAA/B3"/>
After sorting.
How to avoid it? Or how to achieve what I want to another way, if that way is not the best?
UPD:
Imput XML is similar to:
<XML>
<A1>50</A1>
<A2>20</A2>
<A3>70</A3>
<A4>90</A4>
<A5>5</A5>
<A6>45</A6>
<A7>35</A7>
<A8>25</A8>
<A9>80</A9>
<A10>110</A10>
<A11>100</A11>
<A12>30</A12>
<A13>70</A13>
...
<A120>33</A120>
</XML>
And there exists rules, such as:
A3 = A6 + A8
A13 = A1 + A2
and etc. More then hundred rules. I have to write XSLT that would check all those rules, and if rule is wrong at some nodes, I have to print <ERROR>Error!</ERROR>
The best I think is to write XSLT, where I need just to add name of sum node and names of summand nodes.
That is why I wrote xslt as above, to check it. And now I was going just to put all rules to arrays. And then I found it sorts arrays, breaking my plans :-)
I have ~100 tasks to check if one node is a sum of two another nodes.
And I don't want to write comparation 100 times
Well, you have to write it out at least once, since it doesn't seem to follow any kind of logic that could be automated. Here's one way you could do it:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/XML">
<xsl:variable name="tests">
<xsl:call-template name="test-sum">
<xsl:with-param name="sum" select="A3"/>
<xsl:with-param name="summands" select="A6 | A8"/>
</xsl:call-template>
<xsl:call-template name="test-sum">
<xsl:with-param name="sum" select="A13"/>
<xsl:with-param name="summands" select="A1 | A2"/>
</xsl:call-template>
<!-- ... -->
</xsl:variable>
<xsl:if test="contains($tests, 'error')">
<ERROR>Error!</ERROR>
</xsl:if>
</xsl:template>
<xsl:template name="test-sum">
<xsl:param name="sum"/>
<xsl:param name="summands"/>
<xsl:if test="$sum!=sum($summands)">
<xsl:text>error</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could express your rules as XML - either in an external document, or in the stylesheet itself, then apply a template to that.
With XSLT 2.0 and an XSLT 2.0 processor you can use e.g. <xsl:variable name="psumArray" select="//AAA/SUM1 , //AAA/SUM2"/> to get a sequence of SUM1 elements followed by SUM2 elements.
I am taking some data from DB through stored procedure and i am using FOR XML AUTO to get this data.
query is like this
WHERE ITEMNUMBER=#itemid AND DATASET = #dataset FOR XML AUTO, ELEMENTS) AS filters
and result xml is
<filters extra="filters">
<ISP_WebItem>
<FILTER>Type</FILTER>
<FILTERNAME>Matematik</FILTERNAME>
<UNITCODE></UNITCODE>
</ISP_WebItem>
<ISP_WebItem>
<FILTER>Strømkilde</FILTER>
<FILTERNAME>Solceller</FILTERNAME>
<UNITCODE></UNITCODE>
</ISP_WebItem>
</filters>
Now my problem is that when i try to loop through this result in my xslt,I am unable to get any result.
My XSLT code is like this
<xsl:variable name="filt" select="msxsl:node-set(filters)"/>
<xsl:for-each select="$filt/ISP_WebItem">
<xsl:copy-of select="FILTER"/>
</xsl:for-each>
I am getting the filter xml correctly in my 'filt' variable, but the for each loop never executes correctly.
When i tried putting break points in my code,I noticed that code execution never enters inside the loop ,that means
<xsl:for-each select="filt/ISP_WebItem">
this piece of code never satisfies.
And for extra info this filter node is a part of large XML data,every other nodes in this data is working correctly(ie i can loop or do any operation with them).
can any one suggest any possible reasons.
There are two problems with your XSLT. Firstly, filt is a variable, and so you need to prefix it with a $ to indicate this, otherwise the XSLT will be looking for an element named filt
<xsl:for-each select="$filt/ISP_WebItem">
Secondly, in your XML sample, FILTER is an element, but your xsl:copy-of statement is looking for an attribute named 'FILTER'. You should be doing this
<xsl:copy-of select="FILTER"/>
However, if it is an attribute, leave it as <xsl:copy-of select="#FILTER"/>
This XSLT would work, for example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="filt" select="msxsl:node-set(filters)"/>
<xsl:for-each select="$filt/ISP_WebItem">
<xsl:copy-of select="FILTER"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
In fact, if you did just want to copy the elements, and nothing else, you could remove the xsl:for-each and simplify it to this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="filt" select="filters"/>
<xsl:copy-of select="$filt/ISP_WebItem/FILTER"/>
</xsl:template>
</xsl:stylesheet>
However, I am not quite sure if you need to use node-set in this instance (although I guess you are just showing an abdriged code sample, so maybe it is necessary for something elsewhere).
The following would also work
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="filters/ISP_WebItem">
<xsl:copy-of select="FILTER"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: If you are still getting problems, try one of the following expessions instead.
<xsl:for-each select="$filt//ISP_WebItem">