@shbernal/ts-xlsx 3.0.0 → 3.1.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 +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +2 -4
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +3 -0
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +9 -1
- package/dist/core/conditional-formatting.js +34 -18
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +6 -0
- package/dist/core/data-validation.js +15 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +13 -9
- package/dist/core/image.js +7 -5
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +5 -4
- package/dist/core/merge.js +18 -19
- package/dist/core/pivot-table.js +63 -45
- package/dist/core/protection.js +2 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +3 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +27 -9
- package/dist/core/table.js +51 -34
- package/dist/core/theme.js +6 -5
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +55 -6
- package/dist/core/worksheet.js +36 -44
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +1 -1
- package/dist/io/opc/read-opc.d.ts +54 -14
- package/dist/io/opc/read-opc.js +42 -20
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +49 -34
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +385 -596
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +44 -23
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -32
- package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
- package/dist/io/xlsx/conditional-formatting.js +30 -26
- package/dist/io/xlsx/data-validation.js +29 -27
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +14 -21
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +37 -5
- package/dist/io/xlsx/hyperlinks.js +22 -13
- package/dist/io/xlsx/images.js +11 -13
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +14 -2
- package/dist/io/xlsx/package-plan.js +39 -11
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +45 -31
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +6 -71
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +2 -1
- package/dist/io/xlsx/read-worksheet.js +35 -164
- package/dist/io/xlsx/read.d.ts +5 -12
- package/dist/io/xlsx/read.js +73 -406
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +145 -9
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -207
- package/dist/io/xlsx/tables.js +10 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +60 -34
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +48 -229
- package/dist/io/xlsx/write-stream.d.ts +65 -16
- package/dist/io/xlsx/write-stream.js +50 -20
- package/dist/io/xlsx/write.d.ts +1 -2
- package/dist/io/xlsx/write.js +114 -74
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +8 -0
- package/dist/vba/cfb-format.js +1 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +12 -9
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -51
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +51 -16
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +27 -8
- package/dist/vba/vba-encoding.js +4 -5
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +72 -10
- package/dist/xml/xml-read.js +94 -18
- package/dist/xml/xml-scan.d.ts +15 -37
- package/dist/xml/xml-scan.js +12 -40
- package/dist/xml/xml.d.ts +28 -27
- package/dist/xml/xml.js +24 -24
- package/package.json +12 -7
- /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
|
@@ -6,9 +6,12 @@ import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
|
|
|
6
6
|
import { INTERNAL } from '../../core/internal.js';
|
|
7
7
|
import { isSharedFormulaValue } from '../../core/value.js';
|
|
8
8
|
import { Workbook } from '../../core/workbook.js';
|
|
9
|
-
import { AuthoringError } from '../../errors.js';
|
|
9
|
+
import { AuthoringError, quoted } from '../../errors.js';
|
|
10
10
|
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
11
|
-
import {
|
|
11
|
+
import { collectNotes } from './comments.js';
|
|
12
|
+
import { collectHyperlinks } from './hyperlinks.js';
|
|
13
|
+
import { buildColumnDefaults, Extent, renderRow, } from './row-xml.js';
|
|
14
|
+
import { buildPackageParts, createStyleRegistry } from './write.js';
|
|
12
15
|
export class StreamedRow {
|
|
13
16
|
#cells;
|
|
14
17
|
#sheet;
|
|
@@ -26,7 +29,7 @@ export class StreamedRow {
|
|
|
26
29
|
if (this.#committed)
|
|
27
30
|
return;
|
|
28
31
|
this.#committed = true;
|
|
29
|
-
this.#sheet?.flushRow(this.#number, this.#cells);
|
|
32
|
+
this.#sheet?.[INTERNAL].flushRow(this.#number, this.#cells);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
export class WorksheetStreamWriter {
|
|
@@ -39,12 +42,21 @@ export class WorksheetStreamWriter {
|
|
|
39
42
|
#flushedRows = [];
|
|
40
43
|
#flushedNumbers = new Set();
|
|
41
44
|
#extent = new Extent();
|
|
42
|
-
#
|
|
43
|
-
|
|
45
|
+
#rowOutline = new Map();
|
|
46
|
+
#hyperlinks = [];
|
|
47
|
+
#notes = [];
|
|
48
|
+
#dateEpoch;
|
|
49
|
+
constructor(sheet, eager, styles, dateEpoch) {
|
|
44
50
|
this.#sheet = sheet;
|
|
45
51
|
this.#eager = eager;
|
|
46
52
|
this.#styles = styles;
|
|
53
|
+
this.#dateEpoch = dateEpoch;
|
|
47
54
|
}
|
|
55
|
+
static [INTERNAL] = {
|
|
56
|
+
create(sheet, eager, styles, dateEpoch) {
|
|
57
|
+
return new WorksheetStreamWriter(sheet, eager, styles, dateEpoch);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
48
60
|
get name() {
|
|
49
61
|
return this.#sheet.name;
|
|
50
62
|
}
|
|
@@ -77,45 +89,53 @@ export class WorksheetStreamWriter {
|
|
|
77
89
|
});
|
|
78
90
|
return cells;
|
|
79
91
|
}
|
|
80
|
-
flushRow(number, cells) {
|
|
92
|
+
#flushRow(number, cells) {
|
|
81
93
|
for (const cell of cells) {
|
|
82
94
|
if (isSharedFormulaValue(cell.value)) {
|
|
83
|
-
throw new AuthoringError(`row ${number} of streamed sheet
|
|
95
|
+
throw new AuthoringError(`row ${number} of streamed sheet ${quoted(this.#sheet.name)} carries a shared-formula cell; a ` +
|
|
84
96
|
'committed row is finalised before the sheet is planned, so author shared formulas through ' +
|
|
85
97
|
'getCell (leaving the row uncommitted) instead');
|
|
86
98
|
}
|
|
87
99
|
}
|
|
88
100
|
this.#columnDefaults ??= buildColumnDefaults(this.#sheet);
|
|
89
101
|
const properties = this.#sheet.getRow(number).properties;
|
|
90
|
-
this.#
|
|
91
|
-
|
|
102
|
+
this.#rowOutline.set(number, {
|
|
103
|
+
outlineLevel: properties?.outlineLevel ?? 0,
|
|
104
|
+
hidden: properties?.hidden ?? false,
|
|
105
|
+
});
|
|
106
|
+
this.#hyperlinks.push(...collectHyperlinks(cells));
|
|
107
|
+
this.#notes.push(...collectNotes(cells));
|
|
108
|
+
const { xml, attrs, minCol, maxCol } = renderRow({ number, cells, properties }, {
|
|
92
109
|
columnDefaults: this.#columnDefaults,
|
|
93
110
|
styles: this.#styles,
|
|
94
111
|
sharedStrings: null,
|
|
95
112
|
sharedRoles: new Map(),
|
|
96
113
|
collapsedSummaries: new Set(),
|
|
114
|
+
dateEpoch: this.#dateEpoch,
|
|
97
115
|
});
|
|
98
116
|
if (xml !== '') {
|
|
99
|
-
this.#flushedRows.push({ number, xml });
|
|
117
|
+
this.#flushedRows.push({ number, xml, attrs });
|
|
100
118
|
this.#extent.add(number, minCol, maxCol);
|
|
101
119
|
}
|
|
102
120
|
this.#flushedNumbers.add(number);
|
|
103
121
|
this.#sheet[INTERNAL].evictRow(number);
|
|
104
122
|
}
|
|
105
|
-
flushedSheet() {
|
|
123
|
+
#flushedSheet() {
|
|
106
124
|
if (this.#flushedRows.length === 0)
|
|
107
125
|
return undefined;
|
|
108
126
|
return {
|
|
109
127
|
rows: this.#flushedRows,
|
|
110
128
|
extent: this.#extent,
|
|
111
|
-
|
|
129
|
+
rowOutline: this.#rowOutline,
|
|
130
|
+
hyperlinks: this.#hyperlinks,
|
|
131
|
+
notes: this.#notes,
|
|
112
132
|
};
|
|
113
133
|
}
|
|
114
134
|
getCell(reference) {
|
|
115
135
|
this.#assertOpen();
|
|
116
136
|
const target = tryDecodeCellRef(reference);
|
|
117
137
|
if (target !== undefined && this.#flushedNumbers.has(target.row)) {
|
|
118
|
-
throw new AuthoringError(`cell ${reference} of streamed sheet
|
|
138
|
+
throw new AuthoringError(`cell ${reference} of streamed sheet ${quoted(this.#sheet.name)} is in row ${target.row}, which is ` +
|
|
119
139
|
'already committed: its bytes are written and its cells released, so this value could only ' +
|
|
120
140
|
'be emitted as a second row with that number. Style a row before committing it.');
|
|
121
141
|
}
|
|
@@ -153,9 +173,15 @@ export class WorksheetStreamWriter {
|
|
|
153
173
|
get model() {
|
|
154
174
|
return this.#sheet;
|
|
155
175
|
}
|
|
176
|
+
[INTERNAL] = {
|
|
177
|
+
flushRow: (number, cells) => {
|
|
178
|
+
this.#flushRow(number, cells);
|
|
179
|
+
},
|
|
180
|
+
flushedSheet: () => this.#flushedSheet(),
|
|
181
|
+
};
|
|
156
182
|
#assertOpen() {
|
|
157
183
|
if (this.#committed) {
|
|
158
|
-
throw new AuthoringError(`worksheet
|
|
184
|
+
throw new AuthoringError(`worksheet ${quoted(this.#sheet.name)} is already committed: its rows are finalised and no more can be added`);
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
187
|
}
|
|
@@ -172,6 +198,8 @@ export class WorkbookStreamWriter {
|
|
|
172
198
|
constructor(options = {}) {
|
|
173
199
|
this.#writeOptions = { useSharedStrings: options.useSharedStrings ?? false };
|
|
174
200
|
this.#eager = !this.#writeOptions.useSharedStrings;
|
|
201
|
+
if (options.dateEpoch !== undefined)
|
|
202
|
+
this.#workbook.dateEpoch = options.dateEpoch;
|
|
175
203
|
this.#styles = createStyleRegistry(this.#workbook);
|
|
176
204
|
if (options.stream && options.filename) {
|
|
177
205
|
throw new AuthoringError('provide either a stream or a filename to the streaming writer, not both');
|
|
@@ -192,7 +220,7 @@ export class WorkbookStreamWriter {
|
|
|
192
220
|
}
|
|
193
221
|
addWorksheet(name, options = {}) {
|
|
194
222
|
this.#assertOpen('no more worksheets can be added');
|
|
195
|
-
const sheet =
|
|
223
|
+
const sheet = WorksheetStreamWriter[INTERNAL].create(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles, this.#workbook.dateEpoch);
|
|
196
224
|
this.#sheets.push(sheet);
|
|
197
225
|
return sheet;
|
|
198
226
|
}
|
|
@@ -205,12 +233,13 @@ export class WorkbookStreamWriter {
|
|
|
205
233
|
this.#workbook.fullCalcOnLoad = true;
|
|
206
234
|
const flushed = new Map();
|
|
207
235
|
for (const sheet of this.#sheets) {
|
|
208
|
-
const sheetFlushed = sheet.flushedSheet();
|
|
236
|
+
const sheetFlushed = sheet[INTERNAL].flushedSheet();
|
|
209
237
|
if (sheetFlushed)
|
|
210
238
|
flushed.set(sheet.model, sheetFlushed);
|
|
211
239
|
}
|
|
212
240
|
const owned = this.#stream;
|
|
213
241
|
const sink = this.#sink;
|
|
242
|
+
const collect = sink === undefined || owned !== undefined;
|
|
214
243
|
const sinkSettled = sink ? settleOnFinish(sink) : undefined;
|
|
215
244
|
try {
|
|
216
245
|
const parts = buildPackageParts(this.#workbook, {
|
|
@@ -221,7 +250,7 @@ export class WorkbookStreamWriter {
|
|
|
221
250
|
const bytes = await streamZipPackage(parts, (chunk) => {
|
|
222
251
|
owned?.write(chunk);
|
|
223
252
|
sink?.write(chunk);
|
|
224
|
-
});
|
|
253
|
+
}, collect);
|
|
225
254
|
owned?.end();
|
|
226
255
|
sink?.end();
|
|
227
256
|
await sinkSettled;
|
|
@@ -247,7 +276,7 @@ function settleOnFinish(sink) {
|
|
|
247
276
|
sink.once('error', reject);
|
|
248
277
|
});
|
|
249
278
|
}
|
|
250
|
-
function streamZipPackage(parts, onChunk) {
|
|
279
|
+
function streamZipPackage(parts, onChunk, collect) {
|
|
251
280
|
return new Promise((resolve, reject) => {
|
|
252
281
|
const collected = [];
|
|
253
282
|
const zip = new Zip((err, chunk, final) => {
|
|
@@ -255,10 +284,11 @@ function streamZipPackage(parts, onChunk) {
|
|
|
255
284
|
reject(err);
|
|
256
285
|
return;
|
|
257
286
|
}
|
|
258
|
-
|
|
287
|
+
if (collect)
|
|
288
|
+
collected.push(chunk);
|
|
259
289
|
onChunk(chunk);
|
|
260
290
|
if (final)
|
|
261
|
-
resolve(concat(collected));
|
|
291
|
+
resolve(collect ? concat(collected) : undefined);
|
|
262
292
|
});
|
|
263
293
|
for (const [name, data] of Object.entries(parts)) {
|
|
264
294
|
const entry = new ZipDeflate(name, { level: 6 });
|
package/dist/io/xlsx/write.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Workbook } from '../../core/workbook.ts';
|
|
2
2
|
import type { Worksheet } from '../../core/worksheet.ts';
|
|
3
|
+
import type { FlushedSheet } from './row-xml.ts';
|
|
3
4
|
import { StyleRegistry } from './styles.ts';
|
|
4
|
-
import { type FlushedSheet } from './worksheet-xml.ts';
|
|
5
|
-
export { buildColumnDefaults, Extent, type FlushedSheet, type RowRenderContext, renderRow, } from './worksheet-xml.ts';
|
|
6
5
|
/** Options controlling how {@link writeXlsx} serialises a workbook. */
|
|
7
6
|
export interface WriteOptions {
|
|
8
7
|
/**
|
package/dist/io/xlsx/write.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { strToU8, zip, zipSync } from 'fflate';
|
|
2
|
-
import { AuthoringError } from '../../errors.js';
|
|
2
|
+
import { AuthoringError, InternalError, quoted } from '../../errors.js';
|
|
3
|
+
import { isRelType } from '../../rel-type.js';
|
|
3
4
|
import { relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
|
|
4
5
|
import { relsPartXml } from '../opc/rels.js';
|
|
5
6
|
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
6
7
|
import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
|
|
7
|
-
import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
|
|
8
|
+
import { collectHyperlinks, liveCells, planHyperlinks } from './hyperlinks.js';
|
|
8
9
|
import { drawingRelsXml, drawingXml } from './images.js';
|
|
9
|
-
import { planMedia, planPreservedParts,
|
|
10
|
+
import { planMedia, planPreservedParts, RelIdAllocator, } from './package-plan.js';
|
|
10
11
|
import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, mediaPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, printerSettingsPart, SHARED_STRINGS_PART, STYLES_PART, tablePart, threadedCommentsPart, vmlDrawingPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
|
|
11
12
|
import { pivotCacheDefinitionXml, pivotCacheRecordsXml, pivotTableXml } from './pivot.js';
|
|
12
13
|
import { REL } from './relationships.js';
|
|
@@ -15,9 +16,8 @@ import { StyleRegistry } from './styles.js';
|
|
|
15
16
|
import { tableXml } from './tables.js';
|
|
16
17
|
import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
|
|
17
18
|
import { personsXml, threadedCommentsXml } from './threaded-comments.js';
|
|
18
|
-
import { appPropsXml, contentTypesXml, corePropsXml,
|
|
19
|
-
import { worksheetRelsXml, worksheetXml
|
|
20
|
-
export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
|
|
19
|
+
import { appPropsXml, contentTypesXml, corePropsXml, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
|
|
20
|
+
import { worksheetRelsXml, worksheetXml } from './worksheet-xml.js';
|
|
21
21
|
export function writeXlsx(workbook, options = {}) {
|
|
22
22
|
return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
|
|
23
23
|
}
|
|
@@ -49,8 +49,8 @@ export function createStyleRegistry(workbook) {
|
|
|
49
49
|
return styles;
|
|
50
50
|
}
|
|
51
51
|
function planSheet(context) {
|
|
52
|
-
const { sheet, index, media, preserved, numbering } = context;
|
|
53
|
-
const rels = new
|
|
52
|
+
const { sheet, index, media, preserved, numbering, flushed } = context;
|
|
53
|
+
const rels = new RelIdAllocator();
|
|
54
54
|
const tables = sheet.tables.map((table) => ({
|
|
55
55
|
table,
|
|
56
56
|
number: ++numbering.table,
|
|
@@ -70,7 +70,7 @@ function planSheet(context) {
|
|
|
70
70
|
drawing = { number: ++numbering.drawing, relId: rels.next(), images };
|
|
71
71
|
}
|
|
72
72
|
const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
|
|
73
|
-
const sheetComments = collectComments(sheet, threads);
|
|
73
|
+
const sheetComments = collectComments(liveCells(sheet), threads, flushed?.notes ?? []);
|
|
74
74
|
const comments = sheetComments.length === 0
|
|
75
75
|
? null
|
|
76
76
|
: {
|
|
@@ -82,7 +82,7 @@ function planSheet(context) {
|
|
|
82
82
|
const threadedComments = threads.length === 0 ? null : { number: index + 1, threads, relId: rels.next() };
|
|
83
83
|
const printerData = sheet.pageSetup.printerSettings;
|
|
84
84
|
const printerSettings = printerData === undefined ? null : { number: index + 1, data: printerData, relId: rels.next() };
|
|
85
|
-
const hyperlinks = planHyperlinks(collectHyperlinks(sheet), rels);
|
|
85
|
+
const hyperlinks = planHyperlinks([...collectHyperlinks(liveCells(sheet)), ...(flushed?.hyperlinks ?? [])].sort((a, b) => a.row - b.row || a.col - b.col), rels);
|
|
86
86
|
let background = null;
|
|
87
87
|
if (sheet.backgroundImageId !== undefined) {
|
|
88
88
|
const { number, image } = media.resolve(sheet.backgroundImageId);
|
|
@@ -119,7 +119,7 @@ function resolveSheetReferences(plan) {
|
|
|
119
119
|
const preservedDrawingRelId = refs.find((ref) => ref.element === 'drawing')?.relId ?? null;
|
|
120
120
|
const legacyDrawingHFRelId = refs.find((ref) => ref.element === 'legacyDrawingHF')?.relId ?? null;
|
|
121
121
|
const slicerRelIds = refs
|
|
122
|
-
.filter((ref) => ref.relType
|
|
122
|
+
.filter((ref) => isRelType(ref.relType, 'slicer'))
|
|
123
123
|
.map((ref) => ref.relId);
|
|
124
124
|
return {
|
|
125
125
|
drawingRelId: plan.drawing?.relId ?? preservedDrawingRelId,
|
|
@@ -132,25 +132,31 @@ function resolveSheetReferences(plan) {
|
|
|
132
132
|
}
|
|
133
133
|
function assignWorkbookRelIds(context) {
|
|
134
134
|
const { sheetCount, hasSharedStrings, hasPersons, preservedWorkbook, pivots } = context;
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}));
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
const ids = new RelIdAllocator();
|
|
136
|
+
const sheetRelIds = Array.from({ length: sheetCount }, () => ids.next());
|
|
137
|
+
const stylesRelId = ids.next();
|
|
138
|
+
const themeRelId = ids.next();
|
|
139
|
+
const sharedStringsRelId = hasSharedStrings ? ids.next() : null;
|
|
140
|
+
const personsRelId = hasPersons ? ids.next() : null;
|
|
141
|
+
const preservedWorkbookRels = preservedWorkbook.map((ref) => ({ ...ref, relId: ids.next() }));
|
|
142
|
+
for (const pivot of pivots)
|
|
143
|
+
pivot.workbookRelId = ids.next();
|
|
144
|
+
return {
|
|
145
|
+
sheetRelIds,
|
|
146
|
+
stylesRelId,
|
|
147
|
+
themeRelId,
|
|
148
|
+
sharedStringsRelId,
|
|
149
|
+
personsRelId,
|
|
150
|
+
preservedWorkbookRels,
|
|
151
|
+
};
|
|
147
152
|
}
|
|
148
|
-
function planPackage(workbook, sheets) {
|
|
153
|
+
function planPackage(workbook, sheets, flushed) {
|
|
149
154
|
const media = planMedia(workbook, sheets);
|
|
150
155
|
const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
|
|
151
|
-
const
|
|
156
|
+
const generatedPivotCount = sheets.reduce((total, sheet) => total + sheet.pivotTables.length, 0);
|
|
157
|
+
const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount);
|
|
152
158
|
const numbering = { table: 0, drawing: 0, pivot: 0 };
|
|
153
|
-
const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering }));
|
|
159
|
+
const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering, flushed: flushed?.get(sheet) }));
|
|
154
160
|
return {
|
|
155
161
|
media,
|
|
156
162
|
preserved,
|
|
@@ -159,10 +165,21 @@ function planPackage(workbook, sheets) {
|
|
|
159
165
|
allPivots: perSheet.flatMap((plan) => plan.pivots),
|
|
160
166
|
};
|
|
161
167
|
}
|
|
162
|
-
function serialiseSheets(
|
|
168
|
+
function serialiseSheets(context) {
|
|
169
|
+
const { workbook, sheets, plan, styles, sharedStrings, flushed } = context;
|
|
163
170
|
return sheets.map((sheet, i) => {
|
|
164
171
|
const sheetPlan = plan.perSheet[i];
|
|
165
|
-
return worksheetXml(
|
|
172
|
+
return worksheetXml({
|
|
173
|
+
sheet,
|
|
174
|
+
tables: sheetPlan.tables,
|
|
175
|
+
styles,
|
|
176
|
+
references: resolveSheetReferences(sheetPlan),
|
|
177
|
+
hyperlinks: sheetPlan.hyperlinks,
|
|
178
|
+
sharedStrings,
|
|
179
|
+
dateEpoch: workbook.dateEpoch,
|
|
180
|
+
active: i === workbook.activeTabIndex,
|
|
181
|
+
flushed: flushed?.get(sheet),
|
|
182
|
+
});
|
|
166
183
|
});
|
|
167
184
|
}
|
|
168
185
|
function emitPackageParts(context) {
|
|
@@ -174,43 +191,56 @@ function emitPackageParts(context) {
|
|
|
174
191
|
const printerSettingsNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.printerSettings);
|
|
175
192
|
const threadedCommentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.threadedComments);
|
|
176
193
|
const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
|
|
177
|
-
const
|
|
194
|
+
const workbookRels = assignWorkbookRelIds({
|
|
178
195
|
sheetCount: sheets.length,
|
|
179
196
|
hasSharedStrings,
|
|
180
197
|
hasPersons: persons.length > 0,
|
|
181
198
|
preservedWorkbook: preserved.workbook,
|
|
182
199
|
pivots: allPivots,
|
|
183
200
|
});
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
const { preservedWorkbookRels } = workbookRels;
|
|
202
|
+
const files = new PackageFiles();
|
|
203
|
+
files.add('[Content_Types].xml', strToU8(contentTypesXml({
|
|
204
|
+
sheetCount: sheets.length,
|
|
205
|
+
tables: allTables,
|
|
206
|
+
commentNumbers,
|
|
207
|
+
drawingNumbers,
|
|
208
|
+
printerSettingsNumbers,
|
|
209
|
+
mediaExtensions: media.extensions,
|
|
210
|
+
hasSharedStrings,
|
|
211
|
+
preservedParts: preserved.parts,
|
|
212
|
+
pivots: allPivots,
|
|
213
|
+
preservedWorkbookRefs: preservedWorkbookRels,
|
|
214
|
+
threadedCommentNumbers,
|
|
215
|
+
hasPersons: persons.length > 0,
|
|
216
|
+
})));
|
|
217
|
+
files.add('_rels/.rels', strToU8(rootRelsXml(preserved.root)));
|
|
218
|
+
files.add(CORE_PROPS_PART, strToU8(corePropsXml(workbook.properties)));
|
|
219
|
+
files.add(APP_PROPS_PART, strToU8(appPropsXml(workbook.properties)));
|
|
220
|
+
files.add(WORKBOOK_PART, strToU8(workbookXml(workbook, workbookRels, allPivots)));
|
|
221
|
+
files.add(relsPathFor(WORKBOOK_PART), strToU8(workbookRelsXml(workbookRels, allPivots)));
|
|
222
|
+
files.add(STYLES_PART, strToU8(styles.toXml()));
|
|
193
223
|
if (!preserved.themeEmitted) {
|
|
194
224
|
const overrides = workbook.themeOverrides;
|
|
195
|
-
files
|
|
225
|
+
files.add(THEME_PART_PATH, strToU8(overrides === undefined
|
|
196
226
|
? DEFAULT_THEME_XML
|
|
197
|
-
: applyThemeOverrides(DEFAULT_THEME_XML, overrides));
|
|
227
|
+
: applyThemeOverrides(DEFAULT_THEME_XML, overrides)));
|
|
198
228
|
}
|
|
199
229
|
if (hasSharedStrings) {
|
|
200
|
-
files
|
|
230
|
+
files.add(SHARED_STRINGS_PART, strToU8(sharedStrings.toXml()));
|
|
201
231
|
}
|
|
202
232
|
if (persons.length > 0)
|
|
203
|
-
files
|
|
233
|
+
files.add(PERSONS_PART, strToU8(personsXml(persons)));
|
|
204
234
|
for (const part of media.parts) {
|
|
205
|
-
files
|
|
235
|
+
files.add(mediaPart(part.number, part.extension), part.data);
|
|
206
236
|
}
|
|
207
237
|
emitSheetParts(files, perSheet, sheetXml);
|
|
208
238
|
for (const { table, number } of allTables) {
|
|
209
|
-
files
|
|
239
|
+
files.add(tablePart(number), strToU8(tableXml(table, number)));
|
|
210
240
|
}
|
|
211
241
|
emitPivotParts(files, allPivots);
|
|
212
242
|
emitPreservedParts(files, preserved.parts);
|
|
213
|
-
return files;
|
|
243
|
+
return files.toRecord();
|
|
214
244
|
}
|
|
215
245
|
export function buildPackageParts(workbook, options = {}) {
|
|
216
246
|
const sheets = workbook.worksheets;
|
|
@@ -219,15 +249,34 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
219
249
|
}
|
|
220
250
|
const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
|
|
221
251
|
const styles = options.styles ?? createStyleRegistry(workbook);
|
|
222
|
-
const plan = planPackage(workbook, sheets);
|
|
223
|
-
const sheetXml = serialiseSheets(
|
|
252
|
+
const plan = planPackage(workbook, sheets, options.flushed);
|
|
253
|
+
const sheetXml = serialiseSheets({
|
|
254
|
+
workbook,
|
|
255
|
+
sheets,
|
|
256
|
+
plan,
|
|
257
|
+
styles,
|
|
258
|
+
sharedStrings,
|
|
259
|
+
flushed: options.flushed,
|
|
260
|
+
});
|
|
224
261
|
return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, sheetXml });
|
|
225
262
|
}
|
|
263
|
+
class PackageFiles {
|
|
264
|
+
#files = Object.create(null);
|
|
265
|
+
add(path, bytes) {
|
|
266
|
+
if (path in this.#files) {
|
|
267
|
+
throw new InternalError(`two package parts claim the path ${quoted(path)}`);
|
|
268
|
+
}
|
|
269
|
+
this.#files[path] = bytes;
|
|
270
|
+
}
|
|
271
|
+
toRecord() {
|
|
272
|
+
return this.#files;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
226
275
|
function emitSheetParts(files, perSheet, sheetXml) {
|
|
227
276
|
perSheet.forEach((plan, i) => {
|
|
228
277
|
const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
|
|
229
278
|
const hasExternalHyperlink = hyperlinks.some((link) => link.relId !== undefined);
|
|
230
|
-
files
|
|
279
|
+
files.add(worksheetPart(i + 1), strToU8(sheetXml[i]));
|
|
231
280
|
if (tables.length > 0 ||
|
|
232
281
|
drawing !== null ||
|
|
233
282
|
comments !== null ||
|
|
@@ -237,23 +286,23 @@ function emitSheetParts(files, perSheet, sheetXml) {
|
|
|
237
286
|
hasExternalHyperlink ||
|
|
238
287
|
preservedRefs.length > 0 ||
|
|
239
288
|
pivots.length > 0) {
|
|
240
|
-
files
|
|
289
|
+
files.add(relsPathFor(worksheetPart(i + 1)), strToU8(worksheetRelsXml(plan)));
|
|
241
290
|
}
|
|
242
291
|
if (printerSettings !== null) {
|
|
243
|
-
files
|
|
292
|
+
files.add(printerSettingsPart(printerSettings.number), printerSettings.data);
|
|
244
293
|
}
|
|
245
294
|
if (drawing !== null) {
|
|
246
295
|
const drawingPath = drawingPart(drawing.number);
|
|
247
|
-
files
|
|
296
|
+
files.add(drawingPath, strToU8(drawingXml(drawing.images)));
|
|
248
297
|
const targets = drawing.images.map((image) => relativePartPath(drawingPath, mediaPart(image.mediaNumber, image.extension)));
|
|
249
|
-
files
|
|
298
|
+
files.add(relsPathFor(drawingPath), strToU8(drawingRelsXml(targets)));
|
|
250
299
|
}
|
|
251
300
|
if (comments !== null) {
|
|
252
|
-
files
|
|
253
|
-
files
|
|
301
|
+
files.add(commentsPart(comments.number), strToU8(commentsXml(comments.comments)));
|
|
302
|
+
files.add(vmlDrawingPart(comments.number), strToU8(vmlDrawingXml(comments.comments)));
|
|
254
303
|
}
|
|
255
304
|
if (threadedComments !== null) {
|
|
256
|
-
files
|
|
305
|
+
files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads)));
|
|
257
306
|
}
|
|
258
307
|
});
|
|
259
308
|
}
|
|
@@ -262,30 +311,21 @@ function emitPivotParts(files, allPivots) {
|
|
|
262
311
|
const { number, cacheId, table } = pivot;
|
|
263
312
|
const tablePath = pivotTablePart(number);
|
|
264
313
|
const definitionPath = pivotCacheDefinitionPart(number);
|
|
265
|
-
files
|
|
266
|
-
files
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
target: relativePartPath(tablePath, definitionPath),
|
|
271
|
-
},
|
|
272
|
-
]));
|
|
273
|
-
files[definitionPath] = strToU8(pivotCacheDefinitionXml(table));
|
|
274
|
-
files[relsPathFor(definitionPath)] = strToU8(relsPartXml([
|
|
275
|
-
{
|
|
276
|
-
id: 'rId1',
|
|
277
|
-
type: REL.pivotCacheRecords,
|
|
278
|
-
target: relativePartPath(definitionPath, pivotCacheRecordsPart(number)),
|
|
279
|
-
},
|
|
280
|
-
]));
|
|
281
|
-
files[pivotCacheRecordsPart(number)] = strToU8(pivotCacheRecordsXml(table));
|
|
314
|
+
files.add(tablePath, strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId)));
|
|
315
|
+
addSingleRelPart(files, tablePath, REL.pivotCacheDefinition, definitionPath);
|
|
316
|
+
files.add(definitionPath, strToU8(pivotCacheDefinitionXml(table)));
|
|
317
|
+
addSingleRelPart(files, definitionPath, REL.pivotCacheRecords, pivotCacheRecordsPart(number));
|
|
318
|
+
files.add(pivotCacheRecordsPart(number), strToU8(pivotCacheRecordsXml(table)));
|
|
282
319
|
}
|
|
283
320
|
}
|
|
321
|
+
function addSingleRelPart(files, from, type, to) {
|
|
322
|
+
files.add(relsPathFor(from), strToU8(relsPartXml([{ id: 'rId1', type, target: relativePartPath(from, to) }])));
|
|
323
|
+
}
|
|
284
324
|
function emitPreservedParts(files, parts) {
|
|
285
325
|
for (const part of parts) {
|
|
286
|
-
files
|
|
326
|
+
files.add(part.path, part.bytes);
|
|
287
327
|
if (part.relsPath !== null && part.relsXml !== null) {
|
|
288
|
-
files
|
|
328
|
+
files.add(part.relsPath, strToU8(part.relsXml));
|
|
289
329
|
}
|
|
290
330
|
}
|
|
291
331
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does a relationship Type name this class of part?
|
|
3
|
+
*
|
|
4
|
+
* A Type is a URI whose last segment names the part class, and the namespace in front of it varies by
|
|
5
|
+
* relationship family and by the schema version a producer wrote against. So the match is on that
|
|
6
|
+
* last segment.
|
|
7
|
+
*
|
|
8
|
+
* **The separator is the load-bearing character.** `/slicer` and `slicer` are not the same test:
|
|
9
|
+
* without the slash, `slicer` matches `slicerCache`, `table` matches `pivotTable`, and a lookup
|
|
10
|
+
* quietly gathers a part class nobody asked for. Every caller used to spell the slash itself, ten
|
|
11
|
+
* times over, with nowhere to say why it was there.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRelType(type: string, name: string): boolean;
|
|
14
|
+
/** {@link isRelType} against several part classes at once, for the predicates that partition
|
|
15
|
+
* relationships into "modeled", "preserved verbatim", and "dropped". */
|
|
16
|
+
export declare function isAnyRelType(type: string, ...names: readonly string[]): boolean;
|
package/dist/rel-type.js
ADDED
package/dist/token-set.d.ts
CHANGED
|
@@ -13,3 +13,16 @@
|
|
|
13
13
|
* expression instead of as its members.
|
|
14
14
|
*/
|
|
15
15
|
export declare function tokenSet<T extends string>(members: Record<T, true>): (value: string) => value is T;
|
|
16
|
+
/**
|
|
17
|
+
* {@link tokenSet} for a union whose members are also needed as an ordered *list*, because a binary
|
|
18
|
+
* serialisation indexes one.
|
|
19
|
+
*
|
|
20
|
+
* The record form's whole guarantee is that a member cannot be omitted, and a list gives that up: it
|
|
21
|
+
* can name only members of the union, but it can name fewer. So a caller here owes an explicit proof
|
|
22
|
+
* of set equality in both directions, which is what `FILL_PATTERNS_IN_SCHEMA_ORDER` carries beside
|
|
23
|
+
* itself. That is a worse guarantee than the record's, taken deliberately: the alternative is the
|
|
24
|
+
* arrangement it replaced, where the union, the guard's table and the binary codec's index list were
|
|
25
|
+
* three hand-maintained copies of one enumeration and only two of them were checked against each
|
|
26
|
+
* other.
|
|
27
|
+
*/
|
|
28
|
+
export declare function tokenSetOf<T extends string>(members: readonly T[]): (value: string) => value is T;
|
package/dist/token-set.js
CHANGED
package/dist/vba/bytes.d.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
export { concat } from '../bytes.ts';
|
|
1
|
+
export { concat, decodeUtf16le } from '../bytes.ts';
|
|
2
2
|
/** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
|
|
3
3
|
export declare function readU16(buf: Uint8Array, at: number): number;
|
|
4
4
|
/** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
|
|
5
5
|
export declare function readU32(buf: Uint8Array, at: number): number;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Write a little-endian `uint16` over bytes already in `buf`. The value is asserted rather than
|
|
8
|
+
* masked: a caller that arrived at `-1` or `0x1_0000` has miscounted something, and silently storing
|
|
9
|
+
* `0xffff` turns that into a `dir` stream declaring 65,535 modules.
|
|
10
10
|
*/
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function writeU16(buf: Uint8Array, at: number, value: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* Replace `src[start..end)` with `insert`, returning a new array.
|
|
14
|
+
*
|
|
15
|
+
* The three record edits the project editor makes are all this shape: splice a MODULE record block in,
|
|
16
|
+
* cut one out, cut a PROJECTwm entry out. Each was an allocate-and-copy-around written out longhand,
|
|
17
|
+
* and each had to get the same three offsets right; saying it once means the arithmetic is checked
|
|
18
|
+
* once. Passing no `insert` is a pure deletion, and `start === end` a pure insertion.
|
|
19
|
+
*/
|
|
20
|
+
export declare function spliceBytes(src: Uint8Array, start: number, end: number, insert?: Uint8Array): Uint8Array;
|
package/dist/vba/bytes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { concat } from '../bytes.js';
|
|
1
|
+
export { concat, decodeUtf16le } from '../bytes.js';
|
|
2
2
|
import { VbaParseError } from './errors.js';
|
|
3
3
|
function truncated(at, need, length) {
|
|
4
4
|
return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
|
|
@@ -20,10 +20,20 @@ export function readU32(buf, at) {
|
|
|
20
20
|
}
|
|
21
21
|
return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
|
|
22
22
|
}
|
|
23
|
-
export function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
s += String.fromCharCode(readU16(bytes, i));
|
|
23
|
+
export function writeU16(buf, at, value) {
|
|
24
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
|
|
25
|
+
throw new VbaParseError(`${value} does not fit the uint16 at offset ${at}`);
|
|
27
26
|
}
|
|
28
|
-
|
|
27
|
+
if (at + 1 >= buf.length)
|
|
28
|
+
throw truncated(at, 2, buf.length);
|
|
29
|
+
buf[at] = value & 0xff;
|
|
30
|
+
buf[at + 1] = (value >> 8) & 0xff;
|
|
31
|
+
}
|
|
32
|
+
export function spliceBytes(src, start, end, insert = EMPTY) {
|
|
33
|
+
const out = new Uint8Array(src.length - (end - start) + insert.length);
|
|
34
|
+
out.set(src.subarray(0, start), 0);
|
|
35
|
+
out.set(insert, start);
|
|
36
|
+
out.set(src.subarray(end), start + insert.length);
|
|
37
|
+
return out;
|
|
29
38
|
}
|
|
39
|
+
const EMPTY = new Uint8Array(0);
|
package/dist/vba/cfb-format.d.ts
CHANGED
|
@@ -17,6 +17,14 @@ export declare const TYPE_EMPTY = 0;
|
|
|
17
17
|
export declare const TYPE_STORAGE = 1;
|
|
18
18
|
export declare const TYPE_STREAM = 2;
|
|
19
19
|
export declare const TYPE_ROOT = 5;
|
|
20
|
+
/**
|
|
21
|
+
* The stream size at or above which a stream lives in the regular FAT rather than the mini stream
|
|
22
|
+
* ([MS-CFB] 2.2, header offset 56). The spec fixes it at 4096, so the reader checks the header's value
|
|
23
|
+
* against this rather than believing it: a crafted 0 or 0xFFFFFFFF routes every stream through the
|
|
24
|
+
* wrong allocator, and since both destinations are bounds-checked the result is a module's source read
|
|
25
|
+
* back as something nobody wrote rather than a clean rejection.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MINI_STREAM_CUTOFF = 4096;
|
|
20
28
|
/** A directory entry is a fixed 128 bytes ([MS-CFB] 2.6.1), whatever the sector size. */
|
|
21
29
|
export declare const DIR_ENTRY_SIZE = 128;
|
|
22
30
|
/** 32 UTF-16 code units including the NUL terminator. This is also why a VBA module name is capped
|