react-native-nitro-xlsx 0.2.6 → 0.2.7
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/README.md +20 -8
- package/cpp/HybridNitroXlsx.cpp +25 -1
- package/cpp/HybridXlsxCellFormat.cpp +46 -146
- package/cpp/HybridXlsxCellFormat.hpp +9 -41
- package/cpp/HybridXlsxConditionalFormat.cpp +4 -1
- package/cpp/HybridXlsxConditionalFormats.cpp +5 -1
- package/cpp/HybridXlsxWorkbook.cpp +38 -8
- package/cpp/HybridXlsxWorkbook.hpp +10 -1
- package/cpp/HybridXlsxWorksheet.cpp +422 -141
- package/cpp/HybridXlsxWorksheet.hpp +4 -1
- package/cpp/XlsxCacheDir.cpp +25 -2
- package/cpp/XlsxCacheDir.hpp +3 -1
- package/cpp/XlsxCellStyle.hpp +360 -0
- package/cpp/XlsxJson.cpp +29 -8
- package/package.json +1 -1
- package/scripts/sync-openxlsx-headers.sh +34 -0
- package/src/__mocks__/react-native-nitro-modules.ts +523 -20
- package/src/__tests__/index.test.ts +124 -16
- package/src/index.ts +13 -13
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/IZipArchive.hpp +361 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/OpenXLSX-Exports.hpp +12 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCell.hpp +367 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellIterator.hpp +252 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellRange.hpp +223 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellReference.hpp +317 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellValue.hpp +793 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColor.hpp +232 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColumn.hpp +159 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCommandQuery.hpp +228 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLComments.hpp +270 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLConstants.hpp +18 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLContentTypes.hpp +264 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDateTime.hpp +175 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDocument.hpp +505 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDrawing.hpp +452 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLException.hpp +161 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLFormula.hpp +375 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLIterator.hpp +40 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLMergeCells.hpp +216 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLProperties.hpp +348 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRelationships.hpp +348 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRow.hpp +721 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRowData.hpp +488 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSharedStrings.hpp +204 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSheet.hpp +1640 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLStyles.hpp +2390 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLTables.hpp +132 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLWorkbook.hpp +404 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlData.hpp +283 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlFile.hpp +194 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParser.hpp +282 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParserForwardDeclarations.hpp +85 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLZipArchive.hpp +232 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/LibZip.hpp +447 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/OpenXLSXFileSystemTools.hpp +207 -0
- package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/Zippy.hpp +1375 -0
- package/thirdparty/README.md +7 -3
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ const buffer = await workbook.getBuffer();
|
|
|
197
197
|
- `createWorkbook(): XlsxWorkbook` - Create a new workbook. The native side supplies the platform cache directory automatically (`Context.getCacheDir()` on Android, `NSCachesDirectory` on iOS).
|
|
198
198
|
- `openWorkbook(path: string): XlsxWorkbook` - Open workbook from file path
|
|
199
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)
|
|
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). The default sheet is removed unless one of the keys is named after it
|
|
201
201
|
|
|
202
202
|
### XlsxWorkbook
|
|
203
203
|
|
|
@@ -211,7 +211,7 @@ const buffer = await workbook.getBuffer();
|
|
|
211
211
|
- `clone(existingName: string, newName: string): XlsxWorksheet` - Copy a worksheet
|
|
212
212
|
- `addCellFormat(): XlsxCellFormat` - Add a new cell format
|
|
213
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
|
|
214
|
+
- `toJSON(): Record<string, Array<Record<string, string | number | boolean | null>>>` - Export every worksheet as a `Record` keyed by sheet name. Worksheets without a single cell are skipped, so a fresh workbook exports as `{}`
|
|
215
215
|
|
|
216
216
|
### XlsxWorksheet
|
|
217
217
|
|
|
@@ -232,7 +232,7 @@ const buffer = await workbook.getBuffer();
|
|
|
232
232
|
| `writeFormulaNum(row, col, formula, number, format?)` | Write a formula with number default |
|
|
233
233
|
| `writeFormulaString(row, col, formula, string, format?)` | Write a formula with string default |
|
|
234
234
|
| `writeFormulaBoolean(row, col, formula, boolean, format?)` | Write a formula with boolean default |
|
|
235
|
-
| `writeArrayFormula(firstRow, firstCol, lastRow, lastCol, formula, format?)` |
|
|
235
|
+
| `writeArrayFormula(firstRow, firstCol, lastRow, lastCol, formula, format?)` | Not supported by the underlying engine — always throws `UNSUPPORTED` |
|
|
236
236
|
|
|
237
237
|
#### Date/URL
|
|
238
238
|
| Method | Description |
|
|
@@ -409,7 +409,7 @@ The `XlsxCellFormat` interface is based on OpenXLSX's `XLCellFormat` class and p
|
|
|
409
409
|
- `setNumFormatIndex(index)` - Set built-in format (use `NumFormat` constants)
|
|
410
410
|
|
|
411
411
|
#### Colors/Pattern
|
|
412
|
-
- `setBgColor(color)`, `setFgColor(color)`
|
|
412
|
+
- `setBgColor(color)`, `setFgColor(color)` - For solid fills Excel paints the foreground colour, so a colour set only via `setBgColor` is also written as the fill's foreground colour to make it visible
|
|
413
413
|
- `setPattern(pattern)`
|
|
414
414
|
|
|
415
415
|
#### Borders
|
|
@@ -436,9 +436,9 @@ The `XlsxCellFormat` interface is based on OpenXLSX's `XLCellFormat` class and p
|
|
|
436
436
|
|
|
437
437
|
When reading cells, the following types are returned:
|
|
438
438
|
|
|
439
|
-
- `empty` - Empty cell
|
|
440
439
|
- `string` - Text string
|
|
441
|
-
- `number` - Numeric value
|
|
440
|
+
- `number` - Numeric value with no date/time number format applied
|
|
441
|
+
- `date` - Numeric value whose number format is a date/time code. This covers the built-in ids 14–22 and custom codes such as `yyyy-mm-dd`, `h:mm` or `yyyy-mm-dd h:mm:ss`. The stored value is still an **Excel serial number in the 1900 date system** (days since `1900-01-01`, where `1900-01-01` = `1`, including the spurious `1900-02-29`):
|
|
442
442
|
```ts
|
|
443
443
|
// Excel serial (1900-based) → JS Date
|
|
444
444
|
const date = new Date(Math.round((serial - 25569) * 86400000));
|
|
@@ -447,8 +447,11 @@ When reading cells, the following types are returned:
|
|
|
447
447
|
```
|
|
448
448
|
- `boolean` - Boolean value
|
|
449
449
|
- `error` - Error value
|
|
450
|
-
- `formula` -
|
|
451
|
-
- `blank` -
|
|
450
|
+
- `formula` - Cell holds a formula (checked before the value type, so a formula cell never reads as `number`/`string`)
|
|
451
|
+
- `blank` - Cell exists (e.g. it only carries a style) but has no value
|
|
452
|
+
- `empty` - No cell at these coordinates at all
|
|
453
|
+
|
|
454
|
+
`getCellType` returns `empty`/`blank` without creating the cell, so probing a cell never mutates the sheet.
|
|
452
455
|
|
|
453
456
|
## Constants
|
|
454
457
|
|
|
@@ -510,6 +513,15 @@ To regenerate the native specs after modifying the TypeScript spec files:
|
|
|
510
513
|
npx nitrogen
|
|
511
514
|
```
|
|
512
515
|
|
|
516
|
+
### Tests
|
|
517
|
+
|
|
518
|
+
```sh
|
|
519
|
+
yarn test # JS/TS suite (Jest)
|
|
520
|
+
yarn test:cpp # configure + build cpp/tests into cpp/tests/build, then run gtest
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`yarn test:cpp` needs a local GoogleTest installation (`brew install googletest` on macOS); the CMake project looks under `/opt/homebrew`, `/usr/local` and `/usr` first and only falls back to downloading googletest when nothing is found. OpenXLSX, pugixml and miniz are vendored under `thirdparty/`, so the C++ build works offline.
|
|
524
|
+
|
|
513
525
|
## License
|
|
514
526
|
|
|
515
527
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/cpp/HybridNitroXlsx.cpp
CHANGED
|
@@ -39,12 +39,36 @@ std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::openWorkbookFromBuffer(
|
|
|
39
39
|
std::shared_ptr<HybridXlsxWorkbookSpec> HybridNitroXlsx::fromJSON(const std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>>& data) {
|
|
40
40
|
return withXlsxError(xlsx_error::INVALID_ARGUMENT, [&]() -> std::shared_ptr<HybridXlsxWorkbookSpec> {
|
|
41
41
|
auto workbook = std::make_shared<HybridXlsxWorkbook>();
|
|
42
|
-
|
|
42
|
+
// Always leave the workbook usable: create the underlying XLDocument even
|
|
43
|
+
// when there is no data, so getBuffer()/toJSON()/getWorksheetCount() work.
|
|
44
|
+
workbook->ensureOpen();
|
|
45
|
+
|
|
46
|
+
// Snapshot the sheet(s) OpenXLSX created by default (e.g. "Sheet1") so they
|
|
47
|
+
// can be removed once the key sheets exist (deleteSheet refuses to delete
|
|
48
|
+
// the last sheet, so key sheets must be added first).
|
|
49
|
+
std::vector<std::string> defaultNames;
|
|
50
|
+
{
|
|
51
|
+
double count = workbook->getWorksheetCount();
|
|
52
|
+
for (double i = 0; i < count; ++i) {
|
|
53
|
+
defaultNames.push_back(workbook->getWorksheet(i)->getName());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
for (const auto& [sheetName, rows] : data) {
|
|
45
58
|
auto sheet = workbook->addWorksheet(sheetName);
|
|
46
59
|
sheet->fromJSON(rows);
|
|
47
60
|
}
|
|
61
|
+
|
|
62
|
+
// README promises one worksheet per key: drop the default sheet(s) unless a
|
|
63
|
+
// key claimed them. Skip when there is no data - a workbook must keep at
|
|
64
|
+
// least one sheet.
|
|
65
|
+
if (!data.empty()) {
|
|
66
|
+
for (const auto& name : defaultNames) {
|
|
67
|
+
if (data.count(name) == 0) {
|
|
68
|
+
workbook->deleteSheet(name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
return workbook;
|
|
49
73
|
});
|
|
50
74
|
}
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
#include "HybridXlsxCellFormat.hpp"
|
|
2
|
-
#include <sstream>
|
|
3
|
-
#include <iomanip>
|
|
4
|
-
#include <cctype>
|
|
5
2
|
|
|
6
3
|
namespace margelo::nitro::xlsx {
|
|
7
4
|
|
|
8
|
-
static std::string doubleToHexColor(double color) {
|
|
9
|
-
uint32_t c = static_cast<uint32_t>(color);
|
|
10
|
-
// Excel uses ARGB format, prepend alpha channel (FF)
|
|
11
|
-
uint32_t argb = c | 0xFF000000;
|
|
12
|
-
std::stringstream ss;
|
|
13
|
-
ss << std::hex << std::setfill('0') << std::setw(8) << argb;
|
|
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;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
5
|
HybridXlsxCellFormat::HybridXlsxCellFormat()
|
|
23
6
|
: HybridObject("XlsxCellFormat"), HybridXlsxCellFormatSpec() {
|
|
24
7
|
}
|
|
@@ -26,263 +9,180 @@ HybridXlsxCellFormat::HybridXlsxCellFormat()
|
|
|
26
9
|
HybridXlsxCellFormat::~HybridXlsxCellFormat() {
|
|
27
10
|
}
|
|
28
11
|
|
|
29
|
-
OpenXLSX::XLStyleIndex HybridXlsxCellFormat::applyToDocument(OpenXLSX::XLStyles& styles) {
|
|
30
|
-
|
|
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];
|
|
41
|
-
|
|
42
|
-
// Font
|
|
43
|
-
if (!_fontName.empty() || _fontSize > 0 || _fontColor > 0 || _bold || _italic || _underline > 0 || _strikeout || _fontScript > 0) {
|
|
44
|
-
OpenXLSX::XLFont emptyFont;
|
|
45
|
-
OpenXLSX::XLStyleIndex fontIndex = styles.fonts().create(emptyFont);
|
|
46
|
-
OpenXLSX::XLFont font = styles.fonts()[fontIndex];
|
|
47
|
-
if (!_fontName.empty()) font.setFontName(_fontName);
|
|
48
|
-
if (_fontSize > 0) font.setFontSize(static_cast<size_t>(_fontSize));
|
|
49
|
-
if (_fontColor > 0) font.setFontColor(OpenXLSX::XLColor(doubleToHexColor(_fontColor)));
|
|
50
|
-
if (_bold) font.setBold();
|
|
51
|
-
if (_italic) font.setItalic();
|
|
52
|
-
if (_underline > 0) font.setUnderline(static_cast<OpenXLSX::XLUnderlineStyle>(_underline));
|
|
53
|
-
if (_strikeout) font.setStrikethrough();
|
|
54
|
-
if (_fontScript > 0) font.setVertAlign(static_cast<OpenXLSX::XLVerticalAlignRunStyle>(_fontScript));
|
|
55
|
-
cellFormat.setFontIndex(fontIndex);
|
|
56
|
-
cellFormat.setApplyFont(true);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Alignment
|
|
60
|
-
if (_textWrap || _rotation != 0 || _indent > 0 || _align > 0 || _verticalAlign > 0 || _shrink) {
|
|
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();
|
|
67
|
-
cellFormat.setApplyAlignment(true);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Number format
|
|
71
|
-
if (!_numFormat.empty()) {
|
|
72
|
-
OpenXLSX::XLNumberFormat emptyNumFormat;
|
|
73
|
-
OpenXLSX::XLStyleIndex numFmtIndex = styles.numberFormats().create(emptyNumFormat);
|
|
74
|
-
OpenXLSX::XLNumberFormat numFormat = styles.numberFormats()[numFmtIndex];
|
|
75
|
-
numFormat.setFormatCode(_numFormat);
|
|
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());
|
|
80
|
-
cellFormat.setApplyNumberFormat(true);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Fill
|
|
84
|
-
if (_bgColor > 0 || _fgColor > 0 || _pattern > 0) {
|
|
85
|
-
OpenXLSX::XLFill emptyFill;
|
|
86
|
-
OpenXLSX::XLStyleIndex fillIndex = styles.fills().create(emptyFill);
|
|
87
|
-
OpenXLSX::XLFill fill = styles.fills()[fillIndex];
|
|
88
|
-
fill.setPatternType(_pattern > 0
|
|
89
|
-
? static_cast<OpenXLSX::XLPatternType>(_pattern)
|
|
90
|
-
: OpenXLSX::XLPatternType::XLPatternSolid);
|
|
91
|
-
if (_bgColor > 0) fill.setBackgroundColor(OpenXLSX::XLColor(doubleToHexColor(_bgColor)));
|
|
92
|
-
if (_fgColor > 0) fill.setColor(OpenXLSX::XLColor(doubleToHexColor(_fgColor)));
|
|
93
|
-
cellFormat.setFillIndex(fillIndex);
|
|
94
|
-
cellFormat.setApplyFill(true);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Border
|
|
98
|
-
if (_border > 0 || _bottom > 0 || _top > 0 || _left > 0 || _right > 0 || _diagonal > 0) {
|
|
99
|
-
OpenXLSX::XLBorder emptyBorder;
|
|
100
|
-
OpenXLSX::XLStyleIndex borderIndex = styles.borders().create(emptyBorder);
|
|
101
|
-
OpenXLSX::XLBorder border = styles.borders()[borderIndex];
|
|
102
|
-
// TODO: Implement border styling with OpenXLSX v0.5.1 API
|
|
103
|
-
cellFormat.setBorderIndex(borderIndex);
|
|
104
|
-
cellFormat.setApplyBorder(true);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Protection
|
|
108
|
-
if (_locked) {
|
|
109
|
-
cellFormat.setLocked(true);
|
|
110
|
-
cellFormat.setApplyProtection(true);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
_styleIndex = cellFormatIndex;
|
|
114
|
-
return cellFormatIndex;
|
|
12
|
+
OpenXLSX::XLStyleIndex HybridXlsxCellFormat::applyToDocument(OpenXLSX::XLStyles& styles, uint64_t documentGeneration) {
|
|
13
|
+
return _fmt.applyToDocument(styles, documentGeneration);
|
|
115
14
|
}
|
|
116
15
|
|
|
117
16
|
void HybridXlsxCellFormat::applyToCell(OpenXLSX::XLCell& cell, OpenXLSX::XLStyleIndex styleIndex) {
|
|
118
|
-
|
|
17
|
+
_fmt.applyToCell(cell, styleIndex);
|
|
119
18
|
}
|
|
120
19
|
|
|
121
20
|
void HybridXlsxCellFormat::setFontName(const std::string& name) {
|
|
122
|
-
|
|
21
|
+
_fmt.setFontName(name);
|
|
123
22
|
}
|
|
124
23
|
|
|
125
24
|
void HybridXlsxCellFormat::setFontSize(double size) {
|
|
126
|
-
|
|
25
|
+
_fmt.setFontSize(size);
|
|
127
26
|
}
|
|
128
27
|
|
|
129
28
|
void HybridXlsxCellFormat::setFontColor(double color) {
|
|
130
|
-
|
|
29
|
+
_fmt.setFontColor(color);
|
|
131
30
|
}
|
|
132
31
|
|
|
133
32
|
void HybridXlsxCellFormat::setFontScript(double style) {
|
|
134
|
-
|
|
33
|
+
_fmt.setFontScript(style);
|
|
135
34
|
}
|
|
136
35
|
|
|
137
36
|
void HybridXlsxCellFormat::setBold() {
|
|
138
|
-
|
|
37
|
+
_fmt.setBold();
|
|
139
38
|
}
|
|
140
39
|
|
|
141
40
|
void HybridXlsxCellFormat::setItalic() {
|
|
142
|
-
|
|
41
|
+
_fmt.setItalic();
|
|
143
42
|
}
|
|
144
43
|
|
|
145
44
|
void HybridXlsxCellFormat::setUnderline(std::optional<double> style) {
|
|
146
|
-
|
|
45
|
+
_fmt.setUnderline(style);
|
|
147
46
|
}
|
|
148
47
|
|
|
149
48
|
void HybridXlsxCellFormat::setStrikeout() {
|
|
150
|
-
|
|
49
|
+
_fmt.setStrikeout();
|
|
151
50
|
}
|
|
152
51
|
|
|
153
52
|
void HybridXlsxCellFormat::setAlign(double align) {
|
|
154
|
-
|
|
53
|
+
_fmt.setAlign(align);
|
|
155
54
|
}
|
|
156
55
|
|
|
157
56
|
void HybridXlsxCellFormat::setVerticalAlign(double align) {
|
|
158
|
-
|
|
57
|
+
_fmt.setVerticalAlign(align);
|
|
159
58
|
}
|
|
160
59
|
|
|
161
60
|
void HybridXlsxCellFormat::setTextWrap() {
|
|
162
|
-
|
|
61
|
+
_fmt.setTextWrap();
|
|
163
62
|
}
|
|
164
63
|
|
|
165
64
|
void HybridXlsxCellFormat::setRotation(double angle) {
|
|
166
|
-
|
|
65
|
+
_fmt.setRotation(angle);
|
|
167
66
|
}
|
|
168
67
|
|
|
169
68
|
void HybridXlsxCellFormat::setIndent(double level) {
|
|
170
|
-
|
|
69
|
+
_fmt.setIndent(level);
|
|
171
70
|
}
|
|
172
71
|
|
|
173
72
|
void HybridXlsxCellFormat::setShrink() {
|
|
174
|
-
|
|
73
|
+
_fmt.setShrink();
|
|
175
74
|
}
|
|
176
75
|
|
|
177
76
|
void HybridXlsxCellFormat::setNumFormat(const std::string& format) {
|
|
178
|
-
|
|
77
|
+
_fmt.setNumFormat(format);
|
|
179
78
|
}
|
|
180
79
|
|
|
181
80
|
void HybridXlsxCellFormat::setNumFormatIndex(double index) {
|
|
182
|
-
|
|
81
|
+
_fmt.setNumFormatIndex(index);
|
|
183
82
|
}
|
|
184
83
|
|
|
185
84
|
void HybridXlsxCellFormat::setBgColor(double color) {
|
|
186
|
-
|
|
85
|
+
_fmt.setBgColor(color);
|
|
187
86
|
}
|
|
188
87
|
|
|
189
88
|
void HybridXlsxCellFormat::setFgColor(double color) {
|
|
190
|
-
|
|
89
|
+
_fmt.setFgColor(color);
|
|
191
90
|
}
|
|
192
91
|
|
|
193
92
|
void HybridXlsxCellFormat::setPattern(double pattern) {
|
|
194
|
-
|
|
93
|
+
_fmt.setPattern(pattern);
|
|
195
94
|
}
|
|
196
95
|
|
|
197
96
|
void HybridXlsxCellFormat::setBorder(double style) {
|
|
198
|
-
|
|
97
|
+
_fmt.setBorder(style);
|
|
199
98
|
}
|
|
200
99
|
|
|
201
100
|
void HybridXlsxCellFormat::setBorderColor(double color) {
|
|
202
|
-
|
|
101
|
+
_fmt.setBorderColor(color);
|
|
203
102
|
}
|
|
204
103
|
|
|
205
104
|
void HybridXlsxCellFormat::setBottom(double style) {
|
|
206
|
-
|
|
105
|
+
_fmt.setBottom(style);
|
|
207
106
|
}
|
|
208
107
|
|
|
209
108
|
void HybridXlsxCellFormat::setTop(double style) {
|
|
210
|
-
|
|
109
|
+
_fmt.setTop(style);
|
|
211
110
|
}
|
|
212
111
|
|
|
213
112
|
void HybridXlsxCellFormat::setLeft(double style) {
|
|
214
|
-
|
|
113
|
+
_fmt.setLeft(style);
|
|
215
114
|
}
|
|
216
115
|
|
|
217
116
|
void HybridXlsxCellFormat::setRight(double style) {
|
|
218
|
-
|
|
117
|
+
_fmt.setRight(style);
|
|
219
118
|
}
|
|
220
119
|
|
|
221
120
|
void HybridXlsxCellFormat::setBottomColor(double color) {
|
|
222
|
-
|
|
121
|
+
_fmt.setBottomColor(color);
|
|
223
122
|
}
|
|
224
123
|
|
|
225
124
|
void HybridXlsxCellFormat::setTopColor(double color) {
|
|
226
|
-
|
|
125
|
+
_fmt.setTopColor(color);
|
|
227
126
|
}
|
|
228
127
|
|
|
229
128
|
void HybridXlsxCellFormat::setLeftColor(double color) {
|
|
230
|
-
|
|
129
|
+
_fmt.setLeftColor(color);
|
|
231
130
|
}
|
|
232
131
|
|
|
233
132
|
void HybridXlsxCellFormat::setRightColor(double color) {
|
|
234
|
-
|
|
133
|
+
_fmt.setRightColor(color);
|
|
235
134
|
}
|
|
236
135
|
|
|
237
136
|
void HybridXlsxCellFormat::setDiagonal(double style) {
|
|
238
|
-
|
|
137
|
+
_fmt.setDiagonal(style);
|
|
239
138
|
}
|
|
240
139
|
|
|
241
140
|
void HybridXlsxCellFormat::setDiagonalColor(double color) {
|
|
242
|
-
|
|
141
|
+
_fmt.setDiagonalColor(color);
|
|
243
142
|
}
|
|
244
143
|
|
|
245
144
|
void HybridXlsxCellFormat::setLocked() {
|
|
246
|
-
|
|
145
|
+
_fmt.setLocked();
|
|
247
146
|
}
|
|
248
147
|
|
|
249
148
|
void HybridXlsxCellFormat::setUnlocked() {
|
|
250
|
-
|
|
149
|
+
_fmt.setUnlocked();
|
|
251
150
|
}
|
|
252
151
|
|
|
253
152
|
void HybridXlsxCellFormat::setHyperlink() {
|
|
254
|
-
|
|
153
|
+
_fmt.setHyperlink();
|
|
255
154
|
}
|
|
256
155
|
|
|
257
156
|
void HybridXlsxCellFormat::setFontOnly() {
|
|
157
|
+
_fmt.setFontOnly();
|
|
258
158
|
}
|
|
259
159
|
|
|
260
160
|
std::string HybridXlsxCellFormat::getFontName() {
|
|
261
|
-
return
|
|
161
|
+
return _fmt.getFontName();
|
|
262
162
|
}
|
|
263
163
|
|
|
264
164
|
double HybridXlsxCellFormat::getFontSize() {
|
|
265
|
-
return
|
|
165
|
+
return _fmt.getFontSize();
|
|
266
166
|
}
|
|
267
167
|
|
|
268
168
|
double HybridXlsxCellFormat::getFontColor() {
|
|
269
|
-
return
|
|
169
|
+
return _fmt.getFontColor();
|
|
270
170
|
}
|
|
271
171
|
|
|
272
172
|
bool HybridXlsxCellFormat::getIsBold() {
|
|
273
|
-
return
|
|
173
|
+
return _fmt.getIsBold();
|
|
274
174
|
}
|
|
275
175
|
|
|
276
176
|
bool HybridXlsxCellFormat::getIsItalic() {
|
|
277
|
-
return
|
|
177
|
+
return _fmt.getIsItalic();
|
|
278
178
|
}
|
|
279
179
|
|
|
280
180
|
std::string HybridXlsxCellFormat::getNumFormat() {
|
|
281
|
-
return
|
|
181
|
+
return _fmt.getNumFormat();
|
|
282
182
|
}
|
|
283
183
|
|
|
284
184
|
double HybridXlsxCellFormat::getBgColor() {
|
|
285
|
-
return
|
|
185
|
+
return _fmt.getBgColor();
|
|
286
186
|
}
|
|
287
187
|
|
|
288
|
-
}
|
|
188
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include "HybridXlsxCellFormatSpec.hpp"
|
|
4
|
+
#include "XlsxCellStyle.hpp"
|
|
4
5
|
#include <OpenXLSX/XLDocument.hpp>
|
|
5
6
|
#include <OpenXLSX/XLSheet.hpp>
|
|
6
7
|
#include <OpenXLSX/XLStyles.hpp>
|
|
@@ -14,7 +15,11 @@ public:
|
|
|
14
15
|
HybridXlsxCellFormat();
|
|
15
16
|
~HybridXlsxCellFormat() override;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
// `documentGeneration` identifies the XLDocument that owns `styles` (see
|
|
19
|
+
// HybridXlsxWorkbook::documentGeneration()). It must be passed on every call
|
|
20
|
+
// so the style cache can never be reused by a different document that happens
|
|
21
|
+
// to reuse the same heap address.
|
|
22
|
+
OpenXLSX::XLStyleIndex applyToDocument(OpenXLSX::XLStyles& styles, uint64_t documentGeneration);
|
|
18
23
|
void applyToCell(OpenXLSX::XLCell& cell, OpenXLSX::XLStyleIndex styleIndex);
|
|
19
24
|
|
|
20
25
|
void setFontName(const std::string& name) override;
|
|
@@ -68,45 +73,8 @@ public:
|
|
|
68
73
|
double getBgColor() override;
|
|
69
74
|
|
|
70
75
|
private:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
double _fontSize = 0;
|
|
74
|
-
double _fontColor = 0;
|
|
75
|
-
double _fontScript = 0;
|
|
76
|
-
bool _bold = false;
|
|
77
|
-
bool _italic = false;
|
|
78
|
-
double _underline = 0;
|
|
79
|
-
bool _strikeout = false;
|
|
80
|
-
|
|
81
|
-
double _align = 0;
|
|
82
|
-
double _verticalAlign = 0;
|
|
83
|
-
bool _textWrap = false;
|
|
84
|
-
double _rotation = 0;
|
|
85
|
-
double _indent = 0;
|
|
86
|
-
bool _shrink = false;
|
|
87
|
-
|
|
88
|
-
std::string _numFormat;
|
|
89
|
-
double _numFormatIndex = 0;
|
|
90
|
-
|
|
91
|
-
double _bgColor = 0;
|
|
92
|
-
double _fgColor = 0;
|
|
93
|
-
double _pattern = 0;
|
|
94
|
-
|
|
95
|
-
double _border = 0;
|
|
96
|
-
double _borderColor = 0;
|
|
97
|
-
double _bottom = 0;
|
|
98
|
-
double _top = 0;
|
|
99
|
-
double _left = 0;
|
|
100
|
-
double _right = 0;
|
|
101
|
-
double _bottomColor = 0;
|
|
102
|
-
double _topColor = 0;
|
|
103
|
-
double _leftColor = 0;
|
|
104
|
-
double _rightColor = 0;
|
|
105
|
-
double _diagonal = 0;
|
|
106
|
-
double _diagonalColor = 0;
|
|
107
|
-
|
|
108
|
-
bool _locked = false;
|
|
109
|
-
bool _hyperlink = false;
|
|
76
|
+
// Style state and styles.xml materialization, shared with the unit tests.
|
|
77
|
+
XlsxCellStyle _fmt;
|
|
110
78
|
};
|
|
111
79
|
|
|
112
|
-
}
|
|
80
|
+
}
|
|
@@ -84,9 +84,12 @@ void HybridXlsxConditionalFormat::setRuleDxfId(double index, double dxfId) {
|
|
|
84
84
|
|
|
85
85
|
std::string HybridXlsxConditionalFormat::getRuleFormula(double index, double formulaIndex) {
|
|
86
86
|
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
87
|
+
// Validate the rule index first so out-of-range throws INDEX_OUT_OF_RANGE
|
|
88
|
+
// like every other getter (and like setRuleFormula).
|
|
89
|
+
auto rule = ruleAt(index);
|
|
87
90
|
// OpenXLSX only supports the primary <formula> node
|
|
88
91
|
if (formulaIndex != 0) return std::string();
|
|
89
|
-
return
|
|
92
|
+
return rule.formula();
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -19,7 +19,11 @@ std::shared_ptr<HybridXlsxConditionalFormatSpec> HybridXlsxConditionalFormats::c
|
|
|
19
19
|
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
20
20
|
size_t index = _formats.create();
|
|
21
21
|
OpenXLSX::XLConditionalFormat format = _formats.conditionalFormatByIndex(index);
|
|
22
|
-
|
|
22
|
+
// setSqref() returns false when it cannot apply the value (and treats an empty
|
|
23
|
+
// attribute value as a no-op), so a blank sqref is rejected explicitly too.
|
|
24
|
+
if (sqref.empty() || !format.setSqref(sqref)) {
|
|
25
|
+
throw XlsxError(xlsx_error::INVALID_ARGUMENT, "Invalid sqref: " + sqref);
|
|
26
|
+
}
|
|
23
27
|
auto hybrid = std::make_shared<HybridXlsxConditionalFormat>(format);
|
|
24
28
|
_created.push_back(hybrid);
|
|
25
29
|
return std::static_pointer_cast<HybridXlsxConditionalFormatSpec>(hybrid);
|
|
@@ -33,17 +33,34 @@ HybridXlsxWorkbook::~HybridXlsxWorkbook() {
|
|
|
33
33
|
|
|
34
34
|
void HybridXlsxWorkbook::openFromFile(const std::string& path) {
|
|
35
35
|
_workbook->open(path);
|
|
36
|
+
++_generation;
|
|
36
37
|
loadWorksheets();
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
void HybridXlsxWorkbook::openFromBuffer(const uint8_t* data, size_t size) {
|
|
40
41
|
std::string tempPath = tempFilePath("xlsx_open");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
{
|
|
43
|
+
std::ofstream file(tempPath, std::ios::binary);
|
|
44
|
+
if (!file.is_open()) {
|
|
45
|
+
throw XlsxError(xlsx_error::IO_ERROR, "Failed to open temp file for writing: " + tempPath);
|
|
46
|
+
}
|
|
47
|
+
file.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
|
|
48
|
+
file.close();
|
|
49
|
+
if (file.fail()) {
|
|
50
|
+
std::remove(tempPath.c_str());
|
|
51
|
+
throw XlsxError(xlsx_error::IO_ERROR, "Failed to write buffer to temp file: " + tempPath);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
try {
|
|
56
|
+
_workbook->open(tempPath);
|
|
57
|
+
} catch (...) {
|
|
58
|
+
// Never leak the temp file when opening fails.
|
|
59
|
+
std::remove(tempPath.c_str());
|
|
60
|
+
throw;
|
|
61
|
+
}
|
|
46
62
|
std::remove(tempPath.c_str());
|
|
63
|
+
++_generation;
|
|
47
64
|
loadWorksheets();
|
|
48
65
|
}
|
|
49
66
|
|
|
@@ -56,7 +73,7 @@ void HybridXlsxWorkbook::loadWorksheets() {
|
|
|
56
73
|
for (uint16_t i = 1; i <= count; ++i) {
|
|
57
74
|
auto worksheet = wb.worksheet(i);
|
|
58
75
|
std::string sheetName = worksheet.name();
|
|
59
|
-
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
|
|
76
|
+
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet, _generation);
|
|
60
77
|
_worksheets.push_back(hybridWorksheet);
|
|
61
78
|
_worksheetNames[sheetName] = hybridWorksheet;
|
|
62
79
|
}
|
|
@@ -67,6 +84,7 @@ void HybridXlsxWorkbook::ensureOpen() {
|
|
|
67
84
|
std::string tempPath = tempFilePath("xlsx_create");
|
|
68
85
|
_workbook->create(tempPath, OpenXLSX::XLForceOverwrite);
|
|
69
86
|
std::remove(tempPath.c_str());
|
|
87
|
+
++_generation;
|
|
70
88
|
loadWorksheets();
|
|
71
89
|
}
|
|
72
90
|
|
|
@@ -91,7 +109,7 @@ std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::addWorksheet(const
|
|
|
91
109
|
|
|
92
110
|
_workbook->workbook().addWorksheet(sheetName);
|
|
93
111
|
auto worksheet = _workbook->workbook().worksheet(sheetName);
|
|
94
|
-
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
|
|
112
|
+
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet, _generation);
|
|
95
113
|
_worksheets.push_back(hybridWorksheet);
|
|
96
114
|
_worksheetNames[sheetName] = hybridWorksheet;
|
|
97
115
|
return hybridWorksheet;
|
|
@@ -133,6 +151,7 @@ std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::getOrAddWorksheet(c
|
|
|
133
151
|
|
|
134
152
|
double HybridXlsxWorkbook::getWorksheetCount() {
|
|
135
153
|
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
154
|
+
ensureOpen();
|
|
136
155
|
return static_cast<double>(_workbook->workbook().worksheetCount());
|
|
137
156
|
});
|
|
138
157
|
}
|
|
@@ -200,7 +219,7 @@ std::shared_ptr<HybridXlsxWorksheetSpec> HybridXlsxWorkbook::clone(const std::st
|
|
|
200
219
|
|
|
201
220
|
_workbook->workbook().cloneSheet(existingName, newName);
|
|
202
221
|
auto worksheet = _workbook->workbook().worksheet(newName);
|
|
203
|
-
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet);
|
|
222
|
+
auto hybridWorksheet = std::make_shared<HybridXlsxWorksheet>(*_workbook, worksheet, _generation);
|
|
204
223
|
_worksheets.push_back(hybridWorksheet);
|
|
205
224
|
_worksheetNames[newName] = hybridWorksheet;
|
|
206
225
|
return hybridWorksheet;
|
|
@@ -222,6 +241,7 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
|
|
|
222
241
|
}
|
|
223
242
|
|
|
224
243
|
try {
|
|
244
|
+
ensureOpen();
|
|
225
245
|
std::string tempPath = tempFilePath("xlsx_temp");
|
|
226
246
|
_workbook->saveAs(tempPath, OpenXLSX::XLForceOverwrite);
|
|
227
247
|
|
|
@@ -256,8 +276,18 @@ std::shared_ptr<Promise<std::shared_ptr<ArrayBuffer>>> HybridXlsxWorkbook::getBu
|
|
|
256
276
|
|
|
257
277
|
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> HybridXlsxWorkbook::toJSON() {
|
|
258
278
|
return withXlsxError(xlsx_error::XLSX_ERROR, [&] {
|
|
279
|
+
ensureOpen();
|
|
259
280
|
auto wb = _workbook->workbook();
|
|
260
|
-
|
|
281
|
+
auto records = workbookToRecords(wb);
|
|
282
|
+
// Sheets without any records (e.g. the default sheet of a fresh workbook)
|
|
283
|
+
// are omitted so an empty workbook round-trips to an empty map.
|
|
284
|
+
std::unordered_map<std::string, std::vector<std::shared_ptr<AnyMap>>> result;
|
|
285
|
+
for (auto& [name, rows] : records) {
|
|
286
|
+
if (!rows.empty()) {
|
|
287
|
+
result.emplace(name, std::move(rows));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
261
291
|
});
|
|
262
292
|
}
|
|
263
293
|
|
|
@@ -19,6 +19,10 @@ public:
|
|
|
19
19
|
void openFromFile(const std::string& path);
|
|
20
20
|
void openFromBuffer(const uint8_t* data, size_t size);
|
|
21
21
|
|
|
22
|
+
// Ensure the underlying XLDocument has been created/opened so the workbook is
|
|
23
|
+
// usable even if it was never explicitly opened (fromJSON, createWorkbook).
|
|
24
|
+
void ensureOpen();
|
|
25
|
+
|
|
22
26
|
std::shared_ptr<HybridXlsxWorksheetSpec> addWorksheet(const std::optional<std::string>& name) override;
|
|
23
27
|
std::shared_ptr<HybridXlsxWorksheetSpec> getWorksheet(double index) override;
|
|
24
28
|
std::shared_ptr<HybridXlsxWorksheetSpec> getWorksheetByName(const std::string& name) override;
|
|
@@ -37,10 +41,15 @@ private:
|
|
|
37
41
|
std::unordered_map<std::string, std::shared_ptr<HybridXlsxWorksheet>> _worksheetNames;
|
|
38
42
|
std::vector<std::shared_ptr<HybridXlsxCellFormat>> _cellFormats;
|
|
39
43
|
bool _finalized;
|
|
44
|
+
// Identifies the current underlying XLDocument. Bumped every time the
|
|
45
|
+
// document is (re)created or opened so per-document caches (cell styles) can
|
|
46
|
+
// tell a recycled heap address apart from the document it was built for.
|
|
47
|
+
uint64_t documentGeneration() const { return _generation; }
|
|
48
|
+
|
|
40
49
|
std::string _tempDir;
|
|
50
|
+
uint64_t _generation = 0;
|
|
41
51
|
|
|
42
52
|
void loadWorksheets();
|
|
43
|
-
void ensureOpen();
|
|
44
53
|
std::string tempFilePath(const char* prefix);
|
|
45
54
|
};
|
|
46
55
|
|