1
votes

Could someone please explain why the following does not work.

Here is the xml:

<?xml version="1.0" ?>
<testsuites>
    <testsuite errors="0" failures="0" name="test_ui_orchestration" tests="10" time="90.190"/>
    <testsuite errors="1" failures="0" name="test_ui_tables" tests="13" time="1115.771"/>
    <testsuite errors="0" failures="3" name="test_ui_dashboard" tests="18" time="116.397"/>
</testsuites>

I want to calculate total number of tests, total pass and fail. I have a problem getting total number of failures (failures + errors) and total number of pass (for simplicity: tests - failures). I call the same xml-template for calculating totals passing in different values using "with-param" as follows (section within "Calculate sums" comments):

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/testsuites">
  <html>
  <body>

  <!-- Calculate sums start -->
  <xsl:variable name="allSum">
    <xsl:call-template name="testsSum">
      <xsl:with-param name="numTests" select="testsuite/@tests"/>
    </xsl:call-template>
  </xsl:variable>
    <xsl:variable name="failedSum">
      <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests" select="testsuite/@failures"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="errorSum">
      <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests" select="testsuite/@errors"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="failederrorSum">
      <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests" select="testsuite/@failures + testsuite/@errors"/>
      </xsl:call-template>
    </xsl:variable>
  <!-- Calculate sums ends -->

  <h2>Test Results</h2>
  <table border="1">
    <tr bgcolor="#9acd32">
      <th>Test Area</th>
      <th>Total</th>
      <th>Pass</th>
      <th>Fail</th>
      <th>Error</th>
      <th>Fail and Error</th>
    </tr>

    <xsl:for-each select="testsuite">
          <tr>
              <td><xsl:value-of select="@name"/></td>
              <td><xsl:value-of select="@tests"/></td>
              <td><xsl:value-of select="@tests - (@failures + @errors)"/></td>
              <td><xsl:value-of select="@failures"/></td>
              <td><xsl:value-of select="@errors"/></td>
              <td><xsl:value-of select="@failures + @errors"/></td>
              <td> </td>
          </tr>
    </xsl:for-each>

    <tr>
      <td><b>All Tests</b></td>
      <td><xsl:value-of select="$allSum"/></td>
      <td>foo</td>
        <!--
      <td><xsl:value-of select="$passedSum"/></td>
      -->
      <td><xsl:value-of select="$failedSum"/></td>
      <td><xsl:value-of select="$errorSum"/></td>
      <td><xsl:value-of select="$failederrorSum"/></td>
      <td></td>
    </tr>

  </table>
  </body>
  </html>
</xsl:template>

<xsl:template name="testsSum">
  <xsl:param name="numTests"/>
    <xsl:choose>
      <xsl:when test="$numTests">
        <xsl:variable name="recursive_result">
          <xsl:call-template name="testsSum">
            <xsl:with-param name="numTests" select="$numTests[position() > 1]"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="number($numTests[1]) + $recursive_result"/>
      </xsl:when>
      <xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

This produces the following output for the sums (full output is at the end of this post):

41 3 1 0

while it should be:

41 3 1 4

Each individual sum works fine, the corresponding attribute is found. However, when I attempt to add two attributes and pass that to the sum template, it does not work as expected.

It gets worse. The code for generating total passed tests is:

<xsl:variable name="passedSum">
    <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests"
                select="testsuite/@tests - testsuite/@failures"/>
    </xsl:call-template>
</xsl:variable>

When the above code is enabled, I get the following error:

Failed to evaluate the expression of variable 'numTests'

I've been searching online and understand that the "select" of "with-param" accepts "An XPath expression that defines the value of the parameter" (from w3schools). An XPath expression can contain arithmetic operations (http://www.w3schools.com/xpath/xpath_operators.asp), so why do the above fail?

Here is full output of the xsl transformation:

Test Area               Total   Pass    Fail    Error   Fail and Error
----------------------------------------------------------------------
test_ui_orchestration   10      10      0       0       0
test_ui_tables          13      12      0       1       1   
test_ui_dashboard       18      15      3       0       3
----------------------------------------------------------------------  
All Tests               41      foo     3       1       0

Here is the expected output of the xsl transformation (modified values marked with *):

Test Area               Total   Pass    Fail    Error   Fail and Error
----------------------------------------------------------------------
test_ui_orchestration   10      10      0       0       0
test_ui_tables          13      12      0       1       1   
test_ui_dashboard       18      15      3       0       3
----------------------------------------------------------------------  
All Tests               41      37*     3       1       4*
3

3 Answers

2
votes

As Stormtroopr says, you haven't shown us the part of the XSLT code that sets the context, and the fact that you left this out suggests you haven't understood how important context is in XSLT. If your XPath expressions select anything at all, we can probably assume that the context item is the testsuites element, in which case path expressions such as testsuite/@failures all select multiple values (a node set containing several attribute nodes). That's fine if the template you are calling expects a node-set. But when you use a node-set containing multiple nodes as input to an arithmetic operation, then:

(a) In XSLT 1.0 it uses the first node in the node-set and ignores the others

(b) In XSLT 2.0 you get a type error saying that what you are doing makes no sense.

Since you're getting an error, I assume you are using XSLT 2.0, though it doesn't seem a very helpful error message (which XSLT processor are you using?)

As for fixing the problem, I can't help with that because you haven't explained what you are trying to achieve - I can't reverse-engineer the requirements from incorrect code.

0
votes

Since the question has changed dramatically, I've made a new answer.

Your summing template (in fact most of the template) works fine. All you need to do to get those totals down the bottom, you just need to perform addition/subtraction on the variables you've already created:

<xsl:variable name="failederrorSum" select="$failedSum + $errorSum" />
<xsl:variable name="passedSum" select="$allSum - $failederrorSum" />

This stylesheet applied to the input XML:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/testsuites">
  <html>
  <body>

  <!-- Calculate sums start -->
  <xsl:variable name="allSum">
    <xsl:call-template name="testsSum">
      <xsl:with-param name="numTests" select="testsuite/@tests"/>
    </xsl:call-template>
  </xsl:variable>
    <xsl:variable name="failedSum">
      <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests" select="testsuite/@failures"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="errorSum">
      <xsl:call-template name="testsSum">
        <xsl:with-param name="numTests" select="testsuite/@errors"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="failederrorSum" select="$failedSum + $errorSum" />
    <xsl:variable name="passedSum" select="$allSum - $failederrorSum" />
  <!-- Calculate sums ends -->

  <h2>Test Results</h2>
  <table border="1">
    <tr bgcolor="#9acd32">
      <th>Test Area</th>
      <th>Total</th>
      <th>Pass</th>
      <th>Fail</th>
      <th>Error</th>
      <th>Fail and Error</th>
    </tr>

    <xsl:apply-templates />

    <tr>
      <td><b>All Tests</b></td>
      <td><xsl:value-of select="$allSum"/></td>
      <td><xsl:value-of select="$passedSum"/></td>
      <td><xsl:value-of select="$failedSum"/></td>
      <td><xsl:value-of select="$errorSum"/></td>
      <td><xsl:value-of select="$failederrorSum"/></td>
      <td></td>
    </tr>

  </table>
  </body>
  </html>
</xsl:template>


<xsl:template match="testsuite">
          <tr>
              <td><xsl:value-of select="@name"/></td>
              <td><xsl:value-of select="@tests"/></td>
              <td><xsl:value-of select="@tests - (@failures + @errors)"/></td>
              <td><xsl:value-of select="@failures"/></td>
              <td><xsl:value-of select="@errors"/></td>
              <td><xsl:value-of select="@failures + @errors"/></td>
              <td> </td>
          </tr>
</xsl:template>

<xsl:template name="testsSum">
  <xsl:param name="numTests"/>
    <xsl:choose>
      <xsl:when test="$numTests">
        <xsl:variable name="recursive_result">
          <xsl:call-template name="testsSum">
            <xsl:with-param name="numTests" select="$numTests[position() > 1]"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="number($numTests[1]) + $recursive_result"/>
      </xsl:when>
      <xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Gives this output:

<html>
    <body>
        <h2>Test Results</h2>
        <table border="1">
            <tr bgcolor="#9acd32">
                <th>Test Area</th>
                <th>Total</th>
                <th>Pass</th>
                <th>Fail</th>
                <th>Error</th>
                <th>Fail and Error</th>
            </tr>
            <tr>
                <td>test_ui_orchestration</td>
                <td>10</td>
                <td>10</td>
                <td>0</td>
                <td>0</td>
                <td>0</td>
                <td>
                </td>
            </tr>
            <tr>
                <td>test_ui_tables</td>
                <td>13</td>
                <td>12</td>
                <td>0</td>
                <td>1</td>
                <td>1</td>
                <td>
                </td>
            </tr>
            <tr>
                <td>test_ui_dashboard</td>
                <td>18</td>
                <td>15</td>
                <td>3</td>
                <td>0</td>
                <td>3</td>
                <td>
                </td>
            </tr>
            <tr>
                <td>
                    <b>All Tests</b>
                </td>
                <td>41</td>
                <td>37</td>
                <td>3</td>
                <td>1</td>
                <td>4</td>
                <td/>
            </tr>
        </table>
    </body>
</html>

Which looks like this:

Test results screenshot

0
votes

Unfortunately, you've only posted fragments of your XSLT, but I believe the problem is that you are calling testsuite/@failures from the testsuites element.

Rather than returning a number, this is instead returning either a result tree fragment or node-set with the values of the failures attribute for each testsuite element.

As such the traditional numeric operations are failing, as they expect a number and get an RTF or nodeset instead, and attempt to apply the operation giving an unexpected result.

Without knowing what the rest of the code is, I'd suggest the way to fix is to wrap the variable instantiations either in a for-each loop, or even better, in a template that matches on an individual testsuite.