Search code examples
xmlxsltxsl-foapache-fop

XSL:FO Problem in applying html definition rules for PDF generation


I am generating a pdf from xsl:fo through apache FOP by reading dynamic data from a model class. Following is the xsl:fo structure:

<xsl:template match="ProductData">
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
        <fo:layout-master-set>
            <fo:simple-page-master master-name="simple"
                page-height="20cm" page-width="10.5cm" margin-left="0.2cm"
                margin-right="0.2cm">
                <fo:region-body margin-top="0.5cm" />
            </fo:simple-page-master>
        </fo:layout-master-set>
        <fo:page-sequence master-reference="simple">
            <fo:flow flow-name="xsl-region-body">

                <fo:block font-family="Arial" font-size="7pt" font-weight="normal">

                    <fo:table border-left-style="double" border-right-style="double"
                        border-top-style="double" border-width="1mm" width="18.0cm">
                        <fo:table-body height="2cm">
                            <fo:table-row border-top="solid 0.3mm #E0E0E0">
                                <fo:table-cell>
                                    <fo:block margin-left="" margin-right="1.5cm"
                                        margin-top="0.0cm">
                                        <fo:external-graphic width="3cm" height="2cm">
                                            <xsl:attribute name="src"><xsl:value-of
                                                select="pdfLogo" /></xsl:attribute>
                                        </fo:external-graphic>
                                    </fo:block>
                                </fo:table-cell>
                            </fo:table-row>
                        </fo:table-body>
                    </fo:table>

                    <fo:table>
                        <fo:table-column column-number="1" column-width="3cm" />
                        <fo:table-column column-number="2" column-width="2cm" />
                        <fo:table-column column-number="3" column-width="5cm" />
                        <fo:table-body height="10cm">
                            <xsl:for-each select="./productList/product">
                                <fo:table-row border="solid 0.1mm black">

                                    <fo:table-cell text-align="left">
                                        <fo:block>
                                            <xsl:value-of select="name" />
                                        </fo:block>

                                    </fo:table-cell>
                                    <fo:table-cell text-align="center">
                                        <fo:block>
                                            <xsl:value-of select="productDescription" />
                                        </fo:block>

                                    </fo:table-cell>
                                    <fo:table-cell text-align="left">
                                        <fo:block>
                                            <xsl:value-of select="price" />
                                        </fo:block>

                                    </fo:table-cell>

                                </fo:table-row>
                            </xsl:for-each>
                        </fo:table-body>
                    </fo:table>
                </fo:block>
            </fo:flow>
        </fo:page-sequence>
    </fo:root>
</xsl:template>

Here <xsl:value-of select="productDescription" /> is a text data which has html tags and thus the formatting to be preserved. So I create a set of rules as follows:

<xsl:template match="p">
  <fo:block font-size="12pt" line-height="15pt"
    space-after="12pt">
    <xsl:apply-templates select="*|text()"/>
  </fo:block>
</xsl:template>
    <xsl:template match="br">
        <fo:block>
        </fo:block>
    </xsl:template>
    <xsl:template match="ul">
  <fo:list-block provisional-distance-between-starts="1cm"
    provisional-label-separation="0.5cm">
    <xsl:attribute name="space-after">
      <xsl:choose>
        <xsl:when test="ancestor::ul or ancestor::ol">
          <xsl:text>0pt</xsl:text>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text>12pt</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
    <xsl:attribute name="start-indent">
      <xsl:variable name="ancestors">
        <xsl:choose>
          <xsl:when test="count(ancestor::ol) or count(ancestor::ul)">
            <xsl:value-of select="1 + 
                                  (count(ancestor::ol) + 
                                   count(ancestor::ul)) * 
                                  1.25"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:text>1</xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <xsl:value-of select="concat($ancestors, 'cm')"/>
    </xsl:attribute>
    <xsl:apply-templates select="*"/>
  </fo:list-block>
</xsl:template>

<xsl:template match="ul/li">
  <fo:list-item>
    <fo:list-item-label end-indent="label-end()">
      <fo:block>•</fo:block>
    </fo:list-item-label>
    <fo:list-item-body start-indent="body-start()">
      <fo:block>
        <xsl:apply-templates select="*|text()"/>
      </fo:block>
    </fo:list-item-body>
  </fo:list-item>
</xsl:template>

However, I couldn't figure out how to apply all of them to the productDescription text as these templates of html definitions has to reside outside the main template ?


Solution

  • As is How do I display output variable text in XSL, replace:

    <xsl:value-of select="productDescription" />
    

    with:

    <xsl:apply-templates select="productDescription" />
    

    Since FOP uses Xalan, and as described in the XSLT 1.0 spec (see https://www.w3.org/TR/1999/REC-xslt-19991116#section-Applying-Template-Rules and https://www.w3.org/TR/1999/REC-xslt-19991116#built-in-rule):

    1. When the built-in Xalan gets to the <xsl:apply-templates select="productDescription" />, it will try to find a template rule for productDescription.
    2. In the absence of a template rule for productDescription, it will fall back to the built-in template rule for elements and process the child nodes of the productDescription element.
    3. As Xalan processes the p, etc., element children of productDescription, it will use the templates in your stylesheet in preference to falling back to the built-in rules (because the rules about template precedence are set up so that's what happens).
    4. The built-in template rule for text nodes will copy the text nodes to the result tree.