Java – Crear PDF desde XML con FOP

Existen varias técnicas para generar archivos PDF con Java. Con este método es posible separar el código Java de la estructura visual que tendrá el PDF.  Es necesario tener al menos tres cosas:

  • Código en Java usando las librerías de Apache Fop 2.2.
  • Archivo XML que provee los datos que se mostrarán en el PDF.
  • Una hoja de estilo XSLT que define la forma en la que serán mostrados visualmente los datos.

En este ejemplo sólo mostramos como generar un PDF muy sencillo.  Toma en cuenta en quizá debas aprender más de FOP XSLT para documentos más complejos.





Primero te mostramos el archivo XML con datos de ejemplo.

reporte.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0"?>
<empresa>
  <nombre_compania>Empresa de prueba S.A. de C.V.</nombre_compania>
  <empleado>
    <id>1</id>
    <nombre>Eva</nombre>
    <puesto>Gerente</puesto>
  </empleado>
  <empleado>
    <id>2</id>
    <nombre>David</nombre>
    <puesto>Ventas</puesto>
  </empleado>
  <empleado>
    <id>3</id>
    <nombre>Marta</nombre>
    <puesto>Ejecutivo</puesto>
  </empleado>
  <empleado>
    <id>4</id>
    <nombre>Pablo</nombre>
    <puesto>Ejecutivo</puesto>
  </empleado>
  <empleado>
    <id>5</id>
    <nombre>Sofía</nombre>
    <puesto>Ejecutivo</puesto>
  </empleado>
</empresa>

El archivo XML es muy sencillo, se trata de una lista hipotética de empleados de una empresa.

Ahora el archivo XSLT con la definición para generar el PDF.

reporte.xsl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                exclude-result-prefixes="fo">
 
    <xsl:template match="empresa">
 
        <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="simpleA4" page-height="29.7cm" page-width="21cm" margin-top="2cm" margin-bottom="2cm" margin-left="2cm" margin-right="2cm">
                    <fo:region-body/>
                </fo:simple-page-master>
            </fo:layout-master-set>
            <fo:page-sequence master-reference="simpleA4">
                <fo:flow flow-name="xsl-region-body">
                    <fo:block font-size="16pt" font-weight="bold" space-after="5mm">Empresa: <xsl:value-of select="nombre_compania"/>
                    </fo:block>
                    <fo:block font-size="12pt">
                        <fo:table table-layout="fixed" width="100%" border-collapse="separate">    
                            <fo:table-column column-width="4cm"/>
                            <fo:table-column column-width="4cm"/>
                            <fo:table-column column-width="5cm"/>
                            <fo:table-body>
                                <xsl:apply-templates select="empleado"/>
                            </fo:table-body>
                        </fo:table>
                    </fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
    <xsl:template match="empleado">
        <fo:table-row>   
            <xsl:if test="puesto = 'Gerente'">
                <xsl:attribute name="font-weight">bold</xsl:attribute>
            </xsl:if>
            <fo:table-cell>
                <fo:block>
                    <xsl:value-of select="id"/>
                </fo:block>
            </fo:table-cell>
 
            <fo:table-cell>
                <fo:block>
                    <xsl:value-of select="nombre"/>
                </fo:block>
            </fo:table-cell>   
            <fo:table-cell>
                <fo:block>
                    <xsl:value-of select="puesto"/>
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </xsl:template>
</xsl:stylesheet>

Hay al menos un par de cosas que debes notar en el archivo XSLT, la primera es que en el archivo están definidos templetes o plantillas que serán usadas para generar cabeceras o listas dependiendo de los datos que coincidan durante la exploración del archivo fuente de datos en XML.

Si puedes observar tenemos una definición para el manejo de datos de la cabecera en el nodo:

    <xsl:template match=”empresa”> 

Y otra mas para la lista de empleados:

    <xsl:template match=”empleado”>

Ambas con reglas diferentes.  La de empleado define que los datos del mismo serán mostrados en las celdas de una tabla.  Esta tabla está contenida a su vez en la plantilla de la empresa.

    <fo:table-body>
        <xsl:apply-templates select=”empleado”/>
    </fo:table-body>

De este modo es posible separar y definir de manera más clara, como se mostrarán los datos que pertenecen a la cabecera y quizá a un pie de página, de los datos que varían en cantidad, como la lista de empleados.

Ahora las librerías de Fop en el repositorio de Maven.

1
2
3
4
5
<dependency>
  <groupId>org.apache.xmlgraphics</groupId>
  <artifactId>fop</artifactId>
  <version>2.2</version>
</dependency>

Y finalmente el código Java.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.decodigo.ejemplos;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.xmlgraphics.util.MimeConstants;
 
/**
 *
 * @author decodigo
 */
public class CrearPDF {
 
    public static void main(String args[]) {
 
        try {
            // Nombre del archivo FO
            File xsltFile = new File("/home/decodigo/Documentos/java/reporte.xsl");
            //Archivo XML que proveerá de datos
            StreamSource xmlSource = new StreamSource(new File("/home/decodigo/Documentos/java/reporte.xml"));
 
            FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
            FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
            OutputStream out;
            //Archivo PDF
            out = new FileOutputStream("/home/decodigo/Documentos/java/reporte.pdf");
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(new StreamSource(xsltFile));
            Result res = new SAXResult(fop.getDefaultHandler());
            transformer.transform(xmlSource, res);
            out.close();
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

El archivo con el estilo del PDF

    File xsltFile = new File(“/home/decodigo/Documentos/java/reporte.xsl”);

El código Java definimos el archivo de datos fuente:

    StreamSource xmlSource = new StreamSource(new File(“/home/decodigo/Documentos/java/reporte.xml”));

Creamos un objeto Factory.

    FopFactory fopFactory = FopFactory.newInstance(new File(“.”).toURI());

Definimos el nombre del archivo PDF de salida.

    out = new FileOutputStream(“/home/decodigo/Documentos/java/reporte.pdf”);

Y finalmente hacemos la transformación.

    transformer.transform(xmlSource, res);

Debes notar que FOP soporta otros formatos. Pero el que es de interés para nosotros en este ejemplo es el siguiente:

    MimeConstants.MIME_PDF

A continuación el documento PDF generado.

Esperamos que este ejemplo te sea de utilidad.  Más información sobre FOP en la página del proveedor:

https://xmlgraphics.apache.org/fop/quickstartguide.html