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.
Related
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"/>
(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 am quite new to XSLT,
I have a source XML message, which in its simplified version looks something like this:
<?xml version='1.0' encoding='iso-8859-1'?>
<Message>
<Invalid>
<InvalidBody>
<SynchError>
<ErrorText>The value of %1 should not be %2.</ErrorText>
<ErrorParameter>
<!-- Error Parameter is %1 identifier -->
<ErrorParameterType>value</ErrorParameterType>
<ErrorParameterValue>someField</ErrorParameterValue>
</ErrorParameter>
<ErrorParameter>
<!-- Error Parameter is %2 identifier -->
<ErrorParameterType>value</ErrorParameterType>
<ErrorParameterValue>someValue</ErrorParameterValue>
</ErrorParameter>
</SynchError>
</InvalidBody>
</Invalid>
</Message>
Now, I would like to use XSLT 1.0 to extract the ErrorText string and substitute the parameters %1 and %2 with the corresponding ErrorParameter/ErrorParameterValue values. The number of parameters %1, %2, %3... cannot be known in advance, so the solution should be flexible enough to accommodate a variable number of parameters.
Is there any elegant way to do this?
So, after quite a lot of googling around and a healthy dose of headache, I came up with the following solution, which seems to work like a charm:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:variable name="err-text" select="/Message/Invalid/InvalidBody/SynchError/ErrorText" />
<xsl:variable name="param-count" select="count(/Message/Invalid/InvalidBody/SynchError/ErrorParameter)" />
<xsl:call-template name="replace-params">
<xsl:with-param name="position" select="$param-count"/>
<xsl:with-param name="source-text" select="$err-text" />
</xsl:call-template>
</xsl:template>
<xsl:template name="replace-params">
<xsl:param name="position"/>
<xsl:param name="source-text"/>
<xsl:choose>
<xsl:when test="$position = 0">
<xsl:value-of select="$source-text"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="replace-params">
<xsl:with-param name="position" select="$position - 1"/>
<xsl:with-param name="source-text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$source-text" />
<xsl:with-param name="replace" select="concat('%', $position)" />
<xsl:with-param name="by" select="/Message/Invalid/InvalidBody/SynchError/ErrorParameter[$position]/ErrorParameterValue" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I use the "string-replace-all" template as a substitute for the XSLT 2.0 replace() function, since I cannot exclude multiple occurrences of a single parameter.
The "replace-params" template is applied recursively on the original text, iterating backwards on the index of the set of ErrorParameters.
The way I have tackled similar problems is to create a named template that recurses through the string (text element of) <ErrorText> with each cycle picking out the first n% item, then dereferences the <ErrorParameter> to access the contents of that and store in a result, then snip off the n% item just process and calls itself to grab the next one. When there are no more %n items left, return the result.
Here's an example of that, this template basically counts comma-separated items in a parameter passed in on the first cycle called List, and returns a $Count when it's finished.
<xsl:template name="CountList">
<xsl:param name="List"/>
<xsl:param name="Count" select="0"/>
<xsl:choose>
<xsl:when test="contains($List,',') = false()">
<xsl:value-of select="$Count"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="CountList">
<xsl:with-param name="List">
<xsl:value-of select="substring-after($List,',')"/>
</xsl:with-param>
<xsl:with-param name="Count">
<xsl:value-of select="$Count + 1"/>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
You can try something like this. You can use a replace instead of 'substring-before' and 'after' if this function is supported.
<xsl:template match="SynchError">
<xsl:apply-templates select="ErrorParameter[1]">
<xsl:with-param name="text"><xsl:value-of select="ErrorText"/></xsl:with-param>
<xsl:with-param name="position">1</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="ErrorParameter">
<xsl:param name="text"/>
<xsl:param name="position"/>
<xsl:apply-templates select="following::ErrorParameter">
<xsl:with-param name="position"><xsl:value-of select="number($position)+1"/></xsl:with-param>
<xsl:with-param name="text"><xsl:value-of select="concat(substring-before($text,concat('%',$position)),ErrorParameterValue,substring-after($text,concat('%',$position)))"/></xsl:with-param>
</xsl:apply-templates>
<xsl:if test="not(following::ErrorParameter)">
<xsl:value-of select="concat(substring-before($text,concat('%',$position)),ErrorParameterValue,substring-after($text,concat('%',$position)))"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Here's another way you could look at it:
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="/">
<output>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="Message/Invalid/InvalidBody/SynchError/ErrorText"/>
<xsl:with-param name="parameters" select="Message/Invalid/InvalidBody/SynchError/ErrorParameter"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="parameters"/>
<xsl:param name="flag" select="'%'"/>
<xsl:choose>
<xsl:when test="contains($string, $flag)">
<xsl:variable name="subsequent-char"
select="substring(translate(substring-after($string, $flag), '0123456789', ''), 1, 1)"/>
<xsl:variable name="i"
select="substring-before(substring-after($string, $flag), $subsequent-char)" />
<xsl:value-of select="substring-before($string, $flag)"/>
<xsl:value-of select="$parameters[number($i)]/ErrorParameterValue"/>
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after($string, concat($flag, $i))"/>
<xsl:with-param name="parameters" select="$parameters"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
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.
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>