How to generate PDF output containing embedded html tags using xsl-fo

With the report capabilities of Imixs Workflow it is possible to generate PDF files from a process instance using the XML and Rest API. A description and examples of how to generate a PDF Template can be found here.

With the latest Version of the Imixs Rest API it is now also possible to embed HTML output into a PDF file generated using xsl-fo. The Report-Plugin from Imixs-Workflow now preserve embedded HTML and XML structures in a workitem property if the name of the property starts with ‘html’ or ‘xml’.

To transform the HTML block (which have to be well formed XHTML) inside xsl-fo for each html tag a fo template can be applied in the xsl template.

This is a short example how to use this template technique:

<?xml version="1.0" encoding="UTF-8"?>
<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 encoding="UTF-8" method="xml" indent="yes" />
    <xsl:strip-space elements="*" />
    <xsl:param name="font-size" select="''" />
    <xsl:param name="font.symbol" select="'Arial Unicode MS'" />
    <xsl:template match="/">
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="A4"
                    page-width="210mm" page-height="297mm">
                    <fo:region-body region-name="xsl-region-body"
                        margin="20mm" />
                </fo:simple-page-master>
                <fo:simple-page-master master-name="A4-landscape"
                    page-width="297mm" page-height="210mm">
                    <fo:region-body region-name="xsl-region-body"
                        margin="20mm" />
                </fo:simple-page-master>
            </fo:layout-master-set>

            <fo:page-sequence master-reference="A4">
                <fo:flow flow-name="xsl-region-body">
                    <fo:block space-after="20mm">
                        <fo:block text-align="end" font-size="16pt" font-weight="bold">
                            <fo:inline>
                                HTML - PDF - Example
                            </fo:inline>
                        </fo:block>
                    </fo:block>
                    <fo:block>
                        <fo:block font-weight="bold">Text Output:</fo:block>
                        <fo:block>
                            <xsl:value-of
                                select="/collection/entity[normalize-space(item[name = 'txtworkflowgroup']/value) = 'Frage']/item[name='txtname']/value" />
                        </fo:block>

                        <!-- format html output -->
                        <fo:block font-weight="bold">HTML Output:</fo:block>
                        <fo:block>
                            <xsl:apply-templates
                                select="/collection/entity[normalize-space(item[name = 'txtworkflowgroup']/value) = 'Frage']/item[name='htmlanswer']/value" />
                        </fo:block>
                    </fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>

    <xsl:template
        match="/collection/entity[normalize-space(item[name = 'txtworkflowgroup']/value) = 'Frage']/item[name='htmlanswer']/value">
        <xsl:apply-templates />
    </xsl:template>
   
    <!-- html format -->
    <xsl:template match="p">
        <fo:block>
            <fo:inline>
                <xsl:text disable-output-escaping="yes">
                <xsl:apply-templates />
                </xsl:text>
            </fo:inline>
        </fo:block>
    </xsl:template>
    <xsl:template match="b|strong">
        <fo:inline font-weight="bold">
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>

    <xsl:template match="table">
        <fo:table table-layout="auto" width="100%">
            <fo:table-body>
                <xsl:apply-templates />
            </fo:table-body>
        </fo:table>
    </xsl:template>
    <xsl:template match="tr">
        <fo:table-row>
            <xsl:apply-templates />
        </fo:table-row>
    </xsl:template>

    <xsl:template match="td">
        <fo:table-cell>
            <fo:block>
                <xsl:apply-templates />
            </fo:block>
        </fo:table-cell>
    </xsl:template>

    <xsl:template match="address">
        <fo:block font-style="italic">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="blockquote">
        <fo:block space-before="1em" space-after="1em" start-indent="3em"
            end-indent="3em">

            <xsl:apply-templates />
        </fo:block>
    </xsl:template>

    <xsl:template match="caption">
        <fo:block keep-with-next="always" text-align="center">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="center">
        <fo:block text-align="center">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="div|multicol|noembed|noframes   |nolayer|noscript">
        <fo:block>
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>

    <xsl:template match="h1">
        <fo:block font-size="180%" font-weight="bold">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="h2">
        <fo:block font-size="160%" font-weight="bold">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="h3">
        <fo:block font-size="140%" font-weight="bold">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="h4">
        <fo:block font-size="120%" font-weight="bold">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>
    <xsl:template match="h5">
        <fo:block font-size="110%" font-weight="bold">
            <xsl:apply-templates />
        </fo:block>
    </xsl:template>

    <xsl:template match="br">
        <fo:block white-space="pre">
            <xsl:text disable-output-escaping="yes">&#10;</xsl:text>
        </fo:block>
    </xsl:template>

    <xsl:template match="ol">
        <fo:list-block provisional-label-separation=".2em"
            provisional-distance-between-starts="{string-length(count(li))*.9+.6}em">
            <xsl:apply-templates />
        </fo:list-block>
    </xsl:template>

    <xsl:template match="ol/li">
        <fo:list-item>
            <fo:list-item-label end-indent="label-end()">
                <fo:block text-align="end">
                    <xsl:variable name="value">
                        <xsl:choose>
                            <xsl:when test="@value">
                                <xsl:value-of select="@value" />
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:number />
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:variable>
                    <xsl:choose>
                        <xsl:when test="@type='I'">
                            <xsl:number format="I" value="$value" />
                        </xsl:when>
                        <xsl:when test="@type='A'">
                            <xsl:number format="A" value="$value" />
                        </xsl:when>
                        <xsl:when test="@type='i'">
                            <xsl:number format="i" value="$value" />
                        </xsl:when>
                        <xsl:when test="@type='a'">
                            <xsl:number format="a" value="$value" />
                        </xsl:when>
                        <xsl:when test="parent::ol/@type='I'">
                            <xsl:number format="I" value="$value" />
                        </xsl:when>
                        <xsl:when test="parent::ol/@type='I'">
                            <xsl:number format="A" value="$value" />
                        </xsl:when>
                        <xsl:when test="parent::ol/@type='I'">
                            <xsl:number format="i" value="$value" />
                        </xsl:when>
                        <xsl:when test="parent::ol/@type='I'">
                            <xsl:number format="a" value="$value" />
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:number format="1" value="$value" />
                        </xsl:otherwise>
                    </xsl:choose>
                    <xsl:text>.</xsl:text>
                </fo:block>
            </fo:list-item-label>
            <fo:list-item-body start-indent="body-start()">
                <fo:block>
                    <xsl:apply-templates />
                </fo:block>
            </fo:list-item-body>
        </fo:list-item>
    </xsl:template>

    <xsl:template match="ul|menu">
        <fo:list-block provisional-label-separation=".2em"
            provisional-distance-between-starts="1.6em">
            <xsl:apply-templates />
        </fo:list-block>
    </xsl:template>
    <xsl:template match="ul/li|menu/li">
        <fo:list-item>
            <fo:list-item-label end-indent="label-end()">
                <fo:block text-align="end">
                    <fo:inline font-family="{$font.symbol}">
                        <xsl:choose>
                            <xsl:when test="@type='square'">
                                <xsl:text disable-output-escaping="yes">&#x25AA;</xsl:text>
                            </xsl:when>
                            <xsl:when test="@type='circle'">
                                <xsl:text disable-output-escaping="yes">&#x25CB;</xsl:text>
                            </xsl:when>
                            <xsl:when test="parent::ul/@type='square'">
                                <xsl:text disable-output-escaping="yes">&#x25AA;</xsl:text>
                            </xsl:when>
                            <xsl:when test="parent::ul/@type='circle'">
                                <xsl:text disable-output-escaping="yes">&#x25CB;</xsl:text>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:text disable-output-escaping="yes">&#x2022;</xsl:text>
                            </xsl:otherwise>
                        </xsl:choose>
                    </fo:inline>
                </fo:block>
            </fo:list-item-label>
            <fo:list-item-body start-indent="body-start()">
                <fo:block>
                    <xsl:apply-templates />
                </fo:block>
            </fo:list-item-body>
        </fo:list-item>
    </xsl:template>

    <xsl:template match="font">
        <fo:inline>
            <xsl:choose>
                <xsl:when test="@size=1">
                    <xsl:attribute name="font-size">xx-small</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=2">
                    <xsl:attribute name="font-size">x-small</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=3">
                    <xsl:attribute name="font-size">small</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=4">
                    <xsl:attribute name="font-size">medium</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=5">
                    <xsl:attribute name="font-size">large</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=6">
                    <xsl:attribute name="font-size">x-large</xsl:attribute>
                </xsl:when>
                <xsl:when test="@size=7">
                    <xsl:attribute name="font-size">xx-large</xsl:attribute>
                </xsl:when>
            </xsl:choose>
            <xsl:if test="@face">
                <xsl:attribute name="font-family"><xsl:value-of select="@face" /></xsl:attribute>
            </xsl:if>
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>

    <xsl:template match="small">
        <fo:inline font-size="smaller">
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>

    <xsl:template match="span">
        <xsl:choose>
            <xsl:when test="self::node()[contains(@style,'underline')]">
                <fo:inline text-decoration="underline">
                    <xsl:apply-templates />
                </fo:inline>
            </xsl:when>
            <xsl:otherwise>
                <fo:inline>
                    <xsl:apply-templates />
                </fo:inline>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="sub">
        <fo:inline baseline-shift="sub">
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>
    <xsl:template match="sup">
        <fo:inline baseline-shift="super">
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>


    <xsl:template match="ins|u">
        <fo:inline text-decoration="underline">
            <xsl:apply-templates />
        </fo:inline>
    </xsl:template>
</xsl:stylesheet>

A complete example of a xsl-fo template for html tags can be found here.

Entity Encoding!

If you need to parse XML content make sure that this content did not contain HTML encoded characters like &auml; – this entities contained in the xml source can not be processed by the XSL processor. You need to work with raw utf-8 or unicode encoded characters. For example:

"ä" or "&#228;" works, but "&auml;" wont work!

As we are using TinyMCE Edtior in most of our web applications we need to set the “raw” format to initialize TinyMCE. Otherwise characters will be HTML encoded!

$('textarea.imixs-editor')            
            .tinymce(
                    { ....
                    .... // set entity encoding
                        entity_encoding: "raw"
                    });
        }