XSLT-1.0 array disable sorting - arrays

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.

Related

apache camel : generate random numeric ID in xslt

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.

How to declare and use a dynamic array in xslt in stylesheet 3.0 with xpath https://www.w3.org/TR/xpath-functions-31/#func-array-put

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.

Representing collections data correctly in JSON after XSLT conversion from XML

I have the following XSLT, which is converting XML data to JSON.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">{
<xsl:apply-templates select="*"/>}
</xsl:template>
<!-- Object or Element Property-->
<xsl:template match="*">
"<xsl:value-of select="name()"/>" : <xsl:call-template name="Properties"/>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="Properties"/>
</xsl:template>
<!-- Object Properties -->
<xsl:template name="Properties">
<xsl:variable name="childName" select="name(*[1])"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"<xsl:call-template name="ctlchar">
<xsl:with-param name="pText" select="." />
</xsl:call-template>"</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<!-- Attribute Property -->
<xsl:template match="#*">
<xsl:if test="not(starts-with(name(), 'xsi:'))">
"<xsl:value-of select="name()"/>" : "<xsl:value-of select="."/>",
</xsl:if>
</xsl:template>
<!-- replace carriage return with \n -->
<xsl:template name="ctlchar">
<xsl:param name="pText" select="." />
<xsl:choose>
<xsl:when test="not(contains($pText, '
'))">
<xsl:copy-of select="$pText" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="substring-before($pText, '
')" />
<xsl:text>\n</xsl:text>
<xsl:call-template name="ctlchar">
<xsl:with-param name="pText"
select="substring-after($pText, '
')" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This works fairly well, but I have run into an issue.
This xml:
<items>
<item></item>
<item></item>
<item></item>
<item></item>
<items>
gets converted to this JSON:
"items" : {
"item" : [
{},
{},
{},
{}
]
}
The items are all contained in an array, and this seems correct. However, the following XML:
<items>
<item></item>
<items>
Gets converted into this:
"items" : {
"item" : {}
}
This makes the JSON difficult to parse, since item is sometimes an array and sometimes an object.
This is an inherited project, I don't know how it started. I'm just trying to figure out if this is conventional? It seems like it should be an array of 1 if it is an array of 4.
I see the same XSLT posted around online as a solution for xml->json so I am confused. Not sure how to tell a client to parse this. I also don't know how to fix the xslt, since it seems like the xslt can't know whether or not a single-item collection should be an array or a single object.
I also don't know how to fix the xslt, since it seems like the xslt can't know whether or not a single-item collection should be an array or a single object.
That's the whole point here. Your XSLT must somehow know if you're dealing with a collection or a single item.
This is quite difficult to solve in general, but rather easy to solve for a specific case.
The condition in <xsl:when test="count(*[name()=$childName]) > 1"> checks if you'll need an array or a single item. You may consider to modify this condition to work correctly for your specific case. That would be a bit of hardcoding, but it's probably the easiest option.
There is no perfect way of converting XML to JSON, and every converter does it differently. The issue you have hit is one of the classic difficulties. If a book has a single author, there is no way of knowing whether other books might have multiple authors and therefore whether the author should be a single item or an array of length 1.
In my view the only way to do the conversion is with semantic knowledge. Both the XML and the JSON representation need to be derived from the underlying semantic data model, not from each other, and the conversion needs to be aware of the data model, which means that purely mechanical conversion is always going to produce unhappy results.
To add to #MichaelKay's answer...
If the XML document was validated against an XSD using a Java XML parser then it should be possible in principle to get the Post-Schema Validation Information (PSVI) for the element and get its xsd:maxOccurs value. Then, if maxOccurs = 1 you could output a single JSON field, else output an array.
However, I don't know of any tool that does this. Not sure why, but I suspect that it is not always possible to know for sure (from the PSVI) which XSD object a particular tag was matched against. Michael may be able to confirm/deny that theory.

Array At a Variable Index in XSLT

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>.

Can not loop through FOR XML AUTO results

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">

Resources