Xslt for splitting a url in parts and combing them again - url

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

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>

Splitfunction gives troubles

I'm facing the following problem or challenge.
I've a an element in my source XML which can have 450 characters.
With my xslt I want to transform this into chunks of 75 characters.
...
<T61>
<parentInfo>SomeInfo</parentInfo>
<T86>
<info>abcdefghijklmnopqrstuvwxyz01234567890abcdefghijklmnopqrstuvwxyz01234567890</info>
</T86>
</T61>
...
The output I generate should look something like:
<T31>
<x>abcdefghijklmnopqrstuvwxyz01234567890</x>
</T31>
<T31>
<x>abcdefghijklmnopqrstuvwxyz01234567890</x>
</T31>
In my code I use an template for T61 which does his work.
I thought to create another template for T86 and call this from inside the T61 template but this seems not to work because I've the complete string. I created an function which could split up the string in parts of 75. But the outcome of the function is still the complete string.
I used a function from an earlier post:
<xsl:function name="my:splitItUp" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="chunk-size" as="xs:integer"/>
<xsl:value-of>
<xsl:for-each-group select="string-to-codepoints($input)" group-by="(position() -1) idiv $chunk-size">
<xsl:sequence select="codepoints-to-string(current-group())"/>
</xsl:for-each-group>
</xsl:value-of>
</xsl:function>
...
<xsl:template match="T86">
<xsl:for-each select="my:splitItUp(info, 75)">
<T31>
<communication>
<xsl:value-of select="." />
</communication>
</T31>
</xsl:for-each>
</xsl:template>
This structure always result in a complete string. In debug I see it split it up but it concatenates the result together. Can I somehow come out of the function?
Best Regards Dirk
Please have a look this XSLT where you need to set <xsl:param name="stringRequired" select="xs:integer(13)"/> to chunk text:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8"/>
<xsl:param name="XML">
<info>abcdefghijklmnopqrstuvwxyz01234567890abcdefghijklmnopqrstuvwxyz01234567890</info>
</xsl:param>
<xsl:param name="stringRequired" select="xs:integer(13)"/>
<xsl:param name="XMLLenfgh" select="string-length($XML)"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$XMLLenfgh gt $stringRequired">
<xsl:call-template name="getPart"/>
</xsl:when>
<xsl:otherwise>
<T31>
<x>
<xsl:value-of select="$XML/info"/>
</x>
</T31>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="getPart">
<xsl:param name="XML" select="$XML"/>
<xsl:param name="stringRequired" select="$stringRequired"/>
<xsl:param name="XMLLenfgh" select="$XMLLenfgh"/>
<xsl:message>
<xsl:value-of select="$XML"/>
</xsl:message>
<xsl:if test="$XMLLenfgh gt $stringRequired">
<T>
<x>
<xsl:value-of select="substring($XML,1,$stringRequired)"/>
</x>
</T>
<xsl:call-template name="getPart">
<xsl:with-param name="XML"
select="substring($XML,string-length(substring($XML,1,$stringRequired)))"/>
<xsl:with-param name="XMLLenfgh"
select="string-length(substring($XML,string-length(substring($XML,1,$stringRequired))))"/>
<xsl:with-param name="stringRequired" select="$stringRequired"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>abcdefghijklm</x>
</T>
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>mnopqrstuvwxy</x>
</T>
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>yz01234567890</x>
</T>
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>0abcdefghijkl</x>
</T>
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>lmnopqrstuvwx</x>
</T>
<T xmlns:xs="http://www.w3.org/2001/XMLSchema">
<x>xyz0123456789</x>
</T>

XSLT Wrapping text with multiple tags

I have this span element with a class of autbib-pc-bold-italic
<span class="autbib-pc-bold-italic">autbib</span>
I want to create element tags base on the #class attribute value:
My output should be:
<autbib><pc><bold><italic>autbib</italic></bold></pc></autbib>
Here is my xsl templates:
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="count" select="count(tokenize(#class, '-'))"/>
<xsl:with-param name="class" select="tokenize(#class, '-')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="pbib.loop">
<xsl:param name="index" select="1" />
<xsl:param name="count" select="count(tokenize(#class, '-')) + 1"/>
<xsl:param name="class" select="tokenize(#class, '-')"/>
<xsl:element name="{$class[1]}">
<xsl:if test="not($index = $count)">
<xsl:element name="{$class[$index]}">
<xsl:apply-templates/>
</xsl:element>
</xsl:if>
</xsl:element>
<xsl:if test="not($index = $count)">
<xsl:call-template name="pbib.loop">
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
And have this output which is wrong:
<autbib>
<pc>autbib</pc>
<bold>autbib</bold>
<italic>autbib</italic>
</autbib>
I need to have this output:
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>
My problem is that I'm not sure where I should place xsl:apply-template so that tags wrap with each other.
Here is my suggestion:
<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 indent="yes"/>
<xsl:template match="span[contains(#class,'autbib')]">
<xsl:param name="classes" select="tokenize(#class, '-')"/>
<xsl:choose>
<xsl:when test="not($classes[1])">
<xsl:apply-templates/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$classes[1]}">
<xsl:apply-templates select=".">
<xsl:with-param name="classes" select="$classes[position() gt 1]"/>
</xsl:apply-templates>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With Saxon 9.4 that transforms the input
<span class="autbib-pc-bold-italic">autbib</span>
into the result
<autbib>
<pc>
<bold>
<italic>autbib</italic>
</bold>
</pc>
</autbib>

Resources