@shbernal/ts-xlsx 1.3.1 → 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 +72 -3
- package/dist/core/address.js +50 -10
- package/dist/core/autofilter.d.ts +13 -2
- package/dist/core/autofilter.js +35 -10
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +8 -8
- package/dist/core/color-resolution.js +7 -8
- 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 +6 -1
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +48 -8
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +12 -1
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +12 -5
- package/dist/core/data-validation.js +34 -0
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +29 -11
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +39 -2
- package/dist/core/image.js +10 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +17 -12
- package/dist/core/merge.js +36 -5
- package/dist/core/page-setup.d.ts +20 -6
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +4 -4
- package/dist/core/pivot-table.js +29 -20
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +7 -4
- package/dist/core/protection.js +20 -14
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +9 -8
- package/dist/core/row-input.js +12 -10
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +41 -14
- package/dist/core/style.js +82 -67
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +53 -51
- package/dist/core/table.js +83 -41
- package/dist/core/theme.d.ts +16 -44
- package/dist/core/theme.js +6 -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 +133 -87
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +17 -0
- package/dist/core/worksheet-comments.js +70 -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 +100 -79
- package/dist/core/worksheet.js +63 -142
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +7 -7
- package/dist/entries/core.js +1 -1
- 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/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/entries/xlsx.js +1 -1
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +2 -3
- package/dist/io/csv/write.d.ts +19 -4
- package/dist/io/csv/write.js +34 -4
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/inflate.js +2 -12
- 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/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/style/xf-style.d.ts +44 -7
- package/dist/io/style/xf-style.js +27 -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 +26 -9
- package/dist/io/xlsx/cell-accumulator.js +89 -46
- package/dist/io/xlsx/cell-value.d.ts +3 -3
- package/dist/io/xlsx/cell-value.js +9 -6
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +18 -23
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +17 -8
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
- package/dist/io/xlsx/conditional-formatting.js +64 -58
- package/dist/io/xlsx/data-validation.d.ts +3 -3
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +10 -18
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +25 -28
- 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 +4 -13
- 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 -44
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +42 -66
- package/dist/io/xlsx/read-worksheet.js +73 -129
- package/dist/io/xlsx/read.d.ts +4 -6
- package/dist/io/xlsx/read.js +104 -142
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- 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.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +14 -10
- package/dist/io/xlsx/styles.d.ts +9 -22
- package/dist/io/xlsx/styles.js +65 -89
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +20 -25
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +145 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +26 -38
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +40 -48
- package/dist/io/xlsx/write-stream.d.ts +23 -16
- package/dist/io/xlsx/write-stream.js +13 -14
- package/dist/io/xlsx/write.d.ts +8 -7
- package/dist/io/xlsx/write.js +6 -3
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +11 -0
- package/dist/vba/bytes.js +29 -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/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- 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 +6 -44
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +3 -28
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +98 -9
- package/dist/xml/xml-read.js +70 -0
- package/dist/xml/xml.d.ts +74 -10
- package/dist/xml/xml.js +39 -7
- package/package.json +59 -36
|
@@ -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
|
+
}
|
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,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readU16 } 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';
|
|
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';
|
|
4
6
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
5
7
|
import { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
6
8
|
import { parseVbaProject } from './project.js';
|
|
@@ -9,14 +11,6 @@ const DIR_STREAM = 'dir';
|
|
|
9
11
|
const PROJECT_STREAM = 'PROJECT';
|
|
10
12
|
const PROJECTWM_STREAM = 'PROJECTwm';
|
|
11
13
|
const VBA_STORAGE = 'VBA';
|
|
12
|
-
const REC_MODULE_NAME = 0x0019;
|
|
13
|
-
const REC_MODULE_STREAMNAME = 0x001a;
|
|
14
|
-
const REC_MODULE_TERMINATOR = 0x002b;
|
|
15
|
-
const REC_PROJECT_VERSION = 0x0009;
|
|
16
|
-
const REC_MODULES_COUNT = 0x000f;
|
|
17
|
-
const REC_REFERENCE_NAME = 0x0016;
|
|
18
|
-
const REC_REFERENCE_NAME_UNICODE = 0x003e;
|
|
19
|
-
const REC_REFERENCE_REGISTERED = 0x000d;
|
|
20
14
|
export function removeVbaModule(bin, name) {
|
|
21
15
|
const project = parseVbaProject(bin);
|
|
22
16
|
const nameKey = name.toUpperCase();
|
|
@@ -123,18 +117,7 @@ function buildReferenceDirRecords(ref, encode) {
|
|
|
123
117
|
}
|
|
124
118
|
function insertReferenceDirRecords(dir, records) {
|
|
125
119
|
let insertAt = -1;
|
|
126
|
-
|
|
127
|
-
while (pos + 6 <= dir.length) {
|
|
128
|
-
const recordStart = pos;
|
|
129
|
-
const id = readU16(dir, pos);
|
|
130
|
-
const size = readU32(dir, pos + 2);
|
|
131
|
-
const dataStart = pos + 6;
|
|
132
|
-
if (dataStart + size > dir.length) {
|
|
133
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while adding a reference`);
|
|
134
|
-
}
|
|
135
|
-
pos = dataStart + size;
|
|
136
|
-
if (id === REC_PROJECT_VERSION)
|
|
137
|
-
pos += 2;
|
|
120
|
+
for (const { id, recordStart } of dirRecords(dir, 'overruns while adding a reference')) {
|
|
138
121
|
if (id === REC_MODULES_COUNT) {
|
|
139
122
|
insertAt = recordStart;
|
|
140
123
|
break;
|
|
@@ -156,18 +139,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
156
139
|
let removeStart = -1;
|
|
157
140
|
let removeEnd = -1;
|
|
158
141
|
let currentStream;
|
|
159
|
-
|
|
160
|
-
while (pos + 6 <= dir.length) {
|
|
161
|
-
const recordStart = pos;
|
|
162
|
-
const id = readU16(dir, pos);
|
|
163
|
-
const size = readU32(dir, pos + 2);
|
|
164
|
-
const dataStart = pos + 6;
|
|
165
|
-
if (dataStart + size > dir.length) {
|
|
166
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while removing a module`);
|
|
167
|
-
}
|
|
168
|
-
pos = dataStart + size;
|
|
169
|
-
if (id === REC_PROJECT_VERSION)
|
|
170
|
-
pos += 2;
|
|
142
|
+
for (const { id, recordStart, dataStart, size, end } of dirRecords(dir, 'overruns while removing a module')) {
|
|
171
143
|
if (id === REC_MODULES_COUNT) {
|
|
172
144
|
if (size < 2)
|
|
173
145
|
throw new VbaParseError('PROJECTMODULES MODULES_COUNT record is malformed');
|
|
@@ -182,7 +154,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
182
154
|
else if (id === REC_MODULE_TERMINATOR) {
|
|
183
155
|
if (currentStream === streamName) {
|
|
184
156
|
removeStart = blockStart;
|
|
185
|
-
removeEnd =
|
|
157
|
+
removeEnd = end;
|
|
186
158
|
}
|
|
187
159
|
currentStream = undefined;
|
|
188
160
|
blockStart = -1;
|
|
@@ -280,13 +252,3 @@ function replaceStreams(nodes, replacements, applied) {
|
|
|
280
252
|
return { name: node.name, children: replaceStreams(node.children, replacements, applied) };
|
|
281
253
|
});
|
|
282
254
|
}
|
|
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,5 +1,7 @@
|
|
|
1
|
+
import { readU16, readU32 } from './bytes.js';
|
|
1
2
|
import { CompoundFile } from './cfb.js';
|
|
2
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';
|
|
3
5
|
import { VbaParseError } from './errors.js';
|
|
4
6
|
import { decompressContainer } from './ms-ovba.js';
|
|
5
7
|
export const VBA_PROJECT_REL_TYPE = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
|
|
@@ -13,14 +15,6 @@ const SIGNATURE_KIND_BY_REL_SEGMENT = {
|
|
|
13
15
|
export function vbaProjectSignatureKind(relType) {
|
|
14
16
|
return SIGNATURE_KIND_BY_REL_SEGMENT[relType.slice(relType.lastIndexOf('/') + 1)];
|
|
15
17
|
}
|
|
16
|
-
const REC_PROJECT_CODEPAGE = 0x0003;
|
|
17
|
-
const REC_MODULE_NAME = 0x0019;
|
|
18
|
-
const REC_MODULE_STREAMNAME = 0x001a;
|
|
19
|
-
const REC_MODULE_TYPE_PROCEDURAL = 0x0021;
|
|
20
|
-
const REC_MODULE_TYPE_DOCUMENT = 0x0022;
|
|
21
|
-
const REC_MODULE_OFFSET = 0x0031;
|
|
22
|
-
const REC_MODULE_TERMINATOR = 0x002b;
|
|
23
|
-
const REC_PROJECT_VERSION = 0x0009;
|
|
24
18
|
export function parseVbaProject(bin) {
|
|
25
19
|
const cfb = new CompoundFile(bin);
|
|
26
20
|
const dirCompressed = cfb.readStream('dir');
|
|
@@ -30,16 +24,7 @@ export function parseVbaProject(bin) {
|
|
|
30
24
|
let codePage = 1252;
|
|
31
25
|
const rawModules = [];
|
|
32
26
|
let pending = {};
|
|
33
|
-
|
|
34
|
-
while (pos + 6 <= dir.length) {
|
|
35
|
-
const id = readU16(dir, pos);
|
|
36
|
-
const size = readU32(dir, pos + 2);
|
|
37
|
-
const dataStart = pos + 6;
|
|
38
|
-
if (dataStart + size > dir.length)
|
|
39
|
-
throw new VbaParseError(`dir record 0x${id.toString(16)} overruns stream`);
|
|
40
|
-
pos = dataStart + size;
|
|
41
|
-
if (id === REC_PROJECT_VERSION)
|
|
42
|
-
pos += 2;
|
|
27
|
+
for (const { id, dataStart, size } of dirRecords(dir, 'overruns stream')) {
|
|
43
28
|
switch (id) {
|
|
44
29
|
case REC_PROJECT_CODEPAGE:
|
|
45
30
|
if (size >= 2)
|
|
@@ -116,13 +101,3 @@ function readProjectStreamKinds(cfb, decoder) {
|
|
|
116
101
|
}
|
|
117
102
|
return kinds;
|
|
118
103
|
}
|
|
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,16 +112,85 @@ 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
|
|
105
134
|
* re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
|
|
106
135
|
* (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
|
|
107
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
|
@@ -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,7 +203,72 @@ 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;
|
|
204
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
|
+
}
|