All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.scriptlet4docx.docx.DocxTemplater Maven / Gradle / Ivy
package org.scriptlet4docx.docx;
import groovy.text.GStringTemplateEngine;
import groovy.util.AntBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.control.CompilationFailedException;
import org.scriptlet4docx.docx.Placeholder.PlaceholderType;
import org.scriptlet4docx.docx.Placeholder.ScriptWraps;
import org.scriptlet4docx.util.string.StringUtil;
import org.scriptlet4docx.util.xml.XMLUtils;
public class DocxTemplater {
static final String PATH_TO_CONTENT = "word/document.xml";
private File pathToDocx;
private InputStream templateStream;
private String streamTemplateKey;
/**
* Reads template content from file on file system.
* Note that with this constructor implicit template caching occurs.
* This mean if you change source template after first process() invocation,
* result document will not reflect your changes. Use different file names
* if you need no-cache behavior.
*
* @param pathToDocx
* path to docx template. Would be read only once with first
* process invocation.
*/
public DocxTemplater(File pathToDocx) {
this.pathToDocx = pathToDocx;
}
/**
* Reads template content from input stream.
* TemplateKey is used for perfomance and caching. DocxTemplater caches
* input stream content and associates it with given TemplateKey. When
* multiple process() invocations occur with same templateKey, only the 1st
* one will actually read stream content.
*
* @param inputStream
* template binary stream to read from
* @param templateKey
* unique identifier associated with given template. Should not
* contain special characters like '/' and be too long. This
* parameter is used for file system file path.
*/
public DocxTemplater(InputStream inputStream, String templateKey) {
this.templateStream = inputStream;
this.streamTemplateKey = templateKey;
}
private static Pattern scriptPattern = Pattern.compile("((<%=?(.*?)%>)|\\$\\{(.*?)\\})", Pattern.DOTALL
| Pattern.MULTILINE);
String cleanupTemplate(String template) {
template = DividedScriptWrapsProcessor.process(template);
template = TableScriptingProcessor.process(template);
return template;
}
String processCleanedTemplate(String template, Map params) throws CompilationFailedException,
ClassNotFoundException, IOException {
final String methodName = "processScriptedTemplate";
String replacement = UUID.randomUUID().toString();
List scripts = new ArrayList();
Matcher m = scriptPattern.matcher(template);
while (m.find()) {
String scriptText = m.group(0);
Placeholder ph = new Placeholder(UUID.randomUUID().toString(), scriptText, PlaceholderType.SCRIPT);
if (ph.scriptWrap == ScriptWraps.DOLLAR_PRINT) {
ph.setScriptTextNoWrap(m.group(4));
} else if (ph.scriptWrap == ScriptWraps.SCRIPLET || ph.scriptWrap == ScriptWraps.SCRIPLET_PRINT) {
ph.setScriptTextNoWrap(m.group(3));
}
scripts.add(ph);
}
String replacedScriptsTemplate = m.replaceAll(replacement);
List pieces = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(replacedScriptsTemplate,
replacement));
if (pieces.size() != scripts.size() + 1) {
throw new IllegalStateException(String.format(
"Programming bug was detected. Text pieces size does not match scripts size (%s, %s)."
+ " Please report this as a bug to the library author.", pieces.size(), scripts.size()));
}
List tplSkeleton = new ArrayList();
int i = 0;
for (String piece : pieces) {
tplSkeleton.add(new Placeholder(UUID.randomUUID().toString(), piece, PlaceholderType.TEXT));
if (i < scripts.size()) {
tplSkeleton.add(scripts.get(i));
}
i++;
}
StringBuilder builder = new StringBuilder();
for (Placeholder placeholder : tplSkeleton) {
if (PlaceholderType.SCRIPT == placeholder.type) {
String cleanScriptNoWrap = XMLUtils.getNoTagsTrimText(placeholder.getScriptTextNoWrap());
cleanScriptNoWrap = cleanScriptNoWrap.replace(">", ">");
cleanScriptNoWrap = cleanScriptNoWrap.replace("<", "<");
cleanScriptNoWrap = cleanScriptNoWrap.replace(""", "\"");
if (placeholder.scriptWrap == ScriptWraps.DOLLAR_PRINT
|| placeholder.scriptWrap == ScriptWraps.SCRIPLET_PRINT) {
cleanScriptNoWrap = NULL_REPLACER_REF + "(" + cleanScriptNoWrap + ")";
}
String script = placeholder.constructWithCurrentScriptWrap(cleanScriptNoWrap);
builder.append(script);
} else {
builder.append(placeholder.ph);
}
}
template = builder.toString();
params.put(UTIL_FUNC_HOLDER, this);
if (logger.isLoggable(Level.FINEST)) {
logger.logp(Level.FINEST, CLASS_NAME, methodName, String.format("\ntemplate = \n%s\n", template));
}
GStringTemplateEngine engine1 = new GStringTemplateEngine();
String scriptAppliedStr;
try {
scriptAppliedStr = String.valueOf(engine1.createTemplate(template).make(params));
} catch (Throwable e) {
logger.logp(Level.SEVERE, CLASS_NAME, methodName,
String.format("Cannot process template: [%s].", template), e);
throw new RuntimeException(e);
}
scriptAppliedStr = StringUtil.escapeSimpleSet(scriptAppliedStr);
String result = scriptAppliedStr;
for (Placeholder placeholder : tplSkeleton) {
if (PlaceholderType.TEXT == placeholder.type) {
result = StringUtils.replace(result, placeholder.ph, placeholder.text);
}
}
return result;
}
static String CLASS_NAME = DocxTemplater.class.getCanonicalName();
static Logger logger = Logger.getLogger(CLASS_NAME);
String setupTemplate() throws IOException {
String templateKey = null;
if (pathToDocx != null) {
// this is file-base usage
// TODO what if hash collision? A longer hash algorithm may be
// needed.
templateKey = pathToDocx.hashCode() + "-" + FilenameUtils.getBaseName(pathToDocx.getName());
if (!TemplateFileManager.getInstance().isPrepared(templateKey)) {
TemplateFileManager.getInstance().prepare(pathToDocx, templateKey);
}
} else {
// this is stream-based usage
try {
templateKey = streamTemplateKey;
if (!TemplateFileManager.getInstance().isTemplateFileFromStreamExists(templateKey)) {
TemplateFileManager.getInstance().saveTemplateFileFromStream(templateKey, templateStream);
TemplateFileManager.getInstance().prepare(
TemplateFileManager.getInstance().getTemplateFileFromStream(templateKey), templateKey);
}
} finally {
IOUtils.closeQuietly(templateStream);
}
}
return templateKey;
}
/**
* Process template with the given params and return output stream as
* result.
*/
public InputStream processAndReturnInputStream(Map params) {
File tmpResFile = TemplateFileManager.getInstance().getUniqueOutStreamFile();
process(tmpResFile, params);
try {
return new DeleteOnCloseFileInputStream(tmpResFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Process template with the given params and writes result as output
* stream.
* Note that stream will be closed automatically.
*/
public void process(OutputStream outputStream, Map params) {
try {
InputStream inputStream = null;
try {
inputStream = processAndReturnInputStream(params);
IOUtils.copy(inputStream, outputStream);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Process template with the given params and save result as a docx file.
*/
public void process(File destDocx, Map params) {
try {
String templateKey = setupTemplate();
String template = TemplateFileManager.getInstance().getTemplateContent(templateKey);
if (!TemplateFileManager.getInstance().isPreProcessedTemplateExists(templateKey)) {
template = cleanupTemplate(template);
TemplateFileManager.getInstance().savePreProcessed(templateKey, template);
}
String result = processCleanedTemplate(template, params);
processResult(destDocx, templateKey, result);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
void processResult(File destDocx, String templateKey, String result) throws IOException {
File tmpProcessFolder = TemplateFileManager.getInstance().createTmpProcessFolder();
destDocx.delete();
FileUtils.deleteDirectory(tmpProcessFolder);
FileUtils
.copyDirectory(TemplateFileManager.getInstance().getTemplateUnzipFolder(templateKey), tmpProcessFolder);
FileUtils.writeStringToFile(new File(tmpProcessFolder, PATH_TO_CONTENT), result, "UTF-8");
AntBuilder antBuilder = new AntBuilder();
HashMap params1 = new HashMap();
params1.put("destfile", destDocx);
params1.put("basedir", tmpProcessFolder);
params1.put("includes", "**/*.*");
params1.put("excludes", "");
params1.put("encoding", "UTF-8");
antBuilder.invokeMethod("zip", params1);
FileUtils.deleteDirectory(tmpProcessFolder);
}
/**
* Cleans up templater temporary folder.
* Normally should be called when application is about to end its execution.
*/
public static void cleanup() {
try {
TemplateFileManager.getInstance().cleanup();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
final static String UTIL_FUNC_HOLDER = "__docxTemplaterInstance";
final static String NULL_REPLACER_REF = UTIL_FUNC_HOLDER + ".replaceIfNull";
@SuppressWarnings("unused")
private String replaceIfNull(Object o) {
return o == null ? nullReplacement : String.valueOf(o);
}
private String nullReplacement = "";
/**
*
* @param nullReplacement
* When scriptlet output is null this value take it's place.
* Useful when you want nothing to be printed, or custom value
* like "UNKNOWN".
*/
public void setNullReplacement(String nullReplacement) {
this.nullReplacement = nullReplacement;
}
}