MSWord generation made easy(er)

In my swing application the requirement was that the user had to be able to issue an action, from a menu or from a button, that generates a letter from a defined msword template filled with data load from the database. Then the application should launch msword to make a preview that the user can manually complete or edit, then save or print.

But, Microsoft Word file format is not public, and generating such kind of file from Java is not that easy. There are libraries, like jakarta POI, but they are quite complex, and limited; and generating a print layout using those Java API could become a nightmare.
The solution, as for the excel example, is to create a textual format from a word document, that can be easily manipulated: RTF.
RTF, as msword, supports textfields that can be referenced by bookmarks and can be filled modifying the content of the file.

I got the templates, then I added textfield associating them to a bookmark:

MSWord Textfields

Example of a template with textfields. Click to maximize

For example, the rtf source code for the “name” bookmark in above image, will look like:

{\\field{\\*\\fldinst {\\lang2057\\langfe1040\\langnp2057 {\\*\\bkmkstart name} FORMTEXT }{\\lang2057\\langfe1040\\langnp2057 {\\*\\datafield 0000000000000000046e616d6500000000000000000000000000
}{\\*\\formfield{\\fftype0\\fftypetxt0{\\*\\ffname name}}}}}{\\fldrslt {\\lang1024\\langfe1024\\noproof\\langnp2057 \\u8194\\'20\\u8194\\'20\\u8194\\'20\\u8194\\'20\\u8194\\'20}}}{\\lang2057\\langfe1040\\langnp2057 {\\*\\bkmkend name}

I agree, not that easy to understand. The thing is that, if you modify the highlited text you get the field filled with your value. In bold the keywords I used in my code to locate the position of the text to replace.
So, it becomes just a matter of searching the template file for field values identified by the bookmark, and replacing it with the desired value.

Here’s a simplified version of the class that does this job:

 1package it.newinstance.util.rtf;
 2
 3import java.io.FileOutputStream;
 4import java.io.IOException;
 5import java.io.InputStream;
 6
 7public class RTF {
 8    private StringBuilder builder;
 9    
10    public RTF(InputStream in) throws IOException {
11        builder = new StringBuilder(16384);
12        int ch;
13        while ((ch = in.read()) != -1)
14            builder.append((char) ch);
15    }
16
17    public boolean set(String fieldName, String fieldValue) {
18        int bkmkStart = builder.indexOf("\\bkmkstart " + fieldName);
19        if (bkmkStart == -1) return false;
20
21        int fldrsltStart = builder.indexOf("\\fldrslt", bkmkStart);
22        if (fldrsltStart == -1) return false;
23
24        int start = builder.indexOf("{", fldrsltStart);
25        if (start == -1) return false;
26
27        int end = builder.indexOf("}", fldrsltStart);
28        if (end == -1) return false;
29
30        builder.replace(builder.indexOf(" ", start) + 1, end, fieldValue);
31        return true;
32    }
33
34    public byte[] getBytes() {
35        return builder.toString().getBytes();
36    }    
37
38    public void launchMSWord() throws IOException {        
39        runMSWord(saveTempFile());
40    }
41
42    private String saveTempFile() throws IOException {
43        String tmpDir = System.getProperties().getProperty("java.io.tmpdir");
44        long time = System.currentTimeMillis();
45        String filename = tmpDir + "print_" + time + ".rtf";
46
47        FileOutputStream out = new FileOutputStream(filename);
48        try {
49            out.write(getBytes());
50        } finally {
51            out.close();
52        }
53        return filename;
54    }
55
56    private void runMSWord(String filename) throws IOException {
57        String osName = System.getProperties().getProperty("os.name");
58        if (osName.indexOf("Windows") != -1) {
59            String command = 
60                "rundll32 SHELL32.DLL,ShellExec_RunDLL winword " + filename;
61            Runtime.getRuntime().exec(command, null, null);
62        } else {
63            System.out.println("Not in Windows; open yourself " + filename);
64        }
65    }
66}

You construct the RTF object giving an InputStream to the template file; all the job of replacement is made in set method, then you can get resulting bytes with getBytes(), or just call launchMSWord() if you want to pupup Microsoft Word to the user (this works only on standalone applications).
The above code is a slow prototype, as string search and replacement is made in the easiest way for this post to be simple to understand and to read, so I made a much faster version (that also check to not making mess with the rtf code) that you can find at this link. I used this class with very big and complex msword templates containing images, tables, etc. without problems.
A thing that should be added to this class, is character escaping: I didn’t implemented it yet.

Here’s an example of usage:

 1String resourceName = "/sample-module.rtf";
 2RTF rtf = new RTF(Sample.class.getResourceAsStream(resourceName));
 3
 4rtf.set("name", "Foo");
 5rtf.set("surname", "Bar");
 6rtf.set("birthday", "10/10/2010");
 7rtf.set("address", "Sample Test Address");
 8rtf.set("phone", "+010 010101010");
 9rtf.set("test", "testtesttest");
10
11rtf.launchMSWord();

As for the excel example, this class can be used in web applications using application/msword content type, so that the browser will associate the file to word.

For a runnable example, I put an eclipse project on subversion with some of my stuff, at this URL:
http://newinstance-util.googlecode.com/svn/trunk/util/
After opening the project in eclipse, the class to launch is
src/it/newinstance/util/rtf/Sample.java
No junit testcases, sorry :-)
The template is located under the resources folder.

The advantage of using this template-based approach over java-api based generation, is that you can take a template from the user and achieve a document that fully respects the given layout. Also you can easily handle layout change.
Doing document generation using a programming language is much more a matter of art than document layout design.


2 Responses to “MSWord generation made easy(er)”  

  1. 1 Todd

    Are the source code links listed in this blog posting available? I am getting the following error message “Repository access denied.”

    Thanks

  2. 2 Luigi

    Hi Todd.

    Thanks for reporting the problem. Can you try again now?

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Current day month ye@r *


Calendar

March 2007
M T W T F S S
« Feb   May »
 1234
567891011
12131415161718
19202122232425
262728293031  

Follow me

twitter flickr LinkedIn feed

Subscribe by email

Enter your email address:

Archives


Categories

Tag Cloud


Listening