How can I limit a string's word count in XSLT 1.0?
How about something like:
<xsl:template match="data"> <!-- your data element or whatever -->
<xsl:call-template name="firstWords">
<xsl:with-param name="value" select="."/>
<xsl:with-param name="count" select="4"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="firstWords">
<xsl:param name="value"/>
<xsl:param name="count"/>
<xsl:if test="number($count) >= 1">
<xsl:value-of select="concat(substring-before($value,' '),' ')"/>
</xsl:if>
<xsl:if test="number($count) > 1">
<xsl:variable name="remaining" select="substring-after($value,' ')"/>
<xsl:if test="string-length($remaining) > 0">
<xsl:call-template name="firstWords">
<xsl:with-param name="value" select="$remaining"/>
<xsl:with-param name="count" select="number($count)-1"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
This is an XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="/"/>
<xsl:with-param name="pDelimiters"
select="',
()-'"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="strTakeWords">
<xsl:with-param name="pN" select="10"/>
<xsl:with-param name="pText" select="/*"/>
<xsl:with-param name="pWords"
select="ext:node-set($vwordNodes)/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="word" priority="10">
<xsl:value-of select="concat(position(), ' ', ., '
')"/>
</xsl:template>
<xsl:template name="strTakeWords">
<xsl:param name="pN" select="10"/>
<xsl:param name="pText"/>
<xsl:param name="pWords"/>
<xsl:param name="pResult"/>
<xsl:choose>
<xsl:when test="not($pN > 0)">
<xsl:value-of select="$pResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vWord" select="$pWords[1]"/>
<xsl:variable name="vprecDelims" select=
"substring-before($pText,$pWords[1])"/>
<xsl:variable name="vnewText" select=
"concat($vprecDelims, $vWord)"/>
<xsl:call-template name="strTakeWords">
<xsl:with-param name="pN" select="$pN -1"/>
<xsl:with-param name="pText" select=
"substring-after($pText, $vnewText)"/>
<xsl:with-param name="pWords" select=
"$pWords[position() > 1]"/>
<xsl:with-param name="pResult" select=
"concat($pResult, $vnewText)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<t>
(CNN) -- Behind closed doors in recent days,
senior White House aides have been saying that
measuring President Obama's first 100 days
is the journalistic equivalent of a Hallmark holiday.
</t>
the wanted result is returned:
(CNN) -- Behind closed doors in recent days,
senior White House
Do note:
The str-split-to-words template from FXSL is used for tokenization.
This template accepts a parameter pDelimiters which is a string consisting of all characters that should be treated as delimiters. Thus, in contrast with other solutions, it is possible to specify every delimiter (and not just a "space") -- in this case 8 of them.
The named template strTakeWords calls itself recursively to accumulate the text before and including every word from the wordlist produced by the tokenization, until the specified number of words has been processed.
Related
(This question is a less simplified version of my problem. The more simplified version which was already answered can be found here. I'm posting this more complicated question due to a comment by michael.hor257k who suggested that there may be an alternative approach that could solve it - possibly using select in a loop, or possibly a completely different approach.)
I'd like to process an XML file, over whose format I have no control, to generate C++ code. I need to process functions defined in XML in several different ways to produce different parts of the code. As part of this I need to select a subset of function parameters that match a complicated criteria and pass this selection to several named templates; the named templates need to be able to access the original document.
This example creates a complex selection of C++ function parameters that do not have constant values (ie the same min and max), where the min and max may be decimal or hexadecimal, using the "GenerateNonFixedParameters" template. The parameters refer to enumerations which are located elsewhere in the document, and these definitions are referenced by the named template call "ListParameterValues".
There are two problems.
The creation of the variable "nonFixedParameters" does not use select. I cannot work out how to use select for such a complicated case (XSL 1.0), but maybe there is a way.
A copy of the nodes does not suffice, as the "ListParameterValues" template as it currently stands needs to operate on an original set of nodes from the document.
Example XSL with the locations of these two problems marked:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. This does not use 'select' therefore it does not work. This is XSL 1.0 so as="node()*" cannot be used. -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<xsl:for-each select="$parameters">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore this does not work. -->
<xsl:for-each select="//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<!-- Here a copy is clearly the wrong approach! -->
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
Simple XML to feed the above:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<dictionary>
<enum name="EnumName">
<value name="firstValue" val="1" />
<value name="secondValue" val="2" />
<value name="thirdValue" val="3" />
<value name="forthValue" val="4" />
<value name="fifthValue" val="5" />
</enum>
</dictionary>
<function name="FunctionOne">
<parameter name="p1" type="enum" enum="EnumName" min="2" max="0x4"/>
<parameter name="p2" type="enum" enum="EnumName" min="0x03" max="3"/>
</function>
</body>
Wanted output. Note that p1 has all names within [min..max] listed, but p2 has none listed because min and max have the same value.
p1[secondValue thirdValue forthValue ] p2[]
I think your stylesheet can be made to work with XSLT 1.0 if you use an extension function like exsl:node-set to convert your result tree fragment into a node-set and if you store the root node of the primary input tree into a global variable or parameter as then you will be able to compare nodes in your primary input document to nodes of the newly constructed, temporary tree.
Based on these suggestions the code would look like
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:variable name="main-root" select="/"/>
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. Using exsl:node-set or similar you can convert that result tree fragment into a node set to process it further -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<!-- <xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"> for MSXML or XslTransform -->
<xsl:for-each select="exsl:node-set($parameters)/parameter">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore using the global variable works. -->
<xsl:for-each select="$main-root//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
The example is online at http://xsltransform.net/94hvTzi/1.
Let me show a different approach that actually selects and processes the original nodes, in their original context - as was discussed in the previous thread. Consider:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/">
<xsl:for-each select="body/function">
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="parameter"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="select-parameters">
<xsl:param name="input-set"/>
<xsl:param name="output-set" select="dummy-node"/>
<xsl:variable name="current-node" select="$input-set[1]" />
<xsl:choose>
<xsl:when test="$current-node">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#max" />
</xsl:call-template>
</xsl:variable>
<!-- recursive call -->
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="$input-set[position() > 1]"/>
<xsl:with-param name="output-set" select="$output-set | $current-node[$min != $max]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- call a template to process the currently selected node-set -->
<xsl:call-template name="process-parameters">
<xsl:with-param name="input-set" select="$output-set"/>
</xsl:call-template>
<!-- call more templates here, if required -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:key name="enum-by-name" match="enum" use="#name" />
<xsl:template name="process-parameters">
<xsl:param name="input-set"/>
<xsl:for-each select="$input-set">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat(#name, '[')"/>
<xsl:for-each select="key('enum-by-name', #enum)/value[#val >= $min and #val <= $max]">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The problem with this approach is that it works exactly as advertised; the nodes selected at the end of the selecting processes are the original, unmodified parameters. As a result, they still carry the mixture of decimal and hexadecimal values, and you must convert these again when processing the selected set.
So it might well be more worthwhile to pre-process the parameters by normalizing the values to a common base, then use the result (converted to a node-set) for the rest of the processing. I wouldn't spend so much effort at selecting those that meet the criteria - because once the values are consistent, the selection becomes trivial. If you like, I will post a demo showing that.
I'm using XSLT to convert XML to certain file format. I'm excluding reversal transactions matching two fields (OrigTxnId and TxnId). The problem is the count I'm doing in the header for the number of transactions still include the Transactions which have been removed.
Herewith input XML exsample:
<XML>
<Record><GroupId>10028</GroupId><Id>1</Id><User>CHRISVI</User><TxnId>264-10028-1-516739-2</TxnId><Date>30-Sep-2014</Date><Time>12:21:24</Time><Account>12440531</Account><Amount>217090</Amount><AllowableMOP>0</AllowableMOP><BankBranchCode>280071</BankBranchCode><ChequeAccNo>62247628681</ChequeAccNo><ChequeNo>000040</ChequeNo></Record>
<Record><GroupId>10028</GroupId><Id>1</Id><User>CHRISVI</User><TxnId>264-10028-1-516743-2</TxnId><Date>30-Sep-2014</Date><Time>12:21:52</Time><Account>10895388</Account><Amount>150000</Amount><AllowableMOP>1</AllowableMOP></Record>
<Record><GroupId>10028</GroupId><Id>1</Id><User>CHRISVI</User><TxnId>264-10028-1-516748-1</TxnId><Date>30-Sep-2014</Date><Time>12:22:26</Time><OrigTxnId>264-10028-1-516743-2</OrigTxnId><Account>10895388</Account><Amount>150000</Amount><AllowableMOP>1</AllowableMOP></Record>
<Record><GroupId>10028</GroupId><Id>1</Id><User>CHRISVI</User><TxnId>264-10028-1-516756-1</TxnId><Date>30-Sep-2014</Date><Time>12:23:01</Time><Account>10895388</Account><Amount>10000</Amount><AllowableMOP>1</AllowableMOP></Record>
<Record><GroupId>10028</GroupId><Id>1</Id><User>CHRISVI</User><TxnId>264-10028-1-516760-2</TxnId><Date>30-Sep-2014</Date><Time>12:23:24</Time><Account>10605762</Account><Amount>15000</Amount><AllowableMOP>1</AllowableMOP></Record>
</XML>
The XSLT code to convert XML:
]]>
<xsl:key name="original" match="/XML/Record" use="TxnId" />
<xsl:key name="copy" match="/XML/Record" use="OrigTxnId" />
<xsl:template match="/">
<?Header Starts?>
<xsl:value-of select="user:IncrementBatchNo('Batchcow','C:\WebRiposte\Agents\Configuration\Configurations.xml')"/>
<?RECORDTYPE?>
<xsl:text>H</xsl:text>
<?FILETYPE?>
<xsl:text>PNP</xsl:text>
<?COMPANYCODE?>
<xsl:text>WHK</xsl:text>
<?COMPANYNAME?>
<xsl:text> Windhoek Municipality</xsl:text>
<?ACTIONDATETIME?>
<xsl:value-of select="user:getdatetime()"/>
<?PAYMENTBATCHNO?>
<xsl:value-of select="format-number(user:GetBatchNo('Batchcow','C:\WebRiposte\Agents\Configuration\Configurations.xml'),'000000')"/>
<?RECORDSIZE?>
<xsl:text>000256</xsl:text>
<?NUMRECORDS?>
<xsl:value-of select="format-number(count(//SessionId),'000000')"/>
<?TESTLIVE?>
<xsl:text>L</xsl:text>
<?FILLER?>
<xsl:call-template name="pad-some-space">
<xsl:with-param name="currentlength" select="1"/>
<xsl:with-param name="newlength" select="177"/>
</xsl:call-template>
<?Line Feed?>
<xsl:text>
</xsl:text>
<?Header Ends ?>
<?Body Starts?>
<xsl:for-each select="XML/Record[not(key('original', OrigTxnId) or key('copy', TxnId))]">
<?Record Type - 1 - Fixed value “D”(etail)?>
<xsl:text>D</xsl:text>
<?PAYMENTBATCHNO?>
<xsl:value-of select="format-number(user:GetBatchNo('Batchcow','C:\WebRiposte\Agents\Configuration\Configurations.xml'),'000000')"/>
<?SeqNo - 6 - Right justified, zero padded?>
<xsl:value-of select="format-number(count(preceding-sibling::Record)+1, '000000')"/>
<?CompanyCode - 3 - Leave Blank, Space padded?>
<xsl:text>WHK</xsl:text>
<?CustAccountNo - 20 - Right justified, zero padded?>
<xsl:value-of select="format-number(Account, '00000000000000000000')"/>
<?Invoice No and Ref no?>
<xsl:text>0000000000000000000000000</xsl:text>
<?Create Group id Variable?>
<xsl:variable name="GroupId" select="GroupId"/>
<?NamPostBranch - 50 - ?>
<xsl:call-template name="reformat-string-length">
<xsl:with-param name="value" select="user:GetPostOfficeName(string($GroupId),'C:\WebRiposte\Agents\Configuration\Configurations.xml')"/>
<xsl:with-param name="str-len" select="50"/>
</xsl:call-template>
<?NamPostReceiptNo - 16 - Group-Node-Sequence No?>
<xsl:call-template name="reformat-string-length">
<xsl:with-param name="value" select="substring(SessionId,5,16)"/>
<xsl:with-param name="str-len" select="16"/>
<xsl:with-param name="alignment" select=" 'right' "/>
</xsl:call-template>
<?MOPCheck?>
<xsl:choose>
<xsl:when test="ChequeNo > 0">
<xsl:text>1</xsl:text>
<?BankBranchCode - 6 - space padded?>
<xsl:call-template name="reformat-string-length">
<xsl:with-param name="value" select="BankBranchCode"/>
<xsl:with-param name="str-len" select="6"/>
</xsl:call-template>
<?ChequeAccountNo -15- Left justified, space padded?>
<xsl:call-template name="reformat-string-length">
<xsl:with-param name="value" select="ChequeAccNo"/>
<xsl:with-param name="str-len" select="15"/>
</xsl:call-template>
<?ChequeNo - 6 - Right justified, zero padded?>
<xsl:value-of select="format-number(ChequeNo, '000000')"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>2</xsl:text>
<?BankBranchCode - 6 - space padded?>
<xsl:text> </xsl:text>
<?ChequeAccountNo -15- Left justified, space padded?>
<xsl:text> </xsl:text>
<?ChequeNo - 6 - Right justified, zero padded?>
<xsl:value-of select="format-number(0, '000000')"/>
</xsl:otherwise>
</xsl:choose>
<?PayAmountCents - 9 - Right justified, zero padded?>
<xsl:value-of select="format-number(Amount, '000000000')"/>
<?PaymentDateTime (YYYYMMDDHHMMSS)?>
<xsl:value-of select="substring(Date,8,4)"/>
<xsl:call-template name="format-month-3letter-to-number">
<xsl:with-param name="month-3letter" select="substring(Date,4,3)"/>
</xsl:call-template>
<xsl:value-of select="substring(Date,1,2)"/>
<xsl:value-of select="substring(Time,1,2)"/>
<xsl:value-of select="substring(Time,4,2)"/>
<xsl:value-of select="substring(Time,7,2)"/>
<?Entry Mode?>
<xsl:text>M</xsl:text>
<?AmountSign - 1- ?>
<xsl:text>D</xsl:text>
<?Filler?>
<xsl:call-template name="pad-some-space">
<xsl:with-param name="currentlength" select="1"/>
<xsl:with-param name="newlength" select="77"/>
</xsl:call-template>
<?Line?>
<xsl:text>
</xsl:text>
</xsl:for-each>
<?Body Ends?>
<?Trailer Starts?>
<?RECORDTYPE?>
<xsl:text>T</xsl:text>
<?COMPANYCODE?>
<xsl:text>WHK</xsl:text>
<?PAYMENTBATCHNO?>
<xsl:value-of select="format-number(user:GetBatchNo('Batchcow','C:\WebRiposte\Agents\Configuration\Configurations.xml'),'000000')"/>
<?TOTALAMOUNTCENTS?>
<xsl:value-of select="format-number(sum(//Amount), '00000000000')"/>
<?AMOUNTSIGN?>
<xsl:text>D</xsl:text>
<?FILLER?>
<xsl:call-template name="pad-some-space">
<xsl:with-param name="currentlength" select="1"/>
<xsl:with-param name="newlength" select="235"/>
</xsl:call-template>
<?Trailer Ends?>
</xsl:template>
<?Support functions ------------------- ?>
<?Date time format?>
<xsl:template name="format-date-time">
<xsl:param name="currdatetime"/>
<xsl:value-of select="concat(substring($currdatetime,1,4),substring($currdatetime,6,2),substring($currdatetime,9,2),substring($currdatetime,12,2),substring($currdatetime,15,2),substring($currdatetime,18,2))"/>
</xsl:template>
<?Convert month from text to number?>
<xsl:template name="format-month-3letter-to-number">
<xsl:param name="month-3letter"/>
<xsl:variable name="MonthName" select="'Jan01Feb02Mar03Apr04May05Jun06Jul07Aug08Sep09Oct10Nov11Dec12'"/>
<xsl:value-of select="substring(concat(substring-after($MonthName,$month-3letter),'00'),1,2)"/>
</xsl:template>
<?Pad space?>
<xsl:template name="pad-some-space">
<xsl:param name="currentlength"/>
<xsl:param name="newlength"/>
<xsl:if test="number($currentlength) < number($newlength)">
<xsl:text> </xsl:text>
<xsl:call-template name="pad-some-space">
<xsl:with-param name="currentlength" select="number($currentlength)+1"/>
<xsl:with-param name="newlength" select="$newlength"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<?Evaluate-string-length?>
<xsl:template name="reformat-string-length">
<xsl:param name="value"/>
<xsl:param name="str-len"/>
<xsl:choose>
<xsl:when test="string-length($value) > number($str-len)">
<xsl:value-of select="substring($value,1,$str-len)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
<xsl:call-template name="pad-some-space">
<xsl:with-param name="currentlength" select="string-length($value)"/>
<xsl:with-param name="newlength" select="number($str-len)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Simplifying problems is always good. Although it involved some guess work, I simplified your problem to this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="original" match="/XML/Record" use="TxnId"/>
<xsl:key name="copy" match="/XML/Record" use="OrigTxnId"/>
<xsl:template match="/">
<xsl:value-of select="format-number(count(//GroupId),'000000')"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="XML/Record[not(key('original', OrigTxnId) or key('copy', TxnId))]">
<xsl:value-of select="format-number(Account, '00000000000000000000')"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
(Note, as mentioned in my comments, I have replaced <xsl:value-of select="format-number(count(//Session),'000000')"/> with <xsl:value-of select="format-number(count(//GroupId),'000000')"/>)
This outputs the following
000005
00000000000012440531
00000000000010895388
00000000000010605762
So, the first count says 5 items, but there are only 3 rows.
Now, in your code, you have this xsl:for-each
<xsl:for-each
select="XML/Record[not(key('original', OrigTxnId) or key('copy', TxnId))]">
So, all you need to do is add the condition in you xsl:for-each to your count statement.
<xsl:value-of
select="format-number(count(XML/Record[not(key('original', OrigTxnId) or key('copy', TxnId))]/GroupId),'000000')"/>
It might be better to make use of a variable to remove the repetitive coding.
As an example, try this XSLT as a basis
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="original" match="/XML/Record" use="TxnId"/>
<xsl:key name="copy" match="/XML/Record" use="OrigTxnId"/>
<xsl:template match="/">
<xsl:variable name="records" select="XML/Record[not(key('original', OrigTxnId) or key('copy', TxnId))]" />
<xsl:value-of select="format-number(count($records/GroupId),'000000')"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="$records">
<xsl:value-of select="format-number(Account, '00000000000000000000')"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This outputs the following:
000003
00000000000012440531
00000000000010895388
00000000000010605762
I've searched far and wide for an example of this, and have so far had no luck in applying a sum template to make my XSTL work.
This is the XML (number of lines varies on each planfeature)
<PlanFeatures>
<PlanFeature name="Line0001">
<CoordGeom>
<Line>
<Start pntRef="7540">5605 8950 1020</Start>
<End pntRef="7541">5605 8951 1019</End>
</Line>
<Line>
<Start pntRef="7541">5605 8951 1019</Start>
<End pntRef="7542">5605 8947 1019</End>
</Line>
<Line>
<Start pntRef="7542">5605 8947 1019</Start>
<End pntRef="7543">5605 8940 1011</End>
</Line>
<Line>
<Start pntRef="7543">5605 8940 1011</Start>
<End pntRef="7544">5605 8931 1020</End>
</Line>
</CoordGeom>
</PlanFeature>
</PlanFeatures>
This is where I'm at with the XSL, which uses a recursive call template to calculate the distance of each line segment.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:landxml="http://www.landxml.org/schema/LandXML-1.2" xmlns:hexagon="http://xml.hexagon.com/schema/HeXML-1.5" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-16" indent="no" omit-xml-declaration="yes"/>
<xsl:variable name="XML" select="/"/>
<xsl:variable name="fileExt" select="'txt'"/>
<xsl:variable name="fileDesc" select="'line distance report'"/>
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:for-each select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature">
<xsl:value-of select="#name"/><xsl:text>::</xsl:text>
<xsl:for-each select="landxml:CoordGeom/landxml:Line">
<xsl:value-of select="landxml:Start/#pntRef"/><xsl:text>-</xsl:text>
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:value-of select="landxml:End/#pntRef"/><xsl:text>: </xsl:text>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:variable name="seg" select= "((($x2 - $x1)*($x2 - $x1))+(($y2 - $y1)*($y2 - $y1))+(($z2 - $z1)*($z2 - $z1)))"/>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$seg"/>
</xsl:call-template>
<xsl:text>, </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="root">
<xsl:param name="X"/>
<xsl:param name="xn" select="0"/>
<xsl:param name="xn_1" select="($X+1) div 2"/>
<xsl:choose>
<xsl:when test="string(number($X)) = 'NaN'">
<xsl:value-of select=" ' ' "/>
</xsl:when>
<xsl:when test="($xn_1 - $xn) * ($xn_1 - $xn) < 0.00000001">
<xsl:value-of select='format-number($xn_1, "#.000")'/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="xn" select="$xn_1"/>
<xsl:with-param name="xn_1" select="($xn_1 + ($X div $xn_1)) div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I need to sum the value of X (distance) from the root call template, to create a value which represents the sum of each line segment. I think I need to use a match template, but so far it hand even got close to working.
Currently exporting LINEID::StartPt-EndPt: dist, StartPt-EndPt: dist, etc. I need the sum of the 'dist' to be shown at the end of each line as well. As below
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.720
but I would like
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.728 -- 28.184
Any help would be appreciated... the examples on this site have helped me so much already, but I just can't seem to get through this roadblock.
Cheers,
Chris
You can accomplish this with some relatively simple recursion and parameter passing. Try replacing your first template with these four templates:
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:apply-templates
select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature" />
</xsl:for-each>
</xsl:template>
<xsl:template match ="landxml:PlanFeature">
<xsl:value-of select="concat(#name, '::')" />
<xsl:apply-templates select="landxml:CoordGeom/landxml:Line[1]" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="landxml:Line">
<xsl:param name="total" select="0" />
<xsl:value-of
select="concat(landxml:Start/#pntRef, '-', landxml:End/#pntRef, ': ')"/>
<xsl:variable name="len">
<xsl:call-template name="root">
<xsl:with-param name="X">
<xsl:call-template name="seg" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$len"/>
<xsl:variable name="next" select="following-sibling::landxml:Line[1]" />
<xsl:variable name="newTot" select="$total + $len" />
<xsl:choose>
<xsl:when test="$next">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$next">
<xsl:with-param name="total" select="$newTot" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' -- ', format-number($newTot, '#.000'))" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="seg">
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:value-of select= "($x2 - $x1)*($x2 - $x1)+
($y2 - $y1)*($y2 - $y1)+
($z2 - $z1)*($z2 - $z1)"/>
</xsl:template>
When run on your sample input (after adjusting it to match the paths in your XSLT), the result is:
Line0001::7540-7541: 1.414, 7541-7542: 4.000, 7542-7543: 10.630, 7543-7544: 12.728 -- 28.772
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
Pleasanton, CA 94588
United States of America</Address>
</Report_Entry>
<Report_Entry>
<Address>1234 Address Line 1
5678 Address Line 2
Pleasanton, CA 94588
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="'
'"/>
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="'
'"/>
<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="'
'"/>
<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="'
'"/>
<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.
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.