@shbernal/ts-xlsx 2.1.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 +5 -4
- package/dist/core/autofilter.js +34 -35
- package/dist/core/axis-handle.d.ts +10 -0
- package/dist/core/axis-handle.js +17 -0
- package/dist/core/cell.d.ts +25 -0
- package/dist/core/cell.js +9 -3
- 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 +5 -1
- package/dist/core/column.js +34 -35
- 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 +14 -6
- package/dist/core/conditional-formatting.js +45 -43
- 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 +9 -3
- package/dist/core/data-validation.js +21 -18
- 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 +14 -10
- package/dist/core/image.js +9 -9
- 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-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +5 -14
- package/dist/core/merge.js +18 -27
- package/dist/core/page-setup.d.ts +72 -2
- package/dist/core/page-setup.js +34 -8
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +67 -53
- 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 +5 -1
- package/dist/core/row.js +22 -23
- package/dist/core/style.d.ts +134 -75
- package/dist/core/style.js +112 -53
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +28 -10
- package/dist/core/table.js +54 -39
- package/dist/core/theme.js +6 -5
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- 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 +72 -12
- package/dist/core/worksheet.js +68 -70
- package/dist/customui/ribbon.js +6 -7
- 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 +15 -1
- 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 +10 -0
- package/dist/io/opc/part-paths.js +16 -1
- package/dist/io/opc/read-opc.d.ts +69 -10
- package/dist/io/opc/read-opc.js +49 -55
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/rels.d.ts +1 -6
- package/dist/io/opc/rels.js +1 -4
- 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 +48 -2
- package/dist/io/style/xf-style.js +14 -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 +50 -83
- 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 +47 -29
- 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 +9 -2
- 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 -31
- package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
- package/dist/io/xlsx/conditional-formatting.js +51 -46
- package/dist/io/xlsx/data-validation.d.ts +9 -7
- package/dist/io/xlsx/data-validation.js +38 -35
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +15 -29
- 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 +41 -7
- package/dist/io/xlsx/hyperlinks.js +40 -28
- package/dist/io/xlsx/images.js +24 -25
- 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 +23 -3
- package/dist/io/xlsx/package-plan.js +66 -25
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- 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 +3 -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.d.ts → read-rich-runs.d.ts} +1 -1
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +50 -37
- package/dist/io/xlsx/read-shared-strings.js +5 -3
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +65 -109
- 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 +11 -1
- package/dist/io/xlsx/read-worksheet.js +51 -193
- package/dist/io/xlsx/read.d.ts +5 -13
- package/dist/io/xlsx/read.js +111 -436
- 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 +166 -37
- 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 -204
- package/dist/io/xlsx/tables.js +11 -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 +3 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +84 -57
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +59 -242
- package/dist/io/xlsx/write-stream.d.ts +71 -16
- package/dist/io/xlsx/write-stream.js +79 -41
- package/dist/io/xlsx/write.d.ts +6 -2
- package/dist/io/xlsx/write.js +217 -153
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +28 -0
- package/dist/token-set.js +7 -0
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +32 -0
- package/dist/vba/cfb-format.js +13 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +28 -25
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -57
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +79 -23
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +40 -21
- package/dist/vba/vba-encoding.js +5 -6
- 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 +128 -81
- package/dist/xml/xml-read.js +161 -178
- package/dist/xml/xml-scan.d.ts +78 -0
- package/dist/xml/xml-scan.js +180 -0
- package/dist/xml/xml.d.ts +29 -28
- package/dist/xml/xml.js +24 -24
- package/package.json +13 -9
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const MONTH_NAMES = [
|
|
2
|
+
'January',
|
|
3
|
+
'February',
|
|
4
|
+
'March',
|
|
5
|
+
'April',
|
|
6
|
+
'May',
|
|
7
|
+
'June',
|
|
8
|
+
'July',
|
|
9
|
+
'August',
|
|
10
|
+
'September',
|
|
11
|
+
'October',
|
|
12
|
+
'November',
|
|
13
|
+
'December',
|
|
14
|
+
];
|
|
15
|
+
const WEEKDAY_NAMES = [
|
|
16
|
+
'Sunday',
|
|
17
|
+
'Monday',
|
|
18
|
+
'Tuesday',
|
|
19
|
+
'Wednesday',
|
|
20
|
+
'Thursday',
|
|
21
|
+
'Friday',
|
|
22
|
+
'Saturday',
|
|
23
|
+
];
|
|
24
|
+
function formatRuns(code) {
|
|
25
|
+
const runs = [];
|
|
26
|
+
const literal = (text) => {
|
|
27
|
+
if (text !== '')
|
|
28
|
+
runs.push({ kind: 'literal', text, count: text.length });
|
|
29
|
+
};
|
|
30
|
+
for (let i = 0; i < code.length; i++) {
|
|
31
|
+
const ch = code[i];
|
|
32
|
+
if (ch === ';')
|
|
33
|
+
break;
|
|
34
|
+
if (ch === '[') {
|
|
35
|
+
const end = code.indexOf(']', i);
|
|
36
|
+
i = end === -1 ? code.length : end;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch === '"') {
|
|
40
|
+
const end = code.indexOf('"', i + 1);
|
|
41
|
+
literal(code.slice(i + 1, end === -1 ? code.length : end));
|
|
42
|
+
i = end === -1 ? code.length : end;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === '\\') {
|
|
46
|
+
literal(code[i + 1] ?? '');
|
|
47
|
+
i += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const fraction = /^\.0+/.exec(code.slice(i));
|
|
51
|
+
if (fraction !== null && runs.at(-1)?.text === 's') {
|
|
52
|
+
runs.push({ kind: 'placeholder', text: '.0', count: fraction[0].length - 1 });
|
|
53
|
+
i += fraction[0].length - 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const lower = ch.toLowerCase();
|
|
57
|
+
if (lower === 'y' || lower === 'm' || lower === 'd' || lower === 'h' || lower === 's') {
|
|
58
|
+
let count = 1;
|
|
59
|
+
while (code[i + count]?.toLowerCase() === lower)
|
|
60
|
+
count += 1;
|
|
61
|
+
runs.push({ kind: 'placeholder', text: lower, count });
|
|
62
|
+
i += count - 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const meridiem = /^(AM\/PM|A\/P)/i.exec(code.slice(i));
|
|
66
|
+
if (meridiem !== null) {
|
|
67
|
+
runs.push({ kind: 'placeholder', text: 'am/pm', count: meridiem[0].length });
|
|
68
|
+
i += meridiem[0].length - 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
literal(ch);
|
|
72
|
+
}
|
|
73
|
+
return runs;
|
|
74
|
+
}
|
|
75
|
+
function isMinuteRun(runs, index) {
|
|
76
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
77
|
+
const run = runs[i];
|
|
78
|
+
if (run.kind === 'literal')
|
|
79
|
+
continue;
|
|
80
|
+
if (run.text === 'h')
|
|
81
|
+
return true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
for (let i = index + 1; i < runs.length; i++) {
|
|
85
|
+
const run = runs[i];
|
|
86
|
+
if (run.kind === 'literal')
|
|
87
|
+
continue;
|
|
88
|
+
return run.text === 's';
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
export function formatSerialDate(date, code, utc) {
|
|
93
|
+
if (Number.isNaN(date.getTime()))
|
|
94
|
+
return '';
|
|
95
|
+
const runs = formatRuns(code);
|
|
96
|
+
const year = utc ? date.getUTCFullYear() : date.getFullYear();
|
|
97
|
+
const month = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
|
|
98
|
+
const day = utc ? date.getUTCDate() : date.getDate();
|
|
99
|
+
const weekday = utc ? date.getUTCDay() : date.getDay();
|
|
100
|
+
const hour24 = utc ? date.getUTCHours() : date.getHours();
|
|
101
|
+
const minute = utc ? date.getUTCMinutes() : date.getMinutes();
|
|
102
|
+
const second = utc ? date.getUTCSeconds() : date.getSeconds();
|
|
103
|
+
const twelveHour = runs.some((run) => run.kind === 'placeholder' && run.text === 'am/pm');
|
|
104
|
+
const hour = twelveHour ? hour24 % 12 || 12 : hour24;
|
|
105
|
+
const pad = (value, width) => String(value).padStart(width, '0');
|
|
106
|
+
let out = '';
|
|
107
|
+
for (const [index, run] of runs.entries()) {
|
|
108
|
+
if (run.kind === 'literal') {
|
|
109
|
+
out += run.text;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
switch (run.text) {
|
|
113
|
+
case 'y':
|
|
114
|
+
out += run.count <= 2 ? pad(year % 100, 2) : String(year);
|
|
115
|
+
break;
|
|
116
|
+
case 'm':
|
|
117
|
+
if (isMinuteRun(runs, index)) {
|
|
118
|
+
out += run.count >= 2 ? pad(minute, 2) : String(minute);
|
|
119
|
+
}
|
|
120
|
+
else if (run.count >= 5) {
|
|
121
|
+
out += MONTH_NAMES[month - 1][0];
|
|
122
|
+
}
|
|
123
|
+
else if (run.count === 4) {
|
|
124
|
+
out += MONTH_NAMES[month - 1];
|
|
125
|
+
}
|
|
126
|
+
else if (run.count === 3) {
|
|
127
|
+
out += MONTH_NAMES[month - 1].slice(0, 3);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
out += run.count === 2 ? pad(month, 2) : String(month);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'd':
|
|
134
|
+
if (run.count >= 4)
|
|
135
|
+
out += WEEKDAY_NAMES[weekday];
|
|
136
|
+
else if (run.count === 3)
|
|
137
|
+
out += WEEKDAY_NAMES[weekday].slice(0, 3);
|
|
138
|
+
else
|
|
139
|
+
out += run.count === 2 ? pad(day, 2) : String(day);
|
|
140
|
+
break;
|
|
141
|
+
case 'h':
|
|
142
|
+
out += run.count >= 2 ? pad(hour, 2) : String(hour);
|
|
143
|
+
break;
|
|
144
|
+
case 's':
|
|
145
|
+
out += run.count >= 2 ? pad(second, 2) : String(second);
|
|
146
|
+
break;
|
|
147
|
+
case '.0': {
|
|
148
|
+
const digits = pad(utc ? date.getUTCMilliseconds() : date.getMilliseconds(), 3);
|
|
149
|
+
out += `.${(digits + '000').slice(0, run.count)}`;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'am/pm':
|
|
153
|
+
out += hour24 < 12 ? 'AM' : 'PM';
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
package/dist/core/date.d.ts
CHANGED
|
@@ -1,20 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which date system a workbook counts its serials in: the `date1904` flag of `<workbookPr>`.
|
|
3
|
+
*
|
|
4
|
+
* Carried as the epoch year rather than as a boolean because it is what every conversion here
|
|
5
|
+
* actually needs, and a boolean would mean a `? 1904 : 1900` at each of them: five chances to write
|
|
6
|
+
* the ternary backwards, on a value whose wrongness is silent by construction (a date read under the
|
|
7
|
+
* wrong system is still a perfectly good date, four years off).
|
|
8
|
+
*/
|
|
9
|
+
export type DateEpoch = 1900 | 1904;
|
|
1
10
|
/**
|
|
2
11
|
* The number format applied to a `Date` cell that carries no explicit format of its own,
|
|
3
12
|
* so the value renders, and reads back, as a date rather than a bare serial number.
|
|
4
13
|
*/
|
|
5
14
|
export declare const DEFAULT_DATE_NUMFMT = "yyyy-mm-dd";
|
|
6
15
|
/**
|
|
7
|
-
* Convert a JS `Date` to its
|
|
8
|
-
* quirk so the value renders on the calendar date Excel would show.
|
|
9
|
-
* carry the time of day. The caller must reject a non-finite (invalid) date
|
|
16
|
+
* Convert a JS `Date` to its Excel serial under the workbook's date system, reproducing the
|
|
17
|
+
* phantom-leap-day quirk (1900 only) so the value renders on the calendar date Excel would show.
|
|
18
|
+
* Fractional serials carry the time of day. The caller must reject a non-finite (invalid) date
|
|
19
|
+
* before here.
|
|
20
|
+
*/
|
|
21
|
+
export declare function dateToSerial(date: Date, epoch: DateEpoch): number;
|
|
22
|
+
/**
|
|
23
|
+
* Convert an Excel serial back to a UTC `Date` under the workbook's date system, accounting in the
|
|
24
|
+
* 1900 system for the phantom 1900-02-29 so serial 1 reads as 1900-01-01 (not 1899-12-31) and
|
|
25
|
+
* consecutive serials map to consecutive days.
|
|
10
26
|
*/
|
|
11
|
-
export declare function
|
|
27
|
+
export declare function serialToDate(serial: number, epoch: DateEpoch): Date;
|
|
12
28
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
29
|
+
* Parse a date-bearing text field (a Strict-mode `t="d"` cell value, a core-property timestamp) to a
|
|
30
|
+
* `Date`, or `null` when it carries nothing parseable.
|
|
31
|
+
*
|
|
32
|
+
* The `null` is the point. `new Date('not-a-date')` is a `Date` whose time is `NaN`: it satisfies
|
|
33
|
+
* `instanceof Date` and every guard downstream, survives into the model, and reaches serialisation,
|
|
34
|
+
* where it writes back as the literal string `Invalid Date` and loses the value for good. Dropping it
|
|
35
|
+
* at the boundary is the only reading that cannot lie.
|
|
16
36
|
*/
|
|
17
|
-
export declare function
|
|
37
|
+
export declare function parseDateText(text: string): Date | null;
|
|
18
38
|
/**
|
|
19
39
|
* Whether a number-format code renders its value as a date or time. A format is a date
|
|
20
40
|
* format when, once its non-formatting sections are removed (bracketed color/locale/
|
|
@@ -23,3 +43,18 @@ export declare function serialToDate(serial: number): Date;
|
|
|
23
43
|
* while `yyyy-mm-dd`, `dd/mm/yyyy`, and `[$-409]mmmm d, yyyy` are.
|
|
24
44
|
*/
|
|
25
45
|
export declare function isDateFormat(code: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Surface a number stored under a date format as a `Date`, and leave everything else alone.
|
|
48
|
+
*
|
|
49
|
+
* OOXML has no date type: a date is a number plus a number format that renders it as one, so this
|
|
50
|
+
* one test is what separates `45000` from `2023-03-15` across every reader. Only a plain number
|
|
51
|
+
* qualifies; a string, a boolean or a formula result of another kind under a date format keeps its
|
|
52
|
+
* own kind.
|
|
53
|
+
*
|
|
54
|
+
* One function rather than three copies of the test, because the corpus asserts the rule is identical
|
|
55
|
+
* across serialisations: the `.xlsb` reader, the `.xlsx` cell decoder and the cached formula-result
|
|
56
|
+
* decoder must all answer the same, and three spellings of one rule are three chances to disagree.
|
|
57
|
+
* The workbook's {@link DateEpoch} rides along for exactly that reason: it is the one input to this
|
|
58
|
+
* test that is not the cell's, and the three used to agree on the wrong answer for a 1904 file.
|
|
59
|
+
*/
|
|
60
|
+
export declare function coerceDateSerial<T>(value: T, numFmt: string | undefined, epoch: DateEpoch): T | Date;
|
package/dist/core/date.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
const MS_PER_DAY = 86_400_000;
|
|
2
2
|
const EPOCH_1900_UTC = Date.UTC(1899, 11, 30);
|
|
3
3
|
const PHANTOM_SERIAL = 60;
|
|
4
|
+
const EPOCH_1904_UTC = Date.UTC(1904, 0, 1);
|
|
4
5
|
export const DEFAULT_DATE_NUMFMT = 'yyyy-mm-dd';
|
|
5
|
-
export function dateToSerial(date) {
|
|
6
|
+
export function dateToSerial(date, epoch) {
|
|
7
|
+
if (epoch === 1904)
|
|
8
|
+
return (date.getTime() - EPOCH_1904_UTC) / MS_PER_DAY;
|
|
6
9
|
const days = (date.getTime() - EPOCH_1900_UTC) / MS_PER_DAY;
|
|
7
10
|
return days <= PHANTOM_SERIAL ? days - 1 : days;
|
|
8
11
|
}
|
|
9
|
-
export function serialToDate(serial) {
|
|
12
|
+
export function serialToDate(serial, epoch) {
|
|
13
|
+
if (epoch === 1904)
|
|
14
|
+
return new Date(EPOCH_1904_UTC + serial * MS_PER_DAY);
|
|
10
15
|
const dayOffset = serial < PHANTOM_SERIAL ? serial + 1 : serial;
|
|
11
16
|
return new Date(EPOCH_1900_UTC + dayOffset * MS_PER_DAY);
|
|
12
17
|
}
|
|
18
|
+
export function parseDateText(text) {
|
|
19
|
+
if (text === '')
|
|
20
|
+
return null;
|
|
21
|
+
const date = new Date(text);
|
|
22
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
23
|
+
}
|
|
13
24
|
export function isDateFormat(code) {
|
|
14
25
|
const stripped = code
|
|
15
26
|
.replace(/\[[^\]]*\]/g, '')
|
|
@@ -17,3 +28,8 @@ export function isDateFormat(code) {
|
|
|
17
28
|
.replace(/\\./g, '');
|
|
18
29
|
return /[ymdhs]/i.test(stripped);
|
|
19
30
|
}
|
|
31
|
+
export function coerceDateSerial(value, numFmt, epoch) {
|
|
32
|
+
return typeof value === 'number' && numFmt !== undefined && isDateFormat(numFmt)
|
|
33
|
+
? serialToDate(value, epoch)
|
|
34
|
+
: value;
|
|
35
|
+
}
|
package/dist/core/formula.d.ts
CHANGED
|
@@ -47,3 +47,29 @@ export declare function mangleFormula(formula: string): string;
|
|
|
47
47
|
* sheet name is untouched. Function names and defined names carry no row digits, so they pass through.
|
|
48
48
|
*/
|
|
49
49
|
export declare function translateFormula(formula: string, colDelta: number, rowDelta: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* A number as it appears *inside formula text*, which is not the same serialisation as an
|
|
52
|
+
* attribute's.
|
|
53
|
+
*
|
|
54
|
+
* Two divergences, both found as drift rather than designed. The exponent's case: Excel writes
|
|
55
|
+
* `1E+21` where JavaScript writes `1e+21`, so the same literal read back through the two codecs
|
|
56
|
+
* produced two different formula strings depending on which file it came from -- exactly the
|
|
57
|
+
* asymmetry `cell-value.ts` and `cell-accumulator.ts` were extracted to prevent. And the finiteness
|
|
58
|
+
* guard: the BIFF12 decoder had a private copy of this function with none, so a `PtgNum` whose eight
|
|
59
|
+
* bytes decode to an infinity produced the formula text `INFINITY`, which the writer then escaped as
|
|
60
|
+
* ordinary text into a package Excel reports as damaged.
|
|
61
|
+
*
|
|
62
|
+
* Here rather than in `xml/xml.ts` beside the attribute form, because formula text is not XML: the
|
|
63
|
+
* BIFF12 codec produces it too, and reaching the XML serialiser for it put the whole write half of
|
|
64
|
+
* that module into the closure of an entry that has no XML in it.
|
|
65
|
+
*
|
|
66
|
+
* @throws {AuthoringError} when the value is not finite.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formulaNumberLiteral(value: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* Render a formula operand for serialisation: a number becomes its literal, a string is stripped of
|
|
71
|
+
* the single optional leading '=' an author may write (OOXML stores the expression without it, e.g.
|
|
72
|
+
* `=A1>0` on disk is `A1>0`). The result is unescaped: the caller escapes it for its target, whether
|
|
73
|
+
* that is element text or an attribute value.
|
|
74
|
+
*/
|
|
75
|
+
export declare function stripFormulaEquals(value: string | number): string;
|
package/dist/core/formula.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertWritableNumber } from '../errors.js';
|
|
2
|
+
import { MAX_COLUMN, MAX_ROW, numberToColumn, tryColumnToNumber } from './address.js';
|
|
2
3
|
import { MODERN_FUNCTIONS } from './modern-functions.js';
|
|
4
|
+
import { REF_ERROR } from './value.js';
|
|
3
5
|
const XLFN = '_xlfn.';
|
|
4
6
|
const XLPM = '_xlpm.';
|
|
5
7
|
export function quoteSheetName(name, last) {
|
|
@@ -195,8 +197,22 @@ export function translateFormula(formula, colDelta, rowDelta) {
|
|
|
195
197
|
if (colDelta === 0 && rowDelta === 0)
|
|
196
198
|
return formula;
|
|
197
199
|
return scanFormula(formula, (code) => code.replace(CELL_REFERENCE, (_match, colAbs, colLetters, rowAbs, rowDigits) => {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
const decoded = tryColumnToNumber(colLetters);
|
|
201
|
+
if (decoded === undefined)
|
|
202
|
+
return REF_ERROR;
|
|
203
|
+
const col = colAbs === '$' ? decoded : decoded + colDelta;
|
|
204
|
+
const row = rowAbs === '$' ? Number(rowDigits) : Number(rowDigits) + rowDelta;
|
|
205
|
+
if (col < 1 || col > MAX_COLUMN || row < 1 || row > MAX_ROW)
|
|
206
|
+
return REF_ERROR;
|
|
207
|
+
return `${colAbs}${numberToColumn(col)}${rowAbs}${row}`;
|
|
201
208
|
}));
|
|
202
209
|
}
|
|
210
|
+
export function formulaNumberLiteral(value) {
|
|
211
|
+
assertWritableNumber(value);
|
|
212
|
+
return String(value).toUpperCase();
|
|
213
|
+
}
|
|
214
|
+
export function stripFormulaEquals(value) {
|
|
215
|
+
if (typeof value === 'number')
|
|
216
|
+
return formulaNumberLiteral(value);
|
|
217
|
+
return value.startsWith('=') ? value.slice(1) : value;
|
|
218
|
+
}
|
|
@@ -3,10 +3,10 @@ import { Cell } from './cell.ts';
|
|
|
3
3
|
import type { ConditionalFormattingOverlay } from './conditional-formatting-overlay.ts';
|
|
4
4
|
import type { DataValidationOverlay } from './data-validation-overlay.ts';
|
|
5
5
|
import { type AnchoredImage } from './image.ts';
|
|
6
|
-
import type { MergeRect } from './merge.ts';
|
|
7
6
|
import type { Table } from './table.ts';
|
|
8
7
|
import { type CellValue } from './value.ts';
|
|
9
8
|
import type { WorksheetComments } from './worksheet-comments.ts';
|
|
9
|
+
import type { WorksheetMerges } from './worksheet-merges.ts';
|
|
10
10
|
import type { ColumnProperties, RowProperties } from './worksheet.ts';
|
|
11
11
|
/**
|
|
12
12
|
* The sheet's autofilter, reached as a slot rather than held by reference like the containers beside
|
|
@@ -20,8 +20,7 @@ interface GridStorage {
|
|
|
20
20
|
readonly rows: Map<number, Map<number, Cell>>;
|
|
21
21
|
readonly rowProperties: Map<number, RowProperties>;
|
|
22
22
|
readonly columns: Map<number, ColumnProperties>;
|
|
23
|
-
readonly merges:
|
|
24
|
-
readonly mergeRects: MergeRect[];
|
|
23
|
+
readonly merges: WorksheetMerges;
|
|
25
24
|
readonly tables: Table[];
|
|
26
25
|
readonly images: AnchoredImage[];
|
|
27
26
|
readonly dataValidations: DataValidationOverlay;
|
package/dist/core/grid-edits.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
|
|
1
|
+
import { boundedRect, decodeRange, encodeAddress, encodeRect, tryDecodeCellRef } from './address.js';
|
|
2
2
|
import { shiftAutoFilter } from './autofilter.js';
|
|
3
3
|
import { Cell, copyCellContent } from './cell.js';
|
|
4
4
|
import { replaceContents } from './containers.js';
|
|
5
|
-
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
5
|
+
import { isDeletedSpan, shiftIndex, shiftRect } from './grid-shift.js';
|
|
6
6
|
import { isOneCellAnchor } from './image.js';
|
|
7
|
+
import { positionalPlacements } from './row-input.js';
|
|
7
8
|
import { isSharedFormulaValue } from './value.js';
|
|
8
9
|
export class GridEdits {
|
|
9
10
|
#rows;
|
|
10
11
|
#rowProperties;
|
|
11
12
|
#columns;
|
|
12
13
|
#merges;
|
|
13
|
-
#mergeRects;
|
|
14
14
|
#tables;
|
|
15
15
|
#images;
|
|
16
16
|
#dataValidations;
|
|
@@ -22,7 +22,6 @@ export class GridEdits {
|
|
|
22
22
|
this.#rowProperties = storage.rowProperties;
|
|
23
23
|
this.#columns = storage.columns;
|
|
24
24
|
this.#merges = storage.merges;
|
|
25
|
-
this.#mergeRects = storage.mergeRects;
|
|
26
25
|
this.#tables = storage.tables;
|
|
27
26
|
this.#images = storage.images;
|
|
28
27
|
this.#dataValidations = storage.dataValidations;
|
|
@@ -31,13 +30,15 @@ export class GridEdits {
|
|
|
31
30
|
this.#autoFilter = storage.autoFilter;
|
|
32
31
|
}
|
|
33
32
|
spliceRows(start, count, inserted) {
|
|
34
|
-
const
|
|
33
|
+
const splice = { axis: 'row', start, count, delta: inserted.length - count };
|
|
35
34
|
const shifted = new Map();
|
|
36
35
|
for (const [row, cols] of this.#rows) {
|
|
37
36
|
if (row < start)
|
|
38
37
|
shifted.set(row, cols);
|
|
39
|
-
else if (row >= start + count)
|
|
40
|
-
|
|
38
|
+
else if (row >= start + count) {
|
|
39
|
+
const dest = shiftIndex(row, splice);
|
|
40
|
+
shifted.set(dest, this.#relocateRow(cols, dest));
|
|
41
|
+
}
|
|
41
42
|
}
|
|
42
43
|
inserted.forEach((cols, i) => {
|
|
43
44
|
shifted.set(start + i, this.#relocateRow(cols, start + i));
|
|
@@ -45,15 +46,16 @@ export class GridEdits {
|
|
|
45
46
|
this.#rows.clear();
|
|
46
47
|
for (const [row, cols] of shifted)
|
|
47
48
|
this.#rows.set(row, cols);
|
|
48
|
-
this.#shiftLineProperties(this.#rowProperties,
|
|
49
|
-
this.#shiftMerges(
|
|
50
|
-
this.#shiftTables(
|
|
51
|
-
this.#shiftImages(
|
|
52
|
-
this.#reanchorSharedFormulas(
|
|
53
|
-
this.#shiftRangeBoundOverlays(
|
|
49
|
+
this.#shiftLineProperties(this.#rowProperties, splice);
|
|
50
|
+
this.#shiftMerges(splice);
|
|
51
|
+
this.#shiftTables(splice);
|
|
52
|
+
this.#shiftImages(splice);
|
|
53
|
+
this.#reanchorSharedFormulas(splice);
|
|
54
|
+
this.#shiftRangeBoundOverlays(splice);
|
|
54
55
|
}
|
|
55
56
|
spliceColumns(start, count, inserts) {
|
|
56
|
-
const
|
|
57
|
+
const splice = { axis: 'col', start, count, delta: inserts.length - count };
|
|
58
|
+
const shiftedRows = new Map();
|
|
57
59
|
for (const [row, cols] of this.#rows) {
|
|
58
60
|
const shifted = new Map();
|
|
59
61
|
for (const [col, cell] of cols) {
|
|
@@ -61,37 +63,42 @@ export class GridEdits {
|
|
|
61
63
|
shifted.set(col, cell);
|
|
62
64
|
}
|
|
63
65
|
else if (col >= start + count) {
|
|
64
|
-
const dest = col
|
|
66
|
+
const dest = shiftIndex(col, splice);
|
|
65
67
|
const moved = new Cell(row, dest);
|
|
66
68
|
copyCellContent(cell, moved);
|
|
67
69
|
shifted.set(dest, moved);
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
const value = values[row - 1];
|
|
72
|
-
if (value !== undefined) {
|
|
73
|
-
const cell = new Cell(row, start + i);
|
|
74
|
-
cell.value = value;
|
|
75
|
-
shifted.set(start + i, cell);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
this.#rows.set(row, shifted);
|
|
72
|
+
shiftedRows.set(row, shifted);
|
|
79
73
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
inserts.forEach((values, i) => {
|
|
75
|
+
for (const [row, value] of positionalPlacements(values)) {
|
|
76
|
+
const cell = new Cell(row, start + i);
|
|
77
|
+
cell.value = value;
|
|
78
|
+
let cols = shiftedRows.get(row);
|
|
79
|
+
if (cols === undefined) {
|
|
80
|
+
cols = new Map();
|
|
81
|
+
shiftedRows.set(row, cols);
|
|
82
|
+
}
|
|
83
|
+
cols.set(start + i, cell);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
for (const [row, cols] of shiftedRows)
|
|
87
|
+
this.#rows.set(row, cols);
|
|
88
|
+
this.#shiftLineProperties(this.#columns, splice);
|
|
89
|
+
this.#shiftMerges(splice);
|
|
90
|
+
this.#shiftTables(splice);
|
|
91
|
+
this.#shiftImages(splice);
|
|
92
|
+
this.#reanchorSharedFormulas(splice);
|
|
93
|
+
this.#shiftRangeBoundOverlays(splice);
|
|
86
94
|
}
|
|
87
|
-
#shiftRangeBoundOverlays(
|
|
88
|
-
this.#dataValidations.shift(
|
|
89
|
-
this.#conditionalFormattings.shift(
|
|
90
|
-
this.#comments.shift(
|
|
95
|
+
#shiftRangeBoundOverlays(splice) {
|
|
96
|
+
this.#dataValidations.shift(splice);
|
|
97
|
+
this.#conditionalFormattings.shift(splice);
|
|
98
|
+
this.#comments.shift(splice);
|
|
91
99
|
const filter = this.#autoFilter.get();
|
|
92
|
-
if (filter !== undefined)
|
|
93
|
-
this.#autoFilter.set(shiftAutoFilter(filter,
|
|
94
|
-
}
|
|
100
|
+
if (filter !== undefined)
|
|
101
|
+
this.#autoFilter.set(shiftAutoFilter(filter, splice));
|
|
95
102
|
}
|
|
96
103
|
#relocateRow(cols, destRow) {
|
|
97
104
|
const moved = new Map();
|
|
@@ -107,7 +114,7 @@ export class GridEdits {
|
|
|
107
114
|
}
|
|
108
115
|
return moved;
|
|
109
116
|
}
|
|
110
|
-
#reanchorSharedFormulas(
|
|
117
|
+
#reanchorSharedFormulas(splice) {
|
|
111
118
|
for (const cols of this.#rows.values()) {
|
|
112
119
|
for (const cell of cols.values()) {
|
|
113
120
|
const value = cell.value;
|
|
@@ -116,9 +123,9 @@ export class GridEdits {
|
|
|
116
123
|
const master = tryDecodeCellRef(value.sharedFormula);
|
|
117
124
|
if (master === undefined)
|
|
118
125
|
continue;
|
|
119
|
-
const anchored = axis === 'row'
|
|
120
|
-
? encodeAddress(master.col, shiftIndex(master.row,
|
|
121
|
-
: encodeAddress(shiftIndex(master.col,
|
|
126
|
+
const anchored = splice.axis === 'row'
|
|
127
|
+
? encodeAddress(master.col, shiftIndex(master.row, splice))
|
|
128
|
+
: encodeAddress(shiftIndex(master.col, splice), master.row);
|
|
122
129
|
if (anchored === value.sharedFormula)
|
|
123
130
|
continue;
|
|
124
131
|
const reanchored = { ...value, sharedFormula: anchored };
|
|
@@ -126,59 +133,51 @@ export class GridEdits {
|
|
|
126
133
|
}
|
|
127
134
|
}
|
|
128
135
|
}
|
|
129
|
-
#shiftLineProperties(map,
|
|
136
|
+
#shiftLineProperties(map, splice) {
|
|
130
137
|
const shifted = new Map();
|
|
131
138
|
for (const [index, value] of map) {
|
|
132
|
-
if (index
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
shifted.set(index + delta, value);
|
|
139
|
+
if (isDeletedSpan(index, index, splice))
|
|
140
|
+
continue;
|
|
141
|
+
shifted.set(shiftIndex(index, splice), value);
|
|
136
142
|
}
|
|
137
143
|
map.clear();
|
|
138
144
|
for (const [index, value] of shifted)
|
|
139
145
|
map.set(index, value);
|
|
140
146
|
}
|
|
141
|
-
#shiftMerges(
|
|
142
|
-
const shift = (v) => shiftIndex(v, start, count, delta, axis);
|
|
147
|
+
#shiftMerges(splice) {
|
|
143
148
|
const merges = [];
|
|
144
149
|
const rects = [];
|
|
145
|
-
for (const range of this.#merges) {
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
150
|
+
for (const range of this.#merges.ranges) {
|
|
151
|
+
const decoded = boundedRect(decodeRange(range));
|
|
152
|
+
if (decoded === undefined) {
|
|
148
153
|
merges.push(range);
|
|
149
154
|
continue;
|
|
150
155
|
}
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
156
|
+
const rect = shiftRect(decoded, splice);
|
|
157
|
+
if (rect === undefined)
|
|
153
158
|
continue;
|
|
154
|
-
const rect = axis === 'row'
|
|
155
|
-
? { top: shift(top), left, bottom: shift(bottom), right }
|
|
156
|
-
: { top, left: shift(left), bottom, right: shift(right) };
|
|
157
159
|
rects.push(rect);
|
|
158
|
-
merges.push(
|
|
160
|
+
merges.push(encodeRect(rect));
|
|
159
161
|
}
|
|
160
|
-
|
|
161
|
-
replaceContents(this.#mergeRects, rects);
|
|
162
|
+
this.#merges.replaceAll(merges, rects);
|
|
162
163
|
}
|
|
163
|
-
#shiftTables(
|
|
164
|
-
const survivors = this.#tables.filter((table) => axis === 'row'
|
|
165
|
-
? table.shiftRows(start, count, delta)
|
|
166
|
-
: table.shiftColumns(start, count, delta));
|
|
164
|
+
#shiftTables(splice) {
|
|
165
|
+
const survivors = this.#tables.filter((table) => splice.axis === 'row' ? table.shiftRows(splice) : table.shiftColumns(splice));
|
|
167
166
|
replaceContents(this.#tables, survivors);
|
|
168
167
|
}
|
|
169
|
-
#shiftImages(
|
|
170
|
-
const
|
|
171
|
-
const zeroBased = axis === 'row' ? point.row : point.col;
|
|
172
|
-
const shifted = shiftIndex(zeroBased + 1,
|
|
168
|
+
#shiftImages(splice) {
|
|
169
|
+
const shiftAnchor = (point) => {
|
|
170
|
+
const zeroBased = splice.axis === 'row' ? point.row : point.col;
|
|
171
|
+
const shifted = shiftIndex(zeroBased + 1, splice) - 1;
|
|
173
172
|
if (shifted === zeroBased)
|
|
174
173
|
return point;
|
|
175
|
-
return axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
|
|
174
|
+
return splice.axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
|
|
176
175
|
};
|
|
177
176
|
const moved = this.#images.map((image) => {
|
|
178
|
-
const from =
|
|
177
|
+
const from = shiftAnchor(image.anchor.from);
|
|
179
178
|
const anchor = isOneCellAnchor(image.anchor)
|
|
180
179
|
? { ...image.anchor, from }
|
|
181
|
-
: { ...image.anchor, from, to:
|
|
180
|
+
: { ...image.anchor, from, to: shiftAnchor(image.anchor.to) };
|
|
182
181
|
return { imageId: image.imageId, anchor };
|
|
183
182
|
});
|
|
184
183
|
replaceContents(this.#images, moved);
|