react-native-nitro-xlsx 0.1.2 → 0.2.5

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 (42) hide show
  1. package/NOTICE +8 -35
  2. package/NitroXlsx.podspec +21 -57
  3. package/Package.swift +35 -0
  4. package/README.md +243 -36
  5. package/android/CMakeLists.txt +17 -23
  6. package/android/build.gradle +6 -0
  7. package/android/cpp-adapter.cpp +12 -0
  8. package/android/src/main/java/com/margelo/nitro/xlsx/NitroXlsxPackage.kt +9 -0
  9. package/cpp/HybridNitroXlsx.cpp +32 -9
  10. package/cpp/HybridNitroXlsx.hpp +2 -1
  11. package/cpp/HybridXlsxConditionalFormat.cpp +252 -0
  12. package/cpp/HybridXlsxConditionalFormat.hpp +62 -0
  13. package/cpp/HybridXlsxConditionalFormats.cpp +46 -0
  14. package/cpp/HybridXlsxConditionalFormats.hpp +28 -0
  15. package/cpp/HybridXlsxWorkbook.cpp +162 -87
  16. package/cpp/HybridXlsxWorkbook.hpp +7 -1
  17. package/cpp/HybridXlsxWorksheet.cpp +456 -61
  18. package/cpp/HybridXlsxWorksheet.hpp +73 -26
  19. package/cpp/XlsxCacheDir.cpp +76 -0
  20. package/cpp/XlsxCacheDir.hpp +27 -0
  21. package/cpp/XlsxError.hpp +68 -0
  22. package/cpp/XlsxJson.cpp +185 -0
  23. package/cpp/XlsxJson.hpp +36 -0
  24. package/ios/XlsxCacheDir.mm +60 -0
  25. package/nitrogen/generated/android/NitroXlsx+autolinking.cmake +2 -0
  26. package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.cpp +1 -0
  27. package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.hpp +5 -2
  28. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.cpp +54 -0
  29. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.hpp +95 -0
  30. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.cpp +24 -0
  31. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.hpp +68 -0
  32. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp +4 -0
  33. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +7 -0
  34. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp +47 -19
  35. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.hpp +52 -20
  36. package/package.json +5 -2
  37. package/scripts/prepare-openxlsx.js +126 -0
  38. package/scripts/run-cpp-tests.sh +14 -0
  39. package/src/__mocks__/react-native-nitro-modules.ts +1 -0
  40. package/src/__tests__/index.test.ts +76 -7
  41. package/src/index.ts +110 -9
  42. package/src/specs/NitroXlsx.nitro.ts +141 -42
@@ -1,9 +1,15 @@
1
1
  #include "HybridXlsxWorkbook.hpp"
2
+ #include "XlsxError.hpp"
3
+ #include "XlsxJson.hpp"
4
+ #include "XlsxCacheDir.hpp"
2
5
  #include <stdexcept>
3
6
  #include <fstream>
4
7
  #include <sstream>
5
8
  #include <cstdlib>
6
9
  #include <unistd.h>
10
+ #include <cmath>
11
+ #include <cstring>
12
+ #include <chrono>
7
13
 
8
14
  #ifdef __ANDROID__
9
15
  #include <android/api-level.h>
@@ -12,42 +18,14 @@
12
18
 
13
19
  namespace margelo::nitro::xlsx {
14
20
 
15
- namespace {
16
- std::string getTempDir() {
17
- #ifdef __ANDROID__
18
- // /tmp is typically not available in Android sandbox, use app's cache directory
19
- const char* cacheDir = std::getenv("TMPDIR");
20
- if (cacheDir != nullptr && cacheDir[0] != '\0') {
21
- return std::string(cacheDir);
22
- }
23
- // Try common Android cache directories
24
- const char* candidates[] = {
25
- "/data/local/tmp",
26
- "/data/data/tmp",
27
- "/sdcard/tmp",
28
- "/tmp"
29
- };
30
- for (const char* dir : candidates) {
31
- struct stat st;
32
- if (stat(dir, &st) == 0 && S_ISDIR(st.st_mode)) {
33
- return std::string(dir);
34
- }
35
- }
36
- return "/data/local/tmp";
37
- #else
38
- return "/tmp";
39
- #endif
40
- }
41
- }
42
-
43
- HybridXlsxWorkbook::HybridXlsxWorkbook(const std::optional<std::string>& tempDir)
21
+ HybridXlsxWorkbook::HybridXlsxWorkbook()
44
22
  : HybridObject("XlsxWorkbook"), HybridXlsxWorkbookSpec(), _finalized(false) {
45
23
  _workbook = std::make_unique<OpenXLSX::XLDocument>();
46
- if (tempDir.has_value()) {
47
- _tempDir = *tempDir;
48
- } else {
49
- _tempDir = getTempDir();
50
- }
24
+ _tempDir = getCacheDir();
25
+ }
26
+
27
+ std::string HybridXlsxWorkbook::tempFilePath(const char* prefix) {
28
+ return _tempDir + "/" + prefix + "_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".xlsx";
51
29
  }
52
30
 
53
31
  HybridXlsxWorkbook::~HybridXlsxWorkbook() {
@@ -59,7 +37,7 @@ void HybridXlsxWorkbook::openFromFile(const std::string& path) {
59
37
  }
60
38
 
61
39
  void HybridXlsxWorkbook::openFromBuffer(const uint8_t* data, size_t size) {
62
- std::string tempPath = _tempDir + "/xlsx_open_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".xlsx";
40
+ std::string tempPath = tempFilePath("xlsx_open");
63
41
  std::ofstream file(tempPath, std::ios::binary);
64
42
  file.write(reinterpret_cast<const char*>(data), size);
65
43
  file.close();
@@ -72,7 +50,7 @@ void HybridXlsxWorkbook::openFromBuffer(const uint8_t* data, size_t size) {
72
50
  void HybridXlsxWorkbook::loadWorksheets() {
73
51
  _worksheets.clear();
74
52
  _worksheetNames.clear();
75
-
53
+
76
54
  auto wb = _workbook->workbook();
77
55
  auto count = wb.worksheetCount();
78
56
  for (uint16_t i = 1; i <= count; ++i) {
@@ -84,63 +62,149 @@ void HybridXlsxWorkbook::loadWorksheets() {
84
62
  }
85
63
  }
86
64
 
65
+ void HybridXlsxWorkbook::ensureOpen() {
66
+ if (_workbook->isOpen()) return;
67
+ std::string tempPath = tempFilePath("xlsx_create");
68
+ _workbook->create(tempPath, OpenXLSX::XLForceOverwrite);
69
+ std::remove(tempPath.c_str());
70
+ loadWorksheets();
71
+ }
72
+
87
73
  std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::addWorksheet(const std::optional<std::string>& name) {
88
- if (!_workbook->isOpen()) {
89
- std::string tempPath = _tempDir + "/xlsx_create_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".xlsx";
90
- _workbook->create(tempPath, OpenXLSX::XLForceOverwrite);
91
- std::remove(tempPath.c_str());
92
- loadWorksheets();
93
- }
74
+ return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
75
+ ensureOpen();
94
76
 
95
- std::string sheetName;
96
- if (name.has_value()) {
97
- sheetName = *name;
98
- auto it = _worksheetNames.find(sheetName);
99
- if (it != _worksheetNames.end()) {
100
- // Worksheet already exists (e.g., default Sheet1 created by OpenXLSX), return it
101
- return it->second;
77
+ std::string sheetName;
78
+ if (name.has_value()) {
79
+ sheetName = *name;
80
+ auto it = _worksheetNames.find(sheetName);
81
+ if (it != _worksheetNames.end()) {
82
+ // Worksheet already exists (e.g., default Sheet1 created by OpenXLSX), return it
83
+ return it->second;
84
+ }
85
+ } else {
86
+ int idx = 1;
87
+ do {
88
+ sheetName = "Sheet" + std::to_string(idx++);
89
+ } while (_worksheetNames.count(sheetName) > 0);
102
90
  }
103
- } else {
104
- int idx = 1;
105
- do {
106
- sheetName = "Sheet" + std::to_string(idx++);
107
- } while (_worksheetNames.count(sheetName) > 0);
108
- }
109
91
 
110
- _workbook->workbook().addWorksheet(sheetName);
111
- auto worksheet = _workbook->workbook().worksheet(sheetName);
112
- auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
113
- _worksheets.push_back(hybridWorksheet);
114
- _worksheetNames[sheetName] = hybridWorksheet;
115
- return hybridWorksheet;
92
+ _workbook->workbook().addWorksheet(sheetName);
93
+ auto worksheet = _workbook->workbook().worksheet(sheetName);
94
+ auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
95
+ _worksheets.push_back(hybridWorksheet);
96
+ _worksheetNames[sheetName] = hybridWorksheet;
97
+ return hybridWorksheet;
98
+ });
116
99
  }
117
100
 
118
101
  std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getWorksheet(double index) {
119
- size_t idx = static_cast<size_t>(index);
120
- if (idx >= _worksheets.size()) {
121
- throw std::runtime_error("Worksheet index out of range: " + std::to_string(idx));
122
- }
123
- return _worksheets[idx];
102
+ return withXlsxError(xlsx_error::INDEX_OUT_OF_RANGE, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
103
+ ensureOpen();
104
+ size_t idx = static_cast<size_t>(index);
105
+ if (idx >= _worksheets.size()) {
106
+ throw XlsxError(xlsx_error::INDEX_OUT_OF_RANGE, "Worksheet index out of range: " + std::to_string(idx));
107
+ }
108
+ return _worksheets[idx];
109
+ });
124
110
  }
125
111
 
126
112
  std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getWorksheetByName(const std::string& name) {
127
- auto it = _worksheetNames.find(name);
128
- if (it == _worksheetNames.end()) {
129
- throw std::runtime_error("Worksheet not found: " + name);
130
- }
131
- return it->second;
113
+ return withXlsxError(xlsx_error::SHEET_NOT_FOUND, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
114
+ ensureOpen();
115
+ auto it = _worksheetNames.find(name);
116
+ if (it == _worksheetNames.end()) {
117
+ throw XlsxError(xlsx_error::SHEET_NOT_FOUND, "Worksheet not found: " + name);
118
+ }
119
+ return it->second;
120
+ });
132
121
  }
133
122
 
134
123
  std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getOrAddWorksheet(const std::string& name) {
135
- auto it = _worksheetNames.find(name);
136
- if (it != _worksheetNames.end()) {
137
- return it->second;
138
- }
139
- return addWorksheet(name);
124
+ return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
125
+ ensureOpen();
126
+ auto it = _worksheetNames.find(name);
127
+ if (it != _worksheetNames.end()) {
128
+ return it->second;
129
+ }
130
+ return addWorksheet(name);
131
+ });
140
132
  }
141
133
 
142
134
  double HybridXlsxWorkbook::getWorksheetCount() {
143
- return static_cast<double>(_workbook->workbook().worksheetCount());
135
+ return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
136
+ return static_cast<double>(_workbook->workbook().worksheetCount());
137
+ });
138
+ }
139
+
140
+ void HybridXlsxWorkbook::deleteSheet(const std::string& name) {
141
+ withXlsxError(xlsx_error::XLSX_ERROR, [&] {
142
+ ensureOpen();
143
+ auto it = _worksheetNames.find(name);
144
+ if (it == _worksheetNames.end()) {
145
+ throw XlsxError(xlsx_error::SHEET_NOT_FOUND, "Worksheet not found: " + name);
146
+ }
147
+ if (_worksheetNames.size() <= 1) {
148
+ throw XlsxError(xlsx_error::INVALID_ARGUMENT, "Cannot delete the last worksheet in a workbook");
149
+ }
150
+
151
+ _workbook->workbook().deleteSheet(name);
152
+
153
+ // Drop the cached hybrid wrappers for the deleted sheet
154
+ for (auto wit = _worksheets.begin(); wit != _worksheets.end(); ++wit) {
155
+ if (*wit == it->second) {
156
+ _worksheets.erase(wit);
157
+ break;
158
+ }
159
+ }
160
+ _worksheetNames.erase(it);
161
+ });
162
+ }
163
+
164
+ void HybridXlsxWorkbook::updateSheetName(const std::string& oldName, const std::string& newName) {
165
+ withXlsxError(xlsx_error::XLSX_ERROR, [&] {
166
+ ensureOpen();
167
+ auto it = _worksheetNames.find(oldName);
168
+ if (it == _worksheetNames.end()) {
169
+ throw XlsxError(xlsx_error::SHEET_NOT_FOUND, "Worksheet not found: " + oldName);
170
+ }
171
+ if (_worksheetNames.count(newName) > 0) {
172
+ throw XlsxError(xlsx_error::SHEET_EXISTS, "Worksheet already exists: " + newName);
173
+ }
174
+
175
+ // Rename the sheet itself
176
+ it->second->applyRename(oldName, newName);
177
+
178
+ // Rewrite formula references on every worksheet (formulas in other sheets may reference oldName)
179
+ for (auto& [name, hybrid] : _worksheetNames) {
180
+ if (name == oldName) continue;
181
+ hybrid->rewriteSheetNameRef(oldName, newName);
182
+ }
183
+
184
+ // Refresh the name map
185
+ auto hybrid = it->second;
186
+ _worksheetNames.erase(it);
187
+ _worksheetNames[newName] = hybrid;
188
+ });
189
+ }
190
+
191
+ std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::clone(const std::string& existingName, const std::string& newName) {
192
+ return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
193
+ ensureOpen();
194
+ if (_worksheetNames.count(existingName) == 0) {
195
+ throw XlsxError(xlsx_error::SHEET_NOT_FOUND, "Worksheet not found: " + existingName);
196
+ }
197
+ if (_worksheetNames.count(newName) > 0) {
198
+ throw XlsxError(xlsx_error::SHEET_EXISTS, "Worksheet already exists: " + newName);
199
+ }
200
+
201
+ _workbook->workbook().cloneSheet(existingName, newName);
202
+ auto worksheet = _workbook->workbook().worksheet(newName);
203
+ auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
204
+ _worksheets.push_back(hybridWorksheet);
205
+ _worksheetNames[newName] = hybridWorksheet;
206
+ return hybridWorksheet;
207
+ });
144
208
  }
145
209
 
146
210
  std::shared_ptr<HybridXlsxCellFormatSpec> HybridXlsxWorkbook::addCellFormat() {
@@ -153,37 +217,48 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
153
217
  auto promise = Promise<std::shared_ptr<ArrayBuffer>>::create();
154
218
 
155
219
  if (_finalized) {
156
- promise->reject(std::make_exception_ptr(std::runtime_error("Workbook has already been finalized")));
220
+ promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::WORKBOOK_CLOSED, "Workbook has already been finalized")));
157
221
  return promise;
158
222
  }
159
223
 
160
224
  try {
161
- std::string tempPath = _tempDir + "/xlsx_temp_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".xlsx";
225
+ std::string tempPath = tempFilePath("xlsx_temp");
162
226
  _workbook->saveAs(tempPath, OpenXLSX::XLForceOverwrite);
163
-
227
+
164
228
  std::ifstream file(tempPath, std::ios::binary | std::ios::ate);
165
229
  if (!file.is_open()) {
166
- promise->reject(std::make_exception_ptr(std::runtime_error("Failed to open temp file")));
230
+ promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::IO_ERROR, "Failed to open temp file")));
167
231
  return promise;
168
232
  }
169
-
233
+
170
234
  std::streampos size = file.tellg();
171
235
  std::vector<uint8_t> buffer(size);
172
236
  file.seekg(0, std::ios::beg);
173
237
  file.read(reinterpret_cast<char*>(buffer.data()), size);
174
238
  file.close();
175
-
239
+
176
240
  std::remove(tempPath.c_str());
177
-
241
+
178
242
  auto arrayBuffer = ArrayBuffer::copy(buffer.data(), buffer.size());
179
243
  _finalized = true;
180
-
244
+
181
245
  promise->resolve(arrayBuffer);
182
- } catch (const std::exception& e) {
246
+ } catch (const XlsxError& e) {
183
247
  promise->reject(std::make_exception_ptr(e));
248
+ } catch (const OpenXLSX::XLException& e) {
249
+ promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::XLSX_ERROR, e.what())));
250
+ } catch (const std::exception& e) {
251
+ promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::INTERNAL_ERROR, e.what())));
184
252
  }
185
-
253
+
186
254
  return promise;
187
255
  }
188
256
 
257
+ std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> HybridXlsxWorkbook::toJSON() {
258
+ return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
259
+ auto wb = _workbook->workbook();
260
+ return workbookToRecords(wb);
261
+ });
262
+ }
263
+
189
264
  }
@@ -13,7 +13,7 @@ namespace margelo::nitro::xlsx {
13
13
 
14
14
  class HybridXlsxWorkbook : public HybridXlsxWorkbookSpec {
15
15
  public:
16
- HybridXlsxWorkbook(const std::optional<std::string>& tempDir = std::nullopt);
16
+ HybridXlsxWorkbook();
17
17
  ~HybridXlsxWorkbook() override;
18
18
 
19
19
  void openFromFile(const std::string& path);
@@ -24,8 +24,12 @@ public:
24
24
  std::shared_ptr<HybridXlsxWorksheetSpec> getWorksheetByName(const std::string& name) override;
25
25
  std::shared_ptr<HybridXlsxWorksheetSpec> getOrAddWorksheet(const std::string& name) override;
26
26
  double getWorksheetCount() override;
27
+ void deleteSheet(const std::string& name) override;
28
+ void updateSheetName(const std::string& oldName, const std::string& newName) override;
29
+ std::shared_ptr<HybridXlsxWorksheetSpec> clone(const std::string& existingName, const std::string& newName) override;
27
30
  std::shared_ptr<HybridXlsxCellFormatSpec> addCellFormat() override;
28
31
  std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> getBuffer() override;
32
+ std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> toJSON() override;
29
33
 
30
34
  private:
31
35
  std::unique_ptr<OpenXLSX::XLDocument> _workbook;
@@ -36,6 +40,8 @@ private:
36
40
  std::string _tempDir;
37
41
 
38
42
  void loadWorksheets();
43
+ void ensureOpen();
44
+ std::string tempFilePath(const char* prefix);
39
45
  };
40
46
 
41
47
  }