DocxIterator.java

1
package pro.verron.officestamper.utils.wml;
2
3
import org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTTextboxInfo;
4
import org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTWordprocessingShape;
5
import org.docx4j.dml.Graphic;
6
import org.docx4j.dml.GraphicData;
7
import org.docx4j.dml.wordprocessingDrawing.Anchor;
8
import org.docx4j.dml.wordprocessingDrawing.Inline;
9
import org.docx4j.mce.AlternateContent;
10
import org.docx4j.vml.CTTextbox;
11
import org.docx4j.vml.VmlShapeElements;
12
import org.docx4j.wml.*;
13
import org.jspecify.annotations.Nullable;
14
import pro.verron.officestamper.utils.iterator.ResetableIterator;
15
16
import java.util.*;
17
import java.util.function.Supplier;
18
19
import static org.docx4j.XmlUtils.unwrap;
20
21
/// An iterator that allows the traversal of objects within a
22
/// WordprocessingML-based document part. The iterator
23
/// supports nested structures, enabling iteration over content that may have
24
/// hierarchical data, like paragraphs,
25
/// structured document tags (SDTs), and runs.
26
///
27
/// This class implements the [ResetableIterator] interface, allowing for the
28
/// iteration to be reset to its initial
29
/// state, ensuring reusability of the same iterator instance.
30
public class DocxIterator implements ResetableIterator<Object> {
31
32
    private final Supplier<Iterator<Object>> supplier;
33
    private Queue<Iterator<?>> iteratorQueue;
34
    private @Nullable Object next;
35
36
    /// Creates a new [DocxIterator] instance that iterates over the content of
37
    /// the given [ContentAccessor].
38
    ///
39
    /// @param contentAccessor the content accessor whose content will be
40
    /// iterated over
41
    public DocxIterator(ContentAccessor contentAccessor) {
42
        this(contentAccessor.getContent()::iterator);
43
    }
44
45
    private DocxIterator(Supplier<Iterator<Object>> supplier) {
46
        this.supplier = supplier;
47 1 1. <init> : removed call to pro/verron/officestamper/utils/wml/DocxIterator::initialize → KILLED
        initialize();
48
    }
49
50
    private void initialize() {
51
        var startingIterator = supplier.get();
52
        this.iteratorQueue = Collections.asLifoQueue(new ArrayDeque<>());
53
        this.iteratorQueue.add(startingIterator);
54 1 1. initialize : negated conditional → KILLED
        this.next = startingIterator.hasNext() ? unwrap(startingIterator.next()) : null;
55
    }
56
57
    /// Selects and casts elements of the specified class type from the
58
    /// iterator.
59
    ///
60
    /// @param aClass the class type to filter and cast elements to
61
    /// @param <T>    the type of elements to select
62
    /// @return a new [ResetableIterator] containing only elements of the
63
    /// specified class type
64
    public <T> ResetableIterator<T> selectClass(Class<T> aClass) {
65 1 1. selectClass : replaced return value with null for pro/verron/officestamper/utils/wml/DocxIterator::selectClass → KILLED
        return filter(aClass::isInstance).map(aClass::cast);
66
    }
67
68
    @Override
69
    public void reset() {
70 1 1. reset : removed call to pro/verron/officestamper/utils/wml/DocxIterator::initialize → KILLED
        initialize();
71
    }
72
73
    @Override
74
    public boolean hasNext() {
75 2 1. hasNext : replaced boolean return with true for pro/verron/officestamper/utils/wml/DocxIterator::hasNext → KILLED
2. hasNext : negated conditional → KILLED
        return next != null;
76
    }
77
78
79
    @Override
80
    public Object next() {
81 1 1. next : negated conditional → KILLED
        if (next == null) throw new NoSuchElementException("No more elements to iterate");
82
83
        var result = next;
84
85
        next = null;
86
        switch (result) {
87
            case ContentAccessor contentAccessor -> {
88
                var content = contentAccessor.getContent();
89
                iteratorQueue.add(content.iterator());
90
            }
91
            case SdtRun sdtRun -> {
92
                var sdtContent = sdtRun.getSdtContent();
93
                var content = sdtContent.getContent();
94
                iteratorQueue.add(content.iterator());
95
            }
96
            case SdtBlock sdtBlock -> {
97
                var sdtContent = sdtBlock.getSdtContent();
98
                var content = sdtContent.getContent();
99
                iteratorQueue.add(content.iterator());
100
            }
101
            case Pict pict -> {
102
                var content = pict.getAnyAndAny();
103
                iteratorQueue.add(content.iterator());
104
            }
105
            case VmlShapeElements rr -> {
106
                var content = rr.getEGShapeElements();
107
                iteratorQueue.add(content.iterator());
108
            }
109
            case CTTextbox tb -> {
110
                var content = tb.getTxbxContent();
111
                var contentContent = content.getContent();
112
                iteratorQueue.add(contentContent.iterator());
113
            }
114
            case AlternateContent ac -> {
115
                var choiceList = ac.getChoice();
116
                iteratorQueue.add(choiceList.iterator());
117
                var fallback = ac.getFallback();
118
                var fallbackContent = fallback.getAny();
119
                iteratorQueue.add(fallbackContent.iterator());
120
            }
121
            case AlternateContent.Choice c -> {
122
                var content = c.getAny();
123
                iteratorQueue.add(content.iterator());
124
            }
125
            case Drawing d -> {
126
                var content = d.getAnchorOrInline();
127
                iteratorQueue.add(content.iterator());
128
            }
129
            case Anchor a -> {
130
                var content = List.of(a.getGraphic());
131
                iteratorQueue.add(content.iterator());
132
            }
133
            case Graphic g -> {
134
                var content = List.of(g.getGraphicData());
135
                iteratorQueue.add(content.iterator());
136
            }
137
            case GraphicData gd -> {
138
                var content = gd.getAny();
139
                iteratorQueue.add(content.iterator());
140
            }
141
            case CTWordprocessingShape ws -> {
142
                // Decorative shapes (e.g. wps:wsp without a textbox, common in footers) have no
143
                // txbx; getTxbx() is null and List.of(null) would throw an NPE. Skip them.
144
                var txbx = ws.getTxbx();
145 1 1. next : negated conditional → KILLED
                if (txbx != null) iteratorQueue.add(List.of(txbx).iterator());
146
            }
147
            case CTTextboxInfo ti -> {
148
                var content = List.of(ti.getTxbxContent());
149
                iteratorQueue.add(content.iterator());
150
            }
151
            case Inline i -> {
152
                var content = List.of(i.getGraphic());
153
                iteratorQueue.add(content.iterator());
154
            }
155
            case CTSdtCell c -> {
156
                var sdtContent = c.getSdtContent();
157
                var content = sdtContent.getContent();
158
                iteratorQueue.add(content.iterator());
159
            }
160 2 1. next : negated conditional → NO_COVERAGE
2. next : negated conditional → NO_COVERAGE
            case Text _, ProofErr _ -> { /*DO NOTHING*/ }
161
            default -> { /*DO NOTHING*/ }
162
        }
163 2 1. next : negated conditional → KILLED
2. next : negated conditional → KILLED
        while (!iteratorQueue.isEmpty() && next == null) {
164
            var nextIterator = iteratorQueue.poll();
165 1 1. next : negated conditional → KILLED
            if (nextIterator.hasNext()) {
166
                next = unwrap(nextIterator.next());
167
                iteratorQueue.add(nextIterator);
168
            }
169
        }
170 1 1. next : replaced return value with null for pro/verron/officestamper/utils/wml/DocxIterator::next → KILLED
        return result;
171
    }
172
}

Mutations

47

1.1
Location : <init>
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testHasNextReturnsTrueWhenThereIsElement()]
removed call to pro/verron/officestamper/utils/wml/DocxIterator::initialize → KILLED

54

1.1
Location : initialize
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testHasNextReturnsTrueWhenThereIsElement()]
negated conditional → KILLED

65

1.1
Location : selectClass
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testSelectClassFiltersElementsByType()]
replaced return value with null for pro/verron/officestamper/utils/wml/DocxIterator::selectClass → KILLED

70

1.1
Location : reset
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testResetResetsToInitialState()]
removed call to pro/verron/officestamper/utils/wml/DocxIterator::initialize → KILLED

75

1.1
Location : hasNext
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testNextReturnsCorrectFlatObject()]
replaced boolean return with true for pro/verron/officestamper/utils/wml/DocxIterator::hasNext → KILLED

2.2
Location : hasNext
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testHasNextReturnsTrueWhenThereIsElement()]
negated conditional → KILLED

81

1.1
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testNextThrowsExceptionWhenNoMoreElements()]
negated conditional → KILLED

145

1.1
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testNextHandlesWordprocessingShapeWithoutTextbox()]
negated conditional → KILLED

160

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

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

163

1.1
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testResetResetsToInitialState()]
negated conditional → KILLED

2.2
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testResetResetsToInitialState()]
negated conditional → KILLED

165

1.1
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testNextReturnsCorrectFlatObject()]
negated conditional → KILLED

170

1.1
Location : next
Killed by : pro.verron.officestamper.utils.wml.DocxIteratorTest.[engine:junit-jupiter]/[class:pro.verron.officestamper.utils.wml.DocxIteratorTest]/[method:testNextReturnsCorrectFlatObject()]
replaced return value with null for pro/verron/officestamper/utils/wml/DocxIterator::next → KILLED

Active mutators

Tests examined


Report generated by PIT 1.25.5 support