How to dynamically convert Java object to PDF with XSL-FO file in Spring Boot using Apache FOP (Part 2)

Agayev Ilkin
6 min readJul 19, 2024

--

Part 1 — Summary

Hi everyone. In the previous article (Part 1), we explained how to create an XSL-FO file and convert it to PDF using the Apache FOP library through Spring Boot.

The XSL-FO file we created in Part 1 was written with the extension (.fo) and everything inside it, including the text, was static.

This Part

In this article, we will create an XSL-FO (.xsl) file and also configure it to insert dynamic data coming from Java objects. We will do this using Spring Boot and the Apache FOP library.

In this section, what exactly will we write?

  • Create a Java class for sample data.
  • Create an appropriate XSL-FO (.xsl) template for dynamic data placement.
  • Implement logic in the service to convert XSL-FO to PDF using Apache FOP.
  • Generate a sample list of Java objects for PDF generation with dynamic data.
  • Write the list we created into XSL-FO via the service we wrote and convert it to PDF.

<Let`s start …|

1. Generate Java model class

First, let’s create a class. The reason is that we will make a list from a few objects created from this class.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Book {

private String author;
private String title;

}

2. Generate XSL-FO (.xsl)

Now, let’s create an XSL file named “book” under “resource/file”.

The content should be like this:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:template match="data">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="A4" page-width="210mm" page-height="297mm">
<fo:region-body margin="1in"/>
</fo:simple-page-master>
</fo:layout-master-set>

<fo:page-sequence master-reference="A4">
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="16pt" font-weight="bold" text-align="center" margin-bottom="10mm">
Book List
</fo:block>

<fo:table width="100%" table-layout="fixed" border-collapse="collapse">
<fo:table-column column-width="50%"/>
<fo:table-column column-width="50%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell background-color="#CCCCCC" text-align="center" padding="2mm"
border="1pt solid black">
<fo:block font-weight="bold">Title</fo:block>
</fo:table-cell>
<fo:table-cell background-color="#CCCCCC" text-align="center" padding="2mm"
border="1pt solid black">
<fo:block font-weight="bold">Author</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates select="item"/>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>

<xsl:template match="item">
<fo:table-row>
<fo:table-cell border="1pt solid black" padding="2mm">
<fo:block>
<xsl:value-of select="title"/>
</fo:block>
</fo:table-cell>
<fo:table-cell border="1pt solid black" padding="2mm">
<fo:block>
<xsl:value-of select="author"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>

</xsl:stylesheet>

Now here are the things to pay attention to:

Here we have 2 templates. One used “data” and the other used “item” as “match”, these are important.

  1. In the first template, as we can see, there is the “<fo:layout-master-set>” part. This section defines the size of the PDF file, the margins from the edges, the font used, and many other settings.
  2. Then there is the <fo:page-sequence master-reference=”A4">” section. The contents of a page are written inside this tag. To write to another page without finishing it, just create another “<fo:page-sequence master-reference=”A4"> section and write the contents.
  3. “<block>” is for writing normal lines. “<table> is for creating a table.
  4. Remember, the parts that are “match” here (item, data) determine the tag in the XML. In other words, our XML will have item and data tags.
<xsl:template match="data">

...

<fo:table-body>
<xsl:apply-templates select="item"/>
</fo:table-body>

...

</xsl:template>

5. The “data” template will contain the list of Java objects that we will send, and the “item” template it is called from will allow us to add each one as a new line.

<xsl:template match="item">
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="title"/>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:value-of select="author"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>

3. Create a service for set data to XSL-FO dynamically

    Book book1 = new Book();
book1.setAuthor("John");
book1.setTitle("John`s book");

Book book2 = new Book();
book1.setAuthor("Tom");
book1.setTitle("Tom`s book");

Book book3= new Book();
book1.setAuthor("Martin");
book1.setTitle("Martin`s book");

List<Book> bookList = new ArrayList<>();
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);

Here we created 3 sample book objects and placed them in a list. Now let’s complete the converting it to pdf part…

4. Write generatePdf method

First of all, I placed the sample objects we created within a method.

@PostConstruct
public void runCode() {
Book book1 = new Book();
book1.setAuthor("John");
book1.setTitle("John's book");

Book book2 = new Book();
book2.setAuthor("Tom");
book2.setTitle("Tom's book");

Book book3 = new Book();
book3.setAuthor("Martin");
book3.setTitle("Martin's book");

List<Book> bookList = new ArrayList<>();
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);

try {
generatePdf(bookList);
} catch (Exception e) {
log.error("Error generating PDF", e);
}
}

In the generatePdf method that we will create, there is a code that converts the java object to XML. Let’s create the method using it.

private void generatePdf(List<Book> data) throws Exception {

ClassPathResource xslTemplateResource = new ClassPathResource("files/book.xsl");
if (!xslTemplateResource.exists()) {
throw new FileNotFoundException("XSL template not found");
}

XmlMapper xmlMapper = new XmlMapper();
xmlMapper.enable(com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT);
String xmlString = xmlMapper.writer().withRootName("data").writeValueAsString(data);

System.out.println(xmlString);

FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();

String desktopPath = System.getProperty("user.home") + File.separator + "Desktop";
File pdfFile = new File(desktopPath + File.separator + "output.pdf");

try (OutputStream out = new FileOutputStream(pdfFile)) {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(new StreamSource(xslTemplateResource.getInputStream()));

transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
Source src = new StreamSource(new StringReader(xmlString));
Result res = new SAXResult(fop.getDefaultHandler());

transformer.transform(src, res);
}
}

This method takes the list of objects we’ve created and converts it to XML. Then, it applies it to the XSL-FO template (book.xsl).

I used it to see how XML looks.

System.out.println(xmlString);

The XML representation of the object we created will look like this:

<data>
<item>
<author>John</author>
<title>John's book</title>
</item>
<item>
<author>Tom</author>
<title>Tom's book</title>
</item>
<item>
<author>Martin</author>
<title>Martin's book</title>
</item>
</data>

Now, if you apply all of these, the PDF will already be prepared. However, let’s summarize the process of converting a Java object to PDF again.

  1. First, we prepared the Java object list we created and converted it to XML.
  2. We named the root element in the XML “data”.
  3. Then we took the book.xsl template and the XML, and with the help of Apache FOP, we set up the XML with the XSL-FO template and converted it to PDF.
  4. Here, as you can see from the XML, “data” is the tag that contains all the general data, while “item” is the tag that contains each one individually.
  5. If we look at book.xsl, we can see that we use two different templates for this.
    The first template takes the data within the general “data” and calls the other “item” template within it, allowing each item to be processed according to this template.
  6. As a result, each item is adapted according to the template.
If you increase the Java object List, the data here will also increase…

source code: GitHub

--

--