iterator() {
67 | return workbook.first().iterator();
68 | }
69 |
70 | /**
71 | * Closes the streaming resource, attempting to clean up any temporary files created.
72 | *
73 | * @throws com.monitorjbl.xlsx.exceptions.CloseException if there is an issue closing the stream
74 | */
75 | @Override
76 | public void close() throws IOException {
77 | try {
78 | workbook.close();
79 | } finally {
80 | if(tmp != null) {
81 | if (log.isDebugEnabled()) {
82 | log.debug("Deleting tmp file [" + tmp.getAbsolutePath() + "]");
83 | }
84 | tmp.delete();
85 | }
86 | }
87 | }
88 |
89 | public static Builder builder() {
90 | return new Builder();
91 | }
92 |
93 | public static class Builder {
94 | private int rowCacheSize = 10;
95 | private int bufferSize = 1024;
96 | private int sheetIndex = 0;
97 | private int sstCacheSizeBytes = -1;
98 | private String sheetName;
99 | private String password;
100 |
101 | public int getRowCacheSize() {
102 | return rowCacheSize;
103 | }
104 |
105 | public int getBufferSize() {
106 | return bufferSize;
107 | }
108 |
109 | /**
110 | * @return The sheet index
111 | * @deprecated This method will be removed in a future release.
112 | */
113 | public int getSheetIndex() {
114 | return sheetIndex;
115 | }
116 |
117 | /**
118 | * @return The sheet name
119 | * @deprecated This method will be removed in a future release.
120 | */
121 | public String getSheetName() {
122 | return sheetName;
123 | }
124 |
125 | /**
126 | * @return The password to use to unlock this workbook
127 | */
128 | public String getPassword() {
129 | return password;
130 | }
131 |
132 | /**
133 | * @return The size of the shared string table cache. If less than 0, no
134 | * cache will be used and the entire table will be loaded into memory.
135 | */
136 | public int getSstCacheSizeBytes() {
137 | return sstCacheSizeBytes;
138 | }
139 |
140 | /**
141 | * The number of rows to keep in memory at any given point.
142 | *
143 | * Defaults to 10
144 | *
145 | *
146 | * @param rowCacheSize number of rows
147 | * @return reference to current {@code Builder}
148 | */
149 | public Builder rowCacheSize(int rowCacheSize) {
150 | this.rowCacheSize = rowCacheSize;
151 | return this;
152 | }
153 |
154 | /**
155 | * The number of bytes to read into memory from the input
156 | * resource.
157 | *
158 | * Defaults to 1024
159 | *
160 | *
161 | * @param bufferSize buffer size in bytes
162 | * @return reference to current {@code Builder}
163 | */
164 | public Builder bufferSize(int bufferSize) {
165 | this.bufferSize = bufferSize;
166 | return this;
167 | }
168 |
169 | /**
170 | * Which sheet to open. There can only be one sheet open
171 | * for a single instance of {@code StreamingReader}. If
172 | * more sheets need to be read, a new instance must be
173 | * created.
174 | *
175 | * Defaults to 0
176 | *
177 | *
178 | * @param sheetIndex index of sheet
179 | * @return reference to current {@code Builder}
180 | * @deprecated This method will be removed in a future release. Use {@link StreamingWorkbook#getSheetAt(int)} instead.
181 | */
182 | public Builder sheetIndex(int sheetIndex) {
183 | this.sheetIndex = sheetIndex;
184 | return this;
185 | }
186 |
187 | /**
188 | * Which sheet to open. There can only be one sheet open
189 | * for a single instance of {@code StreamingReader}. If
190 | * more sheets need to be read, a new instance must be
191 | * created.
192 | *
193 | * @param sheetName name of sheet
194 | * @return reference to current {@code Builder}
195 | * @deprecated This method will be removed in a future release. Use {@link StreamingWorkbook#getSheet(String)} instead.
196 | */
197 | public Builder sheetName(String sheetName) {
198 | this.sheetName = sheetName;
199 | return this;
200 | }
201 |
202 | /**
203 | * For password protected files specify password to open file.
204 | * If the password is incorrect a {@code ReadException} is thrown on
205 | * {@code read}.
206 | * NULL indicates that no password should be used, this is the
207 | * default value.
208 | *
209 | * @param password to use when opening file
210 | * @return reference to current {@code Builder}
211 | */
212 | public Builder password(String password) {
213 | this.password = password;
214 | return this;
215 | }
216 |
217 | /**
218 | * !!! This option is experimental !!!
219 | *
220 | * Set the size of the Shared Strings Table cache. This option exists to accommodate
221 | * extremely large workbooks with millions of unique strings. Normally the SST is entirely
222 | * loaded into memory, but with large workbooks with high cardinality (i.e., very few
223 | * duplicate values) the SST may not fit entirely into memory.
224 | *
225 | * By default, the entire SST *will* be loaded into memory. Setting a value greater than
226 | * 0 for this option will only cache up to this many entries in memory. However,
227 | * enabling this option at all will have some noticeable performance degredation as you are
228 | * trading memory for disk space.
229 | *
230 | * @param sstCacheSizeBytes size of SST cache
231 | * @return reference to current {@code Builder}
232 | */
233 | public Builder sstCacheSizeBytes(int sstCacheSizeBytes) {
234 | this.sstCacheSizeBytes = sstCacheSizeBytes;
235 | return this;
236 | }
237 |
238 | /**
239 | * Reads a given {@code InputStream} and returns a new
240 | * instance of {@code Workbook}. Due to Apache POI
241 | * limitations, a temporary file must be written in order
242 | * to create a streaming iterator. This process will use
243 | * the same buffer size as specified in {@link #bufferSize(int)}.
244 | *
245 | * @param is input stream to read in
246 | * @return A {@link Workbook} that can be read from
247 | * @throws com.monitorjbl.xlsx.exceptions.ReadException if there is an issue reading the stream
248 | */
249 | public Workbook open(InputStream is) {
250 | StreamingWorkbookReader workbook = new StreamingWorkbookReader(this);
251 | workbook.init(is);
252 | return new StreamingWorkbook(workbook);
253 | }
254 |
255 | /**
256 | * Reads a given {@code File} and returns a new instance
257 | * of {@code Workbook}.
258 | *
259 | * @param file file to read in
260 | * @return built streaming reader instance
261 | * @throws com.monitorjbl.xlsx.exceptions.OpenException if there is an issue opening the file
262 | * @throws com.monitorjbl.xlsx.exceptions.ReadException if there is an issue reading the file
263 | */
264 | public Workbook open(File file) {
265 | StreamingWorkbookReader workbook = new StreamingWorkbookReader(this);
266 | workbook.init(file);
267 | return new StreamingWorkbook(workbook);
268 | }
269 |
270 | /**
271 | * Reads a given {@code InputStream} and returns a new
272 | * instance of {@code StreamingReader}. Due to Apache POI
273 | * limitations, a temporary file must be written in order
274 | * to create a streaming iterator. This process will use
275 | * the same buffer size as specified in {@link #bufferSize(int)}.
276 | *
277 | * @param is input stream to read in
278 | * @return built streaming reader instance
279 | * @throws com.monitorjbl.xlsx.exceptions.ReadException if there is an issue reading the stream
280 | * @deprecated This method will be removed in a future release. Use {@link Builder#open(InputStream)} instead
281 | */
282 | public StreamingReader read(InputStream is) {
283 | File f = null;
284 | try {
285 | f = writeInputStreamToFile(is, bufferSize);
286 | log.debug("Created temp file [" + f.getAbsolutePath() + "]");
287 |
288 | StreamingReader r = read(f);
289 | r.tmp = f;
290 | return r;
291 | } catch(IOException e) {
292 | throw new ReadException("Unable to read input stream", e);
293 | } catch(RuntimeException e) {
294 | if(f != null) {
295 | f.delete();
296 | }
297 | throw e;
298 | }
299 | }
300 |
301 | /**
302 | * Reads a given {@code File} and returns a new instance
303 | * of {@code StreamingReader}.
304 | *
305 | * @param f file to read in
306 | * @return built streaming reader instance
307 | * @throws com.monitorjbl.xlsx.exceptions.OpenException if there is an issue opening the file
308 | * @throws com.monitorjbl.xlsx.exceptions.ReadException if there is an issue reading the file
309 | * @deprecated This method will be removed in a future release. Use {@link Builder#open(File)} instead
310 | */
311 | public StreamingReader read(File f) {
312 | try {
313 | OPCPackage pkg;
314 | if(password != null) {
315 | // Based on: https://poi.apache.org/encryption.html
316 | POIFSFileSystem poifs = new POIFSFileSystem(f);
317 | EncryptionInfo info = new EncryptionInfo(poifs);
318 | Decryptor d = Decryptor.getInstance(info);
319 | d.verifyPassword(password);
320 | pkg = OPCPackage.open(d.getDataStream(poifs));
321 | } else {
322 | pkg = OPCPackage.open(f);
323 | }
324 |
325 | boolean use1904Dates = false;
326 | XSSFReader reader = new XSSFReader(pkg);
327 |
328 | SharedStringsTable sst;
329 | File sstCache = null;
330 | if(sstCacheSizeBytes > 0) {
331 | sstCache = Files.createTempFile("", "").toFile();
332 | log.debug("Created sst cache file [" + sstCache.getAbsolutePath() + "]");
333 | sst = BufferedStringsTable.getSharedStringsTable(sstCache, sstCacheSizeBytes, pkg);
334 | } else {
335 | sst = reader.getSharedStringsTable();
336 | }
337 |
338 | StylesTable styles = reader.getStylesTable();
339 | NodeList workbookPr = searchForNodeList(document(reader.getWorkbookData()), "/ss:workbook/ss:workbookPr");
340 | if (workbookPr.getLength() == 1) {
341 | final Node date1904 = workbookPr.item(0).getAttributes().getNamedItem("date1904");
342 | if (date1904 != null) {
343 | use1904Dates = ("1".equals(date1904.getTextContent()));
344 | }
345 | }
346 | InputStream sheet = findSheet(reader);
347 | if(sheet == null) {
348 | throw new MissingSheetException("Unable to find sheet at index [" + sheetIndex + "]");
349 | }
350 |
351 | XMLEventReader parser = StaxHelper.newXMLInputFactory().createXMLEventReader(sheet);
352 |
353 | return new StreamingReader(new StreamingWorkbookReader(sst, sstCache, pkg, new StreamingSheetReader(sst, styles, parser, use1904Dates, rowCacheSize),
354 | this));
355 | } catch(IOException e) {
356 | throw new OpenException("Failed to open file", e);
357 | } catch(OpenXML4JException | XMLStreamException e) {
358 | throw new ReadException("Unable to read workbook", e);
359 | } catch(GeneralSecurityException e) {
360 | throw new ReadException("Unable to read workbook - Decryption failed", e);
361 | }
362 | }
363 |
364 | /**
365 | * @deprecated This will be removed when the transition to the 1.x API is complete
366 | */
367 | private InputStream findSheet(XSSFReader reader) throws IOException, InvalidFormatException {
368 | int index = sheetIndex;
369 | if(sheetName != null) {
370 | index = -1;
371 | //This file is separate from the worksheet data, and should be fairly small
372 | NodeList nl = searchForNodeList(document(reader.getWorkbookData()), "/ss:workbook/ss:sheets/ss:sheet");
373 | for(int i = 0; i < nl.getLength(); i++) {
374 | if(Objects.equals(nl.item(i).getAttributes().getNamedItem("name").getTextContent(), sheetName)) {
375 | index = i;
376 | }
377 | }
378 | if(index < 0) {
379 | return null;
380 | }
381 | }
382 | Iterator iter = reader.getSheetsData();
383 | InputStream sheet = null;
384 |
385 | int i = 0;
386 | while(iter.hasNext()) {
387 | InputStream is = iter.next();
388 | if(i++ == index) {
389 | sheet = is;
390 | log.debug("Found sheet at index [" + sheetIndex + "]");
391 | break;
392 | }
393 | }
394 | return sheet;
395 | }
396 | }
397 |
398 | }
399 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/XmlUtils.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx;
2 |
3 | import com.monitorjbl.xlsx.exceptions.ParseException;
4 | import org.apache.poi.ooxml.util.DocumentHelper;
5 | import org.w3c.dom.Document;
6 | import org.w3c.dom.NodeList;
7 | import org.xml.sax.SAXException;
8 |
9 | import javax.xml.XMLConstants;
10 | import javax.xml.namespace.NamespaceContext;
11 | import javax.xml.xpath.XPath;
12 | import javax.xml.xpath.XPathConstants;
13 | import javax.xml.xpath.XPathExpressionException;
14 | import javax.xml.xpath.XPathFactory;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.util.*;
18 |
19 | public class XmlUtils {
20 | public static Document document(InputStream is) {
21 | try {
22 | return DocumentHelper.readDocument(is);
23 | } catch(SAXException | IOException e) {
24 | throw new ParseException(e);
25 | }
26 | }
27 |
28 | public static NodeList searchForNodeList(Document document, String xpath) {
29 | try {
30 | XPath xp = XPathFactory.newInstance().newXPath();
31 | NamespaceContextImpl nc = new NamespaceContextImpl();
32 | nc.addNamespace("ss", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
33 | xp.setNamespaceContext(nc);
34 | return (NodeList)xp.compile(xpath)
35 | .evaluate(document, XPathConstants.NODESET);
36 | } catch(XPathExpressionException e) {
37 | throw new ParseException(e);
38 | }
39 | }
40 |
41 | private static class NamespaceContextImpl implements NamespaceContext {
42 | private Map urisByPrefix = new HashMap();
43 |
44 | private Map prefixesByURI = new HashMap();
45 |
46 | public NamespaceContextImpl() {
47 | addNamespace(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
48 | addNamespace(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
49 | }
50 |
51 | public void addNamespace(String prefix, String namespaceURI) {
52 | urisByPrefix.put(prefix, namespaceURI);
53 | if (prefixesByURI.containsKey(namespaceURI)) {
54 | (prefixesByURI.get(namespaceURI)).add(prefix);
55 | } else {
56 | Set set = new HashSet();
57 | set.add(prefix);
58 | prefixesByURI.put(namespaceURI, set);
59 | }
60 | }
61 |
62 | public String getNamespaceURI(String prefix) {
63 | if (prefix == null)
64 | throw new IllegalArgumentException("prefix cannot be null");
65 | if (urisByPrefix.containsKey(prefix))
66 | return (String) urisByPrefix.get(prefix);
67 | else
68 | return XMLConstants.NULL_NS_URI;
69 | }
70 |
71 | public String getPrefix(String namespaceURI) {
72 | return (String) getPrefixes(namespaceURI).next();
73 | }
74 |
75 | public Iterator getPrefixes(String namespaceURI) {
76 | if (namespaceURI == null)
77 | throw new IllegalArgumentException("namespaceURI cannot be null");
78 | if (prefixesByURI.containsKey(namespaceURI)) {
79 | return ((Set) prefixesByURI.get(namespaceURI)).iterator();
80 | } else {
81 | return Collections.EMPTY_SET.iterator();
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/CloseException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class CloseException extends RuntimeException {
4 |
5 | public CloseException() {
6 | super();
7 | }
8 |
9 | public CloseException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public CloseException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public CloseException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/MissingSheetException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class MissingSheetException extends RuntimeException {
4 |
5 | public MissingSheetException() {
6 | super();
7 | }
8 |
9 | public MissingSheetException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public MissingSheetException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public MissingSheetException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/NotSupportedException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class NotSupportedException extends RuntimeException {
4 |
5 | public NotSupportedException() {
6 | super();
7 | }
8 |
9 | public NotSupportedException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public NotSupportedException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public NotSupportedException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/OpenException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class OpenException extends RuntimeException {
4 |
5 | public OpenException() {
6 | super();
7 | }
8 |
9 | public OpenException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public OpenException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public OpenException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/ParseException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class ParseException extends RuntimeException {
4 |
5 | public ParseException() {
6 | super();
7 | }
8 |
9 | public ParseException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public ParseException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public ParseException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/exceptions/ReadException.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.exceptions;
2 |
3 | public class ReadException extends RuntimeException {
4 |
5 | public ReadException() {
6 | super();
7 | }
8 |
9 | public ReadException(String msg) {
10 | super(msg);
11 | }
12 |
13 | public ReadException(Exception e) {
14 | super(e);
15 | }
16 |
17 | public ReadException(String msg, Exception e) {
18 | super(msg, e);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingCell.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import com.monitorjbl.xlsx.exceptions.NotSupportedException;
4 | import org.apache.poi.ss.formula.FormulaParseException;
5 | import org.apache.poi.ss.usermodel.Cell;
6 | import org.apache.poi.ss.usermodel.CellStyle;
7 | import org.apache.poi.ss.usermodel.CellType;
8 | import org.apache.poi.ss.usermodel.Comment;
9 | import org.apache.poi.ss.usermodel.DateUtil;
10 | import org.apache.poi.ss.usermodel.Hyperlink;
11 | import org.apache.poi.ss.usermodel.RichTextString;
12 | import org.apache.poi.ss.usermodel.Row;
13 | import org.apache.poi.ss.usermodel.Sheet;
14 | import org.apache.poi.ss.util.CellAddress;
15 | import org.apache.poi.ss.util.CellRangeAddress;
16 | import org.apache.poi.xssf.usermodel.XSSFRichTextString;
17 |
18 | import java.time.Instant;
19 | import java.time.LocalDateTime;
20 | import java.time.ZoneOffset;
21 | import java.util.Calendar;
22 | import java.util.Date;
23 |
24 | public class StreamingCell implements Cell {
25 |
26 | private static final Supplier NULL_SUPPLIER = () -> null;
27 | private static final String FALSE_AS_STRING = "0";
28 | private static final String TRUE_AS_STRING = "1";
29 |
30 | private final Sheet sheet;
31 | private int columnIndex;
32 | private int rowIndex;
33 | private final boolean use1904Dates;
34 |
35 | private Supplier contentsSupplier = NULL_SUPPLIER;
36 | private Object rawContents;
37 | private String formula;
38 | private String numericFormat;
39 | private Short numericFormatIndex;
40 | private String type;
41 | private CellStyle cellStyle;
42 | private Row row;
43 | private boolean formulaType;
44 |
45 | public StreamingCell(Sheet sheet, int columnIndex, int rowIndex, boolean use1904Dates) {
46 | this.sheet = sheet;
47 | this.columnIndex = columnIndex;
48 | this.rowIndex = rowIndex;
49 | this.use1904Dates = use1904Dates;
50 | }
51 |
52 | public void setContentSupplier(Supplier contentsSupplier) {
53 | this.contentsSupplier = contentsSupplier;
54 | }
55 |
56 | public void setRawContents(Object rawContents) {
57 | this.rawContents = rawContents;
58 | }
59 |
60 | public String getNumericFormat() {
61 | return numericFormat;
62 | }
63 |
64 | public void setNumericFormat(String numericFormat) {
65 | this.numericFormat = numericFormat;
66 | }
67 |
68 | public Short getNumericFormatIndex() {
69 | return numericFormatIndex;
70 | }
71 |
72 | public void setNumericFormatIndex(Short numericFormatIndex) {
73 | this.numericFormatIndex = numericFormatIndex;
74 | }
75 |
76 | public void setFormula(String formula) {
77 | this.formula = formula;
78 | }
79 |
80 | public String getType() {
81 | return type;
82 | }
83 |
84 | public void setType(String type) {
85 | this.type = type;
86 | }
87 |
88 | public boolean isFormulaType() {
89 | return formulaType;
90 | }
91 |
92 | public void setFormulaType(boolean formulaType) {
93 | this.formulaType = formulaType;
94 | }
95 |
96 | @Override
97 | public void setCellStyle(CellStyle cellStyle) {
98 | this.cellStyle = cellStyle;
99 | }
100 |
101 | /* Supported */
102 |
103 | /**
104 | * Returns column index of this cell
105 | *
106 | * @return zero-based column index of a column in a sheet.
107 | */
108 | @Override
109 | public int getColumnIndex() {
110 | return columnIndex;
111 | }
112 |
113 | /**
114 | * Returns row index of a row in the sheet that contains this cell
115 | *
116 | * @return zero-based row index of a row in the sheet that contains this cell
117 | */
118 | @Override
119 | public int getRowIndex() {
120 | return rowIndex;
121 | }
122 |
123 | /**
124 | * Returns the Row this cell belongs to. Note that keeping references to cell
125 | * rows around after the iterator window has passed will preserve them.
126 | *
127 | * @return the Row that owns this cell
128 | */
129 | @Override
130 | public Row getRow() {
131 | return row;
132 | }
133 |
134 | /**
135 | * Sets the Row this cell belongs to. Note that keeping references to cell
136 | * rows around after the iterator window has passed will preserve them.
137 | *
138 | * The row is not automatically set.
139 | *
140 | * @param row The row
141 | */
142 | public void setRow(Row row) {
143 | this.row = row;
144 | }
145 |
146 |
147 | /**
148 | * Return the cell type.
149 | *
150 | * @return the cell type
151 | */
152 | @Override
153 | public CellType getCellType() {
154 | if(formulaType) {
155 | return CellType.FORMULA;
156 | } else if(contentsSupplier.getContent() == null || type == null) {
157 | return CellType.BLANK;
158 | } else if("n".equals(type)) {
159 | return CellType.NUMERIC;
160 | } else if("s".equals(type) || "inlineStr".equals(type) || "str".equals(type)) {
161 | return CellType.STRING;
162 | } else if("str".equals(type)) {
163 | return CellType.FORMULA;
164 | } else if("b".equals(type)) {
165 | return CellType.BOOLEAN;
166 | } else if("e".equals(type)) {
167 | return CellType.ERROR;
168 | } else {
169 | throw new UnsupportedOperationException("Unsupported cell type '" + type + "'");
170 | }
171 | }
172 |
173 | /**
174 | * Get the value of the cell as a string.
175 | * For blank cells we return an empty string.
176 | *
177 | * @return the value of the cell as a string
178 | */
179 | @Override
180 | public String getStringCellValue() {
181 | Object c = contentsSupplier.getContent();
182 |
183 | return c == null ? "" : c.toString();
184 | }
185 |
186 | /**
187 | * Get the value of the cell as a number. For strings we throw an exception. For
188 | * blank cells we return a 0.
189 | *
190 | * @return the value of the cell as a number
191 | * @throws NumberFormatException if the cell value isn't a parsable double
.
192 | */
193 | @Override
194 | public double getNumericCellValue() {
195 | return rawContents == null ? 0.0 : Double.parseDouble((String) rawContents);
196 | }
197 |
198 | /**
199 | * Get the value of the cell as a date. For strings we throw an exception. For
200 | * blank cells we return a null.
201 | *
202 | * @return the value of the cell as a date
203 | * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is CELL_TYPE_STRING
204 | * @throws NumberFormatException if the cell value isn't a parsable double
.
205 | */
206 | @Override
207 | public Date getDateCellValue() {
208 | if(getCellType() == CellType.STRING) {
209 | throw new IllegalStateException("Cell type cannot be CELL_TYPE_STRING");
210 | }
211 | return rawContents == null ? null : DateUtil.getJavaDate(getNumericCellValue(), use1904Dates);
212 | }
213 |
214 | @Override
215 | public LocalDateTime getLocalDateTimeCellValue() {
216 | return LocalDateTime.ofInstant(Instant.ofEpochMilli(getDateCellValue().getTime()), ZoneOffset.systemDefault());
217 | }
218 |
219 | /**
220 | * Get the value of the cell as a boolean. For strings we throw an exception. For
221 | * blank cells we return a false.
222 | *
223 | * @return the value of the cell as a date
224 | */
225 | @Override
226 | public boolean getBooleanCellValue() {
227 | CellType cellType = getCellType();
228 | switch(cellType) {
229 | case BLANK:
230 | return false;
231 | case BOOLEAN:
232 | return rawContents != null && TRUE_AS_STRING.equals(rawContents);
233 | case FORMULA:
234 | throw new NotSupportedException();
235 | default:
236 | throw typeMismatch(CellType.BOOLEAN, cellType, false);
237 | }
238 | }
239 |
240 | /**
241 | * Get the value of the cell as a XSSFRichTextString
242 | *
243 | * For numeric cells we throw an exception. For blank cells we return an empty string.
244 | * For formula cells we return the pre-calculated value if a string, otherwise an exception
245 | *
246 | *
247 | * @return the value of the cell as a XSSFRichTextString
248 | */
249 | @Override
250 | public XSSFRichTextString getRichStringCellValue() {
251 | CellType cellType = getCellType();
252 | XSSFRichTextString rt;
253 | switch(cellType) {
254 | case BLANK:
255 | rt = new XSSFRichTextString("");
256 | break;
257 | case STRING:
258 | rt = new XSSFRichTextString(getStringCellValue());
259 | break;
260 | default:
261 | throw new NotSupportedException();
262 | }
263 | return rt;
264 | }
265 |
266 | @Override
267 | public Sheet getSheet() {
268 | return sheet;
269 | }
270 |
271 | private static RuntimeException typeMismatch(CellType expectedType, CellType actualType, boolean isFormulaCell) {
272 | String msg = "Cannot get a "
273 | + getCellTypeName(expectedType) + " value from a "
274 | + getCellTypeName(actualType) + " " + (isFormulaCell ? "formula " : "") + "cell";
275 | return new IllegalStateException(msg);
276 | }
277 |
278 | /**
279 | * Used to help format error messages
280 | */
281 | private static String getCellTypeName(CellType cellType) {
282 | switch(cellType) {
283 | case BLANK:
284 | return "blank";
285 | case STRING:
286 | return "text";
287 | case BOOLEAN:
288 | return "boolean";
289 | case ERROR:
290 | return "error";
291 | case NUMERIC:
292 | return "numeric";
293 | case FORMULA:
294 | return "formula";
295 | }
296 | return "#unknown cell type (" + cellType + ")#";
297 | }
298 |
299 | /**
300 | * @return the style of the cell
301 | */
302 | @Override
303 | public CellStyle getCellStyle() {
304 | return this.cellStyle;
305 | }
306 |
307 | /**
308 | * Return a formula for the cell, for example, SUM(C4:E4)
309 | *
310 | * @return a formula for the cell
311 | * @throws IllegalStateException if the cell type returned by {@link #getCellType()} is not CELL_TYPE_FORMULA
312 | */
313 | @Override
314 | public String getCellFormula() {
315 | if(!formulaType)
316 | throw new IllegalStateException("This cell does not have a formula");
317 | return formula;
318 | }
319 |
320 | /**
321 | * Only valid for formula cells
322 | *
323 | * @return one of ({@link CellType#NUMERIC}, {@link CellType#STRING},
324 | * {@link CellType#BOOLEAN}, {@link CellType#ERROR}) depending
325 | * on the cached value of the formula
326 | */
327 | @Override
328 | public CellType getCachedFormulaResultType() {
329 | if(formulaType) {
330 | if(contentsSupplier.getContent() == null || type == null) {
331 | return CellType.BLANK;
332 | } else if("n".equals(type)) {
333 | return CellType.NUMERIC;
334 | } else if("s".equals(type) || "inlineStr".equals(type) || "str".equals(type)) {
335 | return CellType.STRING;
336 | } else if("b".equals(type)) {
337 | return CellType.BOOLEAN;
338 | } else if("e".equals(type)) {
339 | return CellType.ERROR;
340 | } else {
341 | throw new UnsupportedOperationException("Unsupported cell type '" + type + "'");
342 | }
343 | } else {
344 | throw new IllegalStateException("Only formula cells have cached results");
345 | }
346 | }
347 |
348 | /* Not supported */
349 |
350 | /**
351 | * Not supported
352 | */
353 | @Override
354 | public void setCellType(CellType cellType) {
355 | throw new NotSupportedException();
356 | }
357 |
358 | /**
359 | * Not supported
360 | */
361 | @Override
362 | public void setCellValue(double value) {
363 | throw new NotSupportedException();
364 | }
365 |
366 | /**
367 | * Not supported
368 | */
369 | @Override
370 | public void setCellValue(Date value) {
371 | throw new NotSupportedException();
372 | }
373 |
374 | /**
375 | * Not supported
376 | */
377 | @Override
378 | public void setCellValue(LocalDateTime value) {
379 | throw new NotSupportedException();
380 | }
381 |
382 | /**
383 | * Not supported
384 | */
385 | @Override
386 | public void setCellValue(Calendar value) {
387 | throw new NotSupportedException();
388 | }
389 |
390 | /**
391 | * Not supported
392 | */
393 | @Override
394 | public void setCellValue(RichTextString value) {
395 | throw new NotSupportedException();
396 | }
397 |
398 | /**
399 | * Not supported
400 | */
401 | @Override
402 | public void setCellValue(String value) {
403 | throw new NotSupportedException();
404 | }
405 |
406 | /**
407 | * Not supported
408 | */
409 | @Override
410 | public void setCellFormula(String formula) throws FormulaParseException {
411 | throw new NotSupportedException();
412 | }
413 |
414 | /**
415 | * Not supported
416 | */
417 | @Override
418 | public void setCellValue(boolean value) {
419 | throw new NotSupportedException();
420 | }
421 |
422 | /**
423 | * Not supported
424 | */
425 | @Override
426 | public void setCellErrorValue(byte value) {
427 | throw new NotSupportedException();
428 | }
429 |
430 | /**
431 | * Not supported
432 | */
433 | @Override
434 | public byte getErrorCellValue() {
435 | throw new NotSupportedException();
436 | }
437 |
438 | /**
439 | * Not supported
440 | */
441 | @Override
442 | public void setAsActiveCell() {
443 | throw new NotSupportedException();
444 | }
445 |
446 | /**
447 | * Not supported
448 | */
449 | @Override
450 | public CellAddress getAddress() {
451 | throw new NotSupportedException();
452 | }
453 |
454 | /**
455 | * Not supported
456 | */
457 | @Override
458 | public void setCellComment(Comment comment) {
459 | throw new NotSupportedException();
460 | }
461 |
462 | /**
463 | * Not supported
464 | */
465 | @Override
466 | public Comment getCellComment() {
467 | throw new NotSupportedException();
468 | }
469 |
470 | /**
471 | * Not supported
472 | */
473 | @Override
474 | public void removeCellComment() {
475 | throw new NotSupportedException();
476 | }
477 |
478 | /**
479 | * Not supported
480 | */
481 | @Override
482 | public Hyperlink getHyperlink() {
483 | throw new NotSupportedException();
484 | }
485 |
486 | /**
487 | * Not supported
488 | */
489 | @Override
490 | public void setHyperlink(Hyperlink link) {
491 | throw new NotSupportedException();
492 | }
493 |
494 | /**
495 | * Not supported
496 | */
497 | @Override
498 | public void removeHyperlink() {
499 | throw new NotSupportedException();
500 | }
501 |
502 | /**
503 | * Not supported
504 | */
505 | @Override
506 | public CellRangeAddress getArrayFormulaRange() {
507 | throw new NotSupportedException();
508 | }
509 |
510 | /**
511 | * Not supported
512 | */
513 | @Override
514 | public boolean isPartOfArrayFormulaGroup() {
515 | throw new NotSupportedException();
516 | }
517 |
518 | /**
519 | * Not supported
520 | */
521 | @Override
522 | public void setBlank() {
523 | throw new NotSupportedException();
524 | }
525 |
526 | /**
527 | * Not supported
528 | */
529 | @Override
530 | public void removeFormula() throws IllegalStateException {
531 | throw new NotSupportedException();
532 | }
533 | }
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingRow.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import com.monitorjbl.xlsx.exceptions.NotSupportedException;
4 | import org.apache.poi.ss.usermodel.Cell;
5 | import org.apache.poi.ss.usermodel.CellStyle;
6 | import org.apache.poi.ss.usermodel.CellType;
7 | import org.apache.poi.ss.usermodel.Row;
8 | import org.apache.poi.ss.usermodel.Sheet;
9 |
10 | import java.util.Iterator;
11 | import java.util.Map;
12 | import java.util.TreeMap;
13 |
14 | public class StreamingRow implements Row {
15 | private final Sheet sheet;
16 | private int rowIndex;
17 | private boolean isHidden;
18 | private TreeMap cellMap = new TreeMap<>();
19 |
20 | public StreamingRow(Sheet sheet, int rowIndex, boolean isHidden) {
21 | this.sheet = sheet;
22 | this.rowIndex = rowIndex;
23 | this.isHidden = isHidden;
24 | }
25 |
26 | @Override
27 | public Sheet getSheet() {
28 | return sheet;
29 | }
30 |
31 | public Map getCellMap() {
32 | return cellMap;
33 | }
34 |
35 | public void setCellMap(TreeMap cellMap) {
36 | this.cellMap = cellMap;
37 | }
38 |
39 | /* Supported */
40 |
41 | /**
42 | * Get row number this row represents
43 | *
44 | * @return the row number (0 based)
45 | */
46 | @Override
47 | public int getRowNum() {
48 | return rowIndex;
49 | }
50 |
51 | /**
52 | * @return Cell iterator of the physically defined cells for this row.
53 | */
54 | @Override
55 | public Iterator cellIterator() {
56 | return cellMap.values().iterator();
57 | }
58 |
59 | /**
60 | * @return Cell iterator of the physically defined cells for this row.
61 | */
62 | @Override
63 | public Iterator iterator() {
64 | return cellMap.values().iterator();
65 | }
66 |
67 | /**
68 | * Get the cell representing a given column (logical cell) 0-based. If you
69 | * ask for a cell that is not defined, you get a null.
70 | *
71 | * @param cellnum 0 based column number
72 | * @return Cell representing that column or null if undefined.
73 | */
74 | @Override
75 | public Cell getCell(int cellnum) {
76 | return cellMap.get(cellnum);
77 | }
78 |
79 | /**
80 | * Gets the index of the last cell contained in this row PLUS ONE.
81 | *
82 | * @return short representing the last logical cell in the row PLUS ONE,
83 | * or -1 if the row does not contain any cells.
84 | */
85 | @Override
86 | public short getLastCellNum() {
87 | return (short) (cellMap.size() == 0 ? -1 : cellMap.lastEntry().getValue().getColumnIndex() + 1);
88 | }
89 |
90 | /**
91 | * Get whether or not to display this row with 0 height
92 | *
93 | * @return - zHeight height is zero or not.
94 | */
95 | @Override
96 | public boolean getZeroHeight() {
97 | return isHidden;
98 | }
99 |
100 | /**
101 | * Gets the number of defined cells (NOT number of cells in the actual row!).
102 | * That is to say if only columns 0,4,5 have values then there would be 3.
103 | *
104 | * @return int representing the number of defined cells in the row.
105 | */
106 | @Override
107 | public int getPhysicalNumberOfCells() {
108 | return cellMap.size();
109 | }
110 |
111 | /**
112 | * {@inheritDoc}
113 | */
114 | @Override
115 | public short getFirstCellNum() {
116 | if(cellMap.size() == 0) {
117 | return -1;
118 | }
119 | return cellMap.firstKey().shortValue();
120 | }
121 |
122 | /**
123 | * {@inheritDoc}
124 | */
125 | @Override
126 | public Cell getCell(int cellnum, MissingCellPolicy policy) {
127 | StreamingCell cell = (StreamingCell) cellMap.get(cellnum);
128 | if(policy == MissingCellPolicy.CREATE_NULL_AS_BLANK) {
129 | if(cell == null) { return new StreamingCell(sheet, cellnum, rowIndex, false); }
130 | } else if(policy == MissingCellPolicy.RETURN_BLANK_AS_NULL) {
131 | if(cell == null || cell.getCellType() == CellType.BLANK) { return null; }
132 | }
133 | return cell;
134 | }
135 |
136 | /* Not supported */
137 |
138 | /**
139 | * Not supported
140 | */
141 | @Override
142 | public Cell createCell(int column) {
143 | throw new NotSupportedException();
144 | }
145 |
146 | /**
147 | * Not supported
148 | */
149 | @Override
150 | public Cell createCell(int i, CellType cellType) {
151 | throw new NotSupportedException();
152 | }
153 |
154 | /**
155 | * Not supported
156 | */
157 | @Override
158 | public void removeCell(Cell cell) {
159 | throw new NotSupportedException();
160 | }
161 |
162 | /**
163 | * Not supported
164 | */
165 | @Override
166 | public void setRowNum(int rowNum) {
167 | throw new NotSupportedException();
168 | }
169 |
170 | /**
171 | * Not supported
172 | */
173 | @Override
174 | public void setHeight(short height) {
175 | throw new NotSupportedException();
176 | }
177 |
178 | /**
179 | * Not supported
180 | */
181 | @Override
182 | public void setZeroHeight(boolean zHeight) {
183 | throw new NotSupportedException();
184 | }
185 |
186 | /**
187 | * Not supported
188 | */
189 | @Override
190 | public void setHeightInPoints(float height) {
191 | throw new NotSupportedException();
192 | }
193 |
194 | /**
195 | * Not supported
196 | */
197 | @Override
198 | public short getHeight() {
199 | throw new NotSupportedException();
200 | }
201 |
202 | /**
203 | * Not supported
204 | */
205 | @Override
206 | public float getHeightInPoints() {
207 | throw new NotSupportedException();
208 | }
209 |
210 | /**
211 | * Not supported
212 | */
213 | @Override
214 | public boolean isFormatted() {
215 | throw new NotSupportedException();
216 | }
217 |
218 | /**
219 | * Not supported
220 | */
221 | @Override
222 | public CellStyle getRowStyle() {
223 | throw new NotSupportedException();
224 | }
225 |
226 | /**
227 | * Not supported
228 | */
229 | @Override
230 | public void setRowStyle(CellStyle style) {
231 | throw new NotSupportedException();
232 | }
233 |
234 | /**
235 | * Not supported
236 | */
237 | @Override
238 | public int getOutlineLevel() {
239 | throw new NotSupportedException();
240 | }
241 |
242 | /**
243 | * Not supported
244 | */
245 | @Override
246 | public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) {
247 | throw new NotSupportedException();
248 | }
249 |
250 | /**
251 | * Not supported
252 | */
253 | @Override
254 | public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) {
255 | throw new NotSupportedException();
256 | }
257 |
258 | }
259 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingSheet.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import org.apache.poi.ss.util.PaneInformation;
4 | import org.apache.poi.ss.usermodel.AutoFilter;
5 | import org.apache.poi.ss.usermodel.Cell;
6 | import org.apache.poi.ss.usermodel.CellRange;
7 | import org.apache.poi.ss.usermodel.CellStyle;
8 | import org.apache.poi.ss.usermodel.Comment;
9 | import org.apache.poi.ss.usermodel.DataValidation;
10 | import org.apache.poi.ss.usermodel.DataValidationHelper;
11 | import org.apache.poi.ss.usermodel.Drawing;
12 | import org.apache.poi.ss.usermodel.Footer;
13 | import org.apache.poi.ss.usermodel.Header;
14 | import org.apache.poi.ss.usermodel.Hyperlink;
15 | import org.apache.poi.ss.usermodel.PrintSetup;
16 | import org.apache.poi.ss.usermodel.Row;
17 | import org.apache.poi.ss.usermodel.Sheet;
18 | import org.apache.poi.ss.usermodel.SheetConditionalFormatting;
19 | import org.apache.poi.ss.usermodel.Workbook;
20 | import org.apache.poi.ss.util.CellAddress;
21 | import org.apache.poi.ss.util.CellRangeAddress;
22 |
23 | import java.util.Collection;
24 | import java.util.Iterator;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 | public class StreamingSheet implements Sheet {
29 |
30 | private final String name;
31 | private final StreamingSheetReader reader;
32 |
33 | public StreamingSheet(String name, StreamingSheetReader reader) {
34 | this.name = name;
35 | this.reader = reader;
36 | reader.setSheet(this);
37 | }
38 |
39 | StreamingSheetReader getReader() {
40 | return reader;
41 | }
42 |
43 | /* Supported */
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | @Override
49 | public Iterator iterator() {
50 | return reader.iterator();
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | @Override
57 | public Iterator rowIterator() {
58 | return reader.iterator();
59 | }
60 |
61 | /**
62 | * {@inheritDoc}
63 | */
64 | @Override
65 | public String getSheetName() {
66 | return name;
67 | }
68 |
69 | /**
70 | * Get the hidden state for a given column
71 | *
72 | * @param columnIndex - the column to set (0-based)
73 | * @return hidden - false if the column is visible
74 | */
75 | @Override
76 | public boolean isColumnHidden(int columnIndex) {
77 | return reader.isColumnHidden(columnIndex);
78 | }
79 |
80 | /* Unsupported */
81 |
82 | /**
83 | * Not supported
84 | */
85 | @Override
86 | public Row createRow(int rownum) {
87 | throw new UnsupportedOperationException();
88 | }
89 |
90 | /**
91 | * Not supported
92 | */
93 | @Override
94 | public void removeRow(Row row) {
95 | throw new UnsupportedOperationException();
96 | }
97 |
98 | /**
99 | * Not supported
100 | */
101 | @Override
102 | public Row getRow(int rownum) {
103 | throw new UnsupportedOperationException();
104 | }
105 |
106 | /**
107 | * Not supported
108 | */
109 | @Override
110 | public int getPhysicalNumberOfRows() {
111 | throw new UnsupportedOperationException();
112 | }
113 |
114 | /**
115 | * Not supported
116 | */
117 | @Override
118 | public int getFirstRowNum() {
119 | throw new UnsupportedOperationException();
120 | }
121 |
122 | /**
123 | * Gets the last row on the sheet
124 | *
125 | * @return last row contained n this sheet (0-based)
126 | */
127 | @Override
128 | public int getLastRowNum() {
129 | return reader.getLastRowNum();
130 | }
131 |
132 | /**
133 | * Not supported
134 | */
135 | @Override
136 | public void setColumnHidden(int columnIndex, boolean hidden) {
137 | throw new UnsupportedOperationException();
138 | }
139 |
140 | /**
141 | * Not supported
142 | */
143 | @Override
144 | public void setRightToLeft(boolean value) {
145 | throw new UnsupportedOperationException();
146 | }
147 |
148 | /**
149 | * Not supported
150 | */
151 | @Override
152 | public boolean isRightToLeft() {
153 | throw new UnsupportedOperationException();
154 | }
155 |
156 | /**
157 | * Not supported
158 | */
159 | @Override
160 | public void setColumnWidth(int columnIndex, int width) {
161 | throw new UnsupportedOperationException();
162 | }
163 |
164 | /**
165 | * Not supported
166 | */
167 | @Override
168 | public int getColumnWidth(int columnIndex) {
169 | throw new UnsupportedOperationException();
170 | }
171 |
172 | /**
173 | * Not supported
174 | */
175 | @Override
176 | public float getColumnWidthInPixels(int columnIndex) {
177 | throw new UnsupportedOperationException();
178 | }
179 |
180 | /**
181 | * Not supported
182 | */
183 | @Override
184 | public void setDefaultColumnWidth(int width) {
185 | throw new UnsupportedOperationException();
186 | }
187 |
188 | /**
189 | * Not supported
190 | */
191 | @Override
192 | public int getDefaultColumnWidth() {
193 | throw new UnsupportedOperationException();
194 | }
195 |
196 | /**
197 | * Not supported
198 | */
199 | @Override
200 | public short getDefaultRowHeight() {
201 | throw new UnsupportedOperationException();
202 | }
203 |
204 | /**
205 | * Not supported
206 | */
207 | @Override
208 | public float getDefaultRowHeightInPoints() {
209 | throw new UnsupportedOperationException();
210 | }
211 |
212 | /**
213 | * Not supported
214 | */
215 | @Override
216 | public void setDefaultRowHeight(short height) {
217 | throw new UnsupportedOperationException();
218 | }
219 |
220 | /**
221 | * Not supported
222 | */
223 | @Override
224 | public void setDefaultRowHeightInPoints(float height) {
225 | throw new UnsupportedOperationException();
226 | }
227 |
228 | /**
229 | * Not supported
230 | */
231 | @Override
232 | public CellStyle getColumnStyle(int column) {
233 | throw new UnsupportedOperationException();
234 | }
235 |
236 | /**
237 | * Not supported
238 | */
239 | @Override
240 | public int addMergedRegion(CellRangeAddress region) {
241 | throw new UnsupportedOperationException();
242 | }
243 |
244 | /**
245 | * Not supported
246 | */
247 | @Override
248 | public int addMergedRegionUnsafe(CellRangeAddress cellRangeAddress) {
249 | throw new UnsupportedOperationException();
250 | }
251 |
252 | /**
253 | * Not supported
254 | */
255 | @Override
256 | public void validateMergedRegions() {
257 | throw new UnsupportedOperationException();
258 | }
259 |
260 | /**
261 | * Not supported
262 | */
263 | @Override
264 | public void setVerticallyCenter(boolean value) {
265 | throw new UnsupportedOperationException();
266 | }
267 |
268 | /**
269 | * Not supported
270 | */
271 | @Override
272 | public void setHorizontallyCenter(boolean value) {
273 | throw new UnsupportedOperationException();
274 | }
275 |
276 | /**
277 | * Not supported
278 | */
279 | @Override
280 | public boolean getHorizontallyCenter() {
281 | throw new UnsupportedOperationException();
282 | }
283 |
284 | /**
285 | * Not supported
286 | */
287 | @Override
288 | public boolean getVerticallyCenter() {
289 | throw new UnsupportedOperationException();
290 | }
291 |
292 | /**
293 | * Not supported
294 | */
295 | @Override
296 | public void removeMergedRegion(int index) {
297 | throw new UnsupportedOperationException();
298 | }
299 |
300 | /**
301 | * Not supported
302 | */
303 | @Override
304 | public void removeMergedRegions(Collection collection) {
305 | throw new UnsupportedOperationException();
306 | }
307 |
308 | /**
309 | * Not supported
310 | */
311 | @Override
312 | public int getNumMergedRegions() {
313 | throw new UnsupportedOperationException();
314 | }
315 |
316 | /**
317 | * Not supported
318 | */
319 | @Override
320 | public CellRangeAddress getMergedRegion(int index) {
321 | throw new UnsupportedOperationException();
322 | }
323 |
324 | /**
325 | * Not supported
326 | */
327 | @Override
328 | public List getMergedRegions() {
329 | throw new UnsupportedOperationException();
330 | }
331 |
332 | /**
333 | * Not supported
334 | */
335 | @Override
336 | public void setForceFormulaRecalculation(boolean value) {
337 | throw new UnsupportedOperationException();
338 | }
339 |
340 | /**
341 | * Not supported
342 | */
343 | @Override
344 | public boolean getForceFormulaRecalculation() {
345 | throw new UnsupportedOperationException();
346 | }
347 |
348 | /**
349 | * Not supported
350 | */
351 | @Override
352 | public void setAutobreaks(boolean value) {
353 | throw new UnsupportedOperationException();
354 | }
355 |
356 | /**
357 | * Not supported
358 | */
359 | @Override
360 | public void setDisplayGuts(boolean value) {
361 | throw new UnsupportedOperationException();
362 | }
363 |
364 | /**
365 | * Not supported
366 | */
367 | @Override
368 | public void setDisplayZeros(boolean value) {
369 | throw new UnsupportedOperationException();
370 | }
371 |
372 | /**
373 | * Not supported
374 | */
375 | @Override
376 | public boolean isDisplayZeros() {
377 | throw new UnsupportedOperationException();
378 | }
379 |
380 | /**
381 | * Not supported
382 | */
383 | @Override
384 | public void setFitToPage(boolean value) {
385 | throw new UnsupportedOperationException();
386 | }
387 |
388 | /**
389 | * Not supported
390 | */
391 | @Override
392 | public void setRowSumsBelow(boolean value) {
393 | throw new UnsupportedOperationException();
394 | }
395 |
396 | /**
397 | * Not supported
398 | */
399 | @Override
400 | public void setRowSumsRight(boolean value) {
401 | throw new UnsupportedOperationException();
402 | }
403 |
404 | /**
405 | * Not supported
406 | */
407 | @Override
408 | public boolean getAutobreaks() {
409 | throw new UnsupportedOperationException();
410 | }
411 |
412 | /**
413 | * Not supported
414 | */
415 | @Override
416 | public boolean getDisplayGuts() {
417 | throw new UnsupportedOperationException();
418 | }
419 |
420 | /**
421 | * Not supported
422 | */
423 | @Override
424 | public boolean getFitToPage() {
425 | throw new UnsupportedOperationException();
426 | }
427 |
428 | /**
429 | * Not supported
430 | */
431 | @Override
432 | public boolean getRowSumsBelow() {
433 | throw new UnsupportedOperationException();
434 | }
435 |
436 | /**
437 | * Not supported
438 | */
439 | @Override
440 | public boolean getRowSumsRight() {
441 | throw new UnsupportedOperationException();
442 | }
443 |
444 | /**
445 | * Not supported
446 | */
447 | @Override
448 | public boolean isPrintGridlines() {
449 | throw new UnsupportedOperationException();
450 | }
451 |
452 | /**
453 | * Not supported
454 | */
455 | @Override
456 | public void setPrintGridlines(boolean show) {
457 | throw new UnsupportedOperationException();
458 | }
459 |
460 | /**
461 | * Not supported
462 | */
463 | @Override
464 | public boolean isPrintRowAndColumnHeadings() {
465 | throw new UnsupportedOperationException();
466 | }
467 |
468 | /**
469 | * Not supported
470 | */
471 | @Override
472 | public void setPrintRowAndColumnHeadings(boolean b) {
473 | throw new UnsupportedOperationException();
474 | }
475 |
476 | /**
477 | * Not supported
478 | */
479 | @Override
480 | public PrintSetup getPrintSetup() {
481 | throw new UnsupportedOperationException();
482 | }
483 |
484 | /**
485 | * Not supported
486 | */
487 | @Override
488 | public Header getHeader() {
489 | throw new UnsupportedOperationException();
490 | }
491 |
492 | /**
493 | * Not supported
494 | */
495 | @Override
496 | public Footer getFooter() {
497 | throw new UnsupportedOperationException();
498 | }
499 |
500 | /**
501 | * Not supported
502 | */
503 | @Override
504 | public void setSelected(boolean value) {
505 | throw new UnsupportedOperationException();
506 | }
507 |
508 | /**
509 | * Not supported
510 | */
511 | @Override
512 | public double getMargin(short margin) {
513 | throw new UnsupportedOperationException();
514 | }
515 |
516 | /**
517 | * Not supported
518 | */
519 | @Override
520 | public void setMargin(short margin, double size) {
521 | throw new UnsupportedOperationException();
522 | }
523 |
524 | /**
525 | * Not supported
526 | */
527 | @Override
528 | public boolean getProtect() {
529 | throw new UnsupportedOperationException();
530 | }
531 |
532 | /**
533 | * Not supported
534 | */
535 | @Override
536 | public void protectSheet(String password) {
537 | throw new UnsupportedOperationException();
538 | }
539 |
540 | /**
541 | * Not supported
542 | */
543 | @Override
544 | public boolean getScenarioProtect() {
545 | throw new UnsupportedOperationException();
546 | }
547 |
548 | /**
549 | * Not supported
550 | */
551 | @Override
552 | public void setZoom(int i) {
553 | throw new UnsupportedOperationException();
554 | }
555 |
556 | /**
557 | * Not supported
558 | */
559 | @Override
560 | public short getTopRow() {
561 | throw new UnsupportedOperationException();
562 | }
563 |
564 | /**
565 | * Not supported
566 | */
567 | @Override
568 | public short getLeftCol() {
569 | throw new UnsupportedOperationException();
570 | }
571 |
572 | /**
573 | * Not supported
574 | */
575 | @Override
576 | public void showInPane(int toprow, int leftcol) {
577 | throw new UnsupportedOperationException();
578 | }
579 |
580 | /**
581 | * Not supported
582 | */
583 | @Override
584 | public void shiftRows(int startRow, int endRow, int n) {
585 | throw new UnsupportedOperationException();
586 | }
587 |
588 | /**
589 | * Not supported
590 | */
591 | @Override
592 | public void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) {
593 | throw new UnsupportedOperationException();
594 | }
595 |
596 | /**
597 | * Not supported
598 | */
599 | @Override
600 | public void shiftColumns(int startColumn, int endColumn, final int n) {
601 | throw new UnsupportedOperationException();
602 | }
603 |
604 | /**
605 | * Not supported
606 | */
607 | @Override
608 | public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) {
609 | throw new UnsupportedOperationException();
610 | }
611 |
612 | /**
613 | * Not supported
614 | */
615 | @Override
616 | public void createFreezePane(int colSplit, int rowSplit) {
617 | throw new UnsupportedOperationException();
618 | }
619 |
620 | /**
621 | * Not supported
622 | */
623 | @Override
624 | public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) {
625 | throw new UnsupportedOperationException();
626 | }
627 |
628 | /**
629 | * Not supported
630 | */
631 | @Override
632 | public PaneInformation getPaneInformation() {
633 | throw new UnsupportedOperationException();
634 | }
635 |
636 | /**
637 | * Not supported
638 | */
639 | @Override
640 | public void setDisplayGridlines(boolean show) {
641 | throw new UnsupportedOperationException();
642 | }
643 |
644 | /**
645 | * Not supported
646 | */
647 | @Override
648 | public boolean isDisplayGridlines() {
649 | throw new UnsupportedOperationException();
650 | }
651 |
652 | /**
653 | * Not supported
654 | */
655 | @Override
656 | public void setDisplayFormulas(boolean show) {
657 | throw new UnsupportedOperationException();
658 | }
659 |
660 | /**
661 | * Not supported
662 | */
663 | @Override
664 | public boolean isDisplayFormulas() {
665 | throw new UnsupportedOperationException();
666 | }
667 |
668 | /**
669 | * Not supported
670 | */
671 | @Override
672 | public void setDisplayRowColHeadings(boolean show) {
673 | throw new UnsupportedOperationException();
674 | }
675 |
676 | /**
677 | * Not supported
678 | */
679 | @Override
680 | public boolean isDisplayRowColHeadings() {
681 | throw new UnsupportedOperationException();
682 | }
683 |
684 | /**
685 | * Not supported
686 | */
687 | @Override
688 | public void setRowBreak(int row) {
689 | throw new UnsupportedOperationException();
690 | }
691 |
692 | /**
693 | * Not supported
694 | */
695 | @Override
696 | public boolean isRowBroken(int row) {
697 | throw new UnsupportedOperationException();
698 | }
699 |
700 | /**
701 | * Not supported
702 | */
703 | @Override
704 | public void removeRowBreak(int row) {
705 | throw new UnsupportedOperationException();
706 | }
707 |
708 | /**
709 | * Not supported
710 | */
711 | @Override
712 | public int[] getRowBreaks() {
713 | throw new UnsupportedOperationException();
714 | }
715 |
716 | /**
717 | * Not supported
718 | */
719 | @Override
720 | public int[] getColumnBreaks() {
721 | throw new UnsupportedOperationException();
722 | }
723 |
724 | /**
725 | * Not supported
726 | */
727 | @Override
728 | public void setColumnBreak(int column) {
729 | throw new UnsupportedOperationException();
730 | }
731 |
732 | /**
733 | * Not supported
734 | */
735 | @Override
736 | public boolean isColumnBroken(int column) {
737 | throw new UnsupportedOperationException();
738 | }
739 |
740 | /**
741 | * Not supported
742 | */
743 | @Override
744 | public void removeColumnBreak(int column) {
745 | throw new UnsupportedOperationException();
746 | }
747 |
748 | /**
749 | * Not supported
750 | */
751 | @Override
752 | public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) {
753 | throw new UnsupportedOperationException();
754 | }
755 |
756 | /**
757 | * Not supported
758 | */
759 | @Override
760 | public void groupColumn(int fromColumn, int toColumn) {
761 | throw new UnsupportedOperationException();
762 | }
763 |
764 | /**
765 | * Not supported
766 | */
767 | @Override
768 | public void ungroupColumn(int fromColumn, int toColumn) {
769 | throw new UnsupportedOperationException();
770 | }
771 |
772 | /**
773 | * Not supported
774 | */
775 | @Override
776 | public void groupRow(int fromRow, int toRow) {
777 | throw new UnsupportedOperationException();
778 | }
779 |
780 | /**
781 | * Not supported
782 | */
783 | @Override
784 | public void ungroupRow(int fromRow, int toRow) {
785 | throw new UnsupportedOperationException();
786 | }
787 |
788 | /**
789 | * Not supported
790 | */
791 | @Override
792 | public void setRowGroupCollapsed(int row, boolean collapse) {
793 | throw new UnsupportedOperationException();
794 | }
795 |
796 | /**
797 | * Not supported
798 | */
799 | @Override
800 | public void setDefaultColumnStyle(int column, CellStyle style) {
801 | throw new UnsupportedOperationException();
802 | }
803 |
804 | /**
805 | * Not supported
806 | */
807 | @Override
808 | public void autoSizeColumn(int column) {
809 | throw new UnsupportedOperationException();
810 | }
811 |
812 | /**
813 | * Not supported
814 | */
815 | @Override
816 | public void autoSizeColumn(int column, boolean useMergedCells) {
817 | throw new UnsupportedOperationException();
818 | }
819 |
820 | /**
821 | * Not supported
822 | */
823 | @Override
824 | public Comment getCellComment(CellAddress cellAddress) {
825 | throw new UnsupportedOperationException();
826 | }
827 |
828 | /**
829 | * Not supported
830 | */
831 | @Override
832 | public Map getCellComments() {
833 | throw new UnsupportedOperationException();
834 | }
835 |
836 | /**
837 | * Not supported
838 | */
839 | @Override
840 | public Drawing getDrawingPatriarch() {
841 | throw new UnsupportedOperationException();
842 | }
843 |
844 | /**
845 | * Not supported
846 | */
847 | @Override
848 | public Drawing createDrawingPatriarch() {
849 | throw new UnsupportedOperationException();
850 | }
851 |
852 | /**
853 | * Not supported
854 | */
855 | @Override
856 | public Workbook getWorkbook() {
857 | throw new UnsupportedOperationException();
858 | }
859 |
860 | /**
861 | * Not supported
862 | */
863 | @Override
864 | public boolean isSelected() {
865 | throw new UnsupportedOperationException();
866 | }
867 |
868 | /**
869 | * Not supported
870 | */
871 | @Override
872 | public CellRange extends Cell> setArrayFormula(String formula, CellRangeAddress range) {
873 | throw new UnsupportedOperationException();
874 | }
875 |
876 | /**
877 | * Not supported
878 | */
879 | @Override
880 | public CellRange extends Cell> removeArrayFormula(Cell cell) {
881 | throw new UnsupportedOperationException();
882 | }
883 |
884 | /**
885 | * Not supported
886 | */
887 | @Override
888 | public DataValidationHelper getDataValidationHelper() {
889 | throw new UnsupportedOperationException();
890 | }
891 |
892 | /**
893 | * Not supported
894 | */
895 | @Override
896 | public List extends DataValidation> getDataValidations() {
897 | throw new UnsupportedOperationException();
898 | }
899 |
900 | /**
901 | * Not supported
902 | */
903 | @Override
904 | public void addValidationData(DataValidation dataValidation) {
905 | throw new UnsupportedOperationException();
906 | }
907 |
908 | /**
909 | * Not supported
910 | */
911 | @Override
912 | public AutoFilter setAutoFilter(CellRangeAddress range) {
913 | throw new UnsupportedOperationException();
914 | }
915 |
916 | /**
917 | * Not supported
918 | */
919 | @Override
920 | public SheetConditionalFormatting getSheetConditionalFormatting() {
921 | throw new UnsupportedOperationException();
922 | }
923 |
924 | /**
925 | * Not supported
926 | */
927 | @Override
928 | public CellRangeAddress getRepeatingRows() {
929 | throw new UnsupportedOperationException();
930 | }
931 |
932 | /**
933 | * Not supported
934 | */
935 | @Override
936 | public CellRangeAddress getRepeatingColumns() {
937 | throw new UnsupportedOperationException();
938 | }
939 |
940 | /**
941 | * Not supported
942 | */
943 | @Override
944 | public void setRepeatingRows(CellRangeAddress rowRangeRef) {
945 | throw new UnsupportedOperationException();
946 | }
947 |
948 | /**
949 | * Not supported
950 | */
951 | @Override
952 | public void setRepeatingColumns(CellRangeAddress columnRangeRef) {
953 | throw new UnsupportedOperationException();
954 | }
955 |
956 | /**
957 | * Not supported
958 | */
959 | @Override
960 | public int getColumnOutlineLevel(int columnIndex) {
961 | throw new UnsupportedOperationException();
962 | }
963 |
964 | /**
965 | * Not supported
966 | */
967 | @Override
968 | public Hyperlink getHyperlink(int i, int i1) {
969 | throw new UnsupportedOperationException();
970 | }
971 |
972 | /**
973 | * Not supported
974 | */
975 | @Override
976 | public Hyperlink getHyperlink(CellAddress cellAddress) {
977 | throw new UnsupportedOperationException();
978 | }
979 |
980 | /**
981 | * Not supported
982 | */
983 | @Override
984 | public List extends Hyperlink> getHyperlinkList() {
985 | throw new UnsupportedOperationException();
986 | }
987 |
988 | /**
989 | * Not supported
990 | */
991 | @Override
992 | public CellAddress getActiveCell() {
993 | throw new UnsupportedOperationException();
994 | }
995 |
996 | /**
997 | * Not supported
998 | */
999 | @Override
1000 | public void setActiveCell(CellAddress cellAddress) {
1001 | throw new UnsupportedOperationException();
1002 | }
1003 | }
1004 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingSheetReader.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import com.monitorjbl.xlsx.exceptions.CloseException;
4 | import com.monitorjbl.xlsx.exceptions.ParseException;
5 | import org.apache.poi.ss.usermodel.BuiltinFormats;
6 | import org.apache.poi.ss.usermodel.DataFormatter;
7 | import org.apache.poi.ss.usermodel.Row;
8 | import org.apache.poi.ss.usermodel.Sheet;
9 | import org.apache.poi.ss.util.CellReference;
10 | import org.apache.poi.xssf.model.SharedStringsTable;
11 | import org.apache.poi.xssf.model.StylesTable;
12 | import org.apache.poi.xssf.usermodel.XSSFCellStyle;
13 | import org.apache.poi.xssf.usermodel.XSSFRichTextString;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 |
17 | import javax.xml.namespace.QName;
18 | import javax.xml.stream.XMLEventReader;
19 | import javax.xml.stream.XMLStreamConstants;
20 | import javax.xml.stream.XMLStreamException;
21 | import javax.xml.stream.events.Attribute;
22 | import javax.xml.stream.events.Characters;
23 | import javax.xml.stream.events.EndElement;
24 | import javax.xml.stream.events.StartElement;
25 | import javax.xml.stream.events.XMLEvent;
26 | import java.util.ArrayList;
27 | import java.util.HashSet;
28 | import java.util.Iterator;
29 | import java.util.List;
30 | import java.util.Set;
31 |
32 | public class StreamingSheetReader implements Iterable {
33 | private static final Logger log = LoggerFactory.getLogger(StreamingSheetReader.class);
34 |
35 | private final SharedStringsTable sst;
36 | private final StylesTable stylesTable;
37 | private final XMLEventReader parser;
38 | private final DataFormatter dataFormatter = new DataFormatter();
39 | private final Set hiddenColumns = new HashSet<>();
40 |
41 | private int lastRowNum;
42 | private int currentRowNum;
43 | private int firstColNum = 0;
44 | private int currentColNum;
45 | private int rowCacheSize;
46 | private List rowCache = new ArrayList<>();
47 | private Iterator rowCacheIterator;
48 |
49 | private String lastContents;
50 | private Sheet sheet;
51 | private StreamingRow currentRow;
52 | private StreamingCell currentCell;
53 | private boolean use1904Dates;
54 |
55 | public StreamingSheetReader(SharedStringsTable sst, StylesTable stylesTable, XMLEventReader parser,
56 | final boolean use1904Dates, int rowCacheSize) {
57 | this.sst = sst;
58 | this.stylesTable = stylesTable;
59 | this.parser = parser;
60 | this.use1904Dates = use1904Dates;
61 | this.rowCacheSize = rowCacheSize;
62 | }
63 |
64 | void setSheet(StreamingSheet sheet) {
65 | this.sheet = sheet;
66 | }
67 |
68 | /**
69 | * Read through a number of rows equal to the rowCacheSize field or until there is no more data to read
70 | *
71 | * @return true if data was read
72 | */
73 | private boolean getRow() {
74 | try {
75 | rowCache.clear();
76 | while(rowCache.size() < rowCacheSize && parser.hasNext()) {
77 | handleEvent(parser.nextEvent());
78 | }
79 | rowCacheIterator = rowCache.iterator();
80 | return rowCacheIterator.hasNext();
81 | } catch(XMLStreamException e) {
82 | throw new ParseException("Error reading XML stream", e);
83 | }
84 | }
85 |
86 | private String[] splitCellRef(String ref) {
87 | int splitPos = -1;
88 |
89 | // start at pos 1, since the first char is expected to always be a letter
90 | for(int i = 1; i < ref.length(); i++) {
91 | char c = ref.charAt(i);
92 |
93 | if(c >= '0' && c <= '9') {
94 | splitPos = i;
95 | break;
96 | }
97 | }
98 |
99 | return new String[]{
100 | ref.substring(0, splitPos),
101 | ref.substring(splitPos)
102 | };
103 | }
104 |
105 | /**
106 | * Handles a SAX event.
107 | *
108 | * @param event
109 | */
110 | private void handleEvent(XMLEvent event) {
111 | if(event.getEventType() == XMLStreamConstants.CHARACTERS) {
112 | Characters c = event.asCharacters();
113 | lastContents += c.getData();
114 | } else if(event.getEventType() == XMLStreamConstants.START_ELEMENT
115 | && isSpreadsheetTag(event.asStartElement().getName())) {
116 | StartElement startElement = event.asStartElement();
117 | String tagLocalName = startElement.getName().getLocalPart();
118 |
119 | if("row".equals(tagLocalName)) {
120 | Attribute rowNumAttr = startElement.getAttributeByName(new QName("r"));
121 | int rowIndex = currentRowNum;
122 | if(rowNumAttr != null) {
123 | rowIndex = Integer.parseInt(rowNumAttr.getValue()) - 1;
124 | currentRowNum = rowIndex;
125 | }
126 | Attribute isHiddenAttr = startElement.getAttributeByName(new QName("hidden"));
127 | boolean isHidden = isHiddenAttr != null && ("1".equals(isHiddenAttr.getValue()) || "true".equals(isHiddenAttr.getValue()));
128 | currentRow = new StreamingRow(sheet, rowIndex, isHidden);
129 | currentColNum = firstColNum;
130 | } else if("col".equals(tagLocalName)) {
131 | Attribute isHiddenAttr = startElement.getAttributeByName(new QName("hidden"));
132 | boolean isHidden = isHiddenAttr != null && ("1".equals(isHiddenAttr.getValue()) || "true".equals(isHiddenAttr.getValue()));
133 | if(isHidden) {
134 | Attribute minAttr = startElement.getAttributeByName(new QName("min"));
135 | Attribute maxAttr = startElement.getAttributeByName(new QName("max"));
136 | int min = Integer.parseInt(minAttr.getValue()) - 1;
137 | int max = Integer.parseInt(maxAttr.getValue()) - 1;
138 | for(int columnIndex = min; columnIndex <= max; columnIndex++)
139 | hiddenColumns.add(columnIndex);
140 | }
141 | } else if("c".equals(tagLocalName)) {
142 | Attribute ref = startElement.getAttributeByName(new QName("r"));
143 |
144 | if(ref != null) {
145 | String[] coord = splitCellRef(ref.getValue());
146 | currentColNum = CellReference.convertColStringToIndex(coord[0]);
147 | currentCell = new StreamingCell(sheet, currentColNum, Integer.parseInt(coord[1]) - 1, use1904Dates);
148 | } else {
149 | currentCell = new StreamingCell(sheet, currentColNum, currentRowNum, use1904Dates);
150 | }
151 | setFormatString(startElement, currentCell);
152 |
153 | Attribute type = startElement.getAttributeByName(new QName("t"));
154 | if(type != null) {
155 | currentCell.setType(type.getValue());
156 | } else {
157 | currentCell.setType("n");
158 | }
159 |
160 | Attribute style = startElement.getAttributeByName(new QName("s"));
161 | if(style != null) {
162 | String indexStr = style.getValue();
163 | try {
164 | int index = Integer.parseInt(indexStr);
165 | currentCell.setCellStyle(stylesTable.getStyleAt(index));
166 | } catch(NumberFormatException nfe) {
167 | log.warn("Ignoring invalid style index {}", indexStr);
168 | }
169 | } else {
170 | currentCell.setCellStyle(stylesTable.getStyleAt(0));
171 | }
172 | } else if("dimension".equals(tagLocalName)) {
173 | Attribute refAttr = startElement.getAttributeByName(new QName("ref"));
174 | String ref = refAttr != null ? refAttr.getValue() : null;
175 | if(ref != null) {
176 | // ref is formatted as A1 or A1:F25. Take the last numbers of this string and use it as lastRowNum
177 | for(int i = ref.length() - 1; i >= 0; i--) {
178 | if(!Character.isDigit(ref.charAt(i))) {
179 | try {
180 | lastRowNum = Integer.parseInt(ref.substring(i + 1)) - 1;
181 | } catch(NumberFormatException ignore) { }
182 | break;
183 | }
184 | }
185 | for(int i = 0; i < ref.length(); i++) {
186 | if(!Character.isAlphabetic(ref.charAt(i))) {
187 | firstColNum = CellReference.convertColStringToIndex(ref.substring(0, i));
188 | break;
189 | }
190 | }
191 | }
192 | } else if("f".equals(tagLocalName)) {
193 | if(currentCell != null) {
194 | currentCell.setFormulaType(true);
195 | }
196 | }
197 |
198 | // Clear contents cache
199 | lastContents = "";
200 | } else if(event.getEventType() == XMLStreamConstants.END_ELEMENT
201 | && isSpreadsheetTag(event.asEndElement().getName())) {
202 | EndElement endElement = event.asEndElement();
203 | String tagLocalName = endElement.getName().getLocalPart();
204 |
205 | if("v".equals(tagLocalName) || "t".equals(tagLocalName)) {
206 | currentCell.setRawContents(unformattedContents());
207 | currentCell.setContentSupplier(formattedContents());
208 | } else if("row".equals(tagLocalName) && currentRow != null) {
209 | rowCache.add(currentRow);
210 | currentRowNum++;
211 | } else if("c".equals(tagLocalName)) {
212 | currentRow.getCellMap().put(currentCell.getColumnIndex(), currentCell);
213 | currentCell = null;
214 | currentColNum++;
215 | } else if("f".equals(tagLocalName)) {
216 | if(currentCell != null) {
217 | currentCell.setFormula(lastContents);
218 | }
219 | }
220 |
221 | }
222 | }
223 |
224 | /**
225 | * Returns true if a tag is part of the main namespace for SpreadsheetML:
226 | *
227 | * - http://schemas.openxmlformats.org/spreadsheetml/2006/main
228 | *
- http://purl.oclc.org/ooxml/spreadsheetml/main
229 | *
230 | * As opposed to http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing, etc.
231 | *
232 | * @param name
233 | * @return
234 | */
235 | private boolean isSpreadsheetTag(QName name) {
236 | return (name.getNamespaceURI() != null
237 | && name.getNamespaceURI().endsWith("/main"));
238 | }
239 |
240 | /**
241 | * Get the hidden state for a given column
242 | *
243 | * @param columnIndex - the column to set (0-based)
244 | * @return hidden - false if the column is visible
245 | */
246 | boolean isColumnHidden(int columnIndex) {
247 | if(rowCacheIterator == null) {
248 | getRow();
249 | }
250 | return hiddenColumns.contains(columnIndex);
251 | }
252 |
253 | /**
254 | * Gets the last row on the sheet
255 | *
256 | * @return
257 | */
258 | int getLastRowNum() {
259 | if(rowCacheIterator == null) {
260 | getRow();
261 | }
262 | return lastRowNum;
263 | }
264 |
265 | /**
266 | * Read the numeric format string out of the styles table for this cell. Stores
267 | * the result in the Cell.
268 | *
269 | * @param startElement
270 | * @param cell
271 | */
272 | void setFormatString(StartElement startElement, StreamingCell cell) {
273 | Attribute cellStyle = startElement.getAttributeByName(new QName("s"));
274 | String cellStyleString = (cellStyle != null) ? cellStyle.getValue() : null;
275 | XSSFCellStyle style = null;
276 |
277 | if(cellStyleString != null) {
278 | style = stylesTable.getStyleAt(Integer.parseInt(cellStyleString));
279 | } else if(stylesTable.getNumCellStyles() > 0) {
280 | style = stylesTable.getStyleAt(0);
281 | }
282 |
283 | if(style != null) {
284 | cell.setNumericFormatIndex(style.getDataFormat());
285 | String formatString = style.getDataFormatString();
286 |
287 | if(formatString != null) {
288 | cell.setNumericFormat(formatString);
289 | } else {
290 | cell.setNumericFormat(BuiltinFormats.getBuiltinFormat(cell.getNumericFormatIndex()));
291 | }
292 | } else {
293 | cell.setNumericFormatIndex(null);
294 | cell.setNumericFormat(null);
295 | }
296 | }
297 |
298 | /**
299 | * Tries to format the contents of the last contents appropriately based on
300 | * the type of cell and the discovered numeric format.
301 | *
302 | * @return
303 | */
304 | Supplier formattedContents() {
305 | return getFormatterForType(currentCell.getType());
306 | }
307 |
308 | /**
309 | * Tries to format the contents of the last contents appropriately based on
310 | * the provided type and the discovered numeric format.
311 | *
312 | * @return
313 | */
314 | private Supplier getFormatterForType(String type) {
315 | switch(type) {
316 | case "s": //string stored in shared table
317 | if(!lastContents.isEmpty()) {
318 | int idx = Integer.parseInt(lastContents);
319 | return new StringSupplier(sst.getItemAt(idx).toString());
320 | }
321 | return new StringSupplier(lastContents);
322 | case "inlineStr": //inline string (not in sst)
323 | case "str":
324 | return new StringSupplier(new XSSFRichTextString(lastContents).toString());
325 | case "e": //error type
326 | return new StringSupplier("ERROR: " + lastContents);
327 | case "n": //numeric type
328 | if(currentCell.getNumericFormat() != null && lastContents.length() > 0) {
329 | // the formatRawCellContents operation incurs a significant overhead on large sheets,
330 | // and we want to defer the execution of this method until the value is actually needed.
331 | // it is not needed in all cases..
332 | final String currentLastContents = lastContents;
333 | final int currentNumericFormatIndex = currentCell.getNumericFormatIndex();
334 | final String currentNumericFormat = currentCell.getNumericFormat();
335 |
336 | return new Supplier() {
337 | String cachedContent;
338 |
339 | @Override
340 | public Object getContent() {
341 | if(cachedContent == null) {
342 | cachedContent = dataFormatter.formatRawCellContents(
343 | Double.parseDouble(currentLastContents),
344 | currentNumericFormatIndex,
345 | currentNumericFormat);
346 | }
347 |
348 | return cachedContent;
349 | }
350 | };
351 | } else {
352 | return new StringSupplier(lastContents);
353 | }
354 | default:
355 | return new StringSupplier(lastContents);
356 | }
357 | }
358 |
359 | /**
360 | * Returns the contents of the cell, with no formatting applied
361 | *
362 | * @return
363 | */
364 | String unformattedContents() {
365 | switch(currentCell.getType()) {
366 | case "s": //string stored in shared table
367 | if(!lastContents.isEmpty()) {
368 | int idx = Integer.parseInt(lastContents);
369 | return sst.getItemAt(idx).toString();
370 | }
371 | return lastContents;
372 | case "inlineStr": //inline string (not in sst)
373 | return new XSSFRichTextString(lastContents).toString();
374 | default:
375 | return lastContents;
376 | }
377 | }
378 |
379 | /**
380 | * Returns a new streaming iterator to loop through rows. This iterator is not
381 | * guaranteed to have all rows in memory, and any particular iteration may
382 | * trigger a load from disk to read in new data.
383 | *
384 | * @return the streaming iterator
385 | */
386 | @Override
387 | public Iterator iterator() {
388 | return new StreamingRowIterator();
389 | }
390 |
391 | public void close() {
392 | try {
393 | parser.close();
394 | } catch(XMLStreamException e) {
395 | throw new CloseException(e);
396 | }
397 | }
398 |
399 | class StreamingRowIterator implements Iterator {
400 | public StreamingRowIterator() {
401 | if(rowCacheIterator == null) {
402 | hasNext();
403 | }
404 | }
405 |
406 | @Override
407 | public boolean hasNext() {
408 | return (rowCacheIterator != null && rowCacheIterator.hasNext()) || getRow();
409 | }
410 |
411 | @Override
412 | public Row next() {
413 | return rowCacheIterator.next();
414 | }
415 |
416 | @Override
417 | public void remove() {
418 | throw new RuntimeException("NotSupported");
419 | }
420 | }
421 | }
422 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingWorkbook.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import com.monitorjbl.xlsx.exceptions.MissingSheetException;
4 | import org.apache.poi.ss.SpreadsheetVersion;
5 | import org.apache.poi.ss.formula.EvaluationWorkbook;
6 | import org.apache.poi.ss.formula.udf.UDFFinder;
7 | import org.apache.poi.ss.usermodel.CellStyle;
8 | import org.apache.poi.ss.usermodel.CreationHelper;
9 | import org.apache.poi.ss.usermodel.DataFormat;
10 | import org.apache.poi.ss.usermodel.Font;
11 | import org.apache.poi.ss.usermodel.Name;
12 | import org.apache.poi.ss.usermodel.PictureData;
13 | import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
14 | import org.apache.poi.ss.usermodel.Sheet;
15 | import org.apache.poi.ss.usermodel.SheetVisibility;
16 | import org.apache.poi.ss.usermodel.Workbook;
17 |
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 | import java.util.Iterator;
21 | import java.util.List;
22 |
23 | public class StreamingWorkbook implements Workbook, AutoCloseable {
24 | private final StreamingWorkbookReader reader;
25 |
26 | public StreamingWorkbook(StreamingWorkbookReader reader) {
27 | this.reader = reader;
28 | }
29 |
30 | int findSheetByName(String name) {
31 | for(int i = 0; i < reader.getSheetProperties().size(); i++) {
32 | if(reader.getSheetProperties().get(i).get("name").equals(name)) {
33 | return i;
34 | }
35 | }
36 | return -1;
37 | }
38 |
39 | /* Supported */
40 |
41 | /**
42 | * {@inheritDoc}
43 | */
44 | @Override
45 | public Iterator iterator() {
46 | return reader.iterator();
47 | }
48 |
49 | /**
50 | * {@inheritDoc}
51 | */
52 | @Override
53 | public Iterator sheetIterator() {
54 | return iterator();
55 | }
56 |
57 | /**
58 | * {@inheritDoc}
59 | */
60 | @Override
61 | public String getSheetName(int sheet) {
62 | return reader.getSheetProperties().get(sheet).get("name");
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | @Override
69 | public int getSheetIndex(String name) {
70 | return findSheetByName(name);
71 | }
72 |
73 | /**
74 | * {@inheritDoc}
75 | */
76 | @Override
77 | public int getSheetIndex(Sheet sheet) {
78 | if(sheet instanceof StreamingSheet) {
79 | return findSheetByName(sheet.getSheetName());
80 | } else {
81 | throw new UnsupportedOperationException("Cannot use non-StreamingSheet sheets");
82 | }
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | @Override
89 | public int getNumberOfSheets() {
90 | return reader.getSheets().size();
91 | }
92 |
93 | /**
94 | * {@inheritDoc}
95 | */
96 | @Override
97 | public Sheet getSheetAt(int index) {
98 | return reader.getSheets().get(index);
99 | }
100 |
101 | /**
102 | * {@inheritDoc}
103 | */
104 | @Override
105 | public Sheet getSheet(String name) {
106 | int index = getSheetIndex(name);
107 | if(index == -1) {
108 | throw new MissingSheetException("Sheet '" + name + "' does not exist");
109 | }
110 | return reader.getSheets().get(index);
111 | }
112 |
113 | /**
114 | * {@inheritDoc}
115 | */
116 | @Override
117 | public boolean isSheetHidden(int sheetIx) {
118 | return "hidden".equals(reader.getSheetProperties().get(sheetIx).get("state"));
119 | }
120 |
121 | /**
122 | * {@inheritDoc}
123 | */
124 | @Override
125 | public boolean isSheetVeryHidden(int sheetIx) {
126 | return "veryHidden".equals(reader.getSheetProperties().get(sheetIx).get("state"));
127 | }
128 |
129 | /**
130 | * {@inheritDoc}
131 | */
132 | @Override
133 | public void close() throws IOException {
134 | reader.close();
135 | }
136 |
137 | /* Not supported */
138 |
139 | /**
140 | * Not supported
141 | */
142 | @Override
143 | public int getActiveSheetIndex() {
144 | throw new UnsupportedOperationException();
145 | }
146 |
147 | /**
148 | * Not supported
149 | */
150 | @Override
151 | public void setActiveSheet(int sheetIndex) {
152 | throw new UnsupportedOperationException();
153 | }
154 |
155 | /**
156 | * Not supported
157 | */
158 | @Override
159 | public int getFirstVisibleTab() {
160 | throw new UnsupportedOperationException();
161 | }
162 |
163 | /**
164 | * Not supported
165 | */
166 | @Override
167 | public void setFirstVisibleTab(int sheetIndex) {
168 | throw new UnsupportedOperationException();
169 | }
170 |
171 | /**
172 | * Not supported
173 | */
174 | @Override
175 | public void setSheetOrder(String sheetname, int pos) {
176 | throw new UnsupportedOperationException();
177 | }
178 |
179 | /**
180 | * Not supported
181 | */
182 | @Override
183 | public void setSelectedTab(int index) {
184 | throw new UnsupportedOperationException();
185 | }
186 |
187 | /**
188 | * Not supported
189 | */
190 | @Override
191 | public void setSheetName(int sheet, String name) {
192 | throw new UnsupportedOperationException();
193 | }
194 |
195 | /**
196 | * Not supported
197 | */
198 | @Override
199 | public Sheet createSheet() {
200 | throw new UnsupportedOperationException();
201 | }
202 |
203 | /**
204 | * Not supported
205 | */
206 | @Override
207 | public Sheet createSheet(String sheetname) {
208 | throw new UnsupportedOperationException();
209 | }
210 |
211 | /**
212 | * Not supported
213 | */
214 | @Override
215 | public Sheet cloneSheet(int sheetNum) {
216 | throw new UnsupportedOperationException();
217 | }
218 |
219 | /**
220 | * Not supported
221 | */
222 | @Override
223 | public void removeSheetAt(int index) {
224 | throw new UnsupportedOperationException();
225 | }
226 |
227 | /**
228 | * Not supported
229 | */
230 | @Override
231 | public Font createFont() {
232 | throw new UnsupportedOperationException();
233 | }
234 |
235 | /**
236 | * Not supported
237 | */
238 | @Override
239 | public Font findFont(boolean b, short i, short i1, String s, boolean b1, boolean b2, short i2, byte b3) {
240 | throw new UnsupportedOperationException();
241 | }
242 |
243 | @Override
244 | public int getNumberOfFonts() {
245 | throw new UnsupportedOperationException();
246 | }
247 |
248 | /**
249 | * Not supported
250 | */
251 | @Override
252 | public int getNumberOfFontsAsInt() { throw new UnsupportedOperationException(); }
253 |
254 | /**
255 | * Not supported
256 | */
257 | @Override
258 | public Font getFontAt(int i) { throw new UnsupportedOperationException(); }
259 |
260 | /**
261 | * Not supported
262 | */
263 | @Override
264 | public CellStyle createCellStyle() {
265 | throw new UnsupportedOperationException();
266 | }
267 |
268 | /**
269 | * Not supported
270 | */
271 | @Override
272 | public int getNumCellStyles() {
273 | throw new UnsupportedOperationException();
274 | }
275 |
276 | /**
277 | * Not supported
278 | */
279 | @Override
280 | public CellStyle getCellStyleAt(int i) {
281 | throw new UnsupportedOperationException();
282 | }
283 |
284 | /**
285 | * Not supported
286 | */
287 | @Override
288 | public void write(OutputStream stream) throws IOException {
289 | throw new UnsupportedOperationException();
290 | }
291 |
292 | /**
293 | * Not supported
294 | */
295 | @Override
296 | public int getNumberOfNames() {
297 | throw new UnsupportedOperationException();
298 | }
299 |
300 | /**
301 | * Not supported
302 | */
303 | @Override
304 | public Name getName(String name) {
305 | throw new UnsupportedOperationException();
306 | }
307 |
308 | /**
309 | * Not supported
310 | */
311 | @Override
312 | public List extends Name> getNames(String s) {
313 | throw new UnsupportedOperationException();
314 | }
315 |
316 | /**
317 | * Not supported
318 | */
319 | @Override
320 | public List extends Name> getAllNames() {
321 | throw new UnsupportedOperationException();
322 | }
323 |
324 | /**
325 | * Not supported
326 | */
327 | @Override
328 | public Name createName() {
329 | throw new UnsupportedOperationException();
330 | }
331 |
332 | /**
333 | * Not supported
334 | */
335 | @Override
336 | public void removeName(Name name) {
337 | throw new UnsupportedOperationException();
338 | }
339 |
340 | /**
341 | * Not supported
342 | */
343 | @Override
344 | public int linkExternalWorkbook(String name, Workbook workbook) {
345 | throw new UnsupportedOperationException();
346 | }
347 |
348 | /**
349 | * Not supported
350 | */
351 | @Override
352 | public void setPrintArea(int sheetIndex, String reference) {
353 | throw new UnsupportedOperationException();
354 | }
355 |
356 | /**
357 | * Not supported
358 | */
359 | @Override
360 | public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) {
361 | throw new UnsupportedOperationException();
362 | }
363 |
364 | /**
365 | * Not supported
366 | */
367 | @Override
368 | public String getPrintArea(int sheetIndex) {
369 | throw new UnsupportedOperationException();
370 | }
371 |
372 | /**
373 | * Not supported
374 | */
375 | @Override
376 | public void removePrintArea(int sheetIndex) {
377 | throw new UnsupportedOperationException();
378 | }
379 |
380 | /**
381 | * Not supported
382 | */
383 | @Override
384 | public MissingCellPolicy getMissingCellPolicy() {
385 | throw new UnsupportedOperationException();
386 | }
387 |
388 | /**
389 | * Not supported
390 | */
391 | @Override
392 | public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) {
393 | throw new UnsupportedOperationException();
394 | }
395 |
396 | /**
397 | * Not supported
398 | */
399 | @Override
400 | public DataFormat createDataFormat() {
401 | throw new UnsupportedOperationException();
402 | }
403 |
404 | /**
405 | * Not supported
406 | */
407 | @Override
408 | public int addPicture(byte[] pictureData, int format) {
409 | throw new UnsupportedOperationException();
410 | }
411 |
412 | /**
413 | * Not supported
414 | */
415 | @Override
416 | public List extends PictureData> getAllPictures() {
417 | throw new UnsupportedOperationException();
418 | }
419 |
420 | /**
421 | * Not supported
422 | */
423 | @Override
424 | public CreationHelper getCreationHelper() {
425 | throw new UnsupportedOperationException();
426 | }
427 |
428 | /**
429 | * Not supported
430 | */
431 | @Override
432 | public boolean isHidden() {
433 | throw new UnsupportedOperationException();
434 | }
435 |
436 | /**
437 | * Not supported
438 | */
439 | @Override
440 | public void setHidden(boolean hiddenFlag) {
441 | throw new UnsupportedOperationException();
442 | }
443 |
444 | /**
445 | * Not supported
446 | */
447 | @Override
448 | public void setSheetHidden(int sheetIx, boolean hidden) {
449 | throw new UnsupportedOperationException();
450 | }
451 |
452 | /**
453 | * Not supported
454 | */
455 | @Override
456 | public SheetVisibility getSheetVisibility(int i) {
457 | throw new UnsupportedOperationException();
458 | }
459 |
460 | /**
461 | * Not supported
462 | */
463 | @Override
464 | public void setSheetVisibility(int i, SheetVisibility sheetVisibility) {
465 | throw new UnsupportedOperationException();
466 | }
467 |
468 | /**
469 | * Not supported
470 | */
471 | @Override
472 | public void addToolPack(UDFFinder toopack) {
473 | throw new UnsupportedOperationException();
474 | }
475 |
476 | /**
477 | * Not supported
478 | */
479 | @Override
480 | public void setForceFormulaRecalculation(boolean value) {
481 | throw new UnsupportedOperationException();
482 | }
483 |
484 | /**
485 | * Not supported
486 | */
487 | @Override
488 | public boolean getForceFormulaRecalculation() {
489 | throw new UnsupportedOperationException();
490 | }
491 |
492 | /**
493 | * Not supported
494 | */
495 | @Override
496 | public SpreadsheetVersion getSpreadsheetVersion() {
497 | throw new UnsupportedOperationException();
498 | }
499 |
500 | /**
501 | * Not supported
502 | */
503 | @Override
504 | public int addOlePackage(byte[] bytes, String s, String s1, String s2) throws IOException {
505 | throw new UnsupportedOperationException();
506 | }
507 |
508 | @Override
509 | public EvaluationWorkbook createEvaluationWorkbook() {
510 | return null;
511 | }
512 | }
513 |
--------------------------------------------------------------------------------
/src/main/java/com/monitorjbl/xlsx/impl/StreamingWorkbookReader.java:
--------------------------------------------------------------------------------
1 | package com.monitorjbl.xlsx.impl;
2 |
3 | import com.monitorjbl.xlsx.StreamingReader.Builder;
4 | import com.monitorjbl.xlsx.exceptions.OpenException;
5 | import com.monitorjbl.xlsx.exceptions.ReadException;
6 | import com.monitorjbl.xlsx.sst.BufferedStringsTable;
7 | import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
8 | import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
9 | import org.apache.poi.openxml4j.opc.OPCPackage;
10 | import org.apache.poi.poifs.crypt.Decryptor;
11 | import org.apache.poi.poifs.crypt.EncryptionInfo;
12 | import org.apache.poi.poifs.filesystem.POIFSFileSystem;
13 | import org.apache.poi.ss.usermodel.Sheet;
14 | import org.apache.poi.util.StaxHelper;
15 | import org.apache.poi.xssf.eventusermodel.XSSFReader;
16 | import org.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator;
17 | import org.apache.poi.xssf.model.SharedStringsTable;
18 | import org.apache.poi.xssf.model.StylesTable;
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 | import org.w3c.dom.Node;
22 | import org.w3c.dom.NodeList;
23 |
24 | import javax.xml.stream.XMLEventReader;
25 | import javax.xml.stream.XMLStreamException;
26 | import java.io.File;
27 | import java.io.IOException;
28 | import java.io.InputStream;
29 | import java.net.URI;
30 | import java.nio.file.Files;
31 | import java.security.GeneralSecurityException;
32 | import java.util.ArrayList;
33 | import java.util.HashMap;
34 | import java.util.Iterator;
35 | import java.util.LinkedHashMap;
36 | import java.util.List;
37 | import java.util.Map;
38 |
39 | import static com.monitorjbl.xlsx.XmlUtils.document;
40 | import static com.monitorjbl.xlsx.XmlUtils.searchForNodeList;
41 | import static com.monitorjbl.xlsx.impl.TempFileUtil.writeInputStreamToFile;
42 | import static java.util.Arrays.asList;
43 |
44 | public class StreamingWorkbookReader implements Iterable, AutoCloseable {
45 | private static final Logger log = LoggerFactory.getLogger(StreamingWorkbookReader.class);
46 |
47 | private final List sheets;
48 | private final List | |