react-native-nitro-xlsx 0.1.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/NitroXlsx.podspec +1 -0
  2. package/Package.swift +6 -2
  3. package/README.md +219 -57
  4. package/android/CMakeLists.txt +4 -0
  5. package/android/cpp-adapter.cpp +12 -0
  6. package/android/src/main/java/com/margelo/nitro/xlsx/NitroXlsxPackage.kt +9 -0
  7. package/cpp/HybridNitroXlsx.cpp +25 -179
  8. package/cpp/HybridNitroXlsx.hpp +2 -2
  9. package/cpp/HybridXlsxConditionalFormat.cpp +252 -0
  10. package/cpp/HybridXlsxConditionalFormat.hpp +62 -0
  11. package/cpp/HybridXlsxConditionalFormats.cpp +46 -0
  12. package/cpp/HybridXlsxConditionalFormats.hpp +28 -0
  13. package/cpp/HybridXlsxWorkbook.cpp +159 -196
  14. package/cpp/HybridXlsxWorkbook.hpp +7 -2
  15. package/cpp/HybridXlsxWorksheet.cpp +433 -62
  16. package/cpp/HybridXlsxWorksheet.hpp +73 -26
  17. package/cpp/XlsxCacheDir.cpp +76 -0
  18. package/cpp/XlsxCacheDir.hpp +27 -0
  19. package/cpp/XlsxError.hpp +68 -0
  20. package/cpp/XlsxJson.cpp +185 -0
  21. package/cpp/XlsxJson.hpp +36 -0
  22. package/ios/XlsxCacheDir.mm +60 -0
  23. package/nitrogen/generated/android/NitroXlsx+autolinking.cmake +2 -0
  24. package/nitrogen/generated/shared/c++/HybridNitroXlsxSpec.hpp +5 -3
  25. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.cpp +54 -0
  26. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatSpec.hpp +95 -0
  27. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.cpp +24 -0
  28. package/nitrogen/generated/shared/c++/HybridXlsxConditionalFormatsSpec.hpp +68 -0
  29. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.cpp +3 -0
  30. package/nitrogen/generated/shared/c++/HybridXlsxWorkbookSpec.hpp +6 -1
  31. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.cpp +47 -19
  32. package/nitrogen/generated/shared/c++/HybridXlsxWorksheetSpec.hpp +52 -20
  33. package/package.json +2 -2
  34. package/scripts/prepare-openxlsx.js +23 -6
  35. package/scripts/run-cpp-tests.sh +14 -0
  36. package/src/__mocks__/react-native-nitro-modules.ts +1 -0
  37. package/src/__tests__/index.test.ts +76 -7
  38. package/src/index.ts +107 -18
  39. package/src/specs/NitroXlsx.nitro.ts +141 -44
  40. package/ios/dummy.swift +0 -2
package/NitroXlsx.podspec CHANGED
@@ -21,6 +21,7 @@ Pod::Spec.new do |s|
21
21
 
22
22
  s.source_files = [
23
23
  "cpp/**/*.{h,hpp,cpp}",
24
+ "ios/**/*.{h,hpp,mm}",
24
25
  "OpenXLSX/OpenXLSX/**/*.hpp",
25
26
  "OpenXLSX/OpenXLSX/**/*.cpp",
26
27
  "OpenXLSX/ThirdParty/pugixml/src/**/*.hpp",
package/Package.swift CHANGED
@@ -8,7 +8,8 @@ import PackageDescription
8
8
  //
9
9
  // The local package reference below lets RN 0.87's spm_dependency helper
10
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.
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.
12
13
 
13
14
  let package = Package(
14
15
  name: "NitroXlsx",
@@ -25,7 +26,10 @@ let package = Package(
25
26
  .target(
26
27
  name: "NitroXlsx",
27
28
  path: "ios",
28
- sources: ["dummy.swift"]
29
+ sources: ["XlsxCacheDir.mm"],
30
+ cxxSettings: [
31
+ .headerSearchPath("../cpp")
32
+ ]
29
33
  )
30
34
  ]
31
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
@@ -126,41 +133,59 @@ const fontSize = format.getFontSize();
126
133
 
127
134
  ### Working with JSON
128
135
 
129
- #### Export to JSON (`toJSON`)
136
+ Per-sheet conversion lives on `XlsxWorksheet`. Workbook-level conversion uses a
137
+ `Record` keyed by worksheet name.
130
138
 
131
- Convert worksheet data to a JSON array. By default, the first row is used as object keys.
139
+ #### Export a worksheet (`sheet.toJSON`)
132
140
 
133
- ```typescript
134
- import { NitroXlsx, toJSON } from 'react-native-nitro-xlsx';
141
+ Convert one worksheet's data to an array of records. By default, the first row is used as object keys.
135
142
 
136
- const workbook = NitroXlsx.openWorkbook('/path/to/data.xlsx');
143
+ ```typescript
144
+ const sheet = workbook.getWorksheetByName('Data');
137
145
 
138
146
  // Use the first row as keys (default)
139
- const data = toJSON(workbook);
147
+ const rows = sheet.toJSON();
140
148
  // Result: [{ "Name": "Alice", "Age": 30 }, { "Name": "Bob", "Age": 25 }]
141
149
 
142
150
  // Provide custom keys
143
- const data = toJSON(workbook, ['firstName', 'yearsOld']);
144
- // Result: [{ "firstName": "Alice", "yearsOld": 30 }, { "firstName": "Bob", "yearsOld": 25 }]
151
+ const rows2 = sheet.toJSON(['firstName', 'yearsOld']);
152
+ // Result: [{ "firstName": "Alice", "yearsOld": 30 }, ...]
145
153
  ```
146
154
 
147
- #### Import from JSON (`fromJSON`)
155
+ #### Import into a worksheet (`sheet.fromJSON`)
148
156
 
149
- Create a workbook from a JSON array. The first object's keys become the header row.
157
+ Write an array of records into an existing worksheet. The first record's keys become the header row.
150
158
 
151
159
  ```typescript
152
- import { NitroXlsx } from 'react-native-nitro-xlsx';
153
-
154
- const data = [
160
+ const sheet = workbook.addWorksheet('Data');
161
+ sheet.fromJSON([
155
162
  { Name: 'Alice', Age: 30, Active: true },
156
163
  { Name: 'Bob', Age: 25, Active: false },
157
- ];
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
+ ```
158
178
 
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
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"
164
189
 
165
190
  const buffer = await workbook.getBuffer();
166
191
  ```
@@ -169,10 +194,10 @@ const buffer = await workbook.getBuffer();
169
194
 
170
195
  ### NitroXlsx
171
196
 
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.
197
+ - `createWorkbook(): XlsxWorkbook` - Create a new workbook. The native side supplies the platform cache directory automatically (`Context.getCacheDir()` on Android, `NSCachesDirectory` on iOS).
173
198
  - `openWorkbook(path: string): XlsxWorkbook` - Open workbook from file path
174
199
  - `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)
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)
176
201
 
177
202
  ### XlsxWorkbook
178
203
 
@@ -181,9 +206,12 @@ const buffer = await workbook.getBuffer();
181
206
  - `getWorksheetByName(name: string): XlsxWorksheet` - Get worksheet by name
182
207
  - `getOrAddWorksheet(name: string): XlsxWorksheet` - Get or add worksheet
183
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
184
212
  - `addCellFormat(): XlsxCellFormat` - Add a new cell format
185
213
  - `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
214
+ - `toJSON(): Record<string, Array<Record<string, string | number | boolean | null>>>` - Export every worksheet as a `Record` keyed by sheet name
187
215
 
188
216
  ### XlsxWorksheet
189
217
 
@@ -209,7 +237,7 @@ const buffer = await workbook.getBuffer();
209
237
  #### Date/URL
210
238
  | Method | Description |
211
239
  |--------|-------------|
212
- | `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) |
213
241
  | `writeURL(row, col, url, format?)` | Write a hyperlink |
214
242
 
215
243
  #### Column/Row
@@ -217,6 +245,8 @@ const buffer = await workbook.getBuffer();
217
245
  |--------|-------------|
218
246
  | `setColumn(firstCol, lastCol, width, format?)` | Set column properties |
219
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) |
220
250
  | `setColumnHidden(firstCol, lastCol, hidden)` | Hide/show columns |
221
251
  | `setRowHidden(row, hidden)` | Hide/show row |
222
252
 
@@ -225,38 +255,75 @@ const buffer = await workbook.getBuffer();
225
255
  |--------|-------------|
226
256
  | `mergeRange(firstRow, firstCol, lastRow, lastCol, value, format?)` | Merge cells with string value |
227
257
  | `mergeRangeNum(firstRow, firstCol, lastRow, lastCol, value, format?)` | Merge cells with number value |
258
+ | `unmergeCells(firstRow, firstCol, lastRow, lastCol)` | Unmerge a previously merged range |
228
259
 
229
- #### Image
260
+ #### Comments
230
261
  | Method | Description |
231
262
  |--------|-------------|
232
- | `insertImage(row, col, path, xOffset?, yOffset?, xScale?, yScale?)` | Insert image from file |
233
- | `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 |
234
270
 
235
271
  #### View
272
+
236
273
  | Method | Description |
237
274
  |--------|-------------|
238
- | `freezePanes(row, col)` | Freeze panes |
239
- | `splitPanes(row, col)` | Split panes |
240
- | `setTabColor(color)` | Set tab color |
241
275
  | `hide()` | Hide worksheet |
242
276
  | `activate()` | Activate worksheet |
243
- | `setFirstSheet()` | Set as first sheet |
244
- | `protect(password?)` | Protect worksheet |
245
277
 
246
- #### Page Setup
278
+ #### Sheet Protection
247
279
  | Method | Description |
248
280
  |--------|-------------|
249
- | `setPortrait()` | Set portrait orientation |
250
- | `setLandscape()` | Set landscape orientation |
251
- | `setPaper(paperType)` | Set paper size |
252
- | `setHeader(header)` | Set header |
253
- | `setFooter(footer)` | Set footer |
254
- | `setPrintArea(firstRow, firstCol, lastRow, lastCol)` | Set print area |
255
- | `fitToPages(width, height)` | Fit to pages |
256
- | `setZoom(scale)` | Set zoom level |
257
- | `setGridlines(option)` | Set gridlines |
258
- | `centerHorizontally()` | Center horizontally |
259
- | `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
+ ```
260
327
 
261
328
  #### Read Methods
262
329
  | Method | Description |
@@ -266,11 +333,62 @@ const buffer = await workbook.getBuffer();
266
333
  | `getCellRawValue(row, col)` | Get raw cell value |
267
334
  | `getCellType(row, col)` | Get cell type |
268
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) |
269
339
  | `getRowCount()` | Get total row count |
270
340
  | `getColumnCount()` | Get total column count |
271
341
  | `getLastRow()` | Get last row index |
272
342
  | `getLastColumn()` | Get last column index |
273
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
+ ```
274
392
 
275
393
  ### XlsxCellFormat
276
394
 
@@ -320,9 +438,14 @@ When reading cells, the following types are returned:
320
438
 
321
439
  - `empty` - Empty cell
322
440
  - `string` - Text string
323
- - `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
+ ```
324
448
  - `boolean` - Boolean value
325
- - `date` - Date/time value
326
449
  - `error` - Error value
327
450
  - `formula` - Formula
328
451
  - `blank` - Blank cell
@@ -341,7 +464,43 @@ The library exports several useful constants:
341
464
  - `Gridlines` - Gridline options
342
465
  - `Colors` - Common RGB colors
343
466
  - `NumFormat` - Built-in number format indices
344
- - `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 |
345
504
 
346
505
  ## Development
347
506
 
@@ -373,17 +532,20 @@ This library is based on OpenXLSX and inherits the following limitations:
373
532
 
374
533
  The following Excel features are not supported and will be ignored when reading or writing files:
375
534
 
376
- - 🖼️ **Images**: Reading and writing images is not supported. Existing images in XLSX files will be ignored.
377
535
  - 📊 **Charts**: Charts, graphs, and sparklines are not supported.
378
- - ✏️ **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.
379
537
  - 🔍 **Pivot Tables**: Pivot tables and pivot charts are not supported.
380
538
  - 💻 **VBA/Macros**: VBA macros, forms, and ActiveX controls are not supported.
381
- - 📋 **Comments**: Cell comments and notes are not supported.
382
539
  - 📌 **Hyperlinks**: Only basic URL hyperlinks are supported.
383
- - 🔄 **Conditional Formatting**: Conditional formatting rules are not supported.
384
540
  - 📑 **Data Validation**: Data validation rules are not supported.
385
541
 
386
542
  ### Notes
387
543
 
388
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.
389
- - 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.
@@ -47,6 +47,10 @@ add_library(
47
47
  ../cpp/HybridXlsxWorkbook.cpp
48
48
  ../cpp/HybridXlsxWorksheet.cpp
49
49
  ../cpp/HybridXlsxCellFormat.cpp
50
+ ../cpp/HybridXlsxConditionalFormat.cpp
51
+ ../cpp/HybridXlsxConditionalFormats.cpp
52
+ ../cpp/XlsxJson.cpp
53
+ ../cpp/XlsxCacheDir.cpp
50
54
  cpp-adapter.cpp
51
55
  )
52
56
 
@@ -1,9 +1,21 @@
1
1
  #include <jni.h>
2
2
  #include <fbjni/fbjni.h>
3
3
  #include "NitroXlsxOnLoad.hpp"
4
+ #include "XlsxCacheDir.hpp"
4
5
 
5
6
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
6
7
  return facebook::jni::initialize(vm, []() {
7
8
  margelo::nitro::xlsx::registerAllNatives();
8
9
  });
9
10
  }
11
+
12
+ // Called from NitroXlsxPackage with Context.getCacheDir().absolutePath
13
+ extern "C" JNIEXPORT void JNICALL
14
+ Java_com_margelo_nitro_xlsx_NitroXlsxPackage_nativeSetCacheDir(JNIEnv* env, jclass, jstring dir) {
15
+ if (dir == nullptr) return;
16
+ const char* chars = env->GetStringUTFChars(dir, nullptr);
17
+ if (chars != nullptr) {
18
+ margelo::nitro::xlsx::setCacheDir(std::string(chars));
19
+ env->ReleaseStringUTFChars(dir, chars);
20
+ }
21
+ }
@@ -10,10 +10,19 @@ class NitroXlsxPackage : ReactPackage {
10
10
  init {
11
11
  NitroXlsxOnLoad.initializeNative()
12
12
  }
13
+
14
+ /** Pass the app cache directory to the native layer for temp XLSX files. */
15
+ @JvmStatic
16
+ private external fun nativeSetCacheDir(dir: String)
13
17
  }
14
18
 
15
19
  @Suppress("OVERRIDE_DEPRECATION")
16
20
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
21
+ try {
22
+ nativeSetCacheDir(reactContext.cacheDir.absolutePath)
23
+ } catch (_: Throwable) {
24
+ // native resolver falls back to Context.getCacheDir() via JNI
25
+ }
17
26
  return emptyList()
18
27
  }
19
28