@shbernal/ts-xlsx 3.0.0 → 3.2.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 +34 -24
- package/dist/bytes.d.ts +10 -2
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +96 -2
- package/dist/core/address.js +87 -33
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +4 -6
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +6 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +11 -1
- package/dist/core/conditional-formatting-overlay.js +30 -3
- package/dist/core/conditional-formatting.d.ts +107 -11
- package/dist/core/conditional-formatting.js +76 -19
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +7 -1
- package/dist/core/data-validation-overlay.js +19 -4
- package/dist/core/data-validation.d.ts +22 -0
- package/dist/core/data-validation.js +30 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +170 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +19 -3
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.js +87 -80
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +30 -3
- package/dist/core/grid-edits.js +248 -81
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +49 -14
- package/dist/core/image.js +16 -5
- package/dist/core/internal.d.ts +10 -2
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +18 -4
- package/dist/core/merge.js +33 -20
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +112 -60
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +6 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +6 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +64 -16
- package/dist/core/table.js +87 -40
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +9 -5
- package/dist/core/value.d.ts +60 -28
- package/dist/core/value.js +48 -43
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +83 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +41 -3
- package/dist/core/workbook.js +46 -41
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +49 -0
- package/dist/core/worksheet-merges.js +65 -0
- package/dist/core/worksheet-model.d.ts +10 -2
- package/dist/core/worksheet-model.js +28 -9
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +165 -27
- package/dist/core/worksheet.js +206 -89
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +21 -14
- package/dist/entries/core.js +14 -7
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +21 -0
- package/dist/io/csv/delimiter.js +18 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +51 -22
- package/dist/io/csv/write.d.ts +19 -5
- package/dist/io/csv/write.js +24 -44
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +5 -5
- package/dist/io/opc/read-opc.d.ts +58 -14
- package/dist/io/opc/read-opc.js +49 -21
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/read-policy/column-budget.js +18 -0
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +52 -35
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +495 -596
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +26 -1
- package/dist/io/xlsb/read-worksheet.js +148 -106
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +70 -31
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +14 -5
- package/dist/io/xlsb/record-types.js +14 -5
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
- package/dist/io/xlsx/cell-accumulator.js +74 -21
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +19 -5
- package/dist/io/xlsx/cell-value.js +45 -30
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +9 -30
- package/dist/io/xlsx/comments.js +25 -83
- package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
- package/dist/io/xlsx/conditional-formatting.js +437 -138
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +67 -53
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +52 -52
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +19 -21
- package/dist/io/xlsx/hyperlinks.js +12 -32
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +149 -50
- package/dist/io/xlsx/namespaces.d.ts +24 -0
- package/dist/io/xlsx/namespaces.js +11 -0
- package/dist/io/xlsx/package-plan.d.ts +60 -15
- package/dist/io/xlsx/package-plan.js +83 -18
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +26 -24
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +259 -0
- package/dist/io/xlsx/read-pivot.js +11 -4
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
- package/dist/io/xlsx/read-rows.d.ts +35 -11
- package/dist/io/xlsx/read-rows.js +73 -49
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +0 -30
- package/dist/io/xlsx/read-styles.js +45 -105
- package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
- package/dist/io/xlsx/read-workbook-xml.js +175 -0
- package/dist/io/xlsx/read-worksheet.d.ts +9 -3
- package/dist/io/xlsx/read-worksheet.js +70 -177
- package/dist/io/xlsx/read.d.ts +3 -14
- package/dist/io/xlsx/read.js +74 -411
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +20 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +139 -0
- package/dist/io/xlsx/row-xml.js +222 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +182 -20
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +41 -0
- package/dist/io/xlsx/style-elements.js +136 -0
- package/dist/io/xlsx/styles.d.ts +17 -3
- package/dist/io/xlsx/styles.js +13 -209
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +55 -52
- package/dist/io/xlsx/theme-xml.d.ts +32 -4
- package/dist/io/xlsx/theme-xml.js +106 -71
- package/dist/io/xlsx/threaded-comments.d.ts +14 -11
- package/dist/io/xlsx/threaded-comments.js +16 -8
- package/dist/io/xlsx/workbook-xml.d.ts +45 -6
- package/dist/io/xlsx/workbook-xml.js +62 -54
- package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
- package/dist/io/xlsx/worksheet-xml.js +69 -282
- package/dist/io/xlsx/write-stream.d.ts +90 -19
- package/dist/io/xlsx/write-stream.js +80 -25
- package/dist/io/xlsx/write.d.ts +12 -6
- package/dist/io/xlsx/write.js +226 -123
- package/dist/rel-type.d.ts +25 -0
- package/dist/rel-type.js +9 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +16 -5
- package/dist/vba/bytes.js +27 -6
- package/dist/vba/cfb-format.d.ts +55 -0
- package/dist/vba/cfb-format.js +35 -0
- package/dist/vba/cfb-writer.d.ts +8 -0
- package/dist/vba/cfb-writer.js +40 -40
- package/dist/vba/cfb.d.ts +21 -4
- package/dist/vba/cfb.js +124 -90
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +11 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +66 -22
- package/dist/vba/project-editor.js +129 -119
- package/dist/vba/project.d.ts +15 -1
- package/dist/vba/project.js +38 -13
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +3 -17
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +101 -11
- package/dist/xml/xml-read.js +150 -18
- package/dist/xml/xml-scan.d.ts +25 -34
- package/dist/xml/xml-scan.js +69 -46
- package/dist/xml/xml.d.ts +59 -26
- package/dist/xml/xml.js +45 -23
- package/package.json +15 -9
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
|
@@ -51,3 +51,9 @@ export interface DirRecord {
|
|
|
51
51
|
* @throws {VbaParseError} when a record's payload runs past the end of the stream.
|
|
52
52
|
*/
|
|
53
53
|
export declare function dirRecords(dir: Uint8Array, context: string): Generator<DirRecord>;
|
|
54
|
+
/**
|
|
55
|
+
* One record's bytes, `Id(u16) Size(u32) data[Size]`: the grammar {@link dirRecords} walks, written.
|
|
56
|
+
* Built as bytes rather than spread into a `number[]`, which overflowed the call stack on a payload of a
|
|
57
|
+
* few hundred thousand bytes. Not for PROJECTVERSION, whose uncounted VersionMinor no edit here writes.
|
|
58
|
+
*/
|
|
59
|
+
export declare function dirRecord(id: number, data: Uint8Array): Uint8Array;
|
package/dist/vba/dir-records.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hex } from '../hex.js';
|
|
2
|
+
import { readU16, readU32, writeU16, writeU32 } from './bytes.js';
|
|
2
3
|
import { VbaParseError } from './errors.js';
|
|
3
4
|
export const REC_PROJECT_CODEPAGE = 0x0003;
|
|
4
5
|
export const REC_PROJECT_VERSION = 0x0009;
|
|
@@ -19,10 +20,17 @@ export function* dirRecords(dir, context) {
|
|
|
19
20
|
const size = readU32(dir, pos + 2);
|
|
20
21
|
const dataStart = pos + 6;
|
|
21
22
|
if (dataStart + size > dir.length) {
|
|
22
|
-
throw new VbaParseError(`dir record 0x${id
|
|
23
|
+
throw new VbaParseError(`dir record 0x${hex(id, 4)} ${context}`);
|
|
23
24
|
}
|
|
24
25
|
const end = dataStart + size + (id === REC_PROJECT_VERSION ? 2 : 0);
|
|
25
26
|
yield { id, recordStart: pos, dataStart, size, end };
|
|
26
27
|
pos = end;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
export function dirRecord(id, data) {
|
|
31
|
+
const out = new Uint8Array(6 + data.length);
|
|
32
|
+
writeU16(out, 0, id);
|
|
33
|
+
writeU32(out, 2, data.length);
|
|
34
|
+
out.set(data, 6);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
package/dist/vba/errors.d.ts
CHANGED
|
@@ -12,11 +12,18 @@ export declare class VbaParseError extends XlsxError {
|
|
|
12
12
|
readonly code = "malformed-input";
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Thrown when
|
|
16
|
-
* produce something that cannot be encoded to a well-formed container: a stream name longer
|
|
17
|
-
* [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed
|
|
18
|
-
* writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
|
|
15
|
+
* Thrown when a structural edit of a VBA project (removing a module, adding a library reference) is
|
|
16
|
+
* asked to produce something that cannot be encoded to a well-formed container: a stream name longer
|
|
17
|
+
* than the [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed
|
|
18
|
+
* the 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).
|
|
20
|
+
*
|
|
21
|
+
* It shares `code: 'authoring'` with `AuthoringError` rather than being one, and it is the only
|
|
22
|
+
* subsystem in the tree that got its own authoring class. The reason is symmetry with
|
|
23
|
+
* {@link VbaParseError} and not the VBA subsystem's importance: a caller who catches the parse error
|
|
24
|
+
* to skip a macro project it cannot read is the same caller who catches the author error to skip one
|
|
25
|
+
* it cannot write, and the two halves of that pair have to be nameable the same way. A CSV or table
|
|
26
|
+
* authoring failure has no read-side twin to pair with, which is why those stay `AuthoringError`.
|
|
20
27
|
*/
|
|
21
28
|
export declare class VbaAuthorError extends XlsxError {
|
|
22
29
|
readonly name = "VbaAuthorError";
|
package/dist/vba/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile } from './cfb-writer.ts';
|
|
2
1
|
export { VbaAuthorError, VbaParseError } from './errors.ts';
|
|
3
|
-
export { compressContainer, decompressContainer } from './ms-ovba.ts';
|
|
4
2
|
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
3
|
export { addVbaReference, removeVbaModule, type VbaLibraryReference } from './project-editor.ts';
|
package/dist/vba/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { writeCompoundFile } from './cfb-writer.js';
|
|
2
1
|
export { VbaAuthorError, VbaParseError } from './errors.js';
|
|
3
|
-
export { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
4
2
|
export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, vbaProjectSignatureKind, } from './project.js';
|
|
5
3
|
export { addVbaReference, removeVbaModule } from './project-editor.js';
|
package/dist/vba/ms-ovba.d.ts
CHANGED
|
@@ -6,7 +6,11 @@ export declare function decompressContainer(buf: Uint8Array, start?: number, max
|
|
|
6
6
|
/**
|
|
7
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
|
-
* stored
|
|
10
|
-
*
|
|
9
|
+
* stored raw when those tokens would not fit the 4096 bytes a chunk's data may hold.
|
|
10
|
+
*
|
|
11
|
+
* The result re-expands to `data` byte-for-byte, with one exception the format itself prescribes: a
|
|
12
|
+
* final window that is shorter than 4096 bytes and does not compress (only possible past about 3640
|
|
13
|
+
* bytes, where a literal's flag-bit overhead first overflows the chunk) is stored raw and padded with
|
|
14
|
+
* `0x00` to 4096, so it re-expands with those zeros after it ([MS-OVBA] 2.4.1.3.10).
|
|
11
15
|
*/
|
|
12
16
|
export declare function compressContainer(data: Uint8Array): Uint8Array;
|
package/dist/vba/ms-ovba.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
+
import { hex } from '../hex.js';
|
|
1
2
|
import { readU16 } from './bytes.js';
|
|
2
3
|
import { VbaParseError } from './errors.js';
|
|
3
4
|
const MAX_CHUNK_DECOMPRESSED = 4096;
|
|
4
5
|
const CHUNK_SIGNATURE = 0b011 << 12;
|
|
6
|
+
const CHUNK_SIGNATURE_MASK = 0b111 << 12;
|
|
5
7
|
const CHUNK_COMPRESSED_FLAG = 0x8000;
|
|
8
|
+
const MAX_MATCH_CANDIDATES = 64;
|
|
9
|
+
const HASH_BITS = 12;
|
|
10
|
+
const NO_CANDIDATE = -1;
|
|
6
11
|
const DEFAULT_MAX_OUTPUT = 64 * 1024 * 1024;
|
|
7
12
|
export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTPUT) {
|
|
8
13
|
if (start >= buf.length) {
|
|
9
14
|
throw new VbaParseError(`compressed container starts past end of stream (${start} >= ${buf.length})`);
|
|
10
15
|
}
|
|
11
16
|
if (buf[start] !== 0x01) {
|
|
12
|
-
throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${(buf[start] ?? 0)
|
|
17
|
+
throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${hex(buf[start] ?? 0, 2)}`);
|
|
13
18
|
}
|
|
14
|
-
const out = new
|
|
19
|
+
const out = new ByteSink(maxOutput);
|
|
15
20
|
let pos = start + 1;
|
|
16
21
|
while (pos + 2 <= buf.length) {
|
|
17
22
|
const header = readU16(buf, pos);
|
|
18
23
|
pos += 2;
|
|
19
24
|
const chunkDataSize = (header & 0x0fff) + 1;
|
|
20
|
-
const compressed = (header &
|
|
21
|
-
if ((
|
|
22
|
-
throw new VbaParseError(`chunk header has a bad 0b011 signature (0x${header
|
|
25
|
+
const compressed = (header & CHUNK_COMPRESSED_FLAG) !== 0;
|
|
26
|
+
if ((header & CHUNK_SIGNATURE_MASK) !== CHUNK_SIGNATURE) {
|
|
27
|
+
throw new VbaParseError(`chunk header has a bad 0b011 signature (0x${hex(header, 4)})`);
|
|
23
28
|
}
|
|
24
29
|
const chunkEnd = pos + chunkDataSize;
|
|
25
30
|
if (chunkEnd > buf.length) {
|
|
@@ -55,16 +60,19 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
55
60
|
for (let i = 0; i < length; i++)
|
|
56
61
|
out.push(out.at(src + i));
|
|
57
62
|
}
|
|
63
|
+
if (out.length - chunkStart > MAX_CHUNK_DECOMPRESSED) {
|
|
64
|
+
throw new VbaParseError(`chunk decompressed to more than ${MAX_CHUNK_DECOMPRESSED} bytes, which [MS-OVBA] forbids`);
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
pos = chunkEnd;
|
|
60
68
|
}
|
|
61
69
|
return out.bytes();
|
|
62
70
|
}
|
|
63
|
-
class
|
|
71
|
+
class ByteSink {
|
|
64
72
|
#buf;
|
|
65
73
|
#length = 0;
|
|
66
74
|
#limit;
|
|
67
|
-
constructor(limit) {
|
|
75
|
+
constructor(limit = Infinity) {
|
|
68
76
|
this.#limit = limit;
|
|
69
77
|
this.#buf = new Uint8Array(Math.min(MAX_CHUNK_DECOMPRESSED, limit));
|
|
70
78
|
}
|
|
@@ -74,6 +82,9 @@ class DecompressedBytes {
|
|
|
74
82
|
at(index) {
|
|
75
83
|
return this.#buf[index];
|
|
76
84
|
}
|
|
85
|
+
set(index, byte) {
|
|
86
|
+
this.#buf[index] = byte;
|
|
87
|
+
}
|
|
77
88
|
push(byte) {
|
|
78
89
|
if (this.#length >= this.#limit) {
|
|
79
90
|
throw new VbaParseError(`decompressed output exceeds the ${this.#limit}-byte ceiling (possible bomb)`);
|
|
@@ -85,26 +96,47 @@ class DecompressedBytes {
|
|
|
85
96
|
}
|
|
86
97
|
this.#buf[this.#length++] = byte;
|
|
87
98
|
}
|
|
99
|
+
pushAll(bytes) {
|
|
100
|
+
for (const byte of bytes)
|
|
101
|
+
this.push(byte);
|
|
102
|
+
}
|
|
88
103
|
bytes() {
|
|
89
104
|
return this.#buf.slice(0, this.#length);
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
export function compressContainer(data) {
|
|
93
|
-
const out =
|
|
108
|
+
const out = new ByteSink();
|
|
109
|
+
out.push(0x01);
|
|
94
110
|
for (let start = 0; start < data.length; start += MAX_CHUNK_DECOMPRESSED) {
|
|
95
111
|
const chunk = data.subarray(start, Math.min(start + MAX_CHUNK_DECOMPRESSED, data.length));
|
|
96
112
|
const tokens = compressChunk(chunk);
|
|
97
|
-
const compressed = tokens.length
|
|
98
|
-
const body = compressed ? tokens : chunk;
|
|
113
|
+
const compressed = tokens.length <= MAX_CHUNK_DECOMPRESSED;
|
|
114
|
+
const body = compressed ? tokens : padToChunk(chunk);
|
|
99
115
|
const header = (compressed ? CHUNK_COMPRESSED_FLAG : 0) | CHUNK_SIGNATURE | ((body.length - 1) & 0x0fff);
|
|
100
|
-
out.push(header & 0xff
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
out.push(header & 0xff);
|
|
117
|
+
out.push((header >> 8) & 0xff);
|
|
118
|
+
out.pushAll(body);
|
|
103
119
|
}
|
|
104
|
-
return
|
|
120
|
+
return out.bytes();
|
|
121
|
+
}
|
|
122
|
+
function padToChunk(chunk) {
|
|
123
|
+
if (chunk.length === MAX_CHUNK_DECOMPRESSED)
|
|
124
|
+
return chunk;
|
|
125
|
+
const padded = new Uint8Array(MAX_CHUNK_DECOMPRESSED);
|
|
126
|
+
padded.set(chunk);
|
|
127
|
+
return padded;
|
|
105
128
|
}
|
|
106
129
|
function compressChunk(chunk) {
|
|
107
|
-
const tokens =
|
|
130
|
+
const tokens = new ByteSink();
|
|
131
|
+
const heads = new Int32Array(1 << HASH_BITS).fill(NO_CANDIDATE);
|
|
132
|
+
const prev = new Int32Array(MAX_CHUNK_DECOMPRESSED).fill(NO_CANDIDATE);
|
|
133
|
+
const remember = (at) => {
|
|
134
|
+
if (at + 2 >= chunk.length)
|
|
135
|
+
return;
|
|
136
|
+
const bucket = hash3(chunk, at);
|
|
137
|
+
prev[at] = heads[bucket];
|
|
138
|
+
heads[bucket] = at;
|
|
139
|
+
};
|
|
108
140
|
let pos = 0;
|
|
109
141
|
while (pos < chunk.length) {
|
|
110
142
|
const flagIndex = tokens.length;
|
|
@@ -116,37 +148,49 @@ function compressChunk(chunk) {
|
|
|
116
148
|
const windowStart = Math.max(0, pos - (1 << bitCount));
|
|
117
149
|
let bestLength = 0;
|
|
118
150
|
let bestOffset = 0;
|
|
119
|
-
|
|
151
|
+
let candidate = pos + 2 < chunk.length ? heads[hash3(chunk, pos)] : NO_CANDIDATE;
|
|
152
|
+
for (let tried = 0; candidate >= windowStart && tried < MAX_MATCH_CANDIDATES; tried++) {
|
|
120
153
|
let len = 0;
|
|
121
154
|
while (len < maxLength &&
|
|
122
155
|
pos + len < chunk.length &&
|
|
123
|
-
chunk[
|
|
156
|
+
chunk[candidate + len] === chunk[pos + len]) {
|
|
124
157
|
len++;
|
|
125
158
|
}
|
|
126
159
|
if (len > bestLength) {
|
|
127
160
|
bestLength = len;
|
|
128
|
-
bestOffset = pos -
|
|
161
|
+
bestOffset = pos - candidate;
|
|
129
162
|
if (bestLength === maxLength)
|
|
130
163
|
break;
|
|
131
164
|
}
|
|
165
|
+
candidate = prev[candidate];
|
|
132
166
|
}
|
|
133
167
|
if (bestLength >= 3) {
|
|
134
168
|
const token = ((bestOffset - 1) << (16 - bitCount)) | (bestLength - 3);
|
|
135
|
-
tokens.push(token & 0xff
|
|
169
|
+
tokens.push(token & 0xff);
|
|
170
|
+
tokens.push((token >> 8) & 0xff);
|
|
136
171
|
flags |= 1 << bit;
|
|
172
|
+
for (let i = 0; i < bestLength; i++)
|
|
173
|
+
remember(pos + i);
|
|
137
174
|
pos += bestLength;
|
|
138
175
|
}
|
|
139
176
|
else {
|
|
140
177
|
tokens.push(chunk[pos]);
|
|
178
|
+
remember(pos);
|
|
141
179
|
pos++;
|
|
142
180
|
}
|
|
143
181
|
}
|
|
144
|
-
tokens
|
|
182
|
+
tokens.set(flagIndex, flags);
|
|
145
183
|
}
|
|
146
|
-
return tokens;
|
|
184
|
+
return tokens.bytes();
|
|
185
|
+
}
|
|
186
|
+
function hash3(chunk, pos) {
|
|
187
|
+
const first = chunk[pos];
|
|
188
|
+
const second = chunk[pos + 1];
|
|
189
|
+
const third = chunk[pos + 2];
|
|
190
|
+
return ((first << 8) ^ (second << 4) ^ third) & ((1 << HASH_BITS) - 1);
|
|
147
191
|
}
|
|
148
192
|
function copyTokenHelp(decompressedSoFar) {
|
|
149
|
-
const bitCount = Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4);
|
|
193
|
+
const bitCount = Math.min(Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4), Math.log2(MAX_CHUNK_DECOMPRESSED));
|
|
150
194
|
const lengthMask = 0xffff >> bitCount;
|
|
151
195
|
return { lengthMask, bitCount };
|
|
152
196
|
}
|
|
@@ -1,62 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { utf16leBytes } from '../bytes.js';
|
|
2
|
+
import { InternalError, quoted } from '../errors.js';
|
|
3
|
+
import { hex } from '../hex.js';
|
|
4
|
+
import { concat, readU16, spliceBytes, writeU16, writeU32 } from './bytes.js';
|
|
5
|
+
import { sameEntryName } from './cfb-format.js';
|
|
6
|
+
import { isStream, writeCompoundFile } from './cfb-writer.js';
|
|
3
7
|
import { CompoundFile } from './cfb.js';
|
|
4
8
|
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';
|
|
9
|
+
import { dirRecord, 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';
|
|
6
10
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
7
11
|
import { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
const DIR_STREAM = 'dir';
|
|
11
|
-
const PROJECT_STREAM = 'PROJECT';
|
|
12
|
-
const PROJECTWM_STREAM = 'PROJECTwm';
|
|
13
|
-
const VBA_STORAGE = 'VBA';
|
|
12
|
+
import { DIR_PATH, parseVbaProjectIn, PROJECT_PATH, PROJECTWM_PATH, VBA_STORAGE } from './project.js';
|
|
13
|
+
import { validateReferenceName } from './vba-encoding.js';
|
|
14
14
|
export function removeVbaModule(bin, name) {
|
|
15
|
-
const
|
|
15
|
+
const cfb = new CompoundFile(bin);
|
|
16
|
+
const project = parseVbaProjectIn(cfb);
|
|
16
17
|
const nameKey = name.toUpperCase();
|
|
17
18
|
const module = project.modules.find((m) => m.name.toUpperCase() === nameKey);
|
|
18
19
|
if (!module)
|
|
19
|
-
throw new VbaAuthorError(`module
|
|
20
|
+
throw new VbaAuthorError(`module ${quoted(name)} is not in the VBA project`);
|
|
20
21
|
if (module.kind !== 'procedural' && module.kind !== 'class') {
|
|
21
|
-
throw new VbaAuthorError(`cannot remove module
|
|
22
|
+
throw new VbaAuthorError(`cannot remove module ${quoted(name)}: its kind ${quoted(module.kind)} is tied to host linkage this ` +
|
|
22
23
|
'primitive cannot verify');
|
|
23
24
|
}
|
|
24
|
-
const
|
|
25
|
-
const dirCompressed = cfb.readStream(
|
|
25
|
+
const decoder = decoderForCodePage(project.codePage);
|
|
26
|
+
const dirCompressed = cfb.readStream(DIR_PATH);
|
|
26
27
|
if (!dirCompressed)
|
|
27
28
|
throw new VbaParseError("VBA project has no 'dir' stream");
|
|
28
|
-
const patchedDir = removeModuleDirRecord(decompressContainer(dirCompressed), module.streamName,
|
|
29
|
-
const replacements =
|
|
30
|
-
const
|
|
31
|
-
const encode = encoderForCodePage(project.codePage);
|
|
32
|
-
const projectText = cfb.readStream(PROJECT_STREAM);
|
|
29
|
+
const patchedDir = removeModuleDirRecord(decompressContainer(dirCompressed), module.streamName, decoder);
|
|
30
|
+
const replacements = [{ path: DIR_PATH, data: compressContainer(patchedDir) }];
|
|
31
|
+
const projectText = cfb.readStream(PROJECT_PATH);
|
|
33
32
|
if (projectText) {
|
|
34
|
-
replacements.
|
|
33
|
+
replacements.push({
|
|
34
|
+
path: PROJECT_PATH,
|
|
35
|
+
data: removeProjectStreamLines(projectText, module.name, module.kind, decoder),
|
|
36
|
+
});
|
|
35
37
|
}
|
|
36
|
-
const projectwm = cfb.readStream(
|
|
38
|
+
const projectwm = cfb.readStream(PROJECTWM_PATH);
|
|
37
39
|
if (projectwm) {
|
|
38
|
-
replacements.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!applied.has(DIR_STREAM))
|
|
43
|
-
throw new VbaParseError("VBA project 'dir' stream is not in the container tree");
|
|
44
|
-
const removed = new Set();
|
|
45
|
-
const newTree = removeFromStorage(withReplacements, VBA_STORAGE, module.streamName, removed);
|
|
46
|
-
if (!removed.has(VBA_STORAGE)) {
|
|
47
|
-
throw new VbaParseError(`module stream '${module.streamName}' is not in the '${VBA_STORAGE}' storage`);
|
|
40
|
+
replacements.push({
|
|
41
|
+
path: PROJECTWM_PATH,
|
|
42
|
+
data: removeProjectwmRecord(projectwm, project.modules.length, module.name, decoder),
|
|
43
|
+
});
|
|
48
44
|
}
|
|
45
|
+
const newTree = withoutStream(replaceStreams(cfb.tree(), replacements), [
|
|
46
|
+
VBA_STORAGE,
|
|
47
|
+
module.streamName,
|
|
48
|
+
]);
|
|
49
49
|
return writeCompoundFile(newTree);
|
|
50
50
|
}
|
|
51
51
|
const MAX_LIBID_VERSION = 0xffff;
|
|
52
52
|
const MAX_LIBID_LCID = 0xffffffff;
|
|
53
53
|
const MAX_DISPLAY_NAME_CHARS = 255;
|
|
54
|
+
const MAX_LIBID_PATH_CHARS = 32_767;
|
|
54
55
|
const GUID_PATTERN = /^\{?([0-9A-Fa-f]{8})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{4})-([0-9A-Fa-f]{12})\}?$/;
|
|
55
56
|
function normalizeReference(ref) {
|
|
56
|
-
|
|
57
|
+
validateReferenceName(ref.name);
|
|
57
58
|
const guidMatch = GUID_PATTERN.exec(ref.guid.trim());
|
|
58
59
|
if (!guidMatch)
|
|
59
|
-
throw new VbaAuthorError(`invalid reference GUID
|
|
60
|
+
throw new VbaAuthorError(`invalid reference GUID ${quoted(ref.guid)}`);
|
|
60
61
|
const guid = `{${guidMatch.slice(1, 6).join('-').toUpperCase()}}`;
|
|
61
62
|
for (const [field, value] of [
|
|
62
63
|
['majorVersion', ref.majorVersion],
|
|
@@ -70,50 +71,43 @@ function normalizeReference(ref) {
|
|
|
70
71
|
if (!Number.isInteger(lcid) || lcid < 0 || lcid > MAX_LIBID_LCID) {
|
|
71
72
|
throw new VbaAuthorError(`reference lcid must be an integer in [0, 0xFFFFFFFF], got ${lcid}`);
|
|
72
73
|
}
|
|
74
|
+
if (ref.path.length > MAX_LIBID_PATH_CHARS) {
|
|
75
|
+
throw new VbaAuthorError(`reference path is ${ref.path.length} characters long, past the ${MAX_LIBID_PATH_CHARS} a Windows path can hold`);
|
|
76
|
+
}
|
|
73
77
|
if (ref.path.length === 0 || ref.path.includes('\0') || ref.path.includes('#')) {
|
|
74
|
-
throw new VbaAuthorError(`invalid reference path
|
|
78
|
+
throw new VbaAuthorError(`invalid reference path ${quoted(ref.path)} (must be non-empty and contain no NUL or '#')`);
|
|
75
79
|
}
|
|
76
80
|
const displayName = ref.displayName ?? ref.name;
|
|
77
81
|
if (displayName.length === 0 ||
|
|
78
82
|
displayName.length > MAX_DISPLAY_NAME_CHARS ||
|
|
79
83
|
displayName.includes('\0')) {
|
|
80
|
-
throw new VbaAuthorError(`invalid reference display name
|
|
84
|
+
throw new VbaAuthorError(`invalid reference display name ${quoted(displayName)}`);
|
|
81
85
|
}
|
|
82
|
-
const libid = `*\\G${guid}#${ref.majorVersion.
|
|
83
|
-
`${ref.minorVersion.toString(16).toUpperCase()}#${lcid.toString(16).toUpperCase()}` +
|
|
86
|
+
const libid = `*\\G${guid}#${hex(ref.majorVersion, 1)}.${hex(ref.minorVersion, 1)}#${hex(lcid, 1)}` +
|
|
84
87
|
`#${ref.path}#${displayName}`;
|
|
85
88
|
return { name: ref.name, libid };
|
|
86
89
|
}
|
|
87
90
|
export function addVbaReference(bin, ref) {
|
|
88
91
|
const normalized = normalizeReference(ref);
|
|
89
|
-
const project = parseVbaProject(bin);
|
|
90
|
-
const encode = encoderForCodePage(project.codePage);
|
|
91
92
|
const cfb = new CompoundFile(bin);
|
|
92
|
-
const
|
|
93
|
+
const encode = encoderForCodePage(parseVbaProjectIn(cfb).codePage);
|
|
94
|
+
const dirCompressed = cfb.readStream(DIR_PATH);
|
|
93
95
|
if (!dirCompressed)
|
|
94
96
|
throw new VbaParseError("VBA project has no 'dir' stream");
|
|
95
97
|
const records = buildReferenceDirRecords(normalized, encode);
|
|
96
98
|
const patchedDir = insertReferenceDirRecords(decompressContainer(dirCompressed), records);
|
|
97
|
-
|
|
98
|
-
const applied = new Set();
|
|
99
|
-
const newTree = replaceStreams(cfb.tree(), replacements, applied);
|
|
100
|
-
if (!applied.has(DIR_STREAM))
|
|
101
|
-
throw new VbaParseError("VBA project 'dir' stream is not in the container tree");
|
|
102
|
-
return writeCompoundFile(newTree);
|
|
99
|
+
return writeCompoundFile(replaceStreams(cfb.tree(), [{ path: DIR_PATH, data: compressContainer(patchedDir) }]));
|
|
103
100
|
}
|
|
104
101
|
function buildReferenceDirRecords(ref, encode) {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
...u32(0),
|
|
114
|
-
...u16(0),
|
|
102
|
+
const libid = encode(ref.libid);
|
|
103
|
+
const sizeOfLibid = new Uint8Array(4);
|
|
104
|
+
writeU32(sizeOfLibid, 0, libid.length);
|
|
105
|
+
const reserved = new Uint8Array(6);
|
|
106
|
+
return concat([
|
|
107
|
+
dirRecord(REC_REFERENCE_NAME, encode(ref.name)),
|
|
108
|
+
dirRecord(REC_REFERENCE_NAME_UNICODE, utf16leBytes(ref.name)),
|
|
109
|
+
dirRecord(REC_REFERENCE_REGISTERED, concat([sizeOfLibid, libid, reserved])),
|
|
115
110
|
]);
|
|
116
|
-
return r;
|
|
117
111
|
}
|
|
118
112
|
function insertReferenceDirRecords(dir, records) {
|
|
119
113
|
let insertAt = -1;
|
|
@@ -125,15 +119,9 @@ function insertReferenceDirRecords(dir, records) {
|
|
|
125
119
|
}
|
|
126
120
|
if (insertAt < 0)
|
|
127
121
|
throw new VbaParseError('dir stream is missing MODULES_COUNT');
|
|
128
|
-
|
|
129
|
-
const out = new Uint8Array(dir.length + rec.length);
|
|
130
|
-
out.set(dir.subarray(0, insertAt), 0);
|
|
131
|
-
out.set(rec, insertAt);
|
|
132
|
-
out.set(dir.subarray(insertAt), insertAt + rec.length);
|
|
133
|
-
return out;
|
|
122
|
+
return spliceBytes(dir, insertAt, insertAt, records);
|
|
134
123
|
}
|
|
135
|
-
function removeModuleDirRecord(dir, streamName,
|
|
136
|
-
const decoder = decoderForCodePage(codePage);
|
|
124
|
+
function removeModuleDirRecord(dir, streamName, decoder) {
|
|
137
125
|
let countAt = -1;
|
|
138
126
|
let blockStart = -1;
|
|
139
127
|
let removeStart = -1;
|
|
@@ -143,7 +131,8 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
143
131
|
if (id === REC_MODULES_COUNT) {
|
|
144
132
|
if (size < 2)
|
|
145
133
|
throw new VbaParseError('PROJECTMODULES MODULES_COUNT record is malformed');
|
|
146
|
-
countAt
|
|
134
|
+
if (countAt < 0)
|
|
135
|
+
countAt = dataStart;
|
|
147
136
|
}
|
|
148
137
|
else if (id === REC_MODULE_NAME) {
|
|
149
138
|
blockStart = recordStart;
|
|
@@ -163,36 +152,49 @@ function removeModuleDirRecord(dir, streamName, codePage) {
|
|
|
163
152
|
if (countAt < 0)
|
|
164
153
|
throw new VbaParseError('dir stream is missing MODULES_COUNT');
|
|
165
154
|
if (removeStart < 0 || removeEnd < 0) {
|
|
166
|
-
throw new VbaParseError(`module stream
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
155
|
+
throw new VbaParseError(`module stream ${quoted(streamName)} not found in the dir stream`);
|
|
156
|
+
}
|
|
157
|
+
if (countAt >= removeStart) {
|
|
158
|
+
throw new VbaParseError('dir stream declares MODULES_COUNT after a module block');
|
|
159
|
+
}
|
|
160
|
+
const count = readU16(dir, countAt);
|
|
161
|
+
if (count === 0) {
|
|
162
|
+
throw new VbaParseError('dir stream declares zero modules but carries a module block');
|
|
163
|
+
}
|
|
164
|
+
const out = spliceBytes(dir, removeStart, removeEnd);
|
|
165
|
+
writeU16(out, countAt, count - 1);
|
|
174
166
|
return out;
|
|
175
167
|
}
|
|
176
|
-
function removeProjectStreamLines(
|
|
177
|
-
const
|
|
178
|
-
const lines = text.split(/\r\n|\r|\n/);
|
|
168
|
+
function removeProjectStreamLines(stream, name, kind, decoder) {
|
|
169
|
+
const lines = projectLines(stream, decoder);
|
|
179
170
|
const declLine = `${kind === 'procedural' ? 'Module' : 'Class'}=${name}`;
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
171
|
+
const removals = [];
|
|
172
|
+
const decl = lines.find((line) => line.text === declLine);
|
|
173
|
+
if (decl !== undefined)
|
|
174
|
+
removals.push(decl);
|
|
175
|
+
const wsIndex = lines.findIndex((line) => line.text.trim() === '[Workspace]');
|
|
176
|
+
for (const line of wsIndex < 0 ? [] : lines.slice(wsIndex + 1)) {
|
|
177
|
+
if (line.text.trim() === '' || line.text.startsWith('['))
|
|
178
|
+
break;
|
|
179
|
+
if (line.text.startsWith(`${name}=`)) {
|
|
180
|
+
removals.push(line);
|
|
181
|
+
break;
|
|
193
182
|
}
|
|
194
183
|
}
|
|
195
|
-
return
|
|
184
|
+
return removals
|
|
185
|
+
.sort((a, b) => b.start - a.start)
|
|
186
|
+
.reduce((bytes, line) => spliceBytes(bytes, line.start, line.end), stream);
|
|
187
|
+
}
|
|
188
|
+
function projectLines(stream, decoder) {
|
|
189
|
+
const lines = [];
|
|
190
|
+
for (let start = 0; start < stream.length;) {
|
|
191
|
+
const lf = stream.indexOf(0x0a, start);
|
|
192
|
+
const end = lf < 0 ? stream.length : lf + 1;
|
|
193
|
+
const contentEnd = lf > start && stream[lf - 1] === 0x0d ? lf - 1 : lf < 0 ? stream.length : lf;
|
|
194
|
+
lines.push({ text: decoder.decode(stream.subarray(start, contentEnd)), start, end });
|
|
195
|
+
start = end;
|
|
196
|
+
}
|
|
197
|
+
return lines;
|
|
196
198
|
}
|
|
197
199
|
function removeProjectwmRecord(wm, existingModuleCount, name, decoder) {
|
|
198
200
|
let pos = 0;
|
|
@@ -218,37 +220,45 @@ function removeProjectwmRecord(wm, existingModuleCount, name, decoder) {
|
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
222
|
if (removeStart < 0 || removeEnd < 0) {
|
|
221
|
-
throw new VbaParseError(`module
|
|
223
|
+
throw new VbaParseError(`module ${quoted(name)} not found in the PROJECTwm stream`);
|
|
222
224
|
}
|
|
223
|
-
|
|
224
|
-
out.set(wm.subarray(0, removeStart), 0);
|
|
225
|
-
out.set(wm.subarray(removeEnd), removeStart);
|
|
226
|
-
return out;
|
|
227
|
-
}
|
|
228
|
-
function removeFromStorage(nodes, storageName, streamName, removed) {
|
|
229
|
-
return nodes.map((n) => {
|
|
230
|
-
if ('data' in n)
|
|
231
|
-
return n;
|
|
232
|
-
const children = removeFromStorage(n.children, storageName, streamName, removed);
|
|
233
|
-
if (n.name === storageName && !removed.has(storageName)) {
|
|
234
|
-
const filtered = children.filter((c) => !('data' in c && c.name === streamName));
|
|
235
|
-
if (filtered.length !== children.length)
|
|
236
|
-
removed.add(storageName);
|
|
237
|
-
return { name: n.name, children: filtered };
|
|
238
|
-
}
|
|
239
|
-
return { name: n.name, children };
|
|
240
|
-
});
|
|
225
|
+
return spliceBytes(wm, removeStart, removeEnd);
|
|
241
226
|
}
|
|
242
|
-
function replaceStreams(nodes, replacements
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
227
|
+
function replaceStreams(nodes, replacements) {
|
|
228
|
+
let applied = 0;
|
|
229
|
+
const rebuild = (level, pending, depth) => level.map((node) => {
|
|
230
|
+
const here = pending.filter((replacement) => {
|
|
231
|
+
const segment = replacement.path[depth];
|
|
232
|
+
return segment !== undefined && sameEntryName(segment, node.name);
|
|
233
|
+
});
|
|
234
|
+
if (here.length === 0)
|
|
250
235
|
return node;
|
|
236
|
+
if (isStream(node)) {
|
|
237
|
+
const replacement = here.find((candidate) => candidate.path.length === depth + 1);
|
|
238
|
+
if (replacement === undefined)
|
|
239
|
+
return node;
|
|
240
|
+
applied++;
|
|
241
|
+
return { name: node.name, data: replacement.data };
|
|
251
242
|
}
|
|
252
|
-
return { name: node.name, children:
|
|
243
|
+
return { name: node.name, children: rebuild(node.children, here, depth + 1) };
|
|
253
244
|
});
|
|
245
|
+
const rebuilt = rebuild(nodes, replacements, 0);
|
|
246
|
+
if (applied !== replacements.length) {
|
|
247
|
+
throw new InternalError('a VBA stream read by its path is missing from the container tree');
|
|
248
|
+
}
|
|
249
|
+
return rebuilt;
|
|
250
|
+
}
|
|
251
|
+
function withoutStream(nodes, path) {
|
|
252
|
+
const [name, ...rest] = path;
|
|
253
|
+
const index = nodes.findIndex((node) => name !== undefined &&
|
|
254
|
+
sameEntryName(node.name, name) &&
|
|
255
|
+
isStream(node) === (rest.length === 0));
|
|
256
|
+
const target = nodes[index];
|
|
257
|
+
if (target === undefined) {
|
|
258
|
+
throw new InternalError('a VBA module stream read by its path is missing from the container tree');
|
|
259
|
+
}
|
|
260
|
+
const kept = isStream(target)
|
|
261
|
+
? []
|
|
262
|
+
: [{ name: target.name, children: withoutStream(target.children, rest) }];
|
|
263
|
+
return [...nodes.slice(0, index), ...kept, ...nodes.slice(index + 1)];
|
|
254
264
|
}
|
package/dist/vba/project.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CompoundFile } from './cfb.ts';
|
|
1
2
|
/** How a module participates in the project: the classification the VBA editor shows. */
|
|
2
3
|
export type VbaModuleKind = 'procedural' | 'document' | 'class' | 'designer';
|
|
3
4
|
export interface VbaModule {
|
|
@@ -42,4 +43,17 @@ export interface VbaProjectSignature {
|
|
|
42
43
|
* carries (`.../office/2006/...` vs `.../2014/...` vs `.../2020/...`).
|
|
43
44
|
*/
|
|
44
45
|
export declare function vbaProjectSignatureKind(relType: string): VbaProjectSignatureKind | undefined;
|
|
45
|
-
export declare
|
|
46
|
+
export declare const VBA_STORAGE = "VBA";
|
|
47
|
+
export declare const DIR_PATH: readonly string[];
|
|
48
|
+
export declare const PROJECT_PATH: readonly string[];
|
|
49
|
+
export declare const PROJECTWM_PATH: readonly string[];
|
|
50
|
+
export declare function parseVbaProject(bin: Uint8Array, maxOutput?: number): VbaProject;
|
|
51
|
+
/**
|
|
52
|
+
* As {@link parseVbaProject}, over a container the caller has already opened.
|
|
53
|
+
*
|
|
54
|
+
* Both project editors parse fail-closed first and then need the container itself, and each was
|
|
55
|
+
* building a second `CompoundFile` over the same bytes: a second walk of the DIFAT, FAT, mini-FAT,
|
|
56
|
+
* directory and mini-stream, which is roughly double the cost and double the peak allocation of every
|
|
57
|
+
* edit. Opening it once and handing it in is the whole of the fix.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseVbaProjectIn(cfb: CompoundFile, maxOutput?: number): VbaProject;
|