react-native-nitro-xlsx 0.1.1 → 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 +51 -17
- package/NitroXlsx.podspec +20 -57
- package/Package.swift +31 -0
- package/README.md +65 -16
- package/android/CMakeLists.txt +23 -10
- package/android/build.gradle +6 -0
- package/android/src/main/java/com/margelo/nitro/xlsx/NitroXlsxPackage.kt +1 -0
- package/cpp/HybridNitroXlsx.cpp +179 -2
- package/cpp/HybridNitroXlsx.hpp +2 -1
- package/cpp/HybridXlsxCellFormat.cpp +45 -20
- package/cpp/HybridXlsxCellFormat.hpp +1 -0
- package/cpp/HybridXlsxWorkbook.cpp +120 -3
- package/cpp/HybridXlsxWorkbook.hpp +2 -1
- package/cpp/HybridXlsxWorksheet.cpp +30 -2
- package/ios/dummy.swift +2 -0
- package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.hpp +3 -1
- package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +2 -0
- package/package.json +4 -1
- package/scripts/prepare-openxlsx.js +109 -0
- package/src/index.ts +14 -2
- package/src/specs/NitroXlsx.nitro.ts +3 -1
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
|
-
|
|
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,33 +28,66 @@ 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
|
|
|
34
|
+
Full MIT License text:
|
|
35
|
+
|
|
36
|
+
MIT License
|
|
37
|
+
|
|
38
|
+
Copyright (c) 2024 Marc Rousavy
|
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
40
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
41
|
+
in the Software without restriction, including without limitation the rights
|
|
42
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
43
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
44
|
+
furnished to do so, subject to the following conditions:
|
|
45
|
+
|
|
46
|
+
The above copyright notice and this permission notice shall be included in all
|
|
47
|
+
copies or substantial portions of the Software.
|
|
48
|
+
|
|
49
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
50
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
51
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
52
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
53
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
54
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
55
|
+
SOFTWARE.
|
|
56
|
+
|
|
33
57
|
================================================================================
|
|
34
58
|
3. pugixml (OpenXLSX dependency)
|
|
35
59
|
Repository: https://github.com/zeux/pugixml
|
|
36
|
-
|
|
60
|
+
Version: 1.16
|
|
61
|
+
License: MIT License (SPDX: MIT)
|
|
37
62
|
Copyright (c) 2006-2024 Arseny Kapoulkine
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Copyright (c)
|
|
64
|
+
Full MIT License text:
|
|
65
|
+
|
|
66
|
+
MIT License
|
|
67
|
+
|
|
68
|
+
Copyright (c) 2006-2026 Arseny Kapoulkine
|
|
69
|
+
|
|
70
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
71
|
+
|
|
72
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
73
|
+
|
|
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.
|
|
44
75
|
|
|
45
76
|
================================================================================
|
|
46
|
-
|
|
77
|
+
4. miniz (OpenXLSX dependency)
|
|
47
78
|
Repository: https://github.com/richgel999/miniz
|
|
48
|
-
|
|
79
|
+
Version: 3.1.2
|
|
80
|
+
License: MIT License (SPDX: MIT)
|
|
49
81
|
Copyright (c) 2013-2014 RAD Game Tools and Valve Software
|
|
50
82
|
Copyright (c) 2010-2014 Rich Geldreich and Tenacious Software LLC
|
|
51
83
|
|
|
52
|
-
Full
|
|
84
|
+
Full MIT License text:
|
|
53
85
|
|
|
54
|
-
|
|
86
|
+
Copyright 2013-2014 RAD Game Tools and Valve Software
|
|
87
|
+
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
|
|
88
|
+
|
|
89
|
+
All Rights Reserved.
|
|
55
90
|
|
|
56
|
-
Copyright (c) 2024 Marc Rousavy
|
|
57
91
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
58
92
|
of this software and associated documentation files (the "Software"), to deal
|
|
59
93
|
in the Software without restriction, including without limitation the rights
|
|
@@ -61,13 +95,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
61
95
|
copies of the Software, and to permit persons to whom the Software is
|
|
62
96
|
furnished to do so, subject to the following conditions:
|
|
63
97
|
|
|
64
|
-
The above copyright notice and this permission notice shall be included in
|
|
65
|
-
copies or substantial portions of the Software.
|
|
98
|
+
The above copyright notice and this permission notice shall be included in
|
|
99
|
+
all copies or substantial portions of the Software.
|
|
66
100
|
|
|
67
101
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
68
102
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
69
103
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
70
104
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
71
105
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
72
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
73
|
-
SOFTWARE.
|
|
106
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
107
|
+
THE SOFTWARE.
|
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 =
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
@@ -34,6 +36,8 @@ No additional setup needed.
|
|
|
34
36
|
|
|
35
37
|
## Usage
|
|
36
38
|
|
|
39
|
+
> **Note**: Row and column indices are **1-based** (following Excel convention).
|
|
40
|
+
|
|
37
41
|
### Creating a Workbook
|
|
38
42
|
|
|
39
43
|
```typescript
|
|
@@ -53,27 +57,27 @@ headerFormat.setFgColor(Colors.BLUE);
|
|
|
53
57
|
headerFormat.setAlign(Align.CENTER);
|
|
54
58
|
|
|
55
59
|
// Write headers
|
|
56
|
-
sheet.writeString(
|
|
57
|
-
sheet.writeString(
|
|
58
|
-
sheet.writeString(
|
|
60
|
+
sheet.writeString(1, 1, 'Name', headerFormat);
|
|
61
|
+
sheet.writeString(1, 2, 'Price', headerFormat);
|
|
62
|
+
sheet.writeString(1, 3, 'Total', headerFormat);
|
|
59
63
|
|
|
60
64
|
// Write data
|
|
61
|
-
sheet.writeString(
|
|
62
|
-
sheet.writeNumber(
|
|
63
|
-
sheet.writeFormula(
|
|
65
|
+
sheet.writeString(2, 1, 'Apple');
|
|
66
|
+
sheet.writeNumber(2, 2, 1.50);
|
|
67
|
+
sheet.writeFormula(2, 3, '=B2*1.1');
|
|
64
68
|
|
|
65
|
-
sheet.writeString(
|
|
66
|
-
sheet.writeNumber(
|
|
67
|
-
sheet.writeFormula(
|
|
69
|
+
sheet.writeString(3, 1, 'Orange');
|
|
70
|
+
sheet.writeNumber(3, 2, 2.00);
|
|
71
|
+
sheet.writeFormula(3, 3, '=B3*1.1');
|
|
68
72
|
|
|
69
73
|
// Date example
|
|
70
74
|
const date = new Date(2024, 0, 15);
|
|
71
75
|
const excelDate = (date.getTime() / 86400000) + 25569; // Convert to Excel date
|
|
72
|
-
sheet.writeDatetime(
|
|
76
|
+
sheet.writeDatetime(4, 1, excelDate);
|
|
73
77
|
|
|
74
78
|
// Set column widths
|
|
75
|
-
sheet.setColumn(
|
|
76
|
-
sheet.setColumn(
|
|
79
|
+
sheet.setColumn(1, 1, 15);
|
|
80
|
+
sheet.setColumn(2, 3, 12);
|
|
77
81
|
|
|
78
82
|
// Get the buffer and do something with it
|
|
79
83
|
const buffer = await workbook.getBuffer();
|
|
@@ -104,8 +108,8 @@ const lastRow = sheet.getLastRow();
|
|
|
104
108
|
const lastCol = sheet.getLastColumn();
|
|
105
109
|
|
|
106
110
|
// Read cell values
|
|
107
|
-
for (let row =
|
|
108
|
-
for (let col =
|
|
111
|
+
for (let row = 1; row <= lastRow; row++) {
|
|
112
|
+
for (let col = 1; col <= lastCol; col++) {
|
|
109
113
|
const value = sheet.getCellValue(row, col);
|
|
110
114
|
const type = sheet.getCellType(row, col);
|
|
111
115
|
const strValue = sheet.getCellString(row, col);
|
|
@@ -114,19 +118,61 @@ for (let row = 0; row <= lastRow; row++) {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
// Read cell format
|
|
117
|
-
const format = sheet.getCellFormat(
|
|
121
|
+
const format = sheet.getCellFormat(1, 1);
|
|
118
122
|
const isBold = format.getIsBold();
|
|
119
123
|
const fontName = format.getFontName();
|
|
120
124
|
const fontSize = format.getFontSize();
|
|
121
125
|
```
|
|
122
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
|
+
|
|
123
168
|
## API
|
|
124
169
|
|
|
125
170
|
### NitroXlsx
|
|
126
171
|
|
|
127
|
-
- `createWorkbook(): XlsxWorkbook` - Create a new workbook
|
|
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.
|
|
128
173
|
- `openWorkbook(path: string): XlsxWorkbook` - Open workbook from file path
|
|
129
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)
|
|
130
176
|
|
|
131
177
|
### XlsxWorkbook
|
|
132
178
|
|
|
@@ -137,9 +183,12 @@ const fontSize = format.getFontSize();
|
|
|
137
183
|
- `getWorksheetCount(): number` - Get number of worksheets
|
|
138
184
|
- `addCellFormat(): XlsxCellFormat` - Add a new cell format
|
|
139
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
|
|
140
187
|
|
|
141
188
|
### XlsxWorksheet
|
|
142
189
|
|
|
190
|
+
> **Note**: All row and column indices are **1-based** (following Excel convention).
|
|
191
|
+
|
|
143
192
|
#### Basic Write Methods
|
|
144
193
|
| Method | Description |
|
|
145
194
|
|--------|-------------|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -5,18 +5,31 @@ 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
|
+
|
|
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
|
+
)
|
|
23
|
+
endif()
|
|
24
|
+
|
|
25
|
+
# Verify OpenXLSX is available
|
|
8
26
|
if(NOT EXISTS ${OPENXLSX_DIR}/CMakeLists.txt)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
OpenXLSX
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
message(FATAL_ERROR
|
|
28
|
+
"OpenXLSX not found at ${OPENXLSX_DIR}.\n"
|
|
29
|
+
"Please manually clone OpenXLSX:\n"
|
|
30
|
+
" cd ${CMAKE_SOURCE_DIR}/.. && git clone --depth 1 --branch v0.5.1 https://github.com/troldal/OpenXLSX.git OpenXLSX\n"
|
|
31
|
+
"Then clean CMake cache and rebuild."
|
|
14
32
|
)
|
|
15
|
-
FetchContent_GetProperties(OpenXLSX)
|
|
16
|
-
if(NOT OpenXLSX_POPULATED)
|
|
17
|
-
FetchContent_Populate(OpenXLSX)
|
|
18
|
-
endif()
|
|
19
|
-
set(OPENXLSX_DIR ${openxlsx_SOURCE_DIR})
|
|
20
33
|
endif()
|
|
21
34
|
|
|
22
35
|
set(OPENXLSX_BUILD_SAMPLES OFF CACHE BOOL "" FORCE)
|
package/android/build.gradle
CHANGED
|
@@ -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
|
}
|
package/cpp/HybridNitroXlsx.cpp
CHANGED
|
@@ -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
|
|
|
@@ -10,8 +103,8 @@ HybridNitroXlsx::HybridNitroXlsx()
|
|
|
10
103
|
HybridNitroXlsx::~HybridNitroXlsx() {
|
|
11
104
|
}
|
|
12
105
|
|
|
13
|
-
std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::createWorkbook() {
|
|
14
|
-
return std::make_shared<HybridXlsxWorkbook>();
|
|
106
|
+
std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::createWorkbook(const std::optional<std::string>& tempDir) {
|
|
107
|
+
return std::make_shared<HybridXlsxWorkbook>(tempDir);
|
|
15
108
|
}
|
|
16
109
|
|
|
17
110
|
std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::openWorkbook(const std::string& path) {
|
|
@@ -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
|
}
|
package/cpp/HybridNitroXlsx.hpp
CHANGED
|
@@ -11,9 +11,10 @@ public:
|
|
|
11
11
|
HybridNitroXlsx();
|
|
12
12
|
~HybridNitroXlsx() override;
|
|
13
13
|
|
|
14
|
-
std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook() override;
|
|
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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "HybridXlsxCellFormat.hpp"
|
|
2
2
|
#include <sstream>
|
|
3
3
|
#include <iomanip>
|
|
4
|
+
#include <cctype>
|
|
4
5
|
|
|
5
6
|
namespace margelo::nitro::xlsx {
|
|
6
7
|
|
|
@@ -10,7 +11,12 @@ static std::string doubleToHexColor(double color) {
|
|
|
10
11
|
uint32_t argb = c | 0xFF000000;
|
|
11
12
|
std::stringstream ss;
|
|
12
13
|
ss << std::hex << std::setfill('0') << std::setw(8) << argb;
|
|
13
|
-
|
|
14
|
+
std::string result = ss.str();
|
|
15
|
+
// Convert to uppercase (OpenXLSX expects uppercase hex)
|
|
16
|
+
for (char& ch : result) {
|
|
17
|
+
ch = std::toupper(ch);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
HybridXlsxCellFormat::HybridXlsxCellFormat()
|
|
@@ -21,11 +27,23 @@ HybridXlsxCellFormat::~HybridXlsxCellFormat() {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
OpenXLSX::XLStyleIndex HybridXlsxCellFormat::applyToDocument(OpenXLSX::XLStyles& styles) {
|
|
24
|
-
|
|
30
|
+
if (_styleIndex.has_value()) {
|
|
31
|
+
return *_styleIndex;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create cell format in document first to get a valid XML node.
|
|
35
|
+
// OpenXLSX default-constructed style objects have empty XML nodes;
|
|
36
|
+
// setters on empty nodes silently fail. We must create the node in
|
|
37
|
+
// the document first, then modify the live node via operator[].
|
|
38
|
+
OpenXLSX::XLCellFormat emptyCellFormat;
|
|
39
|
+
OpenXLSX::XLStyleIndex cellFormatIndex = styles.cellFormats().create(emptyCellFormat);
|
|
40
|
+
OpenXLSX::XLCellFormat cellFormat = styles.cellFormats()[cellFormatIndex];
|
|
25
41
|
|
|
26
42
|
// Font
|
|
27
43
|
if (!_fontName.empty() || _fontSize > 0 || _fontColor > 0 || _bold || _italic || _underline > 0 || _strikeout || _fontScript > 0) {
|
|
28
|
-
OpenXLSX::XLFont
|
|
44
|
+
OpenXLSX::XLFont emptyFont;
|
|
45
|
+
OpenXLSX::XLStyleIndex fontIndex = styles.fonts().create(emptyFont);
|
|
46
|
+
OpenXLSX::XLFont font = styles.fonts()[fontIndex];
|
|
29
47
|
if (!_fontName.empty()) font.setFontName(_fontName);
|
|
30
48
|
if (_fontSize > 0) font.setFontSize(static_cast<size_t>(_fontSize));
|
|
31
49
|
if (_fontColor > 0) font.setFontColor(OpenXLSX::XLColor(doubleToHexColor(_fontColor)));
|
|
@@ -34,49 +52,55 @@ OpenXLSX::XLStyleIndex HybridXlsxCellFormat::applyToDocument(OpenXLSX::XLStyles&
|
|
|
34
52
|
if (_underline > 0) font.setUnderline(static_cast<OpenXLSX::XLUnderlineStyle>(_underline));
|
|
35
53
|
if (_strikeout) font.setStrikethrough();
|
|
36
54
|
if (_fontScript > 0) font.setVertAlign(static_cast<OpenXLSX::XLVerticalAlignRunStyle>(_fontScript));
|
|
37
|
-
cellFormat.setFontIndex(
|
|
55
|
+
cellFormat.setFontIndex(fontIndex);
|
|
38
56
|
cellFormat.setApplyFont(true);
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
// Alignment
|
|
42
60
|
if (_textWrap || _rotation != 0 || _indent > 0 || _align > 0 || _verticalAlign > 0 || _shrink) {
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
48
|
-
if (
|
|
49
|
-
if (_shrink) alignment.setShrinkToFit();
|
|
61
|
+
if (_align > 0) cellFormat.alignment(true).setHorizontal(static_cast<OpenXLSX::XLAlignmentStyle>(_align));
|
|
62
|
+
if (_verticalAlign > 0) cellFormat.alignment(true).setVertical(static_cast<OpenXLSX::XLAlignmentStyle>(_verticalAlign));
|
|
63
|
+
if (_textWrap) cellFormat.alignment(true).setWrapText();
|
|
64
|
+
if (_rotation != 0) cellFormat.alignment(true).setTextRotation(static_cast<uint16_t>(_rotation));
|
|
65
|
+
if (_indent > 0) cellFormat.alignment(true).setIndent(static_cast<uint32_t>(_indent));
|
|
66
|
+
if (_shrink) cellFormat.alignment(true).setShrinkToFit();
|
|
50
67
|
cellFormat.setApplyAlignment(true);
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
// Number format
|
|
54
71
|
if (!_numFormat.empty()) {
|
|
55
|
-
OpenXLSX::XLNumberFormat
|
|
72
|
+
OpenXLSX::XLNumberFormat emptyNumFormat;
|
|
73
|
+
OpenXLSX::XLStyleIndex numFmtIndex = styles.numberFormats().create(emptyNumFormat);
|
|
74
|
+
OpenXLSX::XLNumberFormat numFormat = styles.numberFormats()[numFmtIndex];
|
|
56
75
|
numFormat.setFormatCode(_numFormat);
|
|
57
|
-
|
|
58
|
-
uint32_t
|
|
59
|
-
|
|
76
|
+
// Assign a unique custom numFmtId (>= 164 for user-defined formats)
|
|
77
|
+
static uint32_t nextCustomNumFmtId = 164;
|
|
78
|
+
numFormat.setNumberFormatId(nextCustomNumFmtId++);
|
|
79
|
+
cellFormat.setNumberFormatId(numFormat.numberFormatId());
|
|
60
80
|
cellFormat.setApplyNumberFormat(true);
|
|
61
81
|
}
|
|
62
82
|
|
|
63
83
|
// Fill
|
|
64
84
|
if (_bgColor > 0 || _fgColor > 0 || _pattern > 0) {
|
|
65
|
-
OpenXLSX::XLFill
|
|
85
|
+
OpenXLSX::XLFill emptyFill;
|
|
86
|
+
OpenXLSX::XLStyleIndex fillIndex = styles.fills().create(emptyFill);
|
|
87
|
+
OpenXLSX::XLFill fill = styles.fills()[fillIndex];
|
|
66
88
|
fill.setPatternType(_pattern > 0
|
|
67
89
|
? static_cast<OpenXLSX::XLPatternType>(_pattern)
|
|
68
90
|
: OpenXLSX::XLPatternType::XLPatternSolid);
|
|
69
91
|
if (_bgColor > 0) fill.setBackgroundColor(OpenXLSX::XLColor(doubleToHexColor(_bgColor)));
|
|
70
92
|
if (_fgColor > 0) fill.setColor(OpenXLSX::XLColor(doubleToHexColor(_fgColor)));
|
|
71
|
-
cellFormat.setFillIndex(
|
|
93
|
+
cellFormat.setFillIndex(fillIndex);
|
|
72
94
|
cellFormat.setApplyFill(true);
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
// Border
|
|
76
98
|
if (_border > 0 || _bottom > 0 || _top > 0 || _left > 0 || _right > 0 || _diagonal > 0) {
|
|
77
|
-
OpenXLSX::XLBorder
|
|
99
|
+
OpenXLSX::XLBorder emptyBorder;
|
|
100
|
+
OpenXLSX::XLStyleIndex borderIndex = styles.borders().create(emptyBorder);
|
|
101
|
+
OpenXLSX::XLBorder border = styles.borders()[borderIndex];
|
|
78
102
|
// TODO: Implement border styling with OpenXLSX v0.5.1 API
|
|
79
|
-
cellFormat.setBorderIndex(
|
|
103
|
+
cellFormat.setBorderIndex(borderIndex);
|
|
80
104
|
cellFormat.setApplyBorder(true);
|
|
81
105
|
}
|
|
82
106
|
|
|
@@ -86,7 +110,8 @@ OpenXLSX::XLStyleIndex HybridXlsxCellFormat::applyToDocument(OpenXLSX::XLStyles&
|
|
|
86
110
|
cellFormat.setApplyProtection(true);
|
|
87
111
|
}
|
|
88
112
|
|
|
89
|
-
|
|
113
|
+
_styleIndex = cellFormatIndex;
|
|
114
|
+
return cellFormatIndex;
|
|
90
115
|
}
|
|
91
116
|
|
|
92
117
|
void HybridXlsxCellFormat::applyToCell(OpenXLSX::XLCell& cell, OpenXLSX::XLStyleIndex styleIndex) {
|
|
@@ -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 {
|
|
@@ -40,10 +71,14 @@ namespace {
|
|
|
40
71
|
}
|
|
41
72
|
}
|
|
42
73
|
|
|
43
|
-
HybridXlsxWorkbook::HybridXlsxWorkbook()
|
|
74
|
+
HybridXlsxWorkbook::HybridXlsxWorkbook(const std::optional<std::string>& tempDir)
|
|
44
75
|
: HybridObject("XlsxWorkbook"), HybridXlsxWorkbookSpec(), _finalized(false) {
|
|
45
76
|
_workbook = std::make_unique<OpenXLSX::XLDocument>();
|
|
46
|
-
|
|
77
|
+
if (tempDir.has_value()) {
|
|
78
|
+
_tempDir = *tempDir;
|
|
79
|
+
} else {
|
|
80
|
+
_tempDir = getTempDir();
|
|
81
|
+
}
|
|
47
82
|
}
|
|
48
83
|
|
|
49
84
|
HybridXlsxWorkbook::~HybridXlsxWorkbook() {
|
|
@@ -93,7 +128,8 @@ std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::addWorksheet(const
|
|
|
93
128
|
sheetName = *name;
|
|
94
129
|
auto it = _worksheetNames.find(sheetName);
|
|
95
130
|
if (it != _worksheetNames.end()) {
|
|
96
|
-
|
|
131
|
+
// Worksheet already exists (e.g., default Sheet1 created by OpenXLSX), return it
|
|
132
|
+
return it->second;
|
|
97
133
|
}
|
|
98
134
|
} else {
|
|
99
135
|
int idx = 1;
|
|
@@ -181,4 +217,85 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
|
|
|
181
217
|
return promise;
|
|
182
218
|
}
|
|
183
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
|
+
|
|
184
301
|
}
|
|
@@ -13,7 +13,7 @@ namespace margelo::nitro::xlsx {
|
|
|
13
13
|
|
|
14
14
|
class HybridXlsxWorkbook : public HybridXlsxWorkbookSpec {
|
|
15
15
|
public:
|
|
16
|
-
HybridXlsxWorkbook();
|
|
16
|
+
HybridXlsxWorkbook(const std::optional<std::string>& tempDir = std::nullopt);
|
|
17
17
|
~HybridXlsxWorkbook() override;
|
|
18
18
|
|
|
19
19
|
void openFromFile(const std::string& path);
|
|
@@ -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) {
|
|
@@ -104,7 +128,9 @@ void HybridXlsxWorksheet::mergeRange(double firstRow, double firstCol, double la
|
|
|
104
128
|
OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
|
|
105
129
|
OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
|
|
106
130
|
_worksheet.mergeCells(range);
|
|
107
|
-
_worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol))
|
|
131
|
+
OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
132
|
+
cell.value() = value;
|
|
133
|
+
applyFormat(cell, format);
|
|
108
134
|
}
|
|
109
135
|
|
|
110
136
|
void HybridXlsxWorksheet::mergeRangeNum(double firstRow, double firstCol, double lastRow, double lastCol, double value, const std::optional<std::shared_ptr<HybridXlsxCellFormatSpec>>& format) {
|
|
@@ -112,7 +138,9 @@ void HybridXlsxWorksheet::mergeRangeNum(double firstRow, double firstCol, double
|
|
|
112
138
|
OpenXLSX::XLCellReference bottomRight(static_cast<unsigned int>(lastRow), static_cast<unsigned int>(lastCol));
|
|
113
139
|
OpenXLSX::XLCellRange range = _worksheet.range(topLeft, bottomRight);
|
|
114
140
|
_worksheet.mergeCells(range);
|
|
115
|
-
_worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol))
|
|
141
|
+
OpenXLSX::XLCell cell = _worksheet.cell(static_cast<unsigned int>(firstRow), static_cast<unsigned int>(firstCol));
|
|
142
|
+
cell.value() = value;
|
|
143
|
+
applyFormat(cell, format);
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
void HybridXlsxWorksheet::insertImage(double row, double col, const std::string& path, std::optional<double> xOffset, std::optional<double> yOffset, std::optional<double> xScale, std::optional<double> yScale) {
|
package/ios/dummy.swift
ADDED
|
@@ -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
|
|
|
@@ -19,6 +19,7 @@ namespace margelo::nitro::xlsx { class HybridXlsxWorkbookSpec; }
|
|
|
19
19
|
#include <memory>
|
|
20
20
|
#include "HybridXlsxWorkbookSpec.hpp"
|
|
21
21
|
#include <string>
|
|
22
|
+
#include <optional>
|
|
22
23
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
23
24
|
|
|
24
25
|
namespace margelo::nitro::xlsx {
|
|
@@ -52,9 +53,10 @@ namespace margelo::nitro::xlsx {
|
|
|
52
53
|
|
|
53
54
|
public:
|
|
54
55
|
// Methods
|
|
55
|
-
virtual std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook() = 0;
|
|
56
|
+
virtual std::shared_ptr<HybridXlsxWorkbookSpec> createWorkbook(const std::optional<std::string>& tempDir) = 0;
|
|
56
57
|
virtual std::shared_ptr<HybridXlsxWorkbookSpec> openWorkbook(const std::string& path) = 0;
|
|
57
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;
|
|
58
60
|
|
|
59
61
|
protected:
|
|
60
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.
|
|
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
|
-
|
|
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++' }> {
|
|
@@ -148,7 +149,8 @@ export interface XlsxCellFormat extends HybridObject<{ ios: 'c++'; android: 'c++
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
export interface NitroXlsx extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
|
|
151
|
-
createWorkbook(): XlsxWorkbook
|
|
152
|
+
createWorkbook(tempDir?: string): XlsxWorkbook
|
|
152
153
|
openWorkbook(path: string): XlsxWorkbook
|
|
153
154
|
openWorkbookFromBuffer(buffer: ArrayBuffer): XlsxWorkbook
|
|
155
|
+
fromJSON(data: string): XlsxWorkbook
|
|
154
156
|
}
|