DocxStamper.java
package pro.verron.officestamper.core;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.Part;
import org.docx4j.wml.ContentAccessor;
import pro.verron.officestamper.api.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.docx4j.openpackaging.parts.relationships.Namespaces.FOOTER;
import static org.docx4j.openpackaging.parts.relationships.Namespaces.HEADER;
/// The [DocxStamper] class is an implementation of the [OfficeStamper] interface used to stamp DOCX templates with a
/// context object and write the result to an output stream.
///
/// @author Tom Hombergs
/// @author Joseph Verron
/// @version ${version}
/// @since 1.0.0
public class DocxStamper
implements OfficeStamper<WordprocessingMLPackage> {
private final List<PreProcessor> preprocessors;
private final List<PostProcessor> postprocessors;
private final EngineFactory engineFactory;
private final EvaluationContextFactory contextFactory;
private final Map<Class<?>, Object> interfaceFunctions;
private final List<CustomFunction> customFunctions;
private final Map<Class<?>, CommentProcessorFactory> commentProcessors;
/// Creates new [DocxStamper] with the given configuration.
///
/// @param configuration the configuration to use for this [DocxStamper].
public DocxStamper(OfficeStamperConfiguration configuration) {
this.contextFactory = configuration.getEvaluationContextFactory();
this.interfaceFunctions = configuration.getExpressionFunctions();
this.customFunctions = configuration.customFunctions();
this.commentProcessors = configuration.getCommentProcessors();
this.engineFactory = processorContext -> {
var expressionParser = configuration.getExpressionParser();
var exceptionResolver = configuration.getExceptionResolver();
var resolvers = configuration.getResolvers();
var registry = new ObjectResolverRegistry(resolvers);
return new Engine(expressionParser, exceptionResolver, registry, processorContext);
};
this.preprocessors = new ArrayList<>(configuration.getPreprocessors());
this.postprocessors = new ArrayList<>(configuration.getPostprocessors());
}
/// Reads in a .docx template and "stamps" it, using the specified context object to
/// fill out any expressions it finds.
///
/// In the .docx template you have the following options to influence the "stamping" process:
/// - Use expressions like `${name}` or `${person.isOlderThan(18)}` in the template's text. These expressions are
/// resolved against the contextRoot object you pass into this method and are replaced by the results.
/// - Use comments within the .docx template to mark certain paragraphs to be manipulated.
///
/// Within comments, you can put expressions in which you can use the following methods by default:
/// - `displayParagraphIf(boolean)` to conditionally display paragraphs or not
/// - `displayTableRowIf(boolean)` to conditionally display table rows or not
/// - `displayTableIf(boolean)` to conditionally display whole tables or not
/// - `repeatTableRow(List<Object>)` to create a new table row for each object in the list and resolve expressions
/// within the table cells against one of the objects within the list.
///
/// If you need a wider vocabulary of methods available in the comments, you can create your own [CommentProcessor]
/// and register it via [OfficeStamperConfiguration#addCommentProcessor(Class, CommentProcessorFactory)].
///
/// @param document the .docx template to stamp
/// @param contextRoot the context object to use for stamping
///
/// @return the stamped document
@Override
public WordprocessingMLPackage stamp(WordprocessingMLPackage document, Object contextRoot) {
preprocess(document);
process(document, contextRoot);
postprocess(document);
return document;
}
private void preprocess(WordprocessingMLPackage document) {
preprocessors.forEach(processor -> processor.process(document));
}
private void process(WordprocessingMLPackage document, Object contextRoot) {
var mainDocumentPart = document.getMainDocumentPart();
var mainPart = new TextualDocxPart(document, mainDocumentPart, mainDocumentPart);
process(mainPart, contextRoot);
var relationshipsPart = mainDocumentPart.getRelationshipsPart();
for (var relationship : relationshipsPart.getRelationshipsByType(HEADER)) {
Part part1 = relationshipsPart.getPart(relationship);
TextualDocxPart textualDocxPart = new TextualDocxPart(document, part1, (ContentAccessor) part1);
process(textualDocxPart, contextRoot);
}
for (var relationship : relationshipsPart.getRelationshipsByType(FOOTER)) {
Part part = relationshipsPart.getPart(relationship);
TextualDocxPart textualDocxPart = new TextualDocxPart(document, part, (ContentAccessor) part);
process(textualDocxPart, contextRoot);
}
}
private void postprocess(WordprocessingMLPackage document) {
postprocessors.forEach(processor -> processor.process(document));
}
private void process(DocxPart part, Object contextRoot) {
var contextTree = new ContextRoot(contextRoot);
var iterator = DocxHook.ofHooks(part::content, part);
while (iterator.hasNext()) {
var hook = iterator.next();
var officeStamperContextFactory = new OfficeStamperEvaluationContextFactory(customFunctions,
commentProcessors,
interfaceFunctions,
contextFactory);
if (hook.run(engineFactory, contextTree, officeStamperContextFactory)) {
iterator.reset();
}
}
}
}