[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

[xsl] Output fixed length records using mod operators to group results


Subject: [xsl] Output fixed length records using mod operators to group results
From: Chris <ryder.roundabout@xxxxxxxxx>
Date: Thu, 10 May 2007 14:30:37 -0400

With thanks to replies to my earlier post by David and Abel, the magic
of the mod operator was pointed out.  That does the trick for
selecting the positioning of my output, however, I'm wrestling with
the second part of my problem (which I omitted in the first post for
clarity).

Each record output is a fixed length and belongs in a fixed length
line.  So, when I'm on the last PO or container record and it happens
to not be either a multiple of 2 or 4, i still need to fill fixed
length fields with spaces before closing the record with a line feed.

I can get the PO lines to group correctly (up to 2 per line), but my
containers (up to 4 per line) are showing up other than expected.

My current stylesheet is pretty crude -- I'm using <xsl:choose> to see
my prior and following nodes and a fixed-length formatter template to
output padded results.  I'm also pretty sure I'm not the most
efficient on the for-each or apply-templates approach. I'd welcome any
input on making this approach more elegant.

I seem to get the same results using position() as well as the count
of preceding-sibling or following-sibling nodes in the tests.  I
assume my XPath is incorrect and my containers are stopping because
they are children of the context line_item element. (3,4,2,1)

Here is my actual output (the semi-colon should be in position 51
followed by a line break):
SHIPMENT  2                                       ;
PO        P000001   P000002                       ;
PO        P000001   P000004                       ;
CASE      1         2         3                   ;
CASE      5         6         7         8         ;
CASE      10        11                            ;
CASE      12                                      ;

Each record starts with a 10 position description, and each value is
10 characters long.

My desired output would be this:
SHIPMENT  2                                       ;
PO        P000001   P000002                       ;
PO        P000001   P000004                       ;
CASE      1         2         3         5         ;
CASE      6         7         8         10        ;
CASE      11        12                            ;

Here is the XML source I'm using:
?xml version="1.0" encoding="UTF-8" standalone="no"?>
<asn>
 <header>
   <shipper date="08-May-2007" time="13:27:08" value="2"/>
 </header>
 <detail>
   <line_item part="100-001" po="P000001">
     <quantity shipped="1500" unit_of_measure="EA"/>
     <master_container serial_no="4" type="PLT90">
       <detail_container container_quantity="3"
parts_per_container="500" type="BOX90">
         <container serial_no="1"/>
         <container serial_no="2"/>
         <container serial_no="3"/>
       </detail_container>
     </master_container>
   </line_item>
   <line_item part="100-001" po="P000002">
     <quantity shipped="2000" unit_of_measure="EA"/>
     <master_container serial_no="9" type="PLT90">
       <detail_container container_quantity="4"
parts_per_container="500" type="BOX90">
         <container serial_no="5"/>
         <container serial_no="6"/>
         <container serial_no="7"/>
         <container serial_no="8"/>
       </detail_container>
     </master_container>
   </line_item>
   <line_item part="100-002" po="P000003">
     <quantity shipped="1000" unit_of_measure="EA"/>
     <master_container serial_no="12" type="PLT90">
       <detail_container container_quantity="2"
parts_per_container="500" type="BOX90">
         <container serial_no="10"/>
         <container serial_no="11"/>
       </detail_container>
     </master_container>
   </line_item>
   <line_item part="100-003" po="P000004">
     <quantity shipped="500" unit_of_measure="EA"/>
     <master_container serial_no="13" type="PLT90">
       <detail_container container_quantity="1"
parts_per_container="500" type="BOX90">
         <container serial_no="12"/>
       </detail_container>
     </master_container>
   </line_item>
 </detail>
</asn>

And here is my current stylesheet (long!):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
 <xsl:output method="text" />

 <xsl:template match="asn" >
   <xsl:variable name="lineItemNodes" select="//line_item" />
   <xsl:variable name="containerNodes" select="//container" />
   <xsl:call-template name="shipment" />
   <xsl:call-template name="po">
     <xsl:with-param name="lines" select="$lineItemNodes" />
   </xsl:call-template>
   <xsl:call-template name="cas">
     <xsl:with-param name="boxes" select="$containerNodes" />
   </xsl:call-template>
 </xsl:template>

 <xsl:template name="shipment">
   <xsl:text>SHIPMENT  </xsl:text>
   <xsl:call-template name="fixLenChar">
     <xsl:with-param name="strValue" select="header/shipper/@value"/>
   </xsl:call-template>
   <xsl:text>                              ;&#10;</xsl:text>
 </xsl:template>

 <xsl:template name="po">
   <xsl:param name="lines" />
   <xsl:for-each select="$lines">
     <xsl:choose>
       <xsl:when test="(count(preceding-sibling::line_item) + 1) mod 2 = 0">
         <!-- this is an even numbered node -->
         <xsl:text>PO        </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::line_item/@po" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@po" />
         </xsl:call-template>
         <xsl:text>                    ;&#10;</xsl:text>
       </xsl:when>
       <xsl:when test="(count(preceding-sibling::line_item) + 1) mod 2 = 0">
         <!-- this is an even numbered node -->
         <xsl:text>PO        </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@po" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:text>                    ;&#10;</xsl:text>
       </xsl:when>
       <xsl:otherwise />
     </xsl:choose>
   </xsl:for-each>
 </xsl:template>

 <xsl:template name="cas">
   <xsl:param name="boxes" />
   <xsl:for-each select="$boxes">
     <xsl:choose>
       <xsl:when test="(1 + count(preceding-sibling::container)) mod 4 = 0" >
       <!-- 4th container -->
         <xsl:text>CASE      </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[3]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[2]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[1]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@serial_no" />
         </xsl:call-template>
         <xsl:text>;&#10;</xsl:text>
       </xsl:when>
       <xsl:when test="((1 + count(preceding-sibling::container)) mod
4 = 3) and (count(following-sibling::container) = 0)" >
       <!-- 3rd and last container -->
         <xsl:text>CASE      </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[2]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[1]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:text>;&#10;</xsl:text>
       </xsl:when>
       <xsl:when test="((1 + count(preceding-sibling::container)) mod
4 = 2) and (count(following-sibling::container) = 0)" >
       <!-- 2nd and last container -->
         <xsl:text>CASE      </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue"
select="preceding-sibling::container[1]/@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:text>;&#10;</xsl:text>
       </xsl:when>
       <xsl:when test="((1 + count(preceding-sibling::container)) mod
4 = 1) and (count(following-sibling::container) = 0)" >
       <!-- 1st and last container -->
         <xsl:text>CASE      </xsl:text>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="@serial_no" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:call-template name="fixLenChar">
           <xsl:with-param name="strValue" select="''" />
         </xsl:call-template>
         <xsl:text>;&#10;</xsl:text>
       </xsl:when>
       <xsl:otherwise />
     </xsl:choose>
   </xsl:for-each>
 </xsl:template>

<xsl:template name="fixLenChar">
   <xsl:param name="strLength" select="'10'"/> <!-- set to 10, allow
future pass as param -->
   <xsl:param name="strValue" />
   <xsl:variable name="str10" select="'          '"/>
   <xsl:variable name="curStrLength" select="string-length($strValue)" />
   <xsl:variable name="strPadLength" select="$strLength - $curStrLength" />
   <xsl:variable name="strPadValue" select="substring($str10, 1,
$strPadLength)" />
   <xsl:value-of select="concat($strValue, $strPadValue)" />
 </xsl:template>

</xsl:stylesheet>

Sorry for the long post, but I wanted to get it all out there for
review.  Thanks in advance -- this list (and the archives on biglist)
have helped me tremendously on previous projects!

Chris


Current Thread
Keywords