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
/// @since 3.0
37
public class XlsxIterator
38
        implements ResetableIterator<Object> {
39
40
    private static final Logger log = LoggerFactory.getLogger(XlsxIterator.class);
41
    private final Supplier<Iterator<?>> supplier;
42
    private final SpreadsheetMLPackage spreadsheet;
43
    private Queue<Iterator<?>> iteratorQueue;
44
    private @Nullable Object next;
45
46
47
    /// Constructs a new XlsxIterator for iterating through the elements of a SpreadsheetML package.
48
    ///
49
    /// @param spreadsheet the SpreadsheetMLPackage to iterate through
50
    /// @throws UtilsException if there's an error accessing the workbook contents
51
    public XlsxIterator(SpreadsheetMLPackage spreadsheet) {
52
        this.spreadsheet = spreadsheet;
53
        try {
54
            supplier = spreadsheet.getWorkbookPart()
55
                                  .getContents()
56
                                  .getSheets()
57
                                  .getSheet()::iterator;
58
        } catch (Docx4JException e) {
59
            throw new UtilsException(e);
60
        }
61
        var startingIterator = supplier.get();
62
        this.iteratorQueue = Collections.asLifoQueue(new ArrayDeque<>());
63
        this.iteratorQueue.add(startingIterator);
64 1 1. <init> : negated conditional → NO_COVERAGE
        this.next = startingIterator.hasNext() ? unwrap(startingIterator.next()) : null;
65
    }
66
67
    @Override
68
    public void reset() {
69
        var startingIterator = supplier.get();
70
        this.iteratorQueue = Collections.asLifoQueue(new ArrayDeque<>());
71
        this.iteratorQueue.add(startingIterator);
72 1 1. reset : negated conditional → NO_COVERAGE
        this.next = startingIterator.hasNext() ? unwrap(startingIterator.next()) : null;
73
    }
74
75
    @Override
76
    public boolean hasNext() {
77 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;
78
    }
79
80
    @Override
81
    public Object next() {
82 1 1. next : negated conditional → NO_COVERAGE
        if (next == null) throw new NoSuchElementException("No more elements to iterate");
83
84
        var result = next;
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 Sheet sheet -> {
102
                List<Row> content;
103
                try {
104
                    var sheetId = sheet.getId();
105
                    var relationshipsPart = spreadsheet.getWorkbookPart()
106
                                                       .getRelationshipsPart();
107
                    var part = relationshipsPart.getPart(sheetId);
108
                    content = ((WorksheetPart) part).getContents()
109
                                                    .getSheetData()
110
                                                    .getRow();
111
                } catch (Docx4JException e) {
112
                    throw new UtilsException(e);
113
                }
114
                iteratorQueue.add(content.iterator());
115
            }
116
            case Row row -> {
117
                var content = row.getC();
118
                iteratorQueue.add(content.iterator());
119
            }
120
            default -> log.debug("Unknown type: {}", result.getClass());
121
        }
122 2 1. next : negated conditional → NO_COVERAGE
2. next : negated conditional → NO_COVERAGE
        while (!iteratorQueue.isEmpty() && next == null) {
123
            var nextIterator = iteratorQueue.poll();
124 1 1. next : negated conditional → NO_COVERAGE
            if (nextIterator.hasNext()) {
125
                next = unwrap(nextIterator.next());
126
                iteratorQueue.add(nextIterator);
127
            }
128
        }
129 1 1. next : replaced return value with null for pro/verron/officestamper/utils/sml/XlsxIterator::next → NO_COVERAGE
        return result;
130
    }
131
}

Mutations

64

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

72

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

77

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

82

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

122

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

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

124

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

129

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.25.5 support