Mail merge in java for Microsoft Word document – Part I
In one of the projects I was working on – we defined template using XSL, passed XML data and then generated PDF using Apache FOP. But it was tough to manage change – for even changing a single dot, the business needed to contact the developer.
We then started searching opensource and commercial friendly API which can merge the template defined in Microsoft Word with variables and produce the merged document. Our search ended with XDocReport which is available in The MIT License (MIT) and with really good documentation.
I did a POC and it came really well on expectations. The salient features I found are:
1. Replacing variables
2. Replacing Image Variables
3. Dynamicity for tabular format data.
I am putting below the java code I have written for the POC and attaching files used in merging along with out put files.
Add below in the pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>docx-to-docx</groupId> <artifactId>docx-to-docx</artifactId> <version>0.0.1-SNAPSHOT</version> <name>docx-to-docx</name> <description>Merging Microsoft Word DOCX format with variable replacement using xdocreport</description> <properties> <xdocreport.version>0.9.8</xdocreport.version> </properties> <dependencies> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.core</artifactId> <version>${xdocreport.version}</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.document</artifactId> <version>${xdocreport.version}</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.document.docx</artifactId> <version>${xdocreport.version}</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.converter</artifactId> <version>${xdocreport.version}</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId> <version>${xdocreport.version}</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </pluginManagement> </build> </project> |
Created a java file to produce merged document using XdocReport.
/** * */ package com.sambhashanam.docx; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Map; import fr.opensagres.xdocreport.core.XDocReportException; import fr.opensagres.xdocreport.core.io.internal.ByteArrayOutputStream; import fr.opensagres.xdocreport.document.IXDocReport; import fr.opensagres.xdocreport.document.images.FileImageProvider; import fr.opensagres.xdocreport.document.registry.XDocReportRegistry; import fr.opensagres.xdocreport.template.IContext; import fr.opensagres.xdocreport.template.TemplateEngineKind; import fr.opensagres.xdocreport.template.formatter.FieldsMetadata; /** * @author Dhananjay Jha * */ public class DocxDocumentMergerAndConverter { /** * Takes file path as input and returns the stream opened on it * @param filePath * @return * @throws IOException */ public InputStream loadDocumentAsStream(String filePath) throws IOException{ //URL url =new File(filePath).toURL(); URL url =new File(filePath).toURI().toURL(); InputStream documentTemplateAsStream=null; documentTemplateAsStream= url.openStream(); return documentTemplateAsStream; } /** * Loads the docx report * @param documentTemplateAsStream * @param freemarkerOrVelocityTemplateKind * @return * @throws IOException * @throws XDocReportException */ public IXDocReport loadDocumentAsIDocxReport(InputStream documentTemplateAsStream, TemplateEngineKind freemarkerOrVelocityTemplateKind) throws IOException, XDocReportException{ IXDocReport xdocReport = XDocReportRegistry.getRegistry().loadReport(documentTemplateAsStream, freemarkerOrVelocityTemplateKind); return xdocReport; } /** * Takes the IXDocReport instance, creates IContext instance out of it and puts variables in the context * @param report * @param variablesToBeReplaced * @return * @throws XDocReportException */ public IContext replaceVariabalesInTemplateOtherThanImages(IXDocReport report, Map<String, Object> variablesToBeReplaced) throws XDocReportException{ IContext context = report.createContext(); for(Map.Entry<String, Object> variable: variablesToBeReplaced.entrySet()){ context.put(variable.getKey(), variable.getValue()); } return context; } /** * Takes Map of image variable name and fileptah of the image to be replaced. Creates IImageprovides and adds the variable in context * @param report * @param variablesToBeReplaced * @param context */ public void replaceImagesVariabalesInTemplate(IXDocReport report, Map<String, String> variablesToBeReplaced, IContext context){ FieldsMetadata metadata = new FieldsMetadata(); for(Map.Entry<String, String> variable: variablesToBeReplaced.entrySet()){ metadata.addFieldAsImage(variable.getKey()); context.put(variable.getKey(), new FileImageProvider(new File(variable.getValue()),true)); } report.setFieldsMetadata(metadata); } /** * Generates byte array as output from merged template * @param report * @param context * @return * @throws XDocReportException * @throws IOException */ public byte[] generateMergedOutput(IXDocReport report,IContext context ) throws XDocReportException, IOException{ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); report.process(context, outputStream); return outputStream.toByteArray(); } /** * Takes inputs and returns merged output as byte[] * @param templatePath * @param templateEngineKind * @param nonImageVariableMap * @param imageVariablesWithPathMap * @return * @throws IOException * @throws XDocReportException */ public byte[] mergeAndGenerateOutput(String templatePath, TemplateEngineKind templateEngineKind, Map<String, Object> nonImageVariableMap,Map<String, String> imageVariablesWithPathMap ) throws IOException, XDocReportException{ InputStream inputStream = loadDocumentAsStream(templatePath); IXDocReport xdocReport = loadDocumentAsIDocxReport(inputStream,templateEngineKind); IContext context = replaceVariabalesInTemplateOtherThanImages(xdocReport,nonImageVariableMap); replaceImagesVariabalesInTemplate(xdocReport, imageVariablesWithPathMap, context); byte[] mergedOutput = generateMergedOutput(xdocReport, context); return mergedOutput; } } |
I tested the code using below test case:
/** * */ package test.com.sambhashanam.docx; import static org.junit.Assert.*; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; import com.sambhashanam.docx.DocxDocumentMergerAndConverter; import fr.opensagres.xdocreport.core.XDocReportException; import fr.opensagres.xdocreport.template.TemplateEngineKind; /** * @author Dhananjay Jha * */ public class DocxDocumentMergerAndConverterTest { /** * Test method for {@link com.sambhashanam.docx.DocxDocumentMergerAndConverter#mergeAndGenerateOutput(java.lang.String, fr.opensagres.xdocreport.template.TemplateEngineKind, java.util.Map, java.util.Map)}. * @throws XDocReportException * @throws IOException */ @Test public void testMergeAndGenerateOutput() throws IOException, XDocReportException { String templatePath = "D:\\junoprojects\\docxtopdf\\docx-template\\ThankYouNote_Template.docx"; Map<String, Object> nonImageVariableMap = new HashMap<String, Object>(); nonImageVariableMap.put("thank_you_date", "24-June-2013"); nonImageVariableMap.put("name", "Rajani Jha"); nonImageVariableMap.put("website", "www.sambhashanam.com"); nonImageVariableMap.put("author_name", "Dhananjay Jha"); Map<String, String> imageVariablesWithPathMap =new HashMap<String, String>(); imageVariablesWithPathMap.put("header_image_logo", "D:\\junoprojects\\docxtopdf\\docx-template\\ScreenShot004.jpg"); DocxDocumentMergerAndConverter docxDocumentMergerAndConverter = new DocxDocumentMergerAndConverter(); byte[] mergedOutput = docxDocumentMergerAndConverter.mergeAndGenerateOutput(templatePath, TemplateEngineKind.Freemarker, nonImageVariableMap, imageVariablesWithPathMap); assertNotNull(mergedOutput); FileOutputStream os = new FileOutputStream("D:\\junoprojects\\docxtopdf\\docx-template\\ThankYouNote"+System.nanoTime()+".docx"); os.write(mergedOutput); os.flush(); os.close(); } } |
I have used this ThankYouNote_Template MS word document as template. And the output was as this ThankYouNote13972222961298.
Here is the glimpse of template and final output
I will writing my next post soon – Merge Microsoft Word document and Convert to PDF document using without IText- Part II
Leave a Reply