1 | package pro.verron.officestamper.core; | |
2 | ||
3 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
4 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
5 | import org.docx4j.openpackaging.parts.relationships.Namespaces; | |
6 | import org.springframework.expression.TypedValue; | |
7 | import org.springframework.expression.spel.SpelParserConfiguration; | |
8 | import org.springframework.expression.spel.standard.SpelExpressionParser; | |
9 | import org.springframework.expression.spel.support.StandardEvaluationContext; | |
10 | import org.springframework.lang.NonNull; | |
11 | import pro.verron.officestamper.api.*; | |
12 | ||
13 | import java.io.InputStream; | |
14 | import java.io.OutputStream; | |
15 | import java.util.*; | |
16 | import java.util.function.Function; | |
17 | ||
18 | import static pro.verron.officestamper.core.Invokers.streamInvokers; | |
19 | ||
20 | /** | |
21 | * The DocxStamper class is an implementation of the {@link OfficeStamper} | |
22 | * interface that is used to stamp DOCX templates with a context object and | |
23 | * write the result to an output stream. | |
24 | * | |
25 | * @author Tom Hombergs | |
26 | * @author Joseph Verron | |
27 | * @version ${version} | |
28 | * @since 1.0.0 | |
29 | */ | |
30 | public class DocxStamper | |
31 | implements OfficeStamper<WordprocessingMLPackage> { | |
32 | ||
33 | private final List<PreProcessor> preprocessors; | |
34 | private final PlaceholderReplacer placeholderReplacer; | |
35 | private final Function<DocxPart, CommentProcessorRegistry> commentProcessorRegistrySupplier; | |
36 | ||
37 | /** | |
38 | * Creates a new DocxStamper with the given configuration. | |
39 | * | |
40 | * @param configuration the configuration to use for this DocxStamper. | |
41 | */ | |
42 | public DocxStamper(OfficeStamperConfiguration configuration) { | |
43 | this( | |
44 | configuration.getLineBreakPlaceholder(), | |
45 | configuration.getEvaluationContextConfigurer(), | |
46 | configuration.getExpressionFunctions(), | |
47 | configuration.customFunctions(), | |
48 | configuration.getResolvers(), | |
49 | configuration.getCommentProcessors(), | |
50 | configuration.getPreprocessors(), | |
51 | configuration.getSpelParserConfiguration(), | |
52 | configuration.getExceptionResolver()); | |
53 | } | |
54 | ||
55 | private DocxStamper( | |
56 | @NonNull String lineBreakPlaceholder, | |
57 | EvaluationContextConfigurer evaluationContextConfigurer, | |
58 | Map<Class<?>, Object> expressionFunctions, | |
59 | List<CustomFunction> functions, | |
60 | List<ObjectResolver> resolvers, | |
61 | Map<Class<?>, Function<ParagraphPlaceholderReplacer, CommentProcessor>> configurationCommentProcessors, | |
62 | List<PreProcessor> preprocessors, | |
63 | SpelParserConfiguration spelParserConfiguration, | |
64 | ExceptionResolver exceptionResolver | |
65 | ) { | |
66 | var expressionParser = new SpelExpressionParser(spelParserConfiguration); | |
67 | ||
68 | var evaluationContext = new StandardEvaluationContext(); | |
69 |
1
1. <init> : removed call to pro/verron/officestamper/api/EvaluationContextConfigurer::configureEvaluationContext → RUN_ERROR |
evaluationContextConfigurer.configureEvaluationContext(evaluationContext); |
70 | ||
71 | var expressionResolver = new ExpressionResolver(evaluationContext, expressionParser); | |
72 | var typeResolverRegistry = new ObjectResolverRegistry(resolvers); | |
73 | this.placeholderReplacer = new PlaceholderReplacer( | |
74 | typeResolverRegistry, | |
75 | expressionResolver, | |
76 | Placeholders.raw(lineBreakPlaceholder), | |
77 | exceptionResolver); | |
78 | ||
79 | var commentProcessors = buildCommentProcessors(configurationCommentProcessors); | |
80 |
1
1. <init> : removed call to org/springframework/expression/spel/support/StandardEvaluationContext::addMethodResolver → KILLED |
evaluationContext.addMethodResolver(new Invokers(streamInvokers(commentProcessors))); |
81 |
1
1. <init> : removed call to org/springframework/expression/spel/support/StandardEvaluationContext::addMethodResolver → KILLED |
evaluationContext.addMethodResolver(new Invokers(streamInvokers(expressionFunctions))); |
82 |
2
1. lambda$new$1 : replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$1 → KILLED 2. <init> : removed call to org/springframework/expression/spel/support/StandardEvaluationContext::addMethodResolver → KILLED |
evaluationContext.addMethodResolver(new Invokers(functions.stream().map(cf -> new Invoker(cf.name(), |
83 | new Invokers.Args(cf.parameterTypes()), | |
84 |
1
1. lambda$new$0 : replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$0 → KILLED |
(context, target, arguments) -> new TypedValue(cf.function().apply(Arrays.asList(arguments))))))); |
85 | ||
86 |
1
1. lambda$new$2 : replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$2 → KILLED |
this.commentProcessorRegistrySupplier = source -> new CommentProcessorRegistry( |
87 | source, | |
88 | expressionResolver, | |
89 | commentProcessors, | |
90 | exceptionResolver); | |
91 | ||
92 | this.preprocessors = new ArrayList<>(preprocessors); | |
93 | } | |
94 | ||
95 | private CommentProcessors buildCommentProcessors( | |
96 | Map<Class<?>, Function<ParagraphPlaceholderReplacer, CommentProcessor>> commentProcessors | |
97 | ) { | |
98 | var processors = new HashMap<Class<?>, CommentProcessor>(); | |
99 | for (var entry : commentProcessors.entrySet()) { | |
100 | processors.put( | |
101 | entry.getKey(), | |
102 | entry.getValue() | |
103 | .apply(placeholderReplacer)); | |
104 | } | |
105 |
1
1. buildCommentProcessors : replaced return value with null for pro/verron/officestamper/core/DocxStamper::buildCommentProcessors → KILLED |
return new CommentProcessors(processors); |
106 | } | |
107 | ||
108 | /** | |
109 | * {@inheritDoc} | |
110 | * | |
111 | * <p> | |
112 | * Reads in a .docx template and "stamps" it into the given OutputStream, using the specified context object to | |
113 | * fill out any expressions it finds. | |
114 | * </p> | |
115 | * <p> | |
116 | * In the .docx template you have the following options to influence the "stamping" process: | |
117 | * </p> | |
118 | * <ul> | |
119 | * <li>Use expressions like ${name} or ${person.isOlderThan(18)} in the template's text. These expressions are | |
120 | * resolved | |
121 | * against the contextRoot object you pass into this method and are replaced by the results.</li> | |
122 | * <li>Use comments within the .docx template to mark certain paragraphs to be manipulated. </li> | |
123 | * </ul> | |
124 | * <p> | |
125 | * Within comments, you can put expressions in which you can use the following methods by default: | |
126 | * </p> | |
127 | * <ul> | |
128 | * <li><em>displayParagraphIf(boolean)</em> to conditionally display paragraphs or not</li> | |
129 | * <li><em>displayTableRowIf(boolean)</em> to conditionally display table rows or not</li> | |
130 | * <li><em>displayTableIf(boolean)</em> to conditionally display whole tables or not</li> | |
131 | * <li><em>repeatTableRow(List<Object>)</em> to create a new table row for each object in the list and | |
132 | * resolve expressions | |
133 | * within the table cells against one of the objects within the list.</li> | |
134 | * </ul> | |
135 | * <p> | |
136 | * If you need a wider vocabulary of methods available in the comments, you can create your own ICommentProcessor | |
137 | * and register it via {@link OfficeStamperConfiguration#addCommentProcessor(Class, Function)}. | |
138 | * </p> | |
139 | */ | |
140 | public void stamp(InputStream template, Object contextRoot, OutputStream out) { | |
141 | try { | |
142 | WordprocessingMLPackage document = WordprocessingMLPackage.load(template); | |
143 |
1
1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::stamp → NO_COVERAGE |
stamp(document, contextRoot, out); |
144 | } catch (Docx4JException e) { | |
145 | throw new OfficeStamperException(e); | |
146 | } | |
147 | } | |
148 | ||
149 | ||
150 | /** | |
151 | * {@inheritDoc} | |
152 | * <p> | |
153 | * Same as {@link #stamp(InputStream, Object, OutputStream)} except that you | |
154 | * may pass in a DOCX4J document as a template instead | |
155 | * of an InputStream. | |
156 | */ | |
157 | @Override public void stamp(WordprocessingMLPackage document, Object contextRoot, OutputStream out) { | |
158 | try { | |
159 | var source = new TextualDocxPart(document); | |
160 |
1
1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::preprocess → KILLED |
preprocess(document); |
161 |
1
1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::processComments → KILLED |
processComments(source, contextRoot); |
162 |
1
1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::replaceExpressions → KILLED |
replaceExpressions(source, contextRoot); |
163 |
1
1. stamp : removed call to org/docx4j/openpackaging/packages/WordprocessingMLPackage::save → KILLED |
document.save(out); |
164 | } catch (Docx4JException e) { | |
165 | throw new OfficeStamperException(e); | |
166 | } | |
167 | } | |
168 | ||
169 | private void preprocess(WordprocessingMLPackage document) { | |
170 | for (PreProcessor preprocessor : preprocessors) { | |
171 |
1
1. preprocess : removed call to pro/verron/officestamper/api/PreProcessor::process → KILLED |
preprocessor.process(document); |
172 | } | |
173 | } | |
174 | ||
175 | private void processComments(DocxPart document, Object contextObject) { | |
176 | document.streamParts(Namespaces.HEADER) | |
177 |
2
1. processComments : removed call to java/util/stream/Stream::forEach → SURVIVED 2. lambda$processComments$3 : removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → SURVIVED |
.forEach(header -> runProcessors(header, contextObject)); |
178 |
1
1. processComments : removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → KILLED |
runProcessors(document, contextObject); |
179 | document.streamParts(Namespaces.FOOTER) | |
180 |
2
1. lambda$processComments$4 : removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → SURVIVED 2. processComments : removed call to java/util/stream/Stream::forEach → SURVIVED |
.forEach(footer -> runProcessors(footer, contextObject)); |
181 | } | |
182 | ||
183 | private void replaceExpressions(DocxPart document, Object contextObject) { | |
184 | document.streamParts(Namespaces.HEADER) | |
185 |
2
1. lambda$replaceExpressions$5 : removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED 2. replaceExpressions : removed call to java/util/stream/Stream::forEach → KILLED |
.forEach(s -> placeholderReplacer.resolveExpressions(s, contextObject)); |
186 |
1
1. replaceExpressions : removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED |
placeholderReplacer.resolveExpressions(document, contextObject); |
187 | document.streamParts(Namespaces.FOOTER) | |
188 |
2
1. replaceExpressions : removed call to java/util/stream/Stream::forEach → KILLED 2. lambda$replaceExpressions$6 : removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED |
.forEach(s -> placeholderReplacer.resolveExpressions(s, contextObject)); |
189 | } | |
190 | ||
191 | private void runProcessors(DocxPart source, Object contextObject) { | |
192 | var processors = commentProcessorRegistrySupplier.apply(source); | |
193 |
1
1. runProcessors : removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessors → KILLED |
processors.runProcessors(contextObject); |
194 | } | |
195 | } | |
Mutations | ||
69 |
1.1 |
|
80 |
1.1 |
|
81 |
1.1 |
|
82 |
1.1 2.2 |
|
84 |
1.1 |
|
86 |
1.1 |
|
105 |
1.1 |
|
143 |
1.1 |
|
160 |
1.1 |
|
161 |
1.1 |
|
162 |
1.1 |
|
163 |
1.1 |
|
171 |
1.1 |
|
177 |
1.1 2.2 |
|
178 |
1.1 |
|
180 |
1.1 2.2 |
|
185 |
1.1 2.2 |
|
186 |
1.1 |
|
188 |
1.1 2.2 |
|
193 |
1.1 |