@shbernal/ts-xlsx 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- 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 +2 -4
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +3 -0
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +9 -1
- package/dist/core/conditional-formatting.js +34 -18
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +6 -0
- package/dist/core/data-validation.js +15 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +13 -9
- package/dist/core/image.js +7 -5
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +5 -4
- package/dist/core/merge.js +18 -19
- package/dist/core/pivot-table.js +63 -45
- package/dist/core/protection.js +2 -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 +3 -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 +27 -9
- package/dist/core/table.js +51 -34
- package/dist/core/theme.js +6 -5
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +55 -6
- package/dist/core/worksheet.js +36 -44
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- 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/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- 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 +1 -1
- package/dist/io/opc/read-opc.d.ts +54 -14
- package/dist/io/opc/read-opc.js +42 -20
- 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/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 +49 -34
- 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 +385 -596
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +44 -23
- 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 +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -32
- package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
- package/dist/io/xlsx/conditional-formatting.js +30 -26
- package/dist/io/xlsx/data-validation.js +29 -27
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +14 -21
- 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 +37 -5
- package/dist/io/xlsx/hyperlinks.js +22 -13
- package/dist/io/xlsx/images.js +11 -13
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +14 -2
- package/dist/io/xlsx/package-plan.js +39 -11
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +45 -31
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +6 -71
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +2 -1
- package/dist/io/xlsx/read-worksheet.js +35 -164
- package/dist/io/xlsx/read.d.ts +5 -12
- package/dist/io/xlsx/read.js +73 -406
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -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 +145 -9
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -207
- package/dist/io/xlsx/tables.js +10 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +60 -34
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +48 -229
- package/dist/io/xlsx/write-stream.d.ts +65 -16
- package/dist/io/xlsx/write-stream.js +50 -20
- package/dist/io/xlsx/write.d.ts +1 -2
- package/dist/io/xlsx/write.js +114 -74
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +8 -0
- package/dist/vba/cfb-format.js +1 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +12 -9
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -51
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +51 -16
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +27 -8
- package/dist/vba/vba-encoding.js +4 -5
- 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 +72 -10
- package/dist/xml/xml-read.js +94 -18
- package/dist/xml/xml-scan.d.ts +15 -37
- package/dist/xml/xml-scan.js +12 -40
- package/dist/xml/xml.d.ts +28 -27
- package/dist/xml/xml.js +24 -24
- package/package.json +12 -7
- /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
package/dist/xml/xml-read.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { XmlParseError } from './errors.js';
|
|
2
|
+
import { NamespaceScope } from './xml-namespaces.js';
|
|
2
3
|
import { localName, markupAt, parseAttributes, tagAt, xmlEvents, } from './xml-scan.js';
|
|
3
|
-
|
|
4
|
-
const fragments = new Map();
|
|
5
|
-
const attributes = new Map();
|
|
6
|
-
const finished = new Set();
|
|
7
|
-
let container;
|
|
8
|
-
let capture;
|
|
4
|
+
function* rawTags(source) {
|
|
9
5
|
const length = source.length;
|
|
10
6
|
let i = 0;
|
|
11
7
|
while (i < length) {
|
|
@@ -18,7 +14,17 @@ export function elementSubtrees(source, selection) {
|
|
|
18
14
|
continue;
|
|
19
15
|
}
|
|
20
16
|
const tag = tagAt(source, lt);
|
|
21
|
-
|
|
17
|
+
i = tag.next;
|
|
18
|
+
yield { lt, tag, local: localName(tag.name) };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function elementSubtrees(source, selection) {
|
|
22
|
+
const fragments = new Map();
|
|
23
|
+
const attributes = new Map();
|
|
24
|
+
const finished = new Set();
|
|
25
|
+
let container;
|
|
26
|
+
let capture;
|
|
27
|
+
for (const { lt, tag, local } of rawTags(source)) {
|
|
22
28
|
if (tag.close) {
|
|
23
29
|
if (capture !== undefined && local === capture.local) {
|
|
24
30
|
if (capture.depth > 0)
|
|
@@ -36,7 +42,6 @@ export function elementSubtrees(source, selection) {
|
|
|
36
42
|
container = undefined;
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
|
-
i = tag.next;
|
|
40
45
|
continue;
|
|
41
46
|
}
|
|
42
47
|
const { selfClosing } = tag;
|
|
@@ -66,22 +71,84 @@ export function elementSubtrees(source, selection) {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
|
-
i = tag.next;
|
|
70
74
|
}
|
|
71
75
|
if (capture !== undefined) {
|
|
72
76
|
throw new XmlParseError(`unterminated <${capture.local}> element`);
|
|
73
77
|
}
|
|
74
78
|
return { fragments, attributes };
|
|
75
79
|
}
|
|
80
|
+
export function elementRange(source, path) {
|
|
81
|
+
const open = [];
|
|
82
|
+
let pending;
|
|
83
|
+
for (const { lt, tag, local } of rawTags(source)) {
|
|
84
|
+
if (tag.close) {
|
|
85
|
+
const innermost = open.at(-1);
|
|
86
|
+
if (innermost !== undefined && innermost.local === local) {
|
|
87
|
+
if (innermost.depth > 0)
|
|
88
|
+
innermost.depth -= 1;
|
|
89
|
+
else {
|
|
90
|
+
open.pop();
|
|
91
|
+
if (pending !== undefined && open.length === path.length - 1) {
|
|
92
|
+
return { ...pending, end: tag.next, contentEnd: lt };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (pending === undefined && local === path[open.length]) {
|
|
99
|
+
const last = open.length === path.length - 1;
|
|
100
|
+
if (last) {
|
|
101
|
+
if (tag.selfClosing) {
|
|
102
|
+
return {
|
|
103
|
+
start: lt,
|
|
104
|
+
end: tag.next,
|
|
105
|
+
contentStart: tag.next,
|
|
106
|
+
contentEnd: tag.next,
|
|
107
|
+
name: tag.name,
|
|
108
|
+
attrs: parseAttributes(tag.attrSource),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
pending = {
|
|
112
|
+
start: lt,
|
|
113
|
+
contentStart: tag.next,
|
|
114
|
+
name: tag.name,
|
|
115
|
+
attrs: parseAttributes(tag.attrSource),
|
|
116
|
+
};
|
|
117
|
+
open.push({ local, depth: 0 });
|
|
118
|
+
}
|
|
119
|
+
else if (!tag.selfClosing) {
|
|
120
|
+
open.push({ local, depth: 0 });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const innermost = open.at(-1);
|
|
125
|
+
if (innermost !== undefined && !tag.selfClosing && innermost.local === local) {
|
|
126
|
+
innermost.depth += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (pending !== undefined) {
|
|
131
|
+
throw new XmlParseError(`unterminated <${pending.name}> element`);
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
76
135
|
export function* openElements(source, ...localNames) {
|
|
77
136
|
const filter = localNames.length > 0 ? new Set(localNames) : undefined;
|
|
137
|
+
const scope = new NamespaceScope();
|
|
78
138
|
for (const event of xmlEvents(source)) {
|
|
139
|
+
if (event.kind === 'close') {
|
|
140
|
+
scope.close();
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
79
143
|
if (event.kind !== 'open')
|
|
80
144
|
continue;
|
|
145
|
+
scope.open(event.attrs);
|
|
81
146
|
const local = localName(event.name);
|
|
82
|
-
if (filter
|
|
83
|
-
|
|
84
|
-
|
|
147
|
+
if (filter === undefined || filter.has(local)) {
|
|
148
|
+
yield { name: event.name, local, attrs: event.attrs, scope };
|
|
149
|
+
}
|
|
150
|
+
if (event.selfClosing)
|
|
151
|
+
scope.close();
|
|
85
152
|
}
|
|
86
153
|
}
|
|
87
154
|
export function* closeEmptyElements(events, names) {
|
|
@@ -103,17 +170,17 @@ export function parseXmlPasses(source, passes) {
|
|
|
103
170
|
}
|
|
104
171
|
const handlers = passes.map((pass) => pass.handlers);
|
|
105
172
|
parseXml(source, {
|
|
106
|
-
onOpen(name, attrs, selfClosing) {
|
|
173
|
+
onOpen(name, attrs, selfClosing, scope) {
|
|
107
174
|
for (const handler of handlers)
|
|
108
|
-
handler.onOpen(name, attrs, selfClosing);
|
|
175
|
+
handler.onOpen(name, attrs, selfClosing, scope);
|
|
109
176
|
},
|
|
110
177
|
onText(text) {
|
|
111
178
|
for (const handler of handlers)
|
|
112
179
|
handler.onText?.(text);
|
|
113
180
|
},
|
|
114
|
-
onClose(name) {
|
|
181
|
+
onClose(name, scope) {
|
|
115
182
|
for (const handler of handlers)
|
|
116
|
-
handler.onClose?.(name);
|
|
183
|
+
handler.onClose?.(name, scope);
|
|
117
184
|
},
|
|
118
185
|
}, expanded.size > 0 ? { closeEmptyElements: expanded } : undefined);
|
|
119
186
|
}
|
|
@@ -121,16 +188,21 @@ export function parseXml(source, handlers, options) {
|
|
|
121
188
|
const events = options?.closeEmptyElements
|
|
122
189
|
? closeEmptyElements(xmlEvents(source), options.closeEmptyElements)
|
|
123
190
|
: xmlEvents(source);
|
|
191
|
+
const scope = new NamespaceScope();
|
|
124
192
|
for (const event of events) {
|
|
125
193
|
switch (event.kind) {
|
|
126
194
|
case 'open':
|
|
127
|
-
|
|
195
|
+
scope.open(event.attrs);
|
|
196
|
+
handlers.onOpen(event.name, event.attrs, event.selfClosing, scope);
|
|
197
|
+
if (event.selfClosing)
|
|
198
|
+
scope.close();
|
|
128
199
|
break;
|
|
129
200
|
case 'text':
|
|
130
201
|
handlers.onText?.(event.text);
|
|
131
202
|
break;
|
|
132
203
|
case 'close':
|
|
133
|
-
handlers.onClose?.(event.name);
|
|
204
|
+
handlers.onClose?.(event.name, scope);
|
|
205
|
+
scope.close();
|
|
134
206
|
break;
|
|
135
207
|
}
|
|
136
208
|
}
|
|
@@ -161,6 +233,10 @@ export class TextCapture {
|
|
|
161
233
|
this.#capturing = undefined;
|
|
162
234
|
return this.#text;
|
|
163
235
|
}
|
|
236
|
+
reset() {
|
|
237
|
+
this.#capturing = undefined;
|
|
238
|
+
this.#text = '';
|
|
239
|
+
}
|
|
164
240
|
}
|
|
165
241
|
export function* capturedText(source, names) {
|
|
166
242
|
const capture = new TextCapture(names);
|
package/dist/xml/xml-scan.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export interface XmlAttributes {
|
|
2
2
|
readonly [name: string]: string;
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* One parse event from {@link xmlEvents}. The payloads are what the push adapter in `xml-read.ts`
|
|
6
|
+
* hands a handler triple, unchanged: `text` is already entity-decoded (or verbatim CDATA), and a
|
|
7
|
+
* `<x/>` yields one `open` with `selfClosing: true` and no matching `close`. The discriminated
|
|
8
|
+
* `kind` lets a *pull* consumer drive the parse: the shape the streaming reader needs, where a push
|
|
9
|
+
* callback cannot `yield`.
|
|
10
|
+
*/
|
|
4
11
|
export type XmlEvent = {
|
|
5
12
|
readonly kind: 'open';
|
|
6
13
|
readonly name: string;
|
|
@@ -18,28 +25,16 @@ export type XmlEvent = {
|
|
|
18
25
|
* `&name;` is left verbatim rather than expanded: there is no DTD, so there is nothing
|
|
19
26
|
* to expand it to, and refusing to invent one is what makes entity-expansion attacks
|
|
20
27
|
* impossible.
|
|
21
|
-
*/
|
|
22
|
-
export declare function decodeEntities(value: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* The SpreadsheetML `_xHHHH_` escape, in the only place it may appear: a complete cell-text value.
|
|
25
|
-
*
|
|
26
|
-
* The mirror of `escapeSpreadsheetText` in `./xml.ts`, and it sits here rather than beside it for
|
|
27
|
-
* the same reason `decodeEntities` sits apart from `escapeText`: the write helpers carry an
|
|
28
|
-
* `AuthoringError` and a whole serialisation vocabulary the reader has no business importing.
|
|
29
28
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* The decode is unconditional, not a repair of characters XML cannot carry. Excel reads
|
|
39
|
-
* `a_x0009_b` as a tab even though a literal tab would have been perfectly legal there, so a
|
|
40
|
-
* decoder that only handled the illegal range would disagree with Excel on files Excel wrote.
|
|
29
|
+
* A reference naming a code point XML 1.0 has no representation for is left verbatim too, on the
|
|
30
|
+
* same grounds as one naming no code point at all. `` is not an escape for U+0001, it is another
|
|
31
|
+
* way of spelling an ill-formed document, and decoding it puts in the model a character the writer
|
|
32
|
+
* is *guaranteed* to refuse: a hostile file would then read cleanly and fail on the next save with
|
|
33
|
+
* an `AuthoringError` blaming the caller, several layers from the input that caused it. The bound
|
|
34
|
+
* comes from `./xml-chars.ts` rather than from the writer's own guard so that this half of the
|
|
35
|
+
* codec keeps importing none of the serialisation vocabulary.
|
|
41
36
|
*/
|
|
42
|
-
export declare function
|
|
37
|
+
export declare function decodeEntities(value: string): string;
|
|
43
38
|
export declare function parseAttributes(source: string): XmlAttributes;
|
|
44
39
|
export type Markup = {
|
|
45
40
|
readonly kind: 'comment' | 'pi' | 'declaration';
|
|
@@ -81,20 +76,3 @@ export declare function boolStrict(val: string | undefined): boolean;
|
|
|
81
76
|
* token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
|
|
82
77
|
* attributes the source actually carried, so a re-write stays byte-clean. */
|
|
83
78
|
export declare function boolTristate(val: string | undefined): boolean | undefined;
|
|
84
|
-
/** An OOXML integer attribute at or above `min` (default: unbounded below); `undefined` when the
|
|
85
|
-
* attribute is absent, blank, fractional, not a number, or below the floor. Integers past
|
|
86
|
-
* `Number.MAX_SAFE_INTEGER` read as `undefined` too: no index or count is usable out there, and
|
|
87
|
-
* arithmetic on one silently lies. */
|
|
88
|
-
export declare function numInteger(val: string | undefined, min?: number): number | undefined;
|
|
89
|
-
/** An OOXML decimal attribute at or above `min` (default: unbounded below); `undefined` when the
|
|
90
|
-
* attribute is absent, blank, not a number, or below the floor. Infinities are not finite numbers
|
|
91
|
-
* and read as `undefined`. */
|
|
92
|
-
export declare function numFinite(val: string | undefined, min?: number): number | undefined;
|
|
93
|
-
/** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
|
|
94
|
-
* digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
|
|
95
|
-
* number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
|
|
96
|
-
* re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
|
|
97
|
-
* (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
|
|
98
|
-
export declare function coerceNumericLiteral(text: string): string | number;
|
|
99
|
-
/** Narrow an enumerated attribute through its guard; `undefined` when absent or not a member. */
|
|
100
|
-
export declare function enumToken<T extends string>(val: string | undefined, isMember: (candidate: string) => candidate is T): T | undefined;
|
package/dist/xml/xml-scan.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { XmlParseError } from './errors.js';
|
|
2
|
+
import { isRepresentableCodePoint, stripUnrepresentable } from './xml-chars.js';
|
|
2
3
|
const PREDEFINED_ENTITIES = new Map([
|
|
3
4
|
['amp', '&'],
|
|
4
5
|
['lt', '<'],
|
|
@@ -15,22 +16,15 @@ export function decodeEntities(value) {
|
|
|
15
16
|
const codePoint = body.charCodeAt(1) === 0x78
|
|
16
17
|
? Number.parseInt(body.slice(2), 16)
|
|
17
18
|
: Number.parseInt(body.slice(1), 10);
|
|
18
|
-
if (!
|
|
19
|
+
if (!isRepresentableCodePoint(codePoint))
|
|
19
20
|
return match;
|
|
20
|
-
|
|
21
|
-
return String.fromCodePoint(codePoint);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return match;
|
|
25
|
-
}
|
|
21
|
+
return String.fromCodePoint(codePoint);
|
|
26
22
|
}
|
|
27
23
|
return PREDEFINED_ENTITIES.get(body) ?? match;
|
|
28
24
|
});
|
|
29
25
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return value;
|
|
33
|
-
return value.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
|
|
26
|
+
function admitText(value) {
|
|
27
|
+
return stripUnrepresentable(decodeEntities(value));
|
|
34
28
|
}
|
|
35
29
|
const ATTRIBUTE = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
36
30
|
export function parseAttributes(source) {
|
|
@@ -39,7 +33,7 @@ export function parseAttributes(source) {
|
|
|
39
33
|
let match = ATTRIBUTE.exec(source);
|
|
40
34
|
while (match !== null) {
|
|
41
35
|
const value = match[2] ?? match[3] ?? '';
|
|
42
|
-
attrs[match[1]] =
|
|
36
|
+
attrs[match[1]] = admitText(value);
|
|
43
37
|
match = ATTRIBUTE.exec(source);
|
|
44
38
|
}
|
|
45
39
|
return attrs;
|
|
@@ -123,18 +117,21 @@ export function* xmlEvents(source) {
|
|
|
123
117
|
if (lt === -1) {
|
|
124
118
|
const chunk = source.slice(i);
|
|
125
119
|
if (chunk.length > 0)
|
|
126
|
-
yield { kind: 'text', text:
|
|
120
|
+
yield { kind: 'text', text: admitText(normalizeLineEndings(chunk)) };
|
|
127
121
|
return;
|
|
128
122
|
}
|
|
129
123
|
if (lt > i) {
|
|
130
124
|
const chunk = source.slice(i, lt);
|
|
131
125
|
if (chunk.length > 0)
|
|
132
|
-
yield { kind: 'text', text:
|
|
126
|
+
yield { kind: 'text', text: admitText(normalizeLineEndings(chunk)) };
|
|
133
127
|
}
|
|
134
128
|
const markup = markupAt(source, lt);
|
|
135
129
|
if (markup !== undefined) {
|
|
136
130
|
if (markup.kind === 'cdata') {
|
|
137
|
-
yield {
|
|
131
|
+
yield {
|
|
132
|
+
kind: 'text',
|
|
133
|
+
text: stripUnrepresentable(source.slice(markup.contentStart, markup.contentEnd)),
|
|
134
|
+
};
|
|
138
135
|
}
|
|
139
136
|
i = markup.next;
|
|
140
137
|
continue;
|
|
@@ -181,28 +178,3 @@ export function boolTristate(val) {
|
|
|
181
178
|
return false;
|
|
182
179
|
return undefined;
|
|
183
180
|
}
|
|
184
|
-
export function numInteger(val, min = -Number.MAX_SAFE_INTEGER) {
|
|
185
|
-
const n = parseAttrNumber(val);
|
|
186
|
-
if (n === undefined || !Number.isSafeInteger(n) || n < min)
|
|
187
|
-
return undefined;
|
|
188
|
-
return n;
|
|
189
|
-
}
|
|
190
|
-
export function numFinite(val, min = -Infinity) {
|
|
191
|
-
const n = parseAttrNumber(val);
|
|
192
|
-
if (n === undefined || n < min)
|
|
193
|
-
return undefined;
|
|
194
|
-
return n;
|
|
195
|
-
}
|
|
196
|
-
function parseAttrNumber(val) {
|
|
197
|
-
if (val === undefined || val.trim() === '')
|
|
198
|
-
return undefined;
|
|
199
|
-
const n = Number(val);
|
|
200
|
-
return Number.isFinite(n) ? n : undefined;
|
|
201
|
-
}
|
|
202
|
-
export function coerceNumericLiteral(text) {
|
|
203
|
-
const trimmed = text.trim();
|
|
204
|
-
return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;
|
|
205
|
-
}
|
|
206
|
-
export function enumToken(val, isMember) {
|
|
207
|
-
return val !== undefined && isMember(val) ? val : undefined;
|
|
208
|
-
}
|
package/dist/xml/xml.d.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Refuse a string that XML cannot carry, naming the character and where it is so the author
|
|
3
|
-
* can find it in a value they never inspected. These arrive from a database column or a CSV
|
|
4
|
-
* field, not from a literal in the calling code.
|
|
5
|
-
*
|
|
6
|
-
* Exported for the one escape that cannot be {@link escapeAttr}: a number format code escapes
|
|
7
|
-
* everything but the apostrophe, and needs this guard just the same.
|
|
8
|
-
*
|
|
9
|
-
* @throws {AuthoringError} naming the code point and its offset.
|
|
10
|
-
*/
|
|
11
|
-
export declare function assertRepresentable(value: string): void;
|
|
12
1
|
/** Escape a string for use as XML element text. */
|
|
13
2
|
export declare function escapeText(value: string): string;
|
|
14
3
|
/** Escape a string for use inside a double-quoted XML attribute value. */
|
|
15
4
|
export declare function escapeAttr(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* {@link escapeAttr} for a number format code: the same escapes minus the apostrophe.
|
|
7
|
+
*
|
|
8
|
+
* A format code can legitimately contain `"` (a quoted literal like `"$"`), `<` and `&`, and Excel
|
|
9
|
+
* writes one with bare apostrophes. `'` is not markup-significant inside a double-quoted attribute,
|
|
10
|
+
* so leaving it keeps a round-tripped code byte-identical to the source; that divergence is the whole
|
|
11
|
+
* reason this exists.
|
|
12
|
+
*
|
|
13
|
+
* It is built by *omitting* one entry from {@link ATTR_ESCAPES} rather than by listing the escapes it
|
|
14
|
+
* does want, which is how it used to be written, in `io/xlsx/style-elements.ts`. An independently
|
|
15
|
+
* maintained list does not stay a one-character divergence: that one silently dropped the tab, line
|
|
16
|
+
* feed and carriage return escapes as well, and XML 1.0 3.3.3 requires a parser to normalise all
|
|
17
|
+
* three in an attribute value to a space, so Excel read back a format code with a space where the
|
|
18
|
+
* author wrote a tab. Our own reader preserved the raw character, so a round trip through this
|
|
19
|
+
* library returned it unchanged and no test here could see the loss.
|
|
20
|
+
*/
|
|
21
|
+
export declare function escapeFormatCode(code: string): string;
|
|
16
22
|
/**
|
|
17
23
|
* Escape a cell value: the `_xHHHH_` convention first, then XML's own escapes.
|
|
18
24
|
*
|
|
@@ -36,13 +42,6 @@ export declare function escapeSpreadsheetText(value: string): string;
|
|
|
36
42
|
* identically on the way back.
|
|
37
43
|
*/
|
|
38
44
|
export declare function textElement(value: string): string;
|
|
39
|
-
/**
|
|
40
|
-
* Render a formula operand for serialisation: a number becomes its literal, a string is stripped of
|
|
41
|
-
* the single optional leading '=' an author may write (OOXML stores the expression without it, e.g.
|
|
42
|
-
* `=A1>0` on disk is `A1>0`). The result is unescaped: the caller escapes it for its target,
|
|
43
|
-
* whether that is element text or an attribute value.
|
|
44
|
-
*/
|
|
45
|
-
export declare function stripFormulaEquals(value: string | number): string;
|
|
46
45
|
/**
|
|
47
46
|
* A boolean attribute rendered with a leading space (` name="1"` / ` name="0"`), or '' when the value
|
|
48
47
|
* is undefined. OOXML booleans serialise as 1/0; emitting the explicit `="0"` lets a writer force a
|
|
@@ -85,18 +84,20 @@ export declare function checkedToken(value: string, isValid: (candidate: string)
|
|
|
85
84
|
*/
|
|
86
85
|
export declare function numAttr(name: string, value: number | undefined): string;
|
|
87
86
|
/**
|
|
88
|
-
* Refuse a
|
|
89
|
-
* `xsd:unsignedInt` or a bounded flavour of one, and none of those lexical spaces has a form for a
|
|
90
|
-
* NaN or an infinity, so a value that reaches the file as `NaN` is a package Excel reports as
|
|
91
|
-
* damaged.
|
|
87
|
+
* Refuse a `Date` OOXML cannot spell, naming the property that carries it.
|
|
92
88
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
89
|
+
* Two ways a `Date` fails to have a `dcterms:W3CDTF` / `xsd:dateTime` spelling, and neither is
|
|
90
|
+
* caught by the types. An **Invalid Date** is truthy and is an instance of `Date`, so it passes
|
|
91
|
+
* every guard short of this one and reaches `toISOString()`, which throws a bare
|
|
92
|
+
* `RangeError: Invalid time value` -- outside this taxonomy, and naming neither the property nor
|
|
93
|
+
* the document it was being written into. A year **outside 0000-9999** has no four-digit form, so
|
|
94
|
+
* `toISOString()` falls back to ISO 8601's expanded `+275760-09-13T…` notation, which is not in
|
|
95
|
+
* the lexical space of either type; that one does not throw at all, it writes a package Excel
|
|
96
|
+
* offers to repair.
|
|
96
97
|
*
|
|
97
|
-
* @throws {AuthoringError} naming the
|
|
98
|
+
* @throws {AuthoringError} naming the property.
|
|
98
99
|
*/
|
|
99
|
-
export declare function
|
|
100
|
+
export declare function assertWritableDate(date: Date, property: string): void;
|
|
100
101
|
/**
|
|
101
102
|
* A finite number serialises as its shortest round-trippable decimal; a non-finite one
|
|
102
103
|
* has no OOXML numeric representation, so the writer refuses it rather than emit `NaN`.
|
package/dist/xml/xml.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { assertWritableNumber, AuthoringError, codePointHex, invalidToken, unrepresentable, } from '../errors.js';
|
|
2
|
+
import { XML_UNREPRESENTABLE, XML_UNREPRESENTABLE_GLOBAL } from './xml-chars.js';
|
|
2
3
|
const TEXT_ESCAPES = {
|
|
3
4
|
'&': '&',
|
|
4
5
|
'<': '<',
|
|
@@ -14,18 +15,12 @@ const ATTR_ESCAPES = {
|
|
|
14
15
|
'\r': ' ',
|
|
15
16
|
'\t': '	',
|
|
16
17
|
};
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const found = UNREPRESENTABLE.exec(value);
|
|
24
|
-
if (found === null)
|
|
25
|
-
return;
|
|
26
|
-
throw new AuthoringError(`cannot write U+${codePointHex(value.codePointAt(found.index))} at offset ${found.index}: ` +
|
|
27
|
-
'XML 1.0 has no representation for it, and the _xHHHH_ escape that would carry it is a ' +
|
|
28
|
-
'convention of cell values only');
|
|
18
|
+
const XML_REFUSAL = 'XML 1.0 has no representation for it, and the _xHHHH_ escape that would carry it is a ' +
|
|
19
|
+
'convention of cell values only';
|
|
20
|
+
function assertRepresentable(value) {
|
|
21
|
+
const error = unrepresentable(value, XML_UNREPRESENTABLE, XML_REFUSAL);
|
|
22
|
+
if (error !== undefined)
|
|
23
|
+
throw error;
|
|
29
24
|
}
|
|
30
25
|
export function escapeText(value) {
|
|
31
26
|
assertRepresentable(value);
|
|
@@ -35,10 +30,14 @@ export function escapeAttr(value) {
|
|
|
35
30
|
assertRepresentable(value);
|
|
36
31
|
return value.replace(/[&<>"'\n\r\t]/g, (ch) => ATTR_ESCAPES[ch]);
|
|
37
32
|
}
|
|
33
|
+
export function escapeFormatCode(code) {
|
|
34
|
+
assertRepresentable(code);
|
|
35
|
+
return code.replace(/[&<>"\n\r\t]/g, (ch) => ATTR_ESCAPES[ch]);
|
|
36
|
+
}
|
|
38
37
|
export function escapeSpreadsheetText(value) {
|
|
39
38
|
return escapeText(value
|
|
40
39
|
.replace(/_(x[0-9A-Fa-f]{4}_)/g, '_x005F_$1')
|
|
41
|
-
.replace(
|
|
40
|
+
.replace(XML_UNREPRESENTABLE_GLOBAL, (ch) => `_x${codePointHex(ch.codePointAt(0))}_`));
|
|
42
41
|
}
|
|
43
42
|
function needsSpacePreserve(value) {
|
|
44
43
|
return value.length > 0 && (value !== value.trim() || /[\n\r\t]/.test(value));
|
|
@@ -47,11 +46,6 @@ export function textElement(value) {
|
|
|
47
46
|
const space = needsSpacePreserve(value) ? ' xml:space="preserve"' : '';
|
|
48
47
|
return `<t${space}>${escapeSpreadsheetText(value)}</t>`;
|
|
49
48
|
}
|
|
50
|
-
export function stripFormulaEquals(value) {
|
|
51
|
-
if (typeof value === 'number')
|
|
52
|
-
return String(value);
|
|
53
|
-
return value.startsWith('=') ? value.slice(1) : value;
|
|
54
|
-
}
|
|
55
49
|
export function boolAttr(name, value) {
|
|
56
50
|
return value === undefined ? '' : ` ${name}="${value ? 1 : 0}"`;
|
|
57
51
|
}
|
|
@@ -60,17 +54,23 @@ export function textAttr(name, value) {
|
|
|
60
54
|
}
|
|
61
55
|
export function checkedToken(value, isValid, kind) {
|
|
62
56
|
if (!isValid(value)) {
|
|
63
|
-
throw
|
|
57
|
+
throw invalidToken(kind, value);
|
|
64
58
|
}
|
|
65
59
|
return value;
|
|
66
60
|
}
|
|
67
61
|
export function numAttr(name, value) {
|
|
68
62
|
return value === undefined ? '' : ` ${name}="${numberText(value)}"`;
|
|
69
63
|
}
|
|
70
|
-
export function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
export function assertWritableDate(date, property) {
|
|
65
|
+
const time = date.getTime();
|
|
66
|
+
if (Number.isNaN(time)) {
|
|
67
|
+
throw new AuthoringError(`cannot write ${property}: it is an Invalid Date`);
|
|
68
|
+
}
|
|
69
|
+
const year = date.getUTCFullYear();
|
|
70
|
+
if (year < 0 || year > 9999) {
|
|
71
|
+
throw new AuthoringError(`cannot write ${property}: the year ${year} is outside the 0000-9999 range a W3CDTF ` +
|
|
72
|
+
'timestamp can spell');
|
|
73
|
+
}
|
|
74
74
|
}
|
|
75
75
|
export function numberText(value) {
|
|
76
76
|
assertWritableNumber(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shbernal/ts-xlsx",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "A TypeScript-first library for reading and writing xlsx (OOXML) spreadsheets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"csv",
|
|
@@ -91,10 +91,14 @@
|
|
|
91
91
|
"constitution:check": "node scripts/check-constitution.ts",
|
|
92
92
|
"layering:check": "node scripts/check-layering.ts",
|
|
93
93
|
"entries:check": "node scripts/check-entries.ts",
|
|
94
|
+
"public-types:check": "node scripts/check-public-types.ts",
|
|
95
|
+
"facet-register:check": "node scripts/check-facet-register.ts",
|
|
96
|
+
"tsconfig-coverage:check": "node scripts/check-tsconfig-coverage.ts",
|
|
94
97
|
"browser:check": "node scripts/check-browser-safe.ts",
|
|
95
98
|
"source-text:check": "node scripts/check-source-text.ts",
|
|
99
|
+
"error-messages:check": "node scripts/check-error-messages.ts",
|
|
96
100
|
"chars:check": "charcheck",
|
|
97
|
-
"lint": "node
|
|
101
|
+
"lint": "node scripts/lint.ts",
|
|
98
102
|
"lint:fix": "pnpm run lint --fix",
|
|
99
103
|
"format": "node scripts/format.ts --write",
|
|
100
104
|
"format:check": "node scripts/format.ts --check",
|
|
@@ -103,7 +107,8 @@
|
|
|
103
107
|
"typecheck:test": "tsc --noEmit -p tsconfig.test.json",
|
|
104
108
|
"typecheck:dist": "tsc --noEmit -p tsconfig.dist.json",
|
|
105
109
|
"typecheck:site": "tsc --noEmit -p www/tsconfig.json",
|
|
106
|
-
"test:src": "node
|
|
110
|
+
"test:src": "node scripts/test-src.ts",
|
|
111
|
+
"test:harness": "node --test \"scripts/**/*.test.ts\"",
|
|
107
112
|
"test:site": "node --test \"www/**/*.test.ts\"",
|
|
108
113
|
"coverage": "node scripts/coverage.ts",
|
|
109
114
|
"corpus": "node test/corpus/run.ts",
|
|
@@ -120,17 +125,17 @@
|
|
|
120
125
|
},
|
|
121
126
|
"devDependencies": {
|
|
122
127
|
"@types/node": "^24.13.3",
|
|
123
|
-
"charcheck": "^0.
|
|
128
|
+
"charcheck": "^0.4.0",
|
|
124
129
|
"jszip": "^3.10.1",
|
|
125
|
-
"lefthook": "^2.1.
|
|
130
|
+
"lefthook": "^2.1.12",
|
|
126
131
|
"ooxml-validate": "^0.0.3",
|
|
127
|
-
"oxfmt": "^0.
|
|
132
|
+
"oxfmt": "^0.65.0",
|
|
128
133
|
"oxlint": "^1.80.0",
|
|
129
134
|
"oxlint-tsgolint": "7.0.2001",
|
|
130
135
|
"shiki": "^4.4.3",
|
|
131
136
|
"typescript": "^7.0.2",
|
|
132
137
|
"vitepress": "^1.6.4",
|
|
133
|
-
"vue": "^3.5.
|
|
138
|
+
"vue": "^3.5.42"
|
|
134
139
|
},
|
|
135
140
|
"engines": {
|
|
136
141
|
"node": ">=24"
|
|
File without changes
|