Sorting XSLT variable date - xslt

Sorry if my explanation in the last 'question' was a little rough, I'll give it another shot.I'm really new to xsl.
Through a series of substrings I have extracted the date field by storing it in a variable called $releasedate, I output this by using <xsl:value-of select="$releasedate" /> which would display dd/mm/yyyy e.g. 29/03/2010 in a 'for each' loop as shown below.
My xml is structured similar to below,
<item>
<title>Title Goes Here</title>
<description>test goes here.....<b>Release Date:22/20/2010</b> more text</description>
</item>
<item>
<title>Title Goes Here</title>
<description>More test goes here.....<b>Release Date:22/20/2010</b> more text</description>
</item>
I would like to be able to sort by the value of what is stored in $releasedate.
I think maybe it would look like this below
<xsl:sort select="$releasedate" order="descending" />
I hope this makes a little more sense, my appologies again for my lack of knowledge in this.
Below is my xsl structure
<xsl:for-each select="item">
<xsl:sort select="pubDate"/>
<xsl:if test="position() < 5 ">
<!-- grabs items 1 to 5 -->
<xsl:variable name = "releasedatestart" >
<xsl:value-of select="substring-after(description,'Release Date:</b>')"/>
</xsl:variable>
<xsl:variable name = "releasedate" >
<xsl:value-of select="substring-before($releasedatestart,'</div>')"/>
</xsl:variable>
<xsl:variable name = "displaynamestart" >
<xsl:value-of select="substring-after(description,'Display Name:</b>')"/>
</xsl:variable>
<xsl:variable name = "displayname" >
<xsl:value-of select="substring-before($displaynamestart,'</div>')"/>
</xsl:variable>
<div style="margin:0px;background-color:#f2eff3;border:1px solid #ded6df;">
<xsl:variable name = "start" >
<xsl:value-of select="substring-after(description,'Description:</b>')"/>
</xsl:variable>
<xsl:variable name = "title" >
<xsl:value-of select="substring($start,0,100)"/>
</xsl:variable>
<xsl:variable name = "pagelink" >
<xsl:value-of select="substring(link,1,61)"/>
</xsl:variable>
<xsl:variable name = "pageurl" >
<xsl:value-of select="$pagelink"/>
<xsl:value-of select="title"/>.aspx
</xsl:variable>
<div style="min-height:50px;">
<div class="column" id="feeddate">
<xsl:value-of select="$releasedate" />
</div>
<div class="column" id="displayname">
<xsl:value-of select="$displayname" />
</div>
<div class="column" id="feedtitle">
<xsl:value-of select="$title" disable-output-escaping="yes"/>
<a>
<xsl:attribute name="href">
<xsl:value-of select="$pageurl" />
</xsl:attribute>
..Read More
</a>
</div>
</div>
</div>
</xsl:if>
</xsl:for-each>

This 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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<items>
<xsl:apply-templates>
<xsl:sort select=
"substring(substring-after(description, 'Release Date:'),7)"
data-type="number" order="descending"/>
<xsl:sort select=
"substring(substring-after(description, 'Release Date:'),4,2)"
data-type="number" order="descending"/>
<xsl:sort select=
"substring(substring-after(description, 'Release Date:'),1,2)"
data-type="number" order="descending"/>
</xsl:apply-templates>
</items>
</xsl:template>
</xsl:stylesheet>
when applied on this document:
<items>
<item>
<title>Title1</title>
<description>text1 .....
<b>Release Date:22/12/2010</b> more text
</description>
</item>
<item>
<title>Title Goes Here</title>
<description>More test goes here.....
<b>Release Date:23/12/2010</b> more text
</description>
</item>
</items>
produces the wanted, sorted result:
<items>
<item>
<title>Title Goes Here</title>
<description>More test goes here.....
<b>Release Date:23/12/2010</b> more text
</description>
</item>
<item>
<title>Title1</title>
<description>text1 .....
<b>Release Date:22/12/2010</b> more text
</description>
</item>
</items>
Explanation:
Use of substring-after() and substring().
Multiple <xsl:sort> children of <xsl:apply-templates>.
Identity transform.

Related

XSLT for flat to nested/hierarchical, with level interpolation?

Given this source XML document: input.xml
<body>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="3">content</p>
</body>
I'd like to transform to output.xml:
<list>
<item>
<list>
<item>
<p ilvl="1">content</p>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
<list>
<item>
<p ilvl="3">content</p>
</item>
</list>
</item>
</list>
</item>
</list>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
etc
Attribute ilvl is the list level; its a zero-based index.
I tried adapting https://stackoverflow.com/a/11117548/1031689 and got output:
<rs>
<p ilvl="1"/>
<p ilvl="1">
<p ilvl="2">
<p ilvl="3"/>
</p>
</p>
<p ilvl="1">
<p ilvl="2"/>
<p ilvl="2">
<p ilvl="3"/>
</p>
</p>
<p ilvl="1"/>
<p ilvl="1">
<p ilvl="3"/>
</p>
</rs>
I have 2 issues with it:
It doesn't create structure for any missing level (eg missing level 2 between 1 and 3), and
The starting param level must match the first entry (ie 1 here). If you pass 0, the nesting is wrong.
Before I tried this, I was using my own XSLT 1.0 code, attached below.
The tricky part is how to handle a decrease in nesting eg level 3 to 1:
<p ilvl="3">content</p>
<p ilvl="1">content</p>
Updated
I try to handle this in the addList template, as the recursion is "unwound", but its not quite right yet; in my output when it gets back to level 1 a new list is being inserted, but if I correct that, I drop the last 3 content items... If anyone can solve this, I'll be impressed :-)
Yeah, I know my code is way more complicated, so if there is any easy fix to the for-each-group approach above, it'd be great to have suggestions.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<!-- works, except makes new list for siblings -->
<xsl:template name="listSection">
<xsl:param name="last-level">-1</xsl:param>
<xsl:param name="items"/>
<xsl:variable name="currentItem" select="$items[1]"/>
<xsl:variable name="currentLevel">
<xsl:value-of select="number($currentItem/#ilvl)"/>
</xsl:variable>
<xsl:variable name="nextItems" select="$items[position() > 1]"/>
<xsl:choose>
<xsl:when test="$currentLevel = $last-level">
<!-- just add an item -->
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$currentItem"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
<!-- that handles next level higher case, and level same case-->
<!-- level lower is handled is addList template-->
</xsl:when>
<xsl:when test="$currentLevel > $last-level">
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$last-level"/>
<xsl:with-param name="nextItems" select="$items"/> <!-- since haven't handled current item yet -->
</xsl:call-template>
</xsl:when>
<xsl:otherwise> this level < last level: should not happen?</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="addItem">
<xsl:param name="currentItem"/>
<xsl:param name="nextItems"/>
<xsl:variable name="currentLevel">
<xsl:value-of select="number($currentItem/#ilvl)"/>
</xsl:variable>
<item>
<xsl:apply-templates select="$currentItem"/>
<!-- is the next level higher?-->
<xsl:if test="(count($nextItems) > 0) and
(number($nextItems[1]/#ilvl) > $currentLevel)">
<!-- insert list/item to the necessary depth-->
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$currentLevel"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
</xsl:if>
</item>
<!-- next level same-->
<xsl:if test="(count($nextItems) > 0) and
(number($nextItems[1]/#ilvl) = $currentLevel)">
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$nextItems[1]"/>
<xsl:with-param name="nextItems" select="$nextItems[position() > 1]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="addList">
<xsl:param name="currentLevel">-1</xsl:param>
<xsl:param name="nextItems"/>
<xsl:variable name="targetLevel">
<xsl:value-of select="number($nextItems[1]/#ilvl)"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$targetLevel - $currentLevel > 1">
<!-- interpolate -->
<list>
<xsl:variable name="stuff">
<item>
<xsl:call-template name="addList">
<xsl:with-param name="currentLevel" select="$currentLevel+1"/>
<xsl:with-param name="nextItems" select="$nextItems"/>
</xsl:call-template>
</item>
</xsl:variable>
<xsl:copy-of select="$stuff"/>
<xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />
<xsl:variable name="ascentLevel">
<xsl:value-of select="number($nextItems[$currentPos]/#ilvl)"/>
</xsl:variable>
<xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>
<xsl:variable name="aftertargetLevel">
<xsl:value-of select="number($ascentItems[1]/#ilvl)"/>
</xsl:variable>
<xsl:if test="(count($ascentItems) > 1) and
($aftertargetLevel - $currentLevel = 1)">
<xsl:call-template name="listSection">
<xsl:with-param name="last-level" select="$currentLevel"/>
<xsl:with-param name="items" select="$ascentItems"/>
</xsl:call-template>
</xsl:if>
</list>
</xsl:when>
<xsl:when test="$targetLevel - $currentLevel = 1">
<!-- insert real item -->
<xsl:variable name="stuff">
<list>
<xsl:call-template name="addItem">
<xsl:with-param name="currentItem" select="$nextItems[1]"/>
<xsl:with-param name="nextItems" select="$nextItems[position() > 1]"/>
</xsl:call-template>
</list>
</xsl:variable>
<!-- might be items on the way out -->
<xsl:copy-of select="$stuff"/>
<xsl:variable name="currentPos" select="count(msxsl:node-set($stuff)//p)" />
<xsl:variable name="ascentLevel">
<xsl:value-of select="number($nextItems[$currentPos]/#ilvl)"/>
</xsl:variable>
<xsl:variable name="ascentItems" select="$nextItems[position() > $currentPos]"/>
<xsl:variable name="aftertargetLevel">
<xsl:value-of select="number($ascentItems[1]/#ilvl)"/>
</xsl:variable>
<xsl:if test="(count($ascentItems) > 1) and
($aftertargetLevel - $currentLevel = 1)">
<xsl:call-template name="listSection">
<xsl:with-param name="last-level" select="$currentLevel"/>
<xsl:with-param name="items" select="$ascentItems"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!--should not happen!-->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:call-template name="listSection">
<xsl:with-param name="items" select="*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
This is my second attempt to provide solution to the problem which, in its current state forces people (at least me) to guess what is wanted:
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pStartLevel" select="1"/>
<xsl:key name="kChildren" match="p"
use="generate-id(preceding-sibling::p
[not(#ilvl >= current()/#ilvl)][1])"/>
<xsl:template match="/*">
<list>
<item>
<xsl:apply-templates select="key('kChildren', '')[1]" mode="start">
<xsl:with-param name="pParentLevel" select="$pStartLevel"/>
<xsl:with-param name="pSiblings" select="key('kChildren', '')"/>
</xsl:apply-templates>
</item>
</list>
</xsl:template>
<xsl:template match="p" mode="start">
<xsl:param name="pParentLevel"/>
<xsl:param name="pSiblings"/>
<list>
<xsl:apply-templates select="$pSiblings">
<xsl:with-param name="pParentLevel" select="$pParentLevel"/>
</xsl:apply-templates>
</list>
</xsl:template>
<xsl:template match="p">
<xsl:param name="pParentLevel"/>
<xsl:apply-templates select="self::*[#ilvl - $pParentLevel > 1]"
mode="buildMissingLevels">
<xsl:with-param name="pParentLevel" select="$pParentLevel"/>
</xsl:apply-templates>
<xsl:apply-templates select="self::*[not(#ilvl - $pParentLevel > 1)]" mode="normal">
<xsl:with-param name="pParentLevel" select="$pParentLevel"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="p" mode="normal">
<xsl:param name="pParentLevel"/>
<item>
<xsl:copy-of select="."/>
<xsl:apply-templates mode="start"
select="key('kChildren',generate-id())[1]">
<xsl:with-param name="pParentLevel" select="#ilvl"/>
<xsl:with-param name="pSiblings"
select="key('kChildren',generate-id())"/>
</xsl:apply-templates>
</item>
</xsl:template>
<xsl:template match="p" mode="buildMissingLevels">
<xsl:param name="pParentLevel"/>
<item>
<p ilvl="{$pParentLevel +1}"/>
<list>
<xsl:apply-templates select=".">
<xsl:with-param name="pParentLevel" select="$pParentLevel +1"/>
</xsl:apply-templates>
</list>
</item>
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<body>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="2">content</p>
<p ilvl="2">content</p>
<p ilvl="3">content</p>
<p ilvl="1">content</p>
<p ilvl="1">content</p>
<p ilvl="3">content</p>
</body>
produces what I believe is wanted:
<list>
<item>
<list>
<item>
<p ilvl="1">content</p>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
<list>
<item>
<p ilvl="3">content</p>
</item>
</list>
</item>
</list>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2">content</p>
</item>
<item>
<p ilvl="2">content</p>
<list>
<item>
<p ilvl="3">content</p>
</item>
</list>
</item>
</list>
</item>
<item>
<p ilvl="1">content</p>
</item>
<item>
<p ilvl="1">content</p>
<list>
<item>
<p ilvl="2"/>
<list>
<item>
<p ilvl="3">content</p>
</item>
</list>
</item>
</list>
</item>
</list>
</item>
</list>

How to define attribute name dynamically in XSLT?

I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<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:param name="navigation-xml">
<item id="home" title-en="Services" title-de="Leistungen" />
<item id="company" title-en="Company" title-de="Unternehmen" />
<item id="references" title-en="References" title-de="Referenzen" />
</xsl:param>
<xsl:param name="navigation" select="exsl:node-set($navigation-xml)/*" />
<xsl:param name="navigation-id" />
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
How can I refactor the last 12 lines, so that the attribute name (either #title-de or #title-en) gets determined dynamically rather than in the (silly) way I did it in?
Thanks for any help.
You could write
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
as
<xsl:template name="title">
<xsl:apply-templates select="$navigation[$navigation-id = #id]" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:value-of select="#*[local-name() = concat('title-', $current-language)]" />
</xsl:template>
IMHO, your problem starts much earlier. If you define your navigation-xml parameter as:
<xsl:param name="navigation-xml">
<item id="home">
<title lang="en">Services</title>
<title lang="de">Leistungen</title>
</item>
<item id="company">
<title lang="en">Company</title>
<title lang="de">Unternehmen</title>
</item>
<item id="references">
<title lang="en">References</title>
<title lang="de">Referenzen</title>
</item>
</xsl:param>
you will be able to address its individual nodes much more conveniently and elegantly.

Transform to nest items within one list when source contains flat tagging

I have the following XML file:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict</a></li>
And I want the output to be the following:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
<item><term>Interpersonal conflict</term></item>
</list>
</item>
Basically if I have multiple a[#class='term-ref'], the first instance should start the list rend="runon" and subsequent a[#class='term-ref'] should be included as item/term within the list.
The below was my try, but it is not working as I had hoped, and is closing the list before the second item/term (elements which are also not being output):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<xsl:element name="list">
<xsl:attribute name="rend" select="'runon'"/>
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
<xsl:if test="a[#class='term-ref'][position() >1]">
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="translate(., '.,;', '')"/>
</xsl:template>
</xsl:stylesheet>
On the source, XML, the above stylesheet produces this output:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
</list>
Interpersonal conflict</item>
Which is incorrect.
What am i doing wrong?
This short transformation (almost completely "push style", with no conditional instructions, no xsl:element and no unnecessary function calls like translate() or replace()):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="li">
<item><xsl:apply-templates/></item>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<list rend="runon">
<xsl:apply-templates mode="group"
select="../a[#class='term-ref']"/>
</list>
</xsl:template>
<xsl:template match="a[#class='term-ref']" mode="group">
<item><term><xsl:apply-templates/></term></item>
</xsl:template>
<xsl:template match="a[#class='term-ref']|li/text()" priority="-1"/>
</xsl:stylesheet>
when applied on the provided XML document -- which is well-formed:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525"
href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526"
href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
produces the wanted, correct result:
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict.</term>
</item>
</list>
</item>
This should work...
XML Input (well-formed)
<doc>
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
</doc>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:apply-templates select="i/text()"/>
<xsl:if test="a">
<list rend="runon">
<xsl:apply-templates select="a"/>
</list>
</xsl:if>
</item>
</xsl:template>
<xsl:template match="a">
<item><term><xsl:apply-templates select="node()"/></term></item>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="replace(.,'[.,;]','')"/>
</xsl:template>
</xsl:stylesheet>
Output
<doc>
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
</doc>
This should do what you are looking to do:
<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:template match="li">
<xsl:element name="item">
<xsl:apply-templates select="node()" />
<xsl:apply-templates select="." mode="items" />
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="normalize-space(translate(., '.,;', ''))"/>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" />
<xsl:template match="node()" mode="items" />
<xsl:template match="li" mode="items">
<xsl:apply-templates mode="items" />
</xsl:template>
<xsl:template match="li[count(a[#class = 'term-ref']) > 1]" mode="items">
<list rend="runon">
<xsl:apply-templates select="a[#class = 'term-ref']" mode="items" />
</list>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" mode="items">
<item>
<term>
<xsl:value-of select="."/>
</term>
</item>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<item>
See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
When run on an input file with just one a.term-ref, this produces:
<item>
See also<item>
<term>Interpersonal conflict</term>
</item>
</item>

Flat XML into tree with XSLT. Show one branch only

I have flat xml structure, i need to convert into hierarchy. With the help of stackoverflow I was able to do it.
Question: Is it possible to show only one branch using the same flat structure?
Here is my xml and xsl files:
XML
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="Stack.xsl"?>
<Items>
<Item>
<Id>1</Id>
<ParentId>0</ParentId>
<Name>1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>2</Id>
<ParentId>1</ParentId>
<Name>1.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>3</Id>
<ParentId>1</ParentId>
<Name>1.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>4</Id>
<ParentId>1</ParentId>
<Name>1.3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>5</Id>
<ParentId>1</ParentId>
<Name>1.4</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>6</Id>
<ParentId>0</ParentId>
<Name>2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>7</Id>
<ParentId>6</ParentId>
<Name>2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>8</Id>
<ParentId>6</ParentId>
<Name>2.2</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>9</Id>
<ParentId>0</ParentId>
<Name>3</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>10</Id>
<ParentId>3</ParentId>
<Name>1.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>8</ParentId>
<Name>2.2.1</Name>
<SortOrder>0</SortOrder>
</Item>
<Item>
<Id>11</Id>
<ParentId>5</ParentId>
<Name>1.4.1</Name>
<SortOrder>0</SortOrder>
</Item>
</Items>
XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]" />
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b><xsl:value-of select="Name" /></b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name" />
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)" />
<xsl:if test="count ($Descendants) > 0">
<ul>
<xsl:apply-templates select="$Descendants" />
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Current output I have:
1
1.1
1.2
1.2.1
1.3
1.4
1.4.1
2
2.1
2.2
2.2.1
3
Desireable result example:
1
1.1
1.2
1.2.1
1.3
1.4
2
3
One way to do this is to make use of node-set function, which will require the use of an extension namespace in XSLT.
What you could do is that instead of outputing the Descendants variable directly as currently:
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
You instead store the results in a variable
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
Then you can convert this 'result tree fragment' into a node-set, which you can then check for whether the selected element (held in a b element) exists. If so, you can then output it
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
Here is the full XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="exsl">
<xsl:output method="html"/>
<xsl:param name="SelectedId" select="'10'"/>
<xsl:key name="ChildNodes" match="/Items/Item" use="ParentId"/>
<xsl:template match="Items">
<ul>
<xsl:apply-templates select="Item[ParentId = 0]"/>
</ul>
</xsl:template>
<xsl:template match="Item">
<li>
<xsl:choose>
<xsl:when test="Id = $SelectedId">
<b>
<xsl:value-of select="Name"/>
</b>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Name"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="Descendants" select="key ('ChildNodes', Id)"/>
<xsl:if test="count ($Descendants) > 0">
<xsl:variable name="list">
<ul>
<xsl:apply-templates select="$Descendants"/>
</ul>
</xsl:variable>
<xsl:if test="exsl:node-set($list)//li[b]">
<xsl:copy-of select="$list"/>
</xsl:if>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<ul>
<li>1
<ul>
<li>1.1</li>
<li>1.2
<ul>
<li>
<b>1.2.1</b>
</li>
</ul></li>
<li>1.3</li>
<li>1.4</li>
</ul></li>
<li>2</li>
<li>3</li>
</ul>
Note, because I am using Microsoft XML here, the extension namespace is "urn:schemas-microsoft-com:xslt". For other processors, you will probably have to use "http://exslt.org/common"

XSLT 1.0 to iterate over several XML elements with comma delimited values

I have an XML document structured as follows
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
For each unique attribute value (delimited by commas) I need to list all item names associated with that value like so:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
My initial plan was to use a template to parse the attributes into Attribute nodes, surrounding each with appropriate tags, and then separating out the unique values with an XPATH expression like
Attribute[not(.=following::Attribute)]
but since the result of the template isn't a node-set that ever goes through an XML parser, I can't traverse it. I also tried exslt's node-set() function only to realize it does not allow me to traverse the individual Attribute nodes either.
At this point I'm at a loss for a simple way to do this and would really appreciate any help or ideas on how to proceed. Thanks!
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAtrByVal" match="attr" use="."/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<groups>
<xsl:apply-templates/>
</groups>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:apply-templates select="$vPass1/*"/>
</xsl:template>
<xsl:template match="item">
<group name="{name}">
<xsl:apply-templates select="attributes"/>
</group>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vText" select=
"concat($pText,',')"/>
<attr>
<xsl:value-of select="substring-before($vText,',')"/>
</attr>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match=
"attr[generate-id()
=
generate-id(key('kAtrByVal',.)[1])
]
">
<xsl:value-of select="concat('
',.,': ')"/>
<xsl:for-each select="key('kAtrByVal',.)">
<xsl:value-of select="../#name"/>
<xsl:if test="not(position()=last())">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
produces the wanted, correct result:
a: item1
b: item1
c: item1, item2
d: item1, item2
e: item2
Explanation:
Pass1: tokenization and end result:
<groups>
<group name="item1">
<attr>a</attr>
<attr>b</attr>
<attr>c</attr>
<attr>d</attr>
</group>
<group name="item2">
<attr>c</attr>
<attr>d</attr>
<attr>e</attr>
</group>
</groups>
.2. Pass2 takes the result of Pass1 (converted to a nodeset using the extension function ext:node-set()) as input, performs Muenchian grouping and produces the final, wanted result.
My first thought is to make two passes. First, tokenize the attributes elements using a (slightly) modified version of #Alejandro's answer to this previous question:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="item">
<item name="{name}">
<xsl:apply-templates select="attributes"/>
</item>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="separator" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<val>
<xsl:value-of select="normalize-space($text)"/>
</val>
</xsl:when>
<xsl:otherwise>
<val>
<xsl:value-of select="normalize-space(
substring-before($text, $separator))"/>
</val>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after(
$text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Which produces:
<items>
<item name="item1">
<val>a</val>
<val>b</val>
<val>c</val>
<val>d</val>
</item>
<item name="item2">
<val>c</val>
<val>d</val>
<val>e</val>
</item>
</items>
Then apply the following stylesheet to that output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="byVal" match="val" use="." />
<xsl:template match="val[generate-id() =
generate-id(key('byVal', .)[1])]">
<xsl:value-of select="." />
<xsl:text> : </xsl:text>
<xsl:apply-templates select="key('byVal', .)" mode="group" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="val" mode="group">
<xsl:value-of select="../#name" />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="val" />
</xsl:stylesheet>
Producing:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
Doing this in one stylesheet would require more thought (or an extension function).