xslt compute value based on date - xslt

Good Day,
I have an xml file that looks like:
<albums xmlns="http://www.someurl.com/schema">
<album>
<artist>Rush</artist>
<name>Moving Pictures</name>
<releaseDate>05-31-1981</releaseDate>
<album>
</albums>
what I want is to use xlst to display the artist, name, and how many years is been since the release date.
<div id="recordInfo">
<div class="col"><xsl:value-of select="/t:albums/t:album/t:artist"></div>
<div class="col"><xsl:value-of select="/t:albums/t:album/t:name"></div>
<!-- I want the value of 31 here -->
</div>
Does anyone have any idea of how to do that in XSLT?
TIA,
coson

This transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vTokens" select="tokenize(/*, '-')"/>
<xsl:variable name="vDate" select=
"string-join(($vTokens[3], $vTokens[1], $vTokens[2]), '-')"/>
<xsl:template match="/*">
<xsl:sequence select=
"floor((current-date() - xs:date($vDate)) div xs:dayTimeDuration('P365D')) "/>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<t>05-31-1981</t>
produces the wanted, correct result:
31

Here's an XSLT 2.0 option...
XML Input
<albums xmlns="http://www.someurl.com/schema">
<album>
<artist>Rush</artist>
<name>Moving Pictures</name>
<releaseDate>05-31-1981</releaseDate>
</album>
</albums>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://www.someurl.com/schema" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="albums">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="t:album">
<div id="recordInfo">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="t:artist|t:name">
<div class="col"><xsl:value-of select="."/></div>
</xsl:template>
<xsl:template match="t:releaseDate">
<xsl:variable name="vOrigDate" select="tokenize(.,'-')"/>
<xsl:variable name="vDate" select="xs:date(concat($vOrigDate[3],'-',$vOrigDate[1],'-',$vOrigDate[2]))" as="xs:date"/>
<div class="col"><xsl:value-of select="floor(days-from-duration(current-date() - $vDate) div 365)"/></div>
</xsl:template>
</xsl:stylesheet>
Output
<div id="recordInfo">
<div class="col">Rush</div>
<div class="col">Moving Pictures</div>
<div class="col">31</div>
</div>

This XSLT 1.0 template...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://www.someurl.com/schema"
exclude-result-prefixes="xsl t" >
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="current-year" select="2012" />
<xsl:template match="/">
<r>
<xsl:apply-templates select="t:albums/t:album" />
</r>
</xsl:template>
<xsl:template match="t:album">
<div id="recordInfo">
<div class="col"><xsl:value-of select="t:artist" /></div>
<div class="col"><xsl:value-of select="t:name" /></div>
<div class="col"><xsl:value-of select="$current-year - substring(t:releaseDate,7)" /></div>
</div>
</xsl:template>
</xsl:stylesheet>
...will yield this output...
<r>
<div id="recordInfo">
<div class="col">Rush</div>
<div class="col">Moving Pictures</div>
<div class="col">31</div>
</div>
</r>
I've used a variable to store the date. In practice you will use a function to get the current date. Which function depends on XSLT version and engine.

Related

xslt: trying to understand conditional inside value-of

I'm expecting only Hola to appear:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="helloworld.xslt"?>
<greetings>
<greeting id="1">
Hello World!
</greeting>
<greeting id="2">
Hola!
</greeting>
</greetings>
However, both greetings appear.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="#id[.>1]"/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
It is not so much the conditional that is causing a problem, but the fact your statement is selecting the attribute, and so the xsl:value-of will output the attribute (but only if the value is greater than 1)
What you need to do is move the conditional to your xsl:apply-templates, and then do <xsl:value-of select="." /> to get your "Hola" value
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="greetings">
<xsl:apply-templates select="greeting[#id > 1]"/>
</xsl:template>
<xsl:template match="greeting">
<html>
<body>
<h1>
<xsl:value-of select="."/>
</h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

How to group elements based on their siblings using XSLT

I am trying to group several elements based on a starting and ending attribute of their surrounding siblings.
Sample XML:
<list>
<item>One</item>
<item class="start">Two</item>
<item>Three</item>
<item class="end">Four</item>
<item>Five</item>
<item class="start">Six</item>
<item class="end">Seven</item>
<item>Eight</item>
</list>
Desired Result:
<body>
<p>One</p>
<div>
<p>Two</p>
<p>Three</p>
<p>Four</p>
</div>
<p>Five</p>
<div>
<p>Six</p>
<p>Seven</p>
</div>
<p>Eight</p>
</body>
I come close to the desired results with the following XSLT. However, the following-sibling match doesn't stop after it reaches the ending attribute. Also, the standard matching repeats the elements that were already output from the following-sibling match.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform 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="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="list">
<body>
<xsl:apply-templates />
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="item[#class='start']">
<div>
<p><xsl:apply-templates /></p>
<xsl:apply-templates select="following-sibling::*[not(preceding-sibling::*[1][#class='end'])]" />
</div>
</xsl:template>
</xsl:transform>
Since you're using XSLT 2.0, why don't you take advantage of it:
<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="/list">
<body>
<xsl:for-each-group select="item" group-starting-with="item[#class='start']">
<xsl:for-each-group select="current-group()" group-ending-with="item[#class='end']">
<xsl:choose>
<xsl:when test="count(current-group()) gt 1">
<div>
<xsl:apply-templates select="current-group()" />
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:for-each-group>
</body>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:value-of select="."/>
</p>
</xsl:template>
</xsl:stylesheet>

xslt move node inside sibling node

After a day's research into XSLT, I am admitting defeat!
This is my input:
<div class="a" >
<div class="b">b1</div>
<div class="c">b1c1</div>
<div class="d">b1d1</div>
<div class="d">b1d2</div>
<div class="b">b2</div>
<div class="c">b2c1</div>
<div class="d">b2d1</div>
<div class="d">b2d2</div>
<div class="d">b2d3</div>
<div class="b">b3</div>
<div class="c">b3c1</div>
<div class="d">b3d1</div>
</div>
And this is the output I would like to get:
<div class="a" >
<div class="b">b1
<div class="c">b1c1</div>
<div class="d">b1d1</div>
<div class="d">b1d2</div>
</div>
<div class="b">b2
<div class="c">b2c1</div>
<div class="d">b2d1</div>
<div class="d">b2d2</div>
<div class="d">b2d3</div>
</div>
<div class="b">b3
<div class="c">b3c1</div>
<div class="d">b3d1</div>
</div>
</div>
This is the xslt that I am using:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Identity template, copies everything as is -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Override for target element -->
<xsl:template match="div[#class='a']">
<!-- Copy the element -->
<xsl:copy>
<!-- And everything inside it -->
<xsl:copy-of select="#*|node()"/>
<!-- Move nodes -->
<xsl:apply-templates select="div[#class='c']"/>
<xsl:apply-templates select="div[#class='d']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But it's giving me the wrong output:
<div class="a">
<div class="b">b1</div>
<div class="c">b1c1</div>
<div class="d">b1d1</div>
<div class="d">b1d2</div>
<div class="b">b2</div>
<div class="c">b2c1</div>
<div class="d">b2d1</div>
<div class="d">b2d2</div>
<div class="d">b2d3</div>
<div class="b">b3</div>
<div class="c">b3c1</div>
<div class="d">b3d1</div>
<div class="c">b1c1</div>
<div class="c">b2c1</div>
<div class="c">b3c1</div>
<div class="d">b1d1</div>
<div class="d">b1d2</div>
<div class="d">b2d1</div>
<div class="d">b2d2</div>
<div class="d">b2d3</div>
<div class="d">b3d1</div>
</div>
I understand why it's giving me this output, but I cannot find a way to modify it and get the correct output.
Thank you in advance.
PullingHair
It looks like you are grouping the "c" and "d" classes by the first preceding "b" class. To do this in XSLT 1.0, you could define a key to capture this grouping.
<xsl:key name="b" match="div[#class!='b']" use="generate-id(preceding-sibling::div[#class='b'][1])" />
So, rather than selecting all child nodes in the template that matches the "a" class, you just select the "b" ones
<xsl:apply-templates select="div[#class='b']" />
Then, in the template that matches the "b" class, you can use the key to get the associated "c" and "d" elements
<xsl:template match="div[#class='b']">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="key('b', generate-id())" />
</xsl:copy>
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="b" match="div[#class!='b']" use="generate-id(preceding-sibling::div[#class='b'][1])" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div[#class='a']">
<xsl:copy>
<xsl:apply-templates select="#*|div[#class='b']" />
</xsl:copy>
</xsl:template>
<xsl:template match="div[#class='b']">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
<xsl:apply-templates select="key('b', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0, you can make use of xsl:for-each-group instead
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div[#class='a']">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:for-each-group select="div" group-starting-with="div[#class='b']">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
<xsl:apply-templates select="current-group()[position() > 1]" />
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to move an element by attribute value to the last sibling position with xsl?

I need to move (not copy) an element with a given value in attribute id to the last position of its siblings, e.g. //ul/li[#id='b']:
Input:
<ul>
<li id="a">a</li>
<li id="b">b</li>
<li id="c">c</li>
<li id="d">d</li>
...
</ul>
Output:
<ul>
<li id="a">a</li>
<li id="c">c</li>
<li id="d">d</li>
...
<li id="b">b</li>
</ul>
If your input XML is as simple as that in the question, you can use this:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="ul">
<ul>
<xsl:apply-templates select="li[#id != 'b']"/>
<xsl:apply-templates select="li[#id = 'b']"/>
</ul>
</xsl:template>
<xsl:template match="li">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:transform>
This may help:
<?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" indent="yes" encoding="UTF-8" standalone="yes"/>
<xsl:param name="id" select="string('b')"/>
<xsl:template match="ul">
<xsl:element name="ul">
<xsl:apply-templates select="li[#id!=$id]"/>
<xsl:apply-templates select="li[#id=$id]"/>
</xsl:element>
</xsl:template>
<xsl:template match="li">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>

XSLT node transformation in a predefined order

How do I make transformation follow xml node order?
The xml file is something like this
<root>
<paragraph>First paragraph</paragraph>
<paragraph>Second paragraph</paragraph>
<unordered_list>
<list_name>Unordered list name</list_name>
<list_element>First element</list_element>
<list_element>Second element</list_element>
</unordered_list>
<paragraph>Third paragraph</paragraph>
</root>
I would like to transform it to HTML
...
<p>First paragraph</p>
<p>Second Paragraph</p>
<h3>Unordered list name</h3>
<ul>
<li>First element</li>
<li>Second element</li>
</ul>
<p>Third paragraph</p>
...
When I use xsl:for-each
It outputs all paragraphs first and then the list, or the other way round.
I want to keep the order of the XML file.
I am aware this might be very basic but I seem to be getting nowhere using xsl:choose and xsl:if. So please help me someone.
Here is a sample xslt stylesheet that does exactly what you are looking for:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- iterate through all the child nodes,
and apply the proper template to them -->
<xsl:template match="/">
<!-- added an extra div tag, to create a correct xml
that contains only one root tag -->
<div>
<xsl:apply-templates />
</div>
</xsl:template>
<!-- create the **p** tags -->
<xsl:template match="paragraph">
<p>
<xsl:value-of select="text()" />
</p>
</xsl:template>
<!-- create the **ul** tags -->
<xsl:template match="unordered_list">
<h3>
<xsl:value-of select="list_name" />
</h3>
<ul>
<xsl:apply-templates select="list_element" />
</ul>
</xsl:template>
<!-- create the **li** tags -->
<xsl:template match="list_element">
<li>
<xsl:value-of select="text()" />
</li>
</xsl:template>
</xsl:stylesheet>
The output of this transformation will be:
<?xml version="1.0" encoding="UTF-8"?>
<div>
<p>First paragraph</p>
<p>Second paragraph</p>
<h3>Unordered list name</h3>
<ul>
<li>First element</li>
<li>Second element</li>
</ul>
<p>Third paragraph</p>
</div>
A shorter and more consize transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="paragraph">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="unordered_list/list_name">
<h3><xsl:apply-templates/></h3>
</xsl:template>
<xsl:template match="unordered_list/list_element"/>
<xsl:template match="unordered_list/list_element[1]">
<ul>
<xsl:apply-templates mode="list"
select=".|following-sibling::*"/>
</ul>
</xsl:template>
<xsl:template mode="list" match="unordered_list/list_element">
<li><xsl:apply-templates/></li>
</xsl:template>
</xsl:stylesheet>