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

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>

Related

Remove duplicates based on condition

I am trying to remove duplicates from my xml based on a condition in XSLT1.0
Here is the input xml.
<?xml version="1.0" encoding="UTF-8"?>
<Envelope
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<Header>
<MessageId>{D5B72T7A-58E0-4930-9CEB-A06RT56AR21B0}</MessageId>
<Action>http://tempuri.org/TRH_FinalQueryService/find</Action>
</Header>
<Body>
<MessageParts
xmlns="http://schemas.microsoft.com/dynamics/2011/01/documents/Message">
<TRH_FinalQuery
xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery">
<TRH_UnionView class="entity">
<Company>1</Company>
<CS/>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>1</Company>
<CS>1</CS>
<Text_1>1</Text_1>
<Text_2>Soap</Text_2>
<WS>6</WS>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
<TRH_UnionView class="entity">
<Company>2</Company>
<CS/>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<WS/>
</TRH_UnionView>
</TRH_FinalQuery>
</MessageParts>
</Body>
</Envelope>
Here is the xslt that I have applied.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message" xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery" exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="r:TRH_FinalQuery" match="r:TRH_FinalQuery" use="concat(r:Text_1, '|', r:Company)" />
<!-- move all elements to no namespace -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="r:TRH_FinalQuery[r:TRH_UnionView[#class='entity']/r:WessexCostCenter=''][key('r:TRH_FinalQuery',concat(r:Text_1, '|', r:Company))[1]]"/>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<!-- removes Envelope -->
<xsl:template match="m:Envelope">
<xsl:apply-templates />
</xsl:template>
<!-- removes Header,MessageId,Action and Body -->
<xsl:template match="m:*">
<xsl:apply-templates select="*" />
</xsl:template>
<!-- rename MessageParts to Document + skip the Run wrapper -->
<xsl:template match="m:MessageParts">
<DocumentElement>
<xsl:apply-templates select="r:TRH_FinalQuery/*" />
</DocumentElement>
</xsl:template>
<!-- rename RunObject to Item -->
<xsl:template match="r:TRH_UnionView[#class='entity']">
<xsl:choose>
<xsl:when test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:when>
<xsl:otherwise>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<xsl:apply-templates select="r:Company" />
</Item>
<Item>
<xsl:apply-templates select="r:Text_1" />
<xsl:apply-templates select="r:Text_2" />
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Below is the output I am getting
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>1</Text_1>
<Text_2>Lotion</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
Below is the expected output
<?xml version="1.0" encoding="utf-8"?>
<DocumentElement>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>1</Company>
</Item>
<Item>
<Text_1>6</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>2</Company>
</Item>
<Item>
<Text_1>5</Text_1>
<Text_2>Shampoo</Text_2>
<Company>0123</Company>
</Item>
</DocumentElement>
I am trying to remove all duplicates based on condition
If the Text_1 and Company are same.
If the point 1 is true then retain all records having value in WS tag and remove records where there no value in WS tag.
Can you please suggest what I am doing wrong
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://schemas.microsoft.com/dynamics/2011/01/documents/Message"
xmlns:r="http://schemas.microsoft.com/dynamics/2008/01/documents/TRH_FinalQuery"
exclude-result-prefixes="m r">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="myKey" match="r:TRH_UnionView" use="concat(r:Text_1, '|', r:Company)" />
<!-- Simplify things by not having an identity. Using this approach, you will not have to suppress
any elements.
-->
<xsl:template match="node()">
<xsl:apply-templates select="node()"/>
</xsl:template>
<!-- Start at the root. -->
<xsl:template match="/">
<DocumentElement>
<xsl:apply-templates select="node()" />
</DocumentElement>
</xsl:template>
<xsl:template match="r:TRH_UnionView">
<xsl:choose>
<!-- Handle the duplicates with no value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1 and
count((key('myKey',concat(r:Text_1, '|', r:Company)))[r:WS!='']) = 0">
<!-- Is this the first of the duplicates? -->
<xsl:if test="generate-id(.) = generate-id(key('myKey',concat(r:Text_1, '|', r:Company))[1])">
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<!-- Handle the duplicates with value at least one value in the WS tag. -->
<xsl:when test="count(key('myKey',concat(r:Text_1, '|', r:Company))) > 1">
<xsl:if test="r:WS!=''">
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>
<xsl:value-of select="r:Text_1" />
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:WS" />
</Text_1>
<Text_2>WS BodayWash</Text_2>
<Company>0123</Company>
</Item>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>
<xsl:value-of select="r:Company"/>
</Company>
</Item>
<Item>
<Text_1>
<xsl:value-of select="r:Text_1"/>
</Text_1>
<Text_2>
<xsl:value-of select="r:Text_2"/>
</Text_2>
<Company>0123</Company>
</Item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

virtual nesting of list using XSLT

I need nesting of list in the following manner, I have to create n-1 list wrappers, where n is the value of #virtual-nesting:
XML Doc:
<l virtual-nesting="3">
<li>
<lilabel/>
<p>
<text>Data used:</text>
</p>
</li>
</l>
Output Required:
<list>
<listitem>
<bodytext>
<list>
<listitem>
<label/>
<bodytext>
<p>
<text>Data used:</text>
</p>
</bodytext>
</listitem>
</list>
</bodytext>
</listitem>
</list>
XSLT Itried. As I am new to this:
<xsl:template match="l">
<xsl:choose>
<xsl:when test="#virtual-nesting">
<xsl:variable name="virtual"><xsl:value-of select="#virtual-nesting"/></xsl:variable>
<xsl:if test="$virtual>0">
<xsl:apply-templates select="generate-id(following-sibling::node()),#virtual-nesting-1"/>
</xsl:if>
<xsl:apply-templates select="li"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="list">
<xsl:apply-templates select="li"/> <!-- Template for list and lilabel is already created -->
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Kindly guide me through this.
Recursion is the key to creating nested structures in XSLT.
The following uses a recursive <xsl:template match="l"> with a $level parameter. This parameter defaults to #virtual-nesting - 1 and after that is decremented with every recursive step.
The template takes two different paths, depending on its value.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="l">
<xsl:param name="level" select="#virtual-nesting - 1" />
<list>
<listitem>
<xsl:if test="$level <= 1">
<xsl:apply-templates />
</xsl:if>
<xsl:if test="$level > 1">
<bodytext>
<xsl:apply-templates select=".">
<xsl:with-param name="level" select="$level - 1" />
</xsl:apply-templates>
</bodytext>
</xsl:if>
</listitem>
</list>
</xsl:template>
<xsl:template match="li">
<label>
<xsl:apply-templates select="lilabel/node()" />
</label>
<bodytext>
<xsl:apply-templates select="node()[not(self::lilabel)]" />
</bodytext>
</xsl:template>
</xsl:transform>
Result:
<list>
<listitem>
<bodytext>
<list>
<listitem>
<label/>
<bodytext>
<p>
<text>Data used:</text>
</p>
</bodytext>
</listitem>
</list>
</bodytext>
</listitem>
</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.

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"

Sorting XSLT variable date

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.