react-native-nitro-xlsx 0.1.2 → 0.1.4

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 CHANGED
@@ -4,7 +4,8 @@ This software uses third-party open source components listed below, with full li
4
4
  ================================================================================
5
5
  1. OpenXLSX
6
6
  Repository: https://github.com/troldal/OpenXLSX
7
- License: BSD 3-Clause License
7
+ Version: 0.5.1
8
+ License: BSD 3-Clause License (SPDX: BSD-3-Clause)
8
9
  Copyright (c) 2020 Kenneth Troldal Balslev
9
10
 
10
11
  Full BSD 3-Clause License text:
@@ -27,7 +28,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27
28
  2. react-native-nitro-modules
28
29
  Repository: https://github.com/mrousavy/nitro
29
30
  Version: 0.58.3
30
- License: MIT License
31
+ License: MIT License (SPDX: MIT)
31
32
  Copyright (c) 2024 Marc Rousavy
32
33
 
33
34
  Full MIT License text:
@@ -56,7 +57,8 @@ SOFTWARE.
56
57
  ================================================================================
57
58
  3. pugixml (OpenXLSX dependency)
58
59
  Repository: https://github.com/zeux/pugixml
59
- License: MIT License
60
+ Version: 1.16
61
+ License: MIT License (SPDX: MIT)
60
62
  Copyright (c) 2006-2024 Arseny Kapoulkine
61
63
 
62
64
  Full MIT License text:
@@ -72,39 +74,10 @@ The above copyright notice and this permission notice shall be included in all c
72
74
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
73
75
 
74
76
  ================================================================================
75
- 4. Zippy (OpenXLSX dependency)
76
- Repository: https://github.com/troldal/Zippy
77
- License: MIT License
78
- Copyright (c) 2019 Kenneth Troldal Balslev
79
-
80
- Full MIT License text:
81
-
82
- MIT License
83
-
84
- Copyright (c) 2019 Kenneth Troldal Balslev
85
-
86
- Permission is hereby granted, free of charge, to any person obtaining a copy
87
- of this software and associated documentation files (the "Software"), to deal
88
- in the Software without restriction, including without limitation the rights
89
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90
- copies of the Software, and to permit persons to whom the Software is
91
- furnished to do so, subject to the following conditions:
92
-
93
- The above copyright notice and this permission notice shall be included in all
94
- copies or substantial portions of the Software.
95
-
96
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
102
- SOFTWARE.
103
-
104
- ================================================================================
105
- 5. miniz (OpenXLSX dependency)
77
+ 4. miniz (OpenXLSX dependency)
106
78
  Repository: https://github.com/richgel999/miniz
107
- License: MIT License
79
+ Version: 3.1.2
80
+ License: MIT License (SPDX: MIT)
108
81
  Copyright (c) 2013-2014 RAD Game Tools and Valve Software
109
82
  Copyright (c) 2010-2014 Rich Geldreich and Tenacious Software LLC
110
83
 
package/NitroXlsx.podspec CHANGED
@@ -11,9 +11,13 @@ Pod::Spec.new do |s|
11
11
  s.authors = package["author"]
12
12
 
13
13
  s.platforms = { :ios => "15.1" }
14
- s.source = { :git => "", :tag => "#{s.version}" }
14
+ s.source = { :git => "https://github.com/zydbkqf/react-native-nitro-xlsx.git", :tag => "#{s.version}" }
15
15
 
16
- s.exclude_files = "cpp/tests/**/*"
16
+ s.exclude_files = [
17
+ "cpp/tests/**/*",
18
+ "OpenXLSX/ThirdParty/miniz/tests/**/*",
19
+ "OpenXLSX/ThirdParty/miniz/examples/**/*",
20
+ ]
17
21
 
18
22
  s.source_files = [
19
23
  "cpp/**/*.{h,hpp,cpp}",
@@ -49,61 +53,20 @@ Pod::Spec.new do |s|
49
53
  "HEADER_SEARCH_PATHS" => "$(inherited) ${PODS_TARGET_SRCROOT}/OpenXLSX ${PODS_TARGET_SRCROOT}/OpenXLSX/OpenXLSX ${PODS_TARGET_SRCROOT}/OpenXLSX/OpenXLSX/headers ${PODS_TARGET_SRCROOT}/OpenXLSX/ThirdParty/pugixml/src ${PODS_TARGET_SRCROOT}/OpenXLSX/ThirdParty/miniz",
50
54
  }
51
55
 
52
- s.prepare_command = <<-CMD
53
- if [ ! -d "OpenXLSX" ]; then
54
- git clone --depth 1 --branch v0.5.1 https://github.com/troldal/OpenXLSX.git
55
- fi
56
- mkdir -p OpenXLSX/ThirdParty
57
- if [ ! -d "OpenXLSX/ThirdParty/pugixml" ]; then
58
- git clone --depth 1 --branch v1.15 https://github.com/zeux/pugixml.git OpenXLSX/ThirdParty/pugixml
59
- fi
60
- if [ ! -d "OpenXLSX/ThirdParty/miniz" ]; then
61
- git clone --depth 1 --branch 3.0.2 https://github.com/richgel999/miniz.git OpenXLSX/ThirdParty/miniz
62
- fi
63
- if [ ! -f "OpenXLSX/ThirdParty/miniz/miniz_export.h" ]; then
64
- cat > OpenXLSX/ThirdParty/miniz/miniz_export.h << 'EOF'
65
- #ifndef MINIZ_EXPORT_H
66
- #define MINIZ_EXPORT_H
67
-
68
- #ifndef MINIZ_EXPORT
69
- #define MINIZ_EXPORT
70
- #endif
71
-
72
- #ifndef MINIZ_DEPRECATED
73
- #define MINIZ_DEPRECATED
74
- #endif
75
-
76
- #ifndef MINIZ_DEPRECATED_EXPORT
77
- #define MINIZ_DEPRECATED_EXPORT MINIZ_EXPORT MINIZ_DEPRECATED
78
- #endif
79
-
80
- #ifndef MINIZ_NO_EXPORT
81
- #define MINIZ_NO_EXPORT
82
- #endif
83
-
84
- #endif
85
- EOF
86
- fi
87
- if [ ! -f "OpenXLSX/OpenXLSX/headers/OpenXLSX-Exports.hpp" ]; then
88
- cat > OpenXLSX/OpenXLSX/headers/OpenXLSX-Exports.hpp << 'EOF'
89
- #ifndef OPENXLSX_EXPORTS_HPP
90
- #define OPENXLSX_EXPORTS_HPP
91
-
92
- #ifndef OPENXLSX_EXPORT
93
- #define OPENXLSX_EXPORT
94
- #endif
95
-
96
- #ifndef OPENXLSX_HIDDEN
97
- #define OPENXLSX_HIDDEN
98
- #endif
99
-
100
- #endif
101
- EOF
102
- fi
103
- if [ ! -d "OpenXLSX/OpenXLSX/headers/OpenXLSX" ]; then
104
- cd OpenXLSX/OpenXLSX/headers && ln -sf . OpenXLSX && cd -
105
- fi
106
- CMD
56
+ # Prepare OpenXLSX and its dependencies (pugixml, miniz) via a Node script
57
+ # so the same logic can be reused from both CocoaPods and Android CMake.
58
+ s.prepare_command = "node #{__dir__}/scripts/prepare-openxlsx.js"
59
+
60
+ # Swift Package Manager declaration for React Native 0.87+.
61
+ # This exposes NitroXlsx as a local Swift package; the heavy lifting for
62
+ # OpenXLSX is still done by the prepare command above.
63
+ if ENV['NITRO_XLSX_USE_SPM'] == '1' && defined?(SPM) && SPM.respond_to?(:dependency)
64
+ SPM.dependency(s,
65
+ url: File.join(__dir__, "."),
66
+ requirement: { kind: 'upToNextMajorVersion', minimumVersion: s.version.to_s },
67
+ products: ['NitroXlsx']
68
+ )
69
+ end
107
70
 
108
71
  load 'nitrogen/generated/ios/NitroXlsx+autolinking.rb'
109
72
  add_nitrogen_files(s)
package/Package.swift ADDED
@@ -0,0 +1,31 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ // NOTE: This Package.swift is a Swift Package Manager manifest stub for
5
+ // React Native 0.87+ compatibility. The library is still primarily built
6
+ // through CocoaPods (NitroXlsx.podspec), where OpenXLSX, pugixml and miniz
7
+ // are cloned and compiled from source by scripts/prepare-openxlsx.js.
8
+ //
9
+ // The local package reference below lets RN 0.87's spm_dependency helper
10
+ // register NitroXlsx in the Pods project when SwiftPM mode is enabled.
11
+ // Full SPM-based compilation of the C++ sources is not yet supported.
12
+
13
+ let package = Package(
14
+ name: "NitroXlsx",
15
+ platforms: [
16
+ .iOS(.v15)
17
+ ],
18
+ products: [
19
+ .library(
20
+ name: "NitroXlsx",
21
+ targets: ["NitroXlsx"]
22
+ )
23
+ ],
24
+ targets: [
25
+ .target(
26
+ name: "NitroXlsx",
27
+ path: "ios",
28
+ sources: ["dummy.swift"]
29
+ )
30
+ ]
31
+ )
package/README.md CHANGED
@@ -22,6 +22,8 @@ npm install react-native-nitro-xlsx
22
22
  yarn add react-native-nitro-xlsx
23
23
  ```
24
24
 
25
+ > **Note**: Native dependencies (OpenXlsx) are downloaded from GitHub during the first build. Please make sure you have a stable network connection.
26
+
25
27
  ### iOS
26
28
 
27
29
  ```sh
@@ -122,6 +124,47 @@ const fontName = format.getFontName();
122
124
  const fontSize = format.getFontSize();
123
125
  ```
124
126
 
127
+ ### Working with JSON
128
+
129
+ #### Export to JSON (`toJSON`)
130
+
131
+ Convert worksheet data to a JSON array. By default, the first row is used as object keys.
132
+
133
+ ```typescript
134
+ import { NitroXlsx, toJSON } from 'react-native-nitro-xlsx';
135
+
136
+ const workbook = NitroXlsx.openWorkbook('/path/to/data.xlsx');
137
+
138
+ // Use the first row as keys (default)
139
+ const data = toJSON(workbook);
140
+ // Result: [{ "Name": "Alice", "Age": 30 }, { "Name": "Bob", "Age": 25 }]
141
+
142
+ // Provide custom keys
143
+ const data = toJSON(workbook, ['firstName', 'yearsOld']);
144
+ // Result: [{ "firstName": "Alice", "yearsOld": 30 }, { "firstName": "Bob", "yearsOld": 25 }]
145
+ ```
146
+
147
+ #### Import from JSON (`fromJSON`)
148
+
149
+ Create a workbook from a JSON array. The first object's keys become the header row.
150
+
151
+ ```typescript
152
+ import { NitroXlsx } from 'react-native-nitro-xlsx';
153
+
154
+ const data = [
155
+ { Name: 'Alice', Age: 30, Active: true },
156
+ { Name: 'Bob', Age: 25, Active: false },
157
+ ];
158
+
159
+ const workbook = NitroXlsx.fromJSON(data);
160
+ // The workbook now has a worksheet with:
161
+ // Row 1 (header): Name | Age | Active
162
+ // Row 2: Alice | 30 | true
163
+ // Row 3: Bob | 25 | false
164
+
165
+ const buffer = await workbook.getBuffer();
166
+ ```
167
+
125
168
  ## API
126
169
 
127
170
  ### NitroXlsx
@@ -129,6 +172,7 @@ const fontSize = format.getFontSize();
129
172
  - `createWorkbook(tempDir?: string): XlsxWorkbook` - Create a new workbook. On Android, pass the app's cache directory (e.g., `RNFS.CachesDirectoryPath`) to avoid sandbox write permission issues.
130
173
  - `openWorkbook(path: string): XlsxWorkbook` - Open workbook from file path
131
174
  - `openWorkbookFromBuffer(buffer: ArrayBuffer): XlsxWorkbook` - Open workbook from buffer
175
+ - `fromJSON(data: Array<Record<string, string | number>>): XlsxWorkbook` - Create a workbook from a JSON array (static method)
132
176
 
133
177
  ### XlsxWorkbook
134
178
 
@@ -139,6 +183,7 @@ const fontSize = format.getFontSize();
139
183
  - `getWorksheetCount(): number` - Get number of worksheets
140
184
  - `addCellFormat(): XlsxCellFormat` - Add a new cell format
141
185
  - `getBuffer(): Promise<ArrayBuffer>` - Generate and return the XLSX file as a buffer
186
+ - `toJSON(keys?: string[]): Array<Record<string, string | number>>` - Convert worksheet data to a JSON array
142
187
 
143
188
  ### XlsxWorksheet
144
189
 
@@ -5,22 +5,21 @@ set(CMAKE_CXX_STANDARD 20)
5
5
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
6
 
7
7
  set(OPENXLSX_DIR ${CMAKE_SOURCE_DIR}/../OpenXLSX)
8
- if(NOT EXISTS ${OPENXLSX_DIR}/CMakeLists.txt)
9
- # Clean up incomplete directory to ensure fresh clone
10
- if(EXISTS ${OPENXLSX_DIR})
11
- file(REMOVE_RECURSE ${OPENXLSX_DIR})
12
- endif()
13
8
 
14
- include(FetchContent)
15
- FetchContent_Declare(
16
- OpenXLSX
17
- GIT_REPOSITORY https://github.com/troldal/OpenXLSX.git
18
- GIT_TAG v0.5.1
19
- SOURCE_DIR ${OPENXLSX_DIR}
20
- GIT_SHALLOW TRUE
21
- GIT_PROGRESS TRUE
9
+ # Prepare OpenXLSX and its dependencies (pugixml, miniz) using the same
10
+ # Node script that CocoaPods' prepare_command runs on iOS.
11
+ find_program(NODE_EXECUTABLE node REQUIRED)
12
+ execute_process(
13
+ COMMAND ${NODE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../scripts/prepare-openxlsx.js
14
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/..
15
+ RESULT_VARIABLE PREPARE_RESULT
16
+ )
17
+ if(NOT PREPARE_RESULT EQUAL 0)
18
+ message(FATAL_ERROR
19
+ "Failed to prepare OpenXLSX dependencies.\n"
20
+ "Please ensure Node.js is installed and run:\n"
21
+ " node scripts/prepare-openxlsx.js"
22
22
  )
23
- FetchContent_Populate(OpenXLSX)
24
23
  endif()
25
24
 
26
25
  # Verify OpenXLSX is available
@@ -33,15 +32,6 @@ if(NOT EXISTS ${OPENXLSX_DIR}/CMakeLists.txt)
33
32
  )
34
33
  endif()
35
34
 
36
- # Create symlink for #include <OpenXLSX/XXX.hpp> resolution
37
- # Same as iOS podspec: cd OpenXLSX/OpenXLSX/headers && ln -sf . OpenXLSX
38
- if(NOT EXISTS ${OPENXLSX_DIR}/OpenXLSX/headers/OpenXLSX)
39
- execute_process(
40
- COMMAND ${CMAKE_COMMAND} -E create_symlink . OpenXLSX
41
- WORKING_DIRECTORY ${OPENXLSX_DIR}/OpenXLSX/headers
42
- )
43
- endif()
44
-
45
35
  set(OPENXLSX_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
46
36
  set(OPENXLSX_BUILD_TESTS OFF CACHE BOOL "" FORCE)
47
37
  set(OPENXLSX_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
@@ -4,6 +4,11 @@ buildscript {
4
4
  }
5
5
  }
6
6
 
7
+ def reactNativeArchitectures() {
8
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
9
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
10
+ }
11
+
7
12
  apply plugin: 'com.android.library'
8
13
  apply plugin: 'kotlin-android'
9
14
 
@@ -16,6 +21,7 @@ android {
16
21
  externalNativeBuild {
17
22
  cmake {
18
23
  arguments "-DANDROID_STL=c++_shared"
24
+ abiFilters (*reactNativeArchitectures())
19
25
  }
20
26
  }
21
27
  }
@@ -1,5 +1,98 @@
1
1
  #include "HybridNitroXlsx.hpp"
2
2
  #include <fstream>
3
+ #include <stdexcept>
4
+ #include <cstring>
5
+ #include <cstdlib>
6
+
7
+ namespace {
8
+
9
+ struct JsonParser {
10
+ const char* p;
11
+ explicit JsonParser(const char* s) : p(s) {}
12
+
13
+ void skipWs() { while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') ++p; }
14
+
15
+ char peek() { skipWs(); return *p; }
16
+
17
+ void expect(char c) {
18
+ skipWs();
19
+ if (*p != c) throw std::runtime_error(std::string("Expected '") + c + "' but got '" + *p + "'");
20
+ ++p;
21
+ }
22
+
23
+ std::string parseString() {
24
+ expect('"');
25
+ std::string s;
26
+ while (*p && *p != '"') {
27
+ if (*p == '\\') {
28
+ ++p;
29
+ switch (*p) {
30
+ case '"': s += '"'; break;
31
+ case '\\': s += '\\'; break;
32
+ case '/': s += '/'; break;
33
+ case 'b': s += '\b'; break;
34
+ case 'f': s += '\f'; break;
35
+ case 'n': s += '\n'; break;
36
+ case 'r': s += '\r'; break;
37
+ case 't': s += '\t'; break;
38
+ case 'u': {
39
+ ++p;
40
+ char hex[5] = {};
41
+ std::memcpy(hex, p, 4);
42
+ p += 4;
43
+ unsigned long cp = std::strtoul(hex, nullptr, 16);
44
+ if (cp < 0x80) {
45
+ s += static_cast<char>(cp);
46
+ } else if (cp < 0x800) {
47
+ s += static_cast<char>(0xC0 | (cp >> 6));
48
+ s += static_cast<char>(0x80 | (cp & 0x3F));
49
+ } else {
50
+ s += static_cast<char>(0xE0 | (cp >> 12));
51
+ s += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
52
+ s += static_cast<char>(0x80 | (cp & 0x3F));
53
+ }
54
+ continue;
55
+ }
56
+ default: s += '\\'; s += *p; break;
57
+ }
58
+ } else {
59
+ s += *p;
60
+ }
61
+ ++p;
62
+ }
63
+ if (*p == '"') ++p;
64
+ return s;
65
+ }
66
+
67
+ enum ValueType { T_STRING, T_NUMBER, T_BOOL, T_NULL };
68
+
69
+ struct Value {
70
+ ValueType type;
71
+ std::string str;
72
+ double num = 0;
73
+ bool boolean = false;
74
+ };
75
+
76
+ Value parseValue() {
77
+ skipWs();
78
+ if (*p == '"') return { T_STRING, parseString(), 0, false };
79
+ if (*p == 't') { p += 4; return { T_BOOL, "", 0, true }; }
80
+ if (*p == 'f') { p += 5; return { T_BOOL, "", 0, false }; }
81
+ if (*p == 'n') { p += 4; return { T_NULL, "", 0, false }; }
82
+
83
+ // number
84
+ Value v{ T_NUMBER, "", 0, false };
85
+ const char* start = p;
86
+ if (*p == '-') ++p;
87
+ while (*p >= '0' && *p <= '9') ++p;
88
+ if (*p == '.') { ++p; while (*p >= '0' && *p <= '9') ++p; }
89
+ if (*p == 'e' || *p == 'E') { ++p; if (*p == '+' || *p == '-') ++p; while (*p >= '0' && *p <= '9') ++p; }
90
+ v.num = std::strtod(start, nullptr);
91
+ return v;
92
+ }
93
+ };
94
+
95
+ } // anonymous namespace
3
96
 
4
97
  namespace margelo::nitro::xlsx {
5
98
 
@@ -26,4 +119,88 @@ std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::openWorkbookFromBuffer(
26
119
  return workbook;
27
120
  }
28
121
 
122
+ std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::fromJSON(const std::string& data) {
123
+ auto workbook = std::make_shared<HybridXlsxWorkbook>();
124
+ auto sheet = workbook->addWorksheet("Sheet1");
125
+
126
+ JsonParser parser(data.c_str());
127
+ parser.skipWs();
128
+ parser.expect('[');
129
+
130
+ if (parser.peek() == ']') return workbook;
131
+
132
+ // Parse first object to extract keys
133
+ parser.expect('{');
134
+
135
+ std::vector<std::string> keys;
136
+ unsigned int col = 1;
137
+
138
+ while (parser.peek() != '}') {
139
+ std::string key = parser.parseString();
140
+ keys.push_back(key);
141
+ parser.expect(':');
142
+ auto val = parser.parseValue();
143
+
144
+ switch (val.type) {
145
+ case JsonParser::T_STRING:
146
+ sheet->writeString(1, col, val.str, std::nullopt);
147
+ break;
148
+ case JsonParser::T_NUMBER:
149
+ sheet->writeNumber(1, col, val.num, std::nullopt);
150
+ break;
151
+ case JsonParser::T_BOOL:
152
+ sheet->writeBoolean(1, col, val.boolean, std::nullopt);
153
+ break;
154
+ default:
155
+ break;
156
+ }
157
+ ++col;
158
+ if (parser.peek() == ',') ++parser.p;
159
+ }
160
+ parser.expect('}');
161
+
162
+ unsigned int row = 2;
163
+
164
+ while (parser.peek() == ',') {
165
+ ++parser.p;
166
+ parser.expect('{');
167
+
168
+ while (parser.peek() != '}') {
169
+ std::string key = parser.parseString();
170
+ parser.expect(':');
171
+ auto val = parser.parseValue();
172
+
173
+ unsigned int targetCol = 0;
174
+ for (size_t k = 0; k < keys.size(); ++k) {
175
+ if (keys[k] == key) { targetCol = static_cast<unsigned int>(k + 1); break; }
176
+ }
177
+ if (targetCol == 0) {
178
+ keys.push_back(key);
179
+ targetCol = static_cast<unsigned int>(keys.size());
180
+ }
181
+
182
+ switch (val.type) {
183
+ case JsonParser::T_STRING:
184
+ sheet->writeString(row, targetCol, val.str, std::nullopt);
185
+ break;
186
+ case JsonParser::T_NUMBER:
187
+ sheet->writeNumber(row, targetCol, val.num, std::nullopt);
188
+ break;
189
+ case JsonParser::T_BOOL:
190
+ sheet->writeBoolean(row, targetCol, val.boolean, std::nullopt);
191
+ break;
192
+ default:
193
+ break;
194
+ }
195
+
196
+ if (parser.peek() == ',') ++parser.p;
197
+ }
198
+ parser.expect('}');
199
+ ++row;
200
+ }
201
+
202
+ parser.expect(']');
203
+ return workbook;
204
+ }
205
+
29
206
  }
@@ -14,6 +14,7 @@ public:
14
14
  std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook(const std::optional<std::string>& tempDir = std::nullopt) override;
15
15
  std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbook(const std::string& path) override;
16
16
  std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbookFromBuffer(const std::shared_ptr<ArrayBuffer>& buffer) override;
17
+ std::shared_ptr<HybridXlsxWorkbookSpec> fromJSON(const std::string& data) override;
17
18
  };
18
19
 
19
20
  }
@@ -4,12 +4,43 @@
4
4
  #include <sstream>
5
5
  #include <cstdlib>
6
6
  #include <unistd.h>
7
+ #include <cmath>
8
+ #include <cstring>
9
+ #include <iomanip>
7
10
 
8
11
  #ifdef __ANDROID__
9
12
  #include <android/api-level.h>
10
13
  #include <sys/stat.h>
11
14
  #endif
12
15
 
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
+
13
44
  namespace margelo::nitro::xlsx {
14
45
 
15
46
  namespace {
@@ -186,4 +217,85 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
186
217
  return promise;
187
218
  }
188
219
 
220
+ std::string HybridXlsxWorkbook::toJSON(const std::optional<std::vector<std::string>>& keys) {
221
+ if (_worksheets.empty()) {
222
+ return "[]";
223
+ }
224
+ auto& ws = _worksheets[0];
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();
299
+ }
300
+
189
301
  }
@@ -26,6 +26,7 @@ public:
26
26
  double getWorksheetCount() override;
27
27
  std::shared_ptr<HybridXlsxCellFormatSpec> addCellFormat() override;
28
28
  std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> getBuffer() override;
29
+ std::string toJSON(const std::optional<std::vector<std::string>>& keys) override;
29
30
 
30
31
  private:
31
32
  std::unique_ptr<OpenXLSX::XLDocument> _workbook;
@@ -75,6 +75,7 @@ void HybridXlsxWorksheet::writeFormulaBoolean(double row, double col, const std:
75
75
  void HybridXlsxWorksheet::writeArrayFormula(double firstRow, double firstCol, double lastRow, double lastCol, const std::string& formula, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
76
76
  OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
77
77
  cell.formula() = formula;
78
+ applyFormat(cell, format);
78
79
  }
79
80
 
80
81
  void HybridXlsxWorksheet::writeDatetime(double row, double col, double datetime, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
@@ -93,10 +94,33 @@ void HybridXlsxWorksheet::setColumn(double firstCol, double lastCol, double widt
93
94
  for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
94
95
  _worksheet.column(c).setWidth(static_cast<float>(width));
95
96
  }
97
+ if (format.has_value()) {
98
+ // Apply format to entire column range by setting it on cells in the first row
99
+ // (OpenXLSX does not support column-level style; apply to first row cells as a hint)
100
+ auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
101
+ if (hybridFormat) {
102
+ auto styleIndex = hybridFormat->applyToDocument(_doc.styles());
103
+ for (unsigned int c = static_cast<unsigned int>(firstCol); c <= static_cast<unsigned int>(lastCol); ++c) {
104
+ OpenXLSX::XLCell cell = _worksheet.cell(1, c);
105
+ hybridFormat->applyToCell(cell, styleIndex);
106
+ }
107
+ }
108
+ }
96
109
  }
97
110
 
98
111
  void HybridXlsxWorksheet::setRow(double row, double height, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
99
112
  _worksheet.row(static_cast<unsigned int>(row)).setHeight(static_cast<float>(height));
113
+ if (format.has_value()) {
114
+ auto hybridFormat = std::dynamic_pointer_cast<HybridXlsxCellFormat>(*format);
115
+ if (hybridFormat) {
116
+ auto styleIndex = hybridFormat->applyToDocument(_doc.styles());
117
+ unsigned int colCount = _worksheet.columnCount();
118
+ for (unsigned int c = 1; c <= colCount; ++c) {
119
+ OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(row), c);
120
+ hybridFormat->applyToCell(cell, styleIndex);
121
+ }
122
+ }
123
+ }
100
124
  }
101
125
 
102
126
  void HybridXlsxWorksheet::mergeRange(double firstRow, double firstCol, double lastRow, double lastCol, const std::string& value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
@@ -0,0 +1,2 @@
1
+ // This file exists solely so that SwiftPM has at least one source file
2
+ // for the NitroXlsx target. The actual build is done via CocoaPods.
@@ -17,6 +17,7 @@ namespace margelo::nitro::xlsx {
17
17
  prototype.registerHybridMethod("createWorkbook", &HybridNitroXlsxSpec::createWorkbook);
18
18
  prototype.registerHybridMethod("openWorkbook", &HybridNitroXlsxSpec::openWorkbook);
19
19
  prototype.registerHybridMethod("openWorkbookFromBuffer", &HybridNitroXlsxSpec::openWorkbookFromBuffer);
20
+ prototype.registerHybridMethod("fromJSON", &HybridNitroXlsxSpec::fromJSON);
20
21
  });
21
22
  }
22
23
 
@@ -56,6 +56,7 @@ namespace margelo::nitro::xlsx {
56
56
  virtual std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook(const std::optional<std::string>& tempDir) = 0;
57
57
  virtual std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbook(const std::string& path) = 0;
58
58
  virtual std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbookFromBuffer(const std::shared_ptr<ArrayBuffer>& buffer) = 0;
59
+ virtual std::shared_ptr<HybridXlsxWorkbookSpec> fromJSON(const std::string& data) = 0;
59
60
 
60
61
  protected:
61
62
  // Hybrid Setup
@@ -21,6 +21,7 @@ namespace margelo::nitro::xlsx {
21
21
  prototype.registerHybridMethod("getWorksheetCount", &HybridXlsxWorkbookSpec::getWorksheetCount);
22
22
  prototype.registerHybridMethod("addCellFormat", &HybridXlsxWorkbookSpec::addCellFormat);
23
23
  prototype.registerHybridMethod("getBuffer", &HybridXlsxWorkbookSpec::getBuffer);
24
+ prototype.registerHybridMethod("toJSON", &HybridXlsxWorkbookSpec::toJSON);
24
25
  });
25
26
  }
26
27
 
@@ -25,6 +25,7 @@ namespace margelo::nitro::xlsx { class HybridXlsxCellFormatSpec; }
25
25
  #include "HybridXlsxCellFormatSpec.hpp"
26
26
  #include <NitroModules/ArrayBuffer.hpp>
27
27
  #include <NitroModules/Promise.hpp>
28
+ #include <vector>
28
29
 
29
30
  namespace margelo::nitro::xlsx {
30
31
 
@@ -64,6 +65,7 @@ namespace margelo::nitro::xlsx {
64
65
  virtual double getWorksheetCount() = 0;
65
66
  virtual std::shared_ptr<HybridXlsxCellFormatSpec> addCellFormat() = 0;
66
67
  virtual std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> getBuffer() = 0;
68
+ virtual std::string toJSON(const std::optional<std::vector<std::string>>& keys) = 0;
67
69
 
68
70
  protected:
69
71
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-xlsx",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",
@@ -10,6 +10,7 @@
10
10
  "types": "src/index.ts",
11
11
  "source": "src/index.ts",
12
12
  "scripts": {
13
+ "postinstall": "node scripts/prepare-openxlsx.js",
13
14
  "specs": "nitrogen",
14
15
  "test": "jest",
15
16
  "test:cpp": "cd cpp/tests && [ -n \"$OPENXLSX_PATH\" ] || OPENXLSX_PATH=$HOME/OpenXLSX; cmake -B build -DOPENXLSX_PATH=$OPENXLSX_PATH && cmake --build build && cd build && ctest --output-on-failure",
@@ -36,6 +37,8 @@
36
37
  "!cpp/tests",
37
38
  "android",
38
39
  "ios",
40
+ "scripts",
41
+ "Package.swift",
39
42
  "nitro.json",
40
43
  "nitrogen",
41
44
  "NitroXlsx.podspec",
@@ -0,0 +1,109 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, mkdirSync, writeFileSync, symlinkSync, readFileSync } from 'fs';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const rootDir = dirname(__dirname);
9
+
10
+ function run(command, options = {}) {
11
+ console.log(`> ${command}`);
12
+ execSync(command, { stdio: 'inherit', ...options });
13
+ }
14
+
15
+ function ensureDir(dir) {
16
+ if (!existsSync(dir)) {
17
+ mkdirSync(dir, { recursive: true });
18
+ }
19
+ }
20
+
21
+ function cloneRepo(url, tag, dest) {
22
+ if (existsSync(dest)) {
23
+ console.log(`Already exists: ${dest}`);
24
+ return;
25
+ }
26
+ run(`git clone --depth 1 --branch ${tag} ${url} ${dest}`);
27
+ }
28
+
29
+ function writeFileIfNotExists(path, content) {
30
+ if (existsSync(path)) {
31
+ console.log(`Already exists: ${path}`);
32
+ return;
33
+ }
34
+ writeFileSync(path, content);
35
+ console.log(`Created: ${path}`);
36
+ }
37
+
38
+ function createSymlinkIfNotExists(target, link) {
39
+ if (existsSync(link)) {
40
+ console.log(`Already exists: ${link}`);
41
+ return;
42
+ }
43
+ symlinkSync(target, link);
44
+ console.log(`Created symlink: ${link} -> ${target}`);
45
+ }
46
+
47
+ function getPackageVersion() {
48
+ const packageJsonPath = join(rootDir, 'package.json');
49
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
50
+ return packageJson.version || '0.1.0';
51
+ }
52
+
53
+ const openxlsxDir = join(rootDir, 'OpenXLSX');
54
+ const thirdPartyDir = join(openxlsxDir, 'ThirdParty');
55
+ const pugixmlDir = join(thirdPartyDir, 'pugixml');
56
+ const minizDir = join(thirdPartyDir, 'miniz');
57
+
58
+ // Clone OpenXLSX and dependencies
59
+ cloneRepo('https://github.com/troldal/OpenXLSX.git', 'v0.5.1', openxlsxDir);
60
+ ensureDir(thirdPartyDir);
61
+ cloneRepo('https://github.com/zeux/pugixml.git', 'v1.16', pugixmlDir);
62
+ cloneRepo('https://github.com/richgel999/miniz.git', '3.1.2', minizDir);
63
+
64
+ // Generate miniz_export.h
65
+ const minizExportHeader = `#ifndef MINIZ_EXPORT_H
66
+ #define MINIZ_EXPORT_H
67
+
68
+ #ifndef MINIZ_EXPORT
69
+ #define MINIZ_EXPORT
70
+ #endif
71
+
72
+ #ifndef MINIZ_DEPRECATED
73
+ #define MINIZ_DEPRECATED
74
+ #endif
75
+
76
+ #ifndef MINIZ_DEPRECATED_EXPORT
77
+ #define MINIZ_DEPRECATED_EXPORT MINIZ_EXPORT MINIZ_DEPRECATED
78
+ #endif
79
+
80
+ #ifndef MINIZ_NO_EXPORT
81
+ #define MINIZ_NO_EXPORT
82
+ #endif
83
+
84
+ #endif
85
+ `;
86
+ writeFileIfNotExists(join(minizDir, 'miniz_export.h'), minizExportHeader);
87
+
88
+ // Generate OpenXLSX-Exports.hpp
89
+ const openxlsxExportsHeader = `#ifndef OPENXLSX_EXPORTS_HPP
90
+ #define OPENXLSX_EXPORTS_HPP
91
+
92
+ #ifndef OPENXLSX_EXPORT
93
+ #define OPENXLSX_EXPORT
94
+ #endif
95
+
96
+ #ifndef OPENXLSX_HIDDEN
97
+ #define OPENXLSX_HIDDEN
98
+ #endif
99
+
100
+ #endif
101
+ `;
102
+ const headersDir = join(openxlsxDir, 'OpenXLSX', 'headers');
103
+ ensureDir(headersDir);
104
+ writeFileIfNotExists(join(headersDir, 'OpenXLSX-Exports.hpp'), openxlsxExportsHeader);
105
+
106
+ // Create symlink for #include <OpenXLSX/XXX.hpp> resolution
107
+ createSymlinkIfNotExists('.', join(headersDir, 'OpenXLSX'));
108
+
109
+ console.log(`OpenXLSX dependencies prepared successfully for react-native-nitro-xlsx v${getPackageVersion()}.`);
package/src/index.ts CHANGED
@@ -1,7 +1,19 @@
1
1
  import { NitroModules } from 'react-native-nitro-modules'
2
- import type { NitroXlsx as NitroXlsxSpec } from './specs/NitroXlsx.nitro'
2
+ import type { NitroXlsx as NitroXlsxSpec, XlsxWorkbook as XlsxWorkbookSpec } from './specs/NitroXlsx.nitro'
3
3
 
4
- export const NitroXlsx = NitroModules.createHybridObject<NitroXlsxSpec>('NitroXlsx')
4
+ const NitroXlsxBridge = NitroModules.createHybridObject<NitroXlsxSpec>('NitroXlsx')
5
+
6
+ type JsonRecord = Record<string, string | number>
7
+
8
+ export const NitroXlsx = Object.assign(NitroXlsxBridge, {
9
+ fromJSON(data: JsonRecord[]): XlsxWorkbookSpec {
10
+ return NitroXlsxBridge.fromJSON(JSON.stringify(data))
11
+ },
12
+ })
13
+
14
+ export function toJSON(workbook: XlsxWorkbookSpec, keys?: string[]): JsonRecord[] {
15
+ return JSON.parse(workbook.toJSON(keys))
16
+ }
5
17
 
6
18
  export type { XlsxWorkbook, XlsxWorksheet, XlsxCellFormat } from './specs/NitroXlsx.nitro'
7
19
 
@@ -10,6 +10,7 @@ export interface XlsxWorkbook extends HybridObject<{ ios: 'c++'; android: 'c++'
10
10
  getWorksheetCount(): number
11
11
  addCellFormat(): XlsxCellFormat
12
12
  getBuffer(): Promise<ArrayBuffer>
13
+ toJSON(keys?: string[]): string
13
14
  }
14
15
 
15
16
  export interface XlsxWorksheet extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
@@ -151,4 +152,5 @@ export interface NitroXlsx extends HybridObject<{ ios: 'c++'; android: 'c++' }>
151
152
  createWorkbook(tempDir?: string): XlsxWorkbook
152
153
  openWorkbook(path: string): XlsxWorkbook
153
154
  openWorkbookFromBuffer(buffer: ArrayBuffer): XlsxWorkbook
155
+ fromJSON(data: string): XlsxWorkbook
154
156
  }