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.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&lt;Object&gt;)</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
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:#22]
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

82

1.1
Location : lambda$new$1
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]
replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$1 → KILLED

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

84

1.1
Location : lambda$new$0
Killed by : pro.verron.officestamper.test.CustomFunctionTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.CustomFunctionTests]/[method:bifunctions()]
replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$0 → KILLED

86

1.1
Location : lambda$new$2
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:#30]
replaced return value with null for pro/verron/officestamper/core/DocxStamper::lambda$new$2 → KILLED

105

1.1
Location : buildCommentProcessors
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:#30]
replaced return value with null for pro/verron/officestamper/core/DocxStamper::buildCommentProcessors → KILLED

143

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

160

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:#4]
removed call to pro/verron/officestamper/core/DocxStamper::preprocess → KILLED

161

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

162

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:#30]
removed call to pro/verron/officestamper/core/DocxStamper::replaceExpressions → KILLED

163

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:#28]
removed call to org/docx4j/openpackaging/packages/WordprocessingMLPackage::save → KILLED

171

1.1
Location : preprocess
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:#4]
removed call to pro/verron/officestamper/api/PreProcessor::process → KILLED

177

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

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

178

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

180

1.1
Location : lambda$processComments$4
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

185

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

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

186

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:#30]
removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED

188

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

2.2
Location : lambda$replaceExpressions$6
Killed by : pro.verron.officestamper.test.HeaderAndFooterTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.HeaderAndFooterTest]/[method:placeholders()]
removed call to pro/verron/officestamper/core/PlaceholderReplacer::resolveExpressions → KILLED

193

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.1