react-native-nitro-xlsx 0.1.4 → 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/NitroXlsx.podspec +1 -0
- package/Package.swift +6 -2
- package/README.md +219 -57
- package/android/CMakeLists.txt +4 -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 +25 -179
- package/cpp/HybridNitroXlsx.hpp +2 -2
- 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 +159 -196
- package/cpp/HybridXlsxWorkbook.hpp +7 -2
- package/cpp/HybridXlsxWorksheet.cpp +433 -62
- 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.hpp +5 -3
- 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 +3 -0
- package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +6 -1
- package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp +47 -19
- package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.hpp +52 -20
- package/package.json +2 -2
- package/scripts/prepare-openxlsx.js +23 -6
- 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 +107 -18
- package/src/specs/NitroXlsx.nitro.ts +141 -44
- package/ios/dummy.swift +0 -2
|
@@ -1,4 +1,7 @@
|
|
|
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>
|
|
@@ -6,79 +9,23 @@
|
|
|
6
9
|
#include <unistd.h>
|
|
7
10
|
#include <cmath>
|
|
8
11
|
#include <cstring>
|
|
9
|
-
#include <
|
|
12
|
+
#include <chrono>
|
|
10
13
|
|
|
11
14
|
#ifdef __ANDROID__
|
|
12
15
|
#include <android/api-level.h>
|
|
13
16
|
#include <sys/stat.h>
|
|
14
17
|
#endif
|
|
15
18
|
|
|
16
|
-
namespace {
|
|
17
|
-
|
|
18
|
-
void writeJsonString(std::ostringstream& out, const std::string& s) {
|
|
19
|
-
out << '"';
|
|
20
|
-
for (char c : s) {
|
|
21
|
-
switch (c) {
|
|
22
|
-
case '"': out << "\\\""; break;
|
|
23
|
-
case '\\': out << "\\\\"; break;
|
|
24
|
-
case '\b': out << "\\b"; break;
|
|
25
|
-
case '\f': out << "\\f"; break;
|
|
26
|
-
case '\n': out << "\\n"; break;
|
|
27
|
-
case '\r': out << "\\r"; break;
|
|
28
|
-
case '\t': out << "\\t"; break;
|
|
29
|
-
default:
|
|
30
|
-
if (static_cast<unsigned char>(c) < 0x20) {
|
|
31
|
-
out << "\\u" << std::hex << std::setw(4) << std::setfill('0')
|
|
32
|
-
<< static_cast<int>(static_cast<unsigned char>(c))
|
|
33
|
-
<< std::dec;
|
|
34
|
-
} else {
|
|
35
|
-
out << c;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
out << '"';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
} // anonymous namespace
|
|
43
|
-
|
|
44
19
|
namespace margelo::nitro::xlsx {
|
|
45
20
|
|
|
46
|
-
|
|
47
|
-
std::string getTempDir() {
|
|
48
|
-
#ifdef __ANDROID__
|
|
49
|
-
// /tmp is typically not available in Android sandbox, use app's cache directory
|
|
50
|
-
const char* cacheDir = std::getenv("TMPDIR");
|
|
51
|
-
if (cacheDir != nullptr && cacheDir[0] != '\0') {
|
|
52
|
-
return std::string(cacheDir);
|
|
53
|
-
}
|
|
54
|
-
// Try common Android cache directories
|
|
55
|
-
const char* candidates[] = {
|
|
56
|
-
"/data/local/tmp",
|
|
57
|
-
"/data/data/tmp",
|
|
58
|
-
"/sdcard/tmp",
|
|
59
|
-
"/tmp"
|
|
60
|
-
};
|
|
61
|
-
for (const char* dir : candidates) {
|
|
62
|
-
struct stat st;
|
|
63
|
-
if (stat(dir, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
64
|
-
return std::string(dir);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return "/data/local/tmp";
|
|
68
|
-
#else
|
|
69
|
-
return "/tmp";
|
|
70
|
-
#endif
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
HybridXlsxWorkbook::HybridXlsxWorkbook(const std::optional<std::string>& tempDir)
|
|
21
|
+
HybridXlsxWorkbook::HybridXlsxWorkbook()
|
|
75
22
|
: HybridObject("XlsxWorkbook"), HybridXlsxWorkbookSpec(), _finalized(false) {
|
|
76
23
|
_workbook = std::make_unique<OpenXLSX::XLDocument>();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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";
|
|
82
29
|
}
|
|
83
30
|
|
|
84
31
|
HybridXlsxWorkbook::~HybridXlsxWorkbook() {
|
|
@@ -90,7 +37,7 @@ void HybridXlsxWorkbook::openFromFile(const std::string& path) {
|
|
|
90
37
|
}
|
|
91
38
|
|
|
92
39
|
void HybridXlsxWorkbook::openFromBuffer(const uint8_t* data, size_t size) {
|
|
93
|
-
std::string tempPath =
|
|
40
|
+
std::string tempPath = tempFilePath("xlsx_open");
|
|
94
41
|
std::ofstream file(tempPath, std::ios::binary);
|
|
95
42
|
file.write(reinterpret_cast<const char*>(data), size);
|
|
96
43
|
file.close();
|
|
@@ -103,7 +50,7 @@ void HybridXlsxWorkbook::openFromBuffer(const uint8_t* data, size_t size) {
|
|
|
103
50
|
void HybridXlsxWorkbook::loadWorksheets() {
|
|
104
51
|
_worksheets.clear();
|
|
105
52
|
_worksheetNames.clear();
|
|
106
|
-
|
|
53
|
+
|
|
107
54
|
auto wb = _workbook->workbook();
|
|
108
55
|
auto count = wb.worksheetCount();
|
|
109
56
|
for (uint16_t i = 1; i <= count; ++i) {
|
|
@@ -115,63 +62,149 @@ void HybridXlsxWorkbook::loadWorksheets() {
|
|
|
115
62
|
}
|
|
116
63
|
}
|
|
117
64
|
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
|
125
72
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
73
|
+
std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::addWorksheet(const std::optional<std::string>& name) {
|
|
74
|
+
return withXlsxError(xlsx_error::XLSX_ERROR, [&]() -> std::shared_ptr<HybridXlsxWorksheetSpec> {
|
|
75
|
+
ensureOpen();
|
|
76
|
+
|
|
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);
|
|
133
90
|
}
|
|
134
|
-
} else {
|
|
135
|
-
int idx = 1;
|
|
136
|
-
do {
|
|
137
|
-
sheetName = "Sheet" + std::to_string(idx++);
|
|
138
|
-
} while (_worksheetNames.count(sheetName) > 0);
|
|
139
|
-
}
|
|
140
91
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
});
|
|
147
99
|
}
|
|
148
100
|
|
|
149
101
|
std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getWorksheet(double index) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
});
|
|
155
110
|
}
|
|
156
111
|
|
|
157
112
|
std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getWorksheetByName(const std::string& name) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
});
|
|
163
121
|
}
|
|
164
122
|
|
|
165
123
|
std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getOrAddWorksheet(const std::string& name) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
});
|
|
171
132
|
}
|
|
172
133
|
|
|
173
134
|
double HybridXlsxWorkbook::getWorksheetCount() {
|
|
174
|
-
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
|
+
});
|
|
175
208
|
}
|
|
176
209
|
|
|
177
210
|
std::shared_ptr<HybridXlsxCellFormatSpec> HybridXlsxWorkbook::addCellFormat() {
|
|
@@ -184,118 +217,48 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
|
|
|
184
217
|
auto promise = Promise<std::shared_ptr<ArrayBuffer>>::create();
|
|
185
218
|
|
|
186
219
|
if (_finalized) {
|
|
187
|
-
promise->reject(std::make_exception_ptr(
|
|
220
|
+
promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::WORKBOOK_CLOSED, "Workbook has already been finalized")));
|
|
188
221
|
return promise;
|
|
189
222
|
}
|
|
190
223
|
|
|
191
224
|
try {
|
|
192
|
-
std::string tempPath =
|
|
225
|
+
std::string tempPath = tempFilePath("xlsx_temp");
|
|
193
226
|
_workbook->saveAs(tempPath, OpenXLSX::XLForceOverwrite);
|
|
194
|
-
|
|
227
|
+
|
|
195
228
|
std::ifstream file(tempPath, std::ios::binary | std::ios::ate);
|
|
196
229
|
if (!file.is_open()) {
|
|
197
|
-
promise->reject(std::make_exception_ptr(
|
|
230
|
+
promise->reject(std::make_exception_ptr(XlsxError(xlsx_error::IO_ERROR, "Failed to open temp file")));
|
|
198
231
|
return promise;
|
|
199
232
|
}
|
|
200
|
-
|
|
233
|
+
|
|
201
234
|
std::streampos size = file.tellg();
|
|
202
235
|
std::vector<uint8_t> buffer(size);
|
|
203
236
|
file.seekg(0, std::ios::beg);
|
|
204
237
|
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
|
205
238
|
file.close();
|
|
206
|
-
|
|
239
|
+
|
|
207
240
|
std::remove(tempPath.c_str());
|
|
208
|
-
|
|
241
|
+
|
|
209
242
|
auto arrayBuffer = ArrayBuffer::copy(buffer.data(), buffer.size());
|
|
210
243
|
_finalized = true;
|
|
211
|
-
|
|
244
|
+
|
|
212
245
|
promise->resolve(arrayBuffer);
|
|
213
|
-
} catch (const
|
|
246
|
+
} catch (const XlsxError& e) {
|
|
214
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())));
|
|
215
252
|
}
|
|
216
|
-
|
|
253
|
+
|
|
217
254
|
return promise;
|
|
218
255
|
}
|
|
219
256
|
|
|
220
|
-
std::string
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
auto lastRow = static_cast<unsigned int>(ws->getLastRow());
|
|
226
|
-
auto lastCol = static_cast<unsigned int>(ws->getLastColumn());
|
|
227
|
-
if (lastRow == 0 || lastCol == 0) {
|
|
228
|
-
return "[]";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
unsigned int dataStartRow = 1;
|
|
232
|
-
std::vector<std::string> resolvedKeys;
|
|
233
|
-
|
|
234
|
-
if (keys.has_value()) {
|
|
235
|
-
resolvedKeys = *keys;
|
|
236
|
-
} else {
|
|
237
|
-
dataStartRow = 2;
|
|
238
|
-
for (unsigned int c = 1; c <= lastCol; ++c) {
|
|
239
|
-
auto val = ws->getCellValue(1, c);
|
|
240
|
-
if (auto* s = std::get_if<std::string>(&val)) {
|
|
241
|
-
resolvedKeys.push_back(*s);
|
|
242
|
-
} else if (auto* d = std::get_if<double>(&val)) {
|
|
243
|
-
resolvedKeys.push_back(std::to_string(static_cast<int>(*d)));
|
|
244
|
-
} else {
|
|
245
|
-
resolvedKeys.push_back("col" + std::to_string(c));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (resolvedKeys.empty()) {
|
|
251
|
-
return "[]";
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
std::ostringstream out;
|
|
255
|
-
out << "[";
|
|
256
|
-
|
|
257
|
-
bool firstRow = true;
|
|
258
|
-
|
|
259
|
-
for (unsigned int r = dataStartRow; r <= lastRow; ++r) {
|
|
260
|
-
bool hasData = false;
|
|
261
|
-
for (unsigned int c = 1; c <= std::min(static_cast<unsigned int>(resolvedKeys.size()), lastCol); ++c) {
|
|
262
|
-
auto cellType = ws->getCellType(r, c);
|
|
263
|
-
if (cellType != CellType::EMPTY) {
|
|
264
|
-
hasData = true;
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (!hasData) continue;
|
|
269
|
-
|
|
270
|
-
if (!firstRow) out << ",";
|
|
271
|
-
firstRow = false;
|
|
272
|
-
|
|
273
|
-
out << "{";
|
|
274
|
-
unsigned int keyCount = std::min(static_cast<unsigned int>(resolvedKeys.size()), lastCol);
|
|
275
|
-
for (unsigned int c = 0; c < keyCount; ++c) {
|
|
276
|
-
if (c > 0) out << ",";
|
|
277
|
-
writeJsonString(out, resolvedKeys[c]);
|
|
278
|
-
out << ":";
|
|
279
|
-
auto val = ws->getCellValue(r, c + 1);
|
|
280
|
-
if (auto* s = std::get_if<std::string>(&val)) {
|
|
281
|
-
writeJsonString(out, *s);
|
|
282
|
-
} else if (auto* d = std::get_if<double>(&val)) {
|
|
283
|
-
if (std::isnan(*d) || std::isinf(*d)) {
|
|
284
|
-
out << "null";
|
|
285
|
-
} else {
|
|
286
|
-
out << *d;
|
|
287
|
-
}
|
|
288
|
-
} else if (auto* b = std::get_if<bool>(&val)) {
|
|
289
|
-
out << (*b ? "true" : "false");
|
|
290
|
-
} else {
|
|
291
|
-
out << "null";
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
out << "}";
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
out << "]";
|
|
298
|
-
return out.str();
|
|
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
|
+
});
|
|
299
262
|
}
|
|
300
263
|
|
|
301
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,9 +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;
|
|
29
|
-
std::string
|
|
32
|
+
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> toJSON() override;
|
|
30
33
|
|
|
31
34
|
private:
|
|
32
35
|
std::unique_ptr<OpenXLSX::XLDocument> _workbook;
|
|
@@ -37,6 +40,8 @@ private:
|
|
|
37
40
|
std::string _tempDir;
|
|
38
41
|
|
|
39
42
|
void loadWorksheets();
|
|
43
|
+
void ensureOpen();
|
|
44
|
+
std::string tempFilePath(const char* prefix);
|
|
40
45
|
};
|
|
41
46
|
|
|
42
47
|
}
|