0
votes

I am trying to transform an XML output from our accounting system into an XML that can be imported into Access. Unfortunately Access only accepts XSL 1.0 for transformation, so I have asked already for help in transforming a XSL 2.0 solution into something that works under XSL 1.0. (Grouping problem). The solution there worked perfectly for what I thought I needed for the Access import.

The Access import works now but I realize the data is in a form that is still not really usable. The data consists of header rows and sub-rows, which are not filled with the header data. But to aggregate meaningfully, the data would need to be filled.

This is how the current XML imported into Access looks like:

DBCDATE     |DBBCPARTY         |DBCVCHTYPE     |DBCVCHNO   |DBCVCHREF    
1-Apr-2011  |                  |Stock Journal  |1          |             
            |ME KN YARN B      |               |           |             
            |ME KN YARN G      |               |           |             
            |ME KN YARN I      |               |           |             
3-Apr-2011  |                  |Stock Journal  |2          |903          
            |TIB Raw Wool      |               |           |             
            |ME KN YARN D      |               |           |             

The fields from the header row would need to be copied down also into the sub-rows: DBCDATE, DBCVCHTYPE, DBCVCHNO, DBCVCHREF, DBCNARR.

I could do this with some VBA programming in Access or even export the data into Excel and apply some formulas, but the data is quite large and we are looking into extracting even more. And it would be less flexible. Also, the question probably goes into the direction of grouping an already grouped XML even more (now it is grouped into importable rows in form of the InventoryDaybook tag structure.

So this was the original XML: Edit: Enhanced XML fragment for better testing

<ENVELOPE>
  <DBCFIXED>  <DBCDATE>1-Apr-2011</DBCDATE>
    <DBCPARTY></DBCPARTY>
  </DBCFIXED>
  <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
  <DBCVCHNO>1</DBCVCHNO>
  <DBCVCHREF></DBCVCHREF>
  <DBCSTNO></DBCSTNO>
  <DBCSERVICETAXNO></DBCSERVICETAXNO>
  <DBCPANNO></DBCPANNO>
  <DBCCSTNO></DBCCSTNO>
  <DBCNARR>Opening balance transfar</DBCNARR>
  <DBCQTY>0.000 Kg</DBCQTY>
  <DBCRATE></DBCRATE>
  <DBCAMOUNT></DBCAMOUNT>
  <DBCADDLCOST></DBCADDLCOST>
  <DBCGROSSAMT></DBCGROSSAMT>
  <DBCLEDAMT></DBCLEDAMT>
  <DBCFIXED>  <DBCDATE></DBCDATE>
    <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
  </DBCFIXED>
  <DBCVCHTYPE></DBCVCHTYPE>
  <DBCVCHNO></DBCVCHNO>
  <DBCVCHREF></DBCVCHREF>
  <DBCSTNO></DBCSTNO>
  <DBCSERVICETAXNO></DBCSERVICETAXNO>
  <DBCPANNO></DBCPANNO>
  <DBCCSTNO></DBCCSTNO>
  <DBCNARR></DBCNARR>
  <DBCQTY>0.150 Kg</DBCQTY>
  <DBCRATE>566.00/Kg</DBCRATE>
  <DBCAMOUNT>-84.90</DBCAMOUNT>
  <DBCADDLCOST></DBCADDLCOST>
  <DBCGROSSAMT></DBCGROSSAMT>
  <DBCLEDAMT></DBCLEDAMT>
  <DBCFIXED>  <DBCDATE>1-Apr-2011</DBCDATE>
    <DBCPARTY></DBCPARTY>
  </DBCFIXED>
  <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
  <DBCVCHNO>2</DBCVCHNO>
  <DBCVCHREF>903</DBCVCHREF>
  <DBCSTNO></DBCSTNO>
  <DBCSERVICETAXNO></DBCSERVICETAXNO>
  <DBCPANNO></DBCPANNO>
  <DBCCSTNO></DBCCSTNO>
  <DBCNARR>Opening balance transfar</DBCNARR>
  <DBCQTY>0.000 Kg</DBCQTY>
  <DBCRATE></DBCRATE>
  <DBCAMOUNT></DBCAMOUNT>
  <DBCADDLCOST></DBCADDLCOST>
  <DBCGROSSAMT></DBCGROSSAMT>
  <DBCLEDAMT></DBCLEDAMT>
  <DBCFIXED>  <DBCDATE></DBCDATE>
    <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
  </DBCFIXED>
  <DBCVCHTYPE></DBCVCHTYPE>
  <DBCVCHNO></DBCVCHNO>
  <DBCVCHREF></DBCVCHREF>
  <DBCSTNO></DBCSTNO>
  <DBCSERVICETAXNO></DBCSERVICETAXNO>
  <DBCPANNO></DBCPANNO>
  <DBCCSTNO></DBCCSTNO>
  <DBCNARR></DBCNARR>
  <DBCQTY>0.260 Kg</DBCQTY>
  <DBCRATE>26.00/Kg</DBCRATE>
  <DBCAMOUNT>-8.70</DBCAMOUNT>
  <DBCADDLCOST></DBCADDLCOST>
  <DBCGROSSAMT></DBCGROSSAMT>
  <DBCLEDAMT></DBCLEDAMT>
  <DBCFIXED>  <DBCDATE></DBCDATE>
    <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
  </DBCFIXED>
  <DBCVCHTYPE></DBCVCHTYPE>
  <DBCVCHNO></DBCVCHNO>
  <DBCVCHREF></DBCVCHREF>
  <DBCSTNO></DBCSTNO>
  <DBCSERVICETAXNO></DBCSERVICETAXNO>
  <DBCPANNO></DBCPANNO>
  <DBCCSTNO></DBCCSTNO>
  <DBCNARR></DBCNARR>
  <DBCQTY>0.360 Kg</DBCQTY>
  <DBCRATE>21.00/Kg</DBCRATE>
  <DBCAMOUNT>-45.80</DBCAMOUNT>
  <DBCADDLCOST></DBCADDLCOST>
  <DBCGROSSAMT></DBCGROSSAMT>
  <DBCLEDAMT></DBCLEDAMT>
</ENVELOPE>

This is the XSL (XSLT 1.0), slightly changed from the solution:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
   <xsl:key name="records" match="ENVELOPE/*[not(self::DBCFIXED)]" use="generate-id(preceding-sibling::DBCFIXED[1])" />
   <xsl:template match="/ENVELOPE">
      <Data>
         <xsl:apply-templates select="DBCFIXED" />
      </Data>
   </xsl:template>

   <xsl:template match="DBCFIXED">
      <InventoryDaybook>
         <xsl:copy-of select="./*" />
         <xsl:apply-templates select="key('records', generate-id())" />
      </InventoryDaybook>
   </xsl:template>

   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Edit: Provided resulting XML (Thank you Martin for pointing out the missing resulting XML)

This is how the XML in the end should look like: The InventoryDaybook wrapping tags have been further distinguished into a 'header' tag containing information on the Date, the Voucher Type, the Voucher Number, the Voucher Reference and the Narration. These fields DBCDATE, DBCVCHTYPE, DBCVCHNO, DBCVCHREF, DBCNARR would need to be copied to the other InventoryDaybook wrapping tags, which represent 'sub' rows. 'Headers' can be identified by DBCDATE - this field is only set for header rows.

<Data>
   <InventoryDaybook name="header">
      <DBCDATE>1-Apr-2011</DBCDATE>
      <DBCPARTY/>
      <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
      <DBCVCHNO>1</DBCVCHNO>
      <DBCVCHREF/>
      <DBCSTNO/>
      <DBCSERVICETAXNO/>
      <DBCPANNO/>
      <DBCCSTNO/>
      <DBCNARR>Opening balance transfar</DBCNARR>
      <DBCQTY>0.000 Kg</DBCQTY>
      <DBCRATE/>
      <DBCAMOUNT/>
      <DBCADDLCOST/>
      <DBCGROSSAMT/>
      <DBCLEDAMT/>
   </InventoryDaybook>
   <InventoryDaybook name="sub">
      <DBCDATE>1-Apr-2011</DBCDATE>
      <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
      <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
      <DBCVCHNO>1</DBCVCHNO>
      <DBCVCHREF/>
      <DBCSTNO/>
      <DBCSERVICETAXNO/>
      <DBCPANNO/>
      <DBCCSTNO/>
      <DBCNARR>Opening balance transfar</DBCNARR>
      <DBCQTY>0.150 Kg</DBCQTY>
      <DBCRATE>566.00/Kg</DBCRATE>
      <DBCAMOUNT>-84.90</DBCAMOUNT>
      <DBCADDLCOST/>
      <DBCGROSSAMT/>
      <DBCLEDAMT/>
   </InventoryDaybook>
   <InventoryDaybook name="header">
      <DBCDATE>1-Apr-2011</DBCDATE>
      <DBCPARTY/>
      <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
      <DBCVCHNO>2</DBCVCHNO>
      <DBCVCHREF>903</DBCVCHREF>
      <DBCSTNO/>
      <DBCSERVICETAXNO/>
      <DBCPANNO/>
      <DBCCSTNO/>
      <DBCNARR>Opening balance transfar</DBCNARR>
      <DBCQTY>0.000 Kg</DBCQTY>
      <DBCRATE/>
      <DBCAMOUNT/>
      <DBCADDLCOST/>
      <DBCGROSSAMT/>
      <DBCLEDAMT/>
   </InventoryDaybook>
   <InventoryDaybook name="sub">
      <DBCDATE>1-Apr-2011</DBCDATE>
      <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
      <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
      <DBCVCHNO>2</DBCVCHNO>
      <DBCVCHREF>903</DBCVCHREF>
      <DBCSTNO/>
      <DBCSERVICETAXNO/>
      <DBCPANNO/>
      <DBCCSTNO/>
      <DBCNARR>Opening balance transfar</DBCNARR>
      <DBCQTY>0.260 Kg</DBCQTY>
      <DBCRATE>26.00/Kg</DBCRATE>
      <DBCAMOUNT>-8.70</DBCAMOUNT>
      <DBCADDLCOST/>
      <DBCGROSSAMT/>
      <DBCLEDAMT/>
   </InventoryDaybook>
   <InventoryDaybook name="sub">
      <DBCDATE>1-Apr-2011</DBCDATE>
      <DBCPARTY>ME KN YARN BL 1</DBCPARTY>
      <DBCVCHTYPE>Stock Journal</DBCVCHTYPE>
      <DBCVCHNO>2</DBCVCHNO>
      <DBCVCHREF>903</DBCVCHREF>
      <DBCSTNO/>
      <DBCSERVICETAXNO/>
      <DBCPANNO/>
      <DBCCSTNO/>
      <DBCNARR>Opening balance transfar</DBCNARR>
      <DBCQTY>0.360 Kg</DBCQTY>
      <DBCRATE>21.00/Kg</DBCRATE>
      <DBCAMOUNT>-45.80</DBCAMOUNT>
      <DBCADDLCOST/>
      <DBCGROSSAMT/>
      <DBCLEDAMT/>
   </InventoryDaybook>
</Data>

Is there a way to do this in XSL? Also I was wondering if this means to kind of transform the returned XML again? Something like in this solution? Solution to double transformation question

I realize that I am asking twice on the forum for the same larger problem but I am really grateful for your help on this.

1
Consider to show us the XML result you want to generate with XSLT from the input sample you posted, that way hopefully your requirement becomes clearer. - Martin Honnen
Thank you Martin for pointing that out, I have included a resulting XML and reworked the description. It's rather complex, I hope this is possible with XSL. - Christian Wild

1 Answers

0
votes

You could certainly use a single-sheet pipeline solution. Here is a non-pipeline solution that uses two grouping keys.

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

   <xsl:key name="header" match="ENVELOPE/*" use="generate-id(
  (preceding-sibling::DBCFIXED[DBCDATE != '']|self::DBCFIXED[DBCDATE != ''])[last()])" />
   <!-- The header key groups all the nodes belonging to a DBCDATE. This is
        a big group containing a number of 'sub' subgroups. -->

   <xsl:key name="sub" match="ENVELOPE/*" use="generate-id(
  (preceding-sibling::DBCFIXED|self::DBCFIXED)[last()])" />
   <!-- The sub key groups all the nodes under a DBCFIXED. This corresponds to
        one row in Access -->

   <xsl:template match="/*">
    <!-- This is our entry template. We gather up all the big header groups,
         and process them one group at a time in 'header' mode. -->  
      <Data>
         <xsl:apply-templates select="DBCFIXED[DBCDATE != '']" mode="header" />
      </Data>
   </xsl:template>

   <xsl:template match="DBCFIXED" mode="header">
    <!-- In this template we process the header group. -->
      <InventoryDaybook name="header">
         <!-- The first sub-group of the header group is to be marked as
              @name="header". Process the header subgroup -->
         <xsl:apply-templates select="key('sub', generate-id())" />
      </InventoryDaybook>
      <!-- Now process the other subgroups. These will be marked @name="sub". --> 
      <xsl:apply-templates select="
        key('header', generate-id()) /
        self::DBCFIXED[DBCDATE = '']"
        mode="sub" />
   </xsl:template>

   <xsl:template match="DBCFIXED" mode="sub">
      <!-- Processing a "sub" subgroup. -->
      <InventoryDaybook name="sub">
         <xsl:apply-templates select="key('sub', generate-id())" />
         <!-- The above selects the members of the subgroup. -->
      </InventoryDaybook>
   </xsl:template>

  <xsl:template match="ENVELOPE/*
    [not(self::DBCFIXED)]
    [preceding-sibling::DBCFIXED[1][DBCDATE = '']]
    [.='']">
  <!-- When we are processing a normal node in a 'sub' subgroup AND
       it doesn't have a value, pick up the default value from
       the related 'header' subgroup. This rule doesn't work for
       the DBCFIXED node. We need a special template for that. -->
      <xsl:copy>
        <xsl:value-of select="
        key('sub', generate-id(
        preceding-sibling::DBCFIXED[DBCDATE != ''][1]))        
        [local-name()=local-name(current())][1] " />
      </xsl:copy>
   </xsl:template>

   <xsl:template match="DBCFIXED">
      <!-- Flatten out the DBCFIXED node. -->
      <xsl:apply-templates select="@*|node()"/>
   </xsl:template>

   <xsl:template match="DBCDATE[ . = '']">
     <!-- Pick-up the default date from the parent DBCDATE. -->
      <xsl:copy>
        <xsl:value-of select="
        ../preceding-sibling::DBCFIXED/
        DBCDATE[ . != ''][1]" />        
      </xsl:copy>
   </xsl:template>

   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>