@shbernal/ts-xlsx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- package/package.json +117 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// The OPC (Open Packaging Conventions) layer of the reader: resolving relationship targets to part
|
|
2
|
+
// paths, reading a part's `.rels`, resolving a part's declared content type, and walking the transitive
|
|
3
|
+
// closure of parts a preserved reference reaches. Every helper here is pure over the inflated package —
|
|
4
|
+
// it takes part text/bytes accessors and returns paths or records, touching no Workbook model.
|
|
5
|
+
import { strFromU8 } from 'fflate';
|
|
6
|
+
import { openElements } from "../../xml/xml-read.js";
|
|
7
|
+
import { extensionOf, relsPathFor } from "./part-paths.js";
|
|
8
|
+
// Bind the part-lookup accessors over an inflated package (a part-path → bytes map).
|
|
9
|
+
export function packageAccessors(files) {
|
|
10
|
+
return {
|
|
11
|
+
partText(path) {
|
|
12
|
+
const bytes = files[path];
|
|
13
|
+
return bytes === undefined ? undefined : strFromU8(bytes);
|
|
14
|
+
},
|
|
15
|
+
partBytes: (path) => files[path],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Whether a <Relationship> carries a resolvable Target and its Type ends with `/<suffix>` (a
|
|
19
|
+
// local-name match, so a namespaced or oddly-cased type still resolves). The type guard lets a
|
|
20
|
+
// matching relationship's Target be read without a further presence check.
|
|
21
|
+
function matchesType(attrs, suffix) {
|
|
22
|
+
return (attrs.Type !== undefined && attrs.Target !== undefined && attrs.Type.endsWith(`/${suffix}`));
|
|
23
|
+
}
|
|
24
|
+
// The Target of the first relationship whose Type ends with `/<suffix>`, or undefined when none is
|
|
25
|
+
// declared — for a single expected reference, where the plural form below would over-gather.
|
|
26
|
+
export function relationshipTargetByType(xml, suffix) {
|
|
27
|
+
return relationshipTargetsByType(xml, suffix)[0];
|
|
28
|
+
}
|
|
29
|
+
// Every Target whose Type ends with `/<suffix>`, in declaration order — for a part class a sheet may
|
|
30
|
+
// reference more than once (a sheet can own several tables), where the singular helper's first-match
|
|
31
|
+
// would miss all but one.
|
|
32
|
+
export function relationshipTargetsByType(xml, suffix) {
|
|
33
|
+
const targets = [];
|
|
34
|
+
for (const { attrs } of openElements(xml, 'Relationship')) {
|
|
35
|
+
if (matchesType(attrs, suffix))
|
|
36
|
+
targets.push(attrs.Target);
|
|
37
|
+
}
|
|
38
|
+
return targets;
|
|
39
|
+
}
|
|
40
|
+
// The package part a sheet reaches through the first relationship of a given type, already resolved
|
|
41
|
+
// relative to the sheet — the "load the sheet's rels, find the one relationship of this type, resolve
|
|
42
|
+
// its target" preamble every single-part sheet lookup (notes, printer settings, drawing, background)
|
|
43
|
+
// opens with. undefined when the sheet has no rels part or declares no such relationship.
|
|
44
|
+
export function sheetRelTarget(sheetPath, partText, type) {
|
|
45
|
+
const relsXml = partText(relsPathFor(sheetPath));
|
|
46
|
+
if (relsXml === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const target = relationshipTargetByType(relsXml, type);
|
|
49
|
+
return target === undefined ? undefined : resolveRelativePart(sheetPath, target);
|
|
50
|
+
}
|
|
51
|
+
// Resolve a relationship target (relative to the referencing part's directory, or absolute from the
|
|
52
|
+
// package root) into a package part path, collapsing `.`/`..` segments.
|
|
53
|
+
export function resolveRelativePart(basePart, target) {
|
|
54
|
+
if (target.startsWith('/'))
|
|
55
|
+
return target.slice(1);
|
|
56
|
+
const baseDir = basePart.slice(0, basePart.lastIndexOf('/') + 1);
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const segment of `${baseDir}${target}`.split('/')) {
|
|
59
|
+
if (segment === '' || segment === '.')
|
|
60
|
+
continue;
|
|
61
|
+
if (segment === '..')
|
|
62
|
+
out.pop();
|
|
63
|
+
else
|
|
64
|
+
out.push(segment);
|
|
65
|
+
}
|
|
66
|
+
return out.join('/');
|
|
67
|
+
}
|
|
68
|
+
// A workbook relationship target is relative to the `xl/` directory (`worksheets/sheet1.xml`)
|
|
69
|
+
// or absolute from the package root (`/xl/worksheets/sheet1.xml`); normalise both to a part path.
|
|
70
|
+
export function resolveWorkbookPart(target) {
|
|
71
|
+
if (target.startsWith('/'))
|
|
72
|
+
return target.slice(1);
|
|
73
|
+
return `xl/${target.replace(/^\.\//, '')}`;
|
|
74
|
+
}
|
|
75
|
+
export function parseRelationships(xml) {
|
|
76
|
+
const rels = new Map();
|
|
77
|
+
for (const { attrs } of openElements(xml, 'Relationship')) {
|
|
78
|
+
if (attrs.Id !== undefined && attrs.Target !== undefined) {
|
|
79
|
+
rels.set(attrs.Id, attrs.Target);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return rels;
|
|
83
|
+
}
|
|
84
|
+
export function parseRelationshipRecords(xml) {
|
|
85
|
+
const records = [];
|
|
86
|
+
for (const { attrs } of openElements(xml, 'Relationship')) {
|
|
87
|
+
if (attrs.Id !== undefined && attrs.Type !== undefined && attrs.Target !== undefined) {
|
|
88
|
+
records.push({
|
|
89
|
+
id: attrs.Id,
|
|
90
|
+
type: attrs.Type,
|
|
91
|
+
target: attrs.Target,
|
|
92
|
+
external: attrs.TargetMode === 'External',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return records;
|
|
97
|
+
}
|
|
98
|
+
// Resolve a package part path to its declared content type the way OPC does: an `<Override>` naming
|
|
99
|
+
// the exact part wins, else the `<Default>` registered for the part's extension. An unknown part
|
|
100
|
+
// falls back to the generic binary type so re-declaring it never emits an empty content type.
|
|
101
|
+
export function contentTypeResolver(contentTypesXml) {
|
|
102
|
+
const overrides = new Map();
|
|
103
|
+
const defaults = new Map();
|
|
104
|
+
for (const { local, attrs } of openElements(contentTypesXml, 'Override', 'Default')) {
|
|
105
|
+
if (local === 'Override' && attrs.PartName !== undefined && attrs.ContentType !== undefined) {
|
|
106
|
+
overrides.set(attrs.PartName, attrs.ContentType);
|
|
107
|
+
}
|
|
108
|
+
else if (local === 'Default' &&
|
|
109
|
+
attrs.Extension !== undefined &&
|
|
110
|
+
attrs.ContentType !== undefined) {
|
|
111
|
+
defaults.set(attrs.Extension.toLowerCase(), attrs.ContentType);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return (path) => overrides.get(`/${path}`) ??
|
|
115
|
+
defaults.get(extensionOf(path).toLowerCase()) ??
|
|
116
|
+
'application/octet-stream';
|
|
117
|
+
}
|
|
118
|
+
// Gather the transitive closure of package parts reachable from an entry part — the part itself, then
|
|
119
|
+
// every internal part its relationships target, breadth-first — each with its raw bytes, content type,
|
|
120
|
+
// and (internal) relationships. Returns undefined when the entry part is absent (a dangling reference
|
|
121
|
+
// preserves nothing). A `visited` set dedupes shared parts and bounds the walk to the (finite,
|
|
122
|
+
// inflate-capped) package, so a maliciously self-referential rels graph cannot loop.
|
|
123
|
+
export function capturePartClosure(entryPath, partText, partBytes, contentTypeOf) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
const visited = new Set();
|
|
126
|
+
const queue = [entryPath];
|
|
127
|
+
while (queue.length > 0) {
|
|
128
|
+
const path = queue.shift();
|
|
129
|
+
if (path === undefined || visited.has(path))
|
|
130
|
+
continue;
|
|
131
|
+
visited.add(path);
|
|
132
|
+
const bytes = partBytes(path);
|
|
133
|
+
if (bytes === undefined)
|
|
134
|
+
continue;
|
|
135
|
+
const relsXml = partText(relsPathFor(path));
|
|
136
|
+
const rels = [];
|
|
137
|
+
if (relsXml !== undefined) {
|
|
138
|
+
for (const rel of parseRelationshipRecords(relsXml)) {
|
|
139
|
+
if (rel.external) {
|
|
140
|
+
// A linked workbook lives outside the package: keep the wiring verbatim (an externalLink
|
|
141
|
+
// part's pointer to its source), but do not walk into it — there is no package part to visit.
|
|
142
|
+
rels.push({ id: rel.id, type: rel.type, targetPath: rel.target, external: true });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const targetPath = resolveRelativePart(path, rel.target);
|
|
146
|
+
rels.push({ id: rel.id, type: rel.type, targetPath });
|
|
147
|
+
queue.push(targetPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
parts.push({ path, contentType: contentTypeOf(path), bytes, rels });
|
|
151
|
+
}
|
|
152
|
+
return parts.some((part) => part.path === entryPath) ? parts : undefined;
|
|
153
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ReadXlsxOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Maximum total uncompressed output, in bytes, produced while inflating the package.
|
|
4
|
+
* The bound is enforced by a running counter as bytes are decompressed — never read from
|
|
5
|
+
* the archive's (untrusted, forgeable) size headers — so a zip bomb that lies about its
|
|
6
|
+
* uncompressed size is rejected all the same. Defaults to 512 MiB.
|
|
7
|
+
*/
|
|
8
|
+
readonly maxUncompressedBytes?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_MAX_UNCOMPRESSED: number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The one knob every reader takes, declared apart from any of them.
|
|
2
|
+
//
|
|
3
|
+
// `readXlsx`, `readXlsb`, and the row streamer all inflate the same OPC container under the same
|
|
4
|
+
// bound, so the option bag belongs to none of them individually — and a reader that dispatches to
|
|
5
|
+
// another (the `.xlsx` entry point hands a binary package to the `.xlsb` codec) cannot import it from
|
|
6
|
+
// its peer without the two modules importing each other.
|
|
7
|
+
export const DEFAULT_MAX_UNCOMPRESSED = 512 * 1024 * 1024;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function relationship(id: string, type: string, target: string, options?: {
|
|
2
|
+
external?: boolean;
|
|
3
|
+
}): string;
|
|
4
|
+
export declare function relationshipsPart(relationships: readonly string[]): string;
|
|
5
|
+
export declare function preservedRelsXml(rels: readonly {
|
|
6
|
+
id: string;
|
|
7
|
+
type: string;
|
|
8
|
+
target: string;
|
|
9
|
+
external?: boolean;
|
|
10
|
+
}[]): string;
|
|
11
|
+
export declare function relsPartXml(rels: readonly {
|
|
12
|
+
id: string;
|
|
13
|
+
type: string;
|
|
14
|
+
target: string;
|
|
15
|
+
}[]): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// The write side of the relationship graph: a single `<Relationship>` element and the `.rels` part
|
|
2
|
+
// envelope that wraps a set of them. Pure OPC — a `.rels` part has the same shape whatever the parts
|
|
3
|
+
// it wires together are spelled in, so this is stated once here rather than inside a codec.
|
|
4
|
+
//
|
|
5
|
+
// The reader's half of the same graph (resolving targets, walking a part closure) is in `read-opc.ts`.
|
|
6
|
+
import { escapeAttr, XML_DECLARATION } from "../../xml/xml.js";
|
|
7
|
+
import { PKG_RELS_NS } from "./namespaces.js";
|
|
8
|
+
// A single `<Relationship>`. An `external` target lives outside the package (a hyperlink URL), so the
|
|
9
|
+
// element carries `TargetMode="External"`; a package-internal target (the default) omits it. The caller
|
|
10
|
+
// escapes the target when it is not a writer-controlled package path.
|
|
11
|
+
export function relationship(id, type, target, options) {
|
|
12
|
+
const mode = options?.external ? ' TargetMode="External"' : '';
|
|
13
|
+
return `<Relationship Id="${id}" Type="${type}" Target="${target}"${mode}/>`;
|
|
14
|
+
}
|
|
15
|
+
// Wrap a part's `<Relationship>` elements in the OPC `.rels` envelope (XML declaration + the namespaced
|
|
16
|
+
// `<Relationships>` root). Every `.rels` part the writer emits shares this envelope; only the elements
|
|
17
|
+
// inside differ, so each caller builds its own list of {@link relationship} strings and hands them here.
|
|
18
|
+
export function relationshipsPart(relationships) {
|
|
19
|
+
return `${XML_DECLARATION}<Relationships xmlns="${PKG_RELS_NS}">${relationships.join('')}</Relationships>`;
|
|
20
|
+
}
|
|
21
|
+
export function preservedRelsXml(rels) {
|
|
22
|
+
return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, escapeAttr(rel.target), rel.external ? { external: true } : {})));
|
|
23
|
+
}
|
|
24
|
+
// A `.rels` part for a generated part chain (pivot table → cache definition → cache records). Targets
|
|
25
|
+
// are writer-controlled package paths, so no attribute escaping is needed.
|
|
26
|
+
export function relsPartXml(rels) {
|
|
27
|
+
return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target)));
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UnsupportedFormatError } from './errors.ts';
|
|
2
|
+
/** The container kind a leading magic-byte sniff distinguishes, before any inflation. */
|
|
3
|
+
export type Container = 'cfb' | 'zip' | 'other';
|
|
4
|
+
/** Classify a blob's container by its leading magic bytes alone — no allocation, no inflation. */
|
|
5
|
+
export declare function sniffContainer(data: Uint8Array): Container;
|
|
6
|
+
/**
|
|
7
|
+
* Inflate a spreadsheet package (`.xlsx` or `.xlsb` — the container is the same), translating input
|
|
8
|
+
* that is neither into a typed error before or instead of a raw zip failure:
|
|
9
|
+
* - a legacy `.xls` (CFB) blob → {@link UnsupportedFormatError} `'xls'`, caught by the magic sniff so
|
|
10
|
+
* fflate never runs on it;
|
|
11
|
+
* - a non-ZIP blob → {@link UnsupportedFormatError} `'unknown'`, likewise caught before inflation;
|
|
12
|
+
* - a `PK`-headed blob that fflate then rejects as malformed → {@link PackageReadError}, with the
|
|
13
|
+
* underlying zip message discarded so no internals (or path) leak.
|
|
14
|
+
*
|
|
15
|
+
* The bounded-inflation guard (a probable zip bomb) already raises {@link PackageReadError} with its
|
|
16
|
+
* own informative message, and is re-thrown unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export declare function inflateSpreadsheetPackage(data: Uint8Array, cap: number): Record<string, Uint8Array>;
|
|
19
|
+
/**
|
|
20
|
+
* The typed error for an inflated package that carries no `xl/workbook.xml`: a `.xlsb` if its binary
|
|
21
|
+
* `xl/workbook.bin` office document is present, otherwise an unrecognised (non-workbook) ZIP.
|
|
22
|
+
*
|
|
23
|
+
* The `.xlsb` branch takes the caller's own explanation, because whether a binary workbook is
|
|
24
|
+
* readable now depends on *which* entry point was asked: `readXlsx` reads one, the row streamer
|
|
25
|
+
* cannot yet. A single baked-in "not supported" message would be wrong for one of them.
|
|
26
|
+
*/
|
|
27
|
+
export declare function unsupportedWorkbookPart(partText: (path: string) => string | undefined, xlsbMessage: string): UnsupportedFormatError;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Front of the read pipe: format detection, the inflate bound both readers share, and the typed
|
|
2
|
+
// rejection of everything that is neither an `.xlsx` nor an `.xlsb`.
|
|
3
|
+
//
|
|
4
|
+
// The reader's first job on an arbitrary blob is to reject what it cannot read with a clear, typed
|
|
5
|
+
// error rather than a raw fflate crash. A cheap magic-byte sniff runs *before* the zip layer (so a
|
|
6
|
+
// legacy `.xls` compound file or non-ZIP garbage fails fast, before fflate throws its opaque "end of
|
|
7
|
+
// central directory" error — which can also leak an absolute path from below), and a malformed ZIP is
|
|
8
|
+
// translated after it.
|
|
9
|
+
//
|
|
10
|
+
// The two outcomes are deliberately different types, because they answer different questions. A blob
|
|
11
|
+
// the sniff rejects is the *wrong kind of thing* — {@link UnsupportedFormatError}, branchable on
|
|
12
|
+
// `.format`. A `PK`-headed blob the zip layer then chokes on is the right kind of thing we cannot
|
|
13
|
+
// unpack — {@link PackageReadError}. Whichever it is, the message states the check that actually ran,
|
|
14
|
+
// and callers branch on the type, never on message text.
|
|
15
|
+
//
|
|
16
|
+
// The `.xlsx` and `.xlsb` serialisations share this whole layer — same container, same bound, same
|
|
17
|
+
// rejections — so it is stated once here and neither reader owns it.
|
|
18
|
+
import { PackageReadError, UnsupportedFormatError } from "./errors.js";
|
|
19
|
+
import { inflatePackage } from "./inflate.js";
|
|
20
|
+
// The OLE2 / Compound File Binary signature ([MS-CFB] 2.2) that opens every legacy `.xls` (and the
|
|
21
|
+
// `.doc`/`.ppt` siblings). `src/vba/cfb.ts` reads this same magic for `vbaProject.bin`; here we need
|
|
22
|
+
// only recognise it, not parse the container.
|
|
23
|
+
const CFB_MAGIC = Uint8Array.of(0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
|
|
24
|
+
// The ZIP local-file-header signature (`PK\x03\x04`) that opens every OPC package — `.xlsx`, `.xlsb`,
|
|
25
|
+
// and the rest. An empty or spanned archive starts with a different `PK` marker, but a real workbook
|
|
26
|
+
// package always leads with a local file header.
|
|
27
|
+
const ZIP_LOCAL_FILE_MAGIC = Uint8Array.of(0x50, 0x4b, 0x03, 0x04);
|
|
28
|
+
/** Classify a blob's container by its leading magic bytes alone — no allocation, no inflation. */
|
|
29
|
+
export function sniffContainer(data) {
|
|
30
|
+
if (startsWith(data, CFB_MAGIC))
|
|
31
|
+
return 'cfb';
|
|
32
|
+
if (startsWith(data, ZIP_LOCAL_FILE_MAGIC))
|
|
33
|
+
return 'zip';
|
|
34
|
+
return 'other';
|
|
35
|
+
}
|
|
36
|
+
function startsWith(data, magic) {
|
|
37
|
+
if (data.length < magic.length)
|
|
38
|
+
return false;
|
|
39
|
+
for (let i = 0; i < magic.length; i++) {
|
|
40
|
+
if (data[i] !== magic[i])
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Inflate a spreadsheet package (`.xlsx` or `.xlsb` — the container is the same), translating input
|
|
47
|
+
* that is neither into a typed error before or instead of a raw zip failure:
|
|
48
|
+
* - a legacy `.xls` (CFB) blob → {@link UnsupportedFormatError} `'xls'`, caught by the magic sniff so
|
|
49
|
+
* fflate never runs on it;
|
|
50
|
+
* - a non-ZIP blob → {@link UnsupportedFormatError} `'unknown'`, likewise caught before inflation;
|
|
51
|
+
* - a `PK`-headed blob that fflate then rejects as malformed → {@link PackageReadError}, with the
|
|
52
|
+
* underlying zip message discarded so no internals (or path) leak.
|
|
53
|
+
*
|
|
54
|
+
* The bounded-inflation guard (a probable zip bomb) already raises {@link PackageReadError} with its
|
|
55
|
+
* own informative message, and is re-thrown unchanged.
|
|
56
|
+
*/
|
|
57
|
+
export function inflateSpreadsheetPackage(data, cap) {
|
|
58
|
+
const container = sniffContainer(data);
|
|
59
|
+
if (container === 'cfb')
|
|
60
|
+
throw new UnsupportedFormatError('xls');
|
|
61
|
+
// Not the default message: nothing here has looked for a workbook part, and saying so would point
|
|
62
|
+
// an investigation a layer past the one that actually refused.
|
|
63
|
+
if (container === 'other') {
|
|
64
|
+
throw new UnsupportedFormatError('unknown', 'not a valid .xlsx package: the input is not a ZIP');
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return inflatePackage(data, cap);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// The bomb guard already throws the right type with a better message — surface it. Anything else
|
|
71
|
+
// is fflate reporting a malformed archive: same classification (a ZIP we cannot unpack), but its
|
|
72
|
+
// raw text is replaced wholesale rather than wrapped or attached as `cause`, because it comes from
|
|
73
|
+
// a layer whose strings may name internals — or an absolute path — that must not reach a caller.
|
|
74
|
+
if (err instanceof PackageReadError)
|
|
75
|
+
throw err;
|
|
76
|
+
throw new PackageReadError('not a readable .xlsx package: the ZIP container is corrupt or truncated and could not be inflated');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The typed error for an inflated package that carries no `xl/workbook.xml`: a `.xlsb` if its binary
|
|
81
|
+
* `xl/workbook.bin` office document is present, otherwise an unrecognised (non-workbook) ZIP.
|
|
82
|
+
*
|
|
83
|
+
* The `.xlsb` branch takes the caller's own explanation, because whether a binary workbook is
|
|
84
|
+
* readable now depends on *which* entry point was asked: `readXlsx` reads one, the row streamer
|
|
85
|
+
* cannot yet. A single baked-in "not supported" message would be wrong for one of them.
|
|
86
|
+
*/
|
|
87
|
+
export function unsupportedWorkbookPart(partText, xlsbMessage) {
|
|
88
|
+
if (partText('xl/workbook.bin') !== undefined) {
|
|
89
|
+
return new UnsupportedFormatError('xlsb', xlsbMessage);
|
|
90
|
+
}
|
|
91
|
+
return new UnsupportedFormatError('unknown');
|
|
92
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Cell } from '../../core/cell.ts';
|
|
2
|
+
import type { CellStyle, Font, NamedCellStyle } from '../../core/style.ts';
|
|
3
|
+
/**
|
|
4
|
+
* What an xf resolves to: the {@link CellStyle} facet tuple, plus the two flags an xf carries that
|
|
5
|
+
* are not facets. Absent facets stay undefined, matching the contract that an unset facet is simply
|
|
6
|
+
* not present on the reconstructed cell.
|
|
7
|
+
*
|
|
8
|
+
* It *derives* the facets rather than listing them, so a seventh facet added to `CellStyle` reaches
|
|
9
|
+
* both readers the moment it joins. Re-declaring them here — the shape this replaced — meant a new
|
|
10
|
+
* facet silently stopped at the model and never appeared in a file we read back.
|
|
11
|
+
*/
|
|
12
|
+
export interface XfStyle extends CellStyle {
|
|
13
|
+
readonly quotePrefix?: boolean;
|
|
14
|
+
/** The `xfId` link into the named-style layer (`cellStyleXfs`); absent for the Normal default (0). */
|
|
15
|
+
readonly xfId?: number;
|
|
16
|
+
}
|
|
17
|
+
/** The parsed style table: the cell formats a cell/row/column `s` indexes, plus the named cell-style
|
|
18
|
+
* layer a cell's `xfId` links into. Each cellXfs entry is already merged with its named style, so a
|
|
19
|
+
* cell reading its `s` sees the effective facets; the `xfId` link is carried through for re-write. */
|
|
20
|
+
export interface StyleTable {
|
|
21
|
+
readonly cellXfs: ReadonlyArray<XfStyle>;
|
|
22
|
+
readonly namedStyles: ReadonlyArray<NamedCellStyle>;
|
|
23
|
+
/**
|
|
24
|
+
* Font id 0 — the workbook's declared default font, the face every cell naming no font renders in.
|
|
25
|
+
* Surfaced separately from the fonts it was flattened onto because it is workbook-level state, not a
|
|
26
|
+
* cell format: a re-write must emit *this* face as font 0 rather than an assumed Calibri, or every
|
|
27
|
+
* empty cell changes face and every character-unit column width changes meaning. Absent when the
|
|
28
|
+
* file declares no font table.
|
|
29
|
+
*/
|
|
30
|
+
readonly defaultFont?: Font;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The format code a number-format id denotes: the file's own `<numFmt>`/`BrtFmt` declaration if it
|
|
34
|
+
* has one, else the built-in Excel defines for that id. Id 0 is General — the absence of a format —
|
|
35
|
+
* and resolves to nothing so an ordinary cell carries no `numFmt`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function numFmtCodeFor(id: number, custom: ReadonlyMap<number, string>): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Apply a resolved xf's non-value facets to a cell — the six {@link CellStyle} facets through the
|
|
40
|
+
* shared {@link applyCellStyle}, plus the two links that live on the xf itself rather than in the
|
|
41
|
+
* facet tuple (`quotePrefix`, and the `xfId` pointer into the named-style layer).
|
|
42
|
+
*
|
|
43
|
+
* Shared by every path that commits a cell: the XML reader's ordinary and shared-formula-clone paths,
|
|
44
|
+
* and the BIFF12 reader — so a styled cell keeps its look regardless of which serialisation it came
|
|
45
|
+
* from, and the two cannot drift on what "applying a style" means.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyXfToCell(cell: Cell, style: XfStyle | undefined): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// The resolved cell-format table both codecs read into: what an xf *is* once its id-indirection is
|
|
2
|
+
// flattened, which of the two number-format tables an id resolves against, and what applying an xf to
|
|
3
|
+
// a cell means.
|
|
4
|
+
//
|
|
5
|
+
// None of that is a property of how the style table is spelled — `xl/styles.xml` and `xl/styles.bin`
|
|
6
|
+
// state the same model in XML and BIFF12 records — so it is stated once here, above both codecs. The
|
|
7
|
+
// XML parsing lives in `../xlsx/read-styles.ts`, the record parsing in `../xlsb/read-styles.ts`, and
|
|
8
|
+
// each hands back the same {@link StyleTable}.
|
|
9
|
+
import { applyCellStyle } from "../../core/cell.js";
|
|
10
|
+
import { NAMED_STYLE_ID } from "../../core/internal.js";
|
|
11
|
+
// ECMA-376 reserves numFmt ids below 164 for formats every consumer knows implicitly, so a
|
|
12
|
+
// foreign file may name one with no <numFmt> entry. This maps the standard ids to their
|
|
13
|
+
// codes; id 0 (General) and any unknown id resolve to no format. The writer never emits
|
|
14
|
+
// these — it always defines a custom id — but reading them keeps foreign files faithful.
|
|
15
|
+
const BUILTIN_NUMFMTS = new Map([
|
|
16
|
+
[1, '0'],
|
|
17
|
+
[2, '0.00'],
|
|
18
|
+
[3, '#,##0'],
|
|
19
|
+
[4, '#,##0.00'],
|
|
20
|
+
[9, '0%'],
|
|
21
|
+
[10, '0.00%'],
|
|
22
|
+
[11, '0.00E+00'],
|
|
23
|
+
[12, '# ?/?'],
|
|
24
|
+
[13, '# ??/??'],
|
|
25
|
+
[14, 'mm-dd-yy'],
|
|
26
|
+
[15, 'd-mmm-yy'],
|
|
27
|
+
[16, 'd-mmm'],
|
|
28
|
+
[17, 'mmm-yy'],
|
|
29
|
+
[18, 'h:mm AM/PM'],
|
|
30
|
+
[19, 'h:mm:ss AM/PM'],
|
|
31
|
+
[20, 'h:mm'],
|
|
32
|
+
[21, 'h:mm:ss'],
|
|
33
|
+
[22, 'm/d/yy h:mm'],
|
|
34
|
+
[37, '#,##0 ;(#,##0)'],
|
|
35
|
+
[38, '#,##0 ;[Red](#,##0)'],
|
|
36
|
+
[39, '#,##0.00;(#,##0.00)'],
|
|
37
|
+
[40, '#,##0.00;[Red](#,##0.00)'],
|
|
38
|
+
[45, 'mm:ss'],
|
|
39
|
+
[46, '[h]:mm:ss'],
|
|
40
|
+
[47, 'mmss.0'],
|
|
41
|
+
[48, '##0.0E+0'],
|
|
42
|
+
[49, '@'],
|
|
43
|
+
// Ids 27..36 and 50..58 are reserved for locale-specific built-in East Asian date/time formats;
|
|
44
|
+
// a file authored in a CJK locale styles date cells with them and, being built-ins, emits no
|
|
45
|
+
// <numFmt>. The exact code is locale-defined — these are the representative Excel forms — but what
|
|
46
|
+
// matters for reading is that each resolves to a non-empty date/time code so the serial reads as a
|
|
47
|
+
// date rather than a bare number.
|
|
48
|
+
[27, '[$-404]e/m/d'],
|
|
49
|
+
[28, '[$-404]e"年"m"月"d"日"'],
|
|
50
|
+
[29, '[$-404]e"年"m"月"d"日"'],
|
|
51
|
+
[30, '[$-404]m/d/yy'],
|
|
52
|
+
[31, '[$-404]yyyy"年"m"月"d"日"'],
|
|
53
|
+
[32, '[$-404]h"時"mm"分"'],
|
|
54
|
+
[33, '[$-404]h"時"mm"分"ss"秒"'],
|
|
55
|
+
[34, '上午/下午h"時"mm"分"'],
|
|
56
|
+
[35, '上午/下午h"時"mm"分"ss"秒"'],
|
|
57
|
+
[36, '[$-404]e/m/d'],
|
|
58
|
+
[50, '[$-404]e/m/d'],
|
|
59
|
+
[51, '[$-404]e"年"m"月"d"日"'],
|
|
60
|
+
[52, '[$-404]yyyy"年"m"月"'],
|
|
61
|
+
[53, '[$-404]m"月"d"日"'],
|
|
62
|
+
[54, '[$-404]e"年"m"月"d"日"'],
|
|
63
|
+
[55, '上午/下午h"時"mm"分"'],
|
|
64
|
+
[56, '上午/下午h"時"mm"分"ss"秒"'],
|
|
65
|
+
[57, '[$-404]yyyy"年"m"月"'],
|
|
66
|
+
[58, '[$-404]m"月"d"日"'],
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* The format code a number-format id denotes: the file's own `<numFmt>`/`BrtFmt` declaration if it
|
|
70
|
+
* has one, else the built-in Excel defines for that id. Id 0 is General — the absence of a format —
|
|
71
|
+
* and resolves to nothing so an ordinary cell carries no `numFmt`.
|
|
72
|
+
*/
|
|
73
|
+
export function numFmtCodeFor(id, custom) {
|
|
74
|
+
if (!Number.isInteger(id) || id === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
return custom.get(id) ?? BUILTIN_NUMFMTS.get(id);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Apply a resolved xf's non-value facets to a cell — the six {@link CellStyle} facets through the
|
|
80
|
+
* shared {@link applyCellStyle}, plus the two links that live on the xf itself rather than in the
|
|
81
|
+
* facet tuple (`quotePrefix`, and the `xfId` pointer into the named-style layer).
|
|
82
|
+
*
|
|
83
|
+
* Shared by every path that commits a cell: the XML reader's ordinary and shared-formula-clone paths,
|
|
84
|
+
* and the BIFF12 reader — so a styled cell keeps its look regardless of which serialisation it came
|
|
85
|
+
* from, and the two cannot drift on what "applying a style" means.
|
|
86
|
+
*/
|
|
87
|
+
export function applyXfToCell(cell, style) {
|
|
88
|
+
if (style === undefined)
|
|
89
|
+
return;
|
|
90
|
+
applyCellStyle(cell, style);
|
|
91
|
+
if (style.quotePrefix !== undefined)
|
|
92
|
+
cell.quotePrefix = style.quotePrefix;
|
|
93
|
+
if (style.xfId !== undefined)
|
|
94
|
+
cell[NAMED_STYLE_ID] = style.xfId;
|
|
95
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { XlsxError } from '../../errors.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an `.xlsb` package's binary BIFF12 content cannot be parsed — a record whose declared
|
|
4
|
+
* size runs past the end of its part, a truncated record header, or a structure that does not conform
|
|
5
|
+
* to [MS-XLSB].
|
|
6
|
+
*
|
|
7
|
+
* The record streams are hostile input (a spreadsheet library parses untrusted files), so a malformed
|
|
8
|
+
* part fails closed with this error rather than crashing, hanging, or over-allocating. It is distinct
|
|
9
|
+
* from {@link UnsupportedFormatError}, which reports that the *container* is not a format we read at
|
|
10
|
+
* all; by the time this is raised the input has already been recognised as an `.xlsb`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class XlsbParseError extends XlsxError {
|
|
13
|
+
readonly name = "XlsbParseError";
|
|
14
|
+
readonly code = "malformed-input";
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { XlsxError } from "../../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when an `.xlsb` package's binary BIFF12 content cannot be parsed — a record whose declared
|
|
4
|
+
* size runs past the end of its part, a truncated record header, or a structure that does not conform
|
|
5
|
+
* to [MS-XLSB].
|
|
6
|
+
*
|
|
7
|
+
* The record streams are hostile input (a spreadsheet library parses untrusted files), so a malformed
|
|
8
|
+
* part fails closed with this error rather than crashing, hanging, or over-allocating. It is distinct
|
|
9
|
+
* from {@link UnsupportedFormatError}, which reports that the *container* is not a format we read at
|
|
10
|
+
* all; by the time this is raised the input has already been recognised as an `.xlsb`.
|
|
11
|
+
*/
|
|
12
|
+
export class XlsbParseError extends XlsxError {
|
|
13
|
+
name = 'XlsbParseError';
|
|
14
|
+
code = 'malformed-input';
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** One `Xti` ([MS-XLSB] 2.5.163): the span of sheets, in one supporting book, that an `ixti` names. */
|
|
2
|
+
export interface ExternSheetRef {
|
|
3
|
+
readonly supBook: number;
|
|
4
|
+
/** Zero-based sheet index; the span is inclusive. */
|
|
5
|
+
readonly firstSheet: number;
|
|
6
|
+
readonly lastSheet: number;
|
|
7
|
+
}
|
|
8
|
+
/** The workbook-level tables a formula's references and names resolve through. */
|
|
9
|
+
export interface FormulaScope {
|
|
10
|
+
/** Sheet names in workbook (tab) order — what an `Xti`'s sheet indices point into. */
|
|
11
|
+
readonly sheetNames: readonly string[];
|
|
12
|
+
/** The `BrtExternSheet` table, indexed by a 3-D token's `ixti`. */
|
|
13
|
+
readonly externSheets: readonly ExternSheetRef[];
|
|
14
|
+
/** The index of the supporting book that is this workbook, or `undefined` when the file declares a
|
|
15
|
+
* supporting book this reader does not recognise. A workbook with no external links declares
|
|
16
|
+
* exactly one — itself — so this is the ordinary case; anything else leaves the indices untrustworthy
|
|
17
|
+
* and no 3-D reference resolves, which drops those formulas rather than naming the wrong sheet. */
|
|
18
|
+
readonly selfSupBook: number | undefined;
|
|
19
|
+
/** Every `BrtName` in file order, function placeholders included — a `PtgName` cites one by
|
|
20
|
+
* **1-based** index, so filtering this list would misaddress every name reference. */
|
|
21
|
+
readonly names: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/** The cell a `PtgExp`/`PtgTbl` stream defers to: the top-left of the shared or array formula group. */
|
|
24
|
+
export interface FormulaAnchor {
|
|
25
|
+
/** Zero-based. */
|
|
26
|
+
readonly row: number;
|
|
27
|
+
readonly column: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decode a `CellParsedFormula`'s token stream into formula text, in the same on-disk spelling the XML
|
|
31
|
+
* form writes into `<f>` — `_xlfn.`-prefixed function names included, so the caller applies the same
|
|
32
|
+
* `unmangleFunctions` normalisation to either serialisation.
|
|
33
|
+
*
|
|
34
|
+
* @param rgce the token stream.
|
|
35
|
+
* @param rgcb the trailing extra-data block: the array constants, and the cell ranges a precomputed
|
|
36
|
+
* range token refers to. Its entries are consumed in token order.
|
|
37
|
+
* @returns the formula text, or `undefined` if the stream uses a token this reader does not decode.
|
|
38
|
+
* @throws {XlsbParseError} if a token runs past the end of the stream (a malformed formula).
|
|
39
|
+
*/
|
|
40
|
+
export declare function decodeFormula(rgce: Uint8Array, rgcb: Uint8Array, scope: FormulaScope): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* The master cell a token stream defers to, when the stream is nothing but a `PtgExp` (a shared or
|
|
43
|
+
* array formula's member) or a `PtgTbl` (a data-table cell) rather than a formula of its own.
|
|
44
|
+
*
|
|
45
|
+
* The column lives in the extra-data block rather than the token, which is the one place BIFF12
|
|
46
|
+
* splits a single reference across the two halves of a `CellParsedFormula`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formulaAnchor(rgce: Uint8Array, rgcb: Uint8Array): FormulaAnchor | undefined;
|