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