XSLT - How to tell if an element with specific attribute value is the last in the matching series - xslt-2.0

I have the following XSLT template
<xsl:template match="xsd:element[#name != '' and not(starts-with(#type, 'common:'))]">
<xsl:if test="position() != last()">
"<xsl:value-of select="#name"/>",
</xsl:if>
<xsl:if test="position() = last()">
"<xsl:value-of select="#name"/>"
</xsl:if>
</xsl:template>
that tries to match all elements with non empty name and it's type doesn't start with 'common:' then it will generate a comma separated list of these elements names.
so if applied to
<xsd:element name="One" type="String"/>
<xsd:element name="" type="String"/>
<xsd:OtherNode />
<xsd:element name="Two" type="common:Characters"/>
<xsd:element name="Three" type="Long"/>
<xsd:OtherNode />
it should generate
"One",
"Three"
notice that there is no comma after "Three"
but it sounds like there is something wrong with position() and last() as when printing its values it doesn't sounds to be correct and there is always a comma ','
"One",
"Three",
a complete sample of an input XML that will be processed by the XSLT is an XSD something like
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="[h t t p] ://www.w3.org/2001/XMLSchema"
xmlns:common="[http]://abc.com/common/1.0">
<!-- definition of simple elements -->
<xsd:element name="orderperson" type="xsd:string"/>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="country" type="xsd:string"/>
<xsd:element name="title" type="xsd:string"/>
<xsd:element name="note" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:positiveInteger"/>
<xsd:element name="price" type="common:decimal"/>
<!-- definition of complex elements -->
<xsd:complexType name="shipto">
<xsd:sequence>
<xsd:element ref="name"/>
<xsd:element ref="address"/>
<xsd:element ref="city"/>
<xsd:element ref="country"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="item">
<xsd:sequence>
<xsd:element ref="title"/>
<xsd:element ref="note" minOccurs="0"/>
<xsd:element ref="quantity"/>
<xsd:element ref="price"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="shiporder">
<xsd:sequence>
<xsd:element ref="orderperson"/>
<xsd:element ref="shipto"/>
<xsd:element ref="item" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
and below is part of my XSLT after removing unrelated sections to keep it short
<xsl:stylesheet version="2.0"
xmlns:xsl="[http]://www.w3.org/1999/XSL/Transform"
xmlns:xsd="[http]://www.w3.org/2001/XMLSchema"
xmlns:xml="[http]://www.w3.org/XML/1998/namespace"
xmlns:common="[http]://abc.com/common/1.0">
<xsl:output method="text" media-type="text/xml" indent="yes" encoding="ISO-8859-1" />
<xsl:template match="/">
<xsl:call-template name="pre-properties"/>
<xsl:apply-templates/>
<xsl:call-template name="post-properties"/>
</xsl:template>
<xsl:template match="/xsd:schema/xsd:complexType/xsd:sequence/xsd:element"/>
<xsl:template name="pre-properties">
{
<xsl:template name="post-properties">
}
</xsl:template>
<xsl:template match="xsd:element[#name != '' and not(starts-with(#type, 'common:'))]">
<xsl:if test="position() != last()">
"<xsl:value-of select="#name"/>",
</xsl:if>
<xsl:if test="position() = last()">
"<xsl:value-of select="#name"/>"
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Move the check into a predicate e.g.
<xsl:template match="xsd:element[#name != '' and not(starts-with(#type, 'common:'))][position() != last()]">
"<xsl:value-of select="#name"/>",
</xsl:template>
<xsl:template match="xsd:element[#name != '' and not(starts-with(#type, 'common:'))][position() = last()]">
"<xsl:value-of select="#name"/>"
</xsl:template>
Or you might simply try
<xsl:value-of select="//xsd:element[#name != '' and not(starts-with(#type, 'common:'))]/concat('"', #name, '"')" separator=", "/>
where you want to output those values.

Related

How to access variable defined under for-each loop/If condition in another for each loop

I have to generate the output in sequence and so I wanted to know how to access the variable defined under For-each loop/If condition and then value of select inside another for loop.
As per my example how to access partn and date3? Please help and suggest.
what is the concept for achieving the same..I have tried with-param as well, but didn't work for me.
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="myfunc"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions" >
<xsl:output method="text" encoding="utf-8" />
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1">
<xsl:if test="PA = 'CE'">
<xsl:variable name="partn" select="PAN"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="E13">
<xsl:if test="ID = 033">
<xsl:variable name="date3"
select="substring(DAT,3,8)"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$partn"/>
<xsl:value-of select="$date3"/>
</xsl:for-each>
</xsl:template>
INPUT:
<?xml version='1.0' encoding='utf-8'?>
<ZGS>
<ID BEGIN="1">
<E1 SEGMENT="1">
<PA>AG</PA>
<NAME>ABC</NAME>
<SP>E</SP>
<AND>0004</AND>
</E1>
<E1 SEGMENT="1">
<PA>RE</PA>
<PAN>IUIOP</PAN>
<NAME>ABC1</NAME>
<SP>EQ</SP>
<AND>0005</AND>
<EKA3 SEGMENT="1">
<QU>009</QU>
</EKA3>
</E1>
<E1 SEGMENT="1">
<PA>CE</PA>
<PAN>PODW</PAN>
<NAME>ABC2</NAME>
<SP>EP</SP>
<AND>0006</AND>
</E1>
<E13 SEGMENT="1">
<ID>001</ID>
<DAT>20190329</DAT>
</E13>
<E13 SEGMENT="1">
<ID>002</ID>
<DAT>20190429</DAT>
</E13>
<E13 SEGMENT="1">
<IDD>033</IDD>
<DAT>20190529</DAT>
</E13>
<E1E>
<E1ED>
</E1ED>
<E1ED>
</E1ED>
</E1E>
In XSLT, variables once declared/defined, they cannot be changed. And exists only in the loop they are defined.
You might not need the xsl:for-each loop here. Instead the variables can be globally defined, so that you can use them where you want in your xslt.
You can try the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="myfunc"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="text" encoding="utf-8" />
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:variable name="partn" select="/ZGS/ID/E1[PA = 'CE']/PAN" />
<xsl:variable name="date3" select="substring(/ZGS/ID/E13[ID = '033']/DAT,3,8)" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL" />
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$partn" />
<xsl:value-of select="$date3" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYiu
Using xsl:param, it can be achieved as
<xsl:param name="partn" select="/ZGS/ID/E1[PA = 'CE']/PAN" />
<xsl:param name="date3" select="substring(/ZGS/ID/E13[ID = '033']/DAT,3,8)" />
<xsl:template match="ZGS/ID">
<xsl:for-each select="E1E">
<xsl:text>823</xsl:text>
<xsl:text>03</xsl:text>
<xsl:for-each select="E1ED">
<xsl:if test="QU = 012 ">
<xsl:value-of select="BEL" />
</xsl:if>
</xsl:for-each>
<xsl:for-each select="$partn">
<xsl:value-of select="." />
</xsl:for-each>
<xsl:value-of select="$date3" />
</xsl:for-each>
</xsl:template>
https://xsltfiddle.liberty-development.net/jyRYYiu/1

Subtraction by decimal number

I have a to subtract the amount -1 based on the condition.Please any one help.
Input:
<JD>
<GP xmlns="">
I xmlns="">
<PK>40</PK>
<A/>
<AMNT>11659650.15</AMNT>
<B/>
<C/>
</I>
<I xmlns="">
<PK>50</PK>
<A/>
<AMNT>11659650.15</AMNT>
<B/>
<C/>
</I>
</GP>
</JD>
Tried with below XSLT and got 1.165964915E7 for 50.
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT"/>
<xsl:element name="AMT">
<xsl:value-of select="$a"/>
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT"/>
<xsl:element name="AMT">
<xsl:value-of select="$a - 1"/>
</xsl:element>
</xsl:if>
Considering your given input as following:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<JD>
<GP>
<I>
<PK>40</PK>
<A />
<AMNT>11659650.15</AMNT>
<B />
<C />
</I>
<I>
<PK>50</PK>
<A />
<AMNT>11659650.15</AMNT>
<B />
<C />
</I>
</GP>
</JD>
</Root>
In XSLT 2.0, you can try it using xs:decimal as below:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/Root">
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="$a" />
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="xs:decimal($a) - 1" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
In XSLT 1.0, Use the format-number() function:
<xsl:template match="/Root">
<xsl:for-each select="JD/mo:GP/I">
<xsl:if test="PK='40'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="$a" />
</xsl:element>
</xsl:if>
<xsl:if test="PK='50'">
<xsl:variable name="a" select="AMNT" />
<xsl:element name="AMT">
<xsl:value-of select="format-number($a - 1, '0.##')" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>

Aggregate nodes based on conditon under new parent

I have an given XML and I want to convert it in new xml and want to aggregate nodes based on status and orderId.
<?xml version="1.0" encoding="utf-8"?>
<OrderStatusUpdate>
<OrderStatusEvents>
<OrderStatusEvent>
<StoreCode>store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:37:05</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item2</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order1</OrderId>
<ItemId>Item3</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Shipped</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
<OrderStatusEvent>
<StoreCode>Store1</StoreCode>
<OrderId>Order2</OrderId>
<ItemId>Item1</ItemId>
<OrderStatusDetails>
<OrderStatusDetail>
<OrderStatusEventTimeStamp>2017-03-19 03:48:35</OrderStatusEventTimeStamp>
<StatusName>Cancelled</StatusName>
</OrderStatusDetail>
</OrderStatusDetails>
</OrderStatusEvent>
</OrderStatusEvents>
</OrderStatusUpdate>
And I want an output like this. Here I am grouping elements based on status and orderId.
<Orders>
<group name="CANCELLED">
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order1">
<ORDER_ITEM item="item1" />
<ORDER_ITEM item="item2" />
</ORDER>
</STATUS>
<STATUS ID="CANCELLED" DESCRIPTION="Goods Cancelled">
<ORDER ID="Order2">
<ORDER_ITEM item="item1" />
</ORDER>
</STATUS>
</group>
<group name="SHIPPED">
<STATUS ID="SHIPPED" DESCRIPTION="Goods SHIPPED">
<ORDER ID="Order1">
<ORDER_ITEM item="item3" />
</ORDER>
</STATUS>
<group>
</Orders>
I am using the following xslt and it is working fine. Is there any way to improve this.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<xsl:element name="STATUS">
<xsl:attribute name="ID"><xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:attribute name="DESCRIPTION">Goods <xsl:value-of select="$group-name" /></xsl:attribute>
<xsl:element name="ORDER">
<xsl:attribute name="ID"><xsl:value-of select="$order-id" /></xsl:attribute>
<xsl:for-each select="current-group()">
<xsl:if test="$group-name = 'Shipped'">
<xsl:call-template name="Shipped">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:if test="$group-name = 'Cancelled'">
<xsl:call-template name="Cancelled">
<xsl:with-param name="nodes">
<xsl:copy-of select="." />
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template name="Shipped">
<xsl:param name="nodes">
</xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="Cancelled">
<xsl:param name="nodes"></xsl:param>
<xsl:element name="ORDER_ITEM">
<xsl:attribute name="ID"><xsl:value-of select="$nodes/OrderStatusEvent/ItemId" /></xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is easier to use literal result elements and attribute value templates as long as you don't need to compute element or attribute names at run-time and I don't think you need the two templates and call-template, it suffices to use
<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:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="OrderStatusUpdate/OrderStatusEvents">
<Orders>
<xsl:for-each-group select="OrderStatusEvent" group-by="OrderStatusDetails/OrderStatusDetail/StatusName">
<xsl:variable name="group-name" select="current-grouping-key()" />
<group name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="OrderId">
<xsl:variable name="order-id" select="current-grouping-key()" />
<STATUS ID="{$group-name}" DESCRIPTION="Goods {$group-name}">
<ORDER ID="{$order-id}">
<xsl:apply-templates select="current-group()"/>
</ORDER>
</STATUS>
</xsl:for-each-group>
</group>
</xsl:for-each-group>
</Orders>
</xsl:template>
<xsl:template match="OrderStatusEvent">
<ORDER_ITEM ID="{ItemId}"/>
</xsl:template>
</xsl:stylesheet>

XSLT 2.0 Group Node-set checking descendant attributes

I have the following node-set fragment:
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)">
<category value="Reporting"/>
</category>
</category>
</category>
<category value="Library">
<category value="Enterprise Imaging">
<category value="Desktops"/>
</category>
</category>
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)"/>
</category>
</category>
Which is generated by the following:
<xsl:template name="categories">
<xsl:param name="topicmeta"/>
<xsl:variable name="VarCategories">
<xsl:for-each select="map/topicmeta/category">
<xsl:if test="contains(./data/#value, 'Library')">
<xsl:if
test="string-length(./data/#value) - string-length(translate(./data/#value, '/', '')) > 1">
<xsl:call-template name="category">
<xsl:with-param name="category" select="./data/#value"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="outputCategories">
<xsl:with-param name="categories" select="$VarCategories"/>
</xsl:call-template>
</xsl:template>
I need to output:
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)">
<category value="Reporting"/>
</category>
</category>
<category value="Enterprise Imaging">
<category value="Desktops"/>
</category>
</category>
I am trying to use the for-each-group
<xsl:template name="outputCategories">
<xsl:param name="categories"/>
<xsl:element name="categories">
<xsl:for-each-group select="$categories/*" group-adjacent="#value">
<xsl:sort select="#value"></xsl:sort>
<xsl:copy-of select="." />
</xsl:for-each-group>
</xsl:element>
</xsl:template>
Which gives me:
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)">
<category value="Reporting"/>
</category>
</category>
I need to check each level and group each distinct value.
Put your code into a recursive function:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="mf xs">
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="element(category)*">
<xsl:param name="categories" as="element(category)*"/>
<xsl:for-each-group select="$categories" group-by="#value">
<category value="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group()/category)"/>
</category>
</xsl:for-each-group>
</xsl:function>
<xsl:param name="cats">
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)">
<category value="Reporting"/>
</category>
</category>
</category>
<category value="Library">
<category value="Enterprise Imaging">
<category value="Desktops"/>
</category>
</category>
<category value="Library">
<category value="PACS">
<category value="IMPAX (PACS)"/>
</category>
</category>
</xsl:param>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:sequence select="mf:group($cats/category)"/>
</xsl:template>
</xsl:transform>
Online at http://xsltransform.net/3NJ38ZB.
If you need the sorting as well then change the function to e.g.
<xsl:function name="mf:group" as="element(category)*">
<xsl:param name="categories" as="element(category)*"/>
<xsl:for-each-group select="$categories" group-by="#value">
<xsl:sort select="current-grouping-key()"/>
<category value="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group()/category)"/>
</category>
</xsl:for-each-group>
</xsl:function>
As for the sample you have linked to in your comment, it puts its created category elements in the FO namespace, you either have to avoid that by doing
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:exsl="http://exslt.org/common"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs exsl mf">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="status"/>
<xsl:param name="draft"/>
<xsl:template match="/">
<index>
<xsl:call-template name="categories"> </xsl:call-template>
</index>
</xsl:template>
<xsl:template name="categories">
<xsl:param name="topicmeta"/>
<xsl:variable name="VarCategories">
<xsl:for-each select="map/topicmeta/category">
<xsl:if test="contains(./data/#value, 'Library')">
<xsl:if
test="string-length(./data/#value) - string-length(translate(./data/#value, '/', '')) > 1">
<xsl:call-template name="category">
<xsl:with-param name="category" select="./data/#value"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select="mf:group($VarCategories/category)"/>
</xsl:template>
<xsl:template name="category" xmlns="">
<xsl:param name="category"/>
<xsl:if test="string-length($category) > 0">
<xsl:element name="category">
<xsl:attribute name="value">
<xsl:choose>
<xsl:when test="substring-before($category, '/') = ''">
<xsl:value-of select="$category"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($category, '/')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:call-template name="category">
<xsl:with-param name="category" select="substring-after($category, '/')"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:function name="mf:group" as="element(category)*" xmlns="">
<xsl:param name="categories" as="element(category)*"/>
<xsl:for-each-group select="$categories" group-by="#value">
<xsl:sort select="current-grouping-key()"/>
<category value="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group()/category)"/>
</category>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
or you have to make sure the paths in the function select the elements in that namespace:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns="http://www.w3.org/1999/XSL/Format"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:exsl="http://exslt.org/common"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs exsl mf">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="status"/>
<xsl:param name="draft"/>
<xsl:template match="/">
<index>
<xsl:call-template name="categories"> </xsl:call-template>
</index>
</xsl:template>
<xsl:template name="categories">
<xsl:param name="topicmeta"/>
<xsl:variable name="VarCategories">
<xsl:for-each select="map/topicmeta/category">
<xsl:if test="contains(./data/#value, 'Library')">
<xsl:if
test="string-length(./data/#value) - string-length(translate(./data/#value, '/', '')) > 1">
<xsl:call-template name="category">
<xsl:with-param name="category" select="./data/#value"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select="mf:group($VarCategories/*)"/>
</xsl:template>
<xsl:template name="category">
<xsl:param name="category"/>
<xsl:if test="string-length($category) > 0">
<xsl:element name="category">
<xsl:attribute name="value">
<xsl:choose>
<xsl:when test="substring-before($category, '/') = ''">
<xsl:value-of select="$category"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-before($category, '/')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:call-template name="category">
<xsl:with-param name="category" select="substring-after($category, '/')"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:function name="mf:group" as="element(category)*" xpath-default-namespace="http://www.w3.org/1999/XSL/Format">
<xsl:param name="categories" as="element(category)*"/>
<xsl:for-each-group select="$categories" group-by="#value">
<xsl:sort select="current-grouping-key()"/>
<category value="{current-grouping-key()}">
<xsl:sequence select="mf:group(current-group()/category)"/>
</category>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>

Xslt for splitting a url in parts and combing them again

I want to build a sort of breadcrumb. I have link for example
http://server/site1/site2/site3
and want to build something like
http://serverhttp://server/site1...
How can I do this with xslt?
This can be accomplished with a recursive template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="urlSample" select="'http://server/site1/site2/site3'" />
<xsl:template match="/">
<xsl:call-template name="UrlLinks">
<xsl:with-param name="url" select="$urlSample" />
</xsl:call-template>
</xsl:template>
<xsl:template name="UrlLinks">
<xsl:param name="url" />
<xsl:call-template name="UrlLinksIter">
<xsl:with-param name="portionSoFar" select="concat(substring-before($url, '://'), '://')" />
<xsl:with-param name="remainder" select="concat(substring-after($url, '://'), '/')" />
</xsl:call-template>
</xsl:template>
<xsl:template name="UrlLinksIter">
<xsl:param name="portionSoFar" />
<xsl:param name="remainder" />
<xsl:variable name="nextPart" select="substring-before($remainder, '/')" />
<xsl:variable name="nextRemainder" select="substring-after($remainder, '/')" />
<xsl:if test="normalize-space($nextRemainder) or normalize-space($nextPart)">
<xsl:variable name="url" select="concat($portionSoFar, $nextPart)"/>
<xsl:if test="normalize-space($nextPart)">
<!-- $nextPart could be empty if there are multiple slashes in a row-->
<a href="{$url}">
<xsl:value-of select="$url"/>
</a>
</xsl:if>
<xsl:call-template name="UrlLinksIter">
<xsl:with-param name="portionSoFar" select="concat($url, '/')" />
<xsl:with-param name="remainder" select="$nextRemainder" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this is run on any input (since the sample value is in a variable here) this produces:
http://server
http://server/site1
http://server/site1/site2
http://server/site1/site2/site3

Resources