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.
Files changed (57) hide show
  1. package/README.md +20 -8
  2. package/cpp/HybridNitroXlsx.cpp +25 -1
  3. package/cpp/HybridXlsxCellFormat.cpp +46 -146
  4. package/cpp/HybridXlsxCellFormat.hpp +9 -41
  5. package/cpp/HybridXlsxConditionalFormat.cpp +4 -1
  6. package/cpp/HybridXlsxConditionalFormats.cpp +5 -1
  7. package/cpp/HybridXlsxWorkbook.cpp +38 -8
  8. package/cpp/HybridXlsxWorkbook.hpp +10 -1
  9. package/cpp/HybridXlsxWorksheet.cpp +422 -141
  10. package/cpp/HybridXlsxWorksheet.hpp +4 -1
  11. package/cpp/XlsxCacheDir.cpp +25 -2
  12. package/cpp/XlsxCacheDir.hpp +3 -1
  13. package/cpp/XlsxCellStyle.hpp +360 -0
  14. package/cpp/XlsxJson.cpp +29 -8
  15. package/package.json +1 -1
  16. package/scripts/sync-openxlsx-headers.sh +34 -0
  17. package/src/__mocks__/react-native-nitro-modules.ts +523 -20
  18. package/src/__tests__/index.test.ts +124 -16
  19. package/src/index.ts +13 -13
  20. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/IZipArchive.hpp +361 -0
  21. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/OpenXLSX-Exports.hpp +12 -0
  22. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCell.hpp +367 -0
  23. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellIterator.hpp +252 -0
  24. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellRange.hpp +223 -0
  25. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellReference.hpp +317 -0
  26. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellValue.hpp +793 -0
  27. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColor.hpp +232 -0
  28. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColumn.hpp +159 -0
  29. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCommandQuery.hpp +228 -0
  30. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLComments.hpp +270 -0
  31. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLConstants.hpp +18 -0
  32. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLContentTypes.hpp +264 -0
  33. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDateTime.hpp +175 -0
  34. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDocument.hpp +505 -0
  35. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDrawing.hpp +452 -0
  36. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLException.hpp +161 -0
  37. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLFormula.hpp +375 -0
  38. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLIterator.hpp +40 -0
  39. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLMergeCells.hpp +216 -0
  40. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLProperties.hpp +348 -0
  41. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRelationships.hpp +348 -0
  42. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRow.hpp +721 -0
  43. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRowData.hpp +488 -0
  44. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSharedStrings.hpp +204 -0
  45. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSheet.hpp +1640 -0
  46. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLStyles.hpp +2390 -0
  47. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLTables.hpp +132 -0
  48. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLWorkbook.hpp +404 -0
  49. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlData.hpp +283 -0
  50. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlFile.hpp +194 -0
  51. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParser.hpp +282 -0
  52. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParserForwardDeclarations.hpp +85 -0
  53. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLZipArchive.hpp +232 -0
  54. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/LibZip.hpp +447 -0
  55. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/OpenXLSXFileSystemTools.hpp +207 -0
  56. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/Zippy.hpp +1375 -0
  57. 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, double firstCol, double lastRow, double lastCol, const std::string& formula, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
96
- withXlsxError(xlsx_error::XLSX_ERROR, [&] {
97
- OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
98
- cell.formula() = formula;
99
- applyFormat(cell, format);
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
- for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
121
- _worksheet.column(c).setWidth(static_cast<float>(width));
122
- }
123
- if (format.has_value()) {
124
- // Apply format to entire column range by setting it on cells in the first row
125
- // (OpenXLSX does not support column-level style; apply to first row cells as a hint)
126
- auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
127
- if (hybridFormat) {
128
- auto styleIndex = hybridFormat->applyToDocument(_doc.styles());
129
- for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
130
- OpenXLSX::XLCell cell = _worksheet.cell(1, c);
131
- hybridFormat->applyToCell(cell, styleIndex);
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
- _worksheet.row(static_cast<unsigned int>(row)).setHeight(static_cast<float>(height));
139
- if (format.has_value()) {
140
- auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
141
- if (hybridFormat) {
142
- auto styleIndex = hybridFormat->applyToDocument(_doc.styles());
143
- unsigned int colCount = _worksheet.columnCount();
144
- for (unsigned int c = 1; c <= colCount; ++c) {
145
- OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(row), c);
146
- hybridFormat->applyToCell(cell, styleIndex);
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::XLCell src = _worksheet.cell(r, c);
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
- dst.clear(0);
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::XLCell trailing = _worksheet.cell(r, lastCol);
185
- trailing.clear(0);
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
- OpenXLSX::XLCellReference topLeft(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
202
- OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
203
- OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
204
- _worksheet.mergeCells(range);
205
- OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
206
- cell.value() = value;
207
- applyFormat(cell, format);
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
- OpenXLSX::XLCellReference topLeft(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
212
- OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
213
- OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
214
- _worksheet.mergeCells(range);
215
- OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
216
- cell.value() = value;
217
- applyFormat(cell, format);
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
- _worksheet.setActive();
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
- for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
469
- _worksheet.column(c).setHidden(hidden);
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
- _worksheet.row(static_cast<unsigned int>(row)).setHidden(hidden);
494
+ withXlsxError(xlsx_error::XLSX_ERROR, [&] {
495
+ _worksheet.row(static_cast<unsigned int>(row)).setHidden(hidden);
496
+ });
475
497
  }
476
498
 
477
- std::variant<bool, nitro::NullType, std::string, double> HybridXlsxWorksheet::getCellValue(double row, double col) {
478
- auto cell = _worksheet.cell(static_cast<unsigned int>(row), static_cast<unsigned int>(col));
479
- auto type = cell.value().type();
480
-
481
- switch (type) {
482
- case OpenXLSX::XLValueType::String:
483
- return cell.value().get<std::string>();
484
- case OpenXLSX::XLValueType::Integer:
485
- return static_cast<double>(cell.value().get<int64_t>());
486
- case OpenXLSX::XLValueType::Float:
487
- return cell.value().get<double>();
488
- case OpenXLSX::XLValueType::Boolean:
489
- return cell.value().get<bool>();
490
- case OpenXLSX::XLValueType::Empty:
491
- case OpenXLSX::XLValueType::Error:
492
- default:
493
- return nitro::NullType{};
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
- auto cell = _worksheet.cell(static_cast<unsigned int>(row), static_cast<unsigned int>(col));
499
- auto type = cell.value().type();
500
-
501
- switch (type) {
502
- case OpenXLSX::XLValueType::String:
503
- return cell.value().get<std::string>();
504
- case OpenXLSX::XLValueType::Integer:
505
- return std::to_string(cell.value().get<int64_t>());
506
- case OpenXLSX::XLValueType::Float:
507
- return std::to_string(cell.value().get<double>());
508
- case OpenXLSX::XLValueType::Boolean:
509
- return cell.value().get<bool>() ? "TRUE" : "FALSE";
510
- case OpenXLSX::XLValueType::Empty:
511
- case OpenXLSX::XLValueType::Error:
512
- default:
513
- return "";
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
- auto cell = _worksheet.cell(static_cast<unsigned int>(row), static_cast<unsigned int>(col));
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
- if (cell.hasFormula()) {
521
- return cell.formula().get();
522
- }
673
+ if (cell.hasFormula()) {
674
+ return cell.formula().get();
675
+ }
523
676
 
524
- auto type = cell.value().type();
525
- switch (type) {
526
- case OpenXLSX::XLValueType::String:
527
- return cell.value().get<std::string>();
528
- case OpenXLSX::XLValueType::Integer:
529
- return std::to_string(cell.value().get<int64_t>());
530
- case OpenXLSX::XLValueType::Float:
531
- return std::to_string(cell.value().get<double>());
532
- case OpenXLSX::XLValueType::Boolean:
533
- return cell.value().get<bool>() ? "TRUE" : "FALSE";
534
- case OpenXLSX::XLValueType::Empty:
535
- case OpenXLSX::XLValueType::Error:
536
- default:
537
- return "";
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
- auto cell = _worksheet.cell(static_cast<unsigned int>(row), static_cast<unsigned int>(col));
543
- auto type = cell.value().type();
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
- if (cell.hasFormula()) {
546
- return CellType::FORMULA;
547
- }
700
+ if (cell.hasFormula()) {
701
+ return CellType::FORMULA;
702
+ }
548
703
 
549
- switch (type) {
550
- case OpenXLSX::XLValueType::Empty:
551
- return CellType::EMPTY;
552
- case OpenXLSX::XLValueType::String:
553
- return CellType::STRING;
554
- case OpenXLSX::XLValueType::Integer:
555
- case OpenXLSX::XLValueType::Float:
556
- return CellType::NUMBER;
557
- case OpenXLSX::XLValueType::Boolean:
558
- return CellType::BOOLEAN;
559
- case OpenXLSX::XLValueType::Error:
560
- return CellType::ERROR;
561
- default:
562
- return CellType::EMPTY;
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
- auto cell = _worksheet.cell(static_cast<unsigned int>(row), static_cast<unsigned int>(col));
568
- auto format = std::make_shared<HybridXlsxCellFormat>();
569
- return format;
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 static_cast<double>(_worksheet.rowCount());
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 static_cast<double>(_worksheet.columnCount());
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
- auto last = _worksheet.lastCell();
582
- return static_cast<double>(last.row());
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
- auto last = _worksheet.lastCell();
587
- return static_cast<double>(last.column());
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 _worksheet.name();
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) {