Overwriting nodes within a node tree in xslt - xslt

This is my input
<SimpleInput>
<variable1>1</variable1>
<variable2>2</variable2>
<variable3>3</variable3>
</SimpleInput>
This is the output i am geting now
<Classes>
<ClassA>
<input1>overwrite 1</input1>
<input2>2</input2>
<input3>3</input3>
</ClassA>
<ClassB>
<input1>1</input1>
<input2>overwrite 2</input2>
<input3>3</input3>
</ClassB>
</Classes>
This is how i have achieved the above
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="myVar">
<input1><xsl:value-of select="/SimpleInput/variable1"/></input1>
<input2><xsl:value-of select="/SimpleInput/variable2"/></input2>
<input3><xsl:value-of select="/SimpleInput/variable3"/></input3>
</xsl:variable>
<xsl:template match="/">
<Classes>
<xsl:variable name="paramForClassA">
<input1><xsl:value-of select="'overwrite 1'"/></input1>
<input2><xsl:value-of select="/SimpleInput/variable2"/></input2>
<input3><xsl:value-of select="/SimpleInput/variable3"/></input3>
</xsl:variable>
<ClassA>
<xsl:call-template name="buildSection">
<xsl:with-param name="obj" select="$paramForClassA"/>
</xsl:call-template>
</ClassA>
<ClassB>
<xsl:variable name="paramForClassB">
<input1><xsl:value-of select="/SimpleInput/variable1"/></input1>
<input2><xsl:value-of select="'overwrite 2'"/></input2>
<input3><xsl:value-of select="/SimpleInput/variable3"/></input3>
</xsl:variable>
<xsl:call-template name="buildSection">
<xsl:with-param name="obj" select="$paramForClassB"/>
</xsl:call-template>
</ClassB>
</Classes>
</xsl:template>
<xsl:template name="buildSection">
<xsl:param name="obj" select="()"/>
<xsl:for-each select="$obj/*">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
But I want to be able to overwrite/create the nodes i want dynamically without rebuilding the entire node tree before passing it to the buildSection template.
Below is an example of the output I am looking for (with an additional input4 on ClassB which i am not able to achieve with the code above)
<Classes>
<ClassA>
<input1>overwrite 1</input1>
<input2>2</input2>
<input3>3</input3>
</ClassA>
<ClassB>
<input1>1</input1>
<input2>overwrite 2</input2>
<input3>3</input3>
<input4>New Item</input4>
</ClassB>
</Classes>
Something similar to what is done here, but i am looking to replace or add the node itself if it doesnt exist.
There are 2 limitations with my current design which i am looking to overcome when passing the parameter to a template call.
Adding new elements to ClassX if necessary
Removing the need to rebuild the entire node tree (There could be over 80 variable in the node tree in some cases)
The build template is actually in a separate file and could be used from multiple callers. If we hard code the node tree building logic in each class (which are also in separate files), if there is a requirement to update the template, then I will need to also update all the callers (which can be upto 30 separate ones). Hope this explains the necessity of the new design.
Ideally something like this when building paramForX
<xsl:variable name="myReplacementsForA">
<input1>overwrite 1</input1>
</xsl:variable>
<xsl:variable name="myReplacementsForB">
<input2>overwrite 2</input2>
<input4>Add 4</input4>
</xsl:variable>
<xsl:variable name="paramForClassA">
<xsl:apply-templates select="$myVar" mode="add-or-replace-value">
<with-param name="replacements" select="$myReplacementsForA"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:variable name="paramForClassB">
<xsl:apply-templates select="$myVar" mode="add-or-replace-value">
<with-param name="replacements" select="$myReplacementsForB"/>
</xsl:apply-templates>
</xsl:variable>

I managed to do it this way. Not sure if it is the most efficient
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="myVar">
<input1><xsl:value-of select="/SimpleInput/variable1"/></input1>
<input2><xsl:value-of select="/SimpleInput/variable2"/></input2>
<input3><xsl:value-of select="/SimpleInput/variable3"/></input3>
</xsl:variable>
<xsl:template match="/">
<Classes>
<xsl:variable name="myReplacementsForA">
<input1>overwrite 1</input1>
</xsl:variable>
<xsl:variable name="paramForClassA">
<xsl:apply-templates mode="buildNewParam">
<xsl:with-param name="original" select="$myVar"/>
<xsl:with-param name="replacements" select="$myReplacementsForA"/>
</xsl:apply-templates>
</xsl:variable>
<ClassA>
<xsl:value-of select="$paramForClassA/item2"/>
<xsl:call-template name="buildSection">
<xsl:with-param name="obj" select="$paramForClassA"/>
</xsl:call-template>
</ClassA>
<ClassB>
<xsl:variable name="myReplacementsForB">
<input2>overwrite 2</input2>
<input4>Add 4</input4>
</xsl:variable>
<xsl:variable name="paramForClassB">
<xsl:apply-templates mode="buildNewParam">
<xsl:with-param name="original" select="$myVar"/>
<xsl:with-param name="replacements" select="$myReplacementsForB"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:call-template name="buildSection">
<xsl:with-param name="obj" select="$paramForClassB"/>
</xsl:call-template>
</ClassB>
</Classes>
</xsl:template>
<xsl:template name="buildSection">
<xsl:param name="obj" select="()"/>
<xsl:for-each select="$obj/*">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template mode="buildNewParam" match=".">
<xsl:param name="original"/>
<xsl:param name="replacements"/>
<xsl:variable name="merged">
<xsl:for-each select="$original/*">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
<xsl:for-each select="$replacements/*">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="sorted">
<xsl:for-each select="$merged/*">
<xsl:sort select="name()"/>
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$sorted/*">
<xsl:if test="name()!=name(following-sibling::node()[1])">
<xsl:element name="{name()}">
<xsl:copy-of select="#*|node()" />
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

How to pass a collection of nodes as a param of an XSL template?

This is my template (a simplification of a real situation):
<xsl:template name="i">
<xsl:param name="args"/>
<xsl:for-each select="$args/*">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
I call it like this:
<xsl:template match="f">
<xsl:call-template name="i">
<xsl:with-param name="args"/>
<a><xsl:value-of select="./#one"/></a>
<a><xsl:value-of select="./#two"/></a>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
Basically, I create artificial nodes <a/>, which looks ugly to me. Is there a better way?
My example would use functions but I have done both:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:function name="mf:f1" as="text()">
<xsl:param name="items" as="item()*"/>
<xsl:value-of select="$items"/>
</xsl:function>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="foo">
<function-example>
<xsl:sequence select="mf:f1((#one, #two))"/>
</function-example>
<template-example1>
<xsl:call-template name="h">
<xsl:with-param name="args" select="#one, #two"/>
</xsl:call-template>
</template-example1>
<template-example2>
<xsl:call-template name="i">
<xsl:with-param name="args" select="#one, #two"/>
</xsl:call-template>
</template-example2>
</xsl:template>
<xsl:template name="h">
<xsl:param name="args"/>
<xsl:value-of select="$args"/>
</xsl:template>
<xsl:template name="i">
<xsl:param name="args"/>
<xsl:for-each select="$args">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
transforms
<foo one="1" two="2"/>
into
<function-example>1 2</function-example>
<template-example1>1 2</template-example1>
<template-example2>12</template-example2>
https://xsltfiddle.liberty-development.net/jyfAiD9
Of course, for the sole xsl:value-of use, if you don't want to have the default space separator between the args values, use xsl:value-of separator="".
Consider the following simple example:
XML
<f one="alpha" two="bravo"/>
template call
<xsl:template match="f">
<xsl:call-template name="i">
<xsl:with-param name="args" select="#one, #two"/>
</xsl:call-template>
</xsl:template>
template execution
<xsl:template name="i">
<xsl:param name="args"/>
<xsl:for-each select="$args">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/93PXKr2

How to separate the values by tag using XSLT Transformation/ loop the tag

I am trying to separate the map output values by tag. Right now I am getting all the values in one (BOM) tag. I want the output separated by each BOMTransactionType(Deleted/Added). I am using XSLT Transformation to separate it. Could anyone please let me know how can I do that. Thanks.
XSLT Transformation Logic in DataProcess Shape:
<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="/*">
<PLMData>
<ChangeOrders>
<AffectedItems>
<BOM>
<xsl:apply-templates/>
</BOM>
</AffectedItems>
</ChangeOrders>
</PLMData>
</xsl:template>
//Split the ItemNumber
<xsl:template match="ItemNumber/text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="$pText">
<xsl:element name="ItemNumber">
<xsl:value-of select= "substring-before(concat($pText, ','), ',')"/>
</xsl:element>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
//Split the BOMTransactionType
<xsl:template match="BOM/BOMTransactionType/text()" name="split1">
<xsl:param name="bText" select="."/>
<xsl:param name="bOrd" select="1"/>
<xsl:if test="$bText">
<xsl:element name="BOMTransactionType">
<xsl:value-of select= "substring-before(concat($bText, ','), ',')"/>
</xsl:element>
<xsl:call-template name="split1">
<xsl:with-param name="bText" select="substring-after($bText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Input:
<PLMData>
<ChangeOrders>
<AffectedItems>
<BOM>
<ItemNumber>P00001,020-00003-01</ItemNumber>
<BOMTransactionType>Added,Deleted</BOMTransactionType>
</BOM>
</AffectedItems>
</ChangeOrders>
</PLMData>
Output:
<PLMData>
<ChangeOrders>
<AffectedItems>
<BOM>
<ItemNumber>P00001</ItemNumber>
<ItemNumber>020-00003-01</ItemNumber>
<BOMTransactionType>Added</BOMTransactionType>
<BOMTransactionType>Deleted</BOMTransactionType>
</BOM>
</AffectedItems>
</ChangeOrders>
</PLMData>
Expected Output:
<PLMData>
<ChangeOrders>
<AffectedItems>
<BOM>
<ItemNumber>P00001</ItemNumber>
<BOMTransactionType>Added</BOMTransactionType>
</BOM>
<BOM>
<ItemNumber>020-00003-01</ItemNumber>
<BOMTransactionType>Deleted</BOMTransactionType>
</BOM>
</AffectedItems>
</ChangeOrders>
</PLMData>
I would do it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="BOM">
<xsl:call-template name="tokenize">
<xsl:with-param name="item-numbers" select="ItemNumber"/>
<xsl:with-param name="transaction-types" select="BOMTransactionType"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="item-numbers"/>
<xsl:param name="transaction-types"/>
<xsl:param name="delimiter" select="','"/>
<BOM>
<ItemNumber>
<xsl:value-of select="substring-before(concat($item-numbers, $delimiter), $delimiter)" />
</ItemNumber>
<BOMTransactionType>
<xsl:value-of select="substring-before(concat($transaction-types, $delimiter), $delimiter)" />
</BOMTransactionType>
</BOM>
<xsl:if test="contains($item-numbers, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="item-numbers" select="substring-after($item-numbers, $delimiter)"/>
<xsl:with-param name="transaction-types" select="substring-after($transaction-types, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

print xpath and value of element and attribute using XSLT

I would like to print path of element and attributes if any along with values using XSLT. e.g
XML :
<root>
<node attr='abc' module='try'>
<subnode>Roshan</subnode>
<subnode>Chetan</subnode>
<subnode>Jennifer</subnode>
</node>
</root>
Output :
/root##
/root/node##
/root/node/#attr##abc
/root/node/#module##try
/root/node/subnode[1]##Roshan
/root/node/subnode[2]##Chetan
/root/node/subnode[3]##Jennifer
I am trying with below snippet, but could only print path of element and it's value
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())" />
<xsl:if
test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of
select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')" />
</xsl:if>
<!-- <xsl:call-template name="attrData"></xsl:call-template> -->
</xsl:for-each>
<xsl:text>##</xsl:text>
<xsl:apply-templates select="node()" />
</xsl:template>
I am new to XSLT. Please help!!!!
I made the following XSLT and added also the [position] to the output. You can remove that if you need.
This gives this output:
/root[1]
/root[1]/node[1]
/root[1]/node[1]/#attr[1]##abc
/root[1]/node[1]/#module[1]##try
/root[1]/node[1]/subnode[1]##Roshan
/root[1]/node[1]/subnode[2]##Chetan
/root[1]/node[1]/subnode[3]##Jennifer
With this XSLT. With the two output template you can choose how to print the Xpath.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="" version="2.0">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="''"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="generateXPath">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:if test="not(empty(.))">
<xsl:variable name="thisXPath" select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:variable>
<xsl:apply-templates select="." mode="output">
<xsl:with-param name="previous" select="$previous"/>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
<xsl:for-each select="*|#*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="$thisXPath"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath">
<xsl:value-of select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:value-of>
<xsl:if test="not(*)">
<xsl:value-of select="concat('##',text())"></xsl:value-of>
</xsl:if>
</xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
<xsl:template match="#*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath" select="concat($previous, '/#', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']','##',.)"></xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
</xsl:stylesheet>

multiple concatenation the alphabet if it is not available in the variable 1 and available in variable 2

I have two variables in the xslt for example named var1 and var2.
Var1 = A,B,C,D,F G,H Var2 = L,M,F,C,D,K
I want to check that if any alphabet is not present in Var1 and present in Var2 ,append this alphabet after the Var1.
Means eventually i want below value stored in to var1
A,B,C,D,F,G,H,L,M,K
I am thinking something like below which is not working -
<xsl:variable name="Var1">
<xsl:if test="not(contains($va1,'L'))">
<xsl:value-of select="concat($va1, 'L')"/>
</xsl:if>
<xsl:if test="not(contains($va1,'M'))">
<xsl:value-of select="concat($va1, 'M')"/>
</xsl:if>
</xsl:variable>
NOTE - I am XSLT beginner and above code is just something in my head only and not tried so do not go with down vote please.
It's possible to some extent, but , are not preserved for $var2:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="var1">
<xsl:text>A,B,C,D,F,G,H</xsl:text>
</xsl:variable>
<xsl:variable name="var2">
<xsl:text>L,M,F,C,D,K</xsl:text>
</xsl:variable>
<xsl:variable name="merged" select="concat(concat($var1, ','), translate($var2, $var1, ''))"/>
<xsl:template match="#* | node()">
<xsl:apply-templates select="/"/>
</xsl:template>
<xsl:template match="/">
<xsl:value-of select="$merged"/>
</xsl:template>
</xsl:stylesheet>
Output:
A,B,C,D,F,G,H,LMK
Note that with XSLT 1.0, it's only possible for single characters like in your example (translate does not replace substrings!).
If you don't need the merged characters in a variable, you could do this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="sep" select="','"/>
<xsl:variable name="var1">
<xsl:text>A,B,C,D,F,G,H</xsl:text>
</xsl:variable>
<xsl:variable name="var2">
<xsl:text>L,M,F,C,D,K</xsl:text>
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:apply-templates select="/"/>
</xsl:template>
<xsl:template match="/">
<xsl:value-of select="$var1"/>
<xsl:value-of select="$sep"/>
<xsl:call-template name="not-contained">
<xsl:with-param name="string1" select="$var1"/>
<xsl:with-param name="string2" select="$var2"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="not-contained">
<xsl:param name="string1"></xsl:param>
<xsl:param name="string2"></xsl:param>
<xsl:param name="sep" select="$sep"/>
<xsl:param name="match"/>
<xsl:value-of select="$match"/>
<xsl:choose>
<xsl:when test="contains($string2, $sep)">
<xsl:variable name="before" select="substring-before($string2, $sep)"/>
<xsl:variable name="no-match" select="not(contains($string1, $before))"/>
<xsl:if test="$no-match">
<xsl:value-of select="$before"/>
</xsl:if>
<xsl:call-template name="not-contained">
<xsl:with-param name="string1" select="$string1"/>
<xsl:with-param name="string2" select="substring-after($string2, $sep)"/>
<xsl:with-param name="match">
<xsl:if test="$no-match">
<xsl:value-of select="$sep"/>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:if test="not(contains($string1, $string2))">
<xsl:value-of select="$string2"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
A,B,C,D,F,G,H,L,M,K
If every letter was a text node in a source document, <xsl:key> could be used to group them together and thus eliminate duplicates.

Create a recursive string function for adding a sequence

I've a challenging problem and so far I wasn't able to solve.
Within my xlst I have variable which contains a string.
I need to add the following sequence [eol] to this string.
On a fix position namely every 65 characters
I thought to use a function or template to recursive add this charackter.
The reason is that the string length can variate in length.
<xsl:function name="funct:insert-eol" as="xs:string" >
<xsl:param name="originalString" as="xs:string?"/>
<xsl:variable name="length">
<xsl:value-of select="string-length($originalString)"/>
</xsl:variable>
<xsl:variable name="start" as="xs:integer">
<xsl:value-of select="1"/>
</xsl:variable>
<xsl:variable name="eol" as="xs:integer">
<xsl:value-of select="65"/>
</xsl:variable>
<xsl:variable name="newLines">
<xsl:value-of select="$length idiv number('65')"/>
</xsl:variable>
<xsl:for-each select="1 to $newLines">
<xsl:value-of select="substring($originalString, $start, $eol)" />
</xsl:for-each>
</xsl:function>
The more I write code the more variables I need to introduce. This is still my lack on understanding.
For example we want every 5 chars an [eol]
aaaaaaabbbbbbccccccccc
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
Hope someone has a starting point for me..
Regards Dirk
Rather straight-forward and short -- no recursion is necessary (and can even be specified as a single XPath expression):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pLLength" select="5"/>
<xsl:template match="/*">
<xsl:variable name="vText" select="string()"/>
<xsl:for-each select="1 to string-length($vText) idiv $pLLength +1">
<xsl:value-of select="substring($vText, $pLLength*(position()-1)+1, $pLLength)"/>
<xsl:if test=
"not(position() eq last()
or position() eq last() and string-length($vText) mod $pLLength)">[eol]</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t>aaaaaaabbbbbbccccccccc</t>
the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
When this XML document is processed:
<t>aaaaaaabbbbbbcccccccccddd</t>
again the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]ccddd[eol]
You can treat it as a grouping problem, using for-each-group:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:function name="mf:insert-eol" 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:if test="position() gt 1"><xsl:sequence select="'eol'"/></xsl:if>
<xsl:sequence select="codepoints-to-string(current-group())"/>
</xsl:for-each-group>
</xsl:value-of>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:copy>
<xsl:sequence select="mf:insert-eol(., 5)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That stylesheet transforms
<root>
<text>aaaaaaabbbbbbccccccccc</text>
</root>
into
<root>
<text>aaaaaeolaabbbeolbbbcceolccccceolcc</text>
</root>
Try this one:
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="TextToChange" select="'aaaaaaabbbbbbccccccccc'"/>
<xsl:param name="RequiredLength" select="xs:integer(5)"/>
<xsl:template match="/">
<xsl:call-template name="AddText"/>
</xsl:template>
<xsl:template name="AddText">
<xsl:param name="Text" select="$TextToChange"/>
<xsl:param name="TextLength" select="string-length($TextToChange)"/>
<xsl:param name="start" select="xs:integer(1)"/>
<xsl:param name="end" select="$RequiredLength"/>
<xsl:choose>
<xsl:when test="$TextLength gt $RequiredLength">
<xsl:value-of select="substring($Text,$start,$end)"/>
<xsl:text>[eol]</xsl:text>
<xsl:call-template name="AddText">
<xsl:with-param name="Text" select="substring-after($Text, substring($Text,$start,$end))"/>
<xsl:with-param name="TextLength"
select="string-length(substring-after($Text, substring($Text,$start,$end)))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>