Wednesday, 22 July 2015

Creating PDF from XML using Apache FOP

In this post I'll talk about how to create PDF files from XML using Apache FOP.

What is Apache FOP

Apache™ FOP (Formatting Objects Processor) is a print formatter driven by XSL formatting objects (XSL-FO) and an output independent formatter. It is a Java application that reads a formatting object (FO) tree and renders the resulting pages to a specified output.
FOP uses the standard XSL-FO file format as input, lays the content out into pages, then renders it to the requested output.
Read more about it here - https://xmlgraphics.apache.org/fop/

Steps for creating a PDF from XML

To produce a PDF file from a XML file, first step is that we need an XSLT stylesheet that converts the XML to XSL-FO. Created XSL-FO file is also an XML file which contains formatted objects.
The second step will be done by FOP when it reads the generated XSL-FO document and formats it to a PDF document.

How to get Apache FOP

Get the FOP download from here.
https://xmlgraphics.apache.org/fop/download.html

I have used fop-2.0 for this example code.
Needed jars (found in the lib and build directory in the fop download) -

  • Commons-io
  • Commons-logging
  • Xml-apis
  • Xmlgraphics-commons
  • Fop
  • Batik-all
  • Avalon-framework

Example code

XML I used for creating PDF
<?xml version="1.0"?>

<employees>
<companyname>ABC Inc.</companyname>
 <employee>
  <id>101</id>
  <name>Ram</name>
  <designation>Manager</designation>
 </employee>

 <employee>
  <id>102</id>
  <name>Prabhu</name>
  <designation>Executive</designation>
 </employee>

 <employee>
  <id>103</id>
  <name>John</name>
  <designation>Executive</designation>
 </employee>
</employees>

Stylesheet Used

<?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="employees">
    <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">Company Name: <xsl:value-of select="companyname"/>
          </fo:block>
          <fo:block font-size="10pt">
          <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="employee"/>
            </fo:table-body>
          </fo:table>
          </fo:block>
        </fo:flow>
      </fo:page-sequence>
     </fo:root>
</xsl:template>
<xsl:template match="employee">
    <fo:table-row>   
     <xsl:if test="designation = 'Manager'">
            <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="name"/>
        </fo:block>
      </fo:table-cell>   
      <fo:table-cell>
        <fo:block>
      <xsl:value-of select="designation"/>
        </fo:block>
      </fo:table-cell>
    </fo:table-row>
  </xsl:template>
</xsl:stylesheet>

If you see the XSL, first I am looking for the employees element to get the company Name and also there some formatting is done like how many columns are needed and what should be the width. Then I am looking for the employee element and printing the values, also some logic is there to print the field values in bold if the designation is manager.

Copying the output of PDF I got, that will make it easy to understand the XSL.

PDF from XML using Apache FOP

Java code

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;

public class FOPPdfDemo {

    public static void main(String[] args) {
        FOPPdfDemo fOPPdfDemo = new FOPPdfDemo();
        try {
            fOPPdfDemo.convertToPDF();
        } catch (FOPException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    
    /**
     * Method that will convert the given XML to PDF 
     * @throws IOException
     * @throws FOPException
     * @throws TransformerException
     */
    public void convertToPDF()  throws IOException, FOPException, TransformerException {
        // the XSL FO file
        File xsltFile = new File("F:\\Temp\\template.xsl");
        // the XML file which provides the input
        StreamSource xmlSource = new StreamSource(new File("F:\\Temp\\Employees.xml"));
        // create an instance of fop factory
        FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
        // a user agent is needed for transformation
        FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
        // Setup output
        OutputStream out;
        out = new java.io.FileOutputStream("F:\\Temp\\employee.pdf");
    
        try {
            // Construct fop with desired output format
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);

            // Setup XSLT
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(new StreamSource(xsltFile));

            // Resulting SAX events (the generated FO) must be piped through to FOP
            Result res = new SAXResult(fop.getDefaultHandler());

            // Start XSLT transformation and FOP processing
            // That's where the XML is first transformed to XSL-FO and then 
            // PDF is created
            transformer.transform(xmlSource, res);
        } finally {
            out.close();
        }
    }
    
    /**
     * This method will convert the given XML to XSL-FO
     * @throws IOException
     * @throws FOPException
     * @throws TransformerException
     */
    public void convertToFO()  throws IOException, FOPException, TransformerException {
        // the XSL FO file
        File xsltFile = new File("F:\\Temp\\template.xsl");
        
        
        /*TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(new StreamSource("F:\\Temp\\template.xsl"));*/
        
        // the XML file which provides the input
        StreamSource xmlSource = new StreamSource(new File("F:\\Temp\\Employees.xml"));
        
        // a user agent is needed for transformation
        /*FOUserAgent foUserAgent = fopFactory.newFOUserAgent();*/
        // Setup output
        OutputStream out;
        
        out = new java.io.FileOutputStream("F:\\Temp\\temp.fo");
    
        try {
            // Setup XSLT
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(new StreamSource(xsltFile));

            // Resulting SAX events (the generated FO) must be piped through to FOP
            //Result res = new SAXResult(fop.getDefaultHandler());

            Result res = new StreamResult(out);

            //Start XSLT transformation and FOP processing
            transformer.transform(xmlSource, res);


            // Start XSLT transformation and FOP processing
            // That's where the XML is first transformed to XSL-FO and then 
            // PDF is created
            transformer.transform(xmlSource, res);
        } finally {
            out.close();
        }
    }

}

In the code there are two methods convertToPDF() and convertToFO(), convertToPDF() method is used to convert XML to PDF. convertToFO() method will create the XSL-FO from the XML using the XSLT. If you want to see the created FO which in turn is used to create PDF please call this method.

In case of web application if you want to provide PDF as a download add following lines with in the convertToPDF() method-

//Prepare response
response.setContentType("application/pdf");
response.setContentLength(out.size());

//Send content to Browser
response.getOutputStream().write(out.toByteArray());
response.getOutputStream().flush();

That's all for this topic Creating PDF from XML using Apache FOP. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. How to convert a file to byte array
  2. How to read file from the last line in Java
  3. How to add double quotes to a String
  4. Print odd-even numbers using threads and semaphore
  5. Unzipping files in Java

You may also like -

>>>Go to Java Programs page

22 comments:

  1. Thanks for XML to PDF tutorial! Saved me a lot of trouble.

    ReplyDelete
  2. Hi, can you specify the version of required jars. I am getting below exception while executing the example "Caused by: java.lang.ClassNotFoundException: org.apache.batik.bridge.FontFamilyResolver". This class is not available in batik-all.jar

    ReplyDelete
    Replies
    1. I am getting the same exception.Can you please provide the jar file download link.Actually org.apache.batik.gvt.font.FontFamilyResolver. I didnt get any jar file in google with this package class org.apache.batik.bridge.FontFamilyResolver

      Delete
    2. http://svn.apache.org/viewvc/xmlgraphics/fop/tags/fop-2_1/lib/
      there you can get batik-all-1.8.jar. Click on the link for batik-all-1.8.jar and you can download the latest revision.

      Delete
    3. The Issue got resolved,Thank you very much

      Delete
    4. log4j:WARN No appenders could be found for logger (org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry).
      log4j:WARN Please initialize the log4j system properly.
      log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
      Exception in thread "main" java.lang.ExceptionInInitializerError
      at java.lang.Class.forName0(Native Method)
      at java.lang.Class.forName(Class.java:264)
      at org.apache.fop.image.loader.batik.BatikUtil.isBatikAvailable(BatikUtil.java:41)
      at org.apache.fop.image.loader.batik.ImageLoaderFactorySVG.isAvailable(ImageLoaderFactorySVG.java:56)
      at org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry.registerLoaderFactory(ImageImplRegistry.java:182)
      at org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry.discoverClasspathImplementations(ImageImplRegistry.java:111)
      at org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry.(ImageImplRegistry.java:79)
      at org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry.(ImageImplRegistry.java:87)
      at org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry.(ImageImplRegistry.java:71)
      at org.apache.xmlgraphics.image.loader.ImageManager.(ImageManager.java:64)
      at org.apache.fop.apps.FopFactoryBuilder$FopFactoryConfigImpl.(FopFactoryBuilder.java:373)
      at org.apache.fop.apps.FopFactoryBuilder.(FopFactoryBuilder.java:89)
      at org.apache.fop.apps.FopFactoryBuilder.(FopFactoryBuilder.java:80)
      at org.apache.fop.apps.FopFactoryBuilder.(FopFactoryBuilder.java:70)
      at org.apache.fop.apps.FopFactory.newInstance(FopFactory.java:143)
      at com.db.ebpp.event.PDFGeneration.convertToPDF(PDFGeneration.java:51)
      at com.db.ebpp.event.PDFGeneration.main(PDFGeneration.java:25)
      Caused by: java.lang.ClassCastException: org.apache.batik.extension.svg.BatikDomExtension cannot be cast to org.apache.batik.dom.DomExtension
      at org.apache.batik.dom.ExtensibleDOMImplementation.getDomExtensions(Unknown Source)
      at org.apache.batik.dom.ExtensibleDOMImplementation.(Unknown Source)
      at org.apache.batik.anim.dom.SVGDOMImplementation.(Unknown Source)
      at org.apache.batik.anim.dom.SVGDOMImplementation.(Unknown Source)
      ... 17 more

      Delete
  3. Hi, How to parse the HTML text between XML tags like <description><h1> some description </h1></description>. I would like to render the 'description' node text with 'h1' styling on PDF. Please help me on this.

    ReplyDelete
    Replies
    1. Suppose I have this XML
      <?xml version="1.0"?>
      <employees>
      <description><h1> some description </h1></description>
      <employee>
      <id>101</id>
      <name>Ram</name>
      <designation>Manager</designation>
      </employee>
      </employees>

      Then if I change the XSL as provided in the example in the post to have description that line will look like this -
      <fo:block role="H1" font-weight="bold" space-after="5mm">Description: <xsl:value-of select="description/h1"/>

      Note in the XSL given in the example I have changed the line with company name to have description/h1 instead. For H1 styling "role" attribute has been used.

      Delete
  4. How we can download the source code?

    ReplyDelete
  5. can i use Apache FOP in andorid?

    ReplyDelete
  6. Hi,
    How can use the above code to generate AFP File?

    ReplyDelete
  7. Any example of trying to print a barcode? For example:









    I keep getting this error:

    Sep 29, 2016 8:19:32 AM org.apache.fop.apps.FOUserAgent processEvent
    WARNING: Unknown formatting object "{http://www.w3.org/1999/XSL/Format}root" encountered (a child of {http://www.w3.org/1999/XSL/Format}root}. (No context info available)
    Sep 29, 2016 8:19:32 AM org.apache.fop.fo.FOTreeBuilder fatalError
    SEVERE: org.xml.sax.SAXParseException; systemId: file:/C:/work/sandbox/generate-barcodes/src/main/resources/barcode-fop.xsl; lineNumber: 10; columnNumber: 35; java.lang.ClassCastException: org.apache.fop.fo.UnknownXMLObj cannot be cast to org.apache.fop.fo.pagination.Root
    file:/C:/work/sandbox/generate-barcodes/src/main/resources/barcode-fop.xsl; Line #10; Column #35; java.lang.ClassCastException: org.apache.fop.fo.UnknownXMLObj cannot be cast to org.apache.fop.fo.pagination.Root

    ReplyDelete
  8. 333333333333 file:/C:/Users/veluma/Projects_Ec/Spring_exp/XMLTraining/Employee.xsl
    Exception in thread "main" java.lang.NullPointerException
    at com.springpeople.xml.training.FOPPdfDemo.convertToPDF(FOPPdfDemo.java:82)
    at com.springpeople.xml.training.FOPPdfDemo.main(FOPPdfDemo.java:27)

    ReplyDelete
  9. What if the xml file is dynamic? As in, xml tags vary every time. Then we might need to dynamically generate xsl file. Is this the way? or is there any other approach?

    ReplyDelete
    Replies
    1. XML will always be based on some fixed schema. If it has XSD you can see that ofcourse some elements are required some are optional. If you want to check for optional element while transforming you can use XSL if-else condition for that.

      Delete
  10. Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/xmlgraphics/io/ResourceResolver
    at org.apache.fop.apps.FopFactoryBuilder.(FopFactoryBuilder.java:70)
    at org.apache.fop.apps.FopFactory.newInstance(FopFactory.java:143)
    at src.main.java.Fop_Pdf.convertToPDF(Fop_Pdf.java:46)
    at src.main.java.Fop_Pdf.main(Fop_Pdf.java:27)
    Caused by: java.lang.ClassNotFoundException: org.apache.xmlgraphics.io.ResourceResolver
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 4 more


    I get when i use d this code could u help to solve

    ReplyDelete
    Replies
    1. Have you included all the JARs, ResourceResolver class should be inside Xmlgraphics-commons-2.0.1 jar.

      Delete
    2. while triyng to run above code getting exception which is specified below.
      note:already Xmlgraphics-commons-2.0.1 jar added to builpath



      Exception in thread "main" java.lang.NoSuchMethodError: org.apache.xmlgraphics.xmp.Metadata.mergeInto(Lorg/apache/xmlgraphics/xmp/Metadata;)V
      at org.apache.fop.render.pdf.PDFRenderingUtil.renderXMPMetadata(PDFRenderingUtil.java:356)
      at org.apache.fop.render.pdf.PDFDocumentHandler.handleExtensionObject(PDFDocumentHandler.java:290)
      at org.apache.fop.render.intermediate.util.IFDocumentHandlerProxy.handleExtensionObject(IFDocumentHandlerProxy.java:197)
      at org.apache.fop.render.intermediate.IFRenderer.startPageSequence(IFRenderer.java:519)
      at org.apache.fop.area.RenderPagesModel.startPageSequence(RenderPagesModel.java:97)
      at org.apache.fop.layoutmgr.PageSequenceLayoutManager.activateLayout(PageSequenceLayoutManager.java:104)
      at org.apache.fop.area.AreaTreeHandler.endPageSequence(AreaTreeHandler.java:267)
      at org.apache.fop.fo.pagination.PageSequence.endOfNode(PageSequence.java:128)
      at org.apache.fop.fo.FOTreeBuilder$MainFOHandler.endElement(FOTreeBuilder.java:347)
      at org.apache.fop.fo.FOTreeBuilder.endElement(FOTreeBuilder.java:181)
      at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:265)
      at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:559)
      at temp.template$dot$0()
      at temp.applyTemplates()
      at temp.applyTemplates()
      at temp.transform()
      at com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(AbstractTranslet.java:617)
      at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:748)
      at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:359)
      at FOPPdfDemo.convertToPDF(FOPPdfDemo.java:71)
      at FOPPdfDemo.main(FOPPdfDemo.java:24)

      Delete
  11. hi, i have this situation, a) file xml b) file xslt.. but. The class doesn't work and the error is:
    'First element must be the fo:root formatting object. Found (Namespace URI: "", Local Name: "html") instead. Please make sure you're producing a valid XSL-FO document.'
    Any suggest?
    Thanks

    ReplyDelete
  12. perfect explanation about java programming .its very useful.thanks for your valuable information.java training in chennai | java training in velachery

    ReplyDelete