Can not loop through FOR XML AUTO results - sql-server

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

Related

XSLT count items in array

I'm trying to modify an XLST 1.0 file and I found that I can use an array like this:
<xsl:variable name="array">
<Item>106</Item>
<Item>107</Item>
</xsl:variable>
Now I want to write an IF structure where i have a test on the amount of items in the array.
I'v tried this, but this isn't working:
<xsl:if test="count($array) = 0"></xsl:if>
Am I using the right approach for this problem?
First, there are no "arrays" in XML.
Next, count($array) in your example will always return 1, because your variable contains a single parent node. To count the child Item nodes, you would need to use count($array/Item).
However, that too would fail, because in XSLT 1.0 your variable contains a result-tree-fragment - and XSLT 1.0 can only count nodes in a node-set.
One solution is to turn the RTF into a node-set, using an extension function (which is supported by practically all XSLT 1.0 processors). For example, the following stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="array-rtf">
<Item>106</Item>
<Item>107</Item>
</xsl:variable>
<xsl:variable name="array" select="exsl:node-set($array-rtf)" />
<xsl:template match="/">
<test>
<xsl:value-of select="count($array/Item)"/>
</test>
</xsl:template>
</xsl:stylesheet>
returns:
<?xml version="1.0" encoding="UTF-8"?>
<test>2</test>
Another option is to use an internal element instead of a variable:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://example.com/my"
exclude-result-prefixes="my">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<my:array>
<Item>106</Item>
<Item>107</Item>
</my:array>
<xsl:template match="/">
<test>
<xsl:value-of select="count(document('')/*/my:array/Item)"/>
</test>
</xsl:template>
</xsl:stylesheet>

Transforming XML content to array

As the reverse case of this post, how it's possible to transform content of an XML document to array using XSLT.
For this:
<Records>
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value4</item>
<item>value5</item>
</Records>
The desired result is something like this:
[value1, value2, value3, value4, value5]
What's the idea?
Hope this helps..
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="countItems" select="count(Records/item)"/>
[<xsl:for-each select="Records/item">
<xsl:value-of select="."/>
<xsl:choose>
<xsl:when test="position()=$countItems"/>
<xsl:otherwise>,</xsl:otherwise>
</xsl:choose>
</xsl:for-each>]
</xsl:template>
</xsl:stylesheet>
XSLT 1.0 can be used to generate a resultant xml,text or HTML file from the source xml file.
You can get the resultant data from the file created via XSLT

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

How to write an XSL 1.0 stylesheet with a node-set() function that will run on both MSXML and libxml

I have an XSLT 1.0 stylesheet running using the XSL processor included with PHP (libxml). I want to get the same stylesheet to run on the Microsoft XSL processor MSXML 6.0 (msxml6.dll) ideally so the same stylesheet can run on either processor.
Unfortunately at the moment I would need to have two stylesheets - one for each processor.
This snippet invokes the node-set() function on the PHP processor;
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:template match="root">
<xsl:variable name="rtf">
<a>hello</a><b>world</b>
</xsl:variable>
<xsl:variable name="ns" select="exsl:node-set($rtf)"/>
<xsl:copy-of select="$ns/b"/>
</xsl:template>
</xsl:transform>
This snippet invokes the node-set() function on the Microsoft processor;
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
extension-element-prefixes="msxsl">
<xsl:template match="root">
<xsl:variable name="rtf">
<a>hello</a><b>world</b>
</xsl:variable>
<xsl:variable name="ns" select="msxsl:node-set($rtf)"/>
<xsl:copy-of select="$ns/b"/>
</xsl:template>
</xsl:transform>
If the input document was;
<root/>
The result of both stylesheets would be;
<b>world</b>
I want a single stylesheet that can run unchanged on the PHP processor and the Microsoft processor.
Although my real stylesheet is about 400 lines long and the the node-set() function is used in four places, I hope the examples above demonstrates the problem.
Checked on libxml and msxsl, works in both cases.
Regards
Mike.
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:func="http://exslt.org/functions"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
extension-element-prefixes="exsl func msxsl"
>
<func:function name="msxsl:node-set">
<xsl:param name="node"/>
<func:result select="exsl:node-set($node)"/>
</func:function>
<xsl:template match="root">
<xsl:variable name="rtf">
<a>hello</a><b>world</b>
</xsl:variable>
<xsl:variable name="ns" select="msxsl:node-set($rtf)"/>
<xsl:copy-of select="$ns/b"/>
</xsl:template>
</xsl:transform>

Resources