@shbernal/ts-xlsx 3.1.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 +30 -23
- package/dist/bytes.d.ts +3 -2
- package/dist/core/address.d.ts +36 -0
- package/dist/core/address.js +47 -9
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/color-resolution.js +2 -2
- package/dist/core/column.js +3 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/conditional-formatting-overlay.d.ts +9 -0
- package/dist/core/conditional-formatting-overlay.js +28 -1
- package/dist/core/conditional-formatting.d.ts +98 -10
- package/dist/core/conditional-formatting.js +42 -1
- package/dist/core/data-validation-overlay.d.ts +5 -0
- package/dist/core/data-validation-overlay.js +15 -0
- package/dist/core/data-validation.d.ts +16 -0
- package/dist/core/data-validation.js +15 -0
- package/dist/core/date-format.js +14 -4
- package/dist/core/date.js +1 -1
- 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 +51 -18
- package/dist/core/formula.js +77 -86
- 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 +28 -0
- package/dist/core/grid-edits.js +193 -25
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +36 -5
- package/dist/core/image.js +9 -0
- package/dist/core/internal.d.ts +3 -1
- package/dist/core/limits.d.ts +5 -3
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +15 -1
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +55 -21
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +4 -0
- package/dist/core/row.js +3 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/table.d.ts +38 -8
- package/dist/core/table.js +37 -7
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +4 -1
- package/dist/core/value.d.ts +51 -28
- package/dist/core/value.js +15 -12
- package/dist/core/workbook-styles.d.ts +17 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook.d.ts +20 -3
- package/dist/core/workbook.js +26 -5
- package/dist/core/worksheet-merges.d.ts +9 -4
- package/dist/core/worksheet-merges.js +17 -11
- package/dist/core/worksheet-model.d.ts +1 -1
- package/dist/core/worksheet-model.js +4 -0
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +110 -21
- package/dist/core/worksheet.js +174 -49
- package/dist/entries/core.d.ts +7 -5
- package/dist/entries/core.js +4 -3
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/errors.js +1 -1
- 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 +14 -2
- package/dist/io/csv/delimiter.js +12 -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 +46 -24
- package/dist/io/csv/write.d.ts +10 -3
- package/dist/io/csv/write.js +16 -7
- package/dist/io/opc/part-paths.js +4 -4
- package/dist/io/opc/read-opc.d.ts +4 -0
- package/dist/io/opc/read-opc.js +10 -4
- 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/{xlsx → read-policy}/column-budget.js +4 -8
- 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/xlsb/formula.js +4 -2
- package/dist/io/xlsb/ptg-functions.js +110 -0
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-worksheet.d.ts +6 -0
- package/dist/io/xlsb/read-worksheet.js +95 -37
- package/dist/io/xlsb/read.js +26 -8
- package/dist/io/xlsb/record-types.d.ts +13 -0
- package/dist/io/xlsb/record-types.js +13 -0
- 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 +20 -12
- package/dist/io/xlsx/cell-accumulator.js +49 -19
- 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 +16 -4
- package/dist/io/xlsx/cell-value.js +38 -21
- package/dist/io/xlsx/color-xml.js +1 -1
- 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 +3 -28
- package/dist/io/xlsx/comments.js +3 -65
- package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
- package/dist/io/xlsx/conditional-formatting.js +424 -129
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +41 -29
- package/dist/io/xlsx/edit-vba.js +43 -36
- package/dist/io/xlsx/hyperlinks.d.ts +16 -50
- package/dist/io/xlsx/hyperlinks.js +8 -37
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +145 -44
- package/dist/io/xlsx/namespaces.d.ts +8 -0
- package/dist/io/xlsx/namespaces.js +4 -0
- package/dist/io/xlsx/package-plan.d.ts +48 -15
- package/dist/io/xlsx/package-plan.js +46 -9
- 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 +23 -22
- 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 +2 -2
- package/dist/io/xlsx/read-parts.js +44 -37
- package/dist/io/xlsx/read-pivot.js +9 -3
- package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
- package/dist/io/xlsx/read-rich-runs.js +18 -1
- package/dist/io/xlsx/read-rows.d.ts +18 -7
- package/dist/io/xlsx/read-rows.js +47 -37
- package/dist/io/xlsx/read-styles.d.ts +0 -25
- package/dist/io/xlsx/read-styles.js +39 -34
- package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
- package/dist/io/xlsx/read-workbook-xml.js +9 -4
- package/dist/io/xlsx/read-worksheet.d.ts +8 -3
- package/dist/io/xlsx/read-worksheet.js +45 -23
- package/dist/io/xlsx/read.d.ts +0 -4
- package/dist/io/xlsx/read.js +17 -21
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/row-position.d.ts +3 -1
- package/dist/io/xlsx/row-xml.d.ts +18 -7
- package/dist/io/xlsx/row-xml.js +54 -35
- package/dist/io/xlsx/sheet-properties.js +68 -42
- 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 +0 -2
- package/dist/io/xlsx/style-elements.js +9 -29
- package/dist/io/xlsx/styles.d.ts +5 -0
- package/dist/io/xlsx/styles.js +11 -5
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +49 -42
- package/dist/io/xlsx/theme-xml.d.ts +6 -0
- package/dist/io/xlsx/theme-xml.js +16 -19
- package/dist/io/xlsx/threaded-comments.d.ts +6 -1
- package/dist/io/xlsx/threaded-comments.js +14 -7
- package/dist/io/xlsx/workbook-xml.d.ts +13 -24
- package/dist/io/xlsx/workbook-xml.js +15 -33
- package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
- package/dist/io/xlsx/worksheet-xml.js +37 -69
- package/dist/io/xlsx/write-stream.d.ts +28 -6
- package/dist/io/xlsx/write-stream.js +42 -17
- package/dist/io/xlsx/write.d.ts +11 -4
- package/dist/io/xlsx/write.js +153 -90
- package/dist/rel-type.d.ts +9 -0
- package/dist/rel-type.js +3 -0
- package/dist/vba/bytes.d.ts +2 -0
- package/dist/vba/bytes.js +11 -0
- package/dist/vba/cfb-format.d.ts +47 -0
- package/dist/vba/cfb-format.js +34 -0
- package/dist/vba/cfb-writer.d.ts +1 -2
- package/dist/vba/cfb-writer.js +33 -36
- package/dist/vba/cfb.d.ts +9 -4
- package/dist/vba/cfb.js +55 -43
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +4 -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 +15 -6
- package/dist/vba/project-editor.js +102 -87
- package/dist/vba/project.d.ts +4 -0
- package/dist/vba/project.js +12 -6
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +2 -15
- package/dist/xml/xml-read.d.ts +29 -1
- package/dist/xml/xml-read.js +56 -0
- package/dist/xml/xml-scan.d.ts +13 -0
- package/dist/xml/xml-scan.js +58 -7
- package/dist/xml/xml.d.ts +33 -1
- package/dist/xml/xml.js +22 -0
- package/package.json +5 -4
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
- package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
- package/dist/io/xlsx/cell-style-resolution.js +0 -29
- package/dist/io/xlsx/column-budget.d.ts +0 -26
- package/dist/io/xlsx/read-repair.d.ts +0 -26
- package/dist/io/xlsx/read-repair.js +0 -39
|
@@ -1,17 +1,16 @@
|
|
|
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';
|
|
3
6
|
import { isStream, writeCompoundFile } from './cfb-writer.js';
|
|
4
7
|
import { CompoundFile } from './cfb.js';
|
|
5
8
|
import { decoderForCodePage, encoderForCodePage } from './codepage.js';
|
|
6
|
-
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';
|
|
7
10
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
8
11
|
import { compressContainer, decompressContainer } from './ms-ovba.js';
|
|
9
|
-
import { parseVbaProjectIn } from './project.js';
|
|
10
|
-
import {
|
|
11
|
-
const DIR_STREAM = 'dir';
|
|
12
|
-
const PROJECT_STREAM = 'PROJECT';
|
|
13
|
-
const PROJECTWM_STREAM = 'PROJECTwm';
|
|
14
|
-
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';
|
|
15
14
|
export function removeVbaModule(bin, name) {
|
|
16
15
|
const cfb = new CompoundFile(bin);
|
|
17
16
|
const project = parseVbaProjectIn(cfb);
|
|
@@ -24,37 +23,38 @@ export function removeVbaModule(bin, name) {
|
|
|
24
23
|
'primitive cannot verify');
|
|
25
24
|
}
|
|
26
25
|
const decoder = decoderForCodePage(project.codePage);
|
|
27
|
-
const dirCompressed = cfb.readStream(
|
|
26
|
+
const dirCompressed = cfb.readStream(DIR_PATH);
|
|
28
27
|
if (!dirCompressed)
|
|
29
28
|
throw new VbaParseError("VBA project has no 'dir' stream");
|
|
30
29
|
const patchedDir = removeModuleDirRecord(decompressContainer(dirCompressed), module.streamName, decoder);
|
|
31
|
-
const replacements =
|
|
32
|
-
const
|
|
33
|
-
const projectText = cfb.readStream(PROJECT_STREAM);
|
|
30
|
+
const replacements = [{ path: DIR_PATH, data: compressContainer(patchedDir) }];
|
|
31
|
+
const projectText = cfb.readStream(PROJECT_PATH);
|
|
34
32
|
if (projectText) {
|
|
35
|
-
replacements.
|
|
33
|
+
replacements.push({
|
|
34
|
+
path: PROJECT_PATH,
|
|
35
|
+
data: removeProjectStreamLines(projectText, module.name, module.kind, decoder),
|
|
36
|
+
});
|
|
36
37
|
}
|
|
37
|
-
const projectwm = cfb.readStream(
|
|
38
|
+
const projectwm = cfb.readStream(PROJECTWM_PATH);
|
|
38
39
|
if (projectwm) {
|
|
39
|
-
replacements.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!applied.has(DIR_STREAM))
|
|
44
|
-
throw new VbaParseError("VBA project 'dir' stream is not in the container tree");
|
|
45
|
-
const removed = new Set();
|
|
46
|
-
const newTree = removeFromStorage(withReplacements, VBA_STORAGE, module.streamName, removed);
|
|
47
|
-
if (!removed.has(VBA_STORAGE)) {
|
|
48
|
-
throw new VbaParseError(`module stream ${quoted(module.streamName)} is not in the ${quoted(VBA_STORAGE)} storage`);
|
|
40
|
+
replacements.push({
|
|
41
|
+
path: PROJECTWM_PATH,
|
|
42
|
+
data: removeProjectwmRecord(projectwm, project.modules.length, module.name, decoder),
|
|
43
|
+
});
|
|
49
44
|
}
|
|
45
|
+
const newTree = withoutStream(replaceStreams(cfb.tree(), replacements), [
|
|
46
|
+
VBA_STORAGE,
|
|
47
|
+
module.streamName,
|
|
48
|
+
]);
|
|
50
49
|
return writeCompoundFile(newTree);
|
|
51
50
|
}
|
|
52
51
|
const MAX_LIBID_VERSION = 0xffff;
|
|
53
52
|
const MAX_LIBID_LCID = 0xffffffff;
|
|
54
53
|
const MAX_DISPLAY_NAME_CHARS = 255;
|
|
54
|
+
const MAX_LIBID_PATH_CHARS = 32_767;
|
|
55
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})\}?$/;
|
|
56
56
|
function normalizeReference(ref) {
|
|
57
|
-
|
|
57
|
+
validateReferenceName(ref.name);
|
|
58
58
|
const guidMatch = GUID_PATTERN.exec(ref.guid.trim());
|
|
59
59
|
if (!guidMatch)
|
|
60
60
|
throw new VbaAuthorError(`invalid reference GUID ${quoted(ref.guid)}`);
|
|
@@ -71,6 +71,9 @@ function normalizeReference(ref) {
|
|
|
71
71
|
if (!Number.isInteger(lcid) || lcid < 0 || lcid > MAX_LIBID_LCID) {
|
|
72
72
|
throw new VbaAuthorError(`reference lcid must be an integer in [0, 0xFFFFFFFF], got ${lcid}`);
|
|
73
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
|
+
}
|
|
74
77
|
if (ref.path.length === 0 || ref.path.includes('\0') || ref.path.includes('#')) {
|
|
75
78
|
throw new VbaAuthorError(`invalid reference path ${quoted(ref.path)} (must be non-empty and contain no NUL or '#')`);
|
|
76
79
|
}
|
|
@@ -80,8 +83,7 @@ function normalizeReference(ref) {
|
|
|
80
83
|
displayName.includes('\0')) {
|
|
81
84
|
throw new VbaAuthorError(`invalid reference display name ${quoted(displayName)}`);
|
|
82
85
|
}
|
|
83
|
-
const libid = `*\\G${guid}#${ref.majorVersion.
|
|
84
|
-
`${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)}` +
|
|
85
87
|
`#${ref.path}#${displayName}`;
|
|
86
88
|
return { name: ref.name, libid };
|
|
87
89
|
}
|
|
@@ -89,31 +91,23 @@ export function addVbaReference(bin, ref) {
|
|
|
89
91
|
const normalized = normalizeReference(ref);
|
|
90
92
|
const cfb = new CompoundFile(bin);
|
|
91
93
|
const encode = encoderForCodePage(parseVbaProjectIn(cfb).codePage);
|
|
92
|
-
const dirCompressed = cfb.readStream(
|
|
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,7 +119,7 @@ function insertReferenceDirRecords(dir, records) {
|
|
|
125
119
|
}
|
|
126
120
|
if (insertAt < 0)
|
|
127
121
|
throw new VbaParseError('dir stream is missing MODULES_COUNT');
|
|
128
|
-
return spliceBytes(dir, insertAt, insertAt,
|
|
122
|
+
return spliceBytes(dir, insertAt, insertAt, records);
|
|
129
123
|
}
|
|
130
124
|
function removeModuleDirRecord(dir, streamName, decoder) {
|
|
131
125
|
let countAt = -1;
|
|
@@ -171,26 +165,36 @@ function removeModuleDirRecord(dir, streamName, decoder) {
|
|
|
171
165
|
writeU16(out, countAt, count - 1);
|
|
172
166
|
return out;
|
|
173
167
|
}
|
|
174
|
-
function removeProjectStreamLines(
|
|
175
|
-
const
|
|
176
|
-
const lines = text.split(/\r\n|\r|\n/);
|
|
168
|
+
function removeProjectStreamLines(stream, name, kind, decoder) {
|
|
169
|
+
const lines = projectLines(stream, decoder);
|
|
177
170
|
const declLine = `${kind === 'procedural' ? 'Module' : 'Class'}=${name}`;
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
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;
|
|
191
182
|
}
|
|
192
183
|
}
|
|
193
|
-
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;
|
|
194
198
|
}
|
|
195
199
|
function removeProjectwmRecord(wm, existingModuleCount, name, decoder) {
|
|
196
200
|
let pos = 0;
|
|
@@ -220,30 +224,41 @@ function removeProjectwmRecord(wm, existingModuleCount, name, decoder) {
|
|
|
220
224
|
}
|
|
221
225
|
return spliceBytes(wm, removeStart, removeEnd);
|
|
222
226
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
removed.add(storageName);
|
|
232
|
-
return { name: n.name, children: filtered };
|
|
233
|
-
}
|
|
234
|
-
return { name: n.name, children };
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
function replaceStreams(nodes, replacements, applied) {
|
|
238
|
-
return nodes.map((node) => {
|
|
239
|
-
if (isStream(node)) {
|
|
240
|
-
const data = replacements.get(node.name);
|
|
241
|
-
if (data !== undefined) {
|
|
242
|
-
applied.add(node.name);
|
|
243
|
-
return { name: node.name, data };
|
|
244
|
-
}
|
|
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)
|
|
245
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 };
|
|
246
242
|
}
|
|
247
|
-
return { name: node.name, children:
|
|
243
|
+
return { name: node.name, children: rebuild(node.children, here, depth + 1) };
|
|
248
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)];
|
|
249
264
|
}
|
package/dist/vba/project.d.ts
CHANGED
|
@@ -43,6 +43,10 @@ export interface VbaProjectSignature {
|
|
|
43
43
|
* carries (`.../office/2006/...` vs `.../2014/...` vs `.../2020/...`).
|
|
44
44
|
*/
|
|
45
45
|
export declare function vbaProjectSignatureKind(relType: string): VbaProjectSignatureKind | undefined;
|
|
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[];
|
|
46
50
|
export declare function parseVbaProject(bin: Uint8Array, maxOutput?: number): VbaProject;
|
|
47
51
|
/**
|
|
48
52
|
* As {@link parseVbaProject}, over a container the caller has already opened.
|
package/dist/vba/project.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { quoted } from '../errors.js';
|
|
2
|
+
import { relTypeSegment } from '../rel-type.js';
|
|
2
3
|
import { readU16, readU32 } from './bytes.js';
|
|
3
4
|
import { CompoundFile } from './cfb.js';
|
|
4
5
|
import { decoderForCodePage } from './codepage.js';
|
|
@@ -14,17 +15,21 @@ const SIGNATURE_KIND_BY_REL_SEGMENT = new Map([
|
|
|
14
15
|
['vbaProjectSignatureV3', 'v3'],
|
|
15
16
|
]);
|
|
16
17
|
export function vbaProjectSignatureKind(relType) {
|
|
17
|
-
return SIGNATURE_KIND_BY_REL_SEGMENT.get(
|
|
18
|
+
return SIGNATURE_KIND_BY_REL_SEGMENT.get(relTypeSegment(relType));
|
|
18
19
|
}
|
|
19
20
|
function isComplete(module) {
|
|
20
21
|
return module.streamName !== undefined && module.offset !== undefined;
|
|
21
22
|
}
|
|
23
|
+
export const VBA_STORAGE = 'VBA';
|
|
24
|
+
export const DIR_PATH = [VBA_STORAGE, 'dir'];
|
|
25
|
+
export const PROJECT_PATH = ['PROJECT'];
|
|
26
|
+
export const PROJECTWM_PATH = ['PROJECTwm'];
|
|
22
27
|
export function parseVbaProject(bin, maxOutput = DEFAULT_MAX_PROJECT_OUTPUT) {
|
|
23
28
|
return parseVbaProjectIn(new CompoundFile(bin), maxOutput);
|
|
24
29
|
}
|
|
25
30
|
export function parseVbaProjectIn(cfb, maxOutput = DEFAULT_MAX_PROJECT_OUTPUT) {
|
|
26
31
|
const budget = new DecompressionBudget(maxOutput);
|
|
27
|
-
const dirCompressed = cfb.readStream(
|
|
32
|
+
const dirCompressed = cfb.readStream(DIR_PATH);
|
|
28
33
|
if (!dirCompressed)
|
|
29
34
|
throw new VbaParseError("VBA project has no 'dir' stream");
|
|
30
35
|
const dir = budget.spend(dirCompressed, 0);
|
|
@@ -78,9 +83,10 @@ export function parseVbaProjectIn(cfb, maxOutput = DEFAULT_MAX_PROJECT_OUTPUT) {
|
|
|
78
83
|
return { codePage, modules };
|
|
79
84
|
}
|
|
80
85
|
function readModuleSource(cfb, streamName, textOffset, decoder, budget) {
|
|
81
|
-
const stream = cfb.readStream(streamName);
|
|
82
|
-
if (!stream)
|
|
83
|
-
throw new VbaParseError(`module stream ${quoted(streamName)} not found in
|
|
86
|
+
const stream = cfb.readStream([VBA_STORAGE, streamName]);
|
|
87
|
+
if (!stream) {
|
|
88
|
+
throw new VbaParseError(`module stream ${quoted(streamName)} not found in the ${quoted(VBA_STORAGE)} storage`);
|
|
89
|
+
}
|
|
84
90
|
return decoder.decode(budget.spend(stream, textOffset));
|
|
85
91
|
}
|
|
86
92
|
const DEFAULT_MAX_PROJECT_OUTPUT = 64 * 1024 * 1024;
|
|
@@ -103,7 +109,7 @@ const MODULE_KIND_BY_PROJECT_KEYWORD = new Map([
|
|
|
103
109
|
]);
|
|
104
110
|
function readProjectStreamKinds(cfb, decoder) {
|
|
105
111
|
const kinds = new Map();
|
|
106
|
-
const stream = cfb.readStream(
|
|
112
|
+
const stream = cfb.readStream(PROJECT_PATH);
|
|
107
113
|
if (!stream)
|
|
108
114
|
return kinds;
|
|
109
115
|
const text = decoder.decode(stream);
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Validate a library reference's name against the VBA identifier contract: a valid identifier, at
|
|
3
|
+
* most 31 characters (the CFB stream-name limit, which VBA also applies to its names). The reference
|
|
4
|
+
* is the one new name {@link project-editor.ts | project-editor}'s structural edits write.
|
|
5
5
|
*
|
|
6
6
|
* @throws {VbaAuthorError} if `name` is not a valid VBA identifier or exceeds 31 characters.
|
|
7
7
|
*/
|
|
8
|
-
export declare function
|
|
9
|
-
/** Append one `dir`-stream TLV record (Id, Size, data) to `out`. */
|
|
10
|
-
export declare function push(out: number[], id: number, data: number[]): void;
|
|
11
|
-
export declare function u16(n: number): number[];
|
|
12
|
-
export declare function u32(n: number): number[];
|
|
13
|
-
/** A name as NUL-free UTF-16LE code units: the encoding [MS-OVBA] uses for every "Unicode" name field. */
|
|
14
|
-
export declare function utf16le(s: string): number[];
|
|
8
|
+
export declare function validateReferenceName(name: string): void;
|
package/dist/vba/vba-encoding.js
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
import { utf16leBytes } from '../bytes.js';
|
|
2
1
|
import { quoted } from '../errors.js';
|
|
3
2
|
import { MAX_NAME_CHARS } from './cfb-format.js';
|
|
4
3
|
import { VbaAuthorError } from './errors.js';
|
|
5
4
|
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
6
|
-
export function
|
|
5
|
+
export function validateReferenceName(name) {
|
|
7
6
|
if (!IDENTIFIER.test(name) || name.length > MAX_NAME_CHARS) {
|
|
8
|
-
throw new VbaAuthorError(`invalid
|
|
7
|
+
throw new VbaAuthorError(`invalid reference name ${quoted(name)} (must be a VBA identifier ≤ 31 chars)`);
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
|
-
export function push(out, id, data) {
|
|
12
|
-
out.push(...u16(id), ...u32(data.length), ...data);
|
|
13
|
-
}
|
|
14
|
-
export function u16(n) {
|
|
15
|
-
return [n & 0xff, (n >> 8) & 0xff];
|
|
16
|
-
}
|
|
17
|
-
export function u32(n) {
|
|
18
|
-
return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff];
|
|
19
|
-
}
|
|
20
|
-
export function utf16le(s) {
|
|
21
|
-
return [...utf16leBytes(s)];
|
|
22
|
-
}
|
package/dist/xml/xml-read.d.ts
CHANGED
|
@@ -94,6 +94,34 @@ export interface ElementRange {
|
|
|
94
94
|
* range is not a range.
|
|
95
95
|
*/
|
|
96
96
|
export declare function elementRange(source: string, path: readonly string[]): ElementRange | undefined;
|
|
97
|
+
/** A tag as {@link tagRanges} yields it. */
|
|
98
|
+
export interface TagRange {
|
|
99
|
+
/** Offset of the tag's `<`. */
|
|
100
|
+
readonly start: number;
|
|
101
|
+
/** One past the tag's `>`. */
|
|
102
|
+
readonly end: number;
|
|
103
|
+
/** The name as written, namespace prefix included. */
|
|
104
|
+
readonly name: string;
|
|
105
|
+
/** The parsed attributes of an opening tag; empty for a closing one. */
|
|
106
|
+
readonly attrs: XmlAttributes;
|
|
107
|
+
readonly close: boolean;
|
|
108
|
+
readonly selfClosing: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Every tag in `source`, opening and closing, as offsets into the source: for an edit that rewrites a
|
|
112
|
+
* tag wherever it sits rather than one element a path names, such as translating the namespaces a
|
|
113
|
+
* part declares. A caller that resolves names keeps its own `NamespaceScope` in step, opening it on
|
|
114
|
+
* each opening tag and closing it on each closing one and after each self-closing one.
|
|
115
|
+
*/
|
|
116
|
+
export declare function tagRanges(source: string): Generator<TagRange>;
|
|
117
|
+
/**
|
|
118
|
+
* Locate every element with the local name `local`, at any depth, as offsets into the source, in
|
|
119
|
+
* document order: {@link elementRange} for a part that repeats the element an edit is after, such as
|
|
120
|
+
* the `<c:f>` behind each of a chart's series. A same-named element nested in a match is part of that
|
|
121
|
+
* match's content rather than a range of its own. An element that never closes throws
|
|
122
|
+
* {@link XmlParseError}.
|
|
123
|
+
*/
|
|
124
|
+
export declare function elementRanges(source: string, local: string): Generator<ElementRange>;
|
|
97
125
|
/** An element start surfaced by {@link openElements}: its qualified `name`, the namespace-stripped
|
|
98
126
|
* `local` name the filter matched on, and its already-decoded `attrs`. */
|
|
99
127
|
export interface OpenElement {
|
|
@@ -188,7 +216,7 @@ export declare function parseXml(source: string, handlers: SaxHandlers, options?
|
|
|
188
216
|
* The other thing the open-coded versions disagreed on is what an unrelated element opening
|
|
189
217
|
* mid-capture should do. Ending the capture is never what a caller wants: the text belongs to the
|
|
190
218
|
* element that opened it, and a nested or sibling element is not that element. So an open that is
|
|
191
|
-
* not for a captured name leaves
|
|
219
|
+
* not for a captured name leaves a capture in progress alone, and {@link close} answers only for
|
|
192
220
|
* the element that started it.
|
|
193
221
|
*
|
|
194
222
|
* Decoding stays outside. A `<t>` needs `decodeSpreadsheetText` over the whole element and never
|
package/dist/xml/xml-read.js
CHANGED
|
@@ -132,6 +132,62 @@ export function elementRange(source, path) {
|
|
|
132
132
|
}
|
|
133
133
|
return undefined;
|
|
134
134
|
}
|
|
135
|
+
const NO_ATTRIBUTES = Object.freeze(Object.create(null));
|
|
136
|
+
export function* tagRanges(source) {
|
|
137
|
+
for (const { lt, tag } of rawTags(source)) {
|
|
138
|
+
yield {
|
|
139
|
+
start: lt,
|
|
140
|
+
end: tag.next,
|
|
141
|
+
name: tag.name,
|
|
142
|
+
attrs: tag.close ? NO_ATTRIBUTES : parseAttributes(tag.attrSource),
|
|
143
|
+
close: tag.close,
|
|
144
|
+
selfClosing: tag.selfClosing,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function* elementRanges(source, local) {
|
|
149
|
+
let pending;
|
|
150
|
+
let depth = 0;
|
|
151
|
+
for (const { lt, tag, local: tagLocal } of rawTags(source)) {
|
|
152
|
+
if (tagLocal !== local)
|
|
153
|
+
continue;
|
|
154
|
+
if (pending !== undefined) {
|
|
155
|
+
if (tag.close) {
|
|
156
|
+
if (depth > 0)
|
|
157
|
+
depth -= 1;
|
|
158
|
+
else {
|
|
159
|
+
const open = pending;
|
|
160
|
+
pending = undefined;
|
|
161
|
+
yield { ...open, end: tag.next, contentEnd: lt };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (!tag.selfClosing) {
|
|
165
|
+
depth += 1;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (tag.close)
|
|
170
|
+
continue;
|
|
171
|
+
const attrs = parseAttributes(tag.attrSource);
|
|
172
|
+
if (tag.selfClosing) {
|
|
173
|
+
yield {
|
|
174
|
+
start: lt,
|
|
175
|
+
end: tag.next,
|
|
176
|
+
contentStart: tag.next,
|
|
177
|
+
contentEnd: tag.next,
|
|
178
|
+
name: tag.name,
|
|
179
|
+
attrs,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
pending = { start: lt, contentStart: tag.next, name: tag.name, attrs };
|
|
184
|
+
depth = 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (pending !== undefined) {
|
|
188
|
+
throw new XmlParseError(`unterminated <${pending.name}> element`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
135
191
|
export function* openElements(source, ...localNames) {
|
|
136
192
|
const filter = localNames.length > 0 ? new Set(localNames) : undefined;
|
|
137
193
|
const scope = new NamespaceScope();
|
package/dist/xml/xml-scan.d.ts
CHANGED
|
@@ -35,6 +35,19 @@ export type XmlEvent = {
|
|
|
35
35
|
* codec keeps importing none of the serialisation vocabulary.
|
|
36
36
|
*/
|
|
37
37
|
export declare function decodeEntities(value: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* A tag's attributes: each a name, then `=`, then a value in either quote style. Matching the quotes
|
|
40
|
+
* is what lets a delimiter-respecting scan find a tag's end even when a value holds a `>` (legal but
|
|
41
|
+
* rare). Names may carry a namespace prefix (`r:id`, `xml:space`). Anything that is not an attribute,
|
|
42
|
+
* such as a name nothing assigns or a quote never closed, is skipped rather than refused.
|
|
43
|
+
*
|
|
44
|
+
* Linear in the source, and that is the point of it being hand-written. The regex it replaced
|
|
45
|
+
* backtracked over a run of name characters no `=` followed from every position inside the run, so a
|
|
46
|
+
* single junk token in a tag cost the square of its length: a megabyte of one letter, a kilobyte
|
|
47
|
+
* zipped, cost minutes inside either reader. Here a dropped token resumes the scan where the token
|
|
48
|
+
* ended, and a value's closing quote is found with one `indexOf`, so no character is visited more
|
|
49
|
+
* than a constant number of times.
|
|
50
|
+
*/
|
|
38
51
|
export declare function parseAttributes(source: string): XmlAttributes;
|
|
39
52
|
export type Markup = {
|
|
40
53
|
readonly kind: 'comment' | 'pi' | 'declaration';
|
package/dist/xml/xml-scan.js
CHANGED
|
@@ -26,18 +26,69 @@ export function decodeEntities(value) {
|
|
|
26
26
|
function admitText(value) {
|
|
27
27
|
return stripUnrepresentable(decodeEntities(value));
|
|
28
28
|
}
|
|
29
|
-
const
|
|
29
|
+
const EQUALS = 0x3d;
|
|
30
|
+
const SLASH = 0x2f;
|
|
31
|
+
const GREATER_THAN = 0x3e;
|
|
32
|
+
const DOUBLE_QUOTE = 0x22;
|
|
33
|
+
const SINGLE_QUOTE = 0x27;
|
|
34
|
+
const WIDE_WHITESPACE = /\s/;
|
|
35
|
+
function isScanWhitespace(code) {
|
|
36
|
+
if (code < 0x80)
|
|
37
|
+
return code === 0x20 || (code >= 0x09 && code <= 0x0d);
|
|
38
|
+
return WIDE_WHITESPACE.test(String.fromCharCode(code));
|
|
39
|
+
}
|
|
40
|
+
function skipScanWhitespace(source, from) {
|
|
41
|
+
let i = from;
|
|
42
|
+
while (i < source.length && isScanWhitespace(source.charCodeAt(i)))
|
|
43
|
+
i++;
|
|
44
|
+
return i;
|
|
45
|
+
}
|
|
46
|
+
function endsName(code) {
|
|
47
|
+
if (code > GREATER_THAN && code < 0xa0)
|
|
48
|
+
return false;
|
|
49
|
+
return isScanWhitespace(code) || code === EQUALS || code === SLASH || code === GREATER_THAN;
|
|
50
|
+
}
|
|
30
51
|
export function parseAttributes(source) {
|
|
31
52
|
const attrs = Object.create(null);
|
|
32
|
-
|
|
33
|
-
let
|
|
34
|
-
while (
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
const length = source.length;
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < length) {
|
|
56
|
+
const nameStart = i;
|
|
57
|
+
while (i < length && !endsName(source.charCodeAt(i)))
|
|
58
|
+
i++;
|
|
59
|
+
if (i === nameStart) {
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
let cursor = skipScanWhitespace(source, i);
|
|
64
|
+
if (source.charCodeAt(cursor) !== EQUALS) {
|
|
65
|
+
i = cursor;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
cursor = skipScanWhitespace(source, cursor + 1);
|
|
69
|
+
const quote = source.charCodeAt(cursor);
|
|
70
|
+
const close = quote === DOUBLE_QUOTE || quote === SINGLE_QUOTE
|
|
71
|
+
? source.indexOf(quote === DOUBLE_QUOTE ? '"' : "'", cursor + 1)
|
|
72
|
+
: -1;
|
|
73
|
+
if (close === -1) {
|
|
74
|
+
i = cursor;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
attrs[source.slice(nameStart, i)] = admitText(normalizeAttributeWhitespace(source.slice(cursor + 1, close)));
|
|
78
|
+
i = close + 1;
|
|
38
79
|
}
|
|
39
80
|
return attrs;
|
|
40
81
|
}
|
|
82
|
+
const ATTRIBUTE_WHITESPACE = /\r\n|[\t\n\r]/g;
|
|
83
|
+
function normalizeAttributeWhitespace(raw) {
|
|
84
|
+
for (let i = 0; i < raw.length; i++) {
|
|
85
|
+
const code = raw.charCodeAt(i);
|
|
86
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
87
|
+
return raw.replace(ATTRIBUTE_WHITESPACE, ' ');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return raw;
|
|
91
|
+
}
|
|
41
92
|
function findTagEnd(source, start) {
|
|
42
93
|
let quote = '';
|
|
43
94
|
for (let i = start + 1; i < source.length; i++) {
|
package/dist/xml/xml.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function escapeFormatCode(code: string): string;
|
|
|
31
31
|
* `escapeText` runs last and sees no unrepresentable character left, so its guard is a
|
|
32
32
|
* standing proof that the escape was complete rather than a second check of the same thing.
|
|
33
33
|
*
|
|
34
|
-
* Reversed by `decodeSpreadsheetText` in `./xml-
|
|
34
|
+
* Reversed by `decodeSpreadsheetText` in `./xml-attrs.ts`, whose single left-to-right pass is what
|
|
35
35
|
* makes the `_x005F_` step above reversible. Change either and read its comment first.
|
|
36
36
|
*/
|
|
37
37
|
export declare function escapeSpreadsheetText(value: string): string;
|
|
@@ -42,6 +42,15 @@ export declare function escapeSpreadsheetText(value: string): string;
|
|
|
42
42
|
* identically on the way back.
|
|
43
43
|
*/
|
|
44
44
|
export declare function textElement(value: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Re-render an opening tag, `<name …>` or the empty `<name …/>`, from its parsed name and attributes,
|
|
47
|
+
* for an edit that changes an attribute of an element a part already carries. Attribute order is the
|
|
48
|
+
* scanner's, which is the source's, so an element whose attributes an edit merges over keeps them
|
|
49
|
+
* where they were; only the quoting and the entity spelling become this writer's.
|
|
50
|
+
*/
|
|
51
|
+
export declare function startTag(name: string, attrs: {
|
|
52
|
+
readonly [attribute: string]: string | undefined;
|
|
53
|
+
}, selfClosing: boolean): string;
|
|
45
54
|
/**
|
|
46
55
|
* A boolean attribute rendered with a leading space (` name="1"` / ` name="0"`), or '' when the value
|
|
47
56
|
* is undefined. OOXML booleans serialise as 1/0; emitting the explicit `="0"` lets a writer force a
|
|
@@ -83,6 +92,29 @@ export declare function checkedToken(value: string, isValid: (candidate: string)
|
|
|
83
92
|
* @throws {AuthoringError} when the value is not finite.
|
|
84
93
|
*/
|
|
85
94
|
export declare function numAttr(name: string, value: number | undefined): string;
|
|
95
|
+
/**
|
|
96
|
+
* An integer attribute rendered with a leading space (` name="3"`), or '' when the value is undefined.
|
|
97
|
+
*
|
|
98
|
+
* For an `xsd:int` or `xsd:unsignedInt` attribute, whose lexical space has no spelling for a fraction
|
|
99
|
+
* any more than for a NaN: `priority="1.5"` and `rank="-1"` make a package the schema rejects, so the
|
|
100
|
+
* value is refused here rather than written. `min` is the floor the attribute's type implies, `0` for
|
|
101
|
+
* an unsigned one.
|
|
102
|
+
*
|
|
103
|
+
* @throws {AuthoringError} when the value is not finite, not a safe integer, or below `min`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function intAttr(name: string, value: number | undefined, min?: number): string;
|
|
106
|
+
/**
|
|
107
|
+
* A numeric attribute written only when it differs from its schema default, with a leading space, or
|
|
108
|
+
* '' when the value is undefined or equals `defaultValue`.
|
|
109
|
+
*
|
|
110
|
+
* The number is checked before it is compared, because the two answer different questions: whether
|
|
111
|
+
* the value can be spelled, and whether it is worth spelling. Tested for truthiness instead, `NaN` is
|
|
112
|
+
* falsy and was omitted as if it were a default of zero, so a gradient fill with `degree: NaN` was
|
|
113
|
+
* written as an ordinary gradient rather than refused.
|
|
114
|
+
*
|
|
115
|
+
* @throws {AuthoringError} when the value is not finite.
|
|
116
|
+
*/
|
|
117
|
+
export declare function nonDefaultNumAttr(name: string, value: number | undefined, defaultValue: number): string;
|
|
86
118
|
/**
|
|
87
119
|
* Refuse a `Date` OOXML cannot spell, naming the property that carries it.
|
|
88
120
|
*
|