XSLT count items in array - arrays

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>

Related

XSL Iterate through list of items

I have an rss feed which is pulling from an abandoned 'Joke of the day' blog. Because the blog is no longer updating, I want to iterate through the list and display a different item each day, ideally in chronological order.
How do I identify the 'first' item in the list (the oldest post) and then show the next item each day?
The rss source is here: http://feeds.feedburner.com/DailyJokes-ACleanJokeEveryday?format=xml
The full list is here: http://dailyjokes.somelifeblog.com/
My XSL code is here, and currently displays the item at position 2, which is the most recent post:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates select="//item[position() < 2]"/>
</xsl:template>
<xsl:template match="item">
<content-item>
<h1><xsl:value-of select="substring-before(title, ' #Joke')"/></h1>
<p><xsl:value-of select="substring-before(description, '<a href')" disable-output-escaping="yes"/></p>
</content-item>
</xsl:template>
</xsl:stylesheet>
I am surfacing this feed in a SharePoint 2013 RSS webpart
My goal is to display a different item each day but I might settle for simple randomization. Also if anybody can suggest a free or for-cost 'Joke of the day' site which is appropriate for my work intranet, that would be appreciated!
My goal is to display a different item each day
To accomplish your goal, your processor needs to know the current date.
The following is a minimized example showing how to use the date in order to retrieve a different item every day:
XML
<rss>
<channel>
<item>
<title>Alpha</title>
</item>
<item>
<title>Bravo</title>
</item>
<item>
<title>Charlie</title>
</item>
<item>
<title>Delta</title>
</item>
<item>
<title>Echo</title>
</item>
</channel>
</rss>
XSLT 1.0
<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:strip-space elements="*"/>
<xsl:param name="current-datetime">2017-04-27T00:00:00</xsl:param>
<xsl:template match="/rss">
<xsl:variable name="JDN">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$current-datetime"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="n" select="count(channel/item)" />
<output>
<xsl:apply-templates select="channel/item[$JDN mod $n + 1]"/>
</output>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:value-of select="title"/>
</item>
</xsl:template>
<xsl:template name="JDN">
<xsl:param name="date"/>
<xsl:param name="year" select="substring($date, 1, 4)"/>
<xsl:param name="month" select="substring($date, 6, 2)"/>
<xsl:param name="day" select="substring($date, 9, 2)"/>
<xsl:param name="a" select="floor((14 - $month) div 12)"/>
<xsl:param name="y" select="$year + 4800 - $a"/>
<xsl:param name="m" select="$month + 12*$a - 3"/>
<xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
</xsl:template>
</xsl:stylesheet>
In this example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<item>Bravo</item>
</output>
Live demo: http://xsltransform.net/pNmBxZK
If your processor supports the EXSLT date:date-time()extension function, you can modify your stylesheet's header to:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="date">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="current-datetime" select="date:date-time()"/>
For SharePoint, I am guessing you would need to use the TodayIso function in the ddwrt namespace instead.

Create an XSLT that will convert an array into unique sequential elements

I am trying to create an XSLT that will convert an array into unique sequential elements. I am probably not explaining this correctly so I will show you:
Current XML
<?xml version="1.0" encoding="UTF-8"?>
<DocumentRequests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<PlanID>20151014_103605</PlanID>
<LetterType>
<Fund>Yes</Fund>
<Adding>Yes</Adding>
<Bau>Yes</Bau>
</LetterType>
<PlanNumbers>
<PlanNumber>A01</PlanNumber>
<PlanNumber>A02</PlanNumber>
<PlanNumber>A03</PlanNumber>
<PlanNumber>A04</PlanNumber>
<PlanNumber>A05</PlanNumber>
<PlanNumber>A06</PlanNumber>
<PlanNumber>456</PlanNumber>
</PlanNumbers>
</DocumentRequests>
Current XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="*">
<SourceFeedbackTransformed>
<PlanAdminID><xsl:value-of select="PlanAdminID" /></PlanAdminID>
<xsl:for-each select="LetterType">
<xsl:copy-of select="*" copy-namespaces="no"/>
</xsl:for-each>
<xsl:for-each select="PlanNumbers">
<xsl:copy-of select="*" copy-namespaces="no"/>
</xsl:for-each>
</SourceFeedbackTransformed>
</xsl:template>
</xsl:stylesheet>
Current output
<?xml version="1.0" encoding="UTF-8"?>
<SourceFeedbackTransformed xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<PlanAdminID/>
<Fund>Yes</Fund>
<Adding>Yes</Adding>
<Bau>Yes</Bau>
<PlanNumber>A01</PlanNumber>
<PlanNumber>A02</PlanNumber>
<PlanNumber>A03</PlanNumber>
<PlanNumber>A04</PlanNumber>
<PlanNumber>A05</PlanNumber>
<PlanNumber>A06</PlanNumber>
<PlanNumber>456</PlanNumber>
</SourceFeedbackTransformed>**
Desired Output
<?xml version="1.0" encoding="UTF-8"?>
<SourceFeedbackTransformed xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<PlanAdminID/>
<Fund>Yes</Fund>
<Adding>Yes</Adding>
<Bau>Yes</Bau>
<PlanNumber1>A01</PlanNumber1>
<PlanNumber2>A02</PlanNumber2>
<PlanNumber3>A03</PlanNumber3>
<PlanNumber4>A04</PlanNumber4>
<PlanNumber5>A05</PlanNumber5>
<PlanNumber6>A06</PlanNumber6>
<PlanNumber7>456</PlanNumber7>
</SourceFeedbackTransformed>
As you can see the Array with 7 values has been converted to 7 different elements.
Thank you for your help.
Cheers,
You can get the result you're after quite easily by:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/DocumentRequests">
<SourceFeedbackTransformed>
<PlanAdminID><xsl:value-of select="PlanID" /></PlanAdminID>
<xsl:copy-of select="LetterType/*" copy-namespaces="no"/>
<xsl:for-each select="PlanNumbers/PlanNumber">
<xsl:element name="PlanNumber{position()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</SourceFeedbackTransformed>
</xsl:template>
</xsl:stylesheet>
Note:
I have changed your:
<xsl:value-of select="PlanAdminID" />
to:
<xsl:value-of select="PlanID" />
as there is no PlanAdminID element in your input. If you really want to output an empty <PlanAdminID/> element as shown in your requested output, then you could do so directly, without fetching the value of a non-existing node.

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

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

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