tags:

views:

839

answers:

2
<statisticItems>
    <statisticItem id="1" frontendGroupId="2336" caseId="50264"  />
    <statisticItem id="2" frontendGroupId="2336" caseId="50264"  />
    <statisticItem id="3" frontendGroupId="2337" caseId="50265"  />
    <statisticItem id="4" frontendGroupId="2337" caseId="50266"  />
    <statisticItem id="5" frontendGroupId="2337" caseId="50266"  />
</statisticItems>

<?xml version="1.0" encoding="utf-8"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="xml" omit-xml-declaration="yes"/>

  <xsl:key name="statistic-by-frontendGroupId" match="statisticItem" use="@frontendGroupId" />

  <xsl:for-each select="statisticItems/statisticItem[count(.|key('statistic-by-frontendGroupId', @frontendGroupId)[1]) = 1]">
       <xsl:value-of select="@frontendGroupId"/>
  </xsl:for-each>

What i have done so fare is to go through all distict frontendGroupIds. What i would like to do now is to do a count of all the distict caseIds for each distict frontendGroupId but i cant seem to make that work. Can someone help me here plz?

A: 

You are attempting to sort via the dreaded 'MUENCHIAN' method - something i've found so confusing that is not worth trying - so i worked out my own method.

It uses two transformations instead of one.

The first sorts the data into the right order based on your grouping requirements -- your sample data is already in the right order so i'll leave it out of this explanation( ask if you want help here)

The second transformation does the grouping simply by comparing one node to the next:

    <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>


    <xsl:template match="statisticItems">
     <groupedItem>
      <xsl:apply-templates select="statisticItem"></xsl:apply-templates>
     </groupedItem>
    </xsl:template>

    <xsl:template match="statisticItem">
     <xsl:choose>

      <xsl:when test="position()=1">
       <xsl:apply-templates select="@frontendGroupId" />
      </xsl:when>

      <xsl:when test="@frontendGroupId!=preceding-sibling::statisticItem[1]/@frontendGroupId">
       <xsl:apply-templates select="@frontendGroupId" />
      </xsl:when>

     </xsl:choose>

     <xsl:apply-templates select="@caseId" />


    </xsl:template>

<xsl:template match="@frontendGroupId">
 <group>
  <xsl:variable name="id" select="." ></xsl:variable>
  <xsl:attribute name="count">
   <xsl:value-of select="count(//statisticItem/@frontendGroupId[.=$id])"/>
  </xsl:attribute>  
  <xsl:value-of select="." />
 </group>
</xsl:template>

    <xsl:template match="@caseId">
     <value>
      <xsl:value-of select="." />
     </value>
    </xsl:template>

</xsl:stylesheet>

With this method you can go several groups deep and still have maintainable code.

Adrian
"something i've found so confusing that is not worth trying" What a strange attitude.
Tomalak
you right my grammar is wrong - I did try - alot, i needed to group serveral levels( 4 or 5 if remember correctly). I did get it working but the key definitions were very confusing, espcially when the nested groupings get deep. But the two pass method i've tried to show above is much easier to explain and read
Adrian
I recommend reading my answer to: http://stackoverflow.com/questions/948218/xslt-3-level-grouping-on-attributes - maybe it helps you :-)
Tomalak
I so Wish SO had been around back then! your XSLT to JS descriptions are very good... but I still feel using my method is much much easier to understand and maintain. OK it may be fractionally slower, I did test my transformation with thousands of elements.
Adrian
Just try to use a key every time you need to group something. I'll come to you. You'll just never fully "get it" if you never try.
Tomalak
+1  A: 

You were close:

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

  <xsl:output method="text" />

  <xsl:key 
    name="statistic-by-frontendGroupId" 
    match="statisticItem" 
    use="@frontendGroupId" 
  />

  <xsl:template match="statisticItems">
    <xsl:for-each select="
      statisticItem[
        count(
          . | key('statistic-by-frontendGroupId', @frontendGroupId)[1]
        ) = 1
      ]
    ">
      <xsl:value-of select="@frontendGroupId"/>
      <xsl:value-of select="' - '"/>
      <!-- simple: the item count is the node count of the key -->
      <xsl:value-of select="
        count(
          key('statistic-by-frontendGroupId', @frontendGroupId)
        )
      "/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

This results in:

2336 - 2
2337 - 3


EDIT - Oh, I see you want the distinct count within the group. This would be:

<!-- the other key from the above solution is still defined -->

<xsl:key 
  name="kStatisticItemByGroupAndCase" 
  match="statisticItem" 
  use="concat(@frontendGroupId, ',', @caseId)"
/>

<xsl:template match="statisticItems">
  <xsl:for-each select="
    statisticItem[
      count(
        . | key('kStatisticItemByGroup', @frontendGroupId)[1]
      ) = 1
    ]
  ">
    <xsl:value-of select="@frontendGroupId"/>
    <xsl:value-of select="' - '"/>
    <xsl:value-of select="
      count(
        key('kStatisticItemByGroup', @frontendGroupId)[
          count(
            . | key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
          ) = 1
        ]
      )
    "/>
    <xsl:value-of select="'&#10;'"/>
  </xsl:for-each>
</xsl:template>

Which looks (admittedly) a bit frightening. It outputs:

2336 - 1
2337 - 2

The core expression:

count(
  key('kStatisticItemByGroup', @frontendGroupId)[
    count(
      . | key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
    ) = 1
  ]
)

boils down to:

Count the nodes from "key('kStatisticItemByGroup', @frontendGroupId)" that fulfill the following condition: They are the first in their respective "kStatisticItemByGroupAndCase" group.

Looking closely, you will find that this is no more complicated than what you already do. :-)


EDIT: One last hint. Personally, I find this a lot more expressive than the above expressions, because it emphasizes node equality a lot more than the "count(.|something) = 1" approach:

count(
  key('kStatisticItemByGroup', @frontendGroupId)[
    generate-id()
    =
    generate-id(
      key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
    )
  ]
)

The result is the same.

Tomalak