Replace multiple characters in a single element using XSLT - xslt

I am in need to find and replace a entities and characters to strings. For example,   should be replaced by ' ' empty space, $ should be replaced by |doll|, % should be replaced by |perc|. I can use XSLT 1.0.
XML document:
<?xml version="1.0"?>
<chapter>
<math>
<mtext>This is $400, 300%to500%.</mtext>
</math>
</chapter>
XSLT 1.0 transformation tried:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="mtext">
<mtext>
<xsl:choose>
<xsl:when test="contains(.,' ') or contains(.,'$') or contains(.,'%')">
<xsl:if test="contains(.,' ')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="."/><xsl:with-param name="from"> </xsl:with-param><xsl:with-param name="to" select="' '"/></xsl:call-template><xsl:variable name="mtext_text" select="."/></xsl:if>
<xsl:variable name="mtext_text" select="."/>
<xsl:if test="contains(.,'$')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="mtext_text"/><xsl:with-param name="from">$</xsl:with-param><xsl:with-param name="to" select="'|doll|'"/></xsl:call-template><xsl:variable name="mtext_text" select="."/></xsl:if>
<xsl:variable name="mtext_text" select="."/>
<xsl:if test="contains(.,'%')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="mtext_text"/><xsl:with-param name="from">%</xsl:with-param><xsl:with-param name="to" select="'|perc|'"/></xsl:call-template></xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</mtext>
</xsl:template>
<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text, $from)">
<xsl:variable name="before" select="substring-before($text, $from)"/>
<xsl:variable name="after" select="substring-after($text, $from)"/>
<xsl:variable name="prefix" select="concat($before, $to)"/>
<xsl:copy-of select="$before"/>
<xsl:value-of select="$to" disable-output-escaping="yes"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$after"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Required output:
<?xml version='1.0' ?>
<mtext>This is |doll|400, 300|perc|to500|perc|.</mtext>

This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" extension-element-prefixes="xsl">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:reps>
<rep>
<old> </old>
<new xml:space="preserve"> </new>
</rep>
<rep>
<old>$</old>
<new>|doll|</new>
</rep>
<rep>
<old>%</old>
<new>|perc|</new>
</rep>
</my:reps>
<xsl:variable name="vReps" select="document('')/*/my:reps/*"/>
<xsl:template match="mtext">
<xsl:copy>
<xsl:call-template name="multiReplace"/>
</xsl:copy>
</xsl:template>
<xsl:template name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pRep" select="$vReps[1]"/>
<xsl:choose>
<xsl:when test="not($pRep)"><xsl:value-of select="$pText"/></xsl:when>
<xsl:otherwise>
<xsl:variable name="vReplaced">
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="$pText"/>
<xsl:with-param name="pOld" select="$pRep/old"/>
<xsl:with-param name="pNew" select="$pRep/new"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select="$vReplaced"/>
<xsl:with-param name="pRep" select="$pRep/following-sibling::*[1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="pText"/>
<xsl:param name="pOld"/>
<xsl:param name="pNew"/>
<xsl:if test="$pText">
<xsl:value-of select="substring-before(concat($pText,$pOld), $pOld)"/>
<xsl:if test="contains($pText, $pOld)">
<xsl:value-of select="$pNew"/>
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="substring-after($pText, $pOld)"/>
<xsl:with-param name="pOld" select="$pOld"/>
<xsl:with-param name="pNew" select="$pNew"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<chapter>
<math>
<mtext>This is $400, 300%to500%.</mtext>
</math>
</chapter>
produces the wanted, correct result:
<mtext>This is |doll|400, 300|perc|to500|perc|.</mtext>

Related

XSLT 2.0 Type checking: I get error "Unknown type xsl:string"

I am working on a complex XSLT transformation. I am using XSLT 2.0. This page, https://www.ibm.com/developerworks/library/x-schemaawarexslt/index.html, explains that you can add type checking to your templates. This does not work with me.
Here is my example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xsl:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
My XSLT processor gives the following error:
line [57] column [43]: Unknown type xsl:string
This line reads:
<xsl:param name="in" as="xsl:string"/>
What is going wrong?
You try to get the type "string" from the wrong namespace. The type "string" lives in the XML Schema namespace which differs from the XSLT namespace. Here is the correct version of the above XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xs:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
A namespace prefix "xs" has been introduced for the XML Schema namespace. The definition of the parameter type then references this prefix:
<xsl:param name="in" as="xs:string"/>

Split comma separated string into multiple values using xslt

I need to split two tags with comma separated string into a list of parent-child tags as shown below.
For example, the input will be :-
<UserID>162,163</UserID>
<UserName>Stacy,Stephen</UserName>
Expected output :-
Expected Output
Please help to achieve this result using xslt code
I tried the following format which I got from another query, but its generating a nested pattern instead of the list :-
<xsl:template name="tokenize">
<xsl:param name="textID" select="."/>
<xsl:param name="textName" select="."/>
<xsl:param name="separator" select="','"/>
<User>
<xsl:choose>
<xsl:when test="not(contains($textID, $separator))">
<ID>
<xsl:value-of select="normalize-space($textID)"/>
</ID>
</xsl:when>
<xsl:otherwise>
<ID>
<xsl:value-of select="normalize-space(substring-before($textID, $separator))"/>
</ID>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(contains($textName, $separator))">
<Name>
<xsl:value-of select="normalize-space($textName)"/>
</Name>
</xsl:when>
<xsl:otherwise>
<Name>
<xsl:value-of select="normalize-space(substring-before($textName, $separator))"/>
</Name>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</User>
</xsl:template>
There are few assumptions before simplifying the tokenize template shared in the XSLT code.
The count of comma separated values in <UserID> and <UserName> is always equal.
There is a 1-1 correspondence on the indexes of the values i.e. 162 <-> Stacy and 163 <-> Stephen.
XSLT version is 1.0
A parent node <UserList> has been added as a root node for the shared input XML.
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="UserList">
<xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(UserID)" />
<xsl:with-param name="textName" select="normalize-space(UserName)" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="textID" />
<xsl:param name="textName" />
<xsl:param name="separator" select="','" />
<xsl:choose>
<xsl:when test="not(contains($textID, $separator) and contains($textName, $separator)) ">
<User>
<ID><xsl:value-of select="$textID" /></ID>
<Name><xsl:value-of select="$textName" /></Name>
</User>
</xsl:when>
<xsl:otherwise>
<User>
<ID><xsl:value-of select="substring-before($textID, $separator)" /></ID>
<Name><xsl:value-of select="substring-before($textName, $separator)" /></Name>
</User>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(substring-after($textID, $separator))" />
<xsl:with-param name="textName" select="normalize-space(substring-after($textName, $separator))" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output
<UserList>
<User>
<ID>162</ID>
<Name>Stacy</Name>
</User>
<User>
<ID>163</ID>
<Name>Stephen</Name>
</User>
</UserList>

XSLT Transform to XML

I have some xml in the following format:
<top>
<topValue Value="1#1#5" />
<topValue Value="2#2#10" />
<topValue Value="1#1#3" />
<topValue Value="2#2#30" />
</top>
and output should look like that:
<boo>
<booEnrty>
<v>5</v>
<v>10</v>
</booEnrty>
<booEnrty>
<v>3</v>
<v>30</v>
</booEnrty>
</boo>
my XSLT to transform
<boo>
<xsl:for-each select="top/topValue">
<xsl:if test="position() mod 2 = 0">
<booEnrty>
<v><xsl:value-of select="substring-after(substring-after(#Value,'#'),'#')"/></v>
</booEnrty>
</xsl:if>
</xsl:for-each>
</boo>
What should the XSLT document look like to do this transform?
Any ideas?
Thanks
Maybe someone got a better approach to this, but the XSLT below works for your case.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="top">
<boo>
<xsl:apply-templates select="topValue[position() mod 2 = 1]"/>
</boo>
</xsl:template>
<xsl:template match="topValue[position() mod 2 = 1]">
<booEntry>
<v>
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="#Value" />
<xsl:with-param name="delimiter" select="'#'" />
</xsl:call-template>
</v>
<xsl:apply-templates select="following-sibling::*[1]"/>
</booEntry>
</xsl:template>
<xsl:template match="topValue">
<v>
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="#Value" />
<xsl:with-param name="delimiter" select="'#'" />
</xsl:call-template>
</v>
</xsl:template>
<xsl:template name="substring-after-last">
<xsl:param name="string" />
<xsl:param name="delimiter" />
<xsl:choose>
<xsl:when test="contains($string, $delimiter)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$string"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
How about something short and simple?
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:template match="/top">
<boo>
<xsl:for-each select="topValue[position() mod 2 = 1]">
<booEnrty>
<xsl:for-each select=". | following-sibling::topValue[1]">
<v>
<xsl:value-of select="substring-after(substring-after(#Value,'#'),'#')"/>
</v>
</xsl:for-each>
</booEnrty>
</xsl:for-each>
</boo>
</xsl:template>
</xsl:stylesheet>

Parsing a single node for # of occurrences of a value

I have a XML element with a value similar to the following.
<?xml version='1.0' encoding='UTF-8'?>
<Report_Data>
<Report_Entry>
<Address>1234 Address Line 1&#xa;Pleasanton, CA 94588&#xa;United States of America</Address>
</Report_Entry>
<Report_Entry>
<Address>1234 Address Line 1&#xa;5678 Address Line 2&#xa;Pleasanton, CA 94588&#xa;United States of America</Address>
</Report_Entry>
</Report_Data>
I am trying to count the # of occurences of the following value.
<xsl:variable name="String1" select="'&#xa;'"/>
What I am hoping to have in my output, is to create a new variable that is 2 for the first record and 3 for the second record.
Note that I would be running from a For-Each Report_Entry loop.
The template which you are looking is GetNoOfOccurance
<xsl:template name="GetNoOfOccurance">
<xsl:param name="String"/>
<xsl:param name="SubString"/>
<xsl:param name="Counter" select="0"/>
<xsl:variable name="sa" select="substring-after($String, $SubString)"/>
<xsl:choose>
<xsl:when test="$sa != '' or contains($String, $SubString)">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="$sa"/>
<xsl:with-param name="SubString" select="$SubString"/>
<xsl:with-param name="Counter" select="$Counter + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Counter"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Call the template in the mentioned way:-
<xsl:variable name="searchStr" select="'&#xa;'"/>
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="text()"/>
<xsl:with-param name="SubString" select="$searchStr"/>
</xsl:call-template>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="searchStr" select="'&#xa;'"/>
<xsl:for-each select="//Address">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="text()"/>
<xsl:with-param name="SubString" select="$searchStr"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="GetNoOfOccurance">
<xsl:param name="String"/>
<xsl:param name="SubString"/>
<xsl:param name="Counter" select="0"/>
<xsl:variable name="sa" select="substring-after($String, $SubString)"/>
<xsl:choose>
<xsl:when test="$sa != '' or contains($String, $SubString)">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="$sa"/>
<xsl:with-param name="SubString" select="$SubString"/>
<xsl:with-param name="Counter" select="$Counter + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Counter"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The template GetNoOfOccurance is taken from #Tomalak answer
You forgot to mention to XSLT version.
If you are using XSLT 2.0, the simplest way is to use the tokenize() function and subtract one, like so...
<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="text"/>
<xsl:variable name="String1" select="'&#xa;'"/>
<xsl:template match="/*">
<xsl:for-each select="Report_Entry/Address">There are <xsl:value-of select="count(tokenize(concat(' ',.,' '),$String1)) - 1" /> occurrences.
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...which gives this output for the sample intput...
There are 2 occurrences.
There are 3 occurrences.

How can I use XSLT 1.0 to right justify plain text output?

I'm working with an XML file that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="align-test.xsl"?>
<alignTest>
<item name="Some Name" number="3"></item>
<item name="Another Name" number="10"></item>
<item name="A Third One" number="43"></item>
<item name="A Really Long Name" number="100"></item>
</alignTest>
My goal is to output a plain text file with a nicely formatted table in it. I've figured out how to align and pad text columns and a separater using this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="alignTest/item">
<!-- Scroll right. The next line keeps going, but might not look like it due to formatting -->
<xsl:value-of select="substring(concat(#name, ' '), 1, 22)"/>
<xsl:text> | </xsl:text>
<xsl:value-of select="#number"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Which outputs:
Some Name | 3
Another Name | 10
A Third One | 43
A Really Long Name | 100
I'd also like the numeric values to be right justified. Like so:
Some Name | 3
Another Name | 10
A Third One | 43
A Really Long Name | 100
How can I use XSLT 1.0 to right justify plain-text in that way?
Here is a more readable and more efficient version of your answer:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="#name"/>
<xsl:with-param name="totalLength" select="22"/>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="#number"/>
<xsl:with-param name="totalLength" select="5"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($padBuffer, $stringToPad)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=
"substring($vNewString,
string-length($vNewString) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="stringToPad"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($stringToPad, $padBuffer)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($vNewString,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Another improvement is to dynamically calculate the maximum string-length and not to have to count it manually:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vNamesMaxLen">
<xsl:call-template name="maxLength">
<xsl:with-param name="pNodes"
select="/*/item/#name"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vNumsMaxLen">
<xsl:call-template name="maxLength">
<xsl:with-param name="pNodes"
select="/*/item/#number"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad"
select="#name"/>
<xsl:with-param name="totalLength"
select="$vNamesMaxLen+1"/>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad"
select="#number"/>
<xsl:with-param name="totalLength"
select="$vNumsMaxLen+1"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="maxLength">
<xsl:param name="pNodes" select="/.."/>
<xsl:for-each select="$pNodes">
<xsl:sort select="string-length()" data-type="number"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="string-length()"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($padBuffer, $stringToPad)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=
"substring($vNewString,
string-length($vNewString) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="stringToPad"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($stringToPad, $padBuffer)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($vNewString,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I found a few answers on this page.
The simply way described is to do something like:
<xsl:value-of select="substring(concat(' ', #number), string-length(#number) + 1, 4)"/>
The page also lists a couple of templates for padding both on the left and the right. They call themselves recursively and pad the appropriate amount. (Note that they will also truncate if the requested length is less than that of the string being worked on.) The templates also off a feature of changing the character that is used for padding.
They take more code to implement, but might be easier to maintain. Here's a version of the original stylesheet updated with the two templates to show their usage:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="alignTest/item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="#name"></xsl:with-param>
<xsl:with-param name="totalLength" select="22"></xsl:with-param>
<xsl:with-param name="padCharacter" select="' '"></xsl:with-param>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="#number"/>
<xsl:with-param name="totalLength" select="5"/>
<xsl:with-param name="padCharacteracter" select="' '"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padCharacteracter"/>
<xsl:choose>
<xsl:when test="string-length($stringToPad) < $totalLength">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="concat($padCharacteracter,$stringToPad)"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padCharacteracter" select="$padCharacteracter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($stringToPad,string-length($stringToPad) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="padCharacter"/>
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:choose>
<xsl:when test="string-length($stringToPad) < $totalLength">
<xsl:call-template name="padRightSide">
<xsl:with-param name="padCharacter" select="$padCharacter"/>
<xsl:with-param name="stringToPad" select="concat($stringToPad,$padCharacter)"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($stringToPad,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Of course, you could reduce the size of their footprint a little by hard coding the padding character.