A distinct list based on split values - list

My end goal is to have a unique list of ID's I can iterate through. Here goes:
I have an XML of products (Items). In the complete XML there will be +200,000 items. In this example there is two:
<?xml version="1.0" encoding="utf-8"?>
<Export Shop="Demo Webshop" Type="Full" Clean="true" CleanIsolationShopID="SHOP1">
<Items>
<Item ItemNo="1001" ShopID="SHOP1" VariantCode="1616_42.1615_01.ct_HD">
</Item>
<Item ItemNo="1001" ShopID="SHOP1" VariantCode="1616_42.1615_02.ct_HD" >
</Item>
</Items>
The content of attribute VariantCode I need to split. For the first Item that should give me 1616_42 and 1615_01 and ct_HD. The end result is to import it to a table with the composite primary key ItemNo+VariantOption (VariantOption being the split value).
The XSLT furthermore has:
<table tableName="EcomVariantOptionsProductRelation">
<xsl:for-each select="Export/Items/Item">
<xsl:call-template name="split">
<xsl:with-param name="pText" select="#VariantCode"/>
<xsl:with-param name="ProductID" select="concat(#ItemNo,'##',#ShopID)"/>
/xsl:call-template>
</xsl:for-each>
The template being called that performs the actual split:
<xsl:template match="text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:param name= "ProductID" select="." />
<xsl:choose>
<xsl:when test="string-length($pText) > 0">
<xsl:choose>
<xsl:when test="contains($pText, '.')">
<!-- has dot (more than one variantOption) -->
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID">
<xsl:value-of select="substring-before($pText,'.')"/>
</column>
<column columnName="VariantOptionsProductRelationProductID">
<xsl:value-of select="$ProductID"/>
</column>
</item>
</xsl:when>
<xsl:otherwise>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID">
<xsl:value-of select="$pText"/>
</column>
<column columnName="VariantOptionsProductRelationProductID">
<xsl:value-of select="$ProductID"/>
</column>
</item>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, '.')"/>
<xsl:with-param name="ProductID" select="$ProductID"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- empty string (no variants) -->
<xsl:value-of select="$pText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The problem is that the transformed output, ie
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[1616_42]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
is repeated, because the "1616_42" (and "ct_HD" also) part exists twice in two different items. And I need for the output to be unique, since it finally goes to a table where this composite key (VariantID+ProductID) is unique.
The desired result for the two should be:
<table tableName="EcomVariantOptionsProductRelation">
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[1616_42]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[1615_01]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[ct_HD]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[1615_02]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[1616_50]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
<item tableName="EcomVariantOptionsProductRelation">
<column columnName="VariantOptionsProductRelationVariantID"><![CDATA[ct_NHD]]></column>
<column columnName="VariantOptionsProductRelationProductID"><![CDATA[1001##SHOP1]]></column>
</item>
</table>
Point being: no duplicates.
Searching the web I can see the possibility of creating lists with some kind of unique identifier. But I have no clue if it is possible in my scenario, and even if it is, no clue as how to implement.
Ideas? XSLT 1.0 is used.

The only way I can think of doing this (in XSLT 1.0) is by means of a "two-pass" transform. Effectively, you perform two transforms (although this can be done in a single stylesheet, as I am going to demonstrate). The first transform will split your current VariantCode attributes into separate elements, so the result is like so
<Item ProductId="1001##SHOP1">
<Variant>1616_42</Variant>
<Variant>1615_01</Variant>
<Variant>ct_HD</Variant>
</Item>
The second transform can then use a technique called Muenchian Grouping to output the distinct Variant elements you require.
For this to work, the results of the first transform are simply stored in a variable
<xsl:variable name="variantSplit">
<xsl:apply-templates select="//Item" />
</xsl:variable>
So, in this case, you would have a template matching Item to do the copying and splitting required:
<xsl:template match="Item">
<Item ProductID="{#ItemNo}##{#ShopID}">
<xsl:call-template name="VariantCodeSplit" />
</Item>
</xsl:template>
(In case you haven't seen them before, the curly braces in the ProductID attribute are "Attribute Value Templates", and indicate an expresion to be evaluated, rather than output literally).
Now, you have transformed XML in a variable, where each Item element has multiple child Variant elements as shown above.
But wait! This is XSLT 1.0, which means the contents on the variable is actually a "Result Tree Fragment". If you want to start apply templates on it, you need to use an extension function to transform it into a node-set. This depends on what processor you are using, but you are almost certain to have the node-set function available. It is just a case of declaring the correct namespace. (See http://www.xml.com/pub/a/2003/07/16/nodeset.html for more details).
Anyway, the next stage involves the Muenchian Grouping technique. This involves defining a key to match the new Variant elements, by the a combination of the ProductId and the (split) variant code
<xsl:key name="Test" match="Variant" use="concat(../#ProductID, '|', .)" />
Then, to get the distinct Variant elements, you look for the elements that occur first in the xsl:key for their given combination of ProductID and code
<xsl:apply-templates select="msxml:node-set($variantSplit)/Item/Variant
[generate-id() = generate-id(key('Test', concat(../#ProductID, '|', .))[1])]" />
(Note the use of the node-set extension function here. In my case, I am using Microsoft's).
You can then have a template that matches the Variant element, and you know each match will be a distinct occurence, so you can output the product id and code.
Try this XSLT as a starter. Note it doesn't give you the element and attribute names used in your example (I have shortened them for brevity), but it should give you a start, assuming your head hasn't exploded at this point:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxml">
<xsl:output method="xml" version="1.0" indent="yes" encoding="ISO-8859-1"/>
<xsl:key name="Test" match="Variant" use="concat(../#ProductID, '|', .)" />
<xsl:template match="/">
<xsl:variable name="variantSplit">
<xsl:apply-templates select="//Item" />
</xsl:variable>
<table>
<xsl:apply-templates select="msxml:node-set($variantSplit)/Item/Variant[generate-id() = generate-id(key('Test', concat(../#ProductID, '|', .))[1])]" />
</table>
</xsl:template>
<xsl:template match="Item">
<Item ProductID="{#ItemNo}##{#ShopID}">
<xsl:call-template name="VariantCodeSplit" />
</Item>
</xsl:template>
<xsl:template name="VariantCodeSplit">
<xsl:param name="Code" select="#VariantCode" />
<xsl:choose>
<xsl:when test="contains($Code, '.')">
<Variant>
<xsl:value-of select="substring-before($Code, '.')"/>
</Variant>
<xsl:call-template name="VariantCodeSplit">
<xsl:with-param name="Code" select="substring-after($Code, '.')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Variant>
<xsl:value-of select="$Code"/>
</Variant>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Variant">
<Item>
<Column name="Variant">
<xsl:value-of select="."/>
</Column>
<Column name="Product">
<xsl:value-of select="../#ProductID"/>
</Column>
</Item>
</xsl:template>
</xsl:stylesheet>
Of course, if your actual XML has 200000+ elements this may no be particularly fast.

Related

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.

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).

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.

XSL efficiency problem - need solution

I've got an interesting XSL scenario to run by you guys. So far my solutions seem to be inefficient (noticable increase in transformation time) so thought I'd put it out there.
The scenario
From the following XML we need to get the id of latest news item for each category.
The XML
In the XML I have a list of news items, a list of news categories and a list of item category relationships. Both the item list and item category list may as well be in random order (not date ordered).
<news>
<itemlist>
<item id="1">
<attribute name="title">Great new products</attribute>
<attribute name="startdate">2009-06-13T00:00:00</attribute>
</item>
<item id="2">
<attribute name="title">FTSE down</attribute>
<attribute name="startdate">2009-10-01T00:00:00</attribute>
</item>
<item id="3">
<attribute name="title">SAAB go under</attribute>
<attribute name="startdate">2008-01-22T00:00:00</attribute>
</item>
<item id="4">
<attribute name="title">M&A on increase</attribute>
<attribute name="startdate">2010-05-11T00:00:00</attribute>
</item>
</itemlist>
<categorylist>
<category id="1">
<name>Finance</name>
</category>
<category id="2">
<name>Environment</name>
</category>
<category id="3">
<name>Health</name>
</category>
</categorylist>
<itemcategorylist>
<itemcategory itemid="1" categoryid="2" />
<itemcategory itemid="2" categoryid="3" />
<itemcategory itemid="3" categoryid="1" />
<itemcategory itemid="4" categoryid="1" />
<itemcategory itemid="4" categoryid="2" />
<itemcategory itemid="2" categoryid="2" />
</itemcategorylist>
</news>
What I've tried
Using rtf
<xsl:template match="/">
<!-- for each category -->
<xsl:for-each select="/news/categorylist/category">
<xsl:variable name="categoryid" select="#id"/>
<!-- create RTF item list containing only items in that list ordered by startdate -->
<xsl:variable name="ordereditemlist">
<xsl:for-each select="/news/itemlist/item">
<xsl:sort select="attribute[#name='startdate']" order="descending" data-type="text"/>
<xsl:variable name="itemid" select="#id" />
<xsl:if test="/news/itemcategorylist/itemcategory[#categoryid = $categoryid][#itemid=$itemid]">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<!-- get the id of the first item in the list -->
<xsl:variable name="firstitemid" select="msxsl:node-set($ordereditemlist)/item[position()=1]/#id"/>
</xsl:for-each>
</xsl:template>
Would really appreciate any ideas you have.
Thanks,
Alex
Here is how I would do it:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output encoding="utf-8" />
<!-- this is (literally) the key to the solution -->
<xsl:key name="kItemByItemCategory" match="item" use="
/news/itemcategorylist/itemcategory[#itemid = current()/#id]/#categoryid
" />
<xsl:template match="/news">
<latest>
<xsl:apply-templates select="categorylist/category" mode="latest" />
</latest>
</xsl:template>
<xsl:template match="category" mode="latest">
<xsl:variable name="self" select="." />
<!-- sorted loop to get the latest news item -->
<xsl:for-each select="key('kItemByItemCategory', #id)">
<xsl:sort select="attribute[#name='startdate']" order="descending" />
<xsl:if test="position() = 1">
<category name="{$self/name}">
<xsl:apply-templates select="." />
</category>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="item">
<!-- for the sake of the example, just copy the node -->
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
The <xsl:key> indexes each news item by the associated category ID. Now you have a simple way of retrieving all the news items that belong to a certain category. The rest is straight-forward.
Output for me:
<latest>
<category name="Finance">
<item id="4">
<attribute name="title">M&A on increase</attribute>
<attribute name="startdate">2010-05-11T00:00:00</attribute>
</item>
</category>
<category name="Environment">
<item id="4">
<attribute name="title">M&A on increase</attribute>
<attribute name="startdate">2010-05-11T00:00:00</attribute>
</item>
</category>
<category name="Health">
<item id="2">
<attribute name="title">FTSE down</attribute>
<attribute name="startdate">2009-10-01T00:00:00</attribute>
</item>
</category>
</latest>
It looks like you should explore <xsl:key>. This effectively creates a hashmap and avoids looping through everything.
update Here is a typical tutorial:
http://www.learn-xslt-tutorial.com/Working-with-Keys.cfm
Your're looping through all items and sorting them by date, before you throw most of them away due to not being in the correct category.
Maybe something like this might be more suitable in your case:
<xsl:variable name="ordereditemlist">
<xsl:for-each select="/news/itemcategorylist/itemcategory[#categoryid = $categoryid]">
<xsl:variable name="itemid" select="#itemid"/>
And continue from there to gather only the news items that you actually require, then sort and copy them.

Find max node value

I have a xml given below:
<root title="الصفحة الرئيسة">
<item title="الصفحة الرئيسة" itemuri="tcm:8-29-4" ShowInNav="True" type="sg" pageuri="tcm:8-10592-64" sLink="/ara/index.aspx">
<item title="من نحن" itemuri="tcm:8-779-4" ShowInNav="True" type="sg" pageuri="tcm:8-9934-64" navorder="00500" sLink="/ara/about/index.aspx">
</item>
<item title="برامجنا" itemuri="tcm:8-817-4" ShowInNav="True" type="sg" pageuri="tcm:8-10112-64" navorder="00100" sLink="/ara/courses/language-study-abroad.aspx">
</item>
<item title="مدارسنا" itemuri="tcm:8-824-4" ShowInNav="True" type="sg" pageuri="tcm:8-10162-64" navorder="00300" sLink="/ara/schools/english-language.aspx">
</item>
</item>
</root>
Now I want to the value of maximum navorder, so that I can use that value further in "if" condition.
Below is the XSLT 1.0 code of two possible solutions.
A third solution is to use EXSLT.
A fourth solution is to use the maximum template of FXSL.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vMax1" select=
"*/*/*/#navorder[not(. < ../../*/#navorder)][3]"/>
$vMax1: <xsl:value-of select="$vMax1"/>
<xsl:variable name="vMax2">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="*/*/*/#navorder"/>
</xsl:call-template>
</xsl:variable>
$vMax2: <xsl:value-of select="$vMax2"/>
</xsl:template>
<xsl:template name="max">
<xsl:param name="pSeq"/>
<xsl:variable name="vLen" select="count($pSeq)"/>
<xsl:if test="$vLen > 0">
<xsl:choose>
<xsl:when test="$vLen = 1">
<xsl:value-of select="$pSeq[1]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalf"
select="floor($vLen div 2)"/>
<xsl:variable name="vMax1">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="$pSeq[not(position() > $vHalf)]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vMax2">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="$pSeq[position() > $vHalf]"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vMax1 >= $vMax2">
<xsl:value-of select="$vMax1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$vMax2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When the above transformatio is applied on the original XML document:
<root title="الصفحة الرئيسة">
<item title="الصفحة الرئيسة" itemuri="tcm:8-29-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10592-64"
sLink="/ara/index.aspx">
<item title="من نحن" itemuri="tcm:8-779-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-9934-64"
navorder="00500"
sLink="/ara/about/index.aspx"/>
<item title="برامجنا" itemuri="tcm:8-817-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10112-64"
navorder="00100"
sLink="/ara/courses/language-study-abroad.aspx"/>
<item title="مدارسنا" itemuri="tcm:8-824-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10162-64"
navorder="00300"
sLink="/ara/schools/english-language.aspx"/>
</item>
</root>
the wanted result is produced:
$vMax1: 00500
$vMax2: 00500
the following is a solution using XPath 1.0:
<?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" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="maximum">
<xsl:value-of select="/root/item//item[not(preceding-sibling::item/#navorder > #navorder or following-sibling::item/#navorder > #navorder)]/#navorder"/>
</xsl:variable>
<max>
<xsl:value-of select="$maximum"/>
</max>
</xsl:template>
</xsl:stylesheet>
The expression
/root/item//item[not(preceding-sibling::item/#navorder > #navorder or following-sibling::item/#navorder > #navorder)]/#navorder
filters all item nodes so that there is no preceding or following node that has a higher navorder value on the same level.
I found this forum post as one potential solution.
Of course, if you have access to XPath 2.0, there's a more direct solution: fn:max(arg, arg, …)
Couple of ways: with xpath 2 you can use the max() function, but if you're in xslt 1 you'll have to use sort in a for-each and then put the item at position 1 in your variable.
Have a look at this. Then use it in your xpath
/root/item[#navorder = math:max(/root/item/#navorder)]
Or something similar :)
NOTE This requires the use of EXSLT.
NOTE 2: No it doesn't. Just copy and paste the code.