@shbernal/ts-xlsx 2.0.0 → 2.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 +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +26 -4
- package/dist/core/address.js +21 -9
- package/dist/core/autofilter.d.ts +11 -0
- package/dist/core/autofilter.js +35 -10
- package/dist/core/color-resolution.d.ts +1 -1
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/conditional-formatting-overlay.d.ts +5 -0
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +45 -5
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +11 -0
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +9 -2
- package/dist/core/data-validation.js +34 -0
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +26 -8
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +2 -0
- package/dist/core/image.js +4 -0
- package/dist/core/merge.d.ts +12 -1
- package/dist/core/merge.js +36 -2
- package/dist/core/page-setup.d.ts +16 -2
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +2 -0
- package/dist/core/pivot-table.js +25 -16
- package/dist/core/protection.d.ts +3 -0
- package/dist/core/protection.js +20 -14
- package/dist/core/row-input.d.ts +7 -0
- package/dist/core/row-input.js +10 -8
- package/dist/core/style.d.ts +27 -0
- package/dist/core/style.js +82 -67
- package/dist/core/table.d.ts +25 -11
- package/dist/core/table.js +67 -31
- package/dist/core/theme.d.ts +3 -1
- package/dist/core/theme.js +4 -0
- package/dist/core/workbook.d.ts +2 -2
- package/dist/core/worksheet-comments.d.ts +8 -0
- package/dist/core/worksheet-comments.js +18 -0
- package/dist/core/worksheet.d.ts +15 -2
- package/dist/core/worksheet.js +28 -48
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +3 -3
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/xlsx.d.ts +0 -1
- package/dist/entries/xlsx.js +0 -1
- package/dist/io/csv/read.js +1 -1
- package/dist/io/csv/write.d.ts +12 -2
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/inflate.js +2 -12
- package/dist/io/opc/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/style/xf-style.d.ts +7 -2
- package/dist/io/style/xf-style.js +6 -1
- package/dist/io/xlsx/cell-accumulator.d.ts +1 -3
- package/dist/io/xlsx/cell-accumulator.js +20 -58
- package/dist/io/xlsx/cell-value.d.ts +1 -1
- package/dist/io/xlsx/cell-value.js +7 -4
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +7 -6
- package/dist/io/xlsx/comments.js +4 -2
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +60 -45
- package/dist/io/xlsx/data-validation.d.ts +1 -1
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/hyperlinks.js +6 -14
- package/dist/io/xlsx/images.js +19 -22
- package/dist/io/xlsx/read-pivot.js +2 -8
- package/dist/io/xlsx/read-shared-strings.js +8 -47
- package/dist/io/xlsx/read-styles.js +2 -1
- package/dist/io/xlsx/read-worksheet.js +14 -16
- package/dist/io/xlsx/read.d.ts +1 -1
- package/dist/io/xlsx/read.js +44 -74
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +13 -9
- package/dist/io/xlsx/styles.d.ts +3 -15
- package/dist/io/xlsx/styles.js +63 -88
- package/dist/io/xlsx/tables.js +13 -13
- package/dist/io/xlsx/theme-xml.js +7 -4
- package/dist/io/xlsx/threaded-comments.js +19 -19
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.js +34 -37
- package/dist/io/xlsx/write-stream.d.ts +8 -1
- package/dist/io/xlsx/write-stream.js +10 -11
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +1 -2
- package/dist/vba/bytes.js +1 -12
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/project-editor.js +5 -34
- package/dist/vba/project.js +2 -18
- package/dist/xml/xml-read.d.ts +60 -0
- package/dist/xml/xml-read.js +47 -0
- package/dist/xml/xml.d.ts +43 -1
- package/dist/xml/xml.js +17 -5
- package/package.json +21 -4
|
@@ -88,7 +88,14 @@ export declare class WorksheetStreamWriter {
|
|
|
88
88
|
*/
|
|
89
89
|
flushRow(number: number, cells: readonly Cell[]): void;
|
|
90
90
|
flushedSheet(): FlushedSheet | undefined;
|
|
91
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Address a cell by its A1 reference to read or style it before the sheet is committed. The row it
|
|
93
|
+
* names must still be live: a row whose {@link StreamedRow.commit} has run is finished.
|
|
94
|
+
*
|
|
95
|
+
* @throws {AuthoringError} if the reference names an already-committed row. That row's `<row>` is
|
|
96
|
+
* rendered and its cells are released, so the cell this would materialise could only be written
|
|
97
|
+
* as a second row carrying the same number.
|
|
98
|
+
*/
|
|
92
99
|
getCell(reference: string): Cell;
|
|
93
100
|
/**
|
|
94
101
|
* Attach a data validation to a range before the sheet is committed. Delegates to the model, so the
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
2
|
import { PassThrough } from 'node:stream';
|
|
3
3
|
import { Zip, ZipDeflate } from 'fflate';
|
|
4
|
-
import {
|
|
4
|
+
import { concat } from '../../bytes.js';
|
|
5
|
+
import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
|
|
5
6
|
import { INTERNAL } from '../../core/internal.js';
|
|
6
7
|
import { isSharedFormulaValue } from '../../core/value.js';
|
|
7
8
|
import { Workbook } from '../../core/workbook.js';
|
|
@@ -36,6 +37,7 @@ export class WorksheetStreamWriter {
|
|
|
36
37
|
#lastRow = 0;
|
|
37
38
|
#columnDefaults;
|
|
38
39
|
#flushedRows = [];
|
|
40
|
+
#flushedNumbers = new Set();
|
|
39
41
|
#extent = new Extent();
|
|
40
42
|
#maxRowOutlineLevel = 0;
|
|
41
43
|
constructor(sheet, eager, styles) {
|
|
@@ -97,6 +99,7 @@ export class WorksheetStreamWriter {
|
|
|
97
99
|
this.#flushedRows.push({ number, xml });
|
|
98
100
|
this.#extent.add(number, minCol, maxCol);
|
|
99
101
|
}
|
|
102
|
+
this.#flushedNumbers.add(number);
|
|
100
103
|
this.#sheet[INTERNAL].evictRow(number);
|
|
101
104
|
}
|
|
102
105
|
flushedSheet() {
|
|
@@ -110,6 +113,12 @@ export class WorksheetStreamWriter {
|
|
|
110
113
|
}
|
|
111
114
|
getCell(reference) {
|
|
112
115
|
this.#assertOpen();
|
|
116
|
+
const target = tryDecodeCellRef(reference);
|
|
117
|
+
if (target !== undefined && this.#flushedNumbers.has(target.row)) {
|
|
118
|
+
throw new AuthoringError(`cell ${reference} of streamed sheet "${this.#sheet.name}" is in row ${target.row}, which is ` +
|
|
119
|
+
'already committed: its bytes are written and its cells released, so this value could only ' +
|
|
120
|
+
'be emitted as a second row with that number. Style a row before committing it.');
|
|
121
|
+
}
|
|
113
122
|
return this.#sheet.getCell(reference);
|
|
114
123
|
}
|
|
115
124
|
addDataValidation(sqref, rule, options = {}) {
|
|
@@ -252,13 +261,3 @@ function streamZipPackage(parts, onChunk) {
|
|
|
252
261
|
zip.end();
|
|
253
262
|
});
|
|
254
263
|
}
|
|
255
|
-
function concat(chunks) {
|
|
256
|
-
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
257
|
-
const out = new Uint8Array(total);
|
|
258
|
-
let offset = 0;
|
|
259
|
-
for (const chunk of chunks) {
|
|
260
|
-
out.set(chunk, offset);
|
|
261
|
-
offset += chunk.length;
|
|
262
|
-
}
|
|
263
|
-
return out;
|
|
264
|
-
}
|
package/dist/sha512.d.ts
ADDED
package/dist/sha512.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const BLOCK_BYTES = 128;
|
|
2
|
+
const DIGEST_BYTES = 64;
|
|
3
|
+
const K = new Uint32Array([
|
|
4
|
+
0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc,
|
|
5
|
+
0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118,
|
|
6
|
+
0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
|
|
7
|
+
0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694,
|
|
8
|
+
0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65,
|
|
9
|
+
0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
|
|
10
|
+
0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4,
|
|
11
|
+
0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70,
|
|
12
|
+
0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
|
|
13
|
+
0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b,
|
|
14
|
+
0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30,
|
|
15
|
+
0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
|
|
16
|
+
0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8,
|
|
17
|
+
0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3,
|
|
18
|
+
0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
|
|
19
|
+
0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b,
|
|
20
|
+
0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178,
|
|
21
|
+
0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
|
|
22
|
+
0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c,
|
|
23
|
+
0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,
|
|
24
|
+
]);
|
|
25
|
+
const INITIAL = new Uint32Array([
|
|
26
|
+
0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1,
|
|
27
|
+
0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179,
|
|
28
|
+
]);
|
|
29
|
+
function word(words, index) {
|
|
30
|
+
return words[index] ?? 0;
|
|
31
|
+
}
|
|
32
|
+
export function sha512(message) {
|
|
33
|
+
const blocks = Math.ceil((message.length + 17) / BLOCK_BYTES);
|
|
34
|
+
const padded = new Uint8Array(blocks * BLOCK_BYTES);
|
|
35
|
+
padded.set(message);
|
|
36
|
+
padded[message.length] = 0x80;
|
|
37
|
+
const view = new DataView(padded.buffer);
|
|
38
|
+
const bits = message.length * 8;
|
|
39
|
+
view.setUint32(padded.length - 8, Math.floor(bits / 0x1_0000_0000), false);
|
|
40
|
+
view.setUint32(padded.length - 4, bits >>> 0, false);
|
|
41
|
+
const h = Uint32Array.from(INITIAL);
|
|
42
|
+
const schedule = new Uint32Array(160);
|
|
43
|
+
for (let block = 0; block < blocks; block++)
|
|
44
|
+
compress(h, view, block * BLOCK_BYTES, schedule);
|
|
45
|
+
const digest = new Uint8Array(DIGEST_BYTES);
|
|
46
|
+
const out = new DataView(digest.buffer);
|
|
47
|
+
for (let i = 0; i < 16; i++)
|
|
48
|
+
out.setUint32(i * 4, word(h, i), false);
|
|
49
|
+
return digest;
|
|
50
|
+
}
|
|
51
|
+
function compress(h, input, offset, schedule) {
|
|
52
|
+
for (let t = 0; t < 16; t++) {
|
|
53
|
+
schedule[t * 2] = input.getUint32(offset + t * 8, false);
|
|
54
|
+
schedule[t * 2 + 1] = input.getUint32(offset + t * 8 + 4, false);
|
|
55
|
+
}
|
|
56
|
+
for (let t = 16; t < 80; t++) {
|
|
57
|
+
const xHi = word(schedule, (t - 15) * 2);
|
|
58
|
+
const xLo = word(schedule, (t - 15) * 2 + 1);
|
|
59
|
+
const s0Hi = ((xHi >>> 1) | (xLo << 31)) ^ ((xHi >>> 8) | (xLo << 24)) ^ (xHi >>> 7);
|
|
60
|
+
const s0Lo = ((xLo >>> 1) | (xHi << 31)) ^ ((xLo >>> 8) | (xHi << 24)) ^ ((xLo >>> 7) | (xHi << 25));
|
|
61
|
+
const yHi = word(schedule, (t - 2) * 2);
|
|
62
|
+
const yLo = word(schedule, (t - 2) * 2 + 1);
|
|
63
|
+
const s1Hi = ((yHi >>> 19) | (yLo << 13)) ^ ((yLo >>> 29) | (yHi << 3)) ^ (yHi >>> 6);
|
|
64
|
+
const s1Lo = ((yLo >>> 19) | (yHi << 13)) ^ ((yHi >>> 29) | (yLo << 3)) ^ ((yLo >>> 6) | (yHi << 26));
|
|
65
|
+
const lo = (s1Lo >>> 0) +
|
|
66
|
+
word(schedule, (t - 7) * 2 + 1) +
|
|
67
|
+
(s0Lo >>> 0) +
|
|
68
|
+
word(schedule, (t - 16) * 2 + 1);
|
|
69
|
+
const hi = (s1Hi >>> 0) +
|
|
70
|
+
word(schedule, (t - 7) * 2) +
|
|
71
|
+
(s0Hi >>> 0) +
|
|
72
|
+
word(schedule, (t - 16) * 2) +
|
|
73
|
+
Math.floor(lo / 0x1_0000_0000);
|
|
74
|
+
schedule[t * 2] = hi >>> 0;
|
|
75
|
+
schedule[t * 2 + 1] = lo >>> 0;
|
|
76
|
+
}
|
|
77
|
+
let aHi = word(h, 0);
|
|
78
|
+
let aLo = word(h, 1);
|
|
79
|
+
let bHi = word(h, 2);
|
|
80
|
+
let bLo = word(h, 3);
|
|
81
|
+
let cHi = word(h, 4);
|
|
82
|
+
let cLo = word(h, 5);
|
|
83
|
+
let dHi = word(h, 6);
|
|
84
|
+
let dLo = word(h, 7);
|
|
85
|
+
let eHi = word(h, 8);
|
|
86
|
+
let eLo = word(h, 9);
|
|
87
|
+
let fHi = word(h, 10);
|
|
88
|
+
let fLo = word(h, 11);
|
|
89
|
+
let gHi = word(h, 12);
|
|
90
|
+
let gLo = word(h, 13);
|
|
91
|
+
let hHi = word(h, 14);
|
|
92
|
+
let hLo = word(h, 15);
|
|
93
|
+
for (let t = 0; t < 80; t++) {
|
|
94
|
+
const bigS1Hi = ((eHi >>> 14) | (eLo << 18)) ^ ((eHi >>> 18) | (eLo << 14)) ^ ((eLo >>> 9) | (eHi << 23));
|
|
95
|
+
const bigS1Lo = ((eLo >>> 14) | (eHi << 18)) ^ ((eLo >>> 18) | (eHi << 14)) ^ ((eHi >>> 9) | (eLo << 23));
|
|
96
|
+
const chHi = (eHi & fHi) ^ (~eHi & gHi);
|
|
97
|
+
const chLo = (eLo & fLo) ^ (~eLo & gLo);
|
|
98
|
+
const t1Lo = hLo + (bigS1Lo >>> 0) + (chLo >>> 0) + word(K, t * 2 + 1) + word(schedule, t * 2 + 1);
|
|
99
|
+
const t1Hi = (hHi +
|
|
100
|
+
(bigS1Hi >>> 0) +
|
|
101
|
+
(chHi >>> 0) +
|
|
102
|
+
word(K, t * 2) +
|
|
103
|
+
word(schedule, t * 2) +
|
|
104
|
+
Math.floor(t1Lo / 0x1_0000_0000)) >>>
|
|
105
|
+
0;
|
|
106
|
+
const t1LoWrapped = t1Lo >>> 0;
|
|
107
|
+
const bigS0Hi = ((aHi >>> 28) | (aLo << 4)) ^ ((aLo >>> 2) | (aHi << 30)) ^ ((aLo >>> 7) | (aHi << 25));
|
|
108
|
+
const bigS0Lo = ((aLo >>> 28) | (aHi << 4)) ^ ((aHi >>> 2) | (aLo << 30)) ^ ((aHi >>> 7) | (aLo << 25));
|
|
109
|
+
const majHi = (aHi & bHi) ^ (aHi & cHi) ^ (bHi & cHi);
|
|
110
|
+
const majLo = (aLo & bLo) ^ (aLo & cLo) ^ (bLo & cLo);
|
|
111
|
+
const t2Lo = (bigS0Lo >>> 0) + (majLo >>> 0);
|
|
112
|
+
const t2Hi = ((bigS0Hi >>> 0) + (majHi >>> 0) + Math.floor(t2Lo / 0x1_0000_0000)) >>> 0;
|
|
113
|
+
const t2LoWrapped = t2Lo >>> 0;
|
|
114
|
+
hHi = gHi;
|
|
115
|
+
hLo = gLo;
|
|
116
|
+
gHi = fHi;
|
|
117
|
+
gLo = fLo;
|
|
118
|
+
fHi = eHi;
|
|
119
|
+
fLo = eLo;
|
|
120
|
+
const nextELo = dLo + t1LoWrapped;
|
|
121
|
+
eHi = (dHi + t1Hi + Math.floor(nextELo / 0x1_0000_0000)) >>> 0;
|
|
122
|
+
eLo = nextELo >>> 0;
|
|
123
|
+
dHi = cHi;
|
|
124
|
+
dLo = cLo;
|
|
125
|
+
cHi = bHi;
|
|
126
|
+
cLo = bLo;
|
|
127
|
+
bHi = aHi;
|
|
128
|
+
bLo = aLo;
|
|
129
|
+
const nextALo = t1LoWrapped + t2LoWrapped;
|
|
130
|
+
aHi = (t1Hi + t2Hi + Math.floor(nextALo / 0x1_0000_0000)) >>> 0;
|
|
131
|
+
aLo = nextALo >>> 0;
|
|
132
|
+
}
|
|
133
|
+
addInto(h, 0, aHi, aLo);
|
|
134
|
+
addInto(h, 2, bHi, bLo);
|
|
135
|
+
addInto(h, 4, cHi, cLo);
|
|
136
|
+
addInto(h, 6, dHi, dLo);
|
|
137
|
+
addInto(h, 8, eHi, eLo);
|
|
138
|
+
addInto(h, 10, fHi, fLo);
|
|
139
|
+
addInto(h, 12, gHi, gLo);
|
|
140
|
+
addInto(h, 14, hHi, hLo);
|
|
141
|
+
}
|
|
142
|
+
function addInto(h, index, hi, lo) {
|
|
143
|
+
const sumLo = word(h, index + 1) + lo;
|
|
144
|
+
h[index] = (word(h, index) + hi + Math.floor(sumLo / 0x1_0000_0000)) >>> 0;
|
|
145
|
+
h[index + 1] = sumLo >>> 0;
|
|
146
|
+
}
|
package/dist/vba/bytes.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { concat } from '../bytes.ts';
|
|
1
2
|
/** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
|
|
2
3
|
export declare function readU16(buf: Uint8Array, at: number): number;
|
|
3
4
|
/** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
|
|
@@ -8,5 +9,3 @@ export declare function readU32(buf: Uint8Array, at: number): number;
|
|
|
8
9
|
* producer and a half code unit carries nothing to decode.
|
|
9
10
|
*/
|
|
10
11
|
export declare function decodeUtf16le(bytes: Uint8Array): string;
|
|
11
|
-
/** Join byte chunks into one buffer. */
|
|
12
|
-
export declare function concat(chunks: Uint8Array[]): Uint8Array;
|
package/dist/vba/bytes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { concat } from '../bytes.js';
|
|
1
2
|
import { VbaParseError } from './errors.js';
|
|
2
3
|
function truncated(at, need, length) {
|
|
3
4
|
return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
|
|
@@ -26,15 +27,3 @@ export function decodeUtf16le(bytes) {
|
|
|
26
27
|
}
|
|
27
28
|
return s;
|
|
28
29
|
}
|
|
29
|
-
export function concat(chunks) {
|
|
30
|
-
let total = 0;
|
|
31
|
-
for (const c of chunks)
|
|
32
|
-
total += c.length;
|
|
33
|
-
const out = new Uint8Array(total);
|
|
34
|
-
let off = 0;
|
|
35
|
-
for (const c of chunks) {
|
|
36
|
-
out.set(c, off);
|
|
37
|
-
off += c.length;
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** PROJECTCODEPAGE: the code page every MBCS name in the project decodes through. */
|
|
2
|
+
export declare const REC_PROJECT_CODEPAGE = 3;
|
|
3
|
+
/** PROJECTVERSION: the record whose `Size` under-counts its payload by the trailing VersionMinor. */
|
|
4
|
+
export declare const REC_PROJECT_VERSION = 9;
|
|
5
|
+
/** REFERENCEREGISTERED: a reference to a registered type library, by libid. */
|
|
6
|
+
export declare const REC_REFERENCE_REGISTERED = 13;
|
|
7
|
+
/** PROJECTMODULES MODULES_COUNT: how many module blocks follow, and the first record after the
|
|
8
|
+
* reference array, which has no count of its own. */
|
|
9
|
+
export declare const REC_MODULES_COUNT = 15;
|
|
10
|
+
/** REFERENCENAME: a reference's MBCS name. */
|
|
11
|
+
export declare const REC_REFERENCE_NAME = 22;
|
|
12
|
+
/** MODULENAME: opens a module's record block. */
|
|
13
|
+
export declare const REC_MODULE_NAME = 25;
|
|
14
|
+
/** MODULESTREAMNAME: the storage name the module's p-code lives under. */
|
|
15
|
+
export declare const REC_MODULE_STREAMNAME = 26;
|
|
16
|
+
/** MODULETYPE for a standard module. */
|
|
17
|
+
export declare const REC_MODULE_TYPE_PROCEDURAL = 33;
|
|
18
|
+
/** MODULETYPE for a document module (`ThisWorkbook`, a sheet's code-behind). */
|
|
19
|
+
export declare const REC_MODULE_TYPE_DOCUMENT = 34;
|
|
20
|
+
/** MODULEENDOFBLOCK: closes a module's record block. */
|
|
21
|
+
export declare const REC_MODULE_TERMINATOR = 43;
|
|
22
|
+
/** MODULEOFFSET: where the module's source begins inside its stream. */
|
|
23
|
+
export declare const REC_MODULE_OFFSET = 49;
|
|
24
|
+
/**
|
|
25
|
+
* REFERENCENAME's Unicode half. A *literal* `0x003E` marker rather than a nested record id, but it is
|
|
26
|
+
* laid out as its own Id+Size+data TLV, so a generic walk sees REFERENCENAME as two chained records,
|
|
27
|
+
* exactly like MODULENAME/MODULENAME_UNICODE. Verified against a real Excel-authored dir stream
|
|
28
|
+
* (2026-07-23).
|
|
29
|
+
*/
|
|
30
|
+
export declare const REC_REFERENCE_NAME_UNICODE = 62;
|
|
31
|
+
/** One record as the walk sees it. */
|
|
32
|
+
export interface DirRecord {
|
|
33
|
+
/** The record id; one of the `REC_*` constants above, or a record this library does not consume. */
|
|
34
|
+
readonly id: number;
|
|
35
|
+
/** Offset of the record's `Id` field: where a splice that drops the record cuts from. */
|
|
36
|
+
readonly recordStart: number;
|
|
37
|
+
/** Offset of the record's payload. */
|
|
38
|
+
readonly dataStart: number;
|
|
39
|
+
/** The `Size` field, which is the payload length for every record but PROJECTVERSION. */
|
|
40
|
+
readonly size: number;
|
|
41
|
+
/** Offset of the next record, PROJECTVERSION's uncounted VersionMinor already skipped. Never
|
|
42
|
+
* recompute this from `dataStart + size`: that is the misalignment this module exists to prevent. */
|
|
43
|
+
readonly end: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Walk a decompressed `dir` stream, yielding each record in order. Consumers may stop early.
|
|
47
|
+
*
|
|
48
|
+
* @param context the phrase a truncated record's error carries, naming what the caller was doing:
|
|
49
|
+
* the message reaches a user who has no idea what a TLV is but does know they were removing a
|
|
50
|
+
* module.
|
|
51
|
+
* @throws {VbaParseError} when a record's payload runs past the end of the stream.
|
|
52
|
+
*/
|
|
53
|
+
export declare function dirRecords(dir: Uint8Array, context: string): Generator<DirRecord>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readU16, readU32 } from './bytes.js';
|
|
2
|
+
import { VbaParseError } from './errors.js';
|
|
3
|
+
export const REC_PROJECT_CODEPAGE = 0x0003;
|
|
4
|
+
export const REC_PROJECT_VERSION = 0x0009;
|
|
5
|
+
export const REC_REFERENCE_REGISTERED = 0x000d;
|
|
6
|
+
export const REC_MODULES_COUNT = 0x000f;
|
|
7
|
+
export const REC_REFERENCE_NAME = 0x0016;
|
|
8
|
+
export const REC_MODULE_NAME = 0x0019;
|
|
9
|
+
export const REC_MODULE_STREAMNAME = 0x001a;
|
|
10
|
+
export const REC_MODULE_TYPE_PROCEDURAL = 0x0021;
|
|
11
|
+
export const REC_MODULE_TYPE_DOCUMENT = 0x0022;
|
|
12
|
+
export const REC_MODULE_TERMINATOR = 0x002b;
|
|
13
|
+
export const REC_MODULE_OFFSET = 0x0031;
|
|
14
|
+
export const REC_REFERENCE_NAME_UNICODE = 0x003e;
|
|
15
|
+
export function* dirRecords(dir, context) {
|
|
16
|
+
let pos = 0;
|
|
17
|
+
while (pos + 6 <= dir.length) {
|
|
18
|
+
const id = readU16(dir, pos);
|
|
19
|
+
const size = readU32(dir, pos + 2);
|
|
20
|
+
const dataStart = pos + 6;
|
|
21
|
+
if (dataStart + size > dir.length) {
|
|
22
|
+
throw new VbaParseError(`dir record 0x${id.toString(16)} ${context}`);
|
|
23
|
+
}
|
|
24
|
+
const end = dataStart + size + (id === REC_PROJECT_VERSION ? 2 : 0);
|
|
25
|
+
yield { id, recordStart: pos, dataStart, size, end };
|
|
26
|
+
pos = end;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readU16
|
|
1
|
+
import { readU16 } from './bytes.js';
|
|
2
2
|
import { writeCompoundFile } from './cfb-writer.js';
|
|
3
3
|
import { CompoundFile } from './cfb.js';
|
|
4
4
|
import { decoderForCodePage, encoderForCodePage } from './codepage.js';
|
|
5
|
+
import { dirRecords, REC_MODULE_NAME, REC_MODULE_STREAMNAME, REC_MODULE_TERMINATOR, REC_MODULES_COUNT, REC_REFERENCE_NAME, REC_REFERENCE_NAME_UNICODE, REC_REFERENCE_REGISTERED, } from './dir-records.js';
|
|
5
6
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
6
7
|
import { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
7
8
|
import { parseVbaProject } from './project.js';
|
|
@@ -10,14 +11,6 @@ const DIR_STREAM = 'dir';
|
|
|
10
11
|
const PROJECT_STREAM = 'PROJECT';
|
|
11
12
|
const PROJECTWM_STREAM = 'PROJECTwm';
|
|
12
13
|
const VBA_STORAGE = 'VBA';
|
|
13
|
-
const REC_MODULE_NAME = 0x0019;
|
|
14
|
-
const REC_MODULE_STREAMNAME = 0x001a;
|
|
15
|
-
const REC_MODULE_TERMINATOR = 0x002b;
|
|
16
|
-
const REC_PROJECT_VERSION = 0x0009;
|
|
17
|
-
const REC_MODULES_COUNT = 0x000f;
|
|
18
|
-
const REC_REFERENCE_NAME = 0x0016;
|
|
19
|
-
const REC_REFERENCE_NAME_UNICODE = 0x003e;
|
|
20
|
-
const REC_REFERENCE_REGISTERED = 0x000d;
|
|
21
14
|
export function removeVbaModule(bin, name) {
|
|
22
15
|
const project = parseVbaProject(bin);
|
|
23
16
|
const nameKey = name.toUpperCase();
|
|
@@ -124,18 +117,7 @@ function buildReferenceDirRecords(ref, encode) {
|
|
|
124
117
|
}
|
|
125
118
|
function insertReferenceDirRecords(dir, records) {
|
|
126
119
|
let insertAt = -1;
|
|
127
|
-
|
|
128
|
-
while (pos + 6 <= dir.length) {
|
|
129
|
-
const recordStart = pos;
|
|
130
|
-
const id = readU16(dir, pos);
|
|
131
|
-
const size = readU32(dir, pos + 2);
|
|
132
|
-
const dataStart = pos + 6;
|
|
133
|
-
if (dataStart + size > dir.length) {
|
|
134
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while adding a reference`);
|
|
135
|
-
}
|
|
136
|
-
pos = dataStart + size;
|
|
137
|
-
if (id === REC_PROJECT_VERSION)
|
|
138
|
-
pos += 2;
|
|
120
|
+
for (const { id, recordStart } of dirRecords(dir, 'overruns while adding a reference')) {
|
|
139
121
|
if (id === REC_MODULES_COUNT) {
|
|
140
122
|
insertAt = recordStart;
|
|
141
123
|
break;
|
|
@@ -157,18 +139,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
157
139
|
let removeStart = -1;
|
|
158
140
|
let removeEnd = -1;
|
|
159
141
|
let currentStream;
|
|
160
|
-
|
|
161
|
-
while (pos + 6 <= dir.length) {
|
|
162
|
-
const recordStart = pos;
|
|
163
|
-
const id = readU16(dir, pos);
|
|
164
|
-
const size = readU32(dir, pos + 2);
|
|
165
|
-
const dataStart = pos + 6;
|
|
166
|
-
if (dataStart + size > dir.length) {
|
|
167
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while removing a module`);
|
|
168
|
-
}
|
|
169
|
-
pos = dataStart + size;
|
|
170
|
-
if (id === REC_PROJECT_VERSION)
|
|
171
|
-
pos += 2;
|
|
142
|
+
for (const { id, recordStart, dataStart, size, end } of dirRecords(dir, 'overruns while removing a module')) {
|
|
172
143
|
if (id === REC_MODULES_COUNT) {
|
|
173
144
|
if (size < 2)
|
|
174
145
|
throw new VbaParseError('PROJECTMODULES MODULES_COUNT record is malformed');
|
|
@@ -183,7 +154,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
183
154
|
else if (id === REC_MODULE_TERMINATOR) {
|
|
184
155
|
if (currentStream === streamName) {
|
|
185
156
|
removeStart = blockStart;
|
|
186
|
-
removeEnd =
|
|
157
|
+
removeEnd = end;
|
|
187
158
|
}
|
|
188
159
|
currentStream = undefined;
|
|
189
160
|
blockStart = -1;
|
package/dist/vba/project.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readU16, readU32 } from './bytes.js';
|
|
2
2
|
import { CompoundFile } from './cfb.js';
|
|
3
3
|
import { decoderForCodePage } from './codepage.js';
|
|
4
|
+
import { dirRecords, REC_MODULE_NAME, REC_MODULE_OFFSET, REC_MODULE_STREAMNAME, REC_MODULE_TERMINATOR, REC_MODULE_TYPE_DOCUMENT, REC_MODULE_TYPE_PROCEDURAL, REC_PROJECT_CODEPAGE, } from './dir-records.js';
|
|
4
5
|
import { VbaParseError } from './errors.js';
|
|
5
6
|
import { decompressContainer } from './ms-ovba.js';
|
|
6
7
|
export const VBA_PROJECT_REL_TYPE = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
|
|
@@ -14,14 +15,6 @@ const SIGNATURE_KIND_BY_REL_SEGMENT = {
|
|
|
14
15
|
export function vbaProjectSignatureKind(relType) {
|
|
15
16
|
return SIGNATURE_KIND_BY_REL_SEGMENT[relType.slice(relType.lastIndexOf('/') + 1)];
|
|
16
17
|
}
|
|
17
|
-
const REC_PROJECT_CODEPAGE = 0x0003;
|
|
18
|
-
const REC_MODULE_NAME = 0x0019;
|
|
19
|
-
const REC_MODULE_STREAMNAME = 0x001a;
|
|
20
|
-
const REC_MODULE_TYPE_PROCEDURAL = 0x0021;
|
|
21
|
-
const REC_MODULE_TYPE_DOCUMENT = 0x0022;
|
|
22
|
-
const REC_MODULE_OFFSET = 0x0031;
|
|
23
|
-
const REC_MODULE_TERMINATOR = 0x002b;
|
|
24
|
-
const REC_PROJECT_VERSION = 0x0009;
|
|
25
18
|
export function parseVbaProject(bin) {
|
|
26
19
|
const cfb = new CompoundFile(bin);
|
|
27
20
|
const dirCompressed = cfb.readStream('dir');
|
|
@@ -31,16 +24,7 @@ export function parseVbaProject(bin) {
|
|
|
31
24
|
let codePage = 1252;
|
|
32
25
|
const rawModules = [];
|
|
33
26
|
let pending = {};
|
|
34
|
-
|
|
35
|
-
while (pos + 6 <= dir.length) {
|
|
36
|
-
const id = readU16(dir, pos);
|
|
37
|
-
const size = readU32(dir, pos + 2);
|
|
38
|
-
const dataStart = pos + 6;
|
|
39
|
-
if (dataStart + size > dir.length)
|
|
40
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns stream`);
|
|
41
|
-
pos = dataStart + size;
|
|
42
|
-
if (id === REC_PROJECT_VERSION)
|
|
43
|
-
pos += 2;
|
|
27
|
+
for (const { id, dataStart, size } of dirRecords(dir, 'overruns stream')) {
|
|
44
28
|
switch (id) {
|
|
45
29
|
case REC_PROJECT_CODEPAGE:
|
|
46
30
|
if (size >= 2)
|
package/dist/xml/xml-read.d.ts
CHANGED
|
@@ -134,3 +134,63 @@ export declare function numFinite(val: string | undefined, min?: number): number
|
|
|
134
134
|
* re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
|
|
135
135
|
* (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
|
|
136
136
|
export declare function coerceNumericLiteral(text: string): string | number;
|
|
137
|
+
/** Narrow an enumerated attribute through its guard; `undefined` when absent or not a member. */
|
|
138
|
+
export declare function enumToken<T extends string>(val: string | undefined, isMember: (candidate: string) => candidate is T): T | undefined;
|
|
139
|
+
/**
|
|
140
|
+
* Gathers one element's character data across the open/text/close events a SAX parse delivers it in.
|
|
141
|
+
*
|
|
142
|
+
* Nine parsers used to open-code this: latch a flag and clear a buffer on the open, append every
|
|
143
|
+
* chunk while latched, consume the buffer and unlatch on the close. Six spellings of one idea, and
|
|
144
|
+
* none of them honoured the one thing {@link SaxHandlers.onOpen} warns about. A self-closing `<x/>`
|
|
145
|
+
* fires no matching close, so `<t/>`, `<text/>`, `<xm:f/>` and `<totalsRowFormula/>`, all legal and
|
|
146
|
+
* all written by real files, latched a capture that nothing would ever close. What kept that from
|
|
147
|
+
* corrupting anything was the order the next open happened to reset things in, which is an accident
|
|
148
|
+
* rather than a property anyone chose, on a path that reads untrusted input. Taking `selfClosing`
|
|
149
|
+
* here makes it structural, once.
|
|
150
|
+
*
|
|
151
|
+
* The other thing the open-coded versions disagreed on is what an unrelated element opening
|
|
152
|
+
* mid-capture should do. Ending the capture is never what a caller wants: the text belongs to the
|
|
153
|
+
* element that opened it, and a nested or sibling element is not that element. So an open that is
|
|
154
|
+
* not for a captured name leaves an capture in progress alone, and {@link close} answers only for
|
|
155
|
+
* the element that started it.
|
|
156
|
+
*
|
|
157
|
+
* Decoding stays outside. A `<t>` needs `decodeSpreadsheetText` over the whole element and never
|
|
158
|
+
* over a chunk, an `<xm:f>` needs nothing, and a coordinate needs a number: the caller knows which.
|
|
159
|
+
*/
|
|
160
|
+
export declare class TextCapture {
|
|
161
|
+
#private;
|
|
162
|
+
/** @param names the element local name, or the set of names this instance may capture. */
|
|
163
|
+
constructor(names: string | Iterable<string>);
|
|
164
|
+
/** Whether a capture is currently open. */
|
|
165
|
+
get capturing(): boolean;
|
|
166
|
+
/** Begin capturing `local` if it is one of this instance's names and is not self-closing. */
|
|
167
|
+
open(local: string, selfClosing: boolean): void;
|
|
168
|
+
/** Feed a chunk of character data; ignored when no capture is open. */
|
|
169
|
+
text(chunk: string): void;
|
|
170
|
+
/** The gathered text when `local` closes the captured element, else `undefined`. Unlatches. */
|
|
171
|
+
close(local: string): string | undefined;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Yield each named element's text as that element closes, as `{local, text}`.
|
|
175
|
+
*
|
|
176
|
+
* The third member of the pull-shaped family beside {@link openElements} ("scan opens, read
|
|
177
|
+
* attributes") and {@link closeEmptyElements}: this one is "capture these elements' text, tell me
|
|
178
|
+
* each as it closes". A parser whose whole job is reading a handful of text elements out of a part
|
|
179
|
+
* writes a `for..of` over it instead of a {@link parseXml} handler triple whose open and text arms
|
|
180
|
+
* are the same three lines every time.
|
|
181
|
+
*
|
|
182
|
+
* It is deliberately not for every {@link TextCapture} caller. A parser that interleaves capture
|
|
183
|
+
* with per-element state of its own (a `<dataValidation>` gathering formulae, a `<tableColumn>`
|
|
184
|
+
* attaching a totals formula to the column it is inside) needs the open and attribute events too,
|
|
185
|
+
* and stays bespoke; forcing it through here would trade a handler triple for a second pass.
|
|
186
|
+
*
|
|
187
|
+
* A self-closing `<x/>` carries no text and fires no close, so it yields nothing, which is the
|
|
188
|
+
* behaviour {@link TextCapture} exists to make structural rather than a branch each caller
|
|
189
|
+
* remembers.
|
|
190
|
+
*
|
|
191
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
192
|
+
*/
|
|
193
|
+
export declare function capturedText(source: string, names: string | Iterable<string>): Generator<{
|
|
194
|
+
local: string;
|
|
195
|
+
text: string;
|
|
196
|
+
}>;
|
package/dist/xml/xml-read.js
CHANGED
|
@@ -225,3 +225,50 @@ export function coerceNumericLiteral(text) {
|
|
|
225
225
|
const trimmed = text.trim();
|
|
226
226
|
return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;
|
|
227
227
|
}
|
|
228
|
+
export function enumToken(val, isMember) {
|
|
229
|
+
return val !== undefined && isMember(val) ? val : undefined;
|
|
230
|
+
}
|
|
231
|
+
export class TextCapture {
|
|
232
|
+
#names;
|
|
233
|
+
#capturing;
|
|
234
|
+
#text = '';
|
|
235
|
+
constructor(names) {
|
|
236
|
+
this.#names = new Set(typeof names === 'string' ? [names] : names);
|
|
237
|
+
}
|
|
238
|
+
get capturing() {
|
|
239
|
+
return this.#capturing !== undefined;
|
|
240
|
+
}
|
|
241
|
+
open(local, selfClosing) {
|
|
242
|
+
if (selfClosing || !this.#names.has(local))
|
|
243
|
+
return;
|
|
244
|
+
this.#capturing = local;
|
|
245
|
+
this.#text = '';
|
|
246
|
+
}
|
|
247
|
+
text(chunk) {
|
|
248
|
+
if (this.#capturing !== undefined)
|
|
249
|
+
this.#text += chunk;
|
|
250
|
+
}
|
|
251
|
+
close(local) {
|
|
252
|
+
if (this.#capturing !== local)
|
|
253
|
+
return undefined;
|
|
254
|
+
this.#capturing = undefined;
|
|
255
|
+
return this.#text;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export function* capturedText(source, names) {
|
|
259
|
+
const capture = new TextCapture(names);
|
|
260
|
+
for (const event of xmlEvents(source)) {
|
|
261
|
+
if (event.kind === 'open') {
|
|
262
|
+
capture.open(localName(event.name), event.selfClosing);
|
|
263
|
+
}
|
|
264
|
+
else if (event.kind === 'text') {
|
|
265
|
+
capture.text(event.text);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const local = localName(event.name);
|
|
269
|
+
const text = capture.close(local);
|
|
270
|
+
if (text !== undefined)
|
|
271
|
+
yield { local, text };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
package/dist/xml/xml.d.ts
CHANGED
|
@@ -50,11 +50,53 @@ export declare function stripFormulaEquals(value: string | number): string;
|
|
|
50
50
|
* entirely: the two-state-plus-absent contract every flag writer here shares.
|
|
51
51
|
*/
|
|
52
52
|
export declare function boolAttr(name: string, value: boolean | undefined): string;
|
|
53
|
+
/**
|
|
54
|
+
* A free-string attribute rendered with a leading space (` name="a & b"`), or '' when the value
|
|
55
|
+
* is undefined. The third of the trio with {@link boolAttr} and {@link numAttr}: the value is prose
|
|
56
|
+
* the caller chose, so it is escaped rather than checked.
|
|
57
|
+
*
|
|
58
|
+
* A token from a closed OOXML enumeration is the other case and goes through {@link checkedToken}
|
|
59
|
+
* instead. Escaping a bogus token would produce a well-formed document that Excel still rejects,
|
|
60
|
+
* which hides the mistake in the file rather than raising it at the call.
|
|
61
|
+
*/
|
|
62
|
+
export declare function textAttr(name: string, value: string | undefined): string;
|
|
63
|
+
/**
|
|
64
|
+
* Refuse a token from a closed OOXML enumeration that the writer would otherwise emit verbatim, and
|
|
65
|
+
* return it unchanged when it belongs. The public types already forbid an out-of-contract value, so
|
|
66
|
+
* this fires only for one smuggled past them by an untyped caller, a `JSON.parse`, or a cast.
|
|
67
|
+
*
|
|
68
|
+
* The writer must never serialise such a value: it would be schema-invalid OOXML that Excel
|
|
69
|
+
* sometimes tolerates yet the library's own reader, which narrows every such token through the same
|
|
70
|
+
* guard, discards on read-back. Refusing at the write boundary keeps the two symmetric, garbage out
|
|
71
|
+
* refused exactly as garbage in, so a value the writer accepts is always one that round-trips.
|
|
72
|
+
*
|
|
73
|
+
* @throws {AuthoringError} naming the value and the enumeration.
|
|
74
|
+
*/
|
|
75
|
+
export declare function checkedToken(value: string, isValid: (candidate: string) => boolean, kind: string): string;
|
|
53
76
|
/**
|
|
54
77
|
* A numeric attribute rendered with a leading space (` name="42"`), or '' when the value is undefined
|
|
55
78
|
* so that a count an author never set stays out of the element rather than fabricating a default.
|
|
79
|
+
*
|
|
80
|
+
* The value goes through {@link numberText}, so a non-finite one is refused here rather than written
|
|
81
|
+
* as `NaN`. Every attribute on this path is `xsd:double` or `xsd:unsignedInt`, neither of which has a
|
|
82
|
+
* spelling for it, so the alternative is a package Excel reports as damaged.
|
|
83
|
+
*
|
|
84
|
+
* @throws {AuthoringError} when the value is not finite.
|
|
85
|
+
*/
|
|
86
|
+
export declare function numAttr(name: string, value: number | undefined): string;
|
|
87
|
+
/**
|
|
88
|
+
* Refuse a number OOXML cannot spell. Every numeric attribute in the format is `xsd:double`,
|
|
89
|
+
* `xsd:unsignedInt` or a bounded flavour of one, and none of those lexical spaces has a form for a
|
|
90
|
+
* NaN or an infinity, so a value that reaches the file as `NaN` is a package Excel reports as
|
|
91
|
+
* damaged.
|
|
92
|
+
*
|
|
93
|
+
* Exported for the callers that must refuse before they write. A number can be unwritable and still
|
|
94
|
+
* be read on the way to the bytes: compared, summed, walked. A comparison against `NaN` or an
|
|
95
|
+
* infinity silently takes the wrong branch long before the value would have been serialised.
|
|
96
|
+
*
|
|
97
|
+
* @throws {AuthoringError} naming the value.
|
|
56
98
|
*/
|
|
57
|
-
export declare function
|
|
99
|
+
export declare function assertWritableNumber(value: number): void;
|
|
58
100
|
/**
|
|
59
101
|
* A finite number serialises as its shortest round-trippable decimal; a non-finite one
|
|
60
102
|
* has no OOXML numeric representation, so the writer refuses it rather than emit `NaN`.
|
package/dist/xml/xml.js
CHANGED
|
@@ -55,13 +55,25 @@ export function stripFormulaEquals(value) {
|
|
|
55
55
|
export function boolAttr(name, value) {
|
|
56
56
|
return value === undefined ? '' : ` ${name}="${value ? 1 : 0}"`;
|
|
57
57
|
}
|
|
58
|
-
export function
|
|
59
|
-
return value === undefined ? '' : ` ${name}="${value}"`;
|
|
58
|
+
export function textAttr(name, value) {
|
|
59
|
+
return value === undefined ? '' : ` ${name}="${escapeAttr(value)}"`;
|
|
60
60
|
}
|
|
61
|
-
export function
|
|
62
|
-
if (!
|
|
63
|
-
throw new AuthoringError(`
|
|
61
|
+
export function checkedToken(value, isValid, kind) {
|
|
62
|
+
if (!isValid(value)) {
|
|
63
|
+
throw new AuthoringError(`Invalid ${kind} ${JSON.stringify(value)}: not a value the OOXML enumeration allows`);
|
|
64
64
|
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
export function numAttr(name, value) {
|
|
68
|
+
return value === undefined ? '' : ` ${name}="${numberText(value)}"`;
|
|
69
|
+
}
|
|
70
|
+
export function assertWritableNumber(value) {
|
|
71
|
+
if (Number.isFinite(value))
|
|
72
|
+
return;
|
|
73
|
+
throw new AuthoringError(`cannot write a non-finite number (${value}): it has no OOXML representation`);
|
|
74
|
+
}
|
|
75
|
+
export function numberText(value) {
|
|
76
|
+
assertWritableNumber(value);
|
|
65
77
|
return String(value);
|
|
66
78
|
}
|
|
67
79
|
export const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|