@shbernal/ts-xlsx 1.2.0 → 2.0.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 +5 -0
- package/dist/core/address.d.ts +49 -2
- package/dist/core/address.js +31 -3
- package/dist/core/autofilter.d.ts +2 -2
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +7 -7
- package/dist/core/column.d.ts +8 -9
- package/dist/core/column.js +2 -4
- package/dist/core/comment-thread.d.ts +32 -23
- package/dist/core/comment-thread.js +4 -4
- package/dist/core/conditional-formatting-overlay.d.ts +1 -1
- package/dist/core/conditional-formatting.d.ts +3 -3
- package/dist/core/data-validation-overlay.d.ts +1 -1
- package/dist/core/data-validation.d.ts +3 -3
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.js +3 -3
- package/dist/core/image.d.ts +37 -2
- package/dist/core/image.js +6 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +6 -12
- package/dist/core/merge.js +0 -3
- package/dist/core/page-setup.d.ts +4 -4
- package/dist/core/pivot-table.d.ts +2 -4
- package/dist/core/pivot-table.js +5 -5
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +4 -4
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +2 -8
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +14 -14
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +30 -42
- package/dist/core/table.js +16 -10
- package/dist/core/theme.d.ts +13 -43
- package/dist/core/theme.js +2 -148
- package/dist/core/value.d.ts +7 -7
- package/dist/core/workbook-protection.d.ts +2 -2
- package/dist/core/workbook-styles.d.ts +26 -0
- package/dist/core/workbook-styles.js +48 -0
- package/dist/core/workbook-theme.d.ts +25 -0
- package/dist/core/workbook-theme.js +49 -0
- package/dist/core/workbook-vba.d.ts +17 -0
- package/dist/core/workbook-vba.js +79 -0
- package/dist/core/workbook.d.ts +141 -83
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +9 -0
- package/dist/core/worksheet-comments.js +52 -0
- package/dist/core/worksheet-model.d.ts +4 -4
- package/dist/core/worksheet-model.js +1 -0
- package/dist/core/worksheet-pictures.d.ts +29 -0
- package/dist/core/worksheet-pictures.js +42 -0
- package/dist/core/worksheet.d.ts +94 -77
- package/dist/core/worksheet.js +36 -95
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/entries/core.d.ts +4 -4
- package/dist/entries/core.js +1 -1
- package/dist/entries/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -0
- package/dist/entries/xlsx.js +1 -0
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +1 -2
- package/dist/io/csv/write.d.ts +8 -3
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/namespaces.d.ts +1 -1
- package/dist/io/opc/read-opc.d.ts +19 -3
- package/dist/io/opc/read-opc.js +14 -8
- package/dist/io/opc/read-options.d.ts +2 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/opc/zip-mtime.d.ts +5 -0
- package/dist/io/opc/zip-mtime.js +1 -0
- package/dist/io/style/xf-style.d.ts +38 -6
- package/dist/io/style/xf-style.js +22 -0
- package/dist/io/xlsb/errors.d.ts +1 -1
- package/dist/io/xlsb/formula.d.ts +4 -4
- package/dist/io/xlsb/primitives.d.ts +8 -8
- package/dist/io/xlsb/ptg-functions.d.ts +1 -1
- package/dist/io/xlsb/read-styles.js +2 -20
- package/dist/io/xlsb/read.d.ts +2 -2
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
- package/dist/io/xlsx/cell-accumulator.js +111 -30
- package/dist/io/xlsx/cell-value.d.ts +2 -2
- package/dist/io/xlsx/cell-value.js +3 -3
- package/dist/io/xlsx/color-xml.js +11 -17
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +13 -6
- package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
- package/dist/io/xlsx/conditional-formatting.js +7 -16
- package/dist/io/xlsx/data-validation.d.ts +2 -2
- package/dist/io/xlsx/data-validation.js +1 -1
- package/dist/io/xlsx/edit-vba.js +2 -1
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +5 -5
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +8 -8
- package/dist/io/xlsx/namespaces.d.ts +2 -2
- package/dist/io/xlsx/package-plan.js +5 -2
- package/dist/io/xlsx/read-pivot.js +2 -5
- package/dist/io/xlsx/read-rows.d.ts +13 -13
- package/dist/io/xlsx/read-rows.js +19 -55
- package/dist/io/xlsx/read-shared-strings.js +7 -5
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +40 -65
- package/dist/io/xlsx/read-worksheet.js +67 -115
- package/dist/io/xlsx/read.d.ts +3 -5
- package/dist/io/xlsx/read.js +94 -71
- package/dist/io/xlsx/rich-runs.d.ts +1 -1
- package/dist/io/xlsx/rich-runs.js +1 -1
- package/dist/io/xlsx/rich-text.d.ts +1 -1
- package/dist/io/xlsx/shared-strings.d.ts +2 -2
- package/dist/io/xlsx/sheet-properties.js +5 -4
- package/dist/io/xlsx/styles.d.ts +8 -9
- package/dist/io/xlsx/styles.js +4 -3
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +8 -13
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +142 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +9 -21
- package/dist/io/xlsx/workbook-xml.d.ts +1 -1
- package/dist/io/xlsx/workbook-xml.js +6 -2
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +7 -12
- package/dist/io/xlsx/write-stream.d.ts +15 -15
- package/dist/io/xlsx/write-stream.js +5 -3
- package/dist/io/xlsx/write.d.ts +12 -7
- package/dist/io/xlsx/write.js +10 -6
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/vba/bytes.d.ts +12 -0
- package/dist/vba/bytes.js +40 -0
- package/dist/vba/cfb-writer.d.ts +1 -1
- package/dist/vba/cfb.d.ts +1 -1
- package/dist/vba/cfb.js +15 -51
- package/dist/vba/codepage.d.ts +2 -2
- package/dist/vba/errors.d.ts +2 -2
- package/dist/vba/index.d.ts +2 -2
- package/dist/vba/index.js +2 -2
- package/dist/vba/ms-ovba.d.ts +1 -1
- package/dist/vba/ms-ovba.js +1 -3
- package/dist/vba/project-editor.d.ts +9 -9
- package/dist/vba/project-editor.js +2 -11
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +1 -10
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +38 -9
- package/dist/xml/xml-read.js +23 -0
- package/dist/xml/xml.d.ts +31 -9
- package/dist/xml/xml.js +23 -3
- package/package.json +41 -35
package/dist/io/xlsx/write.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { strToU8, zip, zipSync } from 'fflate';
|
|
2
|
-
import { DEFAULT_THEME_XML } from '../../core/theme.js';
|
|
3
2
|
import { AuthoringError } from '../../errors.js';
|
|
4
3
|
import { THEME_PART_PATH } from '../opc/part-paths.js';
|
|
5
4
|
import { relsPartXml } from '../opc/rels.js';
|
|
5
|
+
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
6
6
|
import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
|
|
7
7
|
import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
|
|
8
8
|
import { drawingRelsXml, drawingXml } from './images.js';
|
|
@@ -12,17 +12,18 @@ import { REL } from './relationships.js';
|
|
|
12
12
|
import { SharedStringTable } from './shared-strings.js';
|
|
13
13
|
import { StyleRegistry } from './styles.js';
|
|
14
14
|
import { tableXml } from './tables.js';
|
|
15
|
+
import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
|
|
15
16
|
import { personsXml, threadedCommentsXml } from './threaded-comments.js';
|
|
16
17
|
import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
|
|
17
18
|
import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
|
|
18
19
|
export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
|
|
19
20
|
export function writeXlsx(workbook, options = {}) {
|
|
20
|
-
return zipSync(buildPackageParts(workbook, options), { level: 6 });
|
|
21
|
+
return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
|
|
21
22
|
}
|
|
22
23
|
export async function writeXlsxAsync(workbook, options = {}) {
|
|
23
24
|
const parts = buildPackageParts(workbook, options);
|
|
24
25
|
return await new Promise((resolve, reject) => {
|
|
25
|
-
zip(parts, { level: 6 }, (error, data) => {
|
|
26
|
+
zip(parts, { level: 6, mtime: FIXED_ENTRY_MTIME }, (error, data) => {
|
|
26
27
|
if (error)
|
|
27
28
|
reject(error);
|
|
28
29
|
else
|
|
@@ -65,7 +66,7 @@ function resolveSheetReferences(plan) {
|
|
|
65
66
|
export function buildPackageParts(workbook, options = {}) {
|
|
66
67
|
const sheets = workbook.worksheets;
|
|
67
68
|
if (sheets.length === 0) {
|
|
68
|
-
throw new AuthoringError('cannot write a workbook with no worksheets
|
|
69
|
+
throw new AuthoringError('cannot write a workbook with no worksheets: a zero-sheet package is corrupt to Excel');
|
|
69
70
|
}
|
|
70
71
|
const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
|
|
71
72
|
const media = planMedia(workbook, sheets);
|
|
@@ -177,13 +178,16 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
177
178
|
'[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
|
|
178
179
|
'_rels/.rels': strToU8(rootRelsXml(preserved.root)),
|
|
179
180
|
'docProps/core.xml': strToU8(corePropsXml(workbook.properties)),
|
|
180
|
-
'docProps/app.xml': strToU8(appPropsXml()),
|
|
181
|
+
'docProps/app.xml': strToU8(appPropsXml(workbook.properties)),
|
|
181
182
|
'xl/workbook.xml': strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
|
|
182
183
|
'xl/_rels/workbook.xml.rels': strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
|
|
183
184
|
'xl/styles.xml': strToU8(styles.toXml()),
|
|
184
185
|
};
|
|
185
186
|
if (!preserved.themeEmitted) {
|
|
186
|
-
|
|
187
|
+
const overrides = workbook.themeOverrides;
|
|
188
|
+
files[THEME_PART_PATH] = strToU8(overrides === undefined
|
|
189
|
+
? DEFAULT_THEME_XML
|
|
190
|
+
: applyThemeOverrides(DEFAULT_THEME_XML, overrides));
|
|
187
191
|
}
|
|
188
192
|
if (hasSharedStrings) {
|
|
189
193
|
files['xl/sharedStrings.xml'] = strToU8(sharedStrings.toXml());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wrap an x14 extension body in the `<ext>` envelope every worksheet- and workbook-level x14
|
|
3
3
|
* extension shares: a `uri` scoping the extension to its feature (a fixed GUID from
|
|
4
|
-
* {@link namespaces.ts}) plus the `xmlns:x14` binding the extension namespace on the element itself
|
|
4
|
+
* {@link namespaces.ts}) plus the `xmlns:x14` binding the extension namespace on the element itself.
|
|
5
5
|
* Excel declares it per `<ext>`, never at the part root. Callers needing an `<extLst>` wrapper add it
|
|
6
6
|
* around the result; the producers the worksheet serialiser gathers return this bare so several exts
|
|
7
7
|
* compose under a single shared `<extLst>`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
|
|
2
|
+
export declare function readU16(buf: Uint8Array, at: number): number;
|
|
3
|
+
/** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
|
|
4
|
+
export declare function readU32(buf: Uint8Array, at: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Decode UTF-16LE code units: the encoding [MS-CFB] uses for directory-entry names and [MS-OVBA] for
|
|
7
|
+
* every "Unicode" name field. A trailing odd byte is dropped: these fields are length-prefixed by the
|
|
8
|
+
* producer and a half code unit carries nothing to decode.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decodeUtf16le(bytes: Uint8Array): string;
|
|
11
|
+
/** Join byte chunks into one buffer. */
|
|
12
|
+
export declare function concat(chunks: Uint8Array[]): Uint8Array;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { VbaParseError } from './errors.js';
|
|
2
|
+
function truncated(at, need, length) {
|
|
3
|
+
return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
|
|
4
|
+
}
|
|
5
|
+
export function readU16(buf, at) {
|
|
6
|
+
const b0 = buf[at];
|
|
7
|
+
const b1 = buf[at + 1];
|
|
8
|
+
if (b0 === undefined || b1 === undefined)
|
|
9
|
+
throw truncated(at, 2, buf.length);
|
|
10
|
+
return b0 | (b1 << 8);
|
|
11
|
+
}
|
|
12
|
+
export function readU32(buf, at) {
|
|
13
|
+
const b0 = buf[at];
|
|
14
|
+
const b1 = buf[at + 1];
|
|
15
|
+
const b2 = buf[at + 2];
|
|
16
|
+
const b3 = buf[at + 3];
|
|
17
|
+
if (b0 === undefined || b1 === undefined || b2 === undefined || b3 === undefined) {
|
|
18
|
+
throw truncated(at, 4, buf.length);
|
|
19
|
+
}
|
|
20
|
+
return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
|
|
21
|
+
}
|
|
22
|
+
export function decodeUtf16le(bytes) {
|
|
23
|
+
let s = '';
|
|
24
|
+
for (let i = 0; i + 1 < bytes.length; i += 2) {
|
|
25
|
+
s += String.fromCharCode(readU16(bytes, i));
|
|
26
|
+
}
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
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
|
+
}
|
package/dist/vba/cfb-writer.d.ts
CHANGED
|
@@ -14,6 +14,6 @@ export type CfbNode = CfbStream | CfbStorage;
|
|
|
14
14
|
* by linear scan and by tree navigation.
|
|
15
15
|
*
|
|
16
16
|
* @throws {VbaAuthorError} if any name is empty or exceeds 31 characters, sibling names collide, or the
|
|
17
|
-
* project is so large it would need more than 109 FAT sectors (~7 MB
|
|
17
|
+
* project is so large it would need more than 109 FAT sectors (~7 MB, far beyond any real project).
|
|
18
18
|
*/
|
|
19
19
|
export declare function writeCompoundFile(root: readonly CfbNode[]): Uint8Array;
|
package/dist/vba/cfb.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class CompoundFile {
|
|
|
8
8
|
readStream(name: string): Uint8Array | undefined;
|
|
9
9
|
/**
|
|
10
10
|
* Reconstruct the container's top-level children as the writer's node shape, recursing into every
|
|
11
|
-
* storage
|
|
11
|
+
* storage, so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
|
|
12
12
|
* Walks the red-black sibling tree each storage navigates (not the linear directory scan), so any part
|
|
13
13
|
* a host reaches is carried through. Cycle- and bounds-guarded like every other chain walk here.
|
|
14
14
|
*/
|
package/dist/vba/cfb.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { concat, decodeUtf16le, readU16, readU32 } from './bytes.js';
|
|
1
2
|
import { VbaParseError } from './errors.js';
|
|
2
3
|
const MAX_REGULAR_SECTOR = 0xfffffffa;
|
|
3
4
|
const NOSTREAM = 0xffffffff;
|
|
@@ -10,7 +11,6 @@ const CFB_SIGNATURE_HI = 0xe11ab1a1;
|
|
|
10
11
|
const DIR_ENTRY_SIZE = 128;
|
|
11
12
|
export class CompoundFile {
|
|
12
13
|
#buf;
|
|
13
|
-
#view;
|
|
14
14
|
#sectorSize;
|
|
15
15
|
#miniSectorSize;
|
|
16
16
|
#miniCutoff;
|
|
@@ -21,14 +21,13 @@ export class CompoundFile {
|
|
|
21
21
|
#miniStream;
|
|
22
22
|
constructor(buf) {
|
|
23
23
|
this.#buf = buf;
|
|
24
|
-
this.#view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
25
24
|
if (buf.length < 512)
|
|
26
25
|
throw new VbaParseError('compound file shorter than its 512-byte header');
|
|
27
|
-
if (this.#
|
|
26
|
+
if (readU32(this.#buf, 0) !== CFB_SIGNATURE_LO || readU32(this.#buf, 4) !== CFB_SIGNATURE_HI) {
|
|
28
27
|
throw new VbaParseError('not a compound file (bad OLE2 signature)');
|
|
29
28
|
}
|
|
30
|
-
const sectorShift = this.#
|
|
31
|
-
const miniSectorShift = this.#
|
|
29
|
+
const sectorShift = readU16(this.#buf, 30);
|
|
30
|
+
const miniSectorShift = readU16(this.#buf, 32);
|
|
32
31
|
if (sectorShift !== 9 && sectorShift !== 12) {
|
|
33
32
|
throw new VbaParseError(`unsupported sector shift ${sectorShift}`);
|
|
34
33
|
}
|
|
@@ -37,12 +36,12 @@ export class CompoundFile {
|
|
|
37
36
|
this.#sectorSize = 1 << sectorShift;
|
|
38
37
|
this.#miniSectorSize = 1 << miniSectorShift;
|
|
39
38
|
this.#maxSector = Math.floor(buf.length / this.#sectorSize);
|
|
40
|
-
const numFatSectors = this.#
|
|
41
|
-
const firstDirSector = this.#
|
|
42
|
-
this.#miniCutoff = this.#
|
|
43
|
-
const firstMiniFatSector = this.#
|
|
44
|
-
const firstDifatSector = this.#
|
|
45
|
-
const numDifatSectors = this.#
|
|
39
|
+
const numFatSectors = readU32(this.#buf, 44);
|
|
40
|
+
const firstDirSector = readU32(this.#buf, 48);
|
|
41
|
+
this.#miniCutoff = readU32(this.#buf, 56);
|
|
42
|
+
const firstMiniFatSector = readU32(this.#buf, 60);
|
|
43
|
+
const firstDifatSector = readU32(this.#buf, 68);
|
|
44
|
+
const numDifatSectors = readU32(this.#buf, 72);
|
|
46
45
|
const fatSectorIds = this.#readDifat(numFatSectors, firstDifatSector, numDifatSectors);
|
|
47
46
|
this.#fat = this.#readFat(fatSectorIds);
|
|
48
47
|
this.#miniFat = this.#readChainValues(firstMiniFatSector);
|
|
@@ -103,7 +102,7 @@ export class CompoundFile {
|
|
|
103
102
|
#readDifat(numFatSectors, firstDifat, numDifat) {
|
|
104
103
|
const ids = [];
|
|
105
104
|
for (let i = 0; i < 109 && ids.length < numFatSectors; i++) {
|
|
106
|
-
const v = this.#
|
|
105
|
+
const v = readU32(this.#buf, 76 + i * 4);
|
|
107
106
|
if (v >= MAX_REGULAR_SECTOR)
|
|
108
107
|
break;
|
|
109
108
|
ids.push(v);
|
|
@@ -117,11 +116,11 @@ export class CompoundFile {
|
|
|
117
116
|
seen.add(sector);
|
|
118
117
|
const base = this.#dataSectorOffset(sector);
|
|
119
118
|
for (let i = 0; i < perSector; i++) {
|
|
120
|
-
const v = this.#
|
|
119
|
+
const v = readU32(this.#buf, base + i * 4);
|
|
121
120
|
if (v < MAX_REGULAR_SECTOR)
|
|
122
121
|
ids.push(v);
|
|
123
122
|
}
|
|
124
|
-
sector = this.#
|
|
123
|
+
sector = readU32(this.#buf, base + perSector * 4);
|
|
125
124
|
}
|
|
126
125
|
return ids;
|
|
127
126
|
}
|
|
@@ -131,7 +130,7 @@ export class CompoundFile {
|
|
|
131
130
|
for (const sid of fatSectorIds) {
|
|
132
131
|
const base = this.#dataSectorOffset(sid);
|
|
133
132
|
for (let i = 0; i < perSector; i++)
|
|
134
|
-
fat.push(this.#
|
|
133
|
+
fat.push(readU32(this.#buf, base + i * 4));
|
|
135
134
|
}
|
|
136
135
|
return fat;
|
|
137
136
|
}
|
|
@@ -146,7 +145,7 @@ export class CompoundFile {
|
|
|
146
145
|
seen.add(sector);
|
|
147
146
|
const base = this.#dataSectorOffset(sector);
|
|
148
147
|
for (let i = 0; i < perSector; i++)
|
|
149
|
-
values.push(this.#
|
|
148
|
+
values.push(readU32(this.#buf, base + i * 4));
|
|
150
149
|
sector = this.#nextInFat(sector);
|
|
151
150
|
}
|
|
152
151
|
return values;
|
|
@@ -256,39 +255,4 @@ export class CompoundFile {
|
|
|
256
255
|
}
|
|
257
256
|
return base;
|
|
258
257
|
}
|
|
259
|
-
#u16(at) {
|
|
260
|
-
return this.#view.getUint16(at, true);
|
|
261
|
-
}
|
|
262
|
-
#u32(at) {
|
|
263
|
-
return this.#view.getUint32(at, true);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function readU16(buf, at) {
|
|
267
|
-
return buf[at] | (buf[at + 1] << 8);
|
|
268
|
-
}
|
|
269
|
-
function readU32(buf, at) {
|
|
270
|
-
return ((buf[at] |
|
|
271
|
-
(buf[at + 1] << 8) |
|
|
272
|
-
(buf[at + 2] << 16) |
|
|
273
|
-
(buf[at + 3] << 24)) >>>
|
|
274
|
-
0);
|
|
275
|
-
}
|
|
276
|
-
function decodeUtf16le(bytes) {
|
|
277
|
-
let s = '';
|
|
278
|
-
for (let i = 0; i + 1 < bytes.length; i += 2) {
|
|
279
|
-
s += String.fromCharCode(bytes[i] | (bytes[i + 1] << 8));
|
|
280
|
-
}
|
|
281
|
-
return s;
|
|
282
|
-
}
|
|
283
|
-
function concat(chunks) {
|
|
284
|
-
let total = 0;
|
|
285
|
-
for (const c of chunks)
|
|
286
|
-
total += c.length;
|
|
287
|
-
const out = new Uint8Array(total);
|
|
288
|
-
let off = 0;
|
|
289
|
-
for (const c of chunks) {
|
|
290
|
-
out.set(c, off);
|
|
291
|
-
off += c.length;
|
|
292
|
-
}
|
|
293
|
-
return out;
|
|
294
258
|
}
|
package/dist/vba/codepage.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type Decoder = InstanceType<typeof TextDecoder>;
|
|
|
3
3
|
export type Encoder = (text: string) => Uint8Array;
|
|
4
4
|
/**
|
|
5
5
|
* A `TextDecoder` for the given VBA project code page. Non-fatal (malformed bytes become U+FFFD rather
|
|
6
|
-
* than throwing) because recovered source is for reading, not re-encoding
|
|
6
|
+
* than throwing) because recovered source is for reading, not re-encoding: a stray byte must not sink
|
|
7
7
|
* the whole extraction. An unknown code page falls back to windows-1252.
|
|
8
8
|
*/
|
|
9
9
|
export declare function decoderForCodePage(codePage: number): Decoder;
|
|
@@ -12,7 +12,7 @@ export declare function decoderForCodePage(codePage: number): Decoder;
|
|
|
12
12
|
* decoding every byte 0x00..0xFF through the matching decoder and inverting the map. This is exact for
|
|
13
13
|
* the single-byte code pages a VBA project realistically declares. Under a multi-byte code page (Shift
|
|
14
14
|
* JIS, GBK, …) the high bytes are lead bytes, not standalone characters, so only ASCII inverts and any
|
|
15
|
-
* non-ASCII character is rejected
|
|
15
|
+
* non-ASCII character is rejected, failing closed rather than emitting a corrupt byte. A character the chosen
|
|
16
16
|
* code page cannot represent throws {@link VbaAuthorError}.
|
|
17
17
|
*/
|
|
18
18
|
export declare function encoderForCodePage(codePage: number): Encoder;
|
package/dist/vba/errors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XlsxError } from '../errors.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed
|
|
3
|
+
* Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed: a malformed
|
|
4
4
|
* compound file, a corrupt compressed stream, or a `dir` record that does not conform to [MS-OVBA].
|
|
5
5
|
* A workbook with no macros never produces this: {@link Workbook.vbaProject} is `undefined` instead.
|
|
6
6
|
*
|
|
@@ -13,7 +13,7 @@ export declare class VbaParseError extends XlsxError {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Thrown when authoring a VBA project (synthesizing a `vbaProject.bin` from module source) is asked to
|
|
16
|
-
* produce something that cannot be encoded to a well-formed container
|
|
16
|
+
* produce something that cannot be encoded to a well-formed container: a stream name longer than the
|
|
17
17
|
* [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
|
|
18
18
|
* writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
|
|
19
19
|
* {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
|
package/dist/vba/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile
|
|
1
|
+
export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile } from './cfb-writer.ts';
|
|
2
2
|
export { VbaAuthorError, VbaParseError } from './errors.ts';
|
|
3
3
|
export { compressContainer, decompressContainer } from './ms-ovba.ts';
|
|
4
4
|
export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, type VbaModule, type VbaModuleKind, type VbaProject, type VbaProjectSignature, type VbaProjectSignatureKind, vbaProjectSignatureKind, } from './project.ts';
|
|
5
|
-
export { addVbaReference, removeVbaModule, type VbaLibraryReference
|
|
5
|
+
export { addVbaReference, removeVbaModule, type VbaLibraryReference } from './project-editor.ts';
|
package/dist/vba/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { writeCompoundFile
|
|
1
|
+
export { writeCompoundFile } from './cfb-writer.js';
|
|
2
2
|
export { VbaAuthorError, VbaParseError } from './errors.js';
|
|
3
3
|
export { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
4
4
|
export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, vbaProjectSignatureKind, } from './project.js';
|
|
5
|
-
export { addVbaReference, removeVbaModule
|
|
5
|
+
export { addVbaReference, removeVbaModule } from './project-editor.js';
|
package/dist/vba/ms-ovba.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare function decompressContainer(buf: Uint8Array, start?: number, maxOutput?: number): Uint8Array;
|
|
6
6
|
/**
|
|
7
|
-
* Compress `data` into an MS-OVBA CompressedContainer
|
|
7
|
+
* Compress `data` into an MS-OVBA CompressedContainer: the inverse of {@link decompressContainer}.
|
|
8
8
|
* Every 4096-decompressed-byte window is emitted as a compressed chunk of literal and copy tokens, or
|
|
9
9
|
* stored verbatim when compression would not shrink it (so the encoded chunk never exceeds the 12-bit
|
|
10
10
|
* size field). The result re-expands to `data` byte-for-byte.
|
package/dist/vba/ms-ovba.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readU16 } from './bytes.js';
|
|
1
2
|
import { VbaParseError } from './errors.js';
|
|
2
3
|
const MAX_CHUNK_DECOMPRESSED = 4096;
|
|
3
4
|
const CHUNK_SIGNATURE = 0b011 << 12;
|
|
@@ -128,6 +129,3 @@ function copyTokenHelp(decompressedSoFar) {
|
|
|
128
129
|
const lengthMask = 0xffff >> bitCount;
|
|
129
130
|
return { lengthMask, bitCount };
|
|
130
131
|
}
|
|
131
|
-
function readU16(buf, at) {
|
|
132
|
-
return buf[at] | (buf[at + 1] << 8);
|
|
133
|
-
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* stream, its MODULE record block in `dir` (decrementing `MODULES_COUNT`), and its `Module=`/`Class=` +
|
|
5
5
|
* workspace lines in `PROJECT`/`PROJECTwm`.
|
|
6
6
|
*
|
|
7
|
-
* Only `procedural` and `class` modules can be removed this way
|
|
7
|
+
* Only `procedural` and `class` modules can be removed this way. Removing a `document` module (e.g.
|
|
8
8
|
* `ThisWorkbook`) or a `designer` module (a UserForm) would leave the host referencing code that no
|
|
9
9
|
* longer exists, since their names are tied to a worksheet/workbook `codeName` or a designer storage
|
|
10
10
|
* this project-level primitive has no visibility into. Editing such a module's code-behind is a job for
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function removeVbaModule(bin: Uint8Array, name: string): Uint8Array;
|
|
17
17
|
/**
|
|
18
|
-
* A registered (COM Automation type-library) reference to add to an existing VBA project
|
|
18
|
+
* A registered (COM Automation type-library) reference to add to an existing VBA project: the shape of
|
|
19
19
|
* a real "add a reference to Microsoft Scripting Runtime" call. Project references (to another VBA
|
|
20
|
-
* project) and control references (to an ActiveX control library) are out of scope
|
|
20
|
+
* project) and control references (to an ActiveX control library) are out of scope. See
|
|
21
21
|
* {@link addVbaReference}.
|
|
22
22
|
*/
|
|
23
23
|
export interface VbaLibraryReference {
|
|
24
24
|
/**
|
|
25
|
-
* The reference's namespace name in the VBA editor
|
|
25
|
+
* The reference's namespace name in the VBA editor: what a qualified reference like
|
|
26
26
|
* `Scripting.Dictionary` resolves through. Must be a valid VBA identifier, at most 31 characters, as
|
|
27
27
|
* real type libraries use (e.g. `Scripting`, `Office`, `stdole`).
|
|
28
28
|
*/
|
|
@@ -34,12 +34,12 @@ export interface VbaLibraryReference {
|
|
|
34
34
|
readonly displayName?: string;
|
|
35
35
|
/** The type library's GUID, e.g. `{420B2830-E718-11CF-893D-00A0C9054228}` (braces optional). */
|
|
36
36
|
readonly guid: string;
|
|
37
|
-
/** The type library's major version
|
|
37
|
+
/** The type library's major version, an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMajorVersion`). */
|
|
38
38
|
readonly majorVersion: number;
|
|
39
|
-
/** The type library's minor version
|
|
39
|
+
/** The type library's minor version, an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMinorVersion`). */
|
|
40
40
|
readonly minorVersion: number;
|
|
41
41
|
/**
|
|
42
|
-
* The type library's LCID
|
|
42
|
+
* The type library's LCID, an integer in `[0, 0xFFFFFFFF]`. Defaults to `0` (locale-neutral), the
|
|
43
43
|
* overwhelming common case (every reference in a real project observed while building this had `0`).
|
|
44
44
|
*/
|
|
45
45
|
readonly lcid?: number;
|
|
@@ -50,9 +50,9 @@ export interface VbaLibraryReference {
|
|
|
50
50
|
* Add a registered (COM type-library) reference to an existing `vbaProject.bin`, returning new bytes
|
|
51
51
|
* that carry every existing module, reference, and host-info record unchanged. It grows the project's
|
|
52
52
|
* `dir` stream by one `REFERENCENAME` + `REFERENCEREGISTERED` record pair, positioned immediately before
|
|
53
|
-
* `MODULES_COUNT` (references have no count field of their own
|
|
53
|
+
* `MODULES_COUNT` (references have no count field of their own; `MODULES_COUNT` simply marks where the
|
|
54
54
|
* reference array ends). It needs no change to `PROJECT`/`PROJECTwm`: a real Excel-authored `PROJECT`
|
|
55
|
-
* stream carries no `Reference=` line at all
|
|
55
|
+
* stream carries no `Reference=` line at all: references live only in `dir` (confirmed against a genuine
|
|
56
56
|
* Excel-authored project).
|
|
57
57
|
*
|
|
58
58
|
* @throws {VbaParseError} if `bin` is not a parseable VBA project (validated before any edit).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readU16, readU32 } from './bytes.js';
|
|
2
2
|
import { writeCompoundFile } from './cfb-writer.js';
|
|
3
|
+
import { CompoundFile } from './cfb.js';
|
|
3
4
|
import { decoderForCodePage, encoderForCodePage } from './codepage.js';
|
|
4
5
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
5
6
|
import { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
@@ -280,13 +281,3 @@ function replaceStreams(nodes, replacements, applied) {
|
|
|
280
281
|
return { name: node.name, children: replaceStreams(node.children, replacements, applied) };
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
|
-
function readU16(buf, at) {
|
|
284
|
-
return buf[at] | (buf[at + 1] << 8);
|
|
285
|
-
}
|
|
286
|
-
function readU32(buf, at) {
|
|
287
|
-
return ((buf[at] |
|
|
288
|
-
(buf[at + 1] << 8) |
|
|
289
|
-
(buf[at + 2] << 16) |
|
|
290
|
-
(buf[at + 3] << 24)) >>>
|
|
291
|
-
0);
|
|
292
|
-
}
|
package/dist/vba/project.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** How a module participates in the project
|
|
1
|
+
/** How a module participates in the project: the classification the VBA editor shows. */
|
|
2
2
|
export type VbaModuleKind = 'procedural' | 'document' | 'class' | 'designer';
|
|
3
3
|
export interface VbaModule {
|
|
4
4
|
/** The module's code name as seen in the VBA editor, e.g. `ThisWorkbook`, `JsonConverter`. */
|
|
5
5
|
readonly name: string;
|
|
6
|
-
/** The CFB stream the module's bytes live in
|
|
6
|
+
/** The CFB stream the module's bytes live in, usually equal to {@link name}. */
|
|
7
7
|
readonly streamName: string;
|
|
8
8
|
/** Procedural (`.bas`), document code-behind, class module, or designer (UserForm). */
|
|
9
9
|
readonly kind: VbaModuleKind;
|
|
@@ -20,17 +20,17 @@ export declare const VBA_PROJECT_REL_TYPE = "http://schemas.microsoft.com/office
|
|
|
20
20
|
export declare const VBA_PROJECT_CONTENT_TYPE = "application/vnd.ms-office.vbaProject";
|
|
21
21
|
export declare const VBA_PROJECT_PART_PATH = "xl/vbaProject.bin";
|
|
22
22
|
/**
|
|
23
|
-
* Which generation of VBA project signature a part is
|
|
23
|
+
* Which generation of VBA project signature a part is. Office emits up to three sibling signature
|
|
24
24
|
* parts off `vbaProject.bin`'s own rels over the same project bytes ([MS-OFFMACRO2]): the original
|
|
25
25
|
* `legacy` signature, the `agile` (V2) successor, and the `v3` scheme that closes a tampering hole
|
|
26
26
|
* the earlier two left open (KB5000676). All three can coexist in one package.
|
|
27
27
|
*/
|
|
28
28
|
export type VbaProjectSignatureKind = 'legacy' | 'agile' | 'v3';
|
|
29
|
-
/** One digital signature over a workbook's VBA project
|
|
29
|
+
/** One digital signature over a workbook's VBA project: its generation and its raw signature bytes. */
|
|
30
30
|
export interface VbaProjectSignature {
|
|
31
31
|
readonly kind: VbaProjectSignatureKind;
|
|
32
32
|
/**
|
|
33
|
-
* The raw signature part bytes (a PKCS#7/CMS blob), passed through verbatim
|
|
33
|
+
* The raw signature part bytes (a PKCS#7/CMS blob), passed through verbatim. This library does not
|
|
34
34
|
* parse or cryptographically verify them. Their presence means "a signature is attached," never
|
|
35
35
|
* "this signature is valid."
|
|
36
36
|
*/
|
package/dist/vba/project.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readU16, readU32 } from './bytes.js';
|
|
1
2
|
import { CompoundFile } from './cfb.js';
|
|
2
3
|
import { decoderForCodePage } from './codepage.js';
|
|
3
4
|
import { VbaParseError } from './errors.js';
|
|
@@ -116,13 +117,3 @@ function readProjectStreamKinds(cfb, decoder) {
|
|
|
116
117
|
}
|
|
117
118
|
return kinds;
|
|
118
119
|
}
|
|
119
|
-
function readU16(buf, at) {
|
|
120
|
-
return buf[at] | (buf[at + 1] << 8);
|
|
121
|
-
}
|
|
122
|
-
function readU32(buf, at) {
|
|
123
|
-
return ((buf[at] |
|
|
124
|
-
(buf[at + 1] << 8) |
|
|
125
|
-
(buf[at + 2] << 16) |
|
|
126
|
-
(buf[at + 3] << 24)) >>>
|
|
127
|
-
0);
|
|
128
|
-
}
|
|
@@ -10,5 +10,5 @@ export declare function validateVbaName(name: string, what: 'project' | 'module'
|
|
|
10
10
|
export declare function push(out: number[], id: number, data: number[]): void;
|
|
11
11
|
export declare function u16(n: number): number[];
|
|
12
12
|
export declare function u32(n: number): number[];
|
|
13
|
-
/** A name as NUL-free UTF-16LE code units
|
|
13
|
+
/** A name as NUL-free UTF-16LE code units: the encoding [MS-OVBA] uses for every "Unicode" name field. */
|
|
14
14
|
export declare function utf16le(s: string): number[];
|
package/dist/xml/errors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XlsxError } from '../errors.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Thrown when XML text cannot be scanned into parse events
|
|
3
|
+
* Thrown when XML text cannot be scanned into parse events: an unterminated tag, comment, CDATA
|
|
4
4
|
* section, markup declaration or processing instruction.
|
|
5
5
|
*
|
|
6
6
|
* This is a *typed* failure rather than the native `SyntaxError` it reads like, because the text
|
package/dist/xml/xml-read.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface SaxHandlers {
|
|
|
14
14
|
* One parse event from {@link xmlEvents}. The payloads match {@link SaxHandlers} exactly: `text`
|
|
15
15
|
* is already entity-decoded (or verbatim CDATA), and a `<x/>` yields one `open` with
|
|
16
16
|
* `selfClosing: true` and no matching `close`. The discriminated `kind` lets a *pull* consumer
|
|
17
|
-
* drive the parse
|
|
17
|
+
* drive the parse: the shape the streaming reader needs, where a push callback cannot `yield`.
|
|
18
18
|
*/
|
|
19
19
|
export type XmlEvent = {
|
|
20
20
|
readonly kind: 'open';
|
|
@@ -30,16 +30,36 @@ export type XmlEvent = {
|
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
32
|
* Decode XML character references and the five predefined entities. An unrecognised
|
|
33
|
-
* `&name;` is left verbatim rather than expanded
|
|
33
|
+
* `&name;` is left verbatim rather than expanded: there is no DTD, so there is nothing
|
|
34
34
|
* to expand it to, and refusing to invent one is what makes entity-expansion attacks
|
|
35
35
|
* impossible.
|
|
36
36
|
*/
|
|
37
37
|
export declare function decodeEntities(value: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* The SpreadsheetML `_xHHHH_` escape, in the only place it may appear: a complete cell-text value.
|
|
40
|
+
*
|
|
41
|
+
* The mirror of `escapeSpreadsheetText` in `./xml.ts`, and it sits here rather than beside it for
|
|
42
|
+
* the same reason `decodeEntities` sits apart from `escapeText`: the write helpers carry an
|
|
43
|
+
* `AuthoringError` and a whole serialisation vocabulary the reader has no business importing.
|
|
44
|
+
*
|
|
45
|
+
* **One left-to-right pass, and that is load-bearing.** `005F` maps to `_` like any other code
|
|
46
|
+
* point, with no special case, because a single pass already gives the underscore escape its
|
|
47
|
+
* meaning: in `_x005F_x0041_` the match at 0 yields `_` and scanning resumes at `x0041_`, which has
|
|
48
|
+
* no leading underscore left to start an escape. So the value reads back as the literal seven
|
|
49
|
+
* characters `_x0041_` the author wrote. Decoding `_x005F_` in a pass of its own, before or after
|
|
50
|
+
* the rest, collapses that to `A` and loses the distinction the encoder went to trouble to keep.
|
|
51
|
+
* Excel agrees: it reads that cell as `_x0041_`.
|
|
52
|
+
*
|
|
53
|
+
* The decode is unconditional, not a repair of characters XML cannot carry. Excel reads
|
|
54
|
+
* `a_x0009_b` as a tab even though a literal tab would have been perfectly legal there, so a
|
|
55
|
+
* decoder that only handled the illegal range would disagree with Excel on files Excel wrote.
|
|
56
|
+
*/
|
|
57
|
+
export declare function decodeSpreadsheetText(value: string): string;
|
|
38
58
|
/**
|
|
39
59
|
* Scan an XML document as a *pull* stream of {@link XmlEvent}s in a single O(n) pass with no
|
|
40
60
|
* recursion. This is the parser's core; {@link parseXml} is a thin push adapter over it. A
|
|
41
61
|
* consumer that must produce output incrementally (the streaming row reader) pulls events and
|
|
42
|
-
* yields as it goes, holding only its own running state
|
|
62
|
+
* yields as it goes, holding only its own running state; a push callback cannot.
|
|
43
63
|
*
|
|
44
64
|
* Throws {@link XmlParseError} on malformed markup.
|
|
45
65
|
*/
|
|
@@ -63,12 +83,12 @@ export interface OpenElement {
|
|
|
63
83
|
export declare function openElements(source: string, ...localNames: string[]): Generator<OpenElement>;
|
|
64
84
|
/**
|
|
65
85
|
* Wrap an {@link XmlEvent} stream so a self-closing `<x/>` whose local name is in `names` is
|
|
66
|
-
* presented as an open (with `selfClosing: false`) immediately followed by a close
|
|
86
|
+
* presented as an open (with `selfClosing: false`) immediately followed by a close: the exact
|
|
67
87
|
* event shape of `<x></x>`. This lets a consumer commit such an element from its close handling
|
|
68
88
|
* alone, instead of hand-coding a parallel self-closing branch: {@link xmlEvents} fires no close
|
|
69
89
|
* for `<x/>`, and forgetting that branch silently drops the empty element. Names not in the set
|
|
70
|
-
* pass through untouched, so an element whose close would wrongly act on absent content
|
|
71
|
-
* `<v/>`/`<f/>` that must not commit captured text
|
|
90
|
+
* pass through untouched, so an element whose close would wrongly act on absent content (an empty
|
|
91
|
+
* `<v/>`/`<f/>` that must not commit captured text) is left as a bare self-closing open.
|
|
72
92
|
*/
|
|
73
93
|
export declare function closeEmptyElements(events: Iterable<XmlEvent>, names: ReadonlySet<string>): Generator<XmlEvent>;
|
|
74
94
|
/** Options for {@link parseXml}. */
|
|
@@ -83,7 +103,7 @@ export interface ParseXmlOptions {
|
|
|
83
103
|
}
|
|
84
104
|
/**
|
|
85
105
|
* Parse an XML document, dispatching SAX events to `handlers`. A thin push adapter over
|
|
86
|
-
* {@link xmlEvents}
|
|
106
|
+
* {@link xmlEvents}: one scanning core serves both the callback and the pull consumers.
|
|
87
107
|
* Throws {@link XmlParseError} on malformed markup.
|
|
88
108
|
*/
|
|
89
109
|
export declare function parseXml(source: string, handlers: SaxHandlers, options?: ParseXmlOptions): void;
|
|
@@ -92,13 +112,22 @@ export declare function localName(qualified: string): string;
|
|
|
92
112
|
/** An OOXML boolean that is on when present with no value (`<b/>` is bold) and off only on an
|
|
93
113
|
* explicit `"0"`/`"false"`; absence reads as on. */
|
|
94
114
|
export declare function boolPresent(val: string | undefined): boolean;
|
|
95
|
-
/** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else
|
|
96
|
-
* absence and a truthy-looking `"0"
|
|
115
|
+
/** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else, including
|
|
116
|
+
* absence and a truthy-looking `"0"`, is off. */
|
|
97
117
|
export declare function boolStrict(val: string | undefined): boolean;
|
|
98
118
|
/** An optional OOXML boolean: `undefined` when the attribute is absent or carries an unrecognised
|
|
99
119
|
* token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
|
|
100
120
|
* attributes the source actually carried, so a re-write stays byte-clean. */
|
|
101
121
|
export declare function boolTristate(val: string | undefined): boolean | undefined;
|
|
122
|
+
/** An OOXML integer attribute at or above `min` (default: unbounded below); `undefined` when the
|
|
123
|
+
* attribute is absent, blank, fractional, not a number, or below the floor. Integers past
|
|
124
|
+
* `Number.MAX_SAFE_INTEGER` read as `undefined` too: no index or count is usable out there, and
|
|
125
|
+
* arithmetic on one silently lies. */
|
|
126
|
+
export declare function numInteger(val: string | undefined, min?: number): number | undefined;
|
|
127
|
+
/** An OOXML decimal attribute at or above `min` (default: unbounded below); `undefined` when the
|
|
128
|
+
* attribute is absent, blank, not a number, or below the floor. Infinities are not finite numbers
|
|
129
|
+
* and read as `undefined`. */
|
|
130
|
+
export declare function numFinite(val: string | undefined, min?: number): number | undefined;
|
|
102
131
|
/** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
|
|
103
132
|
* digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
|
|
104
133
|
* number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
|
package/dist/xml/xml-read.js
CHANGED
|
@@ -28,6 +28,11 @@ export function decodeEntities(value) {
|
|
|
28
28
|
return named ?? match;
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
+
export function decodeSpreadsheetText(value) {
|
|
32
|
+
if (!value.includes('_'))
|
|
33
|
+
return value;
|
|
34
|
+
return value.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
35
|
+
}
|
|
31
36
|
const ATTRIBUTE = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
32
37
|
function parseAttributes(source) {
|
|
33
38
|
const attrs = {};
|
|
@@ -198,6 +203,24 @@ export function boolTristate(val) {
|
|
|
198
203
|
return false;
|
|
199
204
|
return undefined;
|
|
200
205
|
}
|
|
206
|
+
export function numInteger(val, min = -Number.MAX_SAFE_INTEGER) {
|
|
207
|
+
const n = parseAttrNumber(val);
|
|
208
|
+
if (n === undefined || !Number.isSafeInteger(n) || n < min)
|
|
209
|
+
return undefined;
|
|
210
|
+
return n;
|
|
211
|
+
}
|
|
212
|
+
export function numFinite(val, min = -Infinity) {
|
|
213
|
+
const n = parseAttrNumber(val);
|
|
214
|
+
if (n === undefined || n < min)
|
|
215
|
+
return undefined;
|
|
216
|
+
return n;
|
|
217
|
+
}
|
|
218
|
+
function parseAttrNumber(val) {
|
|
219
|
+
if (val === undefined || val.trim() === '')
|
|
220
|
+
return undefined;
|
|
221
|
+
const n = Number(val);
|
|
222
|
+
return Number.isFinite(n) ? n : undefined;
|
|
223
|
+
}
|
|
201
224
|
export function coerceNumericLiteral(text) {
|
|
202
225
|
const trimmed = text.trim();
|
|
203
226
|
return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;
|