MSWord generation made easy(er)
Pubblicato da Luigi il 23 Marzo 2007 in Java, Windows, Software, WebIn 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:

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 Commenti a “MSWord generation made easy(er)”
Lascia un Commento
Cerca
Calendario
| L | M | M | G | V | S | D |
|---|---|---|---|---|---|---|
| « Feb | Mag » | |||||
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 | |
Archivi
- Gennaio 2010 (2)
- Dicembre 2009 (1)
- Novembre 2009 (3)
- Settembre 2009 (2)
- Agosto 2009 (4)
- Luglio 2009 (1)
- Giugno 2009 (2)
- Maggio 2009 (4)
- Aprile 2009 (2)
- Marzo 2009 (7)
- Febbraio 2009 (5)
- Gennaio 2009 (2)
- Dicembre 2008 (1)
- Novembre 2008 (8)
- Ottobre 2008 (12)
- Settembre 2008 (3)
- Agosto 2008 (2)
- Luglio 2008 (6)
- Giugno 2008 (16)
- Maggio 2008 (2)
- Aprile 2008 (3)
- Marzo 2008 (6)
- Ottobre 2007 (1)
- Settembre 2007 (1)
- Agosto 2007 (5)
- Luglio 2007 (6)
- Giugno 2007 (6)
- Maggio 2007 (1)
- Marzo 2007 (1)
- Febbraio 2007 (2)
- Gennaio 2007 (1)
- Dicembre 2006 (2)
- Novembre 2006 (4)
- Ottobre 2006 (7)
- Settembre 2006 (1)
- Agosto 2006 (2)
- Luglio 2006 (6)
- Giugno 2006 (3)
- Febbraio 2006 (1)
- Gennaio 2006 (1)
- Dicembre 2005 (5)
- Novembre 2005 (2)
- Ottobre 2005 (2)
- Settembre 2005 (7)
- Agosto 2005 (2)
- Luglio 2005 (8)
- Giugno 2005 (12)
Categorie
- Books (7)
- Eclipse (10)
- Errors (2)
- Firefox (7)
- Hardware (14)
- Horror Code (8)
- Internet (17)
- Java (85)
- JavaScript (8)
- Life, universe and everything (29)
- Linux (44)
- Mac (18)
- Software (25)
- Speeches and Conferences (8)
- Web (19)
- Windows (16)
Ultimi Post
- Syntactic sugar and Java arrays.
- 3G USB Stick on Ubuntu
- Ipod touch with Linux
- Karmic and Luks: USB drive encryption made (almost) easy
- Suspend/Resume in Karmic /2
- Suspend/Resume problem in Ubuntu Karmic 9.10 running on MacBook Pro 5.1
- MacBook International Keyboard and Linux
- Mighty Mouse: reverse horizontal scrolling workaround on Ubuntu Linux 9.04
- Skype 2.1.0.47 beta released, and amd64 packages available!
- Linux RAM Disks
My open source projects
Blog License
Blogs I like
Friends' Blogs
- Antonio Terreno & Valter Bernardini
- Bruno Bossola
- Daniele Galluccio
- Domenico Ventura
- Ed Schepis
- Fabrizio Gianneschi
- Filippo Diotalevi
- JavaJournal.it Blog
- Luca Grulla
- Luigi Zanderighi
- Marcello Teodori
- Mida Boghetich
- Muralidharan Chandrasekaran
- Piero Ricca
- Renzo Borgatti
- Simone Bordet
- Uberto Barbini
- Valvolog
- Webtide blogs (Greg Wilkins & Jan Bartel)
Links








Are the source code links listed in this blog posting available? I am getting the following error message “Repository access denied.”
Thanks
Hi Todd.
Thanks for reporting the problem. Can you try again now?