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
@@ -13,7 +13,9 @@ namespace margelo::nitro::xlsx {
13
13
 
14
14
  class HybridXlsxWorksheet : public HybridXlsxWorksheetSpec {
15
15
  public:
16
- HybridXlsxWorksheet(OpenXLSX::XLDocument& doc, OpenXLSX::XLWorksheet worksheet);
16
+ // `documentGeneration` must come from the owning workbook so cell format
17
+ // caching stays scoped to one document (see XlsxCellStyle::applyToDocument).
18
+ HybridXlsxWorksheet(OpenXLSX::XLDocument& doc, OpenXLSX::XLWorksheet worksheet, uint64_t documentGeneration = 0);
17
19
  ~HybridXlsxWorksheet() override;
18
20
 
19
21
  void writeString(double row, double col, const std::string& value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) override;
@@ -125,6 +127,7 @@ public:
125
127
  private:
126
128
  OpenXLSX::XLDocument& _doc;
127
129
  OpenXLSX::XLWorksheet _worksheet;
130
+ uint64_t _documentGeneration;
128
131
 
129
132
  void applyFormat(OpenXLSX::XLCell& cell, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format);
130
133
  std::string cellAddress(double row, double col);
@@ -6,7 +6,10 @@
6
6
  #include "XlsxCacheDir.hpp"
7
7
 
8
8
  #include <cstdlib>
9
+ #include <dirent.h>
10
+ #include <mutex>
9
11
  #include <sys/stat.h>
12
+ #include <unistd.h>
10
13
 
11
14
  #ifdef __ANDROID__
12
15
  #include <jni.h>
@@ -17,20 +20,31 @@ namespace margelo::nitro::xlsx {
17
20
 
18
21
  namespace {
19
22
  std::string g_cacheDir;
23
+ std::mutex g_cacheDirMutex;
20
24
 
21
25
  bool isUsableDir(const std::string& path) {
22
26
  if (path.empty()) return false;
23
27
  struct stat st {};
24
- return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
28
+ if (stat(path.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) return false;
29
+ // Must be writable (and traversable) for temp XLSX files to be created.
30
+ if (access(path.c_str(), W_OK | X_OK) != 0) return false;
31
+ DIR* dir = opendir(path.c_str());
32
+ if (dir == nullptr) return false;
33
+ closedir(dir);
34
+ return true;
25
35
  }
26
36
  }
27
37
 
28
38
  void setCacheDir(const std::string& dir) {
39
+ std::lock_guard<std::mutex> lock(g_cacheDirMutex);
29
40
  g_cacheDir = dir;
30
41
  }
31
42
 
32
43
  std::string getCacheDir() {
33
- if (!g_cacheDir.empty()) return g_cacheDir;
44
+ {
45
+ std::lock_guard<std::mutex> lock(g_cacheDirMutex);
46
+ if (!g_cacheDir.empty()) return g_cacheDir;
47
+ }
34
48
  return detail::resolvePlatformCacheDir();
35
49
  }
36
50
 
@@ -63,10 +77,19 @@ std::string resolvePlatformCacheDir() {
63
77
  }
64
78
  #endif
65
79
 
80
+ // Fallback candidates, first usable one wins:
81
+ // 1. $TMPDIR
82
+ // 2. /tmp
83
+ // 3. /data/local/tmp (writable shell dir on Android)
84
+ // /tmp is returned unvalidated only as a last resort when none of the
85
+ // candidates is usable, since e.g. Android may not provide /tmp at all.
66
86
  const char* tmp = std::getenv("TMPDIR");
67
87
  if (tmp != nullptr && isUsableDir(tmp)) {
68
88
  return std::string(tmp);
69
89
  }
90
+ for (const char* candidate : {"/tmp", "/data/local/tmp"}) {
91
+ if (isUsableDir(candidate)) return std::string(candidate);
92
+ }
70
93
  return "/tmp";
71
94
  }
72
95
 
@@ -13,7 +13,9 @@ void setCacheDir(const std::string& dir);
13
13
 
14
14
  /**
15
15
  * Directory used for temporary XLSX files.
16
- * Priority: explicit setCacheDir() > platform cache dir > TMPDIR > /tmp
16
+ * Priority: explicit setCacheDir() > platform cache dir > $TMPDIR > /tmp > /data/local/tmp,
17
+ * where fallbacks are validated as writable directories before being chosen and
18
+ * "/tmp" is used unvalidated only as a last resort.
17
19
  *
18
20
  * Implemented in ios/XlsxCacheDir.mm (Apple) and cpp/XlsxCacheDir.cpp (other).
19
21
  */
@@ -0,0 +1,360 @@
1
+ #pragma once
2
+
3
+ // Nitro-free implementation of the XlsxCellFormat style state and its
4
+ // materialization into a document's styles.xml. HybridXlsxCellFormat wraps this
5
+ // class; unit tests exercise it directly (see cpp/tests/format_testable.hpp).
6
+ #include <OpenXLSX/XLStyles.hpp>
7
+ #include <OpenXLSX/XLCell.hpp>
8
+
9
+ #include <cctype>
10
+ #include <cstdint>
11
+ #include <iomanip>
12
+ #include <optional>
13
+ #include <sstream>
14
+ #include <string>
15
+
16
+ namespace margelo::nitro::xlsx {
17
+
18
+ // Excel stores colors as ARGB hex strings; the alpha channel is always opaque.
19
+ inline std::string doubleToHexColor(double color) {
20
+ uint32_t c = static_cast<uint32_t>(color);
21
+ uint32_t argb = c | 0xFF000000;
22
+ std::stringstream ss;
23
+ ss << std::hex << std::setfill('0') << std::setw(8) << argb;
24
+ std::string result = ss.str();
25
+ for (char& ch : result) {
26
+ ch = std::toupper(ch);
27
+ }
28
+ return result;
29
+ }
30
+
31
+ class XlsxCellStyle {
32
+ public:
33
+ XlsxCellStyle() = default;
34
+ ~XlsxCellStyle() = default;
35
+
36
+ // Setters (each invalidates the cached style index)
37
+ void setFontName(const std::string& name) { _fontName = name; invalidate(); }
38
+ void setFontSize(double size) { _fontSize = size; invalidate(); }
39
+ void setFontColor(double color) { _fontColor = color; invalidate(); }
40
+ void setFontScript(double style) { _fontScript = style; invalidate(); }
41
+ void setBold() { _bold = true; invalidate(); }
42
+ void setItalic() { _italic = true; invalidate(); }
43
+ void setUnderline(std::optional<double> style) { _underline = style.value_or(1); invalidate(); }
44
+ void setStrikeout() { _strikeout = true; invalidate(); }
45
+ void setAlign(double align) { _align = align; invalidate(); }
46
+ void setVerticalAlign(double align) { _verticalAlign = align; invalidate(); }
47
+ void setTextWrap() { _textWrap = true; invalidate(); }
48
+ void setRotation(double angle) { _rotation = angle; invalidate(); }
49
+ void setIndent(double level) { _indent = level; invalidate(); }
50
+ void setShrink() { _shrink = true; invalidate(); }
51
+ void setNumFormat(const std::string& format) { _numFormat = format; invalidate(); }
52
+ void setNumFormatIndex(double index) { _numFormatIndex = index; invalidate(); }
53
+ void setBgColor(double color) { _bgColor = color; invalidate(); }
54
+ void setFgColor(double color) { _fgColor = color; invalidate(); }
55
+ void setPattern(double pattern) { _pattern = pattern; invalidate(); }
56
+ void setBorder(double style) { _border = style; invalidate(); }
57
+ void setBorderColor(double color) { _borderColor = color; invalidate(); }
58
+ void setBottom(double style) { _bottom = style; invalidate(); }
59
+ void setTop(double style) { _top = style; invalidate(); }
60
+ void setLeft(double style) { _left = style; invalidate(); }
61
+ void setRight(double style) { _right = style; invalidate(); }
62
+ void setBottomColor(double color) { _bottomColor = color; invalidate(); }
63
+ void setTopColor(double color) { _topColor = color; invalidate(); }
64
+ void setLeftColor(double color) { _leftColor = color; invalidate(); }
65
+ void setRightColor(double color) { _rightColor = color; invalidate(); }
66
+ void setDiagonal(double style) { _diagonal = style; invalidate(); }
67
+ void setDiagonalColor(double color) { _diagonalColor = color; invalidate(); }
68
+ void setLocked() { _locked = true; invalidate(); }
69
+ void setUnlocked() { _locked = false; invalidate(); }
70
+ void setHyperlink() { _hyperlink = true; invalidate(); }
71
+ void setFontOnly() { _fontOnly = true; invalidate(); }
72
+
73
+ // Getters (unstored colors read back as 0, like the nitro spec)
74
+ std::string getFontName() const { return _fontName; }
75
+ double getFontSize() const { return _fontSize; }
76
+ double getFontColor() const { return _fontColor.value_or(0); }
77
+ double getFontScript() const { return _fontScript; }
78
+ bool getIsBold() const { return _bold; }
79
+ bool getIsItalic() const { return _italic; }
80
+ double getUnderline() const { return _underline; }
81
+ bool getIsStrikeout() const { return _strikeout; }
82
+ double getAlign() const { return _align; }
83
+ double getVerticalAlign() const { return _verticalAlign; }
84
+ bool getIsTextWrap() const { return _textWrap; }
85
+ double getRotation() const { return _rotation; }
86
+ double getIndent() const { return _indent; }
87
+ bool getIsShrink() const { return _shrink; }
88
+ std::string getNumFormat() const { return _numFormat; }
89
+ double getNumFormatIndex() const { return _numFormatIndex.value_or(0); }
90
+ double getBgColor() const { return _bgColor.value_or(0); }
91
+ double getFgColor() const { return _fgColor.value_or(0); }
92
+ double getPattern() const { return _pattern; }
93
+ double getBorder() const { return _border; }
94
+ double getBorderColor() const { return _borderColor.value_or(0); }
95
+ double getBottom() const { return _bottom; }
96
+ double getTop() const { return _top; }
97
+ double getLeft() const { return _left; }
98
+ double getRight() const { return _right; }
99
+ double getBottomColor() const { return _bottomColor.value_or(0); }
100
+ double getTopColor() const { return _topColor.value_or(0); }
101
+ double getLeftColor() const { return _leftColor.value_or(0); }
102
+ double getRightColor() const { return _rightColor.value_or(0); }
103
+ double getDiagonal() const { return _diagonal; }
104
+ double getDiagonalColor() const { return _diagonalColor.value_or(0); }
105
+ bool getIsLocked() const { return _locked.value_or(false); }
106
+ bool getIsHyperlink() const { return _hyperlink; }
107
+ bool getIsFontOnly() const { return _fontOnly; }
108
+
109
+ // Materialize the held style state into the document's styles.xml.
110
+ // The resulting cell format index is cached for one document and invalidated
111
+ // whenever a setter runs.
112
+ //
113
+ // The cache is keyed on (styles address, documentGeneration) rather than on
114
+ // the address alone: a destroyed XLDocument's XLStyles is routinely
115
+ // reallocated at the same address for the next document, and a pointer-only
116
+ // check would then hand out a cell format index that does not exist in the
117
+ // new document ("s=" pointing past the end of cellXfs).
118
+ OpenXLSX::XLStyleIndex applyToDocument(OpenXLSX::XLStyles& styles, uint64_t documentGeneration = 0) {
119
+ if (_styleIndex.has_value() && _cachedStyles == &styles && _cachedGeneration == documentGeneration) {
120
+ return *_styleIndex;
121
+ }
122
+
123
+ // Create cell format in document first to get a valid XML node.
124
+ // OpenXLSX default-constructed style objects have empty XML nodes;
125
+ // setters on empty nodes silently fail. We must create the node in
126
+ // the document first, then modify the live node via operator[].
127
+ OpenXLSX::XLCellFormat emptyCellFormat;
128
+ OpenXLSX::XLStyleIndex cellFormatIndex = styles.cellFormats().create(emptyCellFormat);
129
+ OpenXLSX::XLCellFormat cellFormat = styles.cellFormats()[cellFormatIndex];
130
+
131
+ // Font
132
+ if (!_fontName.empty() || _fontSize > 0 || _fontColor.has_value() || _bold || _italic
133
+ || _underline > 0 || _strikeout || _fontScript > 0) {
134
+ OpenXLSX::XLFont emptyFont;
135
+ OpenXLSX::XLStyleIndex fontIndex = styles.fonts().create(emptyFont);
136
+ OpenXLSX::XLFont font = styles.fonts()[fontIndex];
137
+ if (!_fontName.empty()) font.setFontName(_fontName);
138
+ if (_fontSize > 0) font.setFontSize(static_cast<size_t>(_fontSize));
139
+ if (_fontColor.has_value()) font.setFontColor(OpenXLSX::XLColor(doubleToHexColor(*_fontColor)));
140
+ if (_bold) font.setBold();
141
+ if (_italic) font.setItalic();
142
+ if (_underline > 0) font.setUnderline(static_cast<OpenXLSX::XLUnderlineStyle>(_underline));
143
+ if (_strikeout) font.setStrikethrough();
144
+ if (_fontScript > 0) font.setVertAlign(static_cast<OpenXLSX::XLVerticalAlignRunStyle>(_fontScript));
145
+ cellFormat.setFontIndex(fontIndex);
146
+ cellFormat.setApplyFont(true);
147
+ }
148
+
149
+ // Alignment
150
+ if (_textWrap || _rotation != 0 || _indent > 0 || _align > 0 || _verticalAlign > 0 || _shrink) {
151
+ if (_align > 0) cellFormat.alignment(true).setHorizontal(static_cast<OpenXLSX::XLAlignmentStyle>(_align));
152
+ if (_verticalAlign > 0) cellFormat.alignment(true).setVertical(static_cast<OpenXLSX::XLAlignmentStyle>(_verticalAlign));
153
+ if (_textWrap) cellFormat.alignment(true).setWrapText();
154
+ if (_rotation != 0) cellFormat.alignment(true).setTextRotation(static_cast<uint16_t>(_rotation));
155
+ if (_indent > 0) cellFormat.alignment(true).setIndent(static_cast<uint32_t>(_indent));
156
+ if (_shrink) cellFormat.alignment(true).setShrinkToFit();
157
+ cellFormat.setApplyAlignment(true);
158
+ }
159
+
160
+ // Number format: a custom format string wins over a built-in format index.
161
+ if (!_numFormat.empty()) {
162
+ // Reuse an existing entry with the same format code instead of adding a
163
+ // duplicate id for it.
164
+ uint32_t numFmtId = 0;
165
+ bool found = false;
166
+ for (size_t i = 0; i < styles.numberFormats().count(); ++i) {
167
+ OpenXLSX::XLNumberFormat existing = styles.numberFormats()[i];
168
+ if (existing.formatCode() == _numFormat) {
169
+ numFmtId = existing.numberFormatId();
170
+ found = true;
171
+ break;
172
+ }
173
+ }
174
+ if (!found) {
175
+ // Custom numFmtIds must be unique within this document; built-in
176
+ // formats occupy ids 0-163, so scan for a free id >= 164 instead of
177
+ // sharing a static counter across documents.
178
+ uint32_t nextId = 164;
179
+ for (size_t i = 0; i < styles.numberFormats().count(); ++i) {
180
+ uint32_t id = styles.numberFormats()[i].numberFormatId();
181
+ if (id >= nextId) nextId = id + 1;
182
+ }
183
+ OpenXLSX::XLNumberFormat emptyNumFormat;
184
+ OpenXLSX::XLStyleIndex numFmtIndex = styles.numberFormats().create(emptyNumFormat);
185
+ OpenXLSX::XLNumberFormat numFormat = styles.numberFormats()[numFmtIndex];
186
+ numFormat.setFormatCode(_numFormat);
187
+ numFormat.setNumberFormatId(nextId);
188
+ numFmtId = nextId;
189
+ }
190
+ cellFormat.setNumberFormatId(numFmtId);
191
+ cellFormat.setApplyNumberFormat(true);
192
+ } else if (_numFormatIndex.has_value()) {
193
+ cellFormat.setNumberFormatId(static_cast<uint32_t>(*_numFormatIndex));
194
+ cellFormat.setApplyNumberFormat(true);
195
+ }
196
+
197
+ // Fill
198
+ if (_bgColor.has_value() || _fgColor.has_value() || _pattern > 0) {
199
+ OpenXLSX::XLFill emptyFill;
200
+ OpenXLSX::XLStyleIndex fillIndex = styles.fills().create(emptyFill);
201
+ OpenXLSX::XLFill fill = styles.fills()[fillIndex];
202
+ OpenXLSX::XLPatternType patternType = _pattern > 0
203
+ ? static_cast<OpenXLSX::XLPatternType>(_pattern)
204
+ : OpenXLSX::XLPatternType::XLPatternSolid;
205
+ fill.setPatternType(patternType);
206
+ if (_bgColor.has_value()) fill.setBackgroundColor(OpenXLSX::XLColor(doubleToHexColor(*_bgColor)));
207
+ if (_fgColor.has_value()) fill.setColor(OpenXLSX::XLColor(doubleToHexColor(*_fgColor)));
208
+ // A solid pattern is painted with fgColor; bgColor is only the backdrop
209
+ // for pattern fills. Mirroring a background-only colour into fgColor is
210
+ // what makes setBgColor() actually show up in Excel.
211
+ if (patternType == OpenXLSX::XLPatternType::XLPatternSolid
212
+ && _bgColor.has_value() && !_fgColor.has_value()) {
213
+ fill.setColor(OpenXLSX::XLColor(doubleToHexColor(*_bgColor)));
214
+ }
215
+ cellFormat.setFillIndex(fillIndex);
216
+ cellFormat.setApplyFill(true);
217
+ }
218
+
219
+ // Border
220
+ bool anyBorder = _border > 0 || _bottom > 0 || _top > 0 || _left > 0 || _right > 0 || _diagonal > 0
221
+ || _borderColor.has_value() || _bottomColor.has_value() || _topColor.has_value()
222
+ || _leftColor.has_value() || _rightColor.has_value() || _diagonalColor.has_value();
223
+ if (anyBorder) {
224
+ OpenXLSX::XLBorder emptyBorder;
225
+ OpenXLSX::XLStyleIndex borderIndex = styles.borders().create(emptyBorder);
226
+ OpenXLSX::XLBorder border = styles.borders()[borderIndex];
227
+
228
+ // A side is written when its style > 0 or its color is set (a side with
229
+ // only a color defaults to a THIN line). Per-side values override the
230
+ // all-sides _border/_borderColor pair.
231
+ using BorderSetter = bool (OpenXLSX::XLBorder::*)(OpenXLSX::XLLineStyle, OpenXLSX::XLColor, double);
232
+ auto writeSide = [&](double sideStyle, const std::optional<double>& sideColor, BorderSetter setter) {
233
+ double style = sideStyle > 0 ? sideStyle : _border;
234
+ std::optional<double> color = sideColor.has_value() ? sideColor : _borderColor;
235
+ if (style <= 0 && !color.has_value()) return;
236
+ OpenXLSX::XLLineStyle lineStyle = style > 0
237
+ ? static_cast<OpenXLSX::XLLineStyle>(static_cast<uint8_t>(style))
238
+ : OpenXLSX::XLLineStyle::XLLineStyleThin;
239
+ OpenXLSX::XLColor lineColor = color.has_value()
240
+ ? OpenXLSX::XLColor(doubleToHexColor(*color))
241
+ : OpenXLSX::XLColor();
242
+ (border.*setter)(lineStyle, lineColor, 0.0);
243
+ };
244
+ writeSide(_left, _leftColor, &OpenXLSX::XLBorder::setLeft);
245
+ writeSide(_right, _rightColor, &OpenXLSX::XLBorder::setRight);
246
+ writeSide(_top, _topColor, &OpenXLSX::XLBorder::setTop);
247
+ writeSide(_bottom, _bottomColor, &OpenXLSX::XLBorder::setBottom);
248
+ if (_diagonal > 0 || _diagonalColor.has_value()) {
249
+ writeSide(_diagonal, _diagonalColor, &OpenXLSX::XLBorder::setDiagonal);
250
+ // The line style is only rendered when a diagonal direction is set;
251
+ // the API has no direction parameter, so draw both diagonals.
252
+ border.setDiagonalUp(true);
253
+ border.setDiagonalDown(true);
254
+ }
255
+ cellFormat.setBorderIndex(borderIndex);
256
+ cellFormat.setApplyBorder(true);
257
+ }
258
+
259
+ // Protection: Excel cells are locked by default, so an explicit
260
+ // setUnlocked() must write locked=false + applyProtection.
261
+ if (_locked.has_value()) {
262
+ cellFormat.setLocked(*_locked);
263
+ cellFormat.setApplyProtection(true);
264
+ }
265
+
266
+ _styleIndex = cellFormatIndex;
267
+ _cachedStyles = &styles;
268
+ _cachedGeneration = documentGeneration;
269
+ return cellFormatIndex;
270
+ }
271
+
272
+ void applyToCell(OpenXLSX::XLCell& cell, OpenXLSX::XLStyleIndex styleIndex) {
273
+ cell.setCellFormat(styleIndex);
274
+ }
275
+
276
+ // Reset to defaults
277
+ void reset() {
278
+ _fontName.clear();
279
+ _fontSize = 0;
280
+ _fontColor.reset();
281
+ _fontScript = 0;
282
+ _bold = false;
283
+ _italic = false;
284
+ _underline = 0;
285
+ _strikeout = false;
286
+ _align = 0;
287
+ _verticalAlign = 0;
288
+ _textWrap = false;
289
+ _rotation = 0;
290
+ _indent = 0;
291
+ _shrink = false;
292
+ _numFormat.clear();
293
+ _numFormatIndex.reset();
294
+ _bgColor.reset();
295
+ _fgColor.reset();
296
+ _pattern = 0;
297
+ _border = 0;
298
+ _borderColor.reset();
299
+ _bottom = 0;
300
+ _top = 0;
301
+ _left = 0;
302
+ _right = 0;
303
+ _bottomColor.reset();
304
+ _topColor.reset();
305
+ _leftColor.reset();
306
+ _rightColor.reset();
307
+ _diagonal = 0;
308
+ _diagonalColor.reset();
309
+ _locked.reset();
310
+ _hyperlink = false;
311
+ _fontOnly = false;
312
+ invalidate();
313
+ _cachedStyles = nullptr;
314
+ _cachedGeneration = 0;
315
+ }
316
+
317
+ private:
318
+ void invalidate() { _styleIndex.reset(); }
319
+
320
+ std::optional<OpenXLSX::XLStyleIndex> _styleIndex;
321
+ const OpenXLSX::XLStyles* _cachedStyles = nullptr;
322
+ uint64_t _cachedGeneration = 0;
323
+
324
+ std::string _fontName;
325
+ double _fontSize = 0;
326
+ std::optional<double> _fontColor;
327
+ double _fontScript = 0;
328
+ bool _bold = false;
329
+ bool _italic = false;
330
+ double _underline = 0;
331
+ bool _strikeout = false;
332
+ double _align = 0;
333
+ double _verticalAlign = 0;
334
+ bool _textWrap = false;
335
+ double _rotation = 0;
336
+ double _indent = 0;
337
+ bool _shrink = false;
338
+ std::string _numFormat;
339
+ std::optional<double> _numFormatIndex;
340
+ std::optional<double> _bgColor;
341
+ std::optional<double> _fgColor;
342
+ double _pattern = 0;
343
+ double _border = 0;
344
+ std::optional<double> _borderColor;
345
+ double _bottom = 0;
346
+ double _top = 0;
347
+ double _left = 0;
348
+ double _right = 0;
349
+ std::optional<double> _bottomColor;
350
+ std::optional<double> _topColor;
351
+ std::optional<double> _leftColor;
352
+ std::optional<double> _rightColor;
353
+ double _diagonal = 0;
354
+ std::optional<double> _diagonalColor;
355
+ std::optional<bool> _locked;
356
+ bool _hyperlink = false;
357
+ bool _fontOnly = false;
358
+ };
359
+
360
+ } // namespace margelo::nitro::xlsx
package/cpp/XlsxJson.cpp CHANGED
@@ -1,5 +1,6 @@
1
1
  #include "XlsxJson.hpp"
2
2
  #include <algorithm>
3
+ #include <charconv>
3
4
  #include <cmath>
4
5
  #include <limits>
5
6
  #include <stdexcept>
@@ -20,8 +21,22 @@ std::string keyFromCellValue(OpenXLSX::XLWorksheet& ws, uint32_t row, uint16_t c
20
21
  return cell.value().get<std::string>();
21
22
  case OpenXLSX::XLValueType::Integer:
22
23
  return std::to_string(static_cast<long long>(cell.value().get<int64_t>()));
23
- case OpenXLSX::XLValueType::Float:
24
- return std::to_string(static_cast<int>(cell.value().get<double>()));
24
+ case OpenXLSX::XLValueType::Float: {
25
+ double d = cell.value().get<double>();
26
+ // Integral values in int64 range render exactly like the Integer branch.
27
+ // Bounds are the int64 range as doubles: [-2^63, 2^63).
28
+ constexpr double kInt64Min = -9223372036854775808.0;
29
+ constexpr double kInt64MaxExclusive = 9223372036854775808.0;
30
+ if (std::isfinite(d) && d == std::trunc(d) && d >= kInt64Min && d < kInt64MaxExclusive) {
31
+ return std::to_string(static_cast<long long>(d));
32
+ }
33
+ // Shortest representation that round-trips exactly: no truncation to int,
34
+ // so headers 3.5 and 3.9 stay distinct ("3.5" / "3.9", not both "3").
35
+ char buf[40];
36
+ auto [end, ec] = std::to_chars(buf, buf + sizeof(buf), d);
37
+ if (ec == std::errc()) return std::string(buf, end);
38
+ return std::to_string(d);
39
+ }
25
40
  default:
26
41
  return "col" + std::to_string(col);
27
42
  }
@@ -32,10 +47,14 @@ void recordFromRow(OpenXLSX::XLWorksheet& ws,
32
47
  const std::vector<std::string>& keys,
33
48
  uint16_t lastCol,
34
49
  std::shared_ptr<AnyMap> record) {
35
- uint16_t keyCount = std::min(static_cast<uint16_t>(keys.size()), lastCol);
36
- for (uint16_t c = 0; c < keyCount; ++c) {
50
+ for (size_t c = 0; c < keys.size(); ++c) {
37
51
  const std::string& key = keys[c];
38
- auto cell = ws.findCell(row, c + 1);
52
+ if (c >= static_cast<size_t>(lastCol)) {
53
+ // Column beyond the sheet's width: emit an explicit null instead of dropping the key.
54
+ record->setNull(key);
55
+ continue;
56
+ }
57
+ auto cell = ws.findCell(row, static_cast<uint16_t>(c + 1));
39
58
  if (cell.empty()) {
40
59
  record->setNull(key);
41
60
  continue;
@@ -128,11 +147,13 @@ std::vector<std::shared_ptr<AnyMap>> worksheetToRecords(
128
147
  }
129
148
  if (resolvedKeys.empty()) return result;
130
149
 
131
- uint16_t keyCount = std::min(static_cast<uint16_t>(resolvedKeys.size()), lastCol);
150
+ // Keys beyond lastCol can never hold data, so the has-data scan only needs to
151
+ // cover the sheet's columns; recordFromRow still emits every key (null-filled).
152
+ size_t scanCols = std::min(resolvedKeys.size(), static_cast<size_t>(lastCol));
132
153
  for (uint32_t r = dataStartRow; r <= lastRow; ++r) {
133
154
  bool hasData = false;
134
- for (uint16_t c = 1; c <= keyCount; ++c) {
135
- if (!isCellEmpty(worksheet, r, c)) {
155
+ for (size_t c = 1; c <= scanCols; ++c) {
156
+ if (!isCellEmpty(worksheet, r, static_cast<uint16_t>(c))) {
136
157
  hasData = true;
137
158
  break;
138
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-xlsx",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Fast XLSX file reading and writing for React Native using OpenXLSX and Nitro Modules",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/zydbkqf/react-native-nitro-xlsx",
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # Materialize the `OpenXLSX/` header namespace inside
3
+ # thirdparty/OpenXLSX/OpenXLSX/headers.
4
+ #
5
+ # Why real files instead of a symlink: `headers/OpenXLSX -> .` worked in a git
6
+ # checkout, but npm does not put symlinks in a tarball, so consumers installing
7
+ # this package from npm got no `headers/OpenXLSX` entry at all and every
8
+ # `#include <OpenXLSX/...>` failed to resolve. Upstream's install layout copies
9
+ # the headers into `include/OpenXLSX`, so we mirror that inside the vendored
10
+ # tree with plain files.
11
+ #
12
+ # Re-run this after updating thirdparty/OpenXLSX to a newer upstream tag.
13
+ set -euo pipefail
14
+
15
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
16
+ SRC="$ROOT/thirdparty/OpenXLSX/OpenXLSX/headers"
17
+ DST="$SRC/OpenXLSX"
18
+
19
+ if [ ! -d "$SRC" ]; then
20
+ echo "missing headers dir: $SRC" >&2
21
+ exit 1
22
+ fi
23
+
24
+ rm -rf "$DST"
25
+ mkdir -p "$DST"
26
+
27
+ find "$SRC" -type f -name '*.hpp' -not -path "$DST/*" -print0 |
28
+ while IFS= read -r -d '' f; do
29
+ rel="${f#"$SRC"/}"
30
+ mkdir -p "$DST/$(dirname "$rel")"
31
+ cp "$f" "$DST/$rel"
32
+ done
33
+
34
+ echo "synced $(find "$DST" -type f -name '*.hpp' | wc -l | tr -d ' ') headers into ${DST#"$ROOT"/}"