Template based document generation using ODFDOM

Generating documents from data that is managed by a web application is a quite common need. Think about letters that are generated for a customer relationship management system or bills that are to be send for membership fees. For corporate identity reasons you don’t want these documents to look like generated from a plain text file but you want to have logos, tables, address labels and so on.
As the people that are designing the look of these documents often are not programmers it is a good idea to provide a way to use well know tools for creating and editing templates for these documents. What we have been doing for some time is to let the customer create template documents using OpenOffice.org, the open source word processor, and transform these documents programmatically. OpenOffice.org uses the standardized Open Document format to save its documents. Open Document files are zip archives that contain some XML documents as well as additional content (images, macros, …).

The UNO approach

One of the older approaches we have been using for document processing is to access an OpenOffice.org instance running on the server using the UNO API. UNO is a language agnostic API that provides access to a lot of functionality of OpenOffice.org using an IDL. Though really powerful this approach also yields some drawbacks:

  • Understanding and learning the UNO API is hard and takes a lot of time
  • Some features of a document can’t be accessed using the API (e.g. the id of form control elements is saved in the document but is not accessible using UNO)
  • An instance of OpenOffice.org can only be used by one thread at a time so you need some kind of instance pooling.

These drawbacks make it really hard to design and implement a robust system that can handle the load of a typical web application and can still be maintained by a lot of developers.

ODFDOM

Some time after the standardization of the Open Document format a new project was born: ODFDOM, a sub project of the odftoolkt project. ODFDOM is a pure Java API that provides both low level DOM access to the Open Document XML format as well as convenience functionality to manipulate document data.
As with ODFDOM the application and the document generation all run on the Java Virtual Machine it is easier to maintain from an adminitrators perspective. Also in contrast to the UNO API ODFDOM is really easy to use.
The following snippet creates a new text document, inserts some text and saves the document to a temp file.

OdfTextDocument doc = OdfTextDocument.newTextDocument();
doc.addText("Hello World!");
doc.save(File.createTempFile("odfdom", ".odt"));

Templating

To use Odfdom for templating you can choose one of the many placeholder approaches in OpenOffice.org. A very simple one is the use of user fields. To insert a user field in OpenOffice.org create a new document and go to Insert => Field command => Others. There you choose the tab variables and user field. You can add a name and a value. The value in our case is only there to have a visual feedback when designing the document. The user field will be replaced automatically.
Let’s see how we can replace our placeholder value. The values for user fields as inserted above are stored in a node <text:user-field-decl>. This is an excerpt from the Open Document content.xml for a simple example document:

<text:user-field-decl office:value-type="string" office:string-value="hello" text:name="test"/>

The user field is named test, it’s initial value for visual feedback is set to “hello”.
Imagine that the data that we want to replace with the values in the template is stored in a simple Map of Strings. To replace all dummy values with values from you application you can access the nodes using the method getElementsByTagName(“element”):

Map<String, String> values = new HashMap<String, String>();
values.put("test", "inserted automatically");
OdfDocument doc = OdfDocument.loadDocument("/path/to/template.odt");
NodeList nodes = doc.getOfficeBody().getElementsByTagName(OdfTextUserFieldDecl.ELEMENT_NAME.getQName());
for (int i = 0; i < nodes.getLength(); i++) {
  OdfTextUserFieldDecl element = (OdfTextUserFieldDecl) nodes.item(i);
  if (values.containsKey(element.getTextNameAttribute())) {
    element.setOfficeStringValueAttribute(values.get(element.getTextNameAttribute()));
  }
}
doc.save("/path/to/result.odt");

When running the code above, the value in the document is replaced with the value set programmatically.

Conclusion

So far we are running code using ODFDOM for document generation successfully in two larger projects that have been developed recently. We believe that ODFDOM will help us delivering additional value for our customers with less development effort.

Kommentare

  1. Hi,
    I found a simple solution
    watch this video demo.
    https://www.youtube.com/watch?v=hYmrjf8MC9Y

  2. Hi,
    what are trying to do exactly? Only loading the document or really replacing values? Could you show the code that you are using so I can try to reproduce the issue?

  3. Hello, I have one question :
    I get this error :
    “org.odftoolkit.odfdom.dom.element.office.OfficeDocumentElement cannot be cast to org.odftoolkit.odfdom.dom.element.office.OfficeDocumentContentElement”
    I have a complete openoffice text document (.odt) in a single XML document and my root element (in context.xml) is :
    What is the correct implementation to load my document with odf toolkit,
    (with odfdom-java-0.8.7.jar and simple-odf-0.6.6.jar)
    Regards,