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

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.

Related

Move XML element to different node using XSLT

Below is the XML Input payload. I'm looking for an xml output should have "type" element inside the each Address node. Below is the incoming request XML
<rsp:response xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rsp="rsp.com/employee/Response/v30"
xmlns:res="res.com/Member/details/v1"
xmlns:resp="resp.com/details/v1">
<res:employee>
<resp:Employee>
<resp:FirstName>abc</resp:FirstName>
<resp:middleName></resp:middleName>
<resp:details>
<resp:Details>
<resp:type>postal</resp:type>
<resp:Addresses>
<resp:Address>
<resp:country>XYZ</resp:country>
</resp:Address>
</resp:Addresses>
</resp:Details>
<resp:Details>
<resp:type>ofc</resp:type>
<resp:Addresses>
<resp:Address>
<resp:country>XYZ</resp:country>
</resp:Address>
</resp:Addresses>
</resp:Details>
</resp:details>
</resp:Employee>
</res:employee>
</rsp:response>
Here is the XSLT used and it is not giving desired output. Using this XSLT all "type" elements is reflecting in each address block.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rsp="rsp.com/employee/Response/v30"
xmlns:res="res.com/Member/details/v1"
xmlns:resp="resp.com/details/v1"
version="2.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[local-name()='response']/*[local-name()='employee']/*[local-name()='Employee']/*[local-name()='details']/*[local-name()='Details']/*[local-name()='Addresses']/*[local-name()='Address']">
<xsl:copy>
<xsl:apply-templates/>
<xsl:for-each select="//*[local-name()='response']/*[local-name()='employee']/*[local-name()='Employee']/*[local-name()='details']/*[local-name()='Details']/*[local-name()='type']">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[local-name()='response']/*[local-name()='employee']/*[local-name()='Employee']/*[local-name()='details']/*[local-name()='Details']/*[local-name()='type']"/>
</xsl:stylesheet>
Desired Output XML
<rsp:response
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rsp="rsp.com/employee/Response/v30"
xmlns:res="res.com/Member/details/v1"
xmlns:resp="resp.com/details/v1">
<res:employee>
<resp:Employee>
<resp:FirstName>abc</resp:FirstName>
<resp:middleName/>
<resp:details>
<resp:Details>
<resp:Addresses>
<resp:Address>
<resp:country>XYZ</resp:country>
<resp:type>postal</resp:type>
</resp:Address>
</resp:Addresses>
</resp:Details>
<resp:Details>
<resp:Addresses>
<resp:Address>
<resp:country>XYZ</resp:country>
<resp:type>ofc</resp:type>
</resp:Address>
</resp:Addresses>
</resp:Details>
</resp:details>
</resp:Employee>
</res:employee>
</rsp:response>
Try it this way:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:resp="resp.com/details/v1">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="resp:Address">
<xsl:copy>
<xsl:apply-templates/>
<xsl:copy-of select="../../resp:type"/>
</xsl:copy>
</xsl:template>
<xsl:template match="resp:type"/>
</xsl:stylesheet>
Re your attempt:
There should never be a need to use a hack like *[local-name()='type'];
You should find out what // means.

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.

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

Change a string in xml into an array in xml easily

I was wondering if anyone knew if there was a simple way to change all of my strings in my xml file to arrays?
Example:
//This is the string I need to change:
<string name="sample1">value1, value2, value3, value4, value5</string>
//This is the way I need it to be:
<array name="sample1">
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value4</item>
<item>value5</item>
</array>
My problem is not that I do not know how to manually do it. But I am looking for a way to simulate this process more easily because I have 120 strings with 25-90 values in each.
A good example would be converting multiple images extentions with a single click using an add-on to GIMP that simulates that process for you for each image.
Would anyone who understands what I am asking, know a way to do this for string to array?
This XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<array>
<xsl:attribute name="name">
<xsl:value-of select="string/#name"/>
</xsl:attribute>
<xsl:apply-templates/>
</array>
</xsl:template>
<xsl:template match="string/text()" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="sep" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($text, $sep))">
<item>
<xsl:value-of select="normalize-space($text)"/>
</item>
</xsl:when>
<xsl:otherwise>
<item>
<xsl:value-of select="normalize-space(substring-before($text, $sep))"/>
</item>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $sep)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
applied to this XML:
<?xml version="1.0" encoding="UTF-8"?>
<string name="sample1">value1, value2, value3, value4, value5</string>
gives this output:
<?xml version="1.0" encoding="UTF-8"?>
<array name="sample1">
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value4</item>
<item>value5</item>
</array>
The XSLT is using a recursive template going through your string values and splitting them at the comma.
This can be done with XSLT 2.0 as simply as:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<array name="{#name}">
<xsl:for-each select="tokenize(., ',\s*')">
<item><xsl:value-of select="."/></item>
</xsl:for-each>
</array>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<string name="sample1">value1, value2, value3, value4, value5</string>
the wanted, correct result is produced:
<array name="sample1">
<item>value1</item>
<item>value2</item>
<item>value3</item>
<item>value4</item>
<item>value5</item>
</array>

Resources