How to do square root in xslt 1.0

2020-04-11 21:15发布

问题:

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

回答1:

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="&#xA;" 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


回答2:

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 &lt;= 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 &lt; $verysmall) and ((- $g) &lt; $verysmall)) or
                    (($H - $L) &lt; $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.



标签: xslt