1 | package pro.verron.officestamper.core; | |
2 | ||
3 | import org.docx4j.XmlUtils; | |
4 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
5 | import org.docx4j.wml.Comments; | |
6 | import org.docx4j.wml.P; | |
7 | import org.docx4j.wml.R; | |
8 | import org.slf4j.Logger; | |
9 | import org.slf4j.LoggerFactory; | |
10 | import org.springframework.expression.spel.SpelEvaluationException; | |
11 | import org.springframework.expression.spel.SpelParseException; | |
12 | import org.springframework.lang.Nullable; | |
13 | import pro.verron.officestamper.api.Comment; | |
14 | import pro.verron.officestamper.api.CommentProcessor; | |
15 | import pro.verron.officestamper.api.DocxPart; | |
16 | import pro.verron.officestamper.api.ExceptionResolver; | |
17 | ||
18 | import java.math.BigInteger; | |
19 | import java.util.*; | |
20 | ||
21 | import static pro.verron.officestamper.core.CommentCollectorWalker.collectComments; | |
22 | import static pro.verron.officestamper.core.CommentUtil.getCommentString; | |
23 | ||
24 | /** | |
25 | * Allows registration of {@link CommentProcessor} objects. Each registered | |
26 | * ICommentProcessor must implement an interface which has to be specified at | |
27 | * registration time. Provides several getter methods to access the registered | |
28 | * {@link CommentProcessor}. | |
29 | * | |
30 | * @author Joseph Verron | |
31 | * @author Tom Hombergs | |
32 | * @version ${version} | |
33 | * @since 1.0.0 | |
34 | */ | |
35 | public class CommentProcessorRegistry { | |
36 | ||
37 | private static final Logger logger = LoggerFactory.getLogger(CommentProcessorRegistry.class); | |
38 | private final DocxPart source; | |
39 | private final Map<Class<?>, ?> commentProcessors; | |
40 | private final ExpressionResolver expressionResolver; | |
41 | private final ExceptionResolver exceptionResolver; | |
42 | ||
43 | /** | |
44 | * Constructs a new CommentProcessorRegistry. | |
45 | * | |
46 | * @param source the source part of the Word document. | |
47 | * @param expressionResolver the resolver for evaluating expressions. | |
48 | * @param commentProcessors map of comment processor instances keyed by their respective class types. | |
49 | * @param exceptionResolver the resolver for handling exceptions during processing. | |
50 | */ | |
51 | public CommentProcessorRegistry( | |
52 | DocxPart source, | |
53 | ExpressionResolver expressionResolver, | |
54 | Map<Class<?>, ?> commentProcessors, | |
55 | ExceptionResolver exceptionResolver | |
56 | ) { | |
57 | this.source = source; | |
58 | this.expressionResolver = expressionResolver; | |
59 | this.commentProcessors = commentProcessors; | |
60 | this.exceptionResolver = exceptionResolver; | |
61 | } | |
62 | ||
63 | public <T> void runProcessors(T expressionContext) { | |
64 | var proceedComments = new ArrayList<Comment>(); | |
65 | ||
66 | source.streamParagraphs() | |
67 | .map(P::getContent) | |
68 | .flatMap(Collection::stream) | |
69 | .map(XmlUtils::unwrap) | |
70 | .filter(R.class::isInstance) | |
71 | .map(R.class::cast) | |
72 |
1
1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED |
.forEach(run -> { |
73 | var comments = collectComments(source); | |
74 | var runParent = (P) run.getParent(); | |
75 | var optional = runProcessorsOnRunComment(comments, expressionContext, run, runParent); | |
76 |
1
1. lambda$runProcessors$0 : negated conditional → KILLED |
if (optional.isPresent()) { |
77 | var comment = optional.get(); | |
78 | for (Object processor : commentProcessors.values()) { | |
79 | var commentProcessor = (CommentProcessor) processor; | |
80 |
1
1. lambda$runProcessors$0 : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → SURVIVED |
commentProcessor.commitChanges(source); |
81 |
1
1. lambda$runProcessors$0 : removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED |
commentProcessor.reset(); |
82 | } | |
83 | proceedComments.add(comment); | |
84 | } | |
85 | }); | |
86 | // we run the paragraph afterward so that the comments inside work before the whole paragraph comments | |
87 | source.streamParagraphs() | |
88 |
1
1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED |
.forEach(p -> { |
89 | var document = source.document(); | |
90 | var comments = collectComments(source); | |
91 | var optional = runProcessorsOnParagraphComment(document, comments, expressionContext, p); | |
92 |
1
1. lambda$runProcessors$1 : negated conditional → KILLED |
if (optional.isPresent()) { |
93 | for (Object processor : commentProcessors.values()) { | |
94 | var commentProcessor = (CommentProcessor) processor; | |
95 |
1
1. lambda$runProcessors$1 : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED |
commentProcessor.commitChanges(source); |
96 |
1
1. lambda$runProcessors$1 : removed call to pro/verron/officestamper/api/CommentProcessor::reset → KILLED |
commentProcessor.reset(); |
97 | } | |
98 | proceedComments.add(optional.get()); | |
99 | } | |
100 | }); | |
101 | source.streamParagraphs() | |
102 |
2
1. runProcessors : removed call to java/util/stream/Stream::forEach → KILLED 2. lambda$runProcessors$2 : removed call to pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnInlineContent → KILLED |
.forEach(paragraph -> runProcessorsOnInlineContent(expressionContext, paragraph)); |
103 | for (Comment comment : proceedComments) { | |
104 |
1
1. runProcessors : removed call to pro/verron/officestamper/core/CommentUtil::deleteComment → KILLED |
CommentUtil.deleteComment(comment); |
105 | } | |
106 | } | |
107 | ||
108 | private <T> Optional<Comment> runProcessorsOnRunComment( | |
109 | Map<BigInteger, Comment> comments, | |
110 | T expressionContext, | |
111 | R run, | |
112 | P paragraph | |
113 | ) { | |
114 |
1
1. runProcessorsOnRunComment : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnRunComment → SURVIVED |
return CommentUtil |
115 | .getCommentAround(run, source.document()) | |
116 |
1
1. lambda$runProcessorsOnRunComment$3 : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnRunComment$3 → SURVIVED |
.flatMap(c -> runCommentProcessors( |
117 | comments, | |
118 | expressionContext, | |
119 | c, | |
120 | paragraph, run | |
121 | )); | |
122 | } | |
123 | ||
124 | /** | |
125 | * Takes the first comment on the specified paragraph and tries to evaluate | |
126 | * the string within the comment against all registered | |
127 | * {@link CommentProcessor}s. | |
128 | * | |
129 | * @param document the Word document. | |
130 | * @param comments the comments within the document. | |
131 | * @param expressionContext the context root object | |
132 | * @param <T> the type of the context root object. | |
133 | */ | |
134 | private <T> Optional<Comment> runProcessorsOnParagraphComment( | |
135 | WordprocessingMLPackage document, | |
136 | Map<BigInteger, Comment> comments, | |
137 | T expressionContext, | |
138 | P paragraph | |
139 | ) { | |
140 |
1
1. runProcessorsOnParagraphComment : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runProcessorsOnParagraphComment → KILLED |
return CommentUtil |
141 | .getCommentFor(paragraph, document) | |
142 |
1
1. lambda$runProcessorsOnParagraphComment$4 : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::lambda$runProcessorsOnParagraphComment$4 → KILLED |
.flatMap(c -> runCommentProcessors( |
143 | comments, | |
144 | expressionContext, | |
145 | c, | |
146 | paragraph, | |
147 | null | |
148 | )); | |
149 | } | |
150 | ||
151 | /** | |
152 | * Finds all processor expressions within the specified paragraph and tries | |
153 | * to evaluate it against all registered {@link CommentProcessor}s. | |
154 | * | |
155 | * @param context the context root object against which evaluation is done | |
156 | * @param paragraph the paragraph to process. | |
157 | * @param <T> type of the context root object | |
158 | */ | |
159 | private <T> void runProcessorsOnInlineContent( | |
160 | T context, | |
161 | P paragraph | |
162 | ) { | |
163 | var paragraphWrapper = new StandardParagraph(paragraph); | |
164 | var text = paragraphWrapper.asString(); | |
165 | var placeholders = Placeholders.findProcessors(text); | |
166 | ||
167 | for (var placeholder : placeholders) { | |
168 | for (var processor : commentProcessors.values()) { | |
169 |
1
1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED |
((CommentProcessor) processor).setParagraph(paragraph); |
170 | } | |
171 | ||
172 | try { | |
173 |
1
1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED |
expressionResolver.setContext(context); |
174 | expressionResolver.resolve(placeholder); | |
175 |
1
1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/core/StandardParagraph::replace → KILLED |
paragraphWrapper.replace(placeholder, RunUtil.create("")); |
176 | logger.debug("Placeholder '{}' successfully processed by a comment processor.", placeholder); | |
177 | } catch (SpelEvaluationException | SpelParseException e) { | |
178 | var message = "Placeholder '%s' failed to process.".formatted(placeholder); | |
179 | exceptionResolver.resolve(placeholder, message, e); | |
180 | } | |
181 | for (var processor : commentProcessors.values()) { | |
182 |
1
1. runProcessorsOnInlineContent : removed call to pro/verron/officestamper/api/CommentProcessor::commitChanges → KILLED |
((CommentProcessor) processor).commitChanges(source); |
183 | } | |
184 | } | |
185 | } | |
186 | ||
187 | private <T> Optional<Comment> runCommentProcessors( | |
188 | Map<BigInteger, Comment> comments, | |
189 | T context, | |
190 | Comments.Comment comment, | |
191 | P paragraph, | |
192 | @Nullable R run | |
193 | ) { | |
194 | Comment commentWrapper = comments.get(comment.getId()); | |
195 | ||
196 |
1
1. runCommentProcessors : negated conditional → KILLED |
if (Objects.isNull(commentWrapper)) { |
197 | // no comment to process | |
198 | return Optional.empty(); | |
199 | } | |
200 | ||
201 | var placeholder = getCommentString(comment); | |
202 | ||
203 | for (final Object processor : commentProcessors.values()) { | |
204 |
1
1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setParagraph → KILLED |
((CommentProcessor) processor).setParagraph(paragraph); |
205 |
1
1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentRun → KILLED |
((CommentProcessor) processor).setCurrentRun(run); |
206 |
1
1. runCommentProcessors : removed call to pro/verron/officestamper/api/CommentProcessor::setCurrentCommentWrapper → KILLED |
((CommentProcessor) processor).setCurrentCommentWrapper(commentWrapper); |
207 | } | |
208 | ||
209 | try { | |
210 |
1
1. runCommentProcessors : removed call to pro/verron/officestamper/core/ExpressionResolver::setContext → KILLED |
expressionResolver.setContext(context); |
211 | expressionResolver.resolve(placeholder); | |
212 | comments.remove(comment.getId()); | |
213 | logger.debug("Comment '{}' successfully processed by a comment processor.", placeholder.expression()); | |
214 |
1
1. runCommentProcessors : replaced return value with Optional.empty for pro/verron/officestamper/core/CommentProcessorRegistry::runCommentProcessors → KILLED |
return Optional.of(commentWrapper); |
215 | } catch (SpelEvaluationException | SpelParseException e) { | |
216 | var message = "Comment '%s' failed to process.".formatted(placeholder.expression()); | |
217 | exceptionResolver.resolve(placeholder, message, e); | |
218 | return Optional.empty(); | |
219 | } | |
220 | } | |
221 | } | |
Mutations | ||
72 |
1.1 |
|
76 |
1.1 |
|
80 |
1.1 |
|
81 |
1.1 |
|
88 |
1.1 |
|
92 |
1.1 |
|
95 |
1.1 |
|
96 |
1.1 |
|
102 |
1.1 2.2 |
|
104 |
1.1 |
|
114 |
1.1 |
|
116 |
1.1 |
|
140 |
1.1 |
|
142 |
1.1 |
|
169 |
1.1 |
|
173 |
1.1 |
|
175 |
1.1 |
|
182 |
1.1 |
|
196 |
1.1 |
|
204 |
1.1 |
|
205 |
1.1 |
|
206 |
1.1 |
|
210 |
1.1 |
|
214 |
1.1 |