Question

I am trying to implement a grouping using xslt on xml elements for attributes that do not really share any common value and i am not sure if i should use the muenchian grouping or not, although i already grouped my elements with this method in my code. I also searched in the forum but without luck since most groupings appear to happen on attributes with common values.

More specifically what i am trying to achieve is to print on a pdf, one line for more than one Records that have specific Ids for Att elements with value "PC" on attribute Ty (Att ty="PC"). All this should happen along with my already existing grouping.

Sample of my xml code:

<Document>
    <RecordDetails>
        <Record>
            <contact id="0001" title="Mr" forename="John" surname="Smith" ST='M'/>
            <AggSet>                
                <Att Ty="Addr" Id="43 Menelaou Street" />
                <Att Ty="PC"   Id="15230" />
                <Att Ty="Num"  Id="2580052635" />             
            </AggSet>
            <Charge Amount="3.000" PT="P" />
        </Record>
        <Record>
            <contact id="0001" title="Mr" forename="John" surname="Smith" ST='M'/>
            <AggSet>                
                <Att Ty="Addr" Id="65 Dankan Street" />
                <Att Ty="PC"   Id="15236" />
                <Att Ty="Num"  Id="2580052635" />             
            </AggSet>
            <Charge Amount="10.000" PT="P" />
        </Record>
        <Record>
            <contact id="0002" title="Dr" forename= "Amy" surname="Jones" ST='Y'/>
            <AggSet>                
                <Att Ty="Addr" Id="28 Karman Street" />
                <Att Ty="PC"   Id="15237" />
                <Att Ty="Num"  Id="2584552635" />             
            </AggSet>
            <Charge Amount="-2.000" PT="P" />
        </Record>
        <Record>    
            ...
        </Record>
    </RecordDetails>
</Document>

So for instance for records 2,3 I would like to print only 1 line due to the fact that their post codes belong to the same area for me since Ty="PC" means post and I am trying to group on a greater area basis.

I am using the follow xsl on Apache FOP:

<xsl:key name="ct" match="Record[Charge/@PT='P']" use="@ST"/>


<xsl:template match ="RecordDetails">
    <xsl:for-each select="Record[generate-id(.)=generate-id(key('ct',@ST)[1])]">
        <xsl:if test="@ST='M' and (./AggSet/Att[@Ty='PC']/@Id='15236' or ./AggSet/Att[@Ty='TZ']/@Id='15237' or ... )  ">
            <fo:table-row>                          
                    <xsl:apply-templates select="."/>                            
            </fo:table-row>
        </xsl:if> 
        <xsl:for-each select="key('ct',@ST)">                       
            <xsl:choose>                                    
                 <xsl:when test="@ST='M' and (./AggSet/Att[@Ty='PC']/@Id='15236' or ./AggSet/Att[@Ty='TZ']/@Id='15237' or ... )  "> 
                 </xsl:when>                     
                 <xsl:otherwise>
                  <fo:table-row>
                    <xsl:apply-templates select="."/>
                  </fo:table-row>
                 </xsl:otherwise>
            </xsl:choose>             
        </xsl:for-each>                             
    </xsl:for-each>
</xsl:template>

<xsl:template match="Record">
    <fo:table-cell>
        <xsl:choose>
            <xsl:when test="@ST='M' and (./AggSet/Att[@Ty='PC']/@Id='15236' or ./AggSet/Att[@Ty='TZ']/@Id='15237' or ... )">
                <fo:block text-align="center">
                    <xsl:text>Greater area</xsl:text>
                </fo:block>
            </xsl:when>
            <xsl:otherwise>
                <fo:block text-align="center">
                    <xsl:value-of select="./AggSet/Att[@Ty='PC']/@Id" />
                </fo:block>
            </xsl:otherwise>
        </xsl:choose>
    </fo:table-cell>
    <fo:table-cell>
        <xsl:choose>
            <xsl:when test="@ST='M' and (./AggSet/Att[@Ty='PC']/@Id='15236' or ./AggSet/Att[@Ty='TZ']/@Id='15237' or ... )">
                <fo:block text-align="center">
                    <xsl:value-of select="sum(//Record[@ST='M' and (./AggSet/Att[@Ty='PC']/@Id='15236' or ./AggSet/Att[@Ty='TZ']/@Id='15237' or ... )]/contact/Charge/@Amount)" />
                </fo:block>
            </xsl:when>
            <xsl:otherwise>
                <fo:block text-align="center">
                    <xsl:value-of select="./Charge/@Amount" />
                </fo:block>
            </xsl:otherwise>
        </xsl:choose>
    </fo:table-cell>
</xsl:template>

Although i have implemented this logic in the past for elements that actually share a common attribute value within my existing grouping, the above code gives me no lines at all for my wanting aggregation and i am wondering if there's something wrong with my OR conditions and for some reason it becomes false.

Am I missing something ? Any help would be much appreciated,

Thanks

Edit: As Tomalak points out in my case what I am trying to do is to implement manual groups, meaning indeed hardcoded conditions inside the code. There is no generic way to calculate these values for me now.

Was it helpful?

Solution

How about this approach:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:my="http://tempuri.org/config"
  exclude-result-prefixes="my"
>
  <my:config>
    <PC_group>
      <item>15236</item>
      <item>15237</item>
    </PC_group>
    <!-- more groups like this... -->
  </my:config>

  <!-- create a reference to our own config -->
  <xsl:variable name="config" select="document('')/*/my:config" />
  <xsl:variable name="PC_group" select="$config/PC_group" />

  <xsl:template match="RecordDetails">
    <grouped_RecordDetails>
      <xsl:apply-templates mode="group" select="Record[Charge/@PT='P']" />
    </grouped_RecordDetails>
  </xsl:template>

  <xsl:template match="Record" mode="group">
    <xsl:variable name="myPC" select="AggSet/Att[@Ty = 'PC']/@Id" />

    <!-- select all the PCs in this group -->
    <xsl:variable name="groupPCs" select="$PC_group[item = $myPC]/item" />

    <!-- identify all other members of this group -->
    <xsl:variable name="groupMembers" select=". | ../Record[
      Charge/@PT='P' and AggSet/Att[@Ty = 'PC']/@Id = $groupPCs
    ]" />

    <!-- do the actual grouping, just like the Muenchian method... -->
    <xsl:if test="generate-id() = generate-id($groupMembers[1])">

      <!--
        we are at the first Record in this group now
        all the other group members are at $groupMembers
        output whatever details you like here
      -->
      <xsl:copy-of select="." />

    </xsl:if>
  </xsl:template>

  <xsl:template match="text()[normalize-space() = '']" />

</xsl:stylesheet>

sample output

<grouped_RecordDetails>
  <Record>
    <contact id="0001" title="Mr" forename="John" surname="Smith" ST="M" />
    <AggSet>
      <Att Ty="Addr" Id="43 Menelaou Street" />
      <Att Ty="PC" Id="15230" />
      <Att Ty="Num" Id="2580052635" />
    </AggSet>
    <Charge Amount="3.000" PT="P" />
  </Record>
  <Record>
    <contact id="0001" title="Mr" forename="John" surname="Smith" ST="M" />
    <AggSet>
      <Att Ty="Addr" Id="65 Dankan Street" />
      <Att Ty="PC" Id="15236" />
      <Att Ty="Num" Id="2580052635" />
    </AggSet>
    <Charge Amount="10.000" PT="P" />
  </Record>
</grouped_RecordDetails>

Edit: Of course the grouping can all be done in one big, messy XPath expression, if you prefer:

<!-- identify all members of this group -->
<xsl:variable name="groupMembers" select="
  . | ../Record[
    Charge/@PT = 'P' 
    and AggSet/Att[@Ty = 'PC']/@Id = $PC_group[
          item = current()/AggSet/Att[@Ty = 'PC']/@Id
        ]/item
  ]
" />

Splitting it up into several variables makes it easier to follow, though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top