import com.documents4j.api.DocumentType;
import com.documents4j.api.IConverter;
import com.documents4j.job.LocalConverter;
import org.apache.commons.lang.text.StrSubstitutor;
import org.docx4j.XmlUtils;
import org.docx4j.model.datastorage.migration.VariablePrepare;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.utils.SingleTraversalUtilVisitorCallback;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.Tbl;
import org.docx4j.wml.Tr;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* docx4j utilization.
* <p> for creating template-ready docx files and converting docx to pdf. </p>
*
* @author Hamidreza Rashid.
* @version 0.5
*/
public class DocxUtil {
private Map<String, String> variableMap;
private final String TEMPLATE_FILE;
private ByteArrayOutputStream file;
private WordprocessingMLPackage wordMLPackage;
private MainDocumentPart documentPart;
private List<DocxTable> docxTables;
private DocxUtil(DocxBuilder builder) throws DocxUtilException {
this.TEMPLATE_FILE = builder.templatePath;
this.variableMap = builder.variableMap;
this.docxTables = builder.docxTables;
try {
loadWordMLPackage();
} catch (Docx4JException e) {
throw new DocxUtilException("Template File Not Found. " + e.getMessage());
}
}
private void loadWordMLPackage() throws Docx4JException {
wordMLPackage = WordprocessingMLPackage.load(new File(DocxUtil.class.getResource(TEMPLATE_FILE).getFile()));
documentPart = wordMLPackage.getMainDocumentPart();
}
public byte[] getDocxFile() {
return file.toByteArray();
}
public byte[] getPDF() {
return createPDF(file.toByteArray()).toByteArray();
}
private void create() throws DocxUtilException {
try {
VariablePrepare.prepare(wordMLPackage);
for (DocxTable docxTable : docxTables) {
Table table = new Table(docxTable);
table.createTable();
}
documentPart.variableReplace(variableMap);
file = new ByteArrayOutputStream();
wordMLPackage.save(file);
} catch (Exception e) {
throw new DocxUtilException("Cannot create file. " + e.getMessage());
}
}
private Object prepareVariables(Object body) {
new SingleTraversalUtilVisitorCallback(new VariablePrepare.TraversalUtilParagraphVisitor()).walkJAXBElements(body);
return body;
}
public static class DocxBuilder {
private Map<String, String> variableMap;
private String templatePath;
private List<DocxTable> docxTables;
DocxBuilder(String templatePath) {
this.templatePath = templatePath;
docxTables = new ArrayList<>();
}
public DocxBuilder setDocxTable(DocxTable docxTable) {
this.docxTables.add(docxTable);
return this;
}
public DocxBuilder setVariableMap(Map<String, String> variableMap) {
this.variableMap = variableMap;
return this;
}
public DocxUtil build() throws DocxUtilException {
DocxUtil docxUtil;
try {
docxUtil = new DocxUtil(this);
docxUtil.create();
return docxUtil;
} catch (DocxUtilException e) {
throw new DocxUtilException(e.getMessage());
}
}
}
public static class DocxUtilException extends Exception {
DocxUtilException(String message) {
super(message);
}
}
private class Table {
private final Tbl table;
private final String headerRowAsString;
private final String dataRow;
private final DocxTable docxTable;
public Table(DocxTable docxTable) {
this.docxTable = docxTable;
this.table = XmlUtils.deepCopy(((Tbl) getElements(documentPart, Tbl.class).get(0)));
List<Object> rows = getAllElementFromObject(this.table, Tr.class);
headerRowAsString = XmlUtils.marshaltoString(prepareVariables(rows.get(0)));
dataRow = XmlUtils.marshaltoString(prepareVariables(rows.get(rows.size() - 1)));
}
private void createTable() throws Exception {
removeTableByIndex(wordMLPackage, docxTable.getTableComponentIndex());
documentPart.getContent().add(fillWithData());
}
Tbl fillWithData() throws Exception {
Tbl localTable = XmlUtils.deepCopy(table);
localTable.getContent().clear();
localTable.getContent().add(getFilledHeaderRow());
for (String[] data : docxTable.getTableValue()) {
localTable.getContent().add(getFilledDataRow(data));
}
return localTable;
}
private Object getFilledDataRow(String[] data) throws JAXBException {
int index = 0;
Map<String, String> variableMap = new HashMap<>();
for (Map.Entry<String, String> stringStringEntry : docxTable.getHeaderTitles().entrySet()) {
if (docxTable.getTableRowIndexVariableName() != null && stringStringEntry.getKey().equals(docxTable.getTableRowIndexVariableName())) {
variableMap.put(stringStringEntry.getKey(), String.valueOf(index + 1));
} else {
variableMap.put(stringStringEntry.getKey(), data[index++]);
}
}
StrSubstitutor strSubstitutor = new StrSubstitutor(variableMap);
return XmlUtils.unmarshalString(strSubstitutor.replace(dataRow));
}
private Object getFilledHeaderRow() throws JAXBException {
StrSubstitutor strSubstitutor = new StrSubstitutor(docxTable.getHeaderTitles());
return XmlUtils.unmarshalString(strSubstitutor.replace(headerRowAsString));
}
private boolean removeTableByIndex(WordprocessingMLPackage wordMLPackage, int index) {
boolean flag = false;
if (index < 0) {
return flag;
}
List<Object> objList = wordMLPackage.getMainDocumentPart().getContent();
if (objList == null) {
return flag;
}
int k = -1;
for (int i = 0, len = objList.size(); i < len; i++) {
Object obj = XmlUtils.unwrap(objList.get(i));
if (obj instanceof Tbl) {
k++;
if (k == index) {
wordMLPackage.getMainDocumentPart().getContent().remove(i);
flag = true;
break;
}
}
}
return flag;
}
/**
* get elements from docx file based on type
*
* @param documentPart
* @param type class of type
* @return list of founded elements
*/
private List<Object> getElements(MainDocumentPart documentPart, Class<?> type) {
return getAllElementFromObject(documentPart, type);
}
private List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
List<Object> result = new ArrayList<>();
if (obj instanceof JAXBElement) obj = ((JAXBElement<?>) obj).getValue();
if (obj.getClass().equals(toSearch)) {
result.add(obj);
} else if (obj instanceof ContentAccessor) {
List<?> children = ((ContentAccessor) obj).getContent();
for (Object child : children) {
result.addAll(getAllElementFromObject(child, toSearch));
}
}
return result;
}
}
public static ByteArrayOutputStream convertToPDF(File docxFile){
return createPDF(docxFile);
}
private static ByteArrayOutputStream createPDF(File docxFile){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
IConverter converter = LocalConverter.builder().build();
converter.convert(docxFile).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
return outputStream;
}
private static ByteArrayOutputStream createPDF(byte[] docxFile){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
IConverter converter = LocalConverter.builder().build();
converter.convert(new ByteArrayInputStream(docxFile)).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
return outputStream;
}