tags:

views:

47

answers:

2

Hi everyone,

I have the following XML (simplification):

<?xml version="1.0" encoding="utf-8"?>
<TestCases>
  <TestCase>
    <Name>Test1</Name>
    <Result>Failed</Result>
    <Properties>
      <Type>Type1</Type>
    </Properties>
  </TestCase>
  <TestCase>
    <Name>Test1</Name>
    <Result>Failed</Result>
    <Properties>
      <Type>Type2</Type>
    </Properties>
  </TestCase>
  <TestCase>
    <Name>Test1</Name>
    <Result>Passed</Result>
    <Properties>
      <Type>Type1</Type>
    </Properties>
  </TestCase>
</TestCases>

I am interested to create a table that counts the number of passed/failed test cases, according to their type, like so:

Passed (Type1): 1 Failed (Type1): 1 Passed (Other types): 0 Failed (Other types): 1

To do that I am writing the following query:

<xsl:value-of select="count(//TestCase[Result = 'Passed' and count(Properties/TestType='Type1')>0])"/>
<xsl:value-of select="count(//TestCase[Result = 'Failed' and count(Properties/TestType='Type1')>0])"/>
<xsl:value-of select="count(//TestCase[Result = 'Passed' and count(Properties/TestType='Type1')=0])"/>
<xsl:value-of select="count(//TestCase[Result = 'Failed' and count(Properties/TestType='Type1')=0])"/>

As you can see, there is a LOT of code repetition going and would be great if I could save some of it. I understand that in XSL 2.0 I could use user functions for it, but what should I do in XSL 1.0? Do you see any options you can see to optimize the repeating expressions?

P.S Note that is a simplifications of the real and while the expression doesn't seem long here, in the real code it is quite longer so the need is quite real.

Thanks!

+3  A: 

Try:

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

    <xsl:key name="byType" match="Properties/Type" use="."/> 

    <xsl:template match="text()" /> 

    <xsl:template match="Type[count(.|key('byType',.)[1])=1]"> 
        <xsl:value-of select="concat(' Passed (',.,'): ',
                                      count(key('byType',.)[../../Result='Passed']),
                                      ' Failed (',.,'): ',
                                      count(key('byType',.)[../../Result='Failed']))" /> 
    </xsl:template>  

</xsl:stylesheet> 

And you get:

 Passed (Type1): 1 Failed (Type1): 1 Passed (Type2): 0 Failed (Type2): 1

Edit: less verbose.

Alejandro
Wow, you think you know *some* XSLT and then you see things you've never seen before. I'll try it out and get back to you. Thanks.
VitalyB
Intresting. I didn't think I can do such things with expressions.If they are indeed merely a string and open to manipulations as such, can I also declare a variable using <xsl:variable> and use it in expression?
VitalyB
@VitalyB: Yes. You can use variables and parameters in xpath expressions, but you can't in patterns. Example: `<xsl:value-of select="$var-param-name">`
Alejandro
+1  A: 

In addition to the excellent solution bu @Alejandro using the Muenchian method for grouping, here is one that illustrates the use of variables:

This transformation:

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

 <xsl:variable name="vPassed"
      select="/*/*[Result = 'Passed']"/>

 <xsl:variable name="vFailed"
      select="/*/*[Result = 'Failed']"/>

 <xsl:template match="/">
  <xsl:value-of select=
   "concat('Passed (Type1): ',
           count($vPassed[Properties/Type='Type1']),
           ' Failed (Type1): ',
           count($vFailed[Properties/Type='Type1']),
           ' Passed (Other types): ',
           count($vPassed[not(Properties/Type='Type1')]),
           ' Failed (Other types): ',
           count($vFailed[not(Properties/Type='Type1')])
          )
   "/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<TestCases>
  <TestCase>
    <Name>Test1</Name>
    <Result>Failed</Result>
    <Properties>
      <Type>Type1</Type>
    </Properties>
  </TestCase>
  <TestCase>
    <Name>Test1</Name>
    <Result>Failed</Result>
    <Properties>
      <Type>Type2</Type>
    </Properties>
  </TestCase>
  <TestCase>
    <Name>Test1</Name>
    <Result>Passed</Result>
    <Properties>
      <Type>Type1</Type>
    </Properties>
  </TestCase>
</TestCases>

produces the wanted, correct result:

Passed (Type1): 1 Failed (Type1): 1 Passed (Other types): 0 Failed (Other types): 1
Dimitre Novatchev
Interesting! 2 questions:1) Why did you define only Passed/Failed expressions? How about defining also variable for the types? E.g: `<xsl:variable name="vType1" select="/*/*[Properties/Type='Type1']"/>`Then you could write:`count($vPassed[$vType1])`2) I am confused about your syntax:`<xsl:variable name="vPassed" select="/*/*[Result = 'Passed']"/>`Why have you added /*/* in front?
VitalyB
@VitalyB: 1) What you suggest: `count($vPassed[$vType1])` always selects all nodes in `$vPassed` if `$vType1` is non-empty. You need to learn XPath. This is the reason I didn't choose that route. For your question 2) --again learn XPath --especially read about context node, absolute and relative expressions.
Dimitre Novatchev
Thanks! I will.
VitalyB