react-native-nitro-xlsx 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -8
- package/cpp/HybridNitroXlsx.cpp +25 -1
- package/cpp/HybridXlsxCellFormat.cpp +46 -146
- package/cpp/HybridXlsxCellFormat.hpp +9 -41
- package/cpp/HybridXlsxConditionalFormat.cpp +4 -1
- package/cpp/HybridXlsxConditionalFormats.cpp +5 -1
- package/cpp/HybridXlsxWorkbook.cpp +38 -8
- package/cpp/HybridXlsxWorkbook.hpp +10 -1
- package/cpp/HybridXlsxWorksheet.cpp +422 -141
- package/cpp/HybridXlsxWorksheet.hpp +4 -1
- package/cpp/XlsxCacheDir.cpp +25 -2
- package/cpp/XlsxCacheDir.hpp +3 -1
- package/cpp/XlsxCellStyle.hpp +360 -0
- package/cpp/XlsxJson.cpp +29 -8
- package/package.json +1 -1
- package/scripts/sync-openxlsx-headers.sh +34 -0
- package/src/__mocks__/react-native-nitro-modules.ts +523 -20
- package/src/__tests__/index.test.ts +124 -16
- package/src/index.ts +13 -13
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/IZipArchive.hpp +361 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/OpenXLSX-Exports.hpp +12 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCell.hpp +367 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellIterator.hpp +252 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellRange.hpp +223 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellReference.hpp +317 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellValue.hpp +793 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColor.hpp +232 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColumn.hpp +159 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCommandQuery.hpp +228 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLComments.hpp +270 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLConstants.hpp +18 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLContentTypes.hpp +264 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDateTime.hpp +175 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDocument.hpp +505 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDrawing.hpp +452 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLException.hpp +161 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLFormula.hpp +375 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLIterator.hpp +40 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLMergeCells.hpp +216 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLProperties.hpp +348 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRelationships.hpp +348 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRow.hpp +721 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRowData.hpp +488 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSharedStrings.hpp +204 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSheet.hpp +1640 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLStyles.hpp +2390 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLTables.hpp +132 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLWorkbook.hpp +404 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlData.hpp +283 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlFile.hpp +194 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParser.hpp +282 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParserForwardDeclarations.hpp +85 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLZipArchive.hpp +232 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/LibZip.hpp +447 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/OpenXLSXFileSystemTools.hpp +207 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/Zippy.hpp +1375 -0
- package/thirdparty/README.md +7 -3
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
#include "XlsxError.hpp"
|
|
3
3
|
#include "XlsxJson.hpp"
|
|
4
4
|
#include <OpenXLSX/XLCellReference.hpp>
|
|
5
|
+
#include <cctype>
|
|
5
6
|
#include <cstring>
|
|
6
7
|
#include <cmath>
|
|
7
8
|
#include <fstream>
|
|
8
9
|
#include <chrono>
|
|
10
|
+
#include <string>
|
|
9
11
|
|
|
10
12
|
namespace margelo::nitro::xlsx {
|
|
11
13
|
|
|
12
|
-
HybridXlsxWorksheet::HybridXlsxWorksheet(OpenXLSX::XLDocument& doc, OpenXLSX::XLWorksheet worksheet)
|
|
13
|
-
: HybridObject("XlsxWorksheet"), HybridXlsxWorksheetSpec(), _doc(doc), _worksheet(worksheet) {
|
|
14
|
+
HybridXlsxWorksheet::HybridXlsxWorksheet(OpenXLSX::XLDocument& doc, OpenXLSX::XLWorksheet worksheet, uint64_t documentGeneration)
|
|
15
|
+
: HybridObject("XlsxWorksheet"), HybridXlsxWorksheetSpec(), _doc(doc), _worksheet(worksheet), _documentGeneration(documentGeneration) {
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
HybridXlsxWorksheet::~HybridXlsxWorksheet() {
|
|
@@ -22,7 +24,7 @@ void HybridXlsxWorksheet::applyFormat(OpenXLSX::XLCell& cell, const std::optiona
|
|
|
22
24
|
auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
|
|
23
25
|
if (!hybridFormat) return;
|
|
24
26
|
|
|
25
|
-
auto styleIndex = hybridFormat->applyToDocument(_doc.styles());
|
|
27
|
+
auto styleIndex = hybridFormat->applyToDocument(_doc.styles(), _documentGeneration);
|
|
26
28
|
hybridFormat->applyToCell(cell, styleIndex);
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -92,12 +94,11 @@ void HybridXlsxWorksheet::writeFormulaBoolean(double row, double col, const std:
|
|
|
92
94
|
});
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
void HybridXlsxWorksheet::writeArrayFormula(double firstRow
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
97
|
+
void HybridXlsxWorksheet::writeArrayFormula(double /*firstRow*/, double /*firstCol*/, double /*lastRow*/, double /*lastCol*/, const std::string& /*formula*/, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& /*format*/) {
|
|
98
|
+
// OpenXLSX (XLFormula) explicitly does not support array formulas; attempting to
|
|
99
|
+
// write one would silently produce a normal formula covering only the top-left cell.
|
|
100
|
+
throw XlsxError(xlsx_error::UNSUPPORTED,
|
|
101
|
+
"Array formulas are not supported by the underlying OpenXLSX engine");
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
void HybridXlsxWorksheet::writeDatetime(double row, double col, double datetime, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
@@ -117,36 +118,40 @@ void HybridXlsxWorksheet::writeURL(double row, double col, const std::string& ur
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
void HybridXlsxWorksheet::setColumn(double firstCol, double lastCol, double width, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
122
|
+
for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
|
|
123
|
+
_worksheet.column(c).setWidth(static_cast<float>(width));
|
|
124
|
+
}
|
|
125
|
+
if (format.has_value()) {
|
|
126
|
+
// Apply format to entire column range by setting it on cells in the first row
|
|
127
|
+
// (OpenXLSX does not support column-level style; apply to first row cells as a hint)
|
|
128
|
+
auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
|
|
129
|
+
if (hybridFormat) {
|
|
130
|
+
auto styleIndex = hybridFormat->applyToDocument(_doc.styles(), _documentGeneration);
|
|
131
|
+
for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
|
|
132
|
+
OpenXLSX::XLCell cell = _worksheet.cell(1, c);
|
|
133
|
+
hybridFormat->applyToCell(cell, styleIndex);
|
|
134
|
+
}
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
|
-
}
|
|
137
|
+
});
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
void HybridXlsxWorksheet::setRow(double row, double height, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
142
|
+
_worksheet.row(static_cast<unsigned int>(row)).setHeight(static_cast<float>(height));
|
|
143
|
+
if (format.has_value()) {
|
|
144
|
+
auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
|
|
145
|
+
if (hybridFormat) {
|
|
146
|
+
auto styleIndex = hybridFormat->applyToDocument(_doc.styles(), _documentGeneration);
|
|
147
|
+
unsigned int colCount = _worksheet.columnCount();
|
|
148
|
+
for (unsigned int c = 1; c <= colCount; ++c) {
|
|
149
|
+
OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(row), c);
|
|
150
|
+
hybridFormat->applyToCell(cell, styleIndex);
|
|
151
|
+
}
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
|
-
}
|
|
154
|
+
});
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
bool HybridXlsxWorksheet::deleteRow(double row) {
|
|
@@ -168,21 +173,28 @@ bool HybridXlsxWorksheet::deleteColumn(double col) {
|
|
|
168
173
|
return false;
|
|
169
174
|
}
|
|
170
175
|
|
|
171
|
-
// Shift cell contents left for every row
|
|
176
|
+
// Shift cell contents left for every row. Use findCell for reads so rows/cells
|
|
177
|
+
// that do not exist are never materialized just by shifting.
|
|
172
178
|
for (unsigned int r = 1; r <= lastRow; ++r) {
|
|
173
179
|
for (unsigned int c = targetCol + 1; c <= lastCol; ++c) {
|
|
174
|
-
OpenXLSX::
|
|
175
|
-
OpenXLSX::XLCell dst = _worksheet.cell(r, c - 1);
|
|
180
|
+
OpenXLSX::XLCellAssignable src = _worksheet.findCell(r, c);
|
|
176
181
|
if (!src.empty()) {
|
|
182
|
+
OpenXLSX::XLCell dst = _worksheet.cell(r, c - 1);
|
|
177
183
|
dst.copyFrom(src);
|
|
178
184
|
dst.setCellFormat(src.cellFormat());
|
|
179
185
|
} else {
|
|
180
|
-
|
|
186
|
+
// Only clear an existing destination cell; never create one.
|
|
187
|
+
OpenXLSX::XLCellAssignable dst = _worksheet.findCell(r, c - 1);
|
|
188
|
+
if (!dst.empty()) {
|
|
189
|
+
dst.clear(0);
|
|
190
|
+
}
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
|
-
// Clear the trailing column
|
|
184
|
-
OpenXLSX::
|
|
185
|
-
trailing.
|
|
193
|
+
// Clear the trailing column (only if the cell exists)
|
|
194
|
+
OpenXLSX::XLCellAssignable trailing = _worksheet.findCell(r, lastCol);
|
|
195
|
+
if (!trailing.empty()) {
|
|
196
|
+
trailing.clear(0);
|
|
197
|
+
}
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
// Shift column widths / hidden state left
|
|
@@ -198,23 +210,27 @@ bool HybridXlsxWorksheet::deleteColumn(double col) {
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
void HybridXlsxWorksheet::mergeRange(double firstRow, double firstCol, double lastRow, double lastCol, const std::string& value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
214
|
+
OpenXLSX::XLCellReference topLeft(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
215
|
+
OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
|
|
216
|
+
OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
|
|
217
|
+
_worksheet.mergeCells(range);
|
|
218
|
+
OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
219
|
+
cell.value() = value;
|
|
220
|
+
applyFormat(cell, format);
|
|
221
|
+
});
|
|
208
222
|
}
|
|
209
223
|
|
|
210
224
|
void HybridXlsxWorksheet::mergeRangeNum(double firstRow, double firstCol, double lastRow, double lastCol, double value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
226
|
+
OpenXLSX::XLCellReference topLeft(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
227
|
+
OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
|
|
228
|
+
OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
|
|
229
|
+
_worksheet.mergeCells(range);
|
|
230
|
+
OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
231
|
+
cell.value() = value;
|
|
232
|
+
applyFormat(cell, format);
|
|
233
|
+
});
|
|
218
234
|
}
|
|
219
235
|
|
|
220
236
|
void HybridXlsxWorksheet::unmergeCells(double firstRow, double firstCol, double lastRow, double lastCol) {
|
|
@@ -233,7 +249,9 @@ void HybridXlsxWorksheet::hide() {
|
|
|
233
249
|
}
|
|
234
250
|
|
|
235
251
|
void HybridXlsxWorksheet::activate() {
|
|
236
|
-
|
|
252
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
253
|
+
_worksheet.setActive();
|
|
254
|
+
});
|
|
237
255
|
}
|
|
238
256
|
|
|
239
257
|
// ========== Sheet protection ==========
|
|
@@ -465,130 +483,393 @@ std::string HybridXlsxWorksheet::sheetProtectionSummary() {
|
|
|
465
483
|
}
|
|
466
484
|
|
|
467
485
|
void HybridXlsxWorksheet::setColumnHidden(double firstCol, double lastCol, bool hidden) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
486
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
487
|
+
for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
|
|
488
|
+
_worksheet.column(c).setHidden(hidden);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
471
491
|
}
|
|
472
492
|
|
|
473
493
|
void HybridXlsxWorksheet::setRowHidden(double row, bool hidden) {
|
|
474
|
-
|
|
494
|
+
withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
495
|
+
_worksheet.row(static_cast<unsigned int>(row)).setHidden(hidden);
|
|
496
|
+
});
|
|
475
497
|
}
|
|
476
498
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return
|
|
499
|
+
// ========== Read helpers ==========
|
|
500
|
+
|
|
501
|
+
namespace {
|
|
502
|
+
|
|
503
|
+
// Reverse of HybridXlsxCellFormat's color encoding: XLColor hex "AARRGGBB" ->
|
|
504
|
+
// packed RGB double (0xRRGGBB) used by the public color setters.
|
|
505
|
+
double xlColorToDouble(const OpenXLSX::XLColor& color) {
|
|
506
|
+
std::string hex = color.hex();
|
|
507
|
+
if (hex.empty()) return 0.0;
|
|
508
|
+
uint32_t argb = static_cast<uint32_t>(std::stoul(hex, nullptr, 16));
|
|
509
|
+
return static_cast<double>(argb & 0x00FFFFFFu);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Built-in number format codes (numFmtId 0-49, ECMA-376). nullptr when unknown.
|
|
513
|
+
const char* builtinNumFmtCode(uint32_t numFmtId) {
|
|
514
|
+
switch (numFmtId) {
|
|
515
|
+
case 0: return "General";
|
|
516
|
+
case 1: return "0";
|
|
517
|
+
case 2: return "0.00";
|
|
518
|
+
case 3: return "#,##0";
|
|
519
|
+
case 4: return "#,##0.00";
|
|
520
|
+
case 5: return "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)";
|
|
521
|
+
case 6: return "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)";
|
|
522
|
+
case 7: return "_(#,##0_);(#,##0)";
|
|
523
|
+
case 8: return "_(#,##0.00_);(#,##0.00)";
|
|
524
|
+
case 9: return "0%";
|
|
525
|
+
case 10: return "0.00%";
|
|
526
|
+
case 11: return "0.00E+00";
|
|
527
|
+
case 12: return "# ?/?";
|
|
528
|
+
case 13: return "# ?\?/??";
|
|
529
|
+
case 14: return "mm-dd-yy";
|
|
530
|
+
case 15: return "d-mmm-yy";
|
|
531
|
+
case 16: return "d-mmm";
|
|
532
|
+
case 17: return "mmm-yy";
|
|
533
|
+
case 18: return "h:mm AM/PM";
|
|
534
|
+
case 19: return "h:mm:ss AM/PM";
|
|
535
|
+
case 20: return "h:mm";
|
|
536
|
+
case 21: return "h:mm:ss";
|
|
537
|
+
case 22: return "m/d/yy h:mm";
|
|
538
|
+
case 37: return "#,##0 ;(#,##0)";
|
|
539
|
+
case 38: return "#,##0 ;[Red](#,##0)";
|
|
540
|
+
case 39: return "#,##0.00;(#,##0.00)";
|
|
541
|
+
case 40: return "#,##0.00;[Red](#,##0.00)";
|
|
542
|
+
case 45: return "mm:ss";
|
|
543
|
+
case 46: return "[h]:mm:ss";
|
|
544
|
+
case 47: return "mmss.0";
|
|
545
|
+
case 48: return "##0.0E+0";
|
|
546
|
+
case 49: return "@";
|
|
547
|
+
default: return nullptr;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Resolve a numFmtId to its format code: custom entries in styles.xml first,
|
|
552
|
+
// then the built-in table.
|
|
553
|
+
std::string lookupNumFmtCode(OpenXLSX::XLStyles& styles, uint32_t numFmtId) {
|
|
554
|
+
try {
|
|
555
|
+
return styles.numberFormats().numberFormatById(numFmtId).formatCode();
|
|
556
|
+
} catch (const std::exception&) {
|
|
557
|
+
const char* builtin = builtinNumFmtCode(numFmtId);
|
|
558
|
+
return builtin ? std::string(builtin) : std::string();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Does this format code describe a date/time? Strips quoted literals, escapes
|
|
563
|
+
// and [..] sections, then looks for date/time tokens (y, d, hh, ss, m/d).
|
|
564
|
+
bool formatCodeIsDateTime(const std::string& fmt) {
|
|
565
|
+
std::string s;
|
|
566
|
+
s.reserve(fmt.size());
|
|
567
|
+
bool inQuote = false;
|
|
568
|
+
for (size_t i = 0; i < fmt.size(); ++i) {
|
|
569
|
+
char ch = fmt[i];
|
|
570
|
+
if (inQuote) {
|
|
571
|
+
if (ch == '"') inQuote = false;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (ch == '"') { inQuote = true; continue; }
|
|
575
|
+
if (ch == '\\' && i + 1 < fmt.size()) { ++i; continue; }
|
|
576
|
+
if (ch == '[') {
|
|
577
|
+
size_t end = fmt.find(']', i);
|
|
578
|
+
if (end != std::string::npos) {
|
|
579
|
+
std::string section = fmt.substr(i + 1, end - i - 1);
|
|
580
|
+
// Elapsed-time sections like [h], [mm], [ss] are date/time.
|
|
581
|
+
if (section == "h" || section == "m" || section == "s" ||
|
|
582
|
+
section == "hh" || section == "mm" || section == "ss") {
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
i = end;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
s += static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
|
590
|
+
}
|
|
591
|
+
if (s.find('y') != std::string::npos) return true;
|
|
592
|
+
if (s.find('d') != std::string::npos) return true;
|
|
593
|
+
// Single-letter h covers custom time-only codes such as "h:mm"; the
|
|
594
|
+
// "hh"/"ss" forms are covered by this and the check below.
|
|
595
|
+
if (s.find('h') != std::string::npos) return true;
|
|
596
|
+
if (s.find("ss") != std::string::npos) return true;
|
|
597
|
+
if (s.find("m/") != std::string::npos || s.find("/m") != std::string::npos) return true;
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Date/time detection per the number format applied to a cell style:
|
|
602
|
+
// built-in numFmtId 14-22 are date/time; custom ids >= 164 are inspected by code.
|
|
603
|
+
bool numFmtIsDateTime(OpenXLSX::XLStyles& styles, OpenXLSX::XLStyleIndex styleIndex) {
|
|
604
|
+
try {
|
|
605
|
+
uint32_t numFmtId = styles.cellFormats()[styleIndex].numberFormatId();
|
|
606
|
+
if (numFmtId >= 14 && numFmtId <= 22) return true;
|
|
607
|
+
if (numFmtId >= 164) {
|
|
608
|
+
try {
|
|
609
|
+
return formatCodeIsDateTime(styles.numberFormats().numberFormatById(numFmtId).formatCode());
|
|
610
|
+
} catch (const std::exception&) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
} catch (const std::exception&) {
|
|
616
|
+
return false;
|
|
617
|
+
}
|
|
494
618
|
}
|
|
619
|
+
|
|
620
|
+
} // anonymous namespace
|
|
621
|
+
|
|
622
|
+
std::variant<bool, nitro::NullType, std::string, double> HybridXlsxWorksheet::getCellValue(double row, double col) {
|
|
623
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::variant<bool, nitro::NullType, std::string, double> {
|
|
624
|
+
OpenXLSX::XLCellAssignable cell = _worksheet.findCell(static_cast<uint32_t>(row), static_cast<uint16_t>(col));
|
|
625
|
+
if (cell.empty()) return nitro::NullType{};
|
|
626
|
+
auto type = cell.value().type();
|
|
627
|
+
|
|
628
|
+
switch (type) {
|
|
629
|
+
case OpenXLSX::XLValueType::String:
|
|
630
|
+
return cell.value().get<std::string>();
|
|
631
|
+
case OpenXLSX::XLValueType::Integer:
|
|
632
|
+
return static_cast<double>(cell.value().get<int64_t>());
|
|
633
|
+
case OpenXLSX::XLValueType::Float:
|
|
634
|
+
return cell.value().get<double>();
|
|
635
|
+
case OpenXLSX::XLValueType::Boolean:
|
|
636
|
+
return cell.value().get<bool>();
|
|
637
|
+
case OpenXLSX::XLValueType::Empty:
|
|
638
|
+
case OpenXLSX::XLValueType::Error:
|
|
639
|
+
default:
|
|
640
|
+
return nitro::NullType{};
|
|
641
|
+
}
|
|
642
|
+
});
|
|
495
643
|
}
|
|
496
644
|
|
|
497
645
|
std::string HybridXlsxWorksheet::getCellString(double row, double col) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
646
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
647
|
+
OpenXLSX::XLCellAssignable cell = _worksheet.findCell(static_cast<uint32_t>(row), static_cast<uint16_t>(col));
|
|
648
|
+
if (cell.empty()) return std::string();
|
|
649
|
+
auto type = cell.value().type();
|
|
650
|
+
|
|
651
|
+
switch (type) {
|
|
652
|
+
case OpenXLSX::XLValueType::String:
|
|
653
|
+
return cell.value().get<std::string>();
|
|
654
|
+
case OpenXLSX::XLValueType::Integer:
|
|
655
|
+
return std::to_string(cell.value().get<int64_t>());
|
|
656
|
+
case OpenXLSX::XLValueType::Float:
|
|
657
|
+
return std::to_string(cell.value().get<double>());
|
|
658
|
+
case OpenXLSX::XLValueType::Boolean:
|
|
659
|
+
return cell.value().get<bool>() ? std::string("TRUE") : std::string("FALSE");
|
|
660
|
+
case OpenXLSX::XLValueType::Empty:
|
|
661
|
+
case OpenXLSX::XLValueType::Error:
|
|
662
|
+
default:
|
|
663
|
+
return std::string();
|
|
664
|
+
}
|
|
665
|
+
});
|
|
515
666
|
}
|
|
516
667
|
|
|
517
668
|
std::string HybridXlsxWorksheet::getCellRawValue(double row, double col) {
|
|
518
|
-
|
|
669
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
670
|
+
OpenXLSX::XLCellAssignable cell = _worksheet.findCell(static_cast<uint32_t>(row), static_cast<uint16_t>(col));
|
|
671
|
+
if (cell.empty()) return std::string();
|
|
519
672
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
673
|
+
if (cell.hasFormula()) {
|
|
674
|
+
return cell.formula().get();
|
|
675
|
+
}
|
|
523
676
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
677
|
+
auto type = cell.value().type();
|
|
678
|
+
switch (type) {
|
|
679
|
+
case OpenXLSX::XLValueType::String:
|
|
680
|
+
return cell.value().get<std::string>();
|
|
681
|
+
case OpenXLSX::XLValueType::Integer:
|
|
682
|
+
return std::to_string(cell.value().get<int64_t>());
|
|
683
|
+
case OpenXLSX::XLValueType::Float:
|
|
684
|
+
return std::to_string(cell.value().get<double>());
|
|
685
|
+
case OpenXLSX::XLValueType::Boolean:
|
|
686
|
+
return cell.value().get<bool>() ? std::string("TRUE") : std::string("FALSE");
|
|
687
|
+
case OpenXLSX::XLValueType::Empty:
|
|
688
|
+
case OpenXLSX::XLValueType::Error:
|
|
689
|
+
default:
|
|
690
|
+
return std::string();
|
|
691
|
+
}
|
|
692
|
+
});
|
|
539
693
|
}
|
|
540
694
|
|
|
541
695
|
CellType HybridXlsxWorksheet::getCellType(double row, double col) {
|
|
542
|
-
|
|
543
|
-
|
|
696
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
697
|
+
OpenXLSX::XLCellAssignable cell = _worksheet.findCell(static_cast<uint32_t>(row), static_cast<uint16_t>(col));
|
|
698
|
+
if (cell.empty()) return CellType::EMPTY;
|
|
544
699
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
700
|
+
if (cell.hasFormula()) {
|
|
701
|
+
return CellType::FORMULA;
|
|
702
|
+
}
|
|
548
703
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
704
|
+
auto type = cell.value().type();
|
|
705
|
+
switch (type) {
|
|
706
|
+
case OpenXLSX::XLValueType::Empty:
|
|
707
|
+
return CellType::BLANK;
|
|
708
|
+
case OpenXLSX::XLValueType::String:
|
|
709
|
+
return CellType::STRING;
|
|
710
|
+
case OpenXLSX::XLValueType::Integer:
|
|
711
|
+
case OpenXLSX::XLValueType::Float:
|
|
712
|
+
if (numFmtIsDateTime(_doc.styles(), cell.cellFormat())) {
|
|
713
|
+
return CellType::DATE;
|
|
714
|
+
}
|
|
715
|
+
return CellType::NUMBER;
|
|
716
|
+
case OpenXLSX::XLValueType::Boolean:
|
|
717
|
+
return CellType::BOOLEAN;
|
|
718
|
+
case OpenXLSX::XLValueType::Error:
|
|
719
|
+
return CellType::ERROR;
|
|
720
|
+
default:
|
|
721
|
+
return CellType::EMPTY;
|
|
722
|
+
}
|
|
723
|
+
});
|
|
564
724
|
}
|
|
565
725
|
|
|
566
726
|
std::shared_ptr<HybridXlsxCellFormatSpec> HybridXlsxWorksheet::getCellFormat(double row, double col) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
727
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::shared_ptr<HybridXlsxCellFormatSpec> {
|
|
728
|
+
auto format = std::make_shared<HybridXlsxCellFormat>();
|
|
729
|
+
OpenXLSX::XLCellAssignable cell = _worksheet.findCell(static_cast<uint32_t>(row), static_cast<uint16_t>(col));
|
|
730
|
+
if (cell.empty()) return format;
|
|
731
|
+
|
|
732
|
+
auto& styles = _doc.styles();
|
|
733
|
+
OpenXLSX::XLCellFormat cellFormat = styles.cellFormats()[cell.cellFormat()];
|
|
734
|
+
|
|
735
|
+
// Number format
|
|
736
|
+
std::string numFmtCode = lookupNumFmtCode(styles, cellFormat.numberFormatId());
|
|
737
|
+
if (!numFmtCode.empty() && numFmtCode != "General") {
|
|
738
|
+
format->setNumFormat(numFmtCode);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Font
|
|
742
|
+
OpenXLSX::XLFont font = styles.fonts()[cellFormat.fontIndex()];
|
|
743
|
+
if (!font.fontName().empty()) format->setFontName(font.fontName());
|
|
744
|
+
if (font.fontSize() > 0) format->setFontSize(static_cast<double>(font.fontSize()));
|
|
745
|
+
try {
|
|
746
|
+
double fontColor = xlColorToDouble(font.fontColor());
|
|
747
|
+
if (fontColor > 0) format->setFontColor(fontColor);
|
|
748
|
+
} catch (const std::exception&) {}
|
|
749
|
+
if (font.bold()) format->setBold();
|
|
750
|
+
if (font.italic()) format->setItalic();
|
|
751
|
+
if (font.strikethrough()) format->setStrikeout();
|
|
752
|
+
{
|
|
753
|
+
auto underline = font.underline();
|
|
754
|
+
if (underline != OpenXLSX::XLUnderlineNone && underline != OpenXLSX::XLUnderlineInvalid) {
|
|
755
|
+
format->setUnderline(static_cast<double>(underline));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
{
|
|
759
|
+
auto vertAlign = font.vertAlign();
|
|
760
|
+
if (vertAlign != OpenXLSX::XLBaseline && vertAlign != OpenXLSX::XLVerticalAlignRunInvalid) {
|
|
761
|
+
format->setFontScript(static_cast<double>(vertAlign));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Alignment
|
|
766
|
+
OpenXLSX::XLAlignment alignment = cellFormat.alignment(OpenXLSX::XLDoNotCreate);
|
|
767
|
+
{
|
|
768
|
+
auto horizontal = alignment.horizontal();
|
|
769
|
+
if (horizontal != OpenXLSX::XLAlignGeneral && horizontal != OpenXLSX::XLAlignInvalid) {
|
|
770
|
+
format->setAlign(static_cast<double>(horizontal));
|
|
771
|
+
}
|
|
772
|
+
auto vertical = alignment.vertical();
|
|
773
|
+
if (vertical != OpenXLSX::XLAlignGeneral && vertical != OpenXLSX::XLAlignInvalid) {
|
|
774
|
+
format->setVerticalAlign(static_cast<double>(vertical));
|
|
775
|
+
}
|
|
776
|
+
if (alignment.wrapText()) format->setTextWrap();
|
|
777
|
+
if (alignment.textRotation() != 0) format->setRotation(static_cast<double>(alignment.textRotation()));
|
|
778
|
+
if (alignment.indent() != 0) format->setIndent(static_cast<double>(alignment.indent()));
|
|
779
|
+
if (alignment.shrinkToFit()) format->setShrink();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Fill (pattern + fg/bg colors)
|
|
783
|
+
OpenXLSX::XLFill fill = styles.fills()[cellFormat.fillIndex()];
|
|
784
|
+
if (fill.fillType() == OpenXLSX::XLPatternFill) {
|
|
785
|
+
auto pattern = fill.patternType();
|
|
786
|
+
if (pattern != OpenXLSX::XLPatternNone && pattern != OpenXLSX::XLPatternTypeInvalid) {
|
|
787
|
+
format->setPattern(static_cast<double>(pattern));
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
double fg = xlColorToDouble(fill.color());
|
|
791
|
+
if (fg > 0) format->setFgColor(fg);
|
|
792
|
+
} catch (const std::exception&) {}
|
|
793
|
+
try {
|
|
794
|
+
double bg = xlColorToDouble(fill.backgroundColor());
|
|
795
|
+
if (bg > 0) format->setBgColor(bg);
|
|
796
|
+
} catch (const std::exception&) {}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Border (per-side styles and colors)
|
|
800
|
+
OpenXLSX::XLBorder border = styles.borders()[cellFormat.borderIndex()];
|
|
801
|
+
double leftStyle = 0, rightStyle = 0, topStyle = 0, bottomStyle = 0;
|
|
802
|
+
double leftColor = 0, rightColor = 0, topColor = 0, bottomColor = 0;
|
|
803
|
+
auto readLine = [](const OpenXLSX::XLLine& line, double& styleOut, double& colorOut) {
|
|
804
|
+
if (!line) return;
|
|
805
|
+
auto style = line.style();
|
|
806
|
+
if (style != OpenXLSX::XLLineStyleNone && style != OpenXLSX::XLLineStyleInvalid) {
|
|
807
|
+
styleOut = static_cast<double>(style);
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
double c = xlColorToDouble(line.color().rgb());
|
|
811
|
+
if (c > 0) colorOut = c;
|
|
812
|
+
} catch (const std::exception&) {}
|
|
813
|
+
};
|
|
814
|
+
readLine(border.left(), leftStyle, leftColor);
|
|
815
|
+
readLine(border.right(), rightStyle, rightColor);
|
|
816
|
+
readLine(border.top(), topStyle, topColor);
|
|
817
|
+
readLine(border.bottom(), bottomStyle, bottomColor);
|
|
818
|
+
if (leftStyle > 0) format->setLeft(leftStyle);
|
|
819
|
+
if (rightStyle > 0) format->setRight(rightStyle);
|
|
820
|
+
if (topStyle > 0) format->setTop(topStyle);
|
|
821
|
+
if (bottomStyle > 0) format->setBottom(bottomStyle);
|
|
822
|
+
if (leftColor > 0) format->setLeftColor(leftColor);
|
|
823
|
+
if (rightColor > 0) format->setRightColor(rightColor);
|
|
824
|
+
if (topColor > 0) format->setTopColor(topColor);
|
|
825
|
+
if (bottomColor > 0) format->setBottomColor(bottomColor);
|
|
826
|
+
if (leftStyle > 0 && leftStyle == rightStyle && leftStyle == topStyle && leftStyle == bottomStyle) {
|
|
827
|
+
format->setBorder(leftStyle);
|
|
828
|
+
if (leftColor > 0 && leftColor == rightColor && leftColor == topColor && leftColor == bottomColor) {
|
|
829
|
+
format->setBorderColor(leftColor);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Protection
|
|
834
|
+
if (cellFormat.applyProtection()) {
|
|
835
|
+
if (cellFormat.locked()) format->setLocked();
|
|
836
|
+
else format->setUnlocked();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return format;
|
|
840
|
+
});
|
|
570
841
|
}
|
|
571
842
|
|
|
572
843
|
double HybridXlsxWorksheet::getRowCount() {
|
|
573
|
-
return
|
|
844
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
845
|
+
return static_cast<double>(_worksheet.rowCount());
|
|
846
|
+
});
|
|
574
847
|
}
|
|
575
848
|
|
|
576
849
|
double HybridXlsxWorksheet::getColumnCount() {
|
|
577
|
-
return
|
|
850
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
851
|
+
return static_cast<double>(_worksheet.columnCount());
|
|
852
|
+
});
|
|
578
853
|
}
|
|
579
854
|
|
|
580
855
|
double HybridXlsxWorksheet::getLastRow() {
|
|
581
|
-
|
|
582
|
-
|
|
856
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
857
|
+
if (_worksheet.rowCount() == 0) return 0.0;
|
|
858
|
+
return static_cast<double>(_worksheet.lastCell().row());
|
|
859
|
+
});
|
|
583
860
|
}
|
|
584
861
|
|
|
585
862
|
double HybridXlsxWorksheet::getLastColumn() {
|
|
586
|
-
|
|
587
|
-
|
|
863
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
864
|
+
if (_worksheet.rowCount() == 0) return 0.0;
|
|
865
|
+
return static_cast<double>(_worksheet.lastCell().column());
|
|
866
|
+
});
|
|
588
867
|
}
|
|
589
868
|
|
|
590
869
|
std::string HybridXlsxWorksheet::getName() {
|
|
591
|
-
return
|
|
870
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
871
|
+
return _worksheet.name();
|
|
872
|
+
});
|
|
592
873
|
}
|
|
593
874
|
|
|
594
875
|
void HybridXlsxWorksheet::applyRename(const std::string& oldName, const std::string& newName) {
|