WmlUtils.java

1
package pro.verron.officestamper.utils.wml;
2
3
import jakarta.xml.bind.JAXBElement;
4
import org.docx4j.TraversalUtil;
5
import org.docx4j.finders.CommentFinder;
6
import org.docx4j.model.structure.HeaderFooterPolicy;
7
import org.docx4j.model.structure.SectionWrapper;
8
import org.docx4j.model.styles.StyleUtil;
9
import org.docx4j.openpackaging.exceptions.Docx4JException;
10
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
11
import org.docx4j.openpackaging.parts.JaxbXmlPart;
12
import org.docx4j.openpackaging.parts.WordprocessingML.CommentsPart;
13
import org.docx4j.utils.TraversalUtilVisitor;
14
import org.docx4j.vml.CTShadow;
15
import org.docx4j.vml.CTTextbox;
16
import org.docx4j.vml.VmlShapeElements;
17
import org.docx4j.wml.*;
18
import org.docx4j.wml.Comments.Comment;
19
import org.jspecify.annotations.Nullable;
20
import org.jvnet.jaxb2_commons.ppp.Child;
21
import org.slf4j.Logger;
22
import org.slf4j.LoggerFactory;
23
import pro.verron.officestamper.utils.UtilsException;
24
import pro.verron.officestamper.utils.openpackaging.OpenpackagingFactory;
25
26
import java.math.BigInteger;
27
import java.util.*;
28
import java.util.function.Consumer;
29
import java.util.function.Predicate;
30
import java.util.stream.Stream;
31
32
import static java.util.Collections.emptyList;
33
import static java.util.Optional.ofNullable;
34
import static java.util.stream.Collectors.joining;
35
import static org.docx4j.XmlUtils.unwrap;
36
import static pro.verron.officestamper.utils.wml.WmlFactory.*;
37
38
/// Utility class with methods to help in the interaction with [WordprocessingMLPackage] documents and their elements,
39
/// such as comments, parents, and child elements.
40
public final class WmlUtils {
41
42
    private static final String PRESERVE = "preserve";
43
    private static final Logger log = LoggerFactory.getLogger(WmlUtils.class);
44
45
    private WmlUtils() {
46
        throw new UtilsException("Utility class shouldn't be instantiated");
47
    }
48
49
    /// Attempts to find the first parent of a given child element that is an instance of the specified class within the
50
    /// defined search depth.
51
    ///
52
    /// @param child the [Child] element from which the search for a parent begins.
53
    /// @param clazz the [Class] type to match for the parent
54
    /// @param depth the maximum amount levels to traverse up the parent hierarchy
55
    /// @param <T> the type of the parent class to search for
56
    ///
57
    /// @return an [Optional] containing the first parent matching the specified class, or an empty [Optional] if no
58
    ///         match found.
59
    public static <T> Optional<T> getFirstParentWithClass(Child child, Class<T> clazz, int depth) {
60
        var parent = child.getParent();
61
        var currentDepth = 0;
62 2 1. getFirstParentWithClass : changed conditional boundary → NO_COVERAGE
2. getFirstParentWithClass : negated conditional → NO_COVERAGE
        while (currentDepth <= depth) {
63 1 1. getFirstParentWithClass : Changed increment from 1 to -1 → NO_COVERAGE
            currentDepth++;
64 1 1. getFirstParentWithClass : negated conditional → NO_COVERAGE
            if (parent == null) return Optional.empty();
65 2 1. getFirstParentWithClass : replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::getFirstParentWithClass → NO_COVERAGE
2. getFirstParentWithClass : negated conditional → NO_COVERAGE
            if (clazz.isInstance(parent)) return Optional.of(clazz.cast(parent));
66 1 1. getFirstParentWithClass : negated conditional → NO_COVERAGE
            if (parent instanceof Child next) parent = next.getParent();
67
        }
68
        return Optional.empty();
69
    }
70
71
    /// Extracts a list of comment elements from the specified [WordprocessingMLPackage] document.
72
    ///
73
    /// @param document the [WordprocessingMLPackage] document from which to extract comment elements
74
    ///
75
    /// @return a list of [Child] objects representing the extracted comment elements
76
    public static List<Child> extractCommentElements(WordprocessingMLPackage document) {
77
        var commentFinder = new CommentFinder();
78 1 1. extractCommentElements : removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE
        TraversalUtil.visit(document, true, commentFinder);
79 1 1. extractCommentElements : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::extractCommentElements → NO_COVERAGE
        return commentFinder.getCommentElements();
80
    }
81
82
    /// Finds a comment with the given ID in the specified [WordprocessingMLPackage] document.
83
    ///
84
    /// @param document the [WordprocessingMLPackage] document to search for the comment
85
    /// @param id the ID of the comment to find
86
    ///
87
    /// @return an [Optional] containing the [Comment] if found, or an empty [Optional] if not found.
88
    public static Optional<Comment> findComment(WordprocessingMLPackage document, BigInteger id) {
89
        var name = OpenpackagingFactory.newPartName("/word/comments.xml");
90
        var parts = document.getParts();
91
        var wordComments = (CommentsPart) parts.get(name);
92
        var comments = getComments(wordComments);
93 1 1. findComment : replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::findComment → NO_COVERAGE
        return comments.getComment()
94
                       .stream()
95
                       .filter(idEqual(id))
96
                       .findFirst();
97
    }
98
99
    private static Comments getComments(CommentsPart wordComments) {
100
        try {
101 1 1. getComments : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::getComments → NO_COVERAGE
            return wordComments.getContents();
102
        } catch (Docx4JException e) {
103
            throw new UtilsException(e);
104
        }
105
    }
106
107
    private static Predicate<Comment> idEqual(BigInteger id) {
108 1 1. idEqual : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::idEqual → NO_COVERAGE
        return comment -> {
109
            var commentId = comment.getId();
110 2 1. lambda$idEqual$0 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$idEqual$0 → NO_COVERAGE
2. lambda$idEqual$0 : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$idEqual$0 → NO_COVERAGE
            return commentId.equals(id);
111
        };
112
    }
113
114
115
    /// Removes the specified child element from its parent container. Depending on the type of the parent element, the
116
    /// removal process is delegated to the appropriate helper method. If the child is contained within a table cell and
117
    /// the cell is empty after removal, an empty paragraph is added to the cell.
118
    ///
119
    /// @param child the [Child] element to be removed
120
    ///
121
    /// @throws UtilsException if the parent of the child element is of an unexpected type
122
    public static void remove(Child child) {
123
        switch (child.getParent()) {
124 1 1. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE
            case ContentAccessor parent -> remove(parent, child);
125 1 1. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE
            case CTFootnotes parent -> remove(parent, child);
126 1 1. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE
            case CTEndnotes parent -> remove(parent, child);
127 1 1. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE
            case SdtRun parent -> remove(parent, child);
128
            default -> throw new UtilsException("Unexpected value: " + child.getParent());
129
        }
130 2 1. remove : negated conditional → NO_COVERAGE
2. remove : removed call to pro/verron/officestamper/utils/wml/WmlUtils::ensureValidity → NO_COVERAGE
        if (child.getParent() instanceof Tc cell) ensureValidity(cell);
131
    }
132
133
    private static void remove(ContentAccessor parent, Child child) {
134
        var siblings = parent.getContent();
135
        var iterator = siblings.listIterator();
136 1 1. remove : negated conditional → NO_COVERAGE
        while (iterator.hasNext()) {
137 1 1. remove : negated conditional → NO_COVERAGE
            if (equals(iterator.next(), child)) {
138 1 1. remove : removed call to java/util/ListIterator::remove → NO_COVERAGE
                iterator.remove();
139
                break;
140
            }
141
        }
142
    }
143
144
    @SuppressWarnings("SuspiciousMethodCalls")
145
    private static void remove(CTFootnotes parent, Child child) {
146
        parent.getFootnote()
147
              .remove(child);
148
    }
149
150
    @SuppressWarnings("SuspiciousMethodCalls")
151
    private static void remove(CTEndnotes parent, Child child) {
152
        parent.getEndnote()
153
              .remove(child);
154
    }
155
156
    private static void remove(SdtRun parent, Child child) {
157
        parent.getSdtContent()
158
              .getContent()
159
              .remove(child);
160
    }
161
162
    /// Utility method to ensure the validity of a table cell by adding an empty paragraph if necessary.
163
    ///
164
    /// @param cell the [Tc] to be checked and updated.
165
    public static void ensureValidity(Tc cell) {
166 1 1. ensureValidity : negated conditional → NO_COVERAGE
        if (!containsAnElementOfAnyClasses(cell.getContent(), P.class, Tbl.class)) {
167 1 1. ensureValidity : removed call to pro/verron/officestamper/utils/wml/WmlUtils::addEmptyParagraph → NO_COVERAGE
            addEmptyParagraph(cell);
168
        }
169
    }
170
171
    private static boolean equals(Object o1, Object o2) {
172 1 1. equals : negated conditional → NO_COVERAGE
        if (o1 instanceof JAXBElement<?> e1) o1 = e1.getValue();
173 1 1. equals : negated conditional → NO_COVERAGE
        if (o2 instanceof JAXBElement<?> e2) o2 = e2.getValue();
174 2 1. equals : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::equals → NO_COVERAGE
2. equals : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::equals → NO_COVERAGE
        return Objects.equals(o1, o2);
175
    }
176
177
    private static boolean containsAnElementOfAnyClasses(Collection<Object> collection, Class<?>... classes) {
178 2 1. containsAnElementOfAnyClasses : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::containsAnElementOfAnyClasses → NO_COVERAGE
2. containsAnElementOfAnyClasses : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::containsAnElementOfAnyClasses → NO_COVERAGE
        return collection.stream()
179 2 1. lambda$containsAnElementOfAnyClasses$0 : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$containsAnElementOfAnyClasses$0 → NO_COVERAGE
2. lambda$containsAnElementOfAnyClasses$0 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$containsAnElementOfAnyClasses$0 → NO_COVERAGE
                         .anyMatch(element -> isAnElementOfAnyClasses(element, classes));
180
    }
181
182
    private static void addEmptyParagraph(Tc cell) {
183
        var emptyParagraph = WmlFactory.newParagraph();
184
        var cellContent = cell.getContent();
185
        cellContent.add(emptyParagraph);
186
    }
187
188
    private static boolean isAnElementOfAnyClasses(Object element, Class<?>... classes) {
189
        for (var clazz : classes) {
190 2 1. isAnElementOfAnyClasses : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::isAnElementOfAnyClasses → NO_COVERAGE
2. isAnElementOfAnyClasses : negated conditional → NO_COVERAGE
            if (clazz.isInstance(unwrapJAXBElement(element))) return true;
191
        }
192 1 1. isAnElementOfAnyClasses : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::isAnElementOfAnyClasses → NO_COVERAGE
        return false;
193
    }
194
195
    private static Object unwrapJAXBElement(Object element) {
196 2 1. unwrapJAXBElement : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::unwrapJAXBElement → NO_COVERAGE
2. unwrapJAXBElement : negated conditional → NO_COVERAGE
        return element instanceof JAXBElement<?> jaxbElement ? jaxbElement.getValue() : element;
197
    }
198
199
    /// Extracts textual content from a given object, handling various object types, such as runs, text elements, and
200
    /// other specific constructs. The method accounts for different cases, such as run breaks, hyphens, and other
201
    /// document-specific constructs, and converts them into corresponding string representations.
202
    ///
203
    /// @param content the object from which text content is to be extracted. This could be of various types
204
    ///         such as [R], [JAXBElement], [Text] or specific document elements.
205
    ///
206
    /// @return a string representation of the extracted textual content. If the object's type is not handled, an empty
207
    ///         string is returned.
208
    public static String asString(Object content) {
209 1 1. asString : replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils::asString → NO_COVERAGE
        return switch (content) {
210
            case P paragraph -> asString(paragraph.getContent());
211
            case R run -> asString(run.getContent());
212
            case JAXBElement<?> jaxbElement when jaxbElement.getName()
213
                                                            .getLocalPart()
214 1 1. asString : negated conditional → NO_COVERAGE
                                                            .equals("instrText") -> "<instrText>";
215
            case JAXBElement<?> jaxbElement when !jaxbElement.getName()
216
                                                             .getLocalPart()
217 1 1. asString : negated conditional → NO_COVERAGE
                                                             .equals("instrText") -> asString(jaxbElement.getValue());
218
            case Text text -> asString(text);
219
            case R.Tab _ -> "\t";
220
            case R.Cr _ -> "\n";
221 1 1. asString : negated conditional → NO_COVERAGE
            case Br br when br.getType() == null -> "\n";
222 1 1. asString : negated conditional → NO_COVERAGE
            case Br br when br.getType() == STBrType.PAGE -> "\n";
223 1 1. asString : negated conditional → NO_COVERAGE
            case Br br when br.getType() == STBrType.COLUMN -> "\n";
224 1 1. asString : negated conditional → NO_COVERAGE
            case Br br when br.getType() == STBrType.TEXT_WRAPPING -> "\n";
225
226
            case R.NoBreakHyphen _ -> "‑";
227
            case R.SoftHyphen _ -> "\u00AD";
228 4 1. asString : negated conditional → NO_COVERAGE
2. asString : negated conditional → NO_COVERAGE
3. asString : negated conditional → NO_COVERAGE
4. asString : negated conditional → NO_COVERAGE
            case R.LastRenderedPageBreak _, R.AnnotationRef _, R.CommentReference _, Drawing _ -> "";
229
            case FldChar _ -> "<fldchar>";
230
            case CTFtnEdnRef ref -> "<ref(%s)>".formatted(ref.getId());
231
            case R.Sym sym -> "<sym(%s, %s)>".formatted(sym.getFont(), sym.getChar());
232
            case List<?> list -> list.stream()
233
                                     .map(WmlUtils::asString)
234
                                     .collect(joining());
235 2 1. asString : negated conditional → NO_COVERAGE
2. asString : negated conditional → NO_COVERAGE
            case ProofErr _, CTShadow _ -> "";
236
            case SdtRun sdtRun -> asString(sdtRun.getSdtContent());
237
            case ContentAccessor contentAccessor -> asString(contentAccessor.getContent());
238
            case Pict pict -> asString(pict.getAnyAndAny());
239
            case VmlShapeElements vmlShapeElements -> asString(vmlShapeElements.getEGShapeElements());
240
            case CTTextbox textbox -> asString(textbox.getTxbxContent());
241 2 1. asString : negated conditional → NO_COVERAGE
2. asString : negated conditional → NO_COVERAGE
            case CommentRangeStart _, CommentRangeEnd _ -> "";
242
            default -> {
243
                log.debug("Unhandled object type: {}", content.getClass());
244
                yield "";
245
            }
246
        };
247
    }
248
249
    private static String asString(Text text) {
250
        // According to specs, the 'space' value can be empty or 'preserve'.
251
        // In the first case, we are supposed to ignore spaces around the 'text' value.
252
        var value = text.getValue();
253
        var space = text.getSpace();
254 2 1. asString : negated conditional → NO_COVERAGE
2. asString : replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils::asString → NO_COVERAGE
        return Objects.equals(space, PRESERVE) ? value : value.trim();
255
    }
256
257
    /// Inserts a smart tag with the specified element type into the given paragraph at the position of the expression.
258
    ///
259
    /// @param element the element type for the smart tag
260
    /// @param paragraph the [P] paragraph to insert the smart tag into
261
    /// @param expression the expression to replace with the smart tag
262
    /// @param start the start index of the expression
263
    /// @param end the end index of the expression
264
    ///
265
    /// @return a list of [Object] representing the updated content
266
    public static List<Object> insertSmartTag(String element, P paragraph, String expression, int start, int end) {
267
        var run = newRun(expression);
268
        var smartTag = newSmartTag("officestamper", newCtAttr("type", element), run);
269 1 1. insertSmartTag : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        findFirstAffectedRunPr(paragraph, start, end).ifPresent(run::setRPr);
270 1 1. insertSmartTag : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::insertSmartTag → NO_COVERAGE
        return replace(paragraph, List.of(smartTag), start, end);
271
    }
272
273
    /// Finds the first affected run properties within the specified range.
274
    ///
275
    /// @param contentAccessor the [ContentAccessor] to search in
276
    /// @param start the start index of the range
277
    /// @param end the end index of the range
278
    ///
279
    /// @return an [Optional] containing the [RPr] if found, or an empty [Optional] if not found
280
    public static Optional<RPr> findFirstAffectedRunPr(ContentAccessor contentAccessor, int start, int end) {
281
        var iterator = new DocxIterator(contentAccessor).selectClass(R.class);
282
        var runs = StandardRun.wrap(iterator);
283
284
        var affectedRuns = runs.stream()
285 2 1. lambda$findFirstAffectedRunPr$0 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$findFirstAffectedRunPr$0 → NO_COVERAGE
2. lambda$findFirstAffectedRunPr$0 : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$findFirstAffectedRunPr$0 → NO_COVERAGE
                               .filter(run -> run.isTouchedByRange(start, end))
286
                               .toList();
287
288
        var firstRun = affectedRuns.getFirst();
289
        var firstRunPr = firstRun.getPr();
290 1 1. findFirstAffectedRunPr : replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::findFirstAffectedRunPr → NO_COVERAGE
        return Optional.ofNullable(firstRunPr);
291
    }
292
293
    /// Replaces content within the specified range with the provided insert objects.
294
    ///
295
    /// @param contentAccessor the [ContentAccessor] in which to replace content
296
    /// @param insert the list of objects to insert
297
    /// @param startIndex the start index of the range to replace
298
    /// @param endIndex the end index of the range to replace
299
    ///
300
    /// @return a list of [Object] representing the updated content
301
    public static List<Object> replace(
302
            ContentAccessor contentAccessor,
303
            List<Object> insert,
304
            int startIndex,
305
            int endIndex
306
    ) {
307
        var iterator = new DocxIterator(contentAccessor).selectClass(R.class);
308
        var runs = StandardRun.wrap(iterator);
309
        var affectedRuns = runs.stream()
310 2 1. lambda$replace$0 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$replace$0 → NO_COVERAGE
2. lambda$replace$0 : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$replace$0 → NO_COVERAGE
                               .filter(run -> run.isTouchedByRange(startIndex, endIndex))
311
                               .toList();
312
313
        var firstRun = affectedRuns.getFirst();
314
        var firstR = firstRun.run();
315
        var firstSiblings = ((ContentAccessor) firstR.getParent()).getContent();
316
        var firstIndex = firstSiblings.indexOf(firstRun.run());
317
318 1 1. replace : negated conditional → NO_COVERAGE
        boolean singleRun = affectedRuns.size() == 1;
319 1 1. replace : negated conditional → NO_COVERAGE
        if (singleRun) {
320 2 1. replace : Replaced integer subtraction with addition → NO_COVERAGE
2. replace : negated conditional → NO_COVERAGE
            boolean expressionSpansCompleteRun = endIndex - startIndex == firstRun.length();
321 1 1. replace : negated conditional → NO_COVERAGE
            boolean expressionAtStartOfRun = startIndex == firstRun.startIndex();
322 1 1. replace : negated conditional → NO_COVERAGE
            boolean expressionAtEndOfRun = endIndex == firstRun.endIndex();
323 4 1. replace : changed conditional boundary → NO_COVERAGE
2. replace : negated conditional → NO_COVERAGE
3. replace : changed conditional boundary → NO_COVERAGE
4. replace : negated conditional → NO_COVERAGE
            boolean expressionWithinRun = startIndex > firstRun.startIndex() && endIndex <= firstRun.endIndex();
324
325 1 1. replace : negated conditional → NO_COVERAGE
            if (expressionSpansCompleteRun) {
326 1 1. replace : removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE
                firstRun.replace(startIndex, endIndex, "");
327
                firstSiblings.addAll(firstIndex, insert);
328
            }
329 1 1. replace : negated conditional → NO_COVERAGE
            else if (expressionAtStartOfRun) {
330 1 1. replace : removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE
                firstRun.replace(startIndex, endIndex, "");
331
                firstSiblings.addAll(firstIndex, insert);
332
            }
333 1 1. replace : negated conditional → NO_COVERAGE
            else if (expressionAtEndOfRun) {
334 1 1. replace : removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE
                firstRun.replace(startIndex, endIndex, "");
335 1 1. replace : Replaced integer addition with subtraction → NO_COVERAGE
                firstSiblings.addAll(firstIndex + 1, insert);
336
            }
337 1 1. replace : negated conditional → NO_COVERAGE
            else if (expressionWithinRun) {
338
                var originalRun = firstRun.run();
339
                var originalRPr = originalRun.getRPr();
340
                var newStartRun = create(firstRun.left(startIndex), originalRPr);
341
                var newEndRun = create(firstRun.right(endIndex), originalRPr);
342
                firstSiblings.remove(firstIndex);
343
                firstSiblings.addAll(firstIndex, wrap(newStartRun, insert, newEndRun));
344
            }
345
        }
346
        else {
347
            StandardRun lastRun = affectedRuns.getLast();
348 1 1. replace : removed call to pro/verron/officestamper/utils/wml/WmlUtils::removeExpression → NO_COVERAGE
            removeExpression(firstSiblings, firstRun, startIndex, endIndex, lastRun, affectedRuns);
349
            // add replacement run between first and last run
350 1 1. replace : Replaced integer addition with subtraction → NO_COVERAGE
            firstSiblings.addAll(firstIndex + 1, insert);
351
        }
352 1 1. replace : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replace → NO_COVERAGE
        return new ArrayList<>(contentAccessor.getContent());
353
    }
354
355
    /// Creates a new run with the specified text, and the specified run style.
356
    ///
357
    /// @param text the initial text of the [R].
358
    /// @param rPr the [RPr] to apply to the run
359
    ///
360
    /// @return the newly created [R].
361
    public static R create(String text, RPr rPr) {
362
        R newStartRun = newRun(text);
363 1 1. create : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE
        newStartRun.setRPr(rPr);
364 1 1. create : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::create → NO_COVERAGE
        return newStartRun;
365
    }
366
367
    private static Collection<?> wrap(R prefix, Collection<?> elements, R suffix) {
368
        var merge = new ArrayList<>();
369
        merge.add(prefix);
370
        merge.addAll(elements);
371
        merge.add(suffix);
372 1 1. wrap : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::wrap → NO_COVERAGE
        return merge;
373
    }
374
375
    private static void removeExpression(
376
            List<Object> contents,
377
            StandardRun firstRun,
378
            int matchStartIndex,
379
            int matchEndIndex,
380
            StandardRun lastRun,
381
            List<StandardRun> affectedRuns
382
    ) {
383
        // remove the expression from the first run
384 1 1. removeExpression : removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE
        firstRun.replace(matchStartIndex, matchEndIndex, "");
385
        // remove all runs between first and last
386
        for (StandardRun run : affectedRuns) {
387 2 1. removeExpression : negated conditional → NO_COVERAGE
2. removeExpression : negated conditional → NO_COVERAGE
            if (!Objects.equals(run, firstRun) && !Objects.equals(run, lastRun)) {
388
                contents.remove(run.run());
389
            }
390
        }
391
        // remove the expression from the last run
392 1 1. removeExpression : removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE
        lastRun.replace(matchStartIndex, matchEndIndex, "");
393
    }
394
395
    /// Creates a new run with the specified text and inherits the style of the parent paragraph.
396
    ///
397
    /// @param text the initial text of the [R].
398
    /// @param paragraphPr the [PPr] to apply to the run
399
    ///
400
    /// @return the newly created [R].
401
    public static R create(String text, PPr paragraphPr) {
402
        R run = newRun(text);
403 1 1. create : removed call to pro/verron/officestamper/utils/wml/WmlUtils::applyParagraphStyle → NO_COVERAGE
        applyParagraphStyle(run, paragraphPr);
404 1 1. create : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::create → NO_COVERAGE
        return run;
405
    }
406
407
    /// Applies the style of the given paragraph to the given content object (if the content object is a [R]).
408
    ///
409
    /// @param run the [R] to which the style should be applied.
410
    /// @param paragraphPr the [PPr] containing the style to apply
411
    public static void applyParagraphStyle(R run, @Nullable PPr paragraphPr) {
412 1 1. applyParagraphStyle : negated conditional → NO_COVERAGE
        if (paragraphPr == null) return;
413
        var runPr = paragraphPr.getRPr();
414 1 1. applyParagraphStyle : negated conditional → NO_COVERAGE
        if (runPr == null) return;
415
        RPr runProperties = new RPr();
416
        StyleUtil.apply(runPr, runProperties);
417 1 1. applyParagraphStyle : removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE
        run.setRPr(runProperties);
418
    }
419
420
    /// Sets the text of the given run to the given value.
421
    ///
422
    /// @param run the [R] whose text to change.
423
    /// @param text the text to set.
424
    public static void setText(R run, String text) {
425
        run.getContent()
426 1 1. setText : removed call to java/util/List::clear → NO_COVERAGE
           .clear();
427
        Text textObj = newText(text);
428
        run.getContent()
429
           .add(textObj);
430
    }
431
432
    /// Replaces all occurrences of the specified expression with the provided run objects.
433
    ///
434
    /// @param contentAccessor the [ContentAccessor] in which to replace the expression
435
    /// @param expression the expression to replace
436
    /// @param insert the list of objects to insert
437
    /// @param onRPr a consumer to handle [RPr] properties
438
    ///
439
    /// @return a list of [Object] representing the updated content
440
    public static List<Object> replaceExpressionWithRun(
441
            ContentAccessor contentAccessor,
442
            String expression,
443
            List<Object> insert,
444
            Consumer<RPr> onRPr
445
    ) {
446
        var text = asString(contentAccessor);
447
        int matchStartIndex = text.indexOf(expression);
448 2 1. replaceExpressionWithRun : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replaceExpressionWithRun → NO_COVERAGE
2. replaceExpressionWithRun : negated conditional → NO_COVERAGE
        if (matchStartIndex == -1) /*nothing to replace*/ return contentAccessor.getContent();
449 1 1. replaceExpressionWithRun : Replaced integer addition with subtraction → NO_COVERAGE
        int matchEndIndex = matchStartIndex + expression.length();
450 1 1. replaceExpressionWithRun : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        findFirstAffectedRunPr(contentAccessor, matchStartIndex, matchEndIndex).ifPresent(onRPr);
451 1 1. replaceExpressionWithRun : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replaceExpressionWithRun → NO_COVERAGE
        return replace(contentAccessor, insert, matchStartIndex, matchEndIndex);
452
    }
453
454
    /// Checks if the given [CTSmartTagRun] contains an element that matches the expected element.
455
    ///
456
    /// @param tag the [CTSmartTagRun] object to be evaluated
457
    /// @param expectedElement the expected element to compare against
458
    ///
459
    /// @return true if the actual element of the given tag matches the expected element, false otherwise
460
    public static boolean isTagElement(CTSmartTagRun tag, String expectedElement) {
461
        var actualElement = tag.getElement();
462 2 1. isTagElement : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::isTagElement → NO_COVERAGE
2. isTagElement : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::isTagElement → NO_COVERAGE
        return Objects.equals(expectedElement, actualElement);
463
    }
464
465
    /// Sets or updates an attribute for the specified smart tag. This method ensures that the provided attribute
466
    /// key-value pair is added to the smart tag's attribute list. If the attribute already exists, its value is
467
    /// updated. If the smart tag or its attribute metadata is null, they are initialized.
468
    ///
469
    /// @param smartTag the smart tag object to modify
470
    /// @param attributeKey the key of the attribute to set or update
471
    /// @param attributeValue the value to assign to the specified attribute key
472
    public static void setTagAttribute(CTSmartTagRun smartTag, String attributeKey, String attributeValue) {
473
        var smartTagPr = smartTag.getSmartTagPr();
474 1 1. setTagAttribute : negated conditional → NO_COVERAGE
        if (smartTagPr == null) {
475
            smartTagPr = new CTSmartTagPr();
476 1 1. setTagAttribute : removed call to org/docx4j/wml/CTSmartTagRun::setSmartTagPr → NO_COVERAGE
            smartTag.setSmartTagPr(smartTagPr);
477
        }
478
        var smartTagPrAttr = smartTagPr.getAttr();
479 1 1. setTagAttribute : negated conditional → NO_COVERAGE
        if (smartTagPrAttr == null) {
480
            smartTagPrAttr = new ArrayList<>();
481 1 1. setTagAttribute : removed call to org/docx4j/wml/CTSmartTagRun::setSmartTagPr → NO_COVERAGE
            smartTag.setSmartTagPr(smartTagPr);
482
        }
483
        for (CTAttr attribute : smartTagPrAttr) {
484 1 1. setTagAttribute : negated conditional → NO_COVERAGE
            if (attributeKey.equals(attribute.getName())) {
485 1 1. setTagAttribute : removed call to org/docx4j/wml/CTAttr::setVal → NO_COVERAGE
                attribute.setVal(attributeValue);
486
                return;
487
            }
488
        }
489
        var ctAttr = newAttribute(attributeKey, attributeValue);
490
        smartTagPrAttr.add(ctAttr);
491
    }
492
493
    /// Creates a new attribute object with the specified key and value.
494
    ///
495
    /// @param attributeKey the key for the new attribute
496
    /// @param attributeValue the value for the new attribute
497
    /// @return a CTAttr object representing the new attribute
498
    public static CTAttr newAttribute(String attributeKey, String attributeValue) {
499 1 1. newAttribute : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::newAttribute → NO_COVERAGE
        return newCtAttr(attributeKey, attributeValue);
500
    }
501
502
    /// Deletes all elements associated with the specified comment from the provided list of items.
503
    ///
504
    /// @param commentId the ID of the comment to be deleted
505
    /// @param items the list of items from which elements associated with the comment will be deleted
506
    public static void deleteCommentFromElements(BigInteger commentId, List<Object> items) {
507
        record DeletableItems(List<Object> container, List<Object> items) {
508
            static List<DeletableItems> findAll(List<Object> items, BigInteger commentId) {
509 2 1. lambda$findAll$0 : replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$0 → NO_COVERAGE
2. lambda$findAll$0 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$0 → NO_COVERAGE
                Predicate<BigInteger> predicate = bi -> Objects.equals(bi, commentId);
510
                List<DeletableItems> elementsToRemove = new ArrayList<>();
511 1 1. findAll : removed call to java/util/List::forEach → NO_COVERAGE
                items.forEach(item -> {
512
                    Object unwrapped = unwrap(item);
513
                    // Recursively finds deletable items associated with comment ID
514
                    elementsToRemove.addAll(switch (unwrapped) {
515
                        case CTSmartTagRun str when str.getContent()
516
                                                       .stream()
517 1 1. lambda$findAll$1 : negated conditional → NO_COVERAGE
                                                       .anyMatch(i -> i instanceof CommentRangeStart crs
518 3 1. lambda$findAll$2 : negated conditional → NO_COVERAGE
2. lambda$findAll$2 : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$2 → NO_COVERAGE
3. lambda$findAll$2 : negated conditional → NO_COVERAGE
                                                                      && predicate.test(crs.getId())) ->
519
                                from(items, item);
520 1 1. lambda$findAll$1 : negated conditional → NO_COVERAGE
                        case CommentRangeStart crs when predicate.test(crs.getId()) -> from(items, item);
521 1 1. lambda$findAll$1 : negated conditional → NO_COVERAGE
                        case CommentRangeEnd cre when predicate.test(cre.getId()) -> from(items, item);
522 1 1. lambda$findAll$1 : negated conditional → NO_COVERAGE
                        case R.CommentReference rcr when predicate.test(rcr.getId()) -> from(items, item);
523
                        case ContentAccessor ca -> findAll(ca, commentId);
524
                        case SdtRun sdtRun -> findAll(sdtRun, commentId);
525
                        default -> emptyList();
526
                    });
527
                });
528 1 1. findAll : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE
                return elementsToRemove;
529
            }
530
531
            private static Collection<DeletableItems> findAll(SdtRun sdtRun, BigInteger commentId) {
532 1 1. findAll : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE
                return findAll(sdtRun.getSdtContent(), commentId);
533
            }
534
535
            private static Collection<DeletableItems> findAll(ContentAccessor ca, BigInteger commentId) {
536 1 1. findAll : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE
                return findAll(ca.getContent(), commentId);
537
            }
538
539
            private static List<DeletableItems> from(List<Object> items, Object item) {
540 1 1. from : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::from → NO_COVERAGE
                return Collections.singletonList(new DeletableItems(items, List.of(item)));
541
            }
542
        }
543
        DeletableItems.findAll(items, commentId)
544 1 1. deleteCommentFromElements : removed call to java/util/List::forEach → NO_COVERAGE
                      .forEach(p -> p.container.removeAll(p.items));
545
    }
546
547
    /// Visits the document's main content, header, footer, footnotes, and endnotes using the specified visitor.
548
    ///
549
    /// @param document the WordprocessingMLPackage representing the document to be visited
550
    /// @param visitor the TraversalUtilVisitor to be applied to each relevant part of the document
551
    public static void visitDocument(WordprocessingMLPackage document, TraversalUtilVisitor<?> visitor) {
552
        var mainDocumentPart = document.getMainDocumentPart();
553 1 1. visitDocument : removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE
        TraversalUtil.visit(mainDocumentPart, visitor);
554
        WmlUtils.streamHeaderFooterPart(document)
555 2 1. lambda$visitDocument$0 : removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE
2. visitDocument : removed call to java/util/stream/Stream::forEach → NO_COVERAGE
                .forEach(f -> TraversalUtil.visit(f, visitor));
556 1 1. visitDocument : removed call to pro/verron/officestamper/utils/wml/WmlUtils::visitPartIfExists → NO_COVERAGE
        WmlUtils.visitPartIfExists(visitor, mainDocumentPart.getFootnotesPart());
557 1 1. visitDocument : removed call to pro/verron/officestamper/utils/wml/WmlUtils::visitPartIfExists → NO_COVERAGE
        WmlUtils.visitPartIfExists(visitor, mainDocumentPart.getEndNotesPart());
558
    }
559
560
    private static Stream<Object> streamHeaderFooterPart(WordprocessingMLPackage document) {
561 1 1. streamHeaderFooterPart : replaced return value with Stream.empty for pro/verron/officestamper/utils/wml/WmlUtils::streamHeaderFooterPart → NO_COVERAGE
        return document.getDocumentModel()
562
                       .getSections()
563
                       .stream()
564
                       .map(SectionWrapper::getHeaderFooterPolicy)
565
                       .flatMap(WmlUtils::extractHeaderFooterParts);
566
    }
567
568
    private static void visitPartIfExists(TraversalUtilVisitor<?> visitor, @Nullable JaxbXmlPart<?> part) {
569
        Optional.ofNullable(part)
570
                .map(WmlUtils::extractContent)
571 2 1. lambda$visitPartIfExists$0 : removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE
2. visitPartIfExists : removed call to java/util/Optional::ifPresent → NO_COVERAGE
                .ifPresent(c -> TraversalUtil.visit(c, visitor));
572
    }
573
574
    private static Stream<JaxbXmlPart<?>> extractHeaderFooterParts(HeaderFooterPolicy hfp) {
575
        Stream.Builder<JaxbXmlPart<?>> builder = Stream.builder();
576 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getFirstHeader()).ifPresent(builder::add);
577 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getDefaultHeader()).ifPresent(builder::add);
578 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getEvenHeader()).ifPresent(builder::add);
579 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getFirstFooter()).ifPresent(builder::add);
580 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getDefaultFooter()).ifPresent(builder::add);
581 1 1. extractHeaderFooterParts : removed call to java/util/Optional::ifPresent → NO_COVERAGE
        ofNullable(hfp.getEvenFooter()).ifPresent(builder::add);
582 1 1. extractHeaderFooterParts : replaced return value with Stream.empty for pro/verron/officestamper/utils/wml/WmlUtils::extractHeaderFooterParts → NO_COVERAGE
        return builder.build();
583
    }
584
585
    private static Object extractContent(JaxbXmlPart<?> jaxbXmlPart) {
586
        try {
587 1 1. extractContent : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::extractContent → NO_COVERAGE
            return jaxbXmlPart.getContents();
588
        } catch (Docx4JException e) {
589
            throw new UtilsException(e);
590
        }
591
    }
592
593
    /// @param startIndex the start index of the run relative to the containing paragraph.
594
    /// @param run the [R] run itself.
595
    private record StandardRun(int startIndex, R run) {
596
597
        /// Initializes a list of [StandardRun] objects based on the given iterator of [R] objects.
598
        ///
599
        /// @param iterator the iterator of [R] objects to be processed into [StandardRun] instances
600
        ///
601
        /// @return a list of [StandardRun] objects created from the given iterator
602
        public static List<StandardRun> wrap(Iterator<R> iterator) {
603
            var index = 0;
604
            var runList = new ArrayList<StandardRun>();
605 1 1. wrap : negated conditional → NO_COVERAGE
            while (iterator.hasNext()) {
606
                var run = iterator.next();
607
                var currentRun = new StandardRun(index, run);
608
                runList.add(currentRun);
609 1 1. wrap : Replaced integer addition with subtraction → NO_COVERAGE
                index += currentRun.length();
610
            }
611 1 1. wrap : replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::wrap → NO_COVERAGE
            return runList;
612
        }
613
614
        /// Calculates the length of the text content of this run.
615
        ///
616
        /// @return the length of the text in the current run.
617
        public int length() {
618 1 1. length : replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::length → NO_COVERAGE
            return getText().length();
619
        }
620
621
        /// Returns the text string of a run.
622
        ///
623
        /// @return [String] representation of the run.
624
        public String getText() {
625 1 1. getText : replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::getText → NO_COVERAGE
            return asString(run);
626
        }
627
628
        /// Retrieves the properties associated with this run.
629
        ///
630
        /// @return the [RPr] object representing the properties of the run.
631
        public RPr getPr() {
632 1 1. getPr : replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::getPr → NO_COVERAGE
            return run.getRPr();
633
        }
634
635
        /// Determines whether the current run is affected by the specified range of global start and end indices. A run
636
        /// is considered "touched" if any part of it overlaps with the given range.
637
        ///
638
        /// @param globalStartIndex the global start index of the range.
639
        /// @param globalEndIndex the global end index of the range.
640
        ///
641
        /// @return `true` if the current run is touched by the specified range; `false` otherwise.
642
        public boolean isTouchedByRange(int globalStartIndex, int globalEndIndex) {
643 3 1. isTouchedByRange : negated conditional → NO_COVERAGE
2. isTouchedByRange : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::isTouchedByRange → NO_COVERAGE
3. isTouchedByRange : negated conditional → NO_COVERAGE
            return startsInRange(globalStartIndex, globalEndIndex) || endsInRange(globalStartIndex, globalEndIndex)
644 1 1. isTouchedByRange : negated conditional → NO_COVERAGE
                   || englobesRange(globalStartIndex, globalEndIndex);
645
        }
646
647
        private boolean startsInRange(int globalStartIndex, int globalEndIndex) {
648 5 1. startsInRange : negated conditional → NO_COVERAGE
2. startsInRange : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::startsInRange → NO_COVERAGE
3. startsInRange : changed conditional boundary → NO_COVERAGE
4. startsInRange : negated conditional → NO_COVERAGE
5. startsInRange : changed conditional boundary → NO_COVERAGE
            return globalStartIndex < startIndex && startIndex <= globalEndIndex;
649
        }
650
651
        private boolean endsInRange(int globalStartIndex, int globalEndIndex) {
652 5 1. endsInRange : changed conditional boundary → NO_COVERAGE
2. endsInRange : negated conditional → NO_COVERAGE
3. endsInRange : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::endsInRange → NO_COVERAGE
4. endsInRange : negated conditional → NO_COVERAGE
5. endsInRange : changed conditional boundary → NO_COVERAGE
            return globalStartIndex < endIndex() && endIndex() <= globalEndIndex;
653
        }
654
655
        private boolean englobesRange(int globalStartIndex, int globalEndIndex) {
656 5 1. englobesRange : negated conditional → NO_COVERAGE
2. englobesRange : changed conditional boundary → NO_COVERAGE
3. englobesRange : negated conditional → NO_COVERAGE
4. englobesRange : replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::englobesRange → NO_COVERAGE
5. englobesRange : changed conditional boundary → NO_COVERAGE
            return startIndex <= globalStartIndex && globalEndIndex <= endIndex();
657
        }
658
659
        /// Calculates the end index of the current run based on its start index and length.
660
        ///
661
        /// @return the end index of the run.
662
        public int endIndex() {
663 2 1. endIndex : Replaced integer addition with subtraction → NO_COVERAGE
2. endIndex : replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::endIndex → NO_COVERAGE
            return startIndex + length();
664
        }
665
666
        /// Replaces the substring starting at the given index with the given replacement string.
667
        ///
668
        /// @param globalStartIndex the global index at which to start the replacement.
669
        /// @param globalEndIndex the global index at which to end the replacement.
670
        /// @param replacement the string to replace the substring at the specified global index.
671
        public void replace(int globalStartIndex, int globalEndIndex, String replacement) {
672
            var text = left(globalStartIndex) + replacement + right(globalEndIndex);
673 1 1. replace : removed call to pro/verron/officestamper/utils/wml/WmlUtils::setText → NO_COVERAGE
            setText(run, text);
674
        }
675
676
        /// Extracts a substring of the run's text, starting from the beginning and extending up to the localized index
677
        /// of the specified global end index.
678
        ///
679
        /// @param globalEndIndex the global end index used to determine the cutoff point for the extracted
680
        ///         substring.
681
        ///
682
        /// @return a substring of the run's text, starting at the beginning and ending at the specified localized
683
        ///         index.
684
        public String left(int globalEndIndex) {
685 1 1. left : replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::left → NO_COVERAGE
            return getText().substring(0, localize(globalEndIndex));
686
        }
687
688
        /// Extracts a substring of the run's text, starting from the localized index of the specified global start
689
        /// index to the end of the run's text.
690
        ///
691
        /// @param globalStartIndex the global index specifying the starting point for the substring in the
692
        ///         run's text.
693
        ///
694
        /// @return a substring of the run's text starting from the localized index corresponding to the provided global
695
        ///         start index.
696
        public String right(int globalStartIndex) {
697 1 1. right : replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::right → NO_COVERAGE
            return getText().substring(localize(globalStartIndex));
698
        }
699
700
        /// Converts a global index to a local index within the context of this run. (meaning the index relative to
701
        /// multiple aggregated runs)
702
        ///
703
        /// @param globalIndex the global index to convert.
704
        ///
705
        /// @return the local index corresponding to the given global index.
706
        private int localize(int globalIndex) {
707 2 1. localize : changed conditional boundary → NO_COVERAGE
2. localize : negated conditional → NO_COVERAGE
            if (globalIndex < startIndex) return 0;
708 3 1. localize : changed conditional boundary → NO_COVERAGE
2. localize : replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::localize → NO_COVERAGE
3. localize : negated conditional → NO_COVERAGE
            else if (globalIndex > endIndex()) return length();
709 2 1. localize : replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::localize → NO_COVERAGE
2. localize : Replaced integer subtraction with addition → NO_COVERAGE
            else return globalIndex - startIndex;
710
        }
711
712
        /// Gets the start index of this run.
713
        ///
714
        /// @return the start index of the run relative to the containing paragraph.
715
        @Override
716
        public int startIndex() {return startIndex;}
717
718
        /// Gets the underlying run object.
719
        ///
720
        /// @return the [R] run object.
721
        @Override
722
        public R run() {return run;}
723
    }
724
}

Mutations

62

1.1
Location : getFirstParentWithClass
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : getFirstParentWithClass
Killed by : none
negated conditional → NO_COVERAGE

63

1.1
Location : getFirstParentWithClass
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

64

1.1
Location : getFirstParentWithClass
Killed by : none
negated conditional → NO_COVERAGE

65

1.1
Location : getFirstParentWithClass
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::getFirstParentWithClass → NO_COVERAGE

2.2
Location : getFirstParentWithClass
Killed by : none
negated conditional → NO_COVERAGE

66

1.1
Location : getFirstParentWithClass
Killed by : none
negated conditional → NO_COVERAGE

78

1.1
Location : extractCommentElements
Killed by : none
removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE

79

1.1
Location : extractCommentElements
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::extractCommentElements → NO_COVERAGE

93

1.1
Location : findComment
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::findComment → NO_COVERAGE

101

1.1
Location : getComments
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::getComments → NO_COVERAGE

108

1.1
Location : idEqual
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::idEqual → NO_COVERAGE

110

1.1
Location : lambda$idEqual$0
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$idEqual$0 → NO_COVERAGE

2.2
Location : lambda$idEqual$0
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$idEqual$0 → NO_COVERAGE

124

1.1
Location : remove
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE

125

1.1
Location : remove
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE

126

1.1
Location : remove
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE

127

1.1
Location : remove
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::remove → NO_COVERAGE

130

1.1
Location : remove
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : remove
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::ensureValidity → NO_COVERAGE

136

1.1
Location : remove
Killed by : none
negated conditional → NO_COVERAGE

137

1.1
Location : remove
Killed by : none
negated conditional → NO_COVERAGE

138

1.1
Location : remove
Killed by : none
removed call to java/util/ListIterator::remove → NO_COVERAGE

166

1.1
Location : ensureValidity
Killed by : none
negated conditional → NO_COVERAGE

167

1.1
Location : ensureValidity
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::addEmptyParagraph → NO_COVERAGE

172

1.1
Location : equals
Killed by : none
negated conditional → NO_COVERAGE

173

1.1
Location : equals
Killed by : none
negated conditional → NO_COVERAGE

174

1.1
Location : equals
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::equals → NO_COVERAGE

2.2
Location : equals
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::equals → NO_COVERAGE

178

1.1
Location : containsAnElementOfAnyClasses
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::containsAnElementOfAnyClasses → NO_COVERAGE

2.2
Location : containsAnElementOfAnyClasses
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::containsAnElementOfAnyClasses → NO_COVERAGE

179

1.1
Location : lambda$containsAnElementOfAnyClasses$0
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$containsAnElementOfAnyClasses$0 → NO_COVERAGE

2.2
Location : lambda$containsAnElementOfAnyClasses$0
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$containsAnElementOfAnyClasses$0 → NO_COVERAGE

190

1.1
Location : isAnElementOfAnyClasses
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::isAnElementOfAnyClasses → NO_COVERAGE

2.2
Location : isAnElementOfAnyClasses
Killed by : none
negated conditional → NO_COVERAGE

192

1.1
Location : isAnElementOfAnyClasses
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::isAnElementOfAnyClasses → NO_COVERAGE

196

1.1
Location : unwrapJAXBElement
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::unwrapJAXBElement → NO_COVERAGE

2.2
Location : unwrapJAXBElement
Killed by : none
negated conditional → NO_COVERAGE

209

1.1
Location : asString
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils::asString → NO_COVERAGE

214

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

217

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

221

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

222

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

223

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

224

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

228

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

4.4
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

235

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

241

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

254

1.1
Location : asString
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : asString
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils::asString → NO_COVERAGE

269

1.1
Location : insertSmartTag
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

270

1.1
Location : insertSmartTag
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::insertSmartTag → NO_COVERAGE

285

1.1
Location : lambda$findFirstAffectedRunPr$0
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$findFirstAffectedRunPr$0 → NO_COVERAGE

2.2
Location : lambda$findFirstAffectedRunPr$0
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$findFirstAffectedRunPr$0 → NO_COVERAGE

290

1.1
Location : findFirstAffectedRunPr
Killed by : none
replaced return value with Optional.empty for pro/verron/officestamper/utils/wml/WmlUtils::findFirstAffectedRunPr → NO_COVERAGE

310

1.1
Location : lambda$replace$0
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::lambda$replace$0 → NO_COVERAGE

2.2
Location : lambda$replace$0
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::lambda$replace$0 → NO_COVERAGE

318

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

319

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

320

1.1
Location : replace
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

2.2
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

321

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

322

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

323

1.1
Location : replace
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : replace
Killed by : none
changed conditional boundary → NO_COVERAGE

4.4
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

325

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

326

1.1
Location : replace
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE

329

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

330

1.1
Location : replace
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE

333

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

334

1.1
Location : replace
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE

335

1.1
Location : replace
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

337

1.1
Location : replace
Killed by : none
negated conditional → NO_COVERAGE

348

1.1
Location : replace
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::removeExpression → NO_COVERAGE

350

1.1
Location : replace
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

352

1.1
Location : replace
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replace → NO_COVERAGE

363

1.1
Location : create
Killed by : none
removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE

364

1.1
Location : create
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::create → NO_COVERAGE

372

1.1
Location : wrap
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::wrap → NO_COVERAGE

384

1.1
Location : removeExpression
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE

387

1.1
Location : removeExpression
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : removeExpression
Killed by : none
negated conditional → NO_COVERAGE

392

1.1
Location : removeExpression
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::replace → NO_COVERAGE

403

1.1
Location : create
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::applyParagraphStyle → NO_COVERAGE

404

1.1
Location : create
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::create → NO_COVERAGE

412

1.1
Location : applyParagraphStyle
Killed by : none
negated conditional → NO_COVERAGE

414

1.1
Location : applyParagraphStyle
Killed by : none
negated conditional → NO_COVERAGE

417

1.1
Location : applyParagraphStyle
Killed by : none
removed call to org/docx4j/wml/R::setRPr → NO_COVERAGE

426

1.1
Location : setText
Killed by : none
removed call to java/util/List::clear → NO_COVERAGE

448

1.1
Location : replaceExpressionWithRun
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replaceExpressionWithRun → NO_COVERAGE

2.2
Location : replaceExpressionWithRun
Killed by : none
negated conditional → NO_COVERAGE

449

1.1
Location : replaceExpressionWithRun
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

450

1.1
Location : replaceExpressionWithRun
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

451

1.1
Location : replaceExpressionWithRun
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils::replaceExpressionWithRun → NO_COVERAGE

462

1.1
Location : isTagElement
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils::isTagElement → NO_COVERAGE

2.2
Location : isTagElement
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils::isTagElement → NO_COVERAGE

474

1.1
Location : setTagAttribute
Killed by : none
negated conditional → NO_COVERAGE

476

1.1
Location : setTagAttribute
Killed by : none
removed call to org/docx4j/wml/CTSmartTagRun::setSmartTagPr → NO_COVERAGE

479

1.1
Location : setTagAttribute
Killed by : none
negated conditional → NO_COVERAGE

481

1.1
Location : setTagAttribute
Killed by : none
removed call to org/docx4j/wml/CTSmartTagRun::setSmartTagPr → NO_COVERAGE

484

1.1
Location : setTagAttribute
Killed by : none
negated conditional → NO_COVERAGE

485

1.1
Location : setTagAttribute
Killed by : none
removed call to org/docx4j/wml/CTAttr::setVal → NO_COVERAGE

499

1.1
Location : newAttribute
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::newAttribute → NO_COVERAGE

509

1.1
Location : lambda$findAll$0
Killed by : none
replaced boolean return with false for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$0 → NO_COVERAGE

2.2
Location : lambda$findAll$0
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$0 → NO_COVERAGE

511

1.1
Location : findAll
Killed by : none
removed call to java/util/List::forEach → NO_COVERAGE

517

1.1
Location : lambda$findAll$1
Killed by : none
negated conditional → NO_COVERAGE

518

1.1
Location : lambda$findAll$2
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : lambda$findAll$2
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::lambda$findAll$2 → NO_COVERAGE

3.3
Location : lambda$findAll$2
Killed by : none
negated conditional → NO_COVERAGE

520

1.1
Location : lambda$findAll$1
Killed by : none
negated conditional → NO_COVERAGE

521

1.1
Location : lambda$findAll$1
Killed by : none
negated conditional → NO_COVERAGE

522

1.1
Location : lambda$findAll$1
Killed by : none
negated conditional → NO_COVERAGE

528

1.1
Location : findAll
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE

532

1.1
Location : findAll
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE

536

1.1
Location : findAll
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::findAll → NO_COVERAGE

540

1.1
Location : from
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$1DeletableItems::from → NO_COVERAGE

544

1.1
Location : deleteCommentFromElements
Killed by : none
removed call to java/util/List::forEach → NO_COVERAGE

553

1.1
Location : visitDocument
Killed by : none
removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE

555

1.1
Location : lambda$visitDocument$0
Killed by : none
removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE

2.2
Location : visitDocument
Killed by : none
removed call to java/util/stream/Stream::forEach → NO_COVERAGE

556

1.1
Location : visitDocument
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::visitPartIfExists → NO_COVERAGE

557

1.1
Location : visitDocument
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::visitPartIfExists → NO_COVERAGE

561

1.1
Location : streamHeaderFooterPart
Killed by : none
replaced return value with Stream.empty for pro/verron/officestamper/utils/wml/WmlUtils::streamHeaderFooterPart → NO_COVERAGE

571

1.1
Location : lambda$visitPartIfExists$0
Killed by : none
removed call to org/docx4j/TraversalUtil::visit → NO_COVERAGE

2.2
Location : visitPartIfExists
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

576

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

577

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

578

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

579

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

580

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

581

1.1
Location : extractHeaderFooterParts
Killed by : none
removed call to java/util/Optional::ifPresent → NO_COVERAGE

582

1.1
Location : extractHeaderFooterParts
Killed by : none
replaced return value with Stream.empty for pro/verron/officestamper/utils/wml/WmlUtils::extractHeaderFooterParts → NO_COVERAGE

587

1.1
Location : extractContent
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils::extractContent → NO_COVERAGE

605

1.1
Location : wrap
Killed by : none
negated conditional → NO_COVERAGE

609

1.1
Location : wrap
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

611

1.1
Location : wrap
Killed by : none
replaced return value with Collections.emptyList for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::wrap → NO_COVERAGE

618

1.1
Location : length
Killed by : none
replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::length → NO_COVERAGE

625

1.1
Location : getText
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::getText → NO_COVERAGE

632

1.1
Location : getPr
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::getPr → NO_COVERAGE

643

1.1
Location : isTouchedByRange
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : isTouchedByRange
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::isTouchedByRange → NO_COVERAGE

3.3
Location : isTouchedByRange
Killed by : none
negated conditional → NO_COVERAGE

644

1.1
Location : isTouchedByRange
Killed by : none
negated conditional → NO_COVERAGE

648

1.1
Location : startsInRange
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : startsInRange
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::startsInRange → NO_COVERAGE

3.3
Location : startsInRange
Killed by : none
changed conditional boundary → NO_COVERAGE

4.4
Location : startsInRange
Killed by : none
negated conditional → NO_COVERAGE

5.5
Location : startsInRange
Killed by : none
changed conditional boundary → NO_COVERAGE

652

1.1
Location : endsInRange
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : endsInRange
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : endsInRange
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::endsInRange → NO_COVERAGE

4.4
Location : endsInRange
Killed by : none
negated conditional → NO_COVERAGE

5.5
Location : endsInRange
Killed by : none
changed conditional boundary → NO_COVERAGE

656

1.1
Location : englobesRange
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : englobesRange
Killed by : none
changed conditional boundary → NO_COVERAGE

3.3
Location : englobesRange
Killed by : none
negated conditional → NO_COVERAGE

4.4
Location : englobesRange
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::englobesRange → NO_COVERAGE

5.5
Location : englobesRange
Killed by : none
changed conditional boundary → NO_COVERAGE

663

1.1
Location : endIndex
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

2.2
Location : endIndex
Killed by : none
replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::endIndex → NO_COVERAGE

673

1.1
Location : replace
Killed by : none
removed call to pro/verron/officestamper/utils/wml/WmlUtils::setText → NO_COVERAGE

685

1.1
Location : left
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::left → NO_COVERAGE

697

1.1
Location : right
Killed by : none
replaced return value with "" for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::right → NO_COVERAGE

707

1.1
Location : localize
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : localize
Killed by : none
negated conditional → NO_COVERAGE

708

1.1
Location : localize
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : localize
Killed by : none
replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::localize → NO_COVERAGE

3.3
Location : localize
Killed by : none
negated conditional → NO_COVERAGE

709

1.1
Location : localize
Killed by : none
replaced int return with 0 for pro/verron/officestamper/utils/wml/WmlUtils$StandardRun::localize → NO_COVERAGE

2.2
Location : localize
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.0