Crear documentos PDF con Java

Java | June 21st

Utilizaremos el proyecto open source Apache FOP (Formatting Objects Processor) que nos da una serie de funciones para convertir un documento XML a un documento PDF, PS, Texto entre otros.

Este proyecto toma un archivo XSL-FO y lo convierte en formato PDF, por lo que habrá que convertir primero nuestro archivo XML a XSL-FO antes de convertirlo a un documento PDF.

Archivos XML y XSLT

Vamos a crear dos archivos, el primero será en formato xml. En este archivo se guardarán los datos de nuestro documento PDF.

Archivo glossary.xml

<?xml version="1.0"?>
<glossary>
<term-entry>
	<term>basic-link</term>
	<definition>The fo:basic-link is used for representing the start resource
	of a simple link.</definition>
</term-entry>
<term-entry>
	<term>bidi-override</term>
	<definition>The fo:bidi-override inline formatting object is used where
	it is necessary to override the default Unicode-bidirectionality
	algorithm direction for different (or nested) inline scripts in
	mixed-language documents.</definition>
</term-entry>
<term-entry>
	<term>block</term>
	<definition>The fo:block formatting object is commonly used for formatting
	paragraphs, titles, headlines, figure and table captions, etc.</definition>
</term-entry>

Archivo glossary.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:template match="glossary">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">

<fo:layout-master-set>
<fo:simple-page-master master-name="all" page-height="11.5in" page-width="8.5in" margin-top="1in" margin-bottom="1in" margin-left="0.75in" margin-right="0.75in">
	<fo:region-body margin-top="1in" margin-bottom="0.75in"/>
	<fo:region-before extent="0.75in"/>
	<fo:region-after extent="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>

<fo:page-sequence master-reference="all" format="i">

<fo:static-content flow-name="xsl-region-before">
<fo:block text-align="start" font-size="10pt" font-family="serif" line-height="1em + 2pt">
	<fo:retrieve-marker retrieve-class-name="term" retrieve-boundary="page" retrieve-position="first-starting-within-page"/>
	<fo:leader leader-alignment="reference-area" leader-pattern="dots" leader-length="4in"/>
	<fo:retrieve-marker retrieve-class-name="term" retrieve-boundary="page" retrieve-position="last-ending-within-page"/>
</fo:block>
</fo:static-content> 

<fo:static-content flow-name="xsl-region-after">
<fo:block text-align="start" font-size="10pt" font-family="serif" line-height="1em + 2pt"> Page (<fo:page-number/>)
</fo:block>
</fo:static-content> 

<fo:flow flow-name="xsl-region-body">
<xsl:apply-templates select="term-entry"/>
</fo:flow>

</fo:page-sequence>

</fo:root>
</xsl:template>

<xsl:template match="term-entry">
<fo:block text-align="start" font-size="12pt" font-family="sans-serif">
	<xsl:apply-templates select="term"/>
	<xsl:apply-templates select="definition"/>
</fo:block>
</xsl:template>

<xsl:template match="term">
<fo:block color="blue" space-before.optimum="3pt">
	<fo:marker marker-class-name="term"><xsl:value-of select="."/>
	</fo:marker>
	<xsl:value-of select="."/>
</fo:block>
</xsl:template>

<xsl:template match="definition">
	<fo:block text-align="start" start-indent="2em">
	<xsl:value-of select="."/>
	</fo:block>
</xsl:template>

</xsl:stylesheet>

Convertir documento XML a XSL-FO

El proyecto Apache FOP incluye las librerías: FOP, Logger, Graphics, Xerces, XML y XSLT.

Archivos JAR incluidos en Apache FOP
Archivo JARDescripción
fop.jarFOP API
avalon-framework-cvs-20020806.jarLogger
batik.jarGraphics
xercesImpl-2.2.1.jarXerces API
xml-apis.jarXML API
xalan-2.4.1.jarXSLT API
import org.apache.avalon.framework.logger.*;
import org.apache.fop.apps.*;
import org.xml.sax.InputSource;
import org.xml.sax.*;
import org.w3c.dom.*;
public void generarXSLFO(File archivoXML, File archivoXSLT)
{
	try {
		System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
		System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document documento = builder.parse(archivoXML);

		TransformerFactory tFactory = TransformerFactory.newInstance();
		StreamSource stylesource = new StreamSource(archivoXSLT);

		Transformer transformer = tFactory.newTransformer(stylesource);
		DOMSource source = new DOMSource(documento);

		StreamResult result = new StreamResult("glossary.fo");
		transformer.transform(source, result);

	} catch (TransformerConfigurationException e) {
		System.out.println(e.getMessage());
	} catch (TransformerException e) {
		System.out.println(e.getMessage());
	} catch (SAXException e) {
		System.out.println(e.getMessage());
	} catch (ParserConfigurationException e) {
		System.out.println(e.getMessage());
	} catch (IOException e) {
		System.out.println(e.getMessage());
	}
}

Generar documento PDF

Una vez generados el archivo glossary.fo, podemos generar nuestro documento PDF.

public void generarPDF()
{
	try {
		Driver driver = new Driver();
		Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_INFO);
		driver.setLogger(logger);
		org.apache.fop.messaging.MessageHandler.setScreenLogger(logger);
		driver.setRenderer(Driver.RENDER_PDF);

		InputStream input = new FileInputStream(new File("glossary.fo"));
		driver.setInputSource(new InputSource(input));

		OutputStream output = new FileOutputStream(new File("glossary.pdf"));
		driver.setOutputStream(output);

		driver.run();
		output.flush();
		output.close();

	} catch (IOException e) {
		System.out.println(e.getMessage());
	} catch (org.apache.fop.apps.FOPException e) {
		System.out.println(e.getMessage());
	}
}

Código Completo

Código del archivo ConvertirPDF.java

package org.luisalberto.xml2pdf;

import java.io.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

import org.apache.avalon.framework.logger.*;
import org.apache.fop.apps.*;
import org.xml.sax.InputSource;
import org.xml.sax.*;
import org.w3c.dom.*;

public class ConvertirPDF
{
	public void generarXSLFO(File archivoXML, File archivoXSLT)
	{
		try {
			System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
			System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.processor.TransformerFactoryImpl");

			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document documento = builder.parse(archivoXML);

			TransformerFactory tFactory = TransformerFactory.newInstance();
			StreamSource stylesource = new StreamSource(archivoXSLT);

			Transformer transformer = tFactory.newTransformer(stylesource);
			DOMSource source = new DOMSource(documento);

			StreamResult result = new StreamResult("glossary.fo");
			transformer.transform(source, result);

		} catch (TransformerConfigurationException e) {
			System.out.println(e.getMessage());

		} catch (TransformerException e) {
			System.out.println(e.getMessage());

		} catch (SAXException e) {
			System.out.println(e.getMessage());

		} catch (ParserConfigurationException e) {
			System.out.println(e.getMessage());

		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

	public void generarPDF()
	{
		try {
			Driver driver = new Driver();
			Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_INFO);
			driver.setLogger(logger);
			org.apache.fop.messaging.MessageHandler.setScreenLogger(logger);
			driver.setRenderer(Driver.RENDER_PDF);

			InputStream input = new FileInputStream(new File("glossary.fo"));
			driver.setInputSource(new InputSource(input));

			OutputStream output = new FileOutputStream(new File("glossary.pdf"));
			driver.setOutputStream(output);

			driver.run();
			output.flush();
			output.close();

		} catch (IOException e) {
		} catch (org.apache.fop.apps.FOPException e) {
			System.out.println(e.getMessage());
		}
	}
}

Código del archivo Test.java

package org.luisalberto.xml2pdf;

import java.io.*;

import org.luisalberto.xml2pdf.ConvertirPDF;

public class Test
{
	public static void main(String[] argv)
	{
		ConvertirPDF xmlPDF= new ConvertirPDF();
		File archivoXML = new File("glossary.xml");
		File archivoXSLT = new File("glossary.xslt");

		xmlPDF.generarXSLFO(archivoXML, archivoXSLT);
		xmlPDF.generarPDF();
	}
}

Comentarios

  1. Manuel says:

    Hola,

    He intentado ejecutar tu ejemplo pero no me compila.

    Estoy usando la versión 0.95 de FOP y no me compila la clase Driver.

  2. Manuel says:

    Ok, resuelto con la versión 0.20.5 en lugar de la 0.95

  3. Antonio says:

    Hola

    Estoy usando java+xsl+xml para generar ficheros HTML y espero que puedas responder a mi pregunta (aunque no genere pdf que es el tema del post).

    Al compilar Xalan para la generacion de los HTML yo le puedo pasar parametros del siguiente modo (esto es el comando que meto en MSDOS)
    C:\xalan-j_2_7_1>java -classpath “xalan.jar;serializer.j
    ar;xml-apis.jar;xercesImpl.jar” org.apache.xalan.xslt.Process -IN ficheroxml.xml -XSL ficheroxsl.xsl -OUT salida.html -PARAM nombreparametro valor

    ¿Como puedo pasar parametros usando el codigo que pones aqui que es muy similar al que uso yo?

    He probado añadiendolo detras de los nombres en las siguientes lineas pero da error o genera ficheros lalamdos “glossary.fo -PARAM nombreparametro valor”

    Document documento = builder.parse(archivoXML)+ “-PARAM nombreparametro valor”;

    StreamSource stylesource = new StreamSource(archivoXSLT)+”-PARAM nombreparametro valor”;

    StreamResult result = new StreamResult(“glossary.fo”)+”-PARAM nombreparametro valor”;3

    Espero que me peudas ayudar.

    Saludos

  4. Marcela says:

    Hola gracias por la explicacion esta bien entendible.
    Tengo el mismo problema q Manuel, estoy usando la version 0.95 y la clase Driver no la reconoce, no estoy segura a q libreria corresponde, si me pudiese ayudar, o directamente cambio todos los jar por los de la version 0.20

    Gracias

    • Alberto says:

      Que tal Marcela, este ejemplo funciona muy bien con la versión 0.20. Por otro lado voy a modificar esta publicación ya que al parecer las versiones no son compatibles. Claro que, necesito analizar la última versión estable que es 0.95.

      Gracias por tus comentarios

Deja tu comentario