DocxStamper.java

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
/**
23
 * The DocxStamper class is an implementation of the {@link OfficeStamper}
24
 * interface that is used to stamp DOCX templates with a context object and
25
 * write the result to an output stream.
26
 *
27
 * @author Tom Hombergs
28
 * @author Joseph Verron
29
 * @version ${version}
30
 * @since 1.0.0
31
 */
32
public class DocxStamper
33
        implements OfficeStamper<WordprocessingMLPackage> {
34
35
    private final List<PreProcessor> preprocessors;
36
    private final PlaceholderReplacer placeholderReplacer;
37
    private final Function<DocxPart, CommentProcessorRegistry> commentProcessorRegistrySupplier;
38
39
    /**
40
     * Creates a new DocxStamper with the given configuration.
41
     *
42
     * @param configuration the configuration to use for this DocxStamper.
43
     */
44
    public DocxStamper(OfficeStamperConfiguration configuration) {
45
        this(
46
                configuration.getLineBreakPlaceholder(),
47
                configuration.getEvaluationContextConfigurer(),
48
                configuration.getExpressionFunctions(),
49
                configuration.getResolvers(),
50
                configuration.getCommentProcessors(),
51
                configuration.getPreprocessors(),
52
                configuration.getSpelParserConfiguration(),
53
                configuration.getExceptionResolver());
54
    }
55
56
    private DocxStamper(
57
            @NonNull String lineBreakPlaceholder,
58
            EvaluationContextConfigurer evaluationContextConfigurer,
59
            Map<Class<?>, Object> expressionFunctions,
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
83 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(source,
84
                expressionResolver,
85
                commentProcessors,
86
                exceptionResolver);
87
88
        this.preprocessors = new ArrayList<>(preprocessors);
89
    }
90
91
    private Map<Class<?>, CommentProcessor> buildCommentProcessors(Map<Class<?>,
92
            Function<ParagraphPlaceholderReplacer, CommentProcessor>> commentProcessors) {
93
        var processors = new HashMap<Class<?>, CommentProcessor>();
94
        for (var entry : commentProcessors.entrySet()) {
95
            processors.put(
96
                    entry.getKey(),
97
                    entry.getValue()
98
                         .apply(placeholderReplacer));
99
        }
100 1 1. buildCommentProcessors : replaced return value with Collections.emptyMap for pro/verron/officestamper/core/DocxStamper::buildCommentProcessors → KILLED
        return processors;
101
    }
102
103
    /**
104
     * {@inheritDoc}
105
     *
106
     * <p>
107
     * Reads in a .docx template and "stamps" it into the given OutputStream, using the specified context object to
108
     * fill out any expressions it finds.
109
     * </p>
110
     * <p>
111
     * In the .docx template you have the following options to influence the "stamping" process:
112
     * </p>
113
     * <ul>
114
     * <li>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.</li>
117
     * <li>Use comments within the .docx template to mark certain paragraphs to be manipulated. </li>
118
     * </ul>
119
     * <p>
120
     * Within comments, you can put expressions in which you can use the following methods by default:
121
     * </p>
122
     * <ul>
123
     * <li><em>displayParagraphIf(boolean)</em> to conditionally display paragraphs or not</li>
124
     * <li><em>displayTableRowIf(boolean)</em> to conditionally display table rows or not</li>
125
     * <li><em>displayTableIf(boolean)</em> to conditionally display whole tables or not</li>
126
     * <li><em>repeatTableRow(List&lt;Object&gt;)</em> to create a new table row for each object in the list and
127
     * resolve expressions
128
     * within the table cells against one of the objects within the list.</li>
129
     * </ul>
130
     * <p>
131
     * If you need a wider vocabulary of methods available in the comments, you can create your own ICommentProcessor
132
     * and register it via {@link OfficeStamperConfiguration#addCommentProcessor(Class, Function)}.
133
     * </p>
134
     */
135
    public void stamp(
136
            InputStream template, Object contextRoot, OutputStream out
137
    ) {
138
        try {
139
            WordprocessingMLPackage document = WordprocessingMLPackage.load(template);
140 1 1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::stamp → NO_COVERAGE
            stamp(document, contextRoot, out);
141
        } catch (Docx4JException e) {
142
            throw new OfficeStamperException(e);
143
        }
144
    }
145
146
147
    /**
148
     * {@inheritDoc}
149
     * <p>
150
     * Same as {@link #stamp(InputStream, Object, OutputStream)} except that you
151
     * may pass in a DOCX4J document as a template instead
152
     * of an InputStream.
153
     */
154
    @Override public void stamp(
155
            WordprocessingMLPackage document, Object contextRoot, OutputStream out
156
    ) {
157
        try {
158
            var source = new TextualDocxPart(document);
159 1 1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::preprocess → SURVIVED
            preprocess(document);
160 1 1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::processComments → KILLED
            processComments(source, contextRoot);
161 1 1. stamp : removed call to pro/verron/officestamper/core/DocxStamper::replaceExpressions → KILLED
            replaceExpressions(source, contextRoot);
162 1 1. stamp : removed call to org/docx4j/openpackaging/packages/WordprocessingMLPackage::save → KILLED
            document.save(out);
163
        } catch (Docx4JException e) {
164
            throw new OfficeStamperException(e);
165
        }
166
    }
167
168
    private void preprocess(WordprocessingMLPackage document) {
169
        for (PreProcessor preprocessor : preprocessors) {
170 1 1. preprocess : removed call to pro/verron/officestamper/api/PreProcessor::process → SURVIVED
            preprocessor.process(document);
171
        }
172
    }
173
174
    private void processComments(
175
            DocxPart document,
176
            Object contextObject
177
    ) {
178
        document.streamParts(Namespaces.HEADER)
179 2 1. processComments : removed call to java/util/stream/Stream::forEach → SURVIVED
2. lambda$processComments$1 : removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → SURVIVED
                .forEach(header -> runProcessors(header, contextObject));
180
181 1 1. processComments : removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → KILLED
        runProcessors(document, contextObject);
182
183
        document.streamParts(Namespaces.FOOTER)
184 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(footer -> runProcessors(footer, contextObject));
185
    }
186
187
    private void replaceExpressions(
188
            DocxPart document,
189
            Object contextObject
190
    ) {
191
        document.streamParts(Namespaces.HEADER)
192 2 1. lambda$replaceExpressions$3 : 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));
193 1 1. replaceExpressions : removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED
        placeholderReplacer.resolveExpressions(document, contextObject);
194
        document.streamParts(Namespaces.FOOTER)
195 2 1. lambda$replaceExpressions$4 : 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));
196
    }
197
198
    private void runProcessors(DocxPart source, Object contextObject) {
199
        var processors = commentProcessorRegistrySupplier.apply(source);
200 1 1. runProcessors : removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessors → KILLED
        processors.runProcessors(contextObject);
201
    }
202
}

Mutations

69

1.1
Location : <init>
Killed by : none
removed call to pro/verron/officestamper/api/EvaluationContextConfigurer::configureEvaluationContext → RUN_ERROR

80

1.1
Location : <init>
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
removed call to org/springframework/expression/spel/support/StandardEvaluationContext::addMethodResolver → KILLED

81

1.1
Location : <init>
Killed by : pro.verron.officestamper.test.RegressionTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.RegressionTests]/[method:test64()]
removed call to org/springframework/expression/spel/support/StandardEvaluationContext::addMethodResolver → KILLED

83

1.1
Location : lambda$new$0
Killed by : pro.verron.officestamper.test.ResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ResolutionTest]/[test-template:testCustomResolution(java.lang.String, boolean, boolean, boolean, boolean, boolean, java.lang.String, boolean, java.lang.String)]/[test-template-invocation:#17]
replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$0 → KILLED

100

1.1
Location : buildCommentProcessors
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#21]
replaced return value with Collections.emptyMap for pro/verron/officestamper/core/DocxStamper::buildCommentProcessors → KILLED

140

1.1
Location : stamp
Killed by : none
removed call to pro/verron/officestamper/core/DocxStamper::stamp → NO_COVERAGE

159

1.1
Location : stamp
Killed by : none
removed call to pro/verron/officestamper/core/DocxStamper::preprocess → SURVIVED
Covering tests

160

1.1
Location : stamp
Killed by : pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest]/[method:fails()]
removed call to pro/verron/officestamper/core/DocxStamper::processComments → KILLED

161

1.1
Location : stamp
Killed by : pro.verron.officestamper.test.ResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ResolutionTest]/[test-template:testCustomResolution(java.lang.String, boolean, boolean, boolean, boolean, boolean, java.lang.String, boolean, java.lang.String)]/[test-template-invocation:#17]
removed call to pro/verron/officestamper/core/DocxStamper::replaceExpressions → KILLED

162

1.1
Location : stamp
Killed by : pro.verron.officestamper.test.DefaultTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.DefaultTests]/[test-template:features(java.lang.String, pro.verron.officestamper.api.OfficeStamperConfiguration, java.lang.Object, java.io.InputStream, java.lang.String)]/[test-template-invocation:#34]
removed call to org/docx4j/openpackaging/packages/WordprocessingMLPackage::save → KILLED

170

1.1
Location : preprocess
Killed by : none
removed call to pro/verron/officestamper/api/PreProcessor::process → SURVIVED
Covering tests

179

1.1
Location : processComments
Killed by : none
removed call to java/util/stream/Stream::forEach → SURVIVED
Covering tests

2.2
Location : lambda$processComments$1
Killed by : none
removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → SURVIVED Covering tests

181

1.1
Location : processComments
Killed by : pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest]/[method:fails()]
removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → KILLED

184

1.1
Location : lambda$processComments$2
Killed by : none
removed call to pro/verron/officestamper/core/DocxStamper::runProcessors → SURVIVED
Covering tests

2.2
Location : processComments
Killed by : none
removed call to java/util/stream/Stream::forEach → SURVIVED Covering tests

192

1.1
Location : lambda$replaceExpressions$3
Killed by : pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest]/[method:expressionReplacementInHeaderAndFooterTest()]
removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED

2.2
Location : replaceExpressions
Killed by : pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest]/[method:expressionReplacementInHeaderAndFooterTest()]
removed call to java/util/stream/Stream::forEach → KILLED

193

1.1
Location : replaceExpressions
Killed by : pro.verron.officestamper.test.ResolutionTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ResolutionTest]/[test-template:testCustomResolution(java.lang.String, boolean, boolean, boolean, boolean, boolean, java.lang.String, boolean, java.lang.String)]/[test-template-invocation:#17]
removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED

195

1.1
Location : lambda$replaceExpressions$4
Killed by : pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest]/[method:expressionReplacementInHeaderAndFooterTest()]
removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED

2.2
Location : replaceExpressions
Killed by : pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.PlaceholderReplacementInHeaderAndFooterTest]/[method:expressionReplacementInHeaderAndFooterTest()]
removed call to java/util/stream/Stream::forEach → KILLED

200

1.1
Location : runProcessors
Killed by : pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.FailOnUnresolvedPlaceholderTest]/[method:fails()]
removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessors → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0