Search code examples
xmlxsltmuenchian-grouping

XSLT: How to convert XML into text file with Muenchian Grouping


I hope to get help here one more time. Here is my sample input XML:

<Report>
  <RecordValues>
    <Record>
        <FieldValue fieldName="firm_name" fieldValue="Firm_1"/>
        <FieldValue fieldName="firm_number" fieldValue="11"/>
        <FieldValue fieldName="prepared_by" fieldValue="PARKER"/>
        <FieldValue fieldName="contact_number" fieldValue="123456789"/>
        <FieldValue fieldName="trade_date" fieldValue="2010-10-17"/>
        <FieldValue fieldName="symbol" fieldValue="ADM"/>
    </Record>
    <Record>
    <FieldValue fieldName="firm_name" fieldValue="Firm_1"/>
        <FieldValue fieldName="firm_number" fieldValue="11"/>
        <FieldValue fieldName="prepared_by" fieldValue="PARKER"/>
        <FieldValue fieldName="contact_number" fieldValue="123456789"/>
        <FieldValue fieldName="trade_date" fieldValue="2010-10-16"/>
        <FieldValue fieldName="symbol" fieldValue="ACW"/>
    </Record>
    <Record>
        <FieldValue fieldName="firm_name" fieldValue="Firm_2"/>
        <FieldValue fieldName="firm_number" fieldValue="12"/>
        <FieldValue fieldName="prepared_by" fieldValue="EDWARDS"/>
        <FieldValue fieldName="contact_number" fieldValue="123456780"/>
        <FieldValue fieldName="trade_date" fieldValue="2010-10-19"/>
        <FieldValue fieldName="symbol" fieldValue="ADS"/>
    </Record>
  </RecordValues>
</Report>

Here is the output I need to get:

A Firm_1 11
B PARKER 123456789
C 2010-10-17 ADM
C 2010-10-16 ACW
T 4
A Firm_2 12
B EDWARDS 123456780
C 2010-10-19 ADS
T 3

As you can see I need to group records by 'firm_name' or 'firm_number'. Each group must have one record of type 'A', one records of type 'B' and multiple records of type 'C'. Record 'T' is a total of each group without record 'T'. The input XML already sorted. I found Muenchian Method which is grouping records, but didn't succeed anything. Obviously I'm doing something wrong. Here is XSLT I wrote:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="text"/>
   <xsl:strip-space elements="*"/>

   <xsl:key name="value-by-firm" match="Report/RecordValues/Record/FieldValue" use="firm_number"/>
  <xsl:template match="Record">
   <xsl:for-each select="FieldValue/@fieldValue[count(. | key('value-by-firm', firm_number))]">
    <xsl:text>A </xsl:text>
    <xsl:value-of select="firm_name"/>
    <xsl:text> </xsl:text>
    <xsl:value-of select="firm_number"/>
    <xsl:text>&#xA;</xsl:text>
    <xsl:text>B </xsl:text>
    <xsl:value-of select="prepared_by"/>
    <xsl:text> </xsl:text>
    <xsl:value-of select="contact_number"/>
    <xsl:text>&#xA;</xsl:text>
    <xsl:for-each select="key('value-by-firm', firm_number)">
      <xsl:text>C </xsl:text>
      <xsl:value-of select="trade_date"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="symbol"/>
      <xsl:text>&#xA;</xsl:text>
   </xsl:for-each>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

Maybe there is another way to do that. Thanks in advance.


Solution

  • This stylesheet:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        <xsl:key name="kRecordByFirmAndContact" match="Record"
                 use="concat(FieldValue[@fieldName='firm_number']
                                /@fieldValue,
                             '+',
                             FieldValue[@fieldName='contact_number']
                                /@fieldValue)"/>
        <xsl:template
             match="Record
                       [count(.|key('kRecordByFirmAndContact',
                                    concat(FieldValue
                                              [@fieldName='firm_number']
                                              /@fieldValue,
                                           '+',
                                           FieldValue
                                              [@fieldName='contact_number']
                                              /@fieldValue))[1])
                        = 1 ]">
            <xsl:variable name="vRecords"
                          select="key('kRecordByFirmAndContact',
                                      concat(FieldValue
                                                [@fieldName='firm_number']
                                                /@fieldValue,
                                             '+',
                                             FieldValue
                                                [@fieldName='contact_number']
                                                /@fieldValue))"/>
            <xsl:value-of select="concat('A ',
                                         *[@fieldName='firm_name']
                                          /@fieldValue,
                                         ' ',
                                         *[@fieldName='firm_number']
                                          /@fieldValue,
                                         '&#xA;',
                                         'B ',
                                         *[@fieldName='prepared_by']
                                          /@fieldValue,
                                         ' ',
                                         *[@fieldName='contact_number']
                                          /@fieldValue,
                                         '&#xA;')"/>
            <xsl:apply-templates select="$vRecords" mode="RecordC"/>
            <xsl:value-of select="concat('T ',count($vRecords) + 2,'&#xA;')"/>
        </xsl:template>
        <xsl:template match="Record" mode="RecordC">
            <xsl:value-of select="concat('C ',
                                         *[@fieldName='trade_date']
                                          /@fieldValue,
                                         ' ',
                                         *[@fieldName='symbol']
                                          /@fieldValue,
                                         '&#xA;')"/>
        </xsl:template>
    </xsl:stylesheet>
    

    Output:

    A Firm_1 11
    B PARKER 123456789
    C 2010-10-17 ADM
    C 2010-10-16 ACW
    T 4
    A Firm_2 12
    B EDWARDS 123456780
    C 2010-10-19 ADS
    T 3
    

    Note: As you can see this is not complex, but your schema makes code so verbose... That looks like M$ XML format for data dumps.