6
votes

I am using XSL to read in three XML documents which are identical except their attributes have different values (potentially I will be reading many XML files). I want to count the number of times the "outcome" attribute has a value "Passed" or "Failed" for the element whose "testName" attribute = "TestOne". I am achieving this using the following:

File1.xml

<container>
    <build>
        <Tests>
            <Results>
                <Result testName="TestOne" outcome="Passed" ></Result>
                <Result testName="TestTwo"  outcome="Passed" ></Result>
            </Results>
        </Tests>    
    </build>
</container>

File2.xml

<container>
    <build>
        <Tests>
            <Results>
                <Result testName="TestOne" outcome="Passed" ></Result>
                <Result testName="TestTwo"  outcome="Failed" ></Result>
            </Results>
        </Tests>    
    </build>
</container>

File3.xml

<container>
    <build>
        <Tests>
            <Results>
                <Result testName="TestOne" outcome="Failed" ></Result>
                <Result testName="TestTwo"  outcome="Failed" ></Result>
            </Results>
        </Tests>    
    </build>
</container>

Index.xml

<?xml-stylesheet type="text/xsl" href="merge3.xsl"?>
<list>
    <entry name="File1.xml" />
    <entry name="File2.xml" />
    <entry name="File3.xml" />
</list>

Merge2.xsl

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

<xsl:template match="/">
    <xsl:for-each select="/list/entry">
        <xsl:apply-templates select="document(@name)/container/build/*[local-name()='Tests']" />
    </xsl:for-each>
</xsl:template>

<xsl:template match="*[local-name()='Results']">
    <xsl:variable name="name" select="'TestOne'" />
    <xsl:variable name="totalPassed" select="*[local-name()='Result'][@testName = 'TestOne'][@outcome = 'Passed']" />
    <xsl:variable name="totalFailed" select="*[local-name()='Result'][@testName = 'TestOne'][@outcome = 'Failed']" />

    <h2>Totals</h2>
    <table border="1" cellSpacing="0" cellPadding="5" >
        <tr bgcolor="#9acd32">
            <th>Test Name</th>
            <th>Total Passed</th>
            <th>Total Failed</th>
        </tr>
        <tr>
            <td><xsl:value-of select="$name"/></td>
            <td><xsl:value-of select="count($totalPassed)"/></td>
            <td><xsl:value-of select="count($totalFailed)"/></td>
        </tr>
    </table>
</xsl:template>

</xsl:stylesheet>

The result produces 3 Totals tables. My intention is to display one Totals table displaying how many times TestOne has passed and failed in all the XML documents. It seems each XML document is being read/selected and then processed one at a time. I'd like to read in and select all the XML files before processing them.

2

2 Answers

9
votes

You could gather all the Result elements from across the three documents using something like

<xsl:variable name="allResults"
  select="(/ | document('file2.xml') | document('file3.xml'))//Result" />

and then apply predicates to this to count the elements you're interested in, for example

<xsl:value-of select="
  count($allResults[@testName = 'TestOne'][@outcome = 'Failed'])" />

Instead of a fixed set of file names, if you have a main index.xml that lists all the files you want to combine, for example:

<list>
    <entry name="File1.xml" />
    <entry name="File2.xml" />
    <entry name="File3.xml" />
</list>

then you can use this index as the main input to your stylesheet and the allResults variable becomes:

<xsl:variable name="allResults"
  select="document(/list/entry/@name)//Result" />

When you pass a node set to the document function it takes the string value of each node in turn and treats that as the URI of a file to load, returning the resulting set of document root nodes.

Here's a complete example

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

  <xsl:variable name="allResults"
    select="document(/list/entry/@name)//Result" />

  <xsl:template match="/">
    <xsl:variable name="name" select="'TestOne'" />

    <h2>Totals</h2>
    <table border="1" cellSpacing="0" cellPadding="5" >
        <tr bgcolor="#9acd32">
            <th>Test Name</th>
            <th>Total Passed</th>
            <th>Total Failed</th>
        </tr>
        <tr>
            <td><xsl:value-of select="$name"/></td>
            <td><xsl:value-of select="count($allResults[@testName = $name]
                                              [@outcome = 'Passed'])"/></td>
            <td><xsl:value-of select="count($allResults[@testName = $name]
                                              [@outcome = 'Failed'])"/></td>
        </tr>
    </table>
  </xsl:template>

</xsl:stylesheet>
0
votes

This is almost the same as above, but without a variable.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
    <h2>Totals</h2>
    <table border="1" cellSpacing="0" cellPadding="5" >
        <tr bgcolor="#9acd32">
            <th>Test Name</th>
            <th>Total Passed</th>
            <th>Total Failed</th>
        </tr>
        <xsl:for-each select="/list/entry">
          <tr>
            <td><xsl:value-of select="@name"/></td>
            <td><xsl:value-of select="count(document(@name)/container/build/Tests/Results/Result[@outcome eq 'Passed'])"/></td>
            <td><xsl:value-of select="count(document(@name)/container/build/Tests/Results/Result[@outcome eq 'Failed'])"/></td>
          </tr>
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>