How to dynamically generate row color in XSL:FO without xsl:for-each

2.6k views Asked by At

I have a single data set with varying list of the attributes(about 15), like displayed below.

Sample Data 1:

<cd>
  <name>Tom</name>
  <address>Madison Square</address>
  <phone>000-000-0000</phone>
</cd>

Sample Data 2:

<cd>
  <name>Tom</name>
  <city>New York</city>
  <phone>000-000-0000</phone>
</cd>

Complete list of attributes(subset):

<cd>
   <name>xxx</name>
   <address>xxxx</address>
   <city>xxxx</city>
   <phone>000-000-0000</phone>
</cd>

I want to build a fo:table with alternate row colors for this dynamic table. I have tried using xsl:for-each and position(), but it doesn't work. Is there a way to get previous row properties like, color? Any suggestions.

Added code for reference:

        <fo:table-row>
          <fo:table-cell padding-left="3pt" padding-top="8pt">
              <fo:block> <xsl:value-of select="cd/name"/></fo:block>               
           </fo:table-cell>
         </fo:table-row>
         <xsl:if test="normalize-space(cd/address) !=''">
           <fo:table-row background-color="#EEF0F2" >
             <fo:table-cell padding-left="3pt" padding-top="8pt">
               <fo:block> <xsl:value-of select="cd/address"/></fo:block>                   
             </fo:table-cell>
           </fo:table-row>
         </xsl:if>
         <xsl:if test="normalize-space(cd/city) !=''">
           <fo:table-row background-color="#EEF0F2" >
             <fo:table-cell padding-left="3pt" padding-top="8pt">
              <fo:block> <xsl:value-of select="cd/city"/></fo:block>                  
             </fo:table-cell>
           </fo:table-row>            
         </xsl:if>
2

There are 2 answers

1
Daniel Haley On BEST ANSWER

Your posted code example is not quite enough. We have no idea of context. Something reproducible (something we could run) would be ideal.

Here's a generic answer that should help though.

What you can do is use xsl:apply-templates to process the children of cd that aren't empty. This changes the node set that's being processed and will allow position() to work properly.

Note: You could also do this with xsl:for-each. However, if you're transforming anything but the most simple XML to a very simple XSL-FO you're better off using a push approach rather than a pull approach. This will make your XSLT much easier to maintain and scale.

You can then use mod to see if the position is divisible by 2. If it is, add the background-color.

Example:

XML Input

<cd>
    <name>xxx</name>
    <address>xxxx</address>
    <city>xxx</city>
    <whatevs></whatevs>
    <phone>000-000-0000</phone>
</cd>

XSLT 1.0

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

    <xsl:template match="/">
        <fo:root>
            <fo:layout-master-set>
                <fo:simple-page-master master-name="my-page" page-width="8.5in" page-height="11in">
                    <fo:region-body margin="1in" margin-top="1.5in" margin-bottom="1.5in"/>
                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="my-page">
                <fo:flow flow-name="xsl-region-body"> 
                    <fo:table>
                        <fo:table-body>
                            <xsl:apply-templates select="cd/*[string()]"/>                            
                        </fo:table-body>
                    </fo:table>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>

    <xsl:template match="cd/*">
        <fo:table-row>            
            <xsl:if test="position() mod 2">
                <xsl:attribute name="background-color">
                    <xsl:text>#EEF0F2</xsl:text>
                </xsl:attribute>
            </xsl:if>
            <fo:table-cell padding-left="3pt" padding-top="8pt">
                <fo:block><xsl:value-of select="."/></fo:block>                  
            </fo:table-cell>
        </fo:table-row>
    </xsl:template>

</xsl:stylesheet>

XSL-FO Output

<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
   <fo:layout-master-set>
      <fo:simple-page-master master-name="my-page" page-width="8.5in" page-height="11in">
         <fo:region-body margin="1in" margin-top="1.5in" margin-bottom="1.5in"/>
      </fo:simple-page-master>
   </fo:layout-master-set>
   <fo:page-sequence master-reference="my-page">
      <fo:flow flow-name="xsl-region-body">
         <fo:table>
            <fo:table-body>
               <fo:table-row background-color="#EEF0F2">
                  <fo:table-cell padding-left="3pt" padding-top="8pt">
                     <fo:block>xxx</fo:block>
                  </fo:table-cell>
               </fo:table-row>
               <fo:table-row>
                  <fo:table-cell padding-left="3pt" padding-top="8pt">
                     <fo:block>xxxx</fo:block>
                  </fo:table-cell>
               </fo:table-row>
               <fo:table-row background-color="#EEF0F2">
                  <fo:table-cell padding-left="3pt" padding-top="8pt">
                     <fo:block>xxx</fo:block>
                  </fo:table-cell>
               </fo:table-row>
               <fo:table-row>
                  <fo:table-cell padding-left="3pt" padding-top="8pt">
                     <fo:block>000-000-0000</fo:block>
                  </fo:table-cell>
               </fo:table-row>
            </fo:table-body>
         </fo:table>
      </fo:flow>
   </fo:page-sequence>
</fo:root>

Rendered PDF (used FOP 1.1)

enter image description here

0
Ruprecht von Waldenfels On

Post your code. One strategy in such cases would be to first build the table in a variable, and then copy it to output, adding the color tag while you do. This might be less messy.