Search code examples
javaxsltxsl-foapache-fop

Apache FOP: Print contents of list from XML in PDF without knowing list size?


I have an XML that is being converted to PDF via Apache FOP, embedded in a Java program, and XSLT. This XML contains several lists of items; these lists are in the XML in a format like this:

<NameOfList>
    <Listitem>
        <ListItemAttributeOne/>
        <ListItemAttributeTwo/>
    </ListItem>
    <ListItem>
        <ListItemAttributeOne/>
        <ListItemAttributeTwo/>
    </ListItem>
    <...more ListItems>
</NameOfList>

I do not know ahead of time how many ListItems there are, and I need to print their information in a PDF file like this:

(1) List Item Attribute One:
List Item Attribute Two:
(2) List Item Attribute One:
List Item Attribute Two:
(...)
(n) List Item Attribute One:
List Item Attribute Two:

I'm usually a Java developer, so I know how to do this with Java: take the list of ListItem objects, store them in an ArrayList of custom type "ListItem," and cycle through the ArrayList and print out their associated attributes, incrementing the label (1, 2, etc.) with each new item.

Is there a similar way to do this using XSLT 2.0? Can you read a list from XML into an array and print it out one item at a time in a dynamically generated list?


Solution

  • This is an XSLT 1.0 (you don't even need the features introduced with XSLT 2.0) that transform your input in an XSL-FO list:

    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 method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="/">
            <fo:root>
                <fo:layout-master-set>
                    <fo:simple-page-master master-name="simple" margin="0.5in">
                        <fo:region-body/>
                      </fo:simple-page-master>
                </fo:layout-master-set>
                <fo:page-sequence master-reference="simple">
                    <fo:flow flow-name="xsl-region-body">
                        <xsl:apply-templates/>
                    </fo:flow>
                </fo:page-sequence>
            </fo:root>
        </xsl:template>
    
        <xsl:template match="NameOfList">
            <fo:list-block provisional-distance-between-starts="2cm" provisional-label-separation="2mm">
                <xsl:apply-templates select="*"/>
            </fo:list-block>
        </xsl:template>
    
        <xsl:template match="ListItem">
            <fo:list-item>
                <fo:list-item-label end-indent="label-end()">
                    <fo:block>(<xsl:value-of select="position()"/>)</fo:block>
                </fo:list-item-label>
                <fo:list-item-body start-indent="body-start()">
                    <xsl:apply-templates/>
                </fo:list-item-body>
            </fo:list-item>
        </xsl:template>
    
        <xsl:template match="ListItemAttributeOne">
            <fo:block>List Item Attribute One: <xsl:value-of select="."/></fo:block>
        </xsl:template>
    
        <xsl:template match="ListItemAttributeTwo">
            <fo:block>List Item Attribute Two: <xsl:value-of select="."/></fo:block>
        </xsl:template>
    </xsl:stylesheet>
    

    You may need to tweak it to your specific needs (page size, margins, fonts, ...) but it should give you a general idea.