XlsxIterator.java

1
package pro.verron.officestamper.utils.sml;
2
3
import org.docx4j.openpackaging.exceptions.Docx4JException;
4
import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
5
import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
6
import org.docx4j.wml.ContentAccessor;
7
import org.docx4j.wml.SdtBlock;
8
import org.docx4j.wml.SdtRun;
9
import org.jspecify.annotations.Nullable;
10
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12
import org.xlsx4j.sml.Row;
13
import org.xlsx4j.sml.Sheet;
14
import pro.verron.officestamper.utils.UtilsException;
15
import pro.verron.officestamper.utils.iterator.ResetableIterator;
16
17
import java.util.*;
18
import java.util.function.Supplier;
19
20
import static org.docx4j.XmlUtils.unwrap;
21
22
/// XlsxIterator is an [ResetableIterator] implementation that iterates through the elements of an Excel spreadsheet
23
/// document.
24
///
25
/// This iterator handles various XML elements found in an Excel file, including:
26
///
27
///     - [Sheet] - worksheet elements that contain rows of data
28
///     - [Row] - individual rows containing cells
29
///     - [ContentAccessor] - elements that can contain other content
30
///     - [SdtRun] and [SdtBlock] - structured document tags
31
///
32
/// The iterator automatically traverses the hierarchical structure of the spreadsheet, unwrapping nested elements and
33
/// providing a flat iteration interface over all contained objects.
34
///
35
/// @author Joseph Verron
36
/// @version ${version}
37
/// @since 3.0
38
public class XlsxIterator
39
        implements ResetableIterator<Object> {
40
41
    private static final Logger log = LoggerFactory.getLogger(XlsxIterator.class);
42
    private final Supplier<Iterator<?>> supplier;
43
    private final SpreadsheetMLPackage spreadsheet;
44
    private Queue<Iterator<?>> iteratorQueue;
45
    private @Nullable Object next;
46
47
48
    /// Constructs a new XlsxIterator for iterating through the elements of a SpreadsheetML package.
49
    ///
50
    /// @param spreadsheet the SpreadsheetMLPackage to iterate through
51
    ///
52
    /// @throws UtilsException if there's an error accessing the workbook contents
53
    public XlsxIterator(SpreadsheetMLPackage spreadsheet) {
54
        this.spreadsheet = spreadsheet;
55
        try {
56
            supplier = spreadsheet.getWorkbookPart()
57
                                  .getContents()
58
                                  .getSheets()
59
                                  .getSheet()::iterator;
60
        } catch (Docx4JException e) {
61
            throw new UtilsException(e);
62
        }
63
        var startingIterator = supplier.get();
64
        this.iteratorQueue = Collections.asLifoQueue(new ArrayDeque<>());
65
        this.iteratorQueue.add(startingIterator);
66 1 1. <init> : negated conditional → NO_COVERAGE
        this.next = startingIterator.hasNext() ? unwrap(startingIterator.next()) : null;
67
    }
68
69
    @Override
70
    public void reset() {
71
        var startingIterator = supplier.get();
72
        this.iteratorQueue = Collections.asLifoQueue(new ArrayDeque<>());
73
        this.iteratorQueue.add(startingIterator);
74 1 1. reset : negated conditional → NO_COVERAGE
        this.next = startingIterator.hasNext() ? unwrap(startingIterator.next()) : null;
75
    }
76
77
    @Override
78
    public boolean hasNext() {
79 2 1. hasNext : negated conditional → NO_COVERAGE
2. hasNext : replaced boolean return with true for pro/verron/officestamper/utils/sml/XlsxIterator::hasNext → NO_COVERAGE
        return next != null;
80
    }
81
82
    @Override
83
    public Object next() {
84 1 1. next : negated conditional → NO_COVERAGE
        if (next == null) throw new NoSuchElementException("No more elements to iterate");
85
86
        var result = next;
87
        next = null;
88
        switch (result) {
89
            case ContentAccessor contentAccessor -> {
90
                var content = contentAccessor.getContent();
91
                iteratorQueue.add(content.iterator());
92
            }
93
            case SdtRun sdtRun -> {
94
                var sdtContent = sdtRun.getSdtContent();
95
                var content = sdtContent.getContent();
96
                iteratorQueue.add(content.iterator());
97
            }
98
            case SdtBlock sdtBlock -> {
99
                var sdtContent = sdtBlock.getSdtContent();
100
                var content = sdtContent.getContent();
101
                iteratorQueue.add(content.iterator());
102
            }
103
            case Sheet sheet -> {
104
                List<Row> content;
105
                try {
106
                    var sheetId = sheet.getId();
107
                    var relationshipsPart = spreadsheet.getWorkbookPart()
108
                                                       .getRelationshipsPart();
109
                    var part = relationshipsPart.getPart(sheetId);
110
                    content = ((WorksheetPart) part).getContents()
111
                                                    .getSheetData()
112
                                                    .getRow();
113
                } catch (Docx4JException e) {
114
                    throw new UtilsException(e);
115
                }
116
                iteratorQueue.add(content.iterator());
117
            }
118
            case Row row -> {
119
                var content = row.getC();
120
                iteratorQueue.add(content.iterator());
121
            }
122
            default -> log.debug("Unknown type: {}", result.getClass());
123
        }
124 2 1. next : negated conditional → NO_COVERAGE
2. next : negated conditional → NO_COVERAGE
        while (!iteratorQueue.isEmpty() && next == null) {
125
            var nextIterator = iteratorQueue.poll();
126 1 1. next : negated conditional → NO_COVERAGE
            if (nextIterator.hasNext()) {
127
                next = unwrap(nextIterator.next());
128
                iteratorQueue.add(nextIterator);
129
            }
130
        }
131 1 1. next : replaced return value with null for pro/verron/officestamper/utils/sml/XlsxIterator::next → NO_COVERAGE
        return result;
132
    }
133
}

Mutations

66

1.1
Location : <init>
Killed by : none
negated conditional → NO_COVERAGE

74

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

79

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

2.2
Location : hasNext
Killed by : none
replaced boolean return with true for pro/verron/officestamper/utils/sml/XlsxIterator::hasNext → NO_COVERAGE

84

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

124

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

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

126

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

131

1.1
Location : next
Killed by : none
replaced return value with null for pro/verron/officestamper/utils/sml/XlsxIterator::next → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.0