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.
- package/NOTICE +8 -35
- package/NitroXlsx.podspec +21 -57
- package/Package.swift +35 -0
- package/README.md +243 -36
- package/android/CMakeLists.txt +17 -23
- package/android/build.gradle +6 -0
- package/android/cpp-adapter.cpp +12 -0
- package/android/src/main/java/com/margelo/nitro/xlsx/NitroXlsxPackage.kt +9 -0
- package/cpp/HybridNitroXlsx.cpp +32 -9
- package/cpp/HybridNitroXlsx.hpp +2 -1
- package/cpp/HybridXlsxConditionalFormat.cpp +252 -0
- package/cpp/HybridXlsxConditionalFormat.hpp +62 -0
- package/cpp/HybridXlsxConditionalFormats.cpp +46 -0
- package/cpp/HybridXlsxConditionalFormats.hpp +28 -0
- package/cpp/HybridXlsxWorkbook.cpp +162 -87
- package/cpp/HybridXlsxWorkbook.hpp +7 -1
- package/cpp/HybridXlsxWorksheet.cpp +456 -61
- package/cpp/HybridXlsxWorksheet.hpp +73 -26
- package/cpp/XlsxCacheDir.cpp +76 -0
- package/cpp/XlsxCacheDir.hpp +27 -0
- package/cpp/XlsxError.hpp +68 -0
- package/cpp/XlsxJson.cpp +185 -0
- package/cpp/XlsxJson.hpp +36 -0
- package/ios/XlsxCacheDir.mm +60 -0
- package/nitrogen/generated/android/NitroXlsx+autolinking.cmake +2 -0
- package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.hpp +5 -2
- package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.cpp +54 -0
- package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.hpp +95 -0
- package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +7 -0
- package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp +47 -19
- package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.hpp +52 -20
- package/package.json +5 -2
- package/scripts/prepare-openxlsx.js +126 -0
- package/scripts/run-cpp-tests.sh +14 -0
- package/src/__mocks__/react-native-nitro-modules.ts +1 -0
- package/src/__tests__/index.test.ts +76 -7
- package/src/index.ts +110 -9
- 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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 =
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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
|
}
|