apache camel : generate random numeric ID in xslt - apache-camel

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.

Related

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

XSLT-1.0 array disable sorting

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.

WSO2 ESB foreach function

In WSO2 ESB Proxy service, how can i iterate based on integer value from some webservice response, just like "foreach":
For example such response message:
<Response>
<noOfcustomers>10</noOfCustomers>
</Response>
I need to iterate 10 times (based on the number of customers)
Is this possible? How can i achieve this?
Thanks for your help!
I've not found a clean way to do this, but here's a messy solution.
First you need an XSLT transformation.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" exclude-result-prefixes="xsl xsi">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="iterations"/>
<xsl:template name="for.loop">
<xsl:param name="i"/>
<xsl:param name="count"/>
<!--begin_: Line_by_Line_Output -->
<xsl:if test="$i <= $count">
<iteration>
<xsl:value-of select="$i"/>
</iteration>
</xsl:if>
<!--begin_: RepeatTheLoopUntilFinished-->
<xsl:if test="$i <= $count">
<xsl:call-template name="for.loop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<iterations>
<xsl:call-template name="for.loop">
<xsl:with-param name="i">1</xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$iterations"/></xsl:with-param>
</xsl:call-template>
</iterations>
</xsl:template>
</xsl:stylesheet>
Then you use the transformation in your sequence like this:
<inSequence>
<xslt key="conf:/repository/test/iterations.xslt">
<property name="iterations" expression="//noOfcustomers"/>
</xslt>
<iterate expression="//iterations/iteration" sequential="true">
<target>
<sequence>
</sequence>
</target>
</iterate>
</inSequence>
The sequence in the iterate mediator will run for each element in "iterations". The drawback to this approach is that you are replacing the message body with the iteration XML, so you have to use the enrich meditor before the transformation to save the original message to a property if you wish to reuse it.
You can iterate based on xpath. But currently we don't have counter support. What is your actual usecase?
This is supported with ForEach mediator from ESB 4.9 onwards

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