The Docx-placeholders is a library which allows defining and filling placeholders in .docx template files. There are many cases when business requires a template to be filled with values from java. The approach allows usual .docx files using as templates where placeholders can be added and programmatically evaluated/filled.
A template .docx file containing "Hello {firstName} {lastName}" after evaluation converted to "Hello John Smith".
Content:
The simplest start is to define a template .docx document with placeholders like ${{firstName}} and ${{lastName}} and then evaluate (fill) the template with the following code.
//read template .docx
InputStream templateStream = getClass().getResourceAsStream("/some/resource/MyTemplate.docx");
//create a map with placeholders - key is placeholder name and value is appropriate value
Map<String, String> placeholdersValuesMap = new HashMap<String, String>() {{
put("firstName", "John");
put("lastName", "Smith");
}};
//Create context and set just one map tag processor
DocxTemplateFillerContext context = new DocxTemplateFillerContext();
context.setProcessors(Collections.singletonList(new MapTagProcessor(placeholdersValuesMap)));
//create target stream to store the filled template
ByteArrayOutputStream filledTemplateStream = new ByteArrayOutputStream();
DocxTemplateFiller filler = new DocxTemplateFiller();
//fill the template with the defined placeholders
filler.fillTemplate(templateStream, filledTemplateStream, context);
The tag processor is based on a Map<String, String>. Key of the Map is a tag name and value contains text to be used when the placeholder is filled. The code example is described in the Quick Start section. Developer defines a Map with tags to be filled and the values are placed in the template.
The simplest case described above is not enough in many cases. Let's consider a few alternative cases.
When there is a POJO (Plain Old Java Object) java object and we need the fields of the POJO to be used in a template another tag processors should be used - PojoFieldTagProcessor and PojoCollectionTagProcessor.
A template below shows tags "field" and "collection".
The POJO could be represented as the following JSON.
{
"companyName": "TestCompany",
"projects": [
{
"projectName": "Project One"
},
{
"projectName": "Project Two"
}
]
}
Here ${{collection:projects}} means reference to the POJO field named "projects".
To evaluate the template and fill declared placeholders tag the following code to be executed:
//read template .docx
InputStream templateStream = getClass().getResourceAsStream("/some/resource/MyTemplate.docx");
//create context and add two tag processors to handle the tags
DocxTemplateFillerContext context = new DocxTemplateFillerContext();
context.setProcessors(Arrays.asList(new PojoCollectionTagProcessor(), new PojoFieldTagProcessor()));
//place the POJO as a value root in the context
context.push(null, createPOJOexampleFromJSON());
//create target stream to store the filled template
ByteArrayOutputStream filledTemplateStream = new ByteArrayOutputStream();
DocxTemplateFiller filler = new DocxTemplateFiller();
//fill the template with the defined placeholders
filler.fillTemplate(templateStream, filledTemplateStream, context);
After evaluation the filled .docx template is following:
How it works?
Each tag is evaluated by own Tag Processor. List of tag processors are defined in the DocxTemplateFillerContext. For each detected tag the list is iterated to find the processor which can process the tag. In the described case one processor can process field tags and another one processes collection. The field processing tag gets tag value from the POJO object placed as the DocxTemplateFillerContext root value.
When the collection tag is met the collection processor does the following:
- Detects tag body - all the body elements - paragraphs and tables between open and close tag.
- Found the tag referenced collection in the DocxTemplateFillerContext value root.
- Starts iterating the collection
- Each collection item is placed to be the new value root.
- The tag body (filled in the step 1) is cloned including all the nested elements and theri styles, evaluated (tags filled) with the new local value root. (So the ${{field:projectName}} finds value in the local root - collection member).
- The evaluated tag body for the single collection item is inserted.
- After all the collection items are processed and all the evaluated copies of tag body are inserted the original elements are removed (including the tag start and end).
The same approach is applied to the nested tags (e.g. collection in a collection). Company has a projects collection and each project has a list of developers. Value root is pushed in a stack defined in the DocxTemplateFillerContext and restored after tag body evaluation.
NOTE: The DocxTemplateFillerContext works with a list of TagProcessors. For each found tag the list is iterated asking each TagProcessor whether it can process the found tag. The method of TagProcessor interface.
boolean canProcessTag(TagInfo tag);
If a processor returns yes filler passes the control to the processor to fill the tag.
Suppose there is a POJO User with nested POJO Address
public class UserDto {
private String firstName;
private String lastName;
private AddressDto address;
//getters and setters
}
public class AddressDto {
private String country;
private String city;
private String street;
//getters and setters
}
To show values of the nested Address POJO the ${{block:address}} tag. When the "block" tag is met the referenced field becomes the value root and all the inner "field" tags e.g. ${{field:country}} use the fields of new value root - Address.
So for the template
And after evaluation the filled template
There are some cases when just plain text is not enough. The cases when we need a link or image require more than just text. For such cases separate interfaces were defined: For links
public interface TagLinkData {
String getText();
String getUrl();
String getColor();
}
For images
public interface TagImageData {
String getTitle();
String getContentType();
InputStream getSourceStream();
Integer getWidth();
Integer getHeight();
}
If a link must be inserted in a template a tag "link" should be defined
Image interface includes more methods to define image size, content etc.
There are different tag open and close tokens. E.g. HTML and XML are supposed to use '<' and '>' chars. The library by default use '${{' and '}}' tokens but there is a way to customize them. The following code allows to set XML like tokens for tag start/end:
DocxTemplateFillerContext context = new DocxTemplateFillerContext();
context.setTagStart("<");
context.setTagEnd(">");
After that tags like "<field:firstName>" can be added to the templates to be evaluated.
To be defined.