@shbernal/ts-xlsx 2.0.0 → 3.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 +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 +12 -1
- package/dist/core/autofilter.js +35 -12
- package/dist/core/axis-handle.d.ts +8 -0
- package/dist/core/axis-handle.js +14 -0
- package/dist/core/cell.d.ts +24 -0
- package/dist/core/cell.js +9 -1
- package/dist/core/color-resolution.d.ts +1 -1
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/column.d.ts +4 -1
- package/dist/core/column.js +31 -35
- 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 +74 -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 +26 -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 +2 -0
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +12 -11
- package/dist/core/merge.js +36 -10
- package/dist/core/page-setup.d.ts +86 -2
- package/dist/core/page-setup.js +34 -1
- package/dist/core/pivot-table.d.ts +2 -0
- package/dist/core/pivot-table.js +23 -18
- 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/row.d.ts +4 -1
- package/dist/core/row.js +19 -23
- package/dist/core/style.d.ts +78 -7
- package/dist/core/style.js +90 -80
- package/dist/core/table.d.ts +26 -12
- package/dist/core/table.js +67 -33
- package/dist/core/theme.d.ts +3 -1
- package/dist/core/theme.js +4 -0
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -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 +31 -7
- package/dist/core/worksheet.js +72 -86
- package/dist/customui/ribbon.js +23 -24
- 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 +3 -13
- package/dist/io/opc/part-paths.d.ts +1 -0
- package/dist/io/opc/part-paths.js +15 -0
- package/dist/io/opc/read-opc.d.ts +22 -3
- package/dist/io/opc/read-opc.js +11 -39
- package/dist/io/opc/rels.d.ts +12 -6
- package/dist/io/opc/rels.js +3 -5
- package/dist/io/style/xf-style.d.ts +33 -4
- package/dist/io/style/xf-style.js +12 -1
- package/dist/io/xlsb/read-styles.js +6 -3
- package/dist/io/xlsb/read.d.ts +1 -1
- package/dist/io/xlsb/read.js +5 -8
- package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
- 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 +8 -7
- package/dist/io/xlsx/comments.js +6 -3
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
- package/dist/io/xlsx/conditional-formatting.js +81 -65
- package/dist/io/xlsx/data-validation.d.ts +10 -8
- package/dist/io/xlsx/data-validation.js +44 -41
- package/dist/io/xlsx/edit-vba.js +4 -11
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +26 -31
- package/dist/io/xlsx/images.js +33 -35
- package/dist/io/xlsx/package-plan.d.ts +9 -1
- package/dist/io/xlsx/package-plan.js +29 -16
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/read-pivot.js +4 -9
- package/dist/io/xlsx/read-rows.js +12 -13
- package/dist/io/xlsx/read-shared-strings.js +9 -47
- package/dist/io/xlsx/read-styles.d.ts +1 -1
- package/dist/io/xlsx/read-styles.js +65 -43
- package/dist/io/xlsx/read-worksheet.d.ts +10 -1
- package/dist/io/xlsx/read-worksheet.js +48 -63
- package/dist/io/xlsx/read.d.ts +3 -4
- package/dist/io/xlsx/read.js +124 -146
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +95 -30
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +23 -26
- package/dist/io/xlsx/styles.d.ts +3 -15
- package/dist/io/xlsx/styles.js +83 -105
- package/dist/io/xlsx/tables.js +14 -13
- package/dist/io/xlsx/theme-xml.js +8 -5
- package/dist/io/xlsx/threaded-comments.js +20 -19
- package/dist/io/xlsx/workbook-xml.js +43 -36
- package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
- package/dist/io/xlsx/worksheet-xml.js +47 -52
- package/dist/io/xlsx/write-stream.d.ts +14 -1
- package/dist/io/xlsx/write-stream.js +40 -33
- package/dist/io/xlsx/write.d.ts +5 -0
- package/dist/io/xlsx/write.js +155 -131
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/token-set.d.ts +15 -0
- package/dist/token-set.js +4 -0
- package/dist/vba/bytes.d.ts +1 -2
- package/dist/vba/bytes.js +1 -12
- package/dist/vba/cfb-format.d.ts +24 -0
- package/dist/vba/cfb-format.js +12 -0
- package/dist/vba/cfb-writer.js +17 -17
- package/dist/vba/cfb.js +1 -7
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/ms-ovba.js +32 -11
- package/dist/vba/project-editor.js +5 -34
- package/dist/vba/project.js +15 -31
- package/dist/vba/vba-encoding.js +1 -1
- package/dist/xml/xml-read.d.ts +117 -72
- package/dist/xml/xml-read.js +123 -169
- package/dist/xml/xml-scan.d.ts +100 -0
- package/dist/xml/xml-scan.js +208 -0
- package/dist/xml/xml.d.ts +44 -2
- package/dist/xml/xml.js +17 -5
- package/package.json +22 -6
package/dist/vba/cfb-writer.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
+
import { DIFSECT, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, MAX_NAME_CHARS, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
1
2
|
import { VbaAuthorError } from './errors.js';
|
|
2
3
|
const SECTOR = 512;
|
|
3
4
|
const MINI_SECTOR = 64;
|
|
4
5
|
const MINI_CUTOFF = 4096;
|
|
5
|
-
const DIR_ENTRY_SIZE = 128;
|
|
6
6
|
const ENTRIES_PER_DIR_SECTOR = SECTOR / DIR_ENTRY_SIZE;
|
|
7
7
|
const FAT_ENTRIES_PER_SECTOR = SECTOR / 4;
|
|
8
8
|
const DIFAT_HEADER_SLOTS = 109;
|
|
9
|
-
const MAX_NAME_CHARS = 31;
|
|
10
|
-
const FREESECT = 0xffffffff;
|
|
11
|
-
const ENDOFCHAIN = 0xfffffffe;
|
|
12
|
-
const FATSECT = 0xfffffffd;
|
|
13
|
-
const DIFSECT = 0xfffffffc;
|
|
14
|
-
const NOSTREAM = 0xffffffff;
|
|
15
|
-
const TYPE_STORAGE = 1;
|
|
16
|
-
const TYPE_STREAM = 2;
|
|
17
|
-
const TYPE_ROOT = 5;
|
|
18
9
|
const COLOR_BLACK = 1;
|
|
19
10
|
function isStream(node) {
|
|
20
11
|
return 'data' in node;
|
|
@@ -36,7 +27,8 @@ export function writeCompoundFile(root) {
|
|
|
36
27
|
return entry;
|
|
37
28
|
};
|
|
38
29
|
const rootEntry = addEntry('Root Entry', TYPE_ROOT, ENDOFCHAIN);
|
|
39
|
-
const
|
|
30
|
+
const miniChunks = [];
|
|
31
|
+
let miniLength = 0;
|
|
40
32
|
const miniFat = [];
|
|
41
33
|
const bigStreams = [];
|
|
42
34
|
const addNode = (node, siblings) => {
|
|
@@ -50,11 +42,14 @@ export function writeCompoundFile(root) {
|
|
|
50
42
|
bigStreams.push({ entry, data: node.data, sectors: Math.ceil(node.data.length / SECTOR) });
|
|
51
43
|
}
|
|
52
44
|
else {
|
|
53
|
-
const startMini =
|
|
45
|
+
const startMini = miniLength / MINI_SECTOR;
|
|
54
46
|
const numMini = Math.ceil(node.data.length / MINI_SECTOR);
|
|
55
47
|
for (let k = 0; k < numMini; k++)
|
|
56
48
|
miniFat.push(k < numMini - 1 ? startMini + k + 1 : ENDOFCHAIN);
|
|
57
|
-
|
|
49
|
+
const padded = new Uint8Array(numMini * MINI_SECTOR);
|
|
50
|
+
padded.set(node.data);
|
|
51
|
+
miniChunks.push(padded);
|
|
52
|
+
miniLength += padded.length;
|
|
58
53
|
entry.startSector = startMini;
|
|
59
54
|
}
|
|
60
55
|
}
|
|
@@ -74,7 +69,7 @@ export function writeCompoundFile(root) {
|
|
|
74
69
|
linkChildren(rootEntry, rootKids);
|
|
75
70
|
const dirSectors = Math.ceil(entries.length / ENTRIES_PER_DIR_SECTOR);
|
|
76
71
|
const miniFatSectors = miniFat.length > 0 ? Math.ceil((miniFat.length * 4) / FAT_ENTRIES_PER_SECTOR) : 0;
|
|
77
|
-
const miniStreamSectors = Math.ceil(
|
|
72
|
+
const miniStreamSectors = Math.ceil(miniLength / SECTOR);
|
|
78
73
|
const baseSectors = dirSectors +
|
|
79
74
|
miniFatSectors +
|
|
80
75
|
miniStreamSectors +
|
|
@@ -112,7 +107,7 @@ export function writeCompoundFile(root) {
|
|
|
112
107
|
cursor += difatSectors;
|
|
113
108
|
const totalSectors = cursor;
|
|
114
109
|
rootEntry.startSector = miniStreamStart;
|
|
115
|
-
rootEntry.size =
|
|
110
|
+
rootEntry.size = miniLength;
|
|
116
111
|
const fat = new Array(fatSectors * FAT_ENTRIES_PER_SECTOR).fill(FREESECT);
|
|
117
112
|
const chainRegion = (start, count) => {
|
|
118
113
|
for (let k = 0; k < count; k++)
|
|
@@ -146,8 +141,13 @@ export function writeCompoundFile(root) {
|
|
|
146
141
|
for (let i = 0; i < miniFatSectors * FAT_ENTRIES_PER_SECTOR; i++) {
|
|
147
142
|
dv.setUint32(at(miniFatStart) + i * 4, miniFat[i] ?? FREESECT, true);
|
|
148
143
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
144
|
+
if (miniLength > 0) {
|
|
145
|
+
let offset = at(miniStreamStart);
|
|
146
|
+
for (const chunk of miniChunks) {
|
|
147
|
+
buf.set(chunk, offset);
|
|
148
|
+
offset += chunk.length;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
151
|
for (const big of bigStreams)
|
|
152
152
|
buf.set(big.data, at(big.entry.startSector));
|
|
153
153
|
for (const [i, sector] of fat.entries())
|
package/dist/vba/cfb.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { concat, decodeUtf16le, readU16, readU32 } from './bytes.js';
|
|
2
|
+
import { DIR_ENTRY_SIZE, MAX_REGULAR_SECTOR, NOSTREAM, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
2
3
|
import { VbaParseError } from './errors.js';
|
|
3
|
-
const MAX_REGULAR_SECTOR = 0xfffffffa;
|
|
4
|
-
const NOSTREAM = 0xffffffff;
|
|
5
|
-
const TYPE_EMPTY = 0;
|
|
6
|
-
const TYPE_STORAGE = 1;
|
|
7
|
-
const TYPE_STREAM = 2;
|
|
8
|
-
const TYPE_ROOT = 5;
|
|
9
4
|
const CFB_SIGNATURE_LO = 0xe011cfd0;
|
|
10
5
|
const CFB_SIGNATURE_HI = 0xe11ab1a1;
|
|
11
|
-
const DIR_ENTRY_SIZE = 128;
|
|
12
6
|
export class CompoundFile {
|
|
13
7
|
#buf;
|
|
14
8
|
#sectorSize;
|
|
@@ -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/ms-ovba.js
CHANGED
|
@@ -11,7 +11,7 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
11
11
|
if (buf[start] !== 0x01) {
|
|
12
12
|
throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${(buf[start] ?? 0).toString(16)}`);
|
|
13
13
|
}
|
|
14
|
-
const out =
|
|
14
|
+
const out = new DecompressedBytes(maxOutput);
|
|
15
15
|
let pos = start + 1;
|
|
16
16
|
while (pos + 2 <= buf.length) {
|
|
17
17
|
const header = readU16(buf, pos);
|
|
@@ -28,7 +28,6 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
28
28
|
if (!compressed) {
|
|
29
29
|
for (let i = pos; i < chunkEnd; i++)
|
|
30
30
|
out.push(buf[i]);
|
|
31
|
-
guardOutput(out.length, maxOutput);
|
|
32
31
|
pos = chunkEnd;
|
|
33
32
|
continue;
|
|
34
33
|
}
|
|
@@ -39,7 +38,6 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
39
38
|
const isCopy = (flagByte >> bit) & 1;
|
|
40
39
|
if (!isCopy) {
|
|
41
40
|
out.push(buf[pos++]);
|
|
42
|
-
guardOutput(out.length, maxOutput);
|
|
43
41
|
continue;
|
|
44
42
|
}
|
|
45
43
|
if (pos + 2 > chunkEnd) {
|
|
@@ -55,13 +53,41 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
55
53
|
throw new VbaParseError('copy token references before the start of its chunk');
|
|
56
54
|
}
|
|
57
55
|
for (let i = 0; i < length; i++)
|
|
58
|
-
out.push(out
|
|
59
|
-
guardOutput(out.length, maxOutput);
|
|
56
|
+
out.push(out.at(src + i));
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
59
|
pos = chunkEnd;
|
|
63
60
|
}
|
|
64
|
-
return
|
|
61
|
+
return out.bytes();
|
|
62
|
+
}
|
|
63
|
+
class DecompressedBytes {
|
|
64
|
+
#buf;
|
|
65
|
+
#length = 0;
|
|
66
|
+
#limit;
|
|
67
|
+
constructor(limit) {
|
|
68
|
+
this.#limit = limit;
|
|
69
|
+
this.#buf = new Uint8Array(Math.min(MAX_CHUNK_DECOMPRESSED, limit));
|
|
70
|
+
}
|
|
71
|
+
get length() {
|
|
72
|
+
return this.#length;
|
|
73
|
+
}
|
|
74
|
+
at(index) {
|
|
75
|
+
return this.#buf[index];
|
|
76
|
+
}
|
|
77
|
+
push(byte) {
|
|
78
|
+
if (this.#length >= this.#limit) {
|
|
79
|
+
throw new VbaParseError(`decompressed output exceeds the ${this.#limit}-byte ceiling (possible bomb)`);
|
|
80
|
+
}
|
|
81
|
+
if (this.#length === this.#buf.length) {
|
|
82
|
+
const grown = new Uint8Array(Math.min(Math.max(this.#buf.length * 2, MAX_CHUNK_DECOMPRESSED), this.#limit));
|
|
83
|
+
grown.set(this.#buf);
|
|
84
|
+
this.#buf = grown;
|
|
85
|
+
}
|
|
86
|
+
this.#buf[this.#length++] = byte;
|
|
87
|
+
}
|
|
88
|
+
bytes() {
|
|
89
|
+
return this.#buf.slice(0, this.#length);
|
|
90
|
+
}
|
|
65
91
|
}
|
|
66
92
|
export function compressContainer(data) {
|
|
67
93
|
const out = [0x01];
|
|
@@ -119,11 +145,6 @@ function compressChunk(chunk) {
|
|
|
119
145
|
}
|
|
120
146
|
return tokens;
|
|
121
147
|
}
|
|
122
|
-
function guardOutput(size, maxOutput) {
|
|
123
|
-
if (size > maxOutput) {
|
|
124
|
-
throw new VbaParseError(`decompressed output exceeds the ${maxOutput}-byte ceiling (possible bomb)`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
148
|
function copyTokenHelp(decompressedSoFar) {
|
|
128
149
|
const bitCount = Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4);
|
|
129
150
|
const lengthMask = 0xffff >> bitCount;
|
|
@@ -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,27 +1,20 @@
|
|
|
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';
|
|
7
8
|
export const VBA_PROJECT_CONTENT_TYPE = 'application/vnd.ms-office.vbaProject';
|
|
8
9
|
export const VBA_PROJECT_PART_PATH = 'xl/vbaProject.bin';
|
|
9
|
-
const SIGNATURE_KIND_BY_REL_SEGMENT =
|
|
10
|
-
vbaProjectSignature
|
|
11
|
-
vbaProjectSignatureAgile
|
|
12
|
-
vbaProjectSignatureV3
|
|
13
|
-
|
|
10
|
+
const SIGNATURE_KIND_BY_REL_SEGMENT = new Map([
|
|
11
|
+
['vbaProjectSignature', 'legacy'],
|
|
12
|
+
['vbaProjectSignatureAgile', 'agile'],
|
|
13
|
+
['vbaProjectSignatureV3', 'v3'],
|
|
14
|
+
]);
|
|
14
15
|
export function vbaProjectSignatureKind(relType) {
|
|
15
|
-
return SIGNATURE_KIND_BY_REL_SEGMENT
|
|
16
|
+
return SIGNATURE_KIND_BY_REL_SEGMENT.get(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)
|
|
@@ -92,23 +76,23 @@ function readModuleSource(cfb, streamName, textOffset, decoder) {
|
|
|
92
76
|
throw new VbaParseError(`module stream '${streamName}' not found in container`);
|
|
93
77
|
return decoder.decode(decompressContainer(stream, textOffset));
|
|
94
78
|
}
|
|
79
|
+
const MODULE_KIND_BY_PROJECT_KEYWORD = new Map([
|
|
80
|
+
['Document', 'document'],
|
|
81
|
+
['Module', 'procedural'],
|
|
82
|
+
['Class', 'class'],
|
|
83
|
+
['BaseClass', 'designer'],
|
|
84
|
+
]);
|
|
95
85
|
function readProjectStreamKinds(cfb, decoder) {
|
|
96
86
|
const kinds = new Map();
|
|
97
87
|
const stream = cfb.readStream('PROJECT');
|
|
98
88
|
if (!stream)
|
|
99
89
|
return kinds;
|
|
100
90
|
const text = decoder.decode(stream);
|
|
101
|
-
const keyword = {
|
|
102
|
-
Document: 'document',
|
|
103
|
-
Module: 'procedural',
|
|
104
|
-
Class: 'class',
|
|
105
|
-
BaseClass: 'designer',
|
|
106
|
-
};
|
|
107
91
|
for (const line of text.split(/\r\n|\r|\n/)) {
|
|
108
92
|
const eq = line.indexOf('=');
|
|
109
93
|
if (eq < 0)
|
|
110
94
|
continue;
|
|
111
|
-
const kind =
|
|
95
|
+
const kind = MODULE_KIND_BY_PROJECT_KEYWORD.get(line.slice(0, eq));
|
|
112
96
|
if (kind === undefined)
|
|
113
97
|
continue;
|
|
114
98
|
const name = (line.slice(eq + 1).split('/')[0] ?? '').trim();
|
package/dist/vba/vba-encoding.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { MAX_NAME_CHARS } from './cfb-format.js';
|
|
1
2
|
import { VbaAuthorError } from './errors.js';
|
|
2
3
|
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
3
|
-
const MAX_NAME_CHARS = 31;
|
|
4
4
|
export function validateVbaName(name, what) {
|
|
5
5
|
if (!IDENTIFIER.test(name) || name.length > MAX_NAME_CHARS) {
|
|
6
6
|
throw new VbaAuthorError(`invalid ${what} name '${name}' (must be a VBA identifier ≤ 31 chars)`);
|