@shbernal/ts-xlsx 1.1.0 → 1.2.0
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 +21 -0
- package/dist/core/address.js +0 -33
- package/dist/core/autofilter.js +0 -9
- package/dist/core/cell.js +0 -131
- package/dist/core/color-resolution.js +0 -60
- package/dist/core/column.js +0 -60
- package/dist/core/comment-thread.js +0 -45
- package/dist/core/conditional-formatting-overlay.js +0 -11
- package/dist/core/conditional-formatting.js +0 -10
- package/dist/core/containers.js +0 -15
- package/dist/core/data-validation-overlay.js +0 -25
- package/dist/core/data-validation.js +0 -7
- package/dist/core/date.js +0 -39
- package/dist/core/formula.js +0 -117
- package/dist/core/grid-edits.js +0 -38
- package/dist/core/image.js +0 -25
- package/dist/core/internal.js +0 -27
- package/dist/core/limits.js +0 -45
- package/dist/core/merge.d.ts +18 -0
- package/dist/core/merge.js +22 -7
- package/dist/core/modern-functions.js +0 -17
- package/dist/core/page-setup.js +0 -5
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +0 -48
- package/dist/core/preserved.js +0 -4
- package/dist/core/protection.js +0 -26
- package/dist/core/range.js +0 -112
- package/dist/core/row-input.d.ts +24 -0
- package/dist/core/row-input.js +29 -0
- package/dist/core/row.js +0 -66
- package/dist/core/style.js +0 -26
- package/dist/core/table-style.js +0 -28
- package/dist/core/table.js +0 -97
- package/dist/core/text-metrics.js +0 -40
- package/dist/core/theme.js +0 -85
- package/dist/core/value.js +0 -94
- package/dist/core/workbook-protection.js +0 -13
- package/dist/core/workbook.js +0 -380
- package/dist/core/worksheet-model.js +2 -23
- package/dist/core/worksheet.js +7 -506
- package/dist/customui/errors.js +0 -10
- package/dist/customui/index.js +0 -5
- package/dist/customui/ribbon.js +0 -43
- package/dist/entries/core.js +0 -6
- package/dist/entries/csv.js +0 -4
- package/dist/entries/customui.js +0 -2
- package/dist/entries/errors.js +0 -13
- package/dist/entries/vba.js +0 -5
- package/dist/entries/xlsb.js +0 -6
- package/dist/entries/xlsx.js +0 -13
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +5 -56
- package/dist/index.js +0 -12
- package/dist/io/csv/read.js +0 -18
- package/dist/io/csv/write.js +0 -18
- package/dist/io/opc/errors.js +0 -30
- package/dist/io/opc/inflate.js +0 -35
- package/dist/io/opc/namespaces.js +0 -15
- package/dist/io/opc/part-paths.js +0 -11
- package/dist/io/opc/read-opc.js +0 -31
- package/dist/io/opc/read-options.js +0 -6
- package/dist/io/opc/rels.js +0 -13
- package/dist/io/opc/sniff-format.js +0 -50
- package/dist/io/style/xf-style.js +0 -31
- package/dist/io/xlsb/errors.js +0 -10
- package/dist/io/xlsb/formula.js +1 -99
- package/dist/io/xlsb/primitives.js +1 -83
- package/dist/io/xlsb/ptg-functions.js +0 -34
- package/dist/io/xlsb/read-shared-strings.js +0 -7
- package/dist/io/xlsb/read-styles.js +3 -66
- package/dist/io/xlsb/read-worksheet.js +4 -87
- package/dist/io/xlsb/read.js +2 -60
- package/dist/io/xlsb/record-stream.js +0 -28
- package/dist/io/xlsb/record-types.js +0 -19
- package/dist/io/xlsx/cell-accumulator.js +0 -32
- package/dist/io/xlsx/cell-value.js +0 -29
- package/dist/io/xlsx/color-xml.d.ts +6 -0
- package/dist/io/xlsx/color-xml.js +42 -0
- package/dist/io/xlsx/comments.js +0 -84
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +1 -97
- package/dist/io/xlsx/data-validation.js +0 -56
- package/dist/io/xlsx/edit-vba.js +0 -54
- package/dist/io/xlsx/errors.js +0 -12
- package/dist/io/xlsx/hyperlinks.js +0 -40
- package/dist/io/xlsx/images.js +0 -32
- package/dist/io/xlsx/namespaces.js +0 -42
- package/dist/io/xlsx/package-plan.js +0 -47
- package/dist/io/xlsx/pivot.js +0 -22
- package/dist/io/xlsx/{pivot-read.js → read-pivot.js} +0 -30
- package/dist/io/xlsx/read-rows.js +1 -83
- package/dist/io/xlsx/{shared-strings-read.js → read-shared-strings.js} +0 -11
- package/dist/io/xlsx/read-styles.d.ts +25 -1
- package/dist/io/xlsx/read-styles.js +61 -108
- package/dist/io/xlsx/read-worksheet.js +1 -88
- package/dist/io/xlsx/read.js +3 -211
- package/dist/io/xlsx/relationships.js +0 -11
- package/dist/io/xlsx/rich-runs.js +0 -16
- package/dist/io/xlsx/rich-text.js +0 -12
- package/dist/io/xlsx/shared-formulas.js +0 -7
- package/dist/io/xlsx/shared-strings.js +0 -21
- package/dist/io/xlsx/sheet-properties.js +1 -64
- package/dist/io/xlsx/styles.d.ts +1 -29
- package/dist/io/xlsx/styles.js +2 -366
- package/dist/io/xlsx/tables.js +5 -52
- package/dist/io/xlsx/threaded-comments.js +0 -103
- package/dist/io/xlsx/workbook-xml.js +0 -94
- package/dist/io/xlsx/worksheet-xml.js +0 -174
- package/dist/io/xlsx/write-stream.js +0 -137
- package/dist/io/xlsx/write.js +1 -134
- package/dist/io/xlsx/x14-ext.js +0 -8
- package/dist/vba/cfb-writer.js +12 -59
- package/dist/vba/cfb.js +2 -32
- package/dist/vba/codepage.js +1 -23
- package/dist/vba/errors.js +0 -15
- package/dist/vba/index.js +0 -10
- package/dist/vba/ms-ovba.js +3 -46
- package/dist/vba/project-editor.js +4 -89
- package/dist/vba/project.js +2 -34
- package/dist/vba/vba-encoding.js +1 -17
- package/dist/xml/errors.js +0 -10
- package/dist/xml/xml-read.js +3 -82
- package/dist/xml/xml.js +0 -39
- package/package.json +8 -9
- package/skills/ts-xlsx-upstream/SKILL.md +178 -0
- /package/dist/io/xlsx/{pivot-read.d.ts → read-pivot.d.ts} +0 -0
- /package/dist/io/xlsx/{shared-strings-read.d.ts → read-shared-strings.d.ts} +0 -0
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
// The streaming `.xlsx` writer: author a workbook incrementally and emit its package through a Node
|
|
2
|
-
// stream, rather than holding the finished bytes in one buffer as {@link writeXlsx} does.
|
|
3
|
-
//
|
|
4
|
-
// A producer adds worksheets, appends rows as it generates them, commits each sheet, then commits the
|
|
5
|
-
// workbook — at which point the package is assembled and streamed out. The output goes to whichever
|
|
6
|
-
// destination the caller chose at construction: its own pull-based `stream` to pipe, a caller-owned
|
|
7
|
-
// `Writable` sink (an outbound upload), or a `filename` the writer opens. In every case `commit()`
|
|
8
|
-
// settles — it resolves once a supplied sink has flushed the whole package and rejects if that sink
|
|
9
|
-
// errors (an unopenable file), never hanging on a finish signal that will not come. The output rides a
|
|
10
|
-
// genuinely
|
|
11
|
-
// streamed zip container (fflate's `Zip`/`ZipDeflate`), which computes each entry's CRC-32
|
|
12
|
-
// incrementally, so the archive is well-formed by construction — the defect the upstream "streaming
|
|
13
|
-
// writer emits a corrupt zip" reports describe is structurally absent here. The bytes reload
|
|
14
|
-
// identically to a whole-file write because both writers share `buildPackageParts` for every part.
|
|
15
|
-
//
|
|
16
|
-
// Peak memory: a row committed with `row.commit()` is serialised to its `<row>` XML immediately and
|
|
17
|
-
// its cells evicted from the model, so an append-driven producer holds only the rows still in flight
|
|
18
|
-
// rather than the whole sheet's cell graph. The eagerly-rendered rows intern into the workbook's live
|
|
19
|
-
// style registry — the same one that emits `xl/styles.xml` — so their style ids stay correct; their
|
|
20
|
-
// XML is handed to `buildPackageParts` and emitted at the head of `<sheetData>`. This eager path runs
|
|
21
|
-
// with strings inline (a shared-strings pool is inherently whole-workbook, so it defeats bounding);
|
|
22
|
-
// turning `useSharedStrings` on falls back to holding every row live until commit. A flushed row is a
|
|
23
|
-
// finished row: it cannot join whole-sheet derivations, so a shared-formula clone in a committed row
|
|
24
|
-
// is rejected, and rows reached only through `getCell` (never `row.commit()`) stay live and serialise
|
|
25
|
-
// the ordinary way. The package bytes themselves are still assembled once at commit — a later slice
|
|
26
|
-
// can flush each sheet's `<sheetData>` straight into its streamed zip entry to bound that half too.
|
|
27
1
|
import { createWriteStream } from 'node:fs';
|
|
28
2
|
import { PassThrough } from 'node:stream';
|
|
29
3
|
import { Zip, ZipDeflate } from 'fflate';
|
|
@@ -33,12 +7,6 @@ import { isSharedFormulaValue } from '../../core/value.js';
|
|
|
33
7
|
import { Workbook } from '../../core/workbook.js';
|
|
34
8
|
import { AuthoringError } from '../../errors.js';
|
|
35
9
|
import { buildColumnDefaults, buildPackageParts, createStyleRegistry, Extent, renderRow, } from './write.js';
|
|
36
|
-
/**
|
|
37
|
-
* A row appended to a {@link WorksheetStreamWriter}. Style its cells through {@link cells}, then call
|
|
38
|
-
* {@link commit} to mark it finished. In an eager (inline-strings) writer, committing serialises the
|
|
39
|
-
* row and frees its cells from the model, bounding peak memory; with `useSharedStrings` on it is a
|
|
40
|
-
* no-op and the row stays live until the workbook commits.
|
|
41
|
-
*/
|
|
42
10
|
export class StreamedRow {
|
|
43
11
|
#cells;
|
|
44
12
|
#sheet;
|
|
@@ -49,12 +17,9 @@ export class StreamedRow {
|
|
|
49
17
|
this.#sheet = sheet;
|
|
50
18
|
this.#number = number;
|
|
51
19
|
}
|
|
52
|
-
/** The cells this row materialised, for styling before it is committed. */
|
|
53
20
|
get cells() {
|
|
54
21
|
return this.#cells;
|
|
55
22
|
}
|
|
56
|
-
/** Finalise the row: an eager writer serialises it now and releases its cells; otherwise a no-op.
|
|
57
|
-
* Committing twice is harmless — the second call does nothing rather than re-emitting the row. */
|
|
58
23
|
commit() {
|
|
59
24
|
if (this.#committed)
|
|
60
25
|
return;
|
|
@@ -62,21 +27,12 @@ export class StreamedRow {
|
|
|
62
27
|
this.#sheet?.flushRow(this.#number, this.#cells);
|
|
63
28
|
}
|
|
64
29
|
}
|
|
65
|
-
/**
|
|
66
|
-
* A worksheet being written incrementally. Append rows with {@link addRow}/{@link addRows}, style
|
|
67
|
-
* cells through {@link getCell}, then {@link commit} to freeze it — after which any further mutation
|
|
68
|
-
* is rejected with a legible error rather than silently accepted or crashing.
|
|
69
|
-
*/
|
|
70
30
|
export class WorksheetStreamWriter {
|
|
71
31
|
#sheet;
|
|
72
32
|
#eager;
|
|
73
33
|
#styles;
|
|
74
34
|
#committed = false;
|
|
75
|
-
// The last row number this writer appended, tracked independently of the model: an eager writer
|
|
76
|
-
// evicts flushed rows, which lowers the model's used range, so leaning on it would reuse numbers.
|
|
77
35
|
#lastRow = 0;
|
|
78
|
-
// The column defaults an eagerly-rendered row inherits, frozen at the first flush so every flushed
|
|
79
|
-
// row composes against the same columns even as later ones are defined.
|
|
80
36
|
#columnDefaults;
|
|
81
37
|
#flushedRows = [];
|
|
82
38
|
#extent = new Extent();
|
|
@@ -86,16 +42,12 @@ export class WorksheetStreamWriter {
|
|
|
86
42
|
this.#eager = eager;
|
|
87
43
|
this.#styles = styles;
|
|
88
44
|
}
|
|
89
|
-
/** The sheet's name. */
|
|
90
45
|
get name() {
|
|
91
46
|
return this.#sheet.name;
|
|
92
47
|
}
|
|
93
|
-
/** The number of rows written so far — spans gaps and formatted-only rows, like the model, and
|
|
94
|
-
* survives the eviction of eagerly-flushed rows. */
|
|
95
48
|
get rowCount() {
|
|
96
49
|
return Math.max(this.#lastRow, this.#sheet.rowCount);
|
|
97
50
|
}
|
|
98
|
-
/** Append one row of values after the last used row; the cells are returned for styling. */
|
|
99
51
|
addRow(values) {
|
|
100
52
|
this.#assertOpen();
|
|
101
53
|
if (!this.#eager)
|
|
@@ -103,21 +55,16 @@ export class WorksheetStreamWriter {
|
|
|
103
55
|
const number = this.#nextRowNumber();
|
|
104
56
|
return new StreamedRow(this.#placeRow(number, values), this, number);
|
|
105
57
|
}
|
|
106
|
-
/** Append a batch of rows in one call, each landing directly below the previous. */
|
|
107
58
|
addRows(rows) {
|
|
108
59
|
this.#assertOpen();
|
|
109
60
|
if (!this.#eager)
|
|
110
61
|
return this.#sheet.addRows(rows).map((cells) => new StreamedRow(cells, null, 0));
|
|
111
62
|
return rows.map((values) => this.addRow(values));
|
|
112
63
|
}
|
|
113
|
-
// The next append position: past both this writer's own high-water mark and any rows a `getCell`
|
|
114
|
-
// materialised, so appends never collide with random-access edits or with already-evicted rows.
|
|
115
64
|
#nextRowNumber() {
|
|
116
65
|
this.#lastRow = Math.max(this.#lastRow, this.#sheet.rowCount) + 1;
|
|
117
66
|
return this.#lastRow;
|
|
118
67
|
}
|
|
119
|
-
// Materialise a positional row at an explicit number (the model's own append would reuse numbers once
|
|
120
|
-
// eviction shrinks its used range), returning the cells for styling. A hole leaves its cell absent.
|
|
121
68
|
#placeRow(number, values) {
|
|
122
69
|
const cells = [];
|
|
123
70
|
values.forEach((value, index) => {
|
|
@@ -127,14 +74,6 @@ export class WorksheetStreamWriter {
|
|
|
127
74
|
});
|
|
128
75
|
return cells;
|
|
129
76
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Serialise an eagerly-committed row and release its cells from the model. Called by
|
|
132
|
-
* {@link StreamedRow.commit}; the row's `<row>` XML is retained (interned into the workbook's live
|
|
133
|
-
* style registry so its ids stay valid) and the cell graph is dropped, bounding peak memory.
|
|
134
|
-
*
|
|
135
|
-
* @throws {AuthoringError} if the row carries a shared-formula cell — a finished row cannot join the
|
|
136
|
-
* whole-sheet formula planning, so shared formulas must be authored through {@link getCell}.
|
|
137
|
-
*/
|
|
138
77
|
flushRow(number, cells) {
|
|
139
78
|
for (const cell of cells) {
|
|
140
79
|
if (isSharedFormulaValue(cell.value)) {
|
|
@@ -145,9 +84,6 @@ export class WorksheetStreamWriter {
|
|
|
145
84
|
}
|
|
146
85
|
this.#columnDefaults ??= buildColumnDefaults(this.#sheet);
|
|
147
86
|
const properties = this.#sheet.getRow(number).properties;
|
|
148
|
-
// The row's outline depth is read off here because eviction is about to take its properties with
|
|
149
|
-
// it, and `<sheetFormatPr outlineLevelRow>` — written long after, once every row is flushed —
|
|
150
|
-
// reports the deepest level on the whole sheet.
|
|
151
87
|
this.#maxRowOutlineLevel = Math.max(this.#maxRowOutlineLevel, properties?.outlineLevel ?? 0);
|
|
152
88
|
const { xml, minCol, maxCol } = renderRow({ number, cells, properties }, {
|
|
153
89
|
columnDefaults: this.#columnDefaults,
|
|
@@ -162,7 +98,6 @@ export class WorksheetStreamWriter {
|
|
|
162
98
|
}
|
|
163
99
|
this.#sheet[INTERNAL].evictRow(number);
|
|
164
100
|
}
|
|
165
|
-
// The rows this writer flushed, or undefined if none — handed to buildPackageParts at commit.
|
|
166
101
|
flushedSheet() {
|
|
167
102
|
if (this.#flushedRows.length === 0)
|
|
168
103
|
return undefined;
|
|
@@ -172,44 +107,22 @@ export class WorksheetStreamWriter {
|
|
|
172
107
|
maxRowOutlineLevel: this.#maxRowOutlineLevel,
|
|
173
108
|
};
|
|
174
109
|
}
|
|
175
|
-
/** Address a cell by its A1 reference to read or style it before the sheet is committed. */
|
|
176
110
|
getCell(reference) {
|
|
177
111
|
this.#assertOpen();
|
|
178
112
|
return this.#sheet.getCell(reference);
|
|
179
113
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Attach a data validation to a range before the sheet is committed. Delegates to the model, so the
|
|
182
|
-
* streamed package emits the `<dataValidations>` block in its CT_Worksheet position — before
|
|
183
|
-
* `<hyperlinks>` — because both writers share one worksheet serializer.
|
|
184
|
-
*/
|
|
185
114
|
addDataValidation(sqref, rule, options = {}) {
|
|
186
115
|
this.#assertOpen();
|
|
187
116
|
this.#sheet.addDataValidation(sqref, rule, options);
|
|
188
117
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Attach a conditional formatting to a range before the sheet is committed. Like every other block,
|
|
191
|
-
* it lands in its schema-mandated slot — after `<mergeCells>`, before `<dataValidations>` and
|
|
192
|
-
* `<hyperlinks>` — since the streamed sheet is serialized through the same path as a buffered write.
|
|
193
|
-
*/
|
|
194
118
|
addConditionalFormatting(formatting) {
|
|
195
119
|
this.#assertOpen();
|
|
196
120
|
this.#sheet.addConditionalFormatting(formatting);
|
|
197
121
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Anchor a workbook image (the id from {@link WorkbookStreamWriter.addImage}) to this sheet,
|
|
200
|
-
* spanning the rectangle from the top-left grid point `tl` to the bottom-right `br`. The streamed
|
|
201
|
-
* package emits the drawing part, its media relationship, and the sheet's `<drawing>` reference
|
|
202
|
-
* exactly as a buffered write does — both writers share `buildPackageParts`.
|
|
203
|
-
*/
|
|
204
122
|
addImage(imageId, anchor) {
|
|
205
123
|
this.#assertOpen();
|
|
206
124
|
this.#sheet.addImage(imageId, anchor);
|
|
207
125
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Apply the sheet's autofilter before it is committed; mirrors {@link Worksheet.autoFilter}. The
|
|
210
|
-
* streamed package emits `<autoFilter>` in its CT_Worksheet slot — after `<sheetProtection>` — and
|
|
211
|
-
* contributes the hidden `_FilterDatabase` defined name, exactly as a buffered write does.
|
|
212
|
-
*/
|
|
213
126
|
set autoFilter(filter) {
|
|
214
127
|
this.#assertOpen();
|
|
215
128
|
this.#sheet.autoFilter = filter;
|
|
@@ -217,24 +130,16 @@ export class WorksheetStreamWriter {
|
|
|
217
130
|
get autoFilter() {
|
|
218
131
|
return this.#sheet.autoFilter;
|
|
219
132
|
}
|
|
220
|
-
/**
|
|
221
|
-
* Apply sheet-level protection before the sheet is committed; mirrors {@link Worksheet.protect}. The
|
|
222
|
-
* shared serializer places `<sheetProtection>` ahead of `<autoFilter>` per CT_Worksheet, so a
|
|
223
|
-
* streamed sheet carrying both stays valid rather than corrupt.
|
|
224
|
-
*/
|
|
225
133
|
protect(password, options = {}) {
|
|
226
134
|
this.#assertOpen();
|
|
227
135
|
this.#sheet.protect(password, options);
|
|
228
136
|
}
|
|
229
|
-
/** Freeze the sheet: no more rows or edits may be added after this. */
|
|
230
137
|
commit() {
|
|
231
138
|
this.#committed = true;
|
|
232
139
|
}
|
|
233
|
-
/** Whether the sheet has been committed. */
|
|
234
140
|
get committed() {
|
|
235
141
|
return this.#committed;
|
|
236
142
|
}
|
|
237
|
-
// Internal: the underlying model sheet, so the workbook writer can serialise it at commit time.
|
|
238
143
|
get model() {
|
|
239
144
|
return this.#sheet;
|
|
240
145
|
}
|
|
@@ -244,26 +149,15 @@ export class WorksheetStreamWriter {
|
|
|
244
149
|
}
|
|
245
150
|
}
|
|
246
151
|
}
|
|
247
|
-
/**
|
|
248
|
-
* A workbook written incrementally to a Node stream. Add worksheets, append their rows, commit each
|
|
249
|
-
* sheet, then {@link commit} the workbook to assemble and stream the package. The produced bytes are
|
|
250
|
-
* available both as the resolved value of `commit()` and through {@link stream} (a Node `Readable`
|
|
251
|
-
* that a caller can `pipe`).
|
|
252
|
-
*/
|
|
253
152
|
export class WorkbookStreamWriter {
|
|
254
153
|
#workbook = new Workbook();
|
|
255
154
|
#sheets = [];
|
|
256
155
|
#writeOptions;
|
|
257
156
|
#sink;
|
|
258
|
-
// The single style registry shared by the eager per-row flush and the commit-time serialisation, so a
|
|
259
|
-
// flushed row's style ids match the styles.xml built from the same table.
|
|
260
157
|
#styles;
|
|
261
|
-
// Eager per-row flushing runs with strings inline; a shared-strings pool is inherently whole-workbook,
|
|
262
|
-
// so it cannot bound memory — turning it on keeps every row live until commit.
|
|
263
158
|
#eager;
|
|
264
159
|
#stream;
|
|
265
160
|
#committed = false;
|
|
266
|
-
/** Calculation settings for the workbook; set `fullCalcOnLoad` before committing to emit it. */
|
|
267
161
|
calcProperties = {};
|
|
268
162
|
constructor(options = {}) {
|
|
269
163
|
this.#writeOptions = { useSharedStrings: options.useSharedStrings ?? false };
|
|
@@ -275,32 +169,19 @@ export class WorkbookStreamWriter {
|
|
|
275
169
|
this.#sink =
|
|
276
170
|
options.stream ?? (options.filename ? createWriteStream(options.filename) : undefined);
|
|
277
171
|
}
|
|
278
|
-
/** Document-level metadata written to the package's core properties. */
|
|
279
172
|
get properties() {
|
|
280
173
|
return this.#workbook.properties;
|
|
281
174
|
}
|
|
282
|
-
/**
|
|
283
|
-
* The output stream carrying the package bytes. A caller drives it with Node's standard idiom —
|
|
284
|
-
* `writer.stream.pipe(out)` — which composes because `pipe` returns its destination. The stream is
|
|
285
|
-
* created lazily on first access so a caller handing the writer its own sink is still free to
|
|
286
|
-
* ignore this one.
|
|
287
|
-
*/
|
|
288
175
|
get stream() {
|
|
289
176
|
this.#stream ??= new PassThrough();
|
|
290
177
|
return this.#stream;
|
|
291
178
|
}
|
|
292
|
-
/**
|
|
293
|
-
* Register a picture's bytes on the workbook's shared media registry and return its id, to anchor
|
|
294
|
-
* on any sheet with {@link WorksheetStreamWriter.addImage}. Mirrors {@link Workbook.addImage}: one
|
|
295
|
-
* media part backs an image anchored on several sheets. Rejected once the workbook is committed.
|
|
296
|
-
*/
|
|
297
179
|
addImage(options) {
|
|
298
180
|
if (this.#committed) {
|
|
299
181
|
throw new AuthoringError('the workbook is already committed — no more images can be registered');
|
|
300
182
|
}
|
|
301
183
|
return this.#workbook.addImage(options);
|
|
302
184
|
}
|
|
303
|
-
/** Create a worksheet and append it to the workbook. */
|
|
304
185
|
addWorksheet(name, options = {}) {
|
|
305
186
|
if (this.#committed) {
|
|
306
187
|
throw new AuthoringError('the workbook is already committed — no more worksheets can be added');
|
|
@@ -309,11 +190,6 @@ export class WorkbookStreamWriter {
|
|
|
309
190
|
this.#sheets.push(sheet);
|
|
310
191
|
return sheet;
|
|
311
192
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Assemble the workbook into its package, stream the bytes through {@link stream}, and resolve with
|
|
314
|
-
* the same bytes. Every sheet is frozen first, so a row added after this rejects legibly. Idempotent
|
|
315
|
-
* only in that a second call throws rather than re-emitting.
|
|
316
|
-
*/
|
|
317
193
|
async commit() {
|
|
318
194
|
if (this.#committed) {
|
|
319
195
|
throw new AuthoringError('the workbook is already committed');
|
|
@@ -323,9 +199,6 @@ export class WorkbookStreamWriter {
|
|
|
323
199
|
sheet.commit();
|
|
324
200
|
if (this.calcProperties.fullCalcOnLoad)
|
|
325
201
|
this.#workbook.fullCalcOnLoad = true;
|
|
326
|
-
// Hand every sheet's eagerly-flushed rows to the shared serialiser, which emits them alongside the
|
|
327
|
-
// rows still live in the model. The style registry is the same one the flushed rows interned into,
|
|
328
|
-
// so styles.xml stays consistent with the ids already baked into their XML.
|
|
329
202
|
const flushed = new Map();
|
|
330
203
|
for (const sheet of this.#sheets) {
|
|
331
204
|
const sheetFlushed = sheet.flushedSheet();
|
|
@@ -339,9 +212,6 @@ export class WorkbookStreamWriter {
|
|
|
339
212
|
});
|
|
340
213
|
const owned = this.#stream;
|
|
341
214
|
const sink = this.#sink;
|
|
342
|
-
// Track the caller sink's terminal state before writing a byte, so an open failure that errors on a
|
|
343
|
-
// later tick (a bad filename) is caught rather than lost — the whole point of the reject-not-hang
|
|
344
|
-
// contract.
|
|
345
215
|
const sinkSettled = sink ? settleOnFinish(sink) : undefined;
|
|
346
216
|
const bytes = await streamZipPackage(parts, (chunk) => {
|
|
347
217
|
owned?.write(chunk);
|
|
@@ -353,19 +223,12 @@ export class WorkbookStreamWriter {
|
|
|
353
223
|
return bytes;
|
|
354
224
|
}
|
|
355
225
|
}
|
|
356
|
-
// Resolve when a caller-supplied sink has flushed the whole package (`finish`), or reject if it errors
|
|
357
|
-
// (`error`) — the commit promise must settle either way, never hang. Whichever fires first wins; the
|
|
358
|
-
// other is ignored.
|
|
359
226
|
function settleOnFinish(sink) {
|
|
360
227
|
return new Promise((resolve, reject) => {
|
|
361
228
|
sink.once('finish', resolve);
|
|
362
229
|
sink.once('error', reject);
|
|
363
230
|
});
|
|
364
231
|
}
|
|
365
|
-
// Zip the package parts through fflate's streaming container, forwarding each output chunk to `onChunk`
|
|
366
|
-
// as it is produced and resolving with the whole archive once the final chunk arrives. `ZipDeflate`
|
|
367
|
-
// deflates synchronously, so the callback fires inline as each part is pushed — the CRC-32 fflate
|
|
368
|
-
// stamps into every entry's header therefore always matches the bytes it just compressed.
|
|
369
232
|
function streamZipPackage(parts, onChunk) {
|
|
370
233
|
return new Promise((resolve, reject) => {
|
|
371
234
|
const collected = [];
|
package/dist/io/xlsx/write.js
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
// The buffered `.xlsx` writer: a Workbook model in, an OPC zip package out.
|
|
2
|
-
//
|
|
3
|
-
// It serialises the part of the model that exists today — worksheets; cells holding a
|
|
4
|
-
// number, string, boolean, or formula; column/row formatting; page margins and
|
|
5
|
-
// header/footer; merged ranges; and worksheet tables — into a valid package (content
|
|
6
|
-
// types, relationships, workbook, per-sheet XML, table parts, the default theme and
|
|
7
|
-
// stylesheet, and core/app properties). Styles, images, and the richer value kinds land
|
|
8
|
-
// as the model grows; until then the writer refuses a value it cannot represent
|
|
9
|
-
// faithfully rather than emitting a lossy or corrupt package.
|
|
10
|
-
//
|
|
11
|
-
// This module is the orchestrator: it plans the package graph (via `package-plan.ts`) and
|
|
12
|
-
// stitches the serialised parts (from `workbook-xml.ts` and `worksheet-xml.ts`) into the
|
|
13
|
-
// part map. The row/cell renderer and the sheet's public render types live in
|
|
14
|
-
// `worksheet-xml.ts` and are re-exported here so the streaming writer's import surface is
|
|
15
|
-
// unchanged.
|
|
16
1
|
import { strToU8, zip, zipSync } from 'fflate';
|
|
17
2
|
import { DEFAULT_THEME_XML } from '../../core/theme.js';
|
|
18
3
|
import { AuthoringError } from '../../errors.js';
|
|
@@ -31,35 +16,9 @@ import { personsXml, threadedCommentsXml } from './threaded-comments.js';
|
|
|
31
16
|
import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
|
|
32
17
|
import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
|
|
33
18
|
export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
|
|
34
|
-
/**
|
|
35
|
-
* Serialise a workbook into an `.xlsx` package.
|
|
36
|
-
*
|
|
37
|
-
* @throws {AuthoringError} if the workbook has no worksheets (a zero-sheet package is corrupt),
|
|
38
|
-
* or holds a value the writer cannot yet represent.
|
|
39
|
-
*/
|
|
40
19
|
export function writeXlsx(workbook, options = {}) {
|
|
41
20
|
return zipSync(buildPackageParts(workbook, options), { level: 6 });
|
|
42
21
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Serialise a workbook into an `.xlsx` package, deflating off the calling thread.
|
|
45
|
-
*
|
|
46
|
-
* Produces the same package {@link writeXlsx} does — every part compresses to identical bytes — and
|
|
47
|
-
* exists for one reason: DEFLATE dominates the cost of writing a large workbook, and {@link writeXlsx}
|
|
48
|
-
* spends all of it on the caller's thread. Here `fflate` deflates each part in a worker, so the event
|
|
49
|
-
* loop keeps turning (stalls drop from the whole write to tens of milliseconds) and parts compress in
|
|
50
|
-
* parallel, which on a multi-sheet workbook also finishes sooner. On a single-sheet workbook there is
|
|
51
|
-
* only one part to deflate, so expect responsiveness rather than speed.
|
|
52
|
-
*
|
|
53
|
-
* Building the parts still happens on the calling thread — only compression moves. That is why there
|
|
54
|
-
* is no `readXlsxAsync` mirroring this: reading is dominated by XML parsing and model building, which
|
|
55
|
-
* no worker can take, and the reader's zip-bomb ceiling is enforced by counting output between
|
|
56
|
-
* synchronous input slices. See ADR-0024.
|
|
57
|
-
*
|
|
58
|
-
* @throws {AuthoringError} — as a rejection — under the same conditions as {@link writeXlsx};
|
|
59
|
-
* the part-building it shares happens before any worker is involved. A failure raised by the zip
|
|
60
|
-
* layer itself (including an environment that cannot spawn a worker) propagates unwrapped, exactly
|
|
61
|
-
* as it does from {@link writeXlsx}.
|
|
62
|
-
*/
|
|
63
22
|
export async function writeXlsxAsync(workbook, options = {}) {
|
|
64
23
|
const parts = buildPackageParts(workbook, options);
|
|
65
24
|
return await new Promise((resolve, reject) => {
|
|
@@ -71,47 +30,22 @@ export async function writeXlsxAsync(workbook, options = {}) {
|
|
|
71
30
|
});
|
|
72
31
|
});
|
|
73
32
|
}
|
|
74
|
-
/**
|
|
75
|
-
* A style registry seeded from a workbook's read-in style layers (differential styles, named cell
|
|
76
|
-
* styles, custom indexed palette), ready to intern authored styles after them. Both the buffered
|
|
77
|
-
* pass and the streaming writer build their registry through here so a cell's style id means the
|
|
78
|
-
* same thing whichever writer emits it.
|
|
79
|
-
*/
|
|
80
33
|
export function createStyleRegistry(workbook) {
|
|
81
|
-
// Font id 0 is the workbook's own default face, resolved from what it declared, what was authored,
|
|
82
|
-
// and its theme's body typeface — never an assumed Calibri, which would re-face every empty cell
|
|
83
|
-
// and change the metric every character-unit column width is expressed in.
|
|
84
34
|
const styles = new StyleRegistry({
|
|
85
35
|
defaultFont: workbook.defaultFont,
|
|
86
36
|
...(workbook.declaredDefaultFont === undefined
|
|
87
37
|
? {}
|
|
88
38
|
: { declaredDefaultFont: workbook.declaredDefaultFont }),
|
|
89
39
|
});
|
|
90
|
-
// Seed the differential-style table with the fragments read from a source file so conditional
|
|
91
|
-
// formatting's dxfId references stay valid; styles authored on rules append after them.
|
|
92
40
|
styles.seedDifferentialStyles(workbook.differentialStyles);
|
|
93
|
-
// Seed the named cell-style layer (cellStyleXfs/cellStyles) so each style's facets re-intern into the
|
|
94
|
-
// rebuilt sub-tables and a cell's xfId link stays valid; without any, the default Normal alone emits.
|
|
95
41
|
styles.seedNamedStyles(workbook.namedStyles);
|
|
96
|
-
// Seed the custom indexed-color palette so it re-emits verbatim and an `indexed="…"` colour keeps
|
|
97
|
-
// its intended RGB; a workbook that never overrode the palette seeds nothing and writes no <colors>.
|
|
98
42
|
styles.seedIndexedColors(workbook.indexedColors);
|
|
99
|
-
// Seed the author's "Recent Colors" swatches so they re-emit unchanged rather than being reset.
|
|
100
43
|
styles.seedMruColors(workbook.mruColors);
|
|
101
|
-
// Seed the custom table-style definitions so a table's `styleName` still names a real definition and
|
|
102
|
-
// each element's dxfId still indexes the differential-style table seeded above at its original index.
|
|
103
44
|
styles.seedTableStyles(workbook.tableStyles);
|
|
104
|
-
// Authored styles append after the preserved ones, and intern their elements' formatting after the
|
|
105
|
-
// seeded dxfs — the ordering that keeps every preserved dxfId pointing where it did.
|
|
106
45
|
for (const style of workbook.customTableStyles)
|
|
107
46
|
styles.addTableStyle(style);
|
|
108
47
|
return styles;
|
|
109
48
|
}
|
|
110
|
-
// Resolve one sheet's tail reference ids (the `<drawing>`/`<legacyDrawing>`/`<legacyDrawingHF>`/
|
|
111
|
-
// `<picture>` slots and the slicer list) from its plan. A preserved `<drawing>` and a modeled one are
|
|
112
|
-
// mutually exclusive, so the drawing slot takes whichever exists; a comment's VML rides the legacy-
|
|
113
|
-
// drawing slot; and each preserved slicer surfaces its rel id so the `<x14:slicerList>` can reactivate
|
|
114
|
-
// the widget rather than orphan its part.
|
|
115
49
|
function resolveSheetReferences(plan) {
|
|
116
50
|
const refs = plan.preservedRefs;
|
|
117
51
|
const preservedDrawingRelId = refs.find((ref) => ref.element === 'drawing')?.relId ?? null;
|
|
@@ -128,39 +62,15 @@ function resolveSheetReferences(plan) {
|
|
|
128
62
|
slicerRelIds,
|
|
129
63
|
};
|
|
130
64
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Assemble a workbook into the map of OPC package parts (part name → bytes) that make up an `.xlsx`,
|
|
133
|
-
* short of zipping them. This is the whole serialisation — content types, relationships, workbook,
|
|
134
|
-
* per-sheet XML, styles, theme, media, tables, and props — factored out of {@link writeXlsx} so the
|
|
135
|
-
* streaming writer can drive the identical parts through a streamed zip container rather than
|
|
136
|
-
* `zipSync`. Neither writer duplicates a byte of serialisation.
|
|
137
|
-
*
|
|
138
|
-
* @throws {AuthoringError} if the workbook has no worksheets, or holds a value the writer cannot represent.
|
|
139
|
-
*/
|
|
140
65
|
export function buildPackageParts(workbook, options = {}) {
|
|
141
66
|
const sheets = workbook.worksheets;
|
|
142
67
|
if (sheets.length === 0) {
|
|
143
68
|
throw new AuthoringError('cannot write a workbook with no worksheets — a zero-sheet package is corrupt to Excel');
|
|
144
69
|
}
|
|
145
|
-
// With the option on, plain string cell values are pooled into a shared-strings table interned
|
|
146
|
-
// during the sheet pass (like the style registry); a null table keeps every string inline.
|
|
147
70
|
const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
|
|
148
|
-
// Anchored images share workbook-wide media: every image a sheet references becomes one media part,
|
|
149
|
-
// addressed by a global number. Resolved before the sheet loop so a drawing's embeds can target it.
|
|
150
71
|
const media = planMedia(workbook, sheets);
|
|
151
|
-
// Content the model does not interpret — a vector-shape drawing, a header/footer image, a pivot
|
|
152
|
-
// table and its caches, a slicer — captured on read and re-emitted verbatim onto collision-proof
|
|
153
|
-
// paths. Preserved parts are renumbered past the parts the writer generates of the same kind
|
|
154
|
-
// (drawings, VML, media), so resolving them needs only those generated counts; each sheet's
|
|
155
|
-
// preserved references take their sheet-local rel ids in canonical position in the loop below.
|
|
156
72
|
const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
|
|
157
73
|
const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length);
|
|
158
|
-
// Plan every sheet's parts in a single pass, drawing each sheet-local relationship id from that
|
|
159
|
-
// sheet's allocator in canonical order: tables, drawing, comments (VML + comments part), printer
|
|
160
|
-
// settings, external hyperlinks, background, preserved references, pivot tables. One running
|
|
161
|
-
// allocator per sheet is what keeps the ids gapless and collision-free — no step re-derives its
|
|
162
|
-
// offset by summing the ones before it, so none can drift into another's id. Part numbers (tables,
|
|
163
|
-
// drawings, pivots) are global across the workbook and counted here in the same pass.
|
|
164
74
|
let tableNumber = 0;
|
|
165
75
|
let drawingNumber = 0;
|
|
166
76
|
let pivotNumber = 0;
|
|
@@ -177,8 +87,6 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
177
87
|
const registered = workbook.getImage(image.imageId);
|
|
178
88
|
return {
|
|
179
89
|
anchor: image.anchor,
|
|
180
|
-
// The embed id is local to the drawing part's own rels, not the sheet's, so it is numbered
|
|
181
|
-
// per image from rId1 rather than drawn from the sheet allocator.
|
|
182
90
|
embedId: `rId${j + 1}`,
|
|
183
91
|
mediaNumber: media.numberById.get(image.imageId),
|
|
184
92
|
extension: registered.extension,
|
|
@@ -186,12 +94,6 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
186
94
|
});
|
|
187
95
|
drawing = { number: ++drawingNumber, relId: rels.next(), images };
|
|
188
96
|
}
|
|
189
|
-
// A conversation and the legacy fallback `<comment>` that binds its cell to it are two halves of one
|
|
190
|
-
// representation, so both are derived from this single list and neither can be emitted without the
|
|
191
|
-
// other. Verified against desktop Excel: a `tc=` fallback whose thread part is absent shows as neither
|
|
192
|
-
// a thread nor a note — the text disappears rather than degrading — and a thread part whose fallback is
|
|
193
|
-
// absent is ignored, leaving the cell blank. A thread with no messages is not one of them: it has
|
|
194
|
-
// nothing to say, and no head id for its replies or its fallback to hang off.
|
|
195
97
|
const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
|
|
196
98
|
const sheetComments = collectComments(sheet, threads);
|
|
197
99
|
const comments = sheetComments.length === 0
|
|
@@ -221,8 +123,6 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
221
123
|
const preservedRefs = (preserved.perSheet[i] ?? []).map((reference) => ({ ...reference, relId: rels.next() }));
|
|
222
124
|
const pivots = sheet.pivotTables.map((table) => {
|
|
223
125
|
const number = ++pivotNumber;
|
|
224
|
-
// Each pivot is numbered globally (its parts and its `cacheId` must be workbook-unique); the
|
|
225
|
-
// workbook relationship reaching its cache is assigned once the modeled workbook rels are known.
|
|
226
126
|
return { number, cacheId: String(number), table, sheetRelId: rels.next(), workbookRelId: '' };
|
|
227
127
|
});
|
|
228
128
|
return {
|
|
@@ -239,22 +139,11 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
239
139
|
});
|
|
240
140
|
const allTables = perSheet.flatMap((plan) => plan.tables);
|
|
241
141
|
const allPivots = perSheet.flatMap((plan) => plan.pivots);
|
|
242
|
-
// Serialise the worksheets first: interning each cell/row fill into the style table is a
|
|
243
|
-
// side effect of that pass, so styles.xml can only be generated once every sheet is done. The
|
|
244
|
-
// streaming writer supplies its own registry (already seeded, and already carrying its eagerly
|
|
245
|
-
// flushed rows' styles); the buffered path seeds a fresh one here.
|
|
246
142
|
const styles = options.styles ?? createStyleRegistry(workbook);
|
|
247
143
|
const sheetXml = sheets.map((sheet, i) => {
|
|
248
144
|
const plan = perSheet[i];
|
|
249
|
-
return worksheetXml(sheet, plan.tables, styles, resolveSheetReferences(plan), plan.hyperlinks, sharedStrings,
|
|
250
|
-
// Exactly one sheet is marked selected; the model resolves which, so no package can ship with
|
|
251
|
-
// none selected (no view initialised on open) or with several (an accidental group selection,
|
|
252
|
-
// where an edit to one sheet lands on all of them).
|
|
253
|
-
i === workbook.activeTabIndex, options.flushed?.get(sheet));
|
|
145
|
+
return worksheetXml(sheet, plan.tables, styles, resolveSheetReferences(plan), plan.hyperlinks, sharedStrings, i === workbook.activeTabIndex, options.flushed?.get(sheet));
|
|
254
146
|
});
|
|
255
|
-
// The pool is filled only once every sheet is serialised. Emit the part (and its rel + content
|
|
256
|
-
// type) solely when the option is on and at least one string was interned, so a workbook with no
|
|
257
|
-
// string cells never fabricates an empty table.
|
|
258
147
|
const hasSharedStrings = sharedStrings !== null && !sharedStrings.isEmpty;
|
|
259
148
|
const commentNumbers = perSheet
|
|
260
149
|
.map((plan) => plan.comments)
|
|
@@ -272,14 +161,7 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
272
161
|
.map((plan) => plan.threadedComments)
|
|
273
162
|
.filter((t) => t !== null)
|
|
274
163
|
.map((t) => t.number);
|
|
275
|
-
// The identity registry is emitted only beside the thread parts that point into it. With no conversation
|
|
276
|
-
// in the package nothing can reference a `<person>`, so the part would be a workbook-level relationship
|
|
277
|
-
// to dead weight — and it is the messages, not the registry, that make an identity worth carrying.
|
|
278
164
|
const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
|
|
279
|
-
// A preserved workbook reference's relationship id follows the modeled workbook rels — the sheets,
|
|
280
|
-
// styles, theme, and (when emitted) shared strings and the threaded-comment person registry — so adding
|
|
281
|
-
// one never renumbers an id already used. The workbook body and its rels part are wired from the same
|
|
282
|
-
// assignment, so a pivot cache's `<pivotCaches>` registration and its relationship agree on the id.
|
|
283
165
|
const modeledWorkbookRelCount = sheets.length + FIXED_WORKBOOK_REL_COUNT + (hasSharedStrings ? 1 : 0);
|
|
284
166
|
const personsRelId = persons.length === 0 ? null : `rId${modeledWorkbookRelCount + 1}`;
|
|
285
167
|
const workbookRelBase = modeledWorkbookRelCount + (personsRelId === null ? 0 : 1);
|
|
@@ -287,8 +169,6 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
287
169
|
...ref,
|
|
288
170
|
relId: `rId${workbookRelBase + 1 + i}`,
|
|
289
171
|
}));
|
|
290
|
-
// A generated pivot cache's workbook relationship follows the preserved ones; the assignment
|
|
291
|
-
// mutates the shared plan so the `<pivotCaches>` body and the rels part read the same id.
|
|
292
172
|
const pivotWorkbookRelBase = workbookRelBase + preserved.workbook.length;
|
|
293
173
|
allPivots.forEach((pivot, i) => {
|
|
294
174
|
pivot.workbookRelId = `rId${pivotWorkbookRelBase + 1 + i}`;
|
|
@@ -302,17 +182,12 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
302
182
|
'xl/_rels/workbook.xml.rels': strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
|
|
303
183
|
'xl/styles.xml': strToU8(styles.toXml()),
|
|
304
184
|
};
|
|
305
|
-
// A theme read from a source package is emitted through the preserved-part path, closure and all —
|
|
306
|
-
// with any authored overrides already spliced into its entry part by the planner. A workbook without
|
|
307
|
-
// one gets its authored theme, or the library's default, which the stylesheet's `theme="1"` default
|
|
308
|
-
// font still needs something to resolve against.
|
|
309
185
|
if (!preserved.themeEmitted) {
|
|
310
186
|
files[THEME_PART_PATH] = strToU8(workbook.authoredThemeXml() ?? DEFAULT_THEME_XML);
|
|
311
187
|
}
|
|
312
188
|
if (hasSharedStrings) {
|
|
313
189
|
files['xl/sharedStrings.xml'] = strToU8(sharedStrings.toXml());
|
|
314
190
|
}
|
|
315
|
-
// Singular and unnumbered, unlike the per-sheet thread parts: one registry serves the whole workbook.
|
|
316
191
|
if (persons.length > 0)
|
|
317
192
|
files['xl/persons/person.xml'] = strToU8(personsXml(persons));
|
|
318
193
|
for (const part of media.parts) {
|
|
@@ -326,9 +201,6 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
326
201
|
emitPreservedParts(files, preserved.parts);
|
|
327
202
|
return files;
|
|
328
203
|
}
|
|
329
|
-
// Emit each sheet's own parts: the sheet XML, its rels part (only when the sheet references something),
|
|
330
|
-
// and the drawing/comment/printer-settings parts those relationships point at. `sheetXml[i]` is the
|
|
331
|
-
// already-serialised body for `perSheet[i]`, indexed in lockstep.
|
|
332
204
|
function emitSheetParts(files, perSheet, sheetXml) {
|
|
333
205
|
perSheet.forEach((plan, i) => {
|
|
334
206
|
const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
|
|
@@ -363,9 +235,6 @@ function emitSheetParts(files, perSheet, sheetXml) {
|
|
|
363
235
|
}
|
|
364
236
|
});
|
|
365
237
|
}
|
|
366
|
-
// Emit every pivot table's three chained parts. A pivot spans a pivot-table part (linked from its host
|
|
367
|
-
// sheet) that references a cache definition, which references its cache records. Each cache carries a
|
|
368
|
-
// rels part naming the next link by `rId1` — the id the definition/table XML resolves against.
|
|
369
238
|
function emitPivotParts(files, allPivots) {
|
|
370
239
|
for (const pivot of allPivots) {
|
|
371
240
|
const { number, cacheId, table } = pivot;
|
|
@@ -384,8 +253,6 @@ function emitPivotParts(files, allPivots) {
|
|
|
384
253
|
files[`xl/pivotCache/pivotCacheRecords${number}.xml`] = strToU8(pivotCacheRecordsXml(table));
|
|
385
254
|
}
|
|
386
255
|
}
|
|
387
|
-
// Emit the verbatim-preserved parts (and their rewired rels) last: their paths are collision-proof, so
|
|
388
|
-
// ordering against the generated parts does not matter.
|
|
389
256
|
function emitPreservedParts(files, parts) {
|
|
390
257
|
for (const part of parts) {
|
|
391
258
|
files[part.path] = part.bytes;
|
package/dist/io/xlsx/x14-ext.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { X14_NS } from './namespaces.js';
|
|
2
|
-
/**
|
|
3
|
-
* Wrap an x14 extension body in the `<ext>` envelope every worksheet- and workbook-level x14
|
|
4
|
-
* extension shares: a `uri` scoping the extension to its feature (a fixed GUID from
|
|
5
|
-
* {@link namespaces.ts}) plus the `xmlns:x14` binding the extension namespace on the element itself —
|
|
6
|
-
* Excel declares it per `<ext>`, never at the part root. Callers needing an `<extLst>` wrapper add it
|
|
7
|
-
* around the result; the producers the worksheet serialiser gathers return this bare so several exts
|
|
8
|
-
* compose under a single shared `<extLst>`.
|
|
9
|
-
*/
|
|
10
2
|
export function x14Ext(uri, body) {
|
|
11
3
|
return `<ext uri="${uri}" xmlns:x14="${X14_NS}">${body}</ext>`;
|
|
12
4
|
}
|