Recursive Loop XSLT - xslt

All,
I have the below XSLT
<xsl:template name="loop">
<xsl:param name="count" select="1"/>
<xsl:if test="$count > 0">
<xsl:text> </xsl:text>
<xsl:value-of select="$count"/>
<xsl:call-template name="loop">
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
The way to call it is:
<xsl:call-template name="loop
<xsl:with-param name="count" select="100"/>
</xsl:call-template>
At the moment it displays numbers from 100 to 0 and space between them.
(100 99 98 97.....)
How can I change it to do the opposite ? (1 2 3 4....)
Many Thanks,
M

Use:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="loop">
<xsl:with-param name="count" select="100"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="loop">
<xsl:param name="count" select="1"/>
<xsl:param name="limit" select="$count+1"/>
<xsl:if test="$count > 0">
<xsl:text> </xsl:text>
<xsl:value-of select="$limit - $count"/>
<xsl:call-template name="loop">
<xsl:with-param name="count" select="$count - 1"/>
<xsl:with-param name="limit" select="$limit"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is performed on any XML document (not used), the wanted result: 1 to 100 is produced.
Do note: This solution is tail-recursive and with many XSLT processors will be optimized so that recursion is eliminated. This means you can use it with $count set to millions without stack overflow or slow execution.
A non-tail recursive solution, like the one of #0xA3 crashes with stack-overflow (with Saxon 6.5.4) even with count = 1000

Simply change the order inside the template:
<xsl:template name="loop">
<xsl:param name="count" select="1"/>
<xsl:if test="$count > 0">
<xsl:call-template name="loop">
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
<xsl:value-of select="$count"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>

Try this one.
<xsl:template name="loop">
<xsl:param name="inc"/>
<xsl:param name="str" select="1"/>
<xsl:if test="$str <= $inc">
<xsl:text> </xsl:text>
<xsl:value-of select="$str"/>
<xsl:call-template name="loop">
<xsl:with-param name="inc" select="$inc"/>
<xsl:with-param name="str" select="$str + 1"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:call-template name="loop">
<xsl:with-param name="inc" select="10"/>
</xsl:call-template>

Related

Why is the XSLT count function not working as expected for me?

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

XSLT - Wrapping up of string after delimiter count 5 (e.g. 5 "|")

I am newbie in XSLT. I need to wrap up a long string after certain number of delimiter occurs.
Example of such a string is : -
Jason|Michael|John|James|Rick|Paul|JenYee|Ray|Eliza|Shilpa|Abhishek|Patrick|Brent|Kevin|Jim
I don't want to use template for this due to some constraints.
However if its not possible - i am ok with the template.
The output should be like this:
Line 1: Jason|Michael|John|James|Rick| Line 2: Paul|JenYee|Ray|Eliza|Shilpa| Line 3: Abhishek|Patrick|Brent|Kevin|Jim
Use this recursive templates:
<xsl:template name="beforeSeparators">
<xsl:param name="start"/>
<xsl:param name="rest"/>
<xsl:param name="separator"/>
<xsl:param name="count"/>
<xsl:choose>
<xsl:when test="$count <= 0">
<xsl:value-of select="$start"/>
</xsl:when>
<xsl:when test="contains($rest,$separator)">
<xsl:call-template name="beforeSeparators">
<xsl:with-param name="start" select="concat($start,substring-before($rest,$separator),$separator)"/>
<xsl:with-param name="rest" select="substring-after($rest,$separator)"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($start,$rest)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="wrap">
<xsl:param name="str"/>
<xsl:param name="separator"/>
<xsl:param name="separatorsPerLine"/>
<xsl:variable name="line">
<xsl:call-template name="beforeSeparators">
<xsl:with-param name="start" select="''"/>
<xsl:with-param name="rest" select="$str"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="count" select="$separatorsPerLine"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($line,'
')"/>
<xsl:if test="string-length($line) < string-length($str)">
<xsl:call-template name="wrap">
<xsl:with-param name="str" select="substring($str,string-length($line))"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="separatorsPerLine" select="$separatorsPerLine"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
called like this:
<xsl:call-template name="wrap">
<xsl:with-param name="str" select="'Jason|Michael|John|James|Rick|Paul|JenYee|Ray|Eliza|Shilpa|Abhishek|Patrick|Brent|Kevin|Jim'"/>
<xsl:with-param name="separator" select="'|'"/>
<xsl:with-param name="separatorsPerLine" select="5"/>
</xsl:call-template>
produces:
Jason|Michael|John|James|Rick|
Paul|JenYee|Ray|Eliza|Shilpa|
Abhishek|Patrick|Brent|Kevin|
Here is my complete test XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template name="beforeSeparators">
<xsl:param name="start"/>
<xsl:param name="rest"/>
<xsl:param name="separator"/>
<xsl:param name="count"/>
<xsl:choose>
<xsl:when test="$count <= 0">
<xsl:value-of select="$start"/>
</xsl:when>
<xsl:when test="contains($rest,$separator)">
<xsl:call-template name="beforeSeparators">
<xsl:with-param name="start" select="concat($start,substring-before($rest,$separator),$separator)"/>
<xsl:with-param name="rest" select="substring-after($rest,$separator)"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($start,$rest)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="wrap">
<xsl:param name="str"/>
<xsl:param name="separator"/>
<xsl:param name="separatorsPerLine"/>
<xsl:variable name="line">
<xsl:call-template name="beforeSeparators">
<xsl:with-param name="start" select="''"/>
<xsl:with-param name="rest" select="$str"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="count" select="$separatorsPerLine"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($line,'
')"/>
<xsl:if test="string-length($line) < string-length($str)">
<xsl:call-template name="wrap">
<xsl:with-param name="str" select="substring($str,string-length($line)+1)"/>
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="separatorsPerLine" select="$separatorsPerLine"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="wrap">
<xsl:with-param name="str" select="'Jason|Michael|John|James|Rick|Paul|JenYee|Ray|Eliza|Shilpa|Abhishek|Patrick|Brent|Kevin|'"/>
<xsl:with-param name="separator" select="'|'"/>
<xsl:with-param name="separatorsPerLine" select="5"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
(it just translate a fixed string,, so it can be applied to any XML)
If you can use XSLT 2.0, you could use tokenize().
Example ($input is the string in your question):
<xsl:value-of select="tokenize($input,'\|')[5 >= position()]" separator="|"/>
This will produce: Jason|Michael|John|James|Rick

How to do square root in xslt 1.0

I want to do some calculation within the xslt file and need to do some squre root in the formula. can someone please point out if it is possible and how please?
Thanks a mill
One can use the sqrt template/function of FXSL:
I. In XSLT 1.0:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:import href="sqrt.xsl"/>
<!-- To be applied on any source xml.
This also tests the within() function
-->
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
sqrt(0.25):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="0.25"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(1):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="1"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(2):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="2"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(9):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="9"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(16):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="16"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(25):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="25"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(36):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="36"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(49):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="49"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(64):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="64"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(81):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="81"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(100):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="100"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
sqrt(121):
<xsl:call-template name="sqrt">
<xsl:with-param name="N" select="121"/>
<xsl:with-param name="Eps" select="0.00001"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted correct result is produced:
sqrt(0.25):
0.5000000795866174
sqrt(1):
1.0000000464611474
sqrt(2):
1.4142156862745097
sqrt(9):
3.0000000000393214
sqrt(16):
4.0000001858445895
sqrt(25):
5.000000000016778
sqrt(36):
6.000000002793968
sqrt(49):
7.000000094930961
sqrt(64):
8.000001273385879
sqrt(81):
9.000009415515176
sqrt(100):
10.000000000107445
sqrt(121):
11.000000001323027
II. Using FXSL 2.x for XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/">
<xsl:import href="../f/func-sqrt.xsl"/>
<xsl:import href="../f/func-map.xsl"/>
<xsl:import href="../f/func-standardXpathFunctions.xsl"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of separator="
" select=
"f:map(f:round-half-to-even(f:sqrt(2, 0.000001)),
0 to 13
)
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
1
1.4
1.41
1.414
1.4142
1.41421
1.414214
1.4142136
1.41421356
1.414213562
1.4142135624
1.41421356237
1.414213562375
1.4142135623747
If you are using XSLT 2.0, then problem sorted -- See Dimitre's answer.
If you are using XSLT 1.0, in practice in a production environment, I would still go with Dimitre's answer. Commercially, it is better to use a tested libary than re-invent the wheel.
However I thought your question might be fun to implement a stand-alone solution for. And it was!
Given this bunch of values to find the roots of...
<squares>
<square>0</square>
<square>0.25</square>
<square>1</square>
<square>9</square>
<square>10</square>
</squares>
...when input to this XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="verysmall" select="0.00001" />
<xsl:template match="/">
<roots>
<xsl:apply-templates select="*/square"/>
</roots>
</xsl:template>
<xsl:template match="square">
<root>
<xsl:call-template name="root">
<xsl:with-param name="x" select="." />
</xsl:call-template>
</root>
</xsl:template>
<xsl:template name="root">
<xsl:param name="x"/><!-- Assume x >= 0 -->
<xsl:choose>
<xsl:when test="$x > 1">
<xsl:call-template name="iterate-root">
<xsl:with-param name="x" select="$x" />
<xsl:with-param name="H" select="$x" />
<xsl:with-param name="L" select="0" />
</xsl:call-template>
</xsl:when>
<xsl:when test="($x = 1) or ($x <= 0)">
<xsl:value-of select="$x" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="inv-root">
<xsl:call-template name="iterate-root">
<xsl:with-param name="x" select="1 div $x" />
<xsl:with-param name="H" select="1 div $x" />
<xsl:with-param name="L" select="0" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="1 div $inv-root" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="iterate-root">
<xsl:param name="x"/> <!-- Assume x > 1 -->
<xsl:param name="H"/> <!-- High estimate -->
<xsl:param name="L"/> <!-- Low estimate -->
<xsl:variable name="M" select="($H + $L) div 2" />
<xsl:variable name="g" select="($M * $M - $x) div $x" />
<xsl:choose>
<xsl:when test="(($g < $verysmall) and ((- $g) < $verysmall)) or
(($H - $L) < $verysmall)">
<xsl:value-of select="$M"/>
</xsl:when>
<xsl:when test="$g > 0">
<xsl:call-template name="iterate-root">
<xsl:with-param name="x" select="$x" />
<xsl:with-param name="H" select="$M" />
<xsl:with-param name="L" select="$L" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="iterate-root">
<xsl:with-param name="x" select="$x" />
<xsl:with-param name="H" select="$H" />
<xsl:with-param name="L" select="$M" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
...yields approximate solutions...
<roots>
<root>0</root>
<root>0.5</root>
<root>1</root>
<root>2.999988555908203</root>
<root>3.1622695922851562</root>
</roots>
Notes
The technique uses Divide-And-Conquer to find the root of the equation f(y) = y*y - x, which occurs at the square root of x. The technique is probably only reasonably efficient for XSLT processors which implement optimized tail-end recursion.

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.

How to limit string word count in XSLT 1.0?

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.