react-native-nitro-xlsx 0.1.2 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/NOTICE +8 -35
  2. package/NitroXlsx.podspec +21 -57
  3. package/Package.swift +35 -0
  4. package/README.md +243 -36
  5. package/android/CMakeLists.txt +17 -23
  6. package/android/build.gradle +6 -0
  7. package/android/cpp-adapter.cpp +12 -0
  8. package/android/src/main/java/com/margelo/nitro/xlsx/NitroXlsxPackage.kt +9 -0
  9. package/cpp/HybridNitroXlsx.cpp +32 -9
  10. package/cpp/HybridNitroXlsx.hpp +2 -1
  11. package/cpp/HybridXlsxConditionalFormat.cpp +252 -0
  12. package/cpp/HybridXlsxConditionalFormat.hpp +62 -0
  13. package/cpp/HybridXlsxConditionalFormats.cpp +46 -0
  14. package/cpp/HybridXlsxConditionalFormats.hpp +28 -0
  15. package/cpp/HybridXlsxWorkbook.cpp +162 -87
  16. package/cpp/HybridXlsxWorkbook.hpp +7 -1
  17. package/cpp/HybridXlsxWorksheet.cpp +456 -61
  18. package/cpp/HybridXlsxWorksheet.hpp +73 -26
  19. package/cpp/XlsxCacheDir.cpp +76 -0
  20. package/cpp/XlsxCacheDir.hpp +27 -0
  21. package/cpp/XlsxError.hpp +68 -0
  22. package/cpp/XlsxJson.cpp +185 -0
  23. package/cpp/XlsxJson.hpp +36 -0
  24. package/ios/XlsxCacheDir.mm +60 -0
  25. package/nitrogen/generated/android/NitroXlsx+autolinking.cmake +2 -0
  26. package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.cpp +1 -0
  27. package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.hpp +5 -2
  28. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.cpp +54 -0
  29. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.hpp +95 -0
  30. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.cpp +24 -0
  31. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.hpp +68 -0
  32. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp +4 -0
  33. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +7 -0
  34. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp +47 -19
  35. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.hpp +52 -20
  36. package/package.json +5 -2
  37. package/scripts/prepare-openxlsx.js +126 -0
  38. package/scripts/run-cpp-tests.sh +14 -0
  39. package/src/__mocks__/react-native-nitro-modules.ts +1 -0
  40. package/src/__tests__/index.test.ts +76 -7
  41. package/src/index.ts +110 -9
  42. package/src/specs/NitroXlsx.nitro.ts +141 -42
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,12 +11,17 @@ 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}",
24
+ "ios/**/*.{h,hpp,mm}",
20
25
  "OpenXLSX/OpenXLSX/**/*.hpp",
21
26
  "OpenXLSX/OpenXLSX/**/*.cpp",
22
27
  "OpenXLSX/ThirdParty/pugixml/src/**/*.hpp",
@@ -49,61 +54,20 @@ Pod::Spec.new do |s|
49
54
  "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
55
  }
51
56
 
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
57
+ # Prepare OpenXLSX and its dependencies (pugixml, miniz) via a Node script
58
+ # so the same logic can be reused from both CocoaPods and Android CMake.
59
+ s.prepare_command = "node #{__dir__}/scripts/prepare-openxlsx.js"
60
+
61
+ # Swift Package Manager declaration for React Native 0.87+.
62
+ # This exposes NitroXlsx as a local Swift package; the heavy lifting for
63
+ # OpenXLSX is still done by the prepare command above.
64
+ if ENV['NITRO_XLSX_USE_SPM'] == '1' && defined?(SPM) && SPM.respond_to?(:dependency)
65
+ SPM.dependency(s,
66
+ url: File.join(__dir__, "."),
67
+ requirement: { kind: 'upToNextMajorVersion', minimumVersion: s.version.to_s },
68
+ products: ['NitroXlsx']
69
+ )
70
+ end
107
71
 
108
72
  load 'nitrogen/generated/ios/NitroXlsx+autolinking.rb'
109
73
  add_nitrogen_files(s)
package/Package.swift ADDED
@@ -0,0 +1,35 @@
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
+ // ios/XlsxCacheDir.mm is listed so the target has a real source file.
13
+
14
+ let package = Package(
15
+ name: "NitroXlsx",
16
+ platforms: [
17
+ .iOS(.v15)
18
+ ],
19
+ products: [
20
+ .library(
21
+ name: "NitroXlsx",
22
+ targets: ["NitroXlsx"]
23
+ )
24
+ ],
25
+ targets: [
26
+ .target(
27
+ name: "NitroXlsx",
28
+ path: "ios",
29
+ sources: ["XlsxCacheDir.mm"],
30
+ cxxSettings: [
31
+ .headerSearchPath("../cpp")
32
+ ]
33
+ )
34
+ ]
35
+ )
package/README.md CHANGED
@@ -6,12 +6,19 @@ A React Native module for reading and writing Excel XLSX files using [OpenXLSX](
6
6
 
7
7
  - 📊 Full Excel XLSX file creation support
8
8
  - 📖 Read existing XLSX files (from file path or buffer)
9
- - 📝 Formula support (with default values)
10
- - 📅 Date/DateTime support
11
- - 🖼️ Image insertion (from file path or buffer)
12
- - 🎨 Rich formatting (fonts, colors, borders, alignment)
13
- - 📄 Page setup (margins, orientation, headers/footers)
14
- - 🔒 Sheet protection
9
+ - 📝 Formula support (with default values) & array formulas
10
+ - 📅 Date / DateTime support
11
+ - 🔗 Hyperlinks (URLs)
12
+ - 🔄 JSON import / export (`fromJSON` / `toJSON`)
13
+ - 🎨 Rich formatting (fonts, colors, borders, alignment, number formats)
14
+ - 🔒 Sheet protection (password, objects/scenarios, fine-grained allow/deny)
15
+ - 📋 Cell comments (get / set / delete, multi-author)
16
+ - 🔤 Conditional formatting rules
17
+ - 🗂️ Sheet management (delete / rename / clone)
18
+ - ➕ Row/column delete, merge / unmerge
19
+ - 🧩 Hidden rows/columns
20
+ - 🔍 Read cells (value, type, format, formula) + findCell
21
+ - ⚠️ Structured native error codes mapped to JS
15
22
  - 📦 Export as ArrayBuffer - no file system required!
16
23
 
17
24
  ## Installation
@@ -22,6 +29,8 @@ npm install react-native-nitro-xlsx
22
29
  yarn add react-native-nitro-xlsx
23
30
  ```
24
31
 
32
+ > **Note**: Native dependencies (OpenXlsx) are downloaded from GitHub during the first build. Please make sure you have a stable network connection.
33
+
25
34
  ### iOS
26
35
 
27
36
  ```sh
@@ -122,13 +131,73 @@ const fontName = format.getFontName();
122
131
  const fontSize = format.getFontSize();
123
132
  ```
124
133
 
134
+ ### Working with JSON
135
+
136
+ Per-sheet conversion lives on `XlsxWorksheet`. Workbook-level conversion uses a
137
+ `Record` keyed by worksheet name.
138
+
139
+ #### Export a worksheet (`sheet.toJSON`)
140
+
141
+ Convert one worksheet's data to an array of records. By default, the first row is used as object keys.
142
+
143
+ ```typescript
144
+ const sheet = workbook.getWorksheetByName('Data');
145
+
146
+ // Use the first row as keys (default)
147
+ const rows = sheet.toJSON();
148
+ // Result: [{ "Name": "Alice", "Age": 30 }, { "Name": "Bob", "Age": 25 }]
149
+
150
+ // Provide custom keys
151
+ const rows2 = sheet.toJSON(['firstName', 'yearsOld']);
152
+ // Result: [{ "firstName": "Alice", "yearsOld": 30 }, ...]
153
+ ```
154
+
155
+ #### Import into a worksheet (`sheet.fromJSON`)
156
+
157
+ Write an array of records into an existing worksheet. The first record's keys become the header row.
158
+
159
+ ```typescript
160
+ const sheet = workbook.addWorksheet('Data');
161
+ sheet.fromJSON([
162
+ { Name: 'Alice', Age: 30, Active: true },
163
+ { Name: 'Bob', Age: 25, Active: false },
164
+ ]);
165
+ // Row 1 (header): Active | Age | Name (keys are sorted alphabetically)
166
+ // Row 2: true | 30 | Alice
167
+ // Row 3: false | 25 | Bob
168
+ ```
169
+
170
+ #### Export all sheets (`workbook.toJSON`)
171
+
172
+ Returns a `Record` keyed by worksheet name.
173
+
174
+ ```typescript
175
+ const all = workbook.toJSON();
176
+ // Result: { "Data": [{ Name: 'Alice', ... }], "Summary": [{ ... }] }
177
+ ```
178
+
179
+ #### Create a workbook from multiple sheets (`NitroXlsx.fromJSON`)
180
+
181
+ Each key becomes a worksheet.
182
+
183
+ ```typescript
184
+ const workbook = NitroXlsx.fromJSON({
185
+ Data: [{ Name: 'Alice', Age: 30 }],
186
+ Summary: [{ Total: 1 }],
187
+ });
188
+ // Creates worksheets "Data" and "Summary"
189
+
190
+ const buffer = await workbook.getBuffer();
191
+ ```
192
+
125
193
  ## API
126
194
 
127
195
  ### NitroXlsx
128
196
 
129
- - `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.
197
+ - `createWorkbook(): XlsxWorkbook` - Create a new workbook. The native side supplies the platform cache directory automatically (`Context.getCacheDir()` on Android, `NSCachesDirectory` on iOS).
130
198
  - `openWorkbook(path: string): XlsxWorkbook` - Open workbook from file path
131
199
  - `openWorkbookFromBuffer(buffer: ArrayBuffer): XlsxWorkbook` - Open workbook from buffer
200
+ - `fromJSON(data: Record<string, Array<Record<string, string | number | boolean | null>>>): XlsxWorkbook` - Create a workbook with one worksheet per key (native `AnyMap`s, no JSON round-trip)
132
201
 
133
202
  ### XlsxWorkbook
134
203
 
@@ -137,8 +206,12 @@ const fontSize = format.getFontSize();
137
206
  - `getWorksheetByName(name: string): XlsxWorksheet` - Get worksheet by name
138
207
  - `getOrAddWorksheet(name: string): XlsxWorksheet` - Get or add worksheet
139
208
  - `getWorksheetCount(): number` - Get number of worksheets
209
+ - `deleteSheet(name: string): void` - Delete a worksheet by name
210
+ - `updateSheetName(oldName: string, newName: string): void` - Rename a worksheet and rewrite formula references to it
211
+ - `clone(existingName: string, newName: string): XlsxWorksheet` - Copy a worksheet
140
212
  - `addCellFormat(): XlsxCellFormat` - Add a new cell format
141
213
  - `getBuffer(): Promise<ArrayBuffer>` - Generate and return the XLSX file as a buffer
214
+ - `toJSON(): Record<string, Array<Record<string, string | number | boolean | null>>>` - Export every worksheet as a `Record` keyed by sheet name
142
215
 
143
216
  ### XlsxWorksheet
144
217
 
@@ -164,7 +237,7 @@ const fontSize = format.getFontSize();
164
237
  #### Date/URL
165
238
  | Method | Description |
166
239
  |--------|-------------|
167
- | `writeDatetime(row, col, datetime, format?)` | Write a datetime (Excel serial date number) |
240
+ | `writeDatetime(row, col, datetime, format?)` | Write a datetime (1900-based Excel serial date number; see Cell Types) |
168
241
  | `writeURL(row, col, url, format?)` | Write a hyperlink |
169
242
 
170
243
  #### Column/Row
@@ -172,6 +245,8 @@ const fontSize = format.getFontSize();
172
245
  |--------|-------------|
173
246
  | `setColumn(firstCol, lastCol, width, format?)` | Set column properties |
174
247
  | `setRow(row, height, format?)` | Set row properties |
248
+ | `deleteRow(row)` | Delete a row (returns `true` if a row entry existed and was removed) |
249
+ | `deleteColumn(col)` | Delete a column and shift remaining columns left (returns `true` on success) |
175
250
  | `setColumnHidden(firstCol, lastCol, hidden)` | Hide/show columns |
176
251
  | `setRowHidden(row, hidden)` | Hide/show row |
177
252
 
@@ -180,38 +255,75 @@ const fontSize = format.getFontSize();
180
255
  |--------|-------------|
181
256
  | `mergeRange(firstRow, firstCol, lastRow, lastCol, value, format?)` | Merge cells with string value |
182
257
  | `mergeRangeNum(firstRow, firstCol, lastRow, lastCol, value, format?)` | Merge cells with number value |
258
+ | `unmergeCells(firstRow, firstCol, lastRow, lastCol)` | Unmerge a previously merged range |
183
259
 
184
- #### Image
260
+ #### Comments
185
261
  | Method | Description |
186
262
  |--------|-------------|
187
- | `insertImage(row, col, path, xOffset?, yOffset?, xScale?, yScale?)` | Insert image from file |
188
- | `insertImageBuffer(row, col, buffer, extension, xOffset?, yOffset?, xScale?, yScale?)` | Insert image from buffer |
263
+ | `setComment(row, col, text, authorId?)` | Set a cell comment (creates the comments part on first use) |
264
+ | `getComment(row, col)` | Get comment text (empty string if none) |
265
+ | `hasComment(row, col)` | Whether the cell has a comment |
266
+ | `deleteComment(row, col)` | Delete a cell comment (returns `true` if one existed) |
267
+ | `getCommentCount()` | Number of comments on this sheet |
268
+ | `addCommentAuthor(author)` | Register an author, returns its `authorId` |
269
+ | `getCommentAuthor(authorId)` | Get author name by id |
189
270
 
190
271
  #### View
272
+
191
273
  | Method | Description |
192
274
  |--------|-------------|
193
- | `freezePanes(row, col)` | Freeze panes |
194
- | `splitPanes(row, col)` | Split panes |
195
- | `setTabColor(color)` | Set tab color |
196
275
  | `hide()` | Hide worksheet |
197
276
  | `activate()` | Activate worksheet |
198
- | `setFirstSheet()` | Set as first sheet |
199
- | `protect(password?)` | Protect worksheet |
200
277
 
201
- #### Page Setup
278
+ #### Sheet Protection
202
279
  | Method | Description |
203
280
  |--------|-------------|
204
- | `setPortrait()` | Set portrait orientation |
205
- | `setLandscape()` | Set landscape orientation |
206
- | `setPaper(paperType)` | Set paper size |
207
- | `setHeader(header)` | Set header |
208
- | `setFooter(footer)` | Set footer |
209
- | `setPrintArea(firstRow, firstCol, lastRow, lastCol)` | Set print area |
210
- | `fitToPages(width, height)` | Fit to pages |
211
- | `setZoom(scale)` | Set zoom level |
212
- | `setGridlines(option)` | Set gridlines |
213
- | `centerHorizontally()` | Center horizontally |
214
- | `centerVertically()` | Center vertically |
281
+ | `protect(password?)` | Convenience: set optional password and enable sheet protection |
282
+ | `protectSheet(set?)` | Enable/disable sheet protection (`set` defaults to `true`) |
283
+ | `protectObjects(set?)` | Protect drawing objects |
284
+ | `protectScenarios(set?)` | Protect scenarios |
285
+ | `setPassword(password)` | Set password (hashed by Excel algorithm) |
286
+ | `setPasswordHash(hash)` | Set a pre-computed 4-digit hex password hash |
287
+ | `clearPassword()` | Remove the password attribute |
288
+ | `clearSheetProtection()` | Remove `<sheetProtection>` entirely |
289
+
290
+ Fine-grained permissions (only meaningful while `protectSheet` is on).
291
+ `allow*(set?)` defaults to `true`; `deny*()` is shorthand for `allow*(false)`.
292
+
293
+ | Allow | Deny | Effect when allowed |
294
+ |-------|------|---------------------|
295
+ | `allowInsertColumns(set?)` | `denyInsertColumns()` | User may insert columns |
296
+ | `allowInsertRows(set?)` | `denyInsertRows()` | User may insert rows |
297
+ | `allowDeleteColumns(set?)` | `denyDeleteColumns()` | User may delete columns |
298
+ | `allowDeleteRows(set?)` | `denyDeleteRows()` | User may delete rows |
299
+ | `allowSelectLockedCells(set?)` | `denySelectLockedCells()` | User may select locked cells |
300
+ | `allowSelectUnlockedCells(set?)` | `denySelectUnlockedCells()` | User may select unlocked cells |
301
+
302
+ Protection state getters:
303
+
304
+ | Method | Returns |
305
+ |--------|---------|
306
+ | `sheetProtected()` / `objectsProtected()` / `scenariosProtected()` | Whether the corresponding protection is on |
307
+ | `insertColumnsAllowed()` / `insertRowsAllowed()` | Whether insert is allowed despite protection |
308
+ | `deleteColumnsAllowed()` / `deleteRowsAllowed()` | Whether delete is allowed despite protection |
309
+ | `selectLockedCellsAllowed()` / `selectUnlockedCellsAllowed()` | Whether select is allowed despite protection |
310
+ | `passwordIsSet()` | Whether a password hash is present |
311
+ | `passwordHash()` | The stored password hash (empty if none) |
312
+ | `sheetProtectionSummary()` | Human-readable summary of the protection settings |
313
+
314
+ Example:
315
+
316
+ ```typescript
317
+ const sheet = workbook.addWorksheet('Locked');
318
+ sheet.writeString(1, 1, 'Protected');
319
+ sheet.setPassword('s3cret');
320
+ sheet.protectSheet();
321
+ sheet.allowDeleteRows(); // users may still delete rows
322
+ sheet.denySelectLockedCells(); // users cannot select locked cells
323
+ console.log(sheet.sheetProtected()); // true
324
+ console.log(sheet.deleteRowsAllowed()); // true
325
+ console.log(sheet.selectLockedCellsAllowed()); // false
326
+ ```
215
327
 
216
328
  #### Read Methods
217
329
  | Method | Description |
@@ -221,11 +333,62 @@ const fontSize = format.getFontSize();
221
333
  | `getCellRawValue(row, col)` | Get raw cell value |
222
334
  | `getCellType(row, col)` | Get cell type |
223
335
  | `getCellFormat(row, col)` | Get cell format |
336
+ | `hasFormula(row, col)` | Whether the cell contains a formula |
337
+ | `formula(row, col)` | Get the cell's formula string (empty if none) |
338
+ | `findCell(row, col)` | Whether a cell exists at these coordinates (does **not** create it) |
224
339
  | `getRowCount()` | Get total row count |
225
340
  | `getColumnCount()` | Get total column count |
226
341
  | `getLastRow()` | Get last row index |
227
342
  | `getLastColumn()` | Get last column index |
228
343
  | `getName()` | Get worksheet name |
344
+ | `toJSON(keys?)` | Export this worksheet as an array of records (`keys` defaults to first-row headers) |
345
+ | `fromJSON(data)` | Write an array of records into this worksheet (first row = header) |
346
+
347
+ #### Conditional Formatting
348
+ | Method | Description |
349
+ |--------|-------------|
350
+ | `getConditionalFormats()` | Get the worksheet's `XlsxConditionalFormats` collection |
351
+
352
+ ##### XlsxConditionalFormats
353
+ | Method | Description |
354
+ |--------|-------------|
355
+ | `getCount()` | Number of `<conditionalFormatting>` entries |
356
+ | `create(sqref)` | Create a new entry for a range (e.g. `"A1:A10"`), returns `XlsxConditionalFormat` |
357
+ | `getByIndex(index)` | Get an entry by index |
358
+ | `summary()` | Debug summary string |
359
+
360
+ ##### XlsxConditionalFormat
361
+ | Method | Description |
362
+ |--------|-------------|
363
+ | `getSqref()` / `setSqref(sqref)` | The range these rules apply to |
364
+ | `getRuleCount()` / `createRule()` | Manage `cfRule` entries (`createRule` returns the new index) |
365
+ | `getRuleType(index)` / `setRuleType(index, type)` | Rule type — use `CfType` constants |
366
+ | `getRuleDxfId(index)` / `setRuleDxfId(index, dxfId)` | Differential format id |
367
+ | `getRuleFormula(index, formulaIndex)` / `setRuleFormula(index, formulaIndex, formula)` | Rule formula (`formulaIndex` must be `0`; OpenXLSX supports one formula per rule) |
368
+ | `getRuleOperator(index)` / `setRuleOperator(index, op)` | Comparison operator — use `CfOperator` |
369
+ | `getRuleText(index)` / `setRuleText(index, text)` | Text for text-based rules |
370
+ | `getRulePriority(index)` / `setRulePriority(index, priority)` | Rule priority (1-based) |
371
+ | `getRuleStopIfTrue(index)` / `setRuleStopIfTrue(index, stop)` | Stop evaluating further rules |
372
+ | `getRuleTimePeriod(index)` / `setRuleTimePeriod(index, period)` | Time period — use `CfTimePeriod` |
373
+ | `getRuleRank(index)` / `setRuleRank(index, rank)` | Top/bottom N |
374
+ | `getRuleStdDev(index)` / `setRuleStdDev(index, stdDev)` | Standard deviations for aboveAverage |
375
+ | `getRuleAboveAverage` / `setRuleAboveAverage` | aboveAverage flag |
376
+ | `getRulePercent` / `setRulePercent` | percent flag |
377
+ | `getRuleBottom` / `setRuleBottom` | bottom flag |
378
+ | `getRuleEqualAverage` / `setRuleEqualAverage` | equalAverage flag |
379
+ | `summary()` | Debug summary string |
380
+
381
+ Example:
382
+
383
+ ```typescript
384
+ const cf = sheet.getConditionalFormats().create('B2:B100');
385
+ const ruleIndex = cf.createRule();
386
+ cf.setRuleType(ruleIndex, CfType.CELL_IS);
387
+ cf.setRuleOperator(ruleIndex, CfOperator.GREATER_THAN);
388
+ cf.setRuleFormula(ruleIndex, 0, '100');
389
+ cf.setRuleDxfId(ruleIndex, 0);
390
+ cf.setRulePriority(ruleIndex, 1);
391
+ ```
229
392
 
230
393
  ### XlsxCellFormat
231
394
 
@@ -275,9 +438,14 @@ When reading cells, the following types are returned:
275
438
 
276
439
  - `empty` - Empty cell
277
440
  - `string` - Text string
278
- - `number` - Numeric value
441
+ - `number` - Numeric value. Dates are stored as **Excel serial numbers in the 1900 date system** (days since `1900-01-01`, where `1900-01-01` = `1`, including the spurious `1900-02-29`), and are read back as `number`:
442
+ ```ts
443
+ // Excel serial (1900-based) → JS Date
444
+ const date = new Date(Math.round((serial - 25569) * 86400000));
445
+ // JS Date → Excel serial (1900-based)
446
+ const serial = date.getTime() / 86400000 + 25569;
447
+ ```
279
448
  - `boolean` - Boolean value
280
- - `date` - Date/time value
281
449
  - `error` - Error value
282
450
  - `formula` - Formula
283
451
  - `blank` - Blank cell
@@ -296,7 +464,43 @@ The library exports several useful constants:
296
464
  - `Gridlines` - Gridline options
297
465
  - `Colors` - Common RGB colors
298
466
  - `NumFormat` - Built-in number format indices
299
- - `ImagePosition` - Image positioning options
467
+ - `CfType` - Conditional formatting rule types
468
+ - `CfOperator` - Conditional formatting comparison operators
469
+ - `CfTimePeriod` - Conditional formatting time periods
470
+ - `XlsxErrorCode` - Native error codes
471
+
472
+ ## Error Handling
473
+
474
+ Native failures are thrown as JS `Error`s whose message is `methodName: CODE: detail`.
475
+ Convert them to a structured `XlsxError` with `XlsxError.from`:
476
+
477
+ ```typescript
478
+ import { NitroXlsx, XlsxError, XlsxErrorCode } from 'react-native-nitro-xlsx';
479
+
480
+ try {
481
+ workbook.deleteSheet('Missing');
482
+ } catch (e) {
483
+ const err = XlsxError.from(e);
484
+ if (err.code === XlsxErrorCode.SHEET_NOT_FOUND) {
485
+ // handle missing sheet
486
+ }
487
+ }
488
+ ```
489
+
490
+ Error codes:
491
+
492
+ | Code | Meaning |
493
+ |------|---------|
494
+ | `INVALID_ARGUMENT` | Bad row/col/name or other input |
495
+ | `SHEET_NOT_FOUND` | Worksheet does not exist |
496
+ | `SHEET_EXISTS` | Worksheet name already taken |
497
+ | `INDEX_OUT_OF_RANGE` | Row/col/index out of range |
498
+ | `CELL_NOT_FOUND` | Referenced cell does not exist |
499
+ | `WORKBOOK_CLOSED` | Workbook already finalized via `getBuffer()` |
500
+ | `IO_ERROR` | File open/save failure |
501
+ | `UNSUPPORTED` | Operation not supported by the underlying engine |
502
+ | `XLSX_ERROR` | Generic OpenXLSX failure |
503
+ | `INTERNAL_ERROR` | Unexpected native error |
300
504
 
301
505
  ## Development
302
506
 
@@ -328,17 +532,20 @@ This library is based on OpenXLSX and inherits the following limitations:
328
532
 
329
533
  The following Excel features are not supported and will be ignored when reading or writing files:
330
534
 
331
- - 🖼️ **Images**: Reading and writing images is not supported. Existing images in XLSX files will be ignored.
332
535
  - 📊 **Charts**: Charts, graphs, and sparklines are not supported.
333
- - ✏️ **Drawings/Shape**: Shapes, lines, arrows, and other drawing objects are not supported.
536
+ - 🖼️ **Drawings/Images/Shapes**: Images, shapes, lines, arrows and other drawing objects fall under OpenXLSX's drawing support, which is not implemented. Existing images in XLSX files are silently ignored.
334
537
  - 🔍 **Pivot Tables**: Pivot tables and pivot charts are not supported.
335
538
  - 💻 **VBA/Macros**: VBA macros, forms, and ActiveX controls are not supported.
336
- - 📋 **Comments**: Cell comments and notes are not supported.
337
539
  - 📌 **Hyperlinks**: Only basic URL hyperlinks are supported.
338
- - 🔄 **Conditional Formatting**: Conditional formatting rules are not supported.
339
540
  - 📑 **Data Validation**: Data validation rules are not supported.
340
541
 
341
542
  ### Notes
342
543
 
343
544
  - When reading XLSX files that contain unsupported features, the library will still read the cell data and formatting, but unsupported elements will be silently ignored.
344
- - When writing XLSX files, unsupported features cannot be added and will be omitted from the output.
545
+ - When writing XLSX files, unsupported features cannot be added and will be omitted from the output.
546
+ - `deleteColumn` shifts cell values and formats left; formula references are not rewritten (same limitation as OpenXLSX's `deleteRow`).
547
+ - Conditional formatting supports one formula per `cfRule` (OpenXLSX limitation). `colorScale` / `dataBar` / `iconSet` are not supported.
548
+ - `deleteRule` on `XlsxConditionalFormat` always returns `false` (OpenXLSX does not support removing a `cfRule`).
549
+ - Conditional formatting rule `type` / `operator` / `timePeriod` are direct numeric mappings of OpenXLSX enums; passing an out-of-range value results in undefined behavior. Use the exported `CfType` / `CfOperator` / `CfTimePeriod` constants.
550
+ - `setComment` automatically registers an author so the generated `comments.xml` always has a valid `authorId` (verified to open cleanly in Excel/WPS).
551
+ - `getComment` returns an empty string for a cell without a comment (it does not throw). Use `hasComment` first if you need to distinguish.
@@ -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)
@@ -57,6 +47,10 @@ add_library(
57
47
  ../cpp/HybridXlsxWorkbook.cpp
58
48
  ../cpp/HybridXlsxWorksheet.cpp
59
49
  ../cpp/HybridXlsxCellFormat.cpp
50
+ ../cpp/HybridXlsxConditionalFormat.cpp
51
+ ../cpp/HybridXlsxConditionalFormats.cpp
52
+ ../cpp/XlsxJson.cpp
53
+ ../cpp/XlsxCacheDir.cpp
60
54
  cpp-adapter.cpp
61
55
  )
62
56
 
@@ -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
  }