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
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
#include "HybridXlsxWorksheetSpec.hpp"
|
|
4
4
|
#include <OpenXLSX/XLDocument.hpp>
|
|
5
5
|
#include <OpenXLSX/XLSheet.hpp>
|
|
6
|
+
#include <OpenXLSX/XLComments.hpp>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <memory>
|
|
8
9
|
#include "HybridXlsxCellFormat.hpp"
|
|
10
|
+
#include "HybridXlsxConditionalFormats.hpp"
|
|
9
11
|
|
|
10
12
|
namespace margelo::nitro::xlsx {
|
|
11
13
|
|
|
@@ -30,57 +32,102 @@ public:
|
|
|
30
32
|
|
|
31
33
|
void setColumn(double firstCol, double lastCol, double width, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) override;
|
|
32
34
|
void setRow(double row, double height, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) override;
|
|
33
|
-
|
|
35
|
+
bool deleteRow(double row) override;
|
|
36
|
+
bool deleteColumn(double col) override;
|
|
37
|
+
|
|
34
38
|
void mergeRange(double firstRow, double firstCol, double lastRow, double lastCol, const std::string& value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) override;
|
|
35
39
|
void mergeRangeNum(double firstRow, double firstCol, double lastRow, double lastCol, double value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) override;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
void insertImageBuffer(double row, double col, const std::shared_ptr<ArrayBuffer>& buffer, const std::string& extension, std::optional<double> xOffset, std::optional<double> yOffset, std::optional<double> xScale, std::optional<double> yScale) override;
|
|
39
|
-
|
|
40
|
-
void autofilter(double firstRow, double firstCol, double lastRow, double lastCol) override;
|
|
41
|
-
|
|
42
|
-
void freezePanes(double row, double col) override;
|
|
43
|
-
void splitPanes(double row, double col) override;
|
|
44
|
-
|
|
45
|
-
void setTabColor(double color) override;
|
|
40
|
+
void unmergeCells(double firstRow, double firstCol, double lastRow, double lastCol) override;
|
|
41
|
+
|
|
46
42
|
void hide() override;
|
|
47
43
|
void activate() override;
|
|
48
|
-
|
|
44
|
+
|
|
45
|
+
// Sheet protection
|
|
49
46
|
void protect(const std::optional<std::string>& password) override;
|
|
50
|
-
|
|
51
|
-
void
|
|
52
|
-
void
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
void
|
|
56
|
-
void
|
|
57
|
-
void
|
|
58
|
-
void
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
void
|
|
62
|
-
|
|
63
|
-
void
|
|
47
|
+
void protectSheet(std::optional<bool> set) override;
|
|
48
|
+
void protectObjects(std::optional<bool> set) override;
|
|
49
|
+
void protectScenarios(std::optional<bool> set) override;
|
|
50
|
+
|
|
51
|
+
// Password
|
|
52
|
+
void setPassword(const std::string& password) override;
|
|
53
|
+
void setPasswordHash(const std::string& hash) override;
|
|
54
|
+
void clearPassword() override;
|
|
55
|
+
void clearSheetProtection() override;
|
|
56
|
+
|
|
57
|
+
// Fine-grained permissions
|
|
58
|
+
void allowInsertColumns(std::optional<bool> set) override;
|
|
59
|
+
void allowInsertRows(std::optional<bool> set) override;
|
|
60
|
+
void allowDeleteColumns(std::optional<bool> set) override;
|
|
61
|
+
void allowDeleteRows(std::optional<bool> set) override;
|
|
62
|
+
void allowSelectLockedCells(std::optional<bool> set) override;
|
|
63
|
+
void allowSelectUnlockedCells(std::optional<bool> set) override;
|
|
64
|
+
|
|
65
|
+
void denyInsertColumns() override;
|
|
66
|
+
void denyInsertRows() override;
|
|
67
|
+
void denyDeleteColumns() override;
|
|
68
|
+
void denyDeleteRows() override;
|
|
69
|
+
void denySelectLockedCells() override;
|
|
70
|
+
void denySelectUnlockedCells() override;
|
|
71
|
+
|
|
72
|
+
// Protection state
|
|
73
|
+
bool sheetProtected() override;
|
|
74
|
+
bool objectsProtected() override;
|
|
75
|
+
bool scenariosProtected() override;
|
|
76
|
+
bool insertColumnsAllowed() override;
|
|
77
|
+
bool insertRowsAllowed() override;
|
|
78
|
+
bool deleteColumnsAllowed() override;
|
|
79
|
+
bool deleteRowsAllowed() override;
|
|
80
|
+
bool selectLockedCellsAllowed() override;
|
|
81
|
+
bool selectUnlockedCellsAllowed() override;
|
|
82
|
+
bool passwordIsSet() override;
|
|
83
|
+
std::string passwordHash() override;
|
|
84
|
+
std::string sheetProtectionSummary() override;
|
|
85
|
+
|
|
64
86
|
void setColumnHidden(double firstCol, double lastCol, bool hidden) override;
|
|
65
87
|
void setRowHidden(double row, bool hidden) override;
|
|
66
88
|
|
|
89
|
+
// Comments (XLComments)
|
|
90
|
+
void setComment(double row, double col, const std::string& text, std::optional<double> authorId) override;
|
|
91
|
+
std::string getComment(double row, double col) override;
|
|
92
|
+
bool hasComment(double row, double col) override;
|
|
93
|
+
bool deleteComment(double row, double col) override;
|
|
94
|
+
double getCommentCount() override;
|
|
95
|
+
double addCommentAuthor(const std::string& author) override;
|
|
96
|
+
std::string getCommentAuthor(double authorId) override;
|
|
97
|
+
|
|
98
|
+
// Conditional formatting
|
|
99
|
+
std::shared_ptr<HybridXlsxConditionalFormatsSpec> getConditionalFormats() override;
|
|
100
|
+
|
|
67
101
|
// Read methods
|
|
68
102
|
std::variant<bool, nitro::NullType, std::string, double> getCellValue(double row, double col) override;
|
|
69
103
|
std::string getCellString(double row, double col) override;
|
|
70
104
|
std::string getCellRawValue(double row, double col) override;
|
|
71
105
|
CellType getCellType(double row, double col) override;
|
|
72
106
|
std::shared_ptr<HybridXlsxCellFormatSpec> getCellFormat(double row, double col) override;
|
|
107
|
+
bool hasFormula(double row, double col) override;
|
|
108
|
+
std::string formula(double row, double col) override;
|
|
109
|
+
bool findCell(double row, double col) override;
|
|
73
110
|
double getRowCount() override;
|
|
74
111
|
double getColumnCount() override;
|
|
75
112
|
double getLastRow() override;
|
|
76
113
|
double getLastColumn() override;
|
|
77
114
|
std::string getName() override;
|
|
78
115
|
|
|
116
|
+
// JSON import / export for this worksheet
|
|
117
|
+
std::vector<std::shared_ptr<AnyMap>> toJSON(const std::optional<std::vector<std::string>>& keys) override;
|
|
118
|
+
void fromJSON(const std::vector<std::shared_ptr<AnyMap>>& data) override;
|
|
119
|
+
|
|
120
|
+
// Internal: rename this sheet and rewrite formula references
|
|
121
|
+
void applyRename(const std::string& oldName, const std::string& newName);
|
|
122
|
+
// Internal: rewrite formula references to a renamed sheet (no actual rename)
|
|
123
|
+
void rewriteSheetNameRef(const std::string& oldName, const std::string& newName);
|
|
124
|
+
|
|
79
125
|
private:
|
|
80
126
|
OpenXLSX::XLDocument& _doc;
|
|
81
127
|
OpenXLSX::XLWorksheet _worksheet;
|
|
82
128
|
|
|
83
129
|
void applyFormat(OpenXLSX::XLCell& cell, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format);
|
|
130
|
+
std::string cellAddress(double row, double col);
|
|
84
131
|
};
|
|
85
132
|
|
|
86
133
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Android / desktop implementation of XlsxCacheDir.hpp.
|
|
2
|
+
// (Apple is handled by ios/XlsxCacheDir.mm.)
|
|
3
|
+
|
|
4
|
+
#ifndef __APPLE__
|
|
5
|
+
|
|
6
|
+
#include "XlsxCacheDir.hpp"
|
|
7
|
+
|
|
8
|
+
#include <cstdlib>
|
|
9
|
+
#include <sys/stat.h>
|
|
10
|
+
|
|
11
|
+
#ifdef __ANDROID__
|
|
12
|
+
#include <jni.h>
|
|
13
|
+
#include <fbjni/fbjni.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro::xlsx {
|
|
17
|
+
|
|
18
|
+
namespace {
|
|
19
|
+
std::string g_cacheDir;
|
|
20
|
+
|
|
21
|
+
bool isUsableDir(const std::string& path) {
|
|
22
|
+
if (path.empty()) return false;
|
|
23
|
+
struct stat st {};
|
|
24
|
+
return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void setCacheDir(const std::string& dir) {
|
|
29
|
+
g_cacheDir = dir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
std::string getCacheDir() {
|
|
33
|
+
if (!g_cacheDir.empty()) return g_cacheDir;
|
|
34
|
+
return detail::resolvePlatformCacheDir();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
namespace detail {
|
|
38
|
+
|
|
39
|
+
std::string resolvePlatformCacheDir() {
|
|
40
|
+
#ifdef __ANDROID__
|
|
41
|
+
// Ask the Android runtime for Context.getCacheDir()
|
|
42
|
+
using namespace facebook::jni;
|
|
43
|
+
try {
|
|
44
|
+
auto activityThread = findClassStatic("android/app/ActivityThread");
|
|
45
|
+
auto currentApplication = activityThread->getStaticMethod<JObject()>("currentApplication");
|
|
46
|
+
// JStaticMethod::operator() takes (alias_ref<jclass>, args...)
|
|
47
|
+
auto application = currentApplication(activityThread);
|
|
48
|
+
if (application) {
|
|
49
|
+
auto contextClass = findClassStatic("android/content/Context");
|
|
50
|
+
auto getCacheDir = contextClass->getMethod<JObject()>("getCacheDir");
|
|
51
|
+
// JMethod::operator() takes (alias_ref<jobject>, args...)
|
|
52
|
+
auto cacheDir = getCacheDir(application);
|
|
53
|
+
if (cacheDir) {
|
|
54
|
+
auto fileClass = findClassStatic("java/io/File");
|
|
55
|
+
auto getAbsolutePath = fileClass->getMethod<JString()>("getAbsolutePath");
|
|
56
|
+
auto path = getAbsolutePath(cacheDir);
|
|
57
|
+
std::string result = path->toStdString();
|
|
58
|
+
if (isUsableDir(result)) return result;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (...) {
|
|
62
|
+
// fall through to env / default
|
|
63
|
+
}
|
|
64
|
+
#endif
|
|
65
|
+
|
|
66
|
+
const char* tmp = std::getenv("TMPDIR");
|
|
67
|
+
if (tmp != nullptr && isUsableDir(tmp)) {
|
|
68
|
+
return std::string(tmp);
|
|
69
|
+
}
|
|
70
|
+
return "/tmp";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} // namespace detail
|
|
74
|
+
} // namespace margelo::nitro::xlsx
|
|
75
|
+
|
|
76
|
+
#endif // !__APPLE__
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::xlsx {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Override the cache directory used for temporary XLSX files.
|
|
9
|
+
* Normally called once from the Android native side at startup;
|
|
10
|
+
* if never called, getCacheDir() falls back to the platform resolver.
|
|
11
|
+
*/
|
|
12
|
+
void setCacheDir(const std::string& dir);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Directory used for temporary XLSX files.
|
|
16
|
+
* Priority: explicit setCacheDir() > platform cache dir > TMPDIR > /tmp
|
|
17
|
+
*
|
|
18
|
+
* Implemented in ios/XlsxCacheDir.mm (Apple) and cpp/XlsxCacheDir.cpp (other).
|
|
19
|
+
*/
|
|
20
|
+
std::string getCacheDir();
|
|
21
|
+
|
|
22
|
+
namespace detail {
|
|
23
|
+
/** Platform-specific cache directory (NSCachesDirectory / Context.getCacheDir). */
|
|
24
|
+
std::string resolvePlatformCacheDir();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace margelo::nitro::xlsx
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <OpenXLSX/XLException.hpp>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
namespace margelo::nitro::xlsx {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Structured error with a stable code that the JS side can switch on.
|
|
11
|
+
* Message format written to JS: "CODE: message" (Nitro prefixes the method name).
|
|
12
|
+
*/
|
|
13
|
+
class XlsxError : public std::runtime_error {
|
|
14
|
+
public:
|
|
15
|
+
XlsxError(std::string code, const std::string& message)
|
|
16
|
+
: std::runtime_error(code + ": " + message), _code(std::move(code)) {}
|
|
17
|
+
|
|
18
|
+
const std::string& code() const noexcept { return _code; }
|
|
19
|
+
|
|
20
|
+
private:
|
|
21
|
+
std::string _code;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
namespace xlsx_error {
|
|
25
|
+
inline constexpr const char* INVALID_ARGUMENT = "INVALID_ARGUMENT";
|
|
26
|
+
inline constexpr const char* SHEET_NOT_FOUND = "SHEET_NOT_FOUND";
|
|
27
|
+
inline constexpr const char* SHEET_EXISTS = "SHEET_EXISTS";
|
|
28
|
+
inline constexpr const char* INDEX_OUT_OF_RANGE = "INDEX_OUT_OF_RANGE";
|
|
29
|
+
inline constexpr const char* CELL_NOT_FOUND = "CELL_NOT_FOUND";
|
|
30
|
+
inline constexpr const char* WORKBOOK_CLOSED = "WORKBOOK_CLOSED";
|
|
31
|
+
inline constexpr const char* IO_ERROR = "IO_ERROR";
|
|
32
|
+
inline constexpr const char* UNSUPPORTED = "UNSUPPORTED";
|
|
33
|
+
inline constexpr const char* XLSX_ERROR = "XLSX_ERROR";
|
|
34
|
+
inline constexpr const char* INTERNAL_ERROR = "INTERNAL_ERROR";
|
|
35
|
+
} // namespace xlsx_error
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Map OpenXLSX / std exceptions to XlsxError so the JS side always sees "CODE: message".
|
|
39
|
+
* Anything already an XlsxError is rethrown unchanged.
|
|
40
|
+
*/
|
|
41
|
+
template <typename Fn>
|
|
42
|
+
auto withXlsxError(const char* defaultCode, Fn&& fn) -> decltype(fn()) {
|
|
43
|
+
try {
|
|
44
|
+
return fn();
|
|
45
|
+
} catch (const XlsxError&) {
|
|
46
|
+
throw;
|
|
47
|
+
} catch (const OpenXLSX::XLInputError& e) {
|
|
48
|
+
throw XlsxError(xlsx_error::INVALID_ARGUMENT, e.what());
|
|
49
|
+
} catch (const OpenXLSX::XLCellAddressError& e) {
|
|
50
|
+
throw XlsxError(xlsx_error::INVALID_ARGUMENT, e.what());
|
|
51
|
+
} catch (const OpenXLSX::XLSheetError& e) {
|
|
52
|
+
throw XlsxError(xlsx_error::SHEET_NOT_FOUND, e.what());
|
|
53
|
+
} catch (const OpenXLSX::XLOverflowError& e) {
|
|
54
|
+
throw XlsxError(xlsx_error::INDEX_OUT_OF_RANGE, e.what());
|
|
55
|
+
} catch (const OpenXLSX::XLValueTypeError& e) {
|
|
56
|
+
throw XlsxError(xlsx_error::INVALID_ARGUMENT, e.what());
|
|
57
|
+
} catch (const OpenXLSX::XLFormulaError& e) {
|
|
58
|
+
throw XlsxError(xlsx_error::INVALID_ARGUMENT, e.what());
|
|
59
|
+
} catch (const OpenXLSX::XLException& e) {
|
|
60
|
+
throw XlsxError(defaultCode, e.what());
|
|
61
|
+
} catch (const std::exception& e) {
|
|
62
|
+
throw XlsxError(defaultCode, e.what());
|
|
63
|
+
} catch (...) {
|
|
64
|
+
throw XlsxError(xlsx_error::INTERNAL_ERROR, "Unknown native error");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
} // namespace margelo::nitro::xlsx
|
package/cpp/XlsxJson.cpp
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#include "XlsxJson.hpp"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <limits>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
|
|
7
|
+
namespace margelo::nitro::xlsx {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
bool isCellEmpty(OpenXLSX::XLWorksheet& ws, uint32_t row, uint16_t col) {
|
|
12
|
+
return ws.findCell(row, col).empty() || ws.findCell(row, col).value().type() == OpenXLSX::XLValueType::Empty;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
std::string keyFromCellValue(OpenXLSX::XLWorksheet& ws, uint32_t row, uint16_t col) {
|
|
16
|
+
auto cell = ws.findCell(row, col);
|
|
17
|
+
if (cell.empty()) return "col" + std::to_string(col);
|
|
18
|
+
switch (cell.value().type()) {
|
|
19
|
+
case OpenXLSX::XLValueType::String:
|
|
20
|
+
return cell.value().get<std::string>();
|
|
21
|
+
case OpenXLSX::XLValueType::Integer:
|
|
22
|
+
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>()));
|
|
25
|
+
default:
|
|
26
|
+
return "col" + std::to_string(col);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void recordFromRow(OpenXLSX::XLWorksheet& ws,
|
|
31
|
+
uint32_t row,
|
|
32
|
+
const std::vector<std::string>& keys,
|
|
33
|
+
uint16_t lastCol,
|
|
34
|
+
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) {
|
|
37
|
+
const std::string& key = keys[c];
|
|
38
|
+
auto cell = ws.findCell(row, c + 1);
|
|
39
|
+
if (cell.empty()) {
|
|
40
|
+
record->setNull(key);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
switch (cell.value().type()) {
|
|
44
|
+
case OpenXLSX::XLValueType::String:
|
|
45
|
+
record->setString(key, cell.value().get<std::string>());
|
|
46
|
+
break;
|
|
47
|
+
case OpenXLSX::XLValueType::Integer:
|
|
48
|
+
record->setDouble(key, static_cast<double>(cell.value().get<int64_t>()));
|
|
49
|
+
break;
|
|
50
|
+
case OpenXLSX::XLValueType::Float: {
|
|
51
|
+
double d = cell.value().get<double>();
|
|
52
|
+
if (!std::isnan(d) && !std::isinf(d)) {
|
|
53
|
+
record->setDouble(key, d);
|
|
54
|
+
} else {
|
|
55
|
+
record->setNull(key);
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case OpenXLSX::XLValueType::Boolean:
|
|
60
|
+
record->setBoolean(key, cell.value().get<bool>());
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
record->setNull(key);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void writeValue(OpenXLSX::XLWorksheet& ws,
|
|
70
|
+
uint32_t row,
|
|
71
|
+
uint16_t col,
|
|
72
|
+
const std::shared_ptr<AnyMap>& map,
|
|
73
|
+
const std::string& key) {
|
|
74
|
+
if (!map->contains(key) || map->isNull(key)) return;
|
|
75
|
+
auto cell = ws.cell(row, col);
|
|
76
|
+
if (map->isString(key)) {
|
|
77
|
+
cell.value() = map->getString(key);
|
|
78
|
+
} else if (map->isDouble(key)) {
|
|
79
|
+
cell.value() = map->getDouble(key);
|
|
80
|
+
} else if (map->isInt64(key)) {
|
|
81
|
+
// store as number so get<double> round-trips
|
|
82
|
+
cell.value() = static_cast<double>(map->getInt64(key));
|
|
83
|
+
} else if (map->isBoolean(key)) {
|
|
84
|
+
cell.value() = map->getBoolean(key);
|
|
85
|
+
}
|
|
86
|
+
// arrays / objects are not representable as a single cell — skipped
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// AnyMap is backed by std::unordered_map — sort each row's keys so column
|
|
90
|
+
// order is deterministic, then keep first-seen order across rows.
|
|
91
|
+
std::vector<std::string> collectKeys(const std::vector<std::shared_ptr<AnyMap>>& data) {
|
|
92
|
+
std::vector<std::string> keys;
|
|
93
|
+
for (const auto& row : data) {
|
|
94
|
+
if (!row) continue;
|
|
95
|
+
auto rowKeys = row->getAllKeys();
|
|
96
|
+
std::sort(rowKeys.begin(), rowKeys.end());
|
|
97
|
+
for (const auto& key : rowKeys) {
|
|
98
|
+
bool known = false;
|
|
99
|
+
for (const auto& existing : keys) {
|
|
100
|
+
if (existing == key) { known = true; break; }
|
|
101
|
+
}
|
|
102
|
+
if (!known) keys.push_back(key);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return keys;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
} // anonymous namespace
|
|
109
|
+
|
|
110
|
+
std::vector<std::shared_ptr<AnyMap>> worksheetToRecords(
|
|
111
|
+
OpenXLSX::XLWorksheet& worksheet,
|
|
112
|
+
const std::optional<std::vector<std::string>>& keys) {
|
|
113
|
+
std::vector<std::shared_ptr<AnyMap>> result;
|
|
114
|
+
uint32_t lastRow = worksheet.rowCount();
|
|
115
|
+
uint16_t lastCol = worksheet.columnCount();
|
|
116
|
+
if (lastRow == 0 || lastCol == 0) return result;
|
|
117
|
+
|
|
118
|
+
uint32_t dataStartRow = 1;
|
|
119
|
+
std::vector<std::string> resolvedKeys;
|
|
120
|
+
|
|
121
|
+
if (keys.has_value()) {
|
|
122
|
+
resolvedKeys = *keys;
|
|
123
|
+
} else {
|
|
124
|
+
dataStartRow = 2;
|
|
125
|
+
for (uint16_t c = 1; c <= lastCol; ++c) {
|
|
126
|
+
resolvedKeys.push_back(keyFromCellValue(worksheet, 1, c));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (resolvedKeys.empty()) return result;
|
|
130
|
+
|
|
131
|
+
uint16_t keyCount = std::min(static_cast<uint16_t>(resolvedKeys.size()), lastCol);
|
|
132
|
+
for (uint32_t r = dataStartRow; r <= lastRow; ++r) {
|
|
133
|
+
bool hasData = false;
|
|
134
|
+
for (uint16_t c = 1; c <= keyCount; ++c) {
|
|
135
|
+
if (!isCellEmpty(worksheet, r, c)) {
|
|
136
|
+
hasData = true;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!hasData) continue;
|
|
141
|
+
|
|
142
|
+
auto record = AnyMap::make();
|
|
143
|
+
recordFromRow(worksheet, r, resolvedKeys, lastCol, record);
|
|
144
|
+
result.push_back(record);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void recordsToWorksheet(
|
|
150
|
+
OpenXLSX::XLWorksheet& worksheet,
|
|
151
|
+
const std::vector<std::shared_ptr<AnyMap>>& data) {
|
|
152
|
+
if (data.empty()) return;
|
|
153
|
+
|
|
154
|
+
// Column order: first-seen keys across rows; each row's keys sorted (AnyMap
|
|
155
|
+
// is unordered, so sorting keeps the layout deterministic).
|
|
156
|
+
std::vector<std::string> keys = collectKeys(data);
|
|
157
|
+
|
|
158
|
+
// Header row
|
|
159
|
+
for (size_t c = 0; c < keys.size(); ++c) {
|
|
160
|
+
worksheet.cell(1, static_cast<uint16_t>(c + 1)).value() = keys[c];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Data rows
|
|
164
|
+
for (size_t r = 0; r < data.size(); ++r) {
|
|
165
|
+
const auto& row = data[r];
|
|
166
|
+
if (!row) continue;
|
|
167
|
+
uint32_t rowNum = static_cast<uint32_t>(r + 2);
|
|
168
|
+
for (size_t c = 0; c < keys.size(); ++c) {
|
|
169
|
+
writeValue(worksheet, rowNum, static_cast<uint16_t>(c + 1), row, keys[c]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> workbookToRecords(
|
|
175
|
+
OpenXLSX::XLWorkbook& workbook) {
|
|
176
|
+
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> result;
|
|
177
|
+
uint16_t count = static_cast<uint16_t>(workbook.worksheetCount());
|
|
178
|
+
for (uint16_t i = 1; i <= count; ++i) {
|
|
179
|
+
auto ws = workbook.worksheet(i);
|
|
180
|
+
result[ws.name()] = worksheetToRecords(ws);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
} // namespace margelo::nitro::xlsx
|
package/cpp/XlsxJson.hpp
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <NitroModules/AnyMap.hpp>
|
|
4
|
+
#include <OpenXLSX/XLSheet.hpp>
|
|
5
|
+
#include <OpenXLSX/XLWorkbook.hpp>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro::xlsx {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert one worksheet to an array of records.
|
|
16
|
+
* @param keys if provided, used as record keys; otherwise row 1 is treated as the header
|
|
17
|
+
*/
|
|
18
|
+
std::vector<std::shared_ptr<AnyMap>> worksheetToRecords(
|
|
19
|
+
OpenXLSX::XLWorksheet& worksheet,
|
|
20
|
+
const std::optional<std::vector<std::string>>& keys = std::nullopt);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Write an array of records into a worksheet.
|
|
24
|
+
* The first record's keys become the header row; later-only keys extend the columns.
|
|
25
|
+
*/
|
|
26
|
+
void recordsToWorksheet(
|
|
27
|
+
OpenXLSX::XLWorksheet& worksheet,
|
|
28
|
+
const std::vector<std::shared_ptr<AnyMap>>& data);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Export every worksheet as a Record keyed by sheet name.
|
|
32
|
+
*/
|
|
33
|
+
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> workbookToRecords(
|
|
34
|
+
OpenXLSX::XLWorkbook& workbook);
|
|
35
|
+
|
|
36
|
+
} // namespace margelo::nitro::xlsx
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Apple (iOS / macOS) cache directory via Foundation.
|
|
2
|
+
// Full implementation of XlsxCacheDir.hpp for __APPLE__.
|
|
3
|
+
|
|
4
|
+
#ifdef __APPLE__
|
|
5
|
+
|
|
6
|
+
#include "XlsxCacheDir.hpp"
|
|
7
|
+
|
|
8
|
+
#include <cstdlib>
|
|
9
|
+
#include <sys/stat.h>
|
|
10
|
+
|
|
11
|
+
#import <Foundation/Foundation.h>
|
|
12
|
+
|
|
13
|
+
namespace margelo::nitro::xlsx {
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
std::string g_cacheDir;
|
|
17
|
+
|
|
18
|
+
bool isUsableDir(const std::string& path) {
|
|
19
|
+
if (path.empty()) return false;
|
|
20
|
+
struct stat st {};
|
|
21
|
+
return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void setCacheDir(const std::string& dir) {
|
|
26
|
+
g_cacheDir = dir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::string getCacheDir() {
|
|
30
|
+
if (!g_cacheDir.empty()) return g_cacheDir;
|
|
31
|
+
return detail::resolvePlatformCacheDir();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
namespace detail {
|
|
35
|
+
|
|
36
|
+
std::string resolvePlatformCacheDir() {
|
|
37
|
+
@autoreleasepool {
|
|
38
|
+
NSArray<NSString*>* caches =
|
|
39
|
+
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
|
40
|
+
if (caches.count > 0) {
|
|
41
|
+
std::string result([caches.firstObject UTF8String]);
|
|
42
|
+
if (isUsableDir(result)) return result;
|
|
43
|
+
}
|
|
44
|
+
NSString* tmp = NSTemporaryDirectory();
|
|
45
|
+
if (tmp != nil) {
|
|
46
|
+
std::string result([tmp UTF8String]);
|
|
47
|
+
if (isUsableDir(result)) return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const char* env = std::getenv("TMPDIR");
|
|
51
|
+
if (env != nullptr && isUsableDir(env)) {
|
|
52
|
+
return std::string(env);
|
|
53
|
+
}
|
|
54
|
+
return "/tmp";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
} // namespace detail
|
|
58
|
+
} // namespace margelo::nitro::xlsx
|
|
59
|
+
|
|
60
|
+
#endif // __APPLE__
|
|
@@ -35,6 +35,8 @@ target_sources(
|
|
|
35
35
|
# Shared Nitrogen C++ sources
|
|
36
36
|
../nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp
|
|
37
37
|
../nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp
|
|
38
|
+
../nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.cpp
|
|
39
|
+
../nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.cpp
|
|
38
40
|
../nitrogen/generated/shared/c++/HybridXlsxCellFormatSpec.cpp
|
|
39
41
|
../nitrogen/generated/shared/c++/HybridNitroXlsxSpec.cpp
|
|
40
42
|
# Android-specific Nitrogen C++ sources
|
|
@@ -19,8 +19,10 @@ namespace margelo::nitro::xlsx { class HybridXlsxWorkbookSpec; }
|
|
|
19
19
|
#include <memory>
|
|
20
20
|
#include "HybridXlsxWorkbookSpec.hpp"
|
|
21
21
|
#include <string>
|
|
22
|
-
#include <optional>
|
|
23
22
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
23
|
+
#include <NitroModules/AnyMap.hpp>
|
|
24
|
+
#include <vector>
|
|
25
|
+
#include <unordered_map>
|
|
24
26
|
|
|
25
27
|
namespace margelo::nitro::xlsx {
|
|
26
28
|
|
|
@@ -53,10 +55,10 @@ namespace margelo::nitro::xlsx {
|
|
|
53
55
|
|
|
54
56
|
public:
|
|
55
57
|
// Methods
|
|
56
|
-
virtual std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook(
|
|
58
|
+
virtual std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook() = 0;
|
|
57
59
|
virtual std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbook(const std::string& path) = 0;
|
|
58
60
|
virtual std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbookFromBuffer(const std::shared_ptr<ArrayBuffer>& buffer) = 0;
|
|
59
|
-
virtual std::shared_ptr<HybridXlsxWorkbookSpec> fromJSON(const std::string
|
|
61
|
+
virtual std::shared_ptr<HybridXlsxWorkbookSpec> fromJSON(const std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>>& data) = 0;
|
|
60
62
|
|
|
61
63
|
protected:
|
|
62
64
|
// Hybrid Setup
|