Search code examples
xsltxslt-1.0xslt-2.0xsl-foxslt-3.0

How to wirte a part of <fo:table> in a function


I have to create many <fo:table-row> in a <fo:table-body>. I think it's not nice if I write almost 5 lines of code several times (Maybe 50 times) to create the rows.

Like this:

<fo:table-body>
<fo:table-row>
   <fo:table-cell padding-bottom="6pt">
       <fo:value-of select="row1"/>
   </fo:table-cell>
</fo:table-row>
<fo:table-row>
   <fo:table-cell padding-bottom="6pt">
       <fo:value-of select="row2"/>
   </fo:table-cell>
</fo:table-row>
<fo:table-row>
   <fo:table-cell padding-bottom="6pt">
       <fo:value-of select="row3"/>
   </fo:table-cell>
</fo:table-row>
....
</fo:table-body>

I tried to write a function that writes the <fo:table-row> for me. And I have to call the function every time and pass a parameter.

<xsl:function name="fn:createRow">
    <xsl:param name="string1"/>
    
    <fo:table-row>
       <fo:table-cell padding-bottom="6pt">
           <fo:value-of select="$string1"/>
       </fo:table-cell>
    </fo:table-row>
               
  </xsl:function>

And now my XSLT look like this.

<fo:table-body>
    <fo:block>
           <fo:value-of select="fn:createRow('row1')"/>
           <fo:value-of select="fn:createRow('row2')"/>
   </fo:block>
</fo:table-body>

But I get the error:

"fo:block" is not a valid child of "fo:table-body"!

But when I work without <fo:block> I get nothing in the PDF:

<fo:table-body>
       <fo:value-of select="fn:createRow('row1')"/>
       <fo:value-of select="fn:createRow('row2')"/>
</fo:table-body>

Is there any opportunity to do it?

Thanks!


Solution

  • fo:table-cell can contain one or more block-level FOs, including fo:block. (See https://www.w3.org/TR/xsl11/#fo_table-cell)

    You don't show your XML, but if all of the row* elements are contained by one element, then in the template for that element, you could do something like:

    <fo:table>
      <fo:table-body>
        <xsl:for-each select="*">
          <fo:table-row>
            <fo:table-cell padding-bottom="6pt">
              <fo:block>
                <!-- The row* element is the current element here. -->
                <xsl:apply-templates />
              </fo:block>
            </fo:table-cell>
          </fo:table-row>
        </fo:for-each>
      </fo:table-body>
    </fo:table>
    

    Alternatively, you could make a template for all of the row* elements:

    <xsl:template match="*[starts-with(local-name(), 'row')]">
      <fo:table-row>
      ...
    

    (From this distance, it's not clear why the row elements need separate element names.)


    When you know the names of the elements that you want to format, you can do:

    <xsl:apply-templates select="row1, row2, row3" />
    

    and:

    <xsl:template match="row1 | row2 | row3">
      <fo:table-row>
      ...