@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/core/style.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { tokenSet } from '../token-set.js';
|
|
1
|
+
import { tokenSet, tokenSetOf } from '../token-set.js';
|
|
2
|
+
import { cloneWith } from './clone.js';
|
|
3
|
+
import { copyKeyIfPresent } from './containers.js';
|
|
4
|
+
import { NAMED_STYLE_ID } from './internal.js';
|
|
2
5
|
export const isNamedUnderlineStyle = tokenSet({
|
|
3
6
|
none: true,
|
|
4
7
|
single: true,
|
|
@@ -11,27 +14,50 @@ export function parseArgb(value) {
|
|
|
11
14
|
const argb = hex.length === 6 ? `FF${hex}` : hex;
|
|
12
15
|
return /^[0-9a-fA-F]{8}$/.test(argb) ? argb : undefined;
|
|
13
16
|
}
|
|
14
|
-
export const
|
|
15
|
-
none
|
|
16
|
-
solid
|
|
17
|
-
|
|
18
|
-
darkGray
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
export const FILL_PATTERNS_IN_SCHEMA_ORDER = [
|
|
18
|
+
'none',
|
|
19
|
+
'solid',
|
|
20
|
+
'mediumGray',
|
|
21
|
+
'darkGray',
|
|
22
|
+
'lightGray',
|
|
23
|
+
'darkHorizontal',
|
|
24
|
+
'darkVertical',
|
|
25
|
+
'darkDown',
|
|
26
|
+
'darkUp',
|
|
27
|
+
'darkGrid',
|
|
28
|
+
'darkTrellis',
|
|
29
|
+
'lightHorizontal',
|
|
30
|
+
'lightVertical',
|
|
31
|
+
'lightDown',
|
|
32
|
+
'lightUp',
|
|
33
|
+
'lightGrid',
|
|
34
|
+
'lightTrellis',
|
|
35
|
+
'gray125',
|
|
36
|
+
'gray0625',
|
|
37
|
+
];
|
|
38
|
+
export const isFillPatternType = tokenSetOf(FILL_PATTERNS_IN_SCHEMA_ORDER);
|
|
39
|
+
const GRADIENT_STOP_CLONE = { position: 'value', color: 'record' };
|
|
40
|
+
const PATTERN_FILL_CLONE = {
|
|
41
|
+
type: 'value',
|
|
42
|
+
pattern: 'value',
|
|
43
|
+
fgColor: 'record',
|
|
44
|
+
bgColor: 'record',
|
|
45
|
+
};
|
|
46
|
+
const GRADIENT_FILL_CLONE = {
|
|
47
|
+
type: 'value',
|
|
48
|
+
gradient: 'value',
|
|
49
|
+
degree: 'value',
|
|
50
|
+
left: 'value',
|
|
51
|
+
right: 'value',
|
|
52
|
+
top: 'value',
|
|
53
|
+
bottom: 'value',
|
|
54
|
+
stops: (stops) => stops.map((stop) => cloneWith(stop, GRADIENT_STOP_CLONE)),
|
|
55
|
+
};
|
|
56
|
+
export function cloneFill(fill) {
|
|
57
|
+
return fill.type === 'gradient'
|
|
58
|
+
? cloneWith(fill, GRADIENT_FILL_CLONE)
|
|
59
|
+
: cloneWith(fill, PATTERN_FILL_CLONE);
|
|
60
|
+
}
|
|
35
61
|
export const isBorderStyle = tokenSet({
|
|
36
62
|
thin: true,
|
|
37
63
|
medium: true,
|
|
@@ -47,11 +73,42 @@ export const isBorderStyle = tokenSet({
|
|
|
47
73
|
mediumDashDotDot: true,
|
|
48
74
|
slantDashDot: true,
|
|
49
75
|
});
|
|
76
|
+
const BORDER_EDGE_CLONE = { style: 'value', color: 'record' };
|
|
77
|
+
const cloneBorderEdge = (edge) => cloneWith(edge, BORDER_EDGE_CLONE);
|
|
78
|
+
const BORDER_CLONE = {
|
|
79
|
+
left: cloneBorderEdge,
|
|
80
|
+
right: cloneBorderEdge,
|
|
81
|
+
top: cloneBorderEdge,
|
|
82
|
+
bottom: cloneBorderEdge,
|
|
83
|
+
diagonal: cloneBorderEdge,
|
|
84
|
+
diagonalUp: 'value',
|
|
85
|
+
diagonalDown: 'value',
|
|
86
|
+
};
|
|
87
|
+
export function cloneBorder(border) {
|
|
88
|
+
return cloneWith(border, BORDER_CLONE);
|
|
89
|
+
}
|
|
50
90
|
export const isFontVerticalAlignment = tokenSet({
|
|
51
91
|
superscript: true,
|
|
52
92
|
subscript: true,
|
|
53
93
|
});
|
|
54
94
|
export const isFontScheme = tokenSet({ minor: true, major: true, none: true });
|
|
95
|
+
const FONT_CLONE = {
|
|
96
|
+
name: 'value',
|
|
97
|
+
size: 'value',
|
|
98
|
+
family: 'value',
|
|
99
|
+
scheme: 'value',
|
|
100
|
+
charset: 'value',
|
|
101
|
+
color: 'record',
|
|
102
|
+
bold: 'value',
|
|
103
|
+
italic: 'value',
|
|
104
|
+
underline: 'value',
|
|
105
|
+
strike: 'value',
|
|
106
|
+
outline: 'value',
|
|
107
|
+
vertAlign: 'value',
|
|
108
|
+
};
|
|
109
|
+
export function cloneFont(font) {
|
|
110
|
+
return cloneWith(font, FONT_CLONE);
|
|
111
|
+
}
|
|
55
112
|
export const isHorizontalAlignment = tokenSet({
|
|
56
113
|
general: true,
|
|
57
114
|
left: true,
|
|
@@ -93,17 +150,24 @@ const CELL_STYLE_FACET_KEYS = {
|
|
|
93
150
|
protection: true,
|
|
94
151
|
};
|
|
95
152
|
export const CELL_STYLE_FACETS = Object.keys(CELL_STYLE_FACET_KEYS);
|
|
153
|
+
const CELL_CONTENT_ONLY_KEYS = {
|
|
154
|
+
quotePrefix: true,
|
|
155
|
+
[NAMED_STYLE_ID]: true,
|
|
156
|
+
};
|
|
157
|
+
export const CELL_CONTENT_FACETS = [
|
|
158
|
+
...CELL_STYLE_FACETS,
|
|
159
|
+
...Reflect.ownKeys(CELL_CONTENT_ONLY_KEYS),
|
|
160
|
+
];
|
|
161
|
+
export function assignContentFacets(target, source) {
|
|
162
|
+
for (const facet of CELL_CONTENT_FACETS)
|
|
163
|
+
copyKeyIfPresent(target, source, facet);
|
|
164
|
+
}
|
|
96
165
|
export function assignStyleFacets(target, source) {
|
|
97
166
|
for (const facet of CELL_STYLE_FACETS)
|
|
98
|
-
|
|
167
|
+
copyKeyIfPresent(target, source, facet);
|
|
99
168
|
}
|
|
100
169
|
export function pickStyleFacets(source) {
|
|
101
170
|
const facets = {};
|
|
102
171
|
assignStyleFacets(facets, source);
|
|
103
172
|
return facets;
|
|
104
173
|
}
|
|
105
|
-
function copyFacet(target, source, key) {
|
|
106
|
-
const value = source[key];
|
|
107
|
-
if (value !== undefined)
|
|
108
|
-
target[key] = value;
|
|
109
|
-
}
|
package/dist/core/table-style.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, invalidToken, quoted } from '../errors.js';
|
|
2
2
|
export const TABLE_STYLE_ELEMENT_TYPES = [
|
|
3
3
|
'wholeTable',
|
|
4
4
|
'headerRow',
|
|
@@ -46,17 +46,17 @@ export function checkTableStyle(style) {
|
|
|
46
46
|
if (element === undefined)
|
|
47
47
|
continue;
|
|
48
48
|
if (!isTableStyleElementType(type)) {
|
|
49
|
-
throw
|
|
49
|
+
throw invalidToken('table style element', type);
|
|
50
50
|
}
|
|
51
51
|
const { size } = element;
|
|
52
52
|
if (size === undefined)
|
|
53
53
|
continue;
|
|
54
54
|
if (!STRIPE_ELEMENT_TYPES.has(type)) {
|
|
55
|
-
throw new AuthoringError(`table style element
|
|
55
|
+
throw new AuthoringError(`table style element ${quoted(type)} cannot carry a size: band width applies only to ` +
|
|
56
56
|
`${[...STRIPE_ELEMENT_TYPES].join(', ')}`);
|
|
57
57
|
}
|
|
58
58
|
if (!Number.isInteger(size) || size < 1) {
|
|
59
|
-
throw new RangeError(`
|
|
59
|
+
throw new RangeError(`invalid table style band size ${size}: expected a positive integer`);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
package/dist/core/table.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type GridRect } from './address.ts';
|
|
2
|
+
import { type ClonePlan } from './clone.ts';
|
|
3
|
+
import { type AxisSplice } from './grid-shift.ts';
|
|
4
|
+
import type { AssertNever } from './internal.ts';
|
|
2
5
|
import type { CellStyle } from './style.ts';
|
|
3
6
|
import type { CellValue } from './value.ts';
|
|
4
7
|
/** A per-column cell format applied to a table's body cells: the facets Excel's table-column style
|
|
@@ -57,6 +60,16 @@ export interface TableStyleInfo {
|
|
|
57
60
|
/** Band the columns (alternating fill). */
|
|
58
61
|
readonly showColumnStripes?: boolean;
|
|
59
62
|
}
|
|
63
|
+
/** Copy a style, keeping only its defined fields off the literal so `exactOptionalPropertyTypes`
|
|
64
|
+
* never sees a fabricated `key: undefined`: an absent attribute must stay absent across a copy.
|
|
65
|
+
*
|
|
66
|
+
* The sentinel name `"None"` (Excel's table-style gallery entry for *no* style) is normalised to an
|
|
67
|
+
* absent name: OOXML expresses "unstyled" as a `<tableStyleInfo>` with no `name` attribute, so a
|
|
68
|
+
* literal `name="None"` would reference a style that does not exist and make the file suspect. The
|
|
69
|
+
* banding flags set alongside it are untouched. */
|
|
70
|
+
declare const STYLE_INFO_CLONE: ClonePlan<TableStyleInfo>;
|
|
71
|
+
/** The proof that {@link STYLE_INFO_CLONE} names every field of the style. */
|
|
72
|
+
export type EveryTableStyleInfoFieldIsCloned = AssertNever<Exclude<keyof Required<TableStyleInfo>, keyof typeof STYLE_INFO_CLONE>>;
|
|
60
73
|
/**
|
|
61
74
|
* OOXML's totals-row function names (`ST_TotalsRowFunction`) to the `SUBTOTAL` first-argument code
|
|
62
75
|
* Excel writes into a materialised totals cell. The `10x` band ignores manually hidden rows, the
|
|
@@ -158,20 +171,24 @@ export declare class Table {
|
|
|
158
171
|
*/
|
|
159
172
|
addRow(values?: readonly CellValue[]): void;
|
|
160
173
|
/**
|
|
161
|
-
* Re-pin the table through a row splice
|
|
162
|
-
*
|
|
163
|
-
* whole range by `delta`; one landing inside grows or shrinks the data rows to absorb the change;
|
|
174
|
+
* Re-pin the table through a row splice. A splice entirely above the table moves its whole range
|
|
175
|
+
* by the splice's `delta`; one landing inside grows or shrinks the data rows to absorb the change;
|
|
164
176
|
* one that deletes the table's every row removes it. Returns `false` when the table no longer has
|
|
165
177
|
* a row to occupy (the caller drops it), `true` when it survives.
|
|
166
178
|
*/
|
|
167
|
-
shiftRows(
|
|
179
|
+
shiftRows(splice: AxisSplice): boolean;
|
|
168
180
|
/**
|
|
169
|
-
* Re-pin the table through a column splice. A splice entirely to
|
|
170
|
-
* by `delta`; one to its right leaves it untouched
|
|
171
|
-
*
|
|
172
|
-
*
|
|
181
|
+
* Re-pin the table through a column splice, the mirror of {@link shiftRows}. A splice entirely to
|
|
182
|
+
* the table's left moves its anchor by the splice's `delta`; one to its right leaves it untouched;
|
|
183
|
+
* one that deletes the table's every column removes it. Returns `false` when the table no longer
|
|
184
|
+
* has a column to occupy (the caller drops it), `true` when it survives.
|
|
185
|
+
*
|
|
186
|
+
* A splice landing *inside* the table's columns is structural surgery on named columns with no
|
|
187
|
+
* unambiguous answer, so those columns are left as-is rather than fabricated or dropped. Whole-table
|
|
188
|
+
* deletion is not that case: a table left declared over whatever slid into its place, carrying the
|
|
189
|
+
* names of columns that no longer exist, is content the writer then emits.
|
|
173
190
|
*/
|
|
174
|
-
shiftColumns(
|
|
191
|
+
shiftColumns(splice: AxisSplice): boolean;
|
|
175
192
|
/**
|
|
176
193
|
* The options that reconstruct this table: the anchor as a single-cell ref (not the derived
|
|
177
194
|
* full range), the columns, and the data-row count with the header/totals flags. Feeding this
|
|
@@ -194,3 +211,4 @@ export declare class Table {
|
|
|
194
211
|
/** The occupied rectangle, for conflict checks such as overlapping merges. */
|
|
195
212
|
get region(): TableRegion;
|
|
196
213
|
}
|
|
214
|
+
export {};
|
package/dist/core/table.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
2
|
import { tokenSet } from '../token-set.js';
|
|
3
|
-
import { decodeCellRef, encodeAddress } from './address.js';
|
|
3
|
+
import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, numberToColumn, } from './address.js';
|
|
4
|
+
import { cloneWith } from './clone.js';
|
|
4
5
|
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
6
|
+
import { MAX_TABLE_NAME_LENGTH, TABLE_NAME_PATTERN } from './limits.js';
|
|
7
|
+
const STYLE_INFO_CLONE = {
|
|
8
|
+
name: (name) => (name === 'None' ? undefined : name),
|
|
9
|
+
showFirstColumn: 'value',
|
|
10
|
+
showLastColumn: 'value',
|
|
11
|
+
showRowStripes: 'value',
|
|
12
|
+
showColumnStripes: 'value',
|
|
13
|
+
};
|
|
5
14
|
function cloneStyleInfo(style) {
|
|
6
|
-
const clone =
|
|
7
|
-
if (
|
|
8
|
-
clone.name
|
|
9
|
-
if (style.showFirstColumn !== undefined)
|
|
10
|
-
clone.showFirstColumn = style.showFirstColumn;
|
|
11
|
-
if (style.showLastColumn !== undefined)
|
|
12
|
-
clone.showLastColumn = style.showLastColumn;
|
|
13
|
-
if (style.showRowStripes !== undefined)
|
|
14
|
-
clone.showRowStripes = style.showRowStripes;
|
|
15
|
-
if (style.showColumnStripes !== undefined)
|
|
16
|
-
clone.showColumnStripes = style.showColumnStripes;
|
|
15
|
+
const clone = cloneWith(style, STYLE_INFO_CLONE);
|
|
16
|
+
if (clone.name === undefined)
|
|
17
|
+
delete clone.name;
|
|
17
18
|
return clone;
|
|
18
19
|
}
|
|
19
20
|
export const TOTALS_ROW_SUBTOTAL_CODE = {
|
|
@@ -38,7 +39,6 @@ export const isTotalsRowFunction = tokenSet({
|
|
|
38
39
|
custom: true,
|
|
39
40
|
none: true,
|
|
40
41
|
});
|
|
41
|
-
const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
|
|
42
42
|
function disambiguateColumnNames(columns) {
|
|
43
43
|
const seen = new Set();
|
|
44
44
|
return columns.map((column) => {
|
|
@@ -50,11 +50,11 @@ function disambiguateColumnNames(columns) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
function validateTableName(name) {
|
|
53
|
-
if (name.length === 0 || name.length >
|
|
54
|
-
throw new RangeError(`table name ${
|
|
53
|
+
if (name.length === 0 || name.length > MAX_TABLE_NAME_LENGTH) {
|
|
54
|
+
throw new RangeError(`table name ${quoted(name)} must be between 1 and ${MAX_TABLE_NAME_LENGTH} characters`);
|
|
55
55
|
}
|
|
56
|
-
if (!
|
|
57
|
-
throw new SyntaxError(`table name ${
|
|
56
|
+
if (!TABLE_NAME_PATTERN.test(name)) {
|
|
57
|
+
throw new SyntaxError(`table name ${quoted(name)} is not a valid Excel identifier: it must start with a letter, ` +
|
|
58
58
|
'underscore, or backslash and contain only letters, digits, periods, and underscores');
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -74,17 +74,17 @@ export class Table {
|
|
|
74
74
|
constructor(options, grid) {
|
|
75
75
|
validateTableName(options.name);
|
|
76
76
|
if (options.columns.length === 0) {
|
|
77
|
-
throw new AuthoringError(`table
|
|
77
|
+
throw new AuthoringError(`table ${quoted(options.name)} must declare at least one column`);
|
|
78
78
|
}
|
|
79
79
|
if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
|
|
80
|
-
throw new RangeError(`table
|
|
80
|
+
throw new RangeError(`table ${quoted(options.name)} has an invalid data-row count (${options.rowCount})`);
|
|
81
81
|
}
|
|
82
82
|
let anchor;
|
|
83
83
|
try {
|
|
84
84
|
anchor = decodeCellRef(options.ref);
|
|
85
85
|
}
|
|
86
86
|
catch (cause) {
|
|
87
|
-
throw new SyntaxError(`table ref
|
|
87
|
+
throw new SyntaxError(`table ref ${quoted(options.ref)} must anchor at a single cell (e.g. "A1")`, {
|
|
88
88
|
cause,
|
|
89
89
|
});
|
|
90
90
|
}
|
|
@@ -102,7 +102,15 @@ export class Table {
|
|
|
102
102
|
this.#dataRowCount = options.rowCount;
|
|
103
103
|
this.#grid = grid;
|
|
104
104
|
if (this.#rowSpan < 1) {
|
|
105
|
-
throw new AuthoringError(`table
|
|
105
|
+
throw new AuthoringError(`table ${quoted(this.name)} has no rows: it needs a header row or at least one data row`);
|
|
106
|
+
}
|
|
107
|
+
if (this.#right > MAX_COLUMN) {
|
|
108
|
+
throw new RangeError(`table ${quoted(this.name)} spans ${this.columns.length} columns from ` +
|
|
109
|
+
`${numberToColumn(this.#anchorCol)}, past the last column (${numberToColumn(MAX_COLUMN)})`);
|
|
110
|
+
}
|
|
111
|
+
if (this.#bottom > MAX_ROW) {
|
|
112
|
+
throw new RangeError(`table ${quoted(this.name)} spans ${this.#rowSpan} rows from ${this.#anchorRow}, ` +
|
|
113
|
+
`past the last row (${MAX_ROW})`);
|
|
106
114
|
}
|
|
107
115
|
if (grid !== undefined)
|
|
108
116
|
this.#materializeFrame(grid);
|
|
@@ -115,13 +123,13 @@ export class Table {
|
|
|
115
123
|
}
|
|
116
124
|
addRow(values = []) {
|
|
117
125
|
if (values.length > this.columnCount) {
|
|
118
|
-
throw new RangeError(`row has ${values.length} values but table
|
|
126
|
+
throw new RangeError(`row has ${values.length} values but table ${quoted(this.name)} has ${this.columnCount} columns`);
|
|
119
127
|
}
|
|
120
128
|
const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
|
|
121
129
|
const grid = this.#grid;
|
|
122
130
|
if (this.totalsRow) {
|
|
123
131
|
if (grid === undefined) {
|
|
124
|
-
throw new AuthoringError(`table
|
|
132
|
+
throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot relocate its totals row to append a data row`);
|
|
125
133
|
}
|
|
126
134
|
grid.insertRow(target);
|
|
127
135
|
this.#writeRow(grid, target, values);
|
|
@@ -129,7 +137,7 @@ export class Table {
|
|
|
129
137
|
}
|
|
130
138
|
if (values.length > 0) {
|
|
131
139
|
if (grid === undefined) {
|
|
132
|
-
throw new AuthoringError(`table
|
|
140
|
+
throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot write appended row values`);
|
|
133
141
|
}
|
|
134
142
|
this.#writeRow(grid, target, values);
|
|
135
143
|
}
|
|
@@ -174,11 +182,11 @@ export class Table {
|
|
|
174
182
|
});
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
|
-
shiftRows(
|
|
178
|
-
if (isDeletedSpan(this.#anchorRow, this.#bottom,
|
|
185
|
+
shiftRows(splice) {
|
|
186
|
+
if (isDeletedSpan(this.#anchorRow, this.#bottom, splice))
|
|
179
187
|
return false;
|
|
180
|
-
const top = shiftIndex(this.#anchorRow,
|
|
181
|
-
const bottom = shiftIndex(this.#bottom,
|
|
188
|
+
const top = shiftIndex(this.#anchorRow, splice);
|
|
189
|
+
const bottom = shiftIndex(this.#bottom, splice);
|
|
182
190
|
const span = bottom - top + 1;
|
|
183
191
|
const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
|
|
184
192
|
const dataRows = span - fixedRows;
|
|
@@ -188,9 +196,13 @@ export class Table {
|
|
|
188
196
|
this.#dataRowCount = dataRows;
|
|
189
197
|
return true;
|
|
190
198
|
}
|
|
191
|
-
shiftColumns(
|
|
192
|
-
if (this.#anchorCol
|
|
193
|
-
|
|
199
|
+
shiftColumns(splice) {
|
|
200
|
+
if (isDeletedSpan(this.#anchorCol, this.#right, splice))
|
|
201
|
+
return false;
|
|
202
|
+
const anchor = shiftIndex(this.#anchorCol, splice);
|
|
203
|
+
if (anchor + this.columns.length - 1 > MAX_COLUMN)
|
|
204
|
+
return false;
|
|
205
|
+
this.#anchorCol = anchor;
|
|
194
206
|
return true;
|
|
195
207
|
}
|
|
196
208
|
get options() {
|
|
@@ -211,13 +223,18 @@ export class Table {
|
|
|
211
223
|
return options;
|
|
212
224
|
}
|
|
213
225
|
get range() {
|
|
214
|
-
return
|
|
226
|
+
return encodeRect({
|
|
227
|
+
top: this.#anchorRow,
|
|
228
|
+
left: this.#anchorCol,
|
|
229
|
+
bottom: this.#bottom,
|
|
230
|
+
right: this.#right,
|
|
231
|
+
});
|
|
215
232
|
}
|
|
216
233
|
get autoFilterRef() {
|
|
217
234
|
if (!this.autoFilter)
|
|
218
235
|
return undefined;
|
|
219
236
|
const bottom = this.#anchorRow + this.#dataRowCount;
|
|
220
|
-
return
|
|
237
|
+
return encodeRect({ top: this.#anchorRow, left: this.#anchorCol, bottom, right: this.#right });
|
|
221
238
|
}
|
|
222
239
|
get region() {
|
|
223
240
|
return { top: this.#anchorRow, left: this.#anchorCol, bottom: this.#bottom, right: this.#right };
|
package/dist/core/theme.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
2
|
+
import { parseArgb } from './style.js';
|
|
1
3
|
export const THEME_COLOR_SLOTS = [
|
|
2
4
|
'lt1',
|
|
3
5
|
'dk1',
|
|
@@ -36,10 +38,9 @@ export const DEFAULT_THEME_FONTS = {
|
|
|
36
38
|
minor: OFFICE_BODY_FACE,
|
|
37
39
|
};
|
|
38
40
|
export function normalizeThemeColor(value) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new SyntaxError(`Invalid theme colour ${JSON.stringify(value)}: expected 6 hexadecimal digits (RRGGBB)`);
|
|
41
|
+
const argb = parseArgb(value);
|
|
42
|
+
if (argb === undefined) {
|
|
43
|
+
throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected 6 hexadecimal digits (RRGGBB)`);
|
|
43
44
|
}
|
|
44
|
-
return
|
|
45
|
+
return argb.slice(2).toUpperCase();
|
|
45
46
|
}
|
package/dist/core/value.d.ts
CHANGED
|
@@ -15,6 +15,15 @@ export type ValueType = (typeof ValueType)[keyof typeof ValueType];
|
|
|
15
15
|
/** The canonical Excel error literals a cell (or formula result) can carry. */
|
|
16
16
|
export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#SPILL!', '#CALC!', '#GETTING_DATA'];
|
|
17
17
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
18
|
+
/**
|
|
19
|
+
* The error a spreadsheet puts in place of a reference that has nowhere left to point.
|
|
20
|
+
*
|
|
21
|
+
* Named here rather than in the two formula codecs that produce it. Both of them -- the A1 renderer
|
|
22
|
+
* in `core/formula.ts` and the BIFF12 one in `io/xlsb/formula.ts` -- had a private literal of their
|
|
23
|
+
* own, spelled the same and named differently, for a value {@link ERROR_CODES} already publishes and
|
|
24
|
+
* `isErrorCode` already recognises.
|
|
25
|
+
*/
|
|
26
|
+
export declare const REF_ERROR: ErrorCode;
|
|
18
27
|
/** An in-cell error, e.g. `{error: '#REF!'}`. */
|
|
19
28
|
export interface ErrorValue {
|
|
20
29
|
readonly error: ErrorCode;
|
package/dist/core/value.js
CHANGED
|
@@ -21,6 +21,7 @@ export const ERROR_CODES = [
|
|
|
21
21
|
'#CALC!',
|
|
22
22
|
'#GETTING_DATA',
|
|
23
23
|
];
|
|
24
|
+
export const REF_ERROR = '#REF!';
|
|
24
25
|
const ERROR_SET = new Set(ERROR_CODES);
|
|
25
26
|
function hasKey(value, key) {
|
|
26
27
|
return typeof value === 'object' && value !== null && key in value;
|
|
@@ -47,58 +48,59 @@ export function richTextToPlain(value) {
|
|
|
47
48
|
return value.richText.map((run) => run.text).join('');
|
|
48
49
|
}
|
|
49
50
|
export function cellValueToText(value) {
|
|
50
|
-
|
|
51
|
-
return '';
|
|
52
|
-
switch (typeof value) {
|
|
53
|
-
case 'number':
|
|
54
|
-
return String(value);
|
|
55
|
-
case 'string':
|
|
56
|
-
return value;
|
|
57
|
-
case 'boolean':
|
|
58
|
-
return value ? 'TRUE' : 'FALSE';
|
|
59
|
-
default:
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
if (value instanceof Date)
|
|
63
|
-
return Number.isNaN(value.getTime()) ? '' : value.toISOString();
|
|
64
|
-
if (isHyperlinkValue(value)) {
|
|
65
|
-
return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
|
|
66
|
-
}
|
|
67
|
-
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
68
|
-
return value.result === undefined ? '' : cellValueToText(value.result);
|
|
69
|
-
}
|
|
70
|
-
if (isRichTextValue(value))
|
|
71
|
-
return richTextToPlain(value);
|
|
72
|
-
if (isErrorValue(value))
|
|
73
|
-
return value.error;
|
|
74
|
-
return unsupportedValue(value);
|
|
51
|
+
return classify(value, TO_TEXT);
|
|
75
52
|
}
|
|
76
53
|
export function detectValueType(value) {
|
|
54
|
+
return classify(value, TO_TYPE);
|
|
55
|
+
}
|
|
56
|
+
function classify(value, visit) {
|
|
77
57
|
if (value === null)
|
|
78
|
-
return
|
|
58
|
+
return visit.null(value);
|
|
79
59
|
switch (typeof value) {
|
|
80
60
|
case 'number':
|
|
81
|
-
return
|
|
61
|
+
return visit.number(value);
|
|
82
62
|
case 'string':
|
|
83
|
-
return
|
|
63
|
+
return visit.string(value);
|
|
84
64
|
case 'boolean':
|
|
85
|
-
return
|
|
65
|
+
return visit.boolean(value);
|
|
86
66
|
default:
|
|
87
67
|
break;
|
|
88
68
|
}
|
|
89
69
|
if (value instanceof Date)
|
|
90
|
-
return
|
|
70
|
+
return visit.date(value);
|
|
91
71
|
if (isHyperlinkValue(value))
|
|
92
|
-
return
|
|
72
|
+
return visit.hyperlink(value);
|
|
93
73
|
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
94
|
-
return
|
|
74
|
+
return visit.formula(value);
|
|
95
75
|
}
|
|
96
76
|
if (isRichTextValue(value))
|
|
97
|
-
return
|
|
77
|
+
return visit.richText(value);
|
|
98
78
|
if (isErrorValue(value))
|
|
99
|
-
return
|
|
79
|
+
return visit.error(value);
|
|
100
80
|
return unsupportedValue(value);
|
|
101
81
|
}
|
|
82
|
+
const TO_TYPE = {
|
|
83
|
+
null: () => ValueType.Null,
|
|
84
|
+
number: () => ValueType.Number,
|
|
85
|
+
string: () => ValueType.String,
|
|
86
|
+
boolean: () => ValueType.Boolean,
|
|
87
|
+
date: () => ValueType.Date,
|
|
88
|
+
error: () => ValueType.Error,
|
|
89
|
+
formula: () => ValueType.Formula,
|
|
90
|
+
richText: () => ValueType.RichText,
|
|
91
|
+
hyperlink: () => ValueType.Hyperlink,
|
|
92
|
+
};
|
|
93
|
+
const TO_TEXT = {
|
|
94
|
+
null: () => '',
|
|
95
|
+
number: (value) => String(value),
|
|
96
|
+
string: (value) => value,
|
|
97
|
+
boolean: (value) => (value ? 'TRUE' : 'FALSE'),
|
|
98
|
+
date: (value) => (Number.isNaN(value.getTime()) ? '' : value.toISOString()),
|
|
99
|
+
error: (value) => value.error,
|
|
100
|
+
formula: (value) => (value.result === undefined ? '' : cellValueToText(value.result)),
|
|
101
|
+
richText: richTextToPlain,
|
|
102
|
+
hyperlink: (value) => (typeof value.text === 'string' ? value.text : richTextToPlain(value.text)),
|
|
103
|
+
};
|
|
102
104
|
function unsupportedValue(value) {
|
|
103
105
|
throw new TypeError(`unsupported cell value: ${describe(value)}`);
|
|
104
106
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type WorkbookImage } from './image.ts';
|
|
2
|
+
export declare class WorkbookMedia {
|
|
3
|
+
#private;
|
|
4
|
+
/** The registered images, indexed by the id {@link register} returned. Live, not a copy. */
|
|
5
|
+
get all(): readonly WorkbookImage[];
|
|
6
|
+
/** Look up a registered image by its id, or `undefined` if no image carries that id. */
|
|
7
|
+
get(id: number): WorkbookImage | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* {@link get} for a caller that has no answer for an absent id.
|
|
10
|
+
*
|
|
11
|
+
* An unregistered id is what a sheet belonging to *another* workbook looks like from here, which
|
|
12
|
+
* is why the message names the sheet rather than the id alone. Emitting a package with a drawing
|
|
13
|
+
* pointing at media nobody registered is the silently-broken-image failure this refuses to start.
|
|
14
|
+
*
|
|
15
|
+
* @throws {AuthoringError} if no image carries that id.
|
|
16
|
+
*/
|
|
17
|
+
require(id: number, forSheetName: string): WorkbookImage;
|
|
18
|
+
/** Store a picture's bytes and return the id that names them. */
|
|
19
|
+
register(extension: string | undefined, buffer: Uint8Array): number;
|
|
20
|
+
/**
|
|
21
|
+
* Register a picture arriving from elsewhere, re-using an identical one already held.
|
|
22
|
+
*
|
|
23
|
+
* The extension is re-normalised rather than trusted: a hand-built {@link WorkbookImage} may carry
|
|
24
|
+
* `".PNG"` where the registry holds `"png"`, and two spellings of one kind must not read as two
|
|
25
|
+
* pictures.
|
|
26
|
+
*
|
|
27
|
+
* Through the content index rather than a scan. Comparing byte-by-byte against every held picture
|
|
28
|
+
* made importing n distinct images cost n squared byte comparisons: fifty 1 MB pictures carried
|
|
29
|
+
* between workbooks compared about 2.5 GB. The *rule* is unchanged, and `imageContentKey` states
|
|
30
|
+
* why identity here is content and never object identity. The index is built in reverse so the
|
|
31
|
+
* FIRST id registered under a key wins, which is the answer the scan gave.
|
|
32
|
+
*/
|
|
33
|
+
registerExisting(image: WorkbookImage): number;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
|
+
import { imageContentKey, normalizeImageExtension } from './image.js';
|
|
3
|
+
export class WorkbookMedia {
|
|
4
|
+
#images = [];
|
|
5
|
+
#byContent;
|
|
6
|
+
get all() {
|
|
7
|
+
return this.#images;
|
|
8
|
+
}
|
|
9
|
+
get(id) {
|
|
10
|
+
return this.#images[id];
|
|
11
|
+
}
|
|
12
|
+
require(id, forSheetName) {
|
|
13
|
+
const image = this.#images[id];
|
|
14
|
+
if (image === undefined) {
|
|
15
|
+
throw new AuthoringError(`worksheet ${quoted(forSheetName)} shows image id ${id}, which is not registered on this ` +
|
|
16
|
+
"workbook: a sheet's images can only be exported by the workbook that holds them");
|
|
17
|
+
}
|
|
18
|
+
return image;
|
|
19
|
+
}
|
|
20
|
+
register(extension, buffer) {
|
|
21
|
+
const image = {
|
|
22
|
+
extension: normalizeImageExtension(extension, buffer),
|
|
23
|
+
data: buffer,
|
|
24
|
+
};
|
|
25
|
+
this.#images.push(image);
|
|
26
|
+
const id = this.#images.length - 1;
|
|
27
|
+
const index = this.#byContent;
|
|
28
|
+
if (index !== undefined) {
|
|
29
|
+
const key = imageContentKey(image);
|
|
30
|
+
if (!index.has(key))
|
|
31
|
+
index.set(key, id);
|
|
32
|
+
}
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
registerExisting(image) {
|
|
36
|
+
const candidate = {
|
|
37
|
+
extension: normalizeImageExtension(image.extension, image.data),
|
|
38
|
+
data: image.data,
|
|
39
|
+
};
|
|
40
|
+
this.#byContent ??= new Map(this.#images.map((held, id) => [imageContentKey(held), id]).reverse());
|
|
41
|
+
return (this.#byContent.get(imageContentKey(candidate)) ??
|
|
42
|
+
this.register(candidate.extension, candidate.data));
|
|
43
|
+
}
|
|
44
|
+
}
|