@shbernal/ts-xlsx 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -23
- package/dist/bytes.d.ts +3 -2
- package/dist/core/address.d.ts +36 -0
- package/dist/core/address.js +47 -9
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/color-resolution.js +2 -2
- package/dist/core/column.js +3 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/conditional-formatting-overlay.d.ts +9 -0
- package/dist/core/conditional-formatting-overlay.js +28 -1
- package/dist/core/conditional-formatting.d.ts +98 -10
- package/dist/core/conditional-formatting.js +42 -1
- package/dist/core/data-validation-overlay.d.ts +5 -0
- package/dist/core/data-validation-overlay.js +15 -0
- package/dist/core/data-validation.d.ts +16 -0
- package/dist/core/data-validation.js +15 -0
- package/dist/core/date-format.js +14 -4
- package/dist/core/date.js +1 -1
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +51 -18
- package/dist/core/formula.js +77 -86
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +28 -0
- package/dist/core/grid-edits.js +193 -25
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +36 -5
- package/dist/core/image.js +9 -0
- package/dist/core/internal.d.ts +3 -1
- package/dist/core/limits.d.ts +5 -3
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +15 -1
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +55 -21
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +4 -0
- package/dist/core/row.js +3 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/table.d.ts +38 -8
- package/dist/core/table.js +37 -7
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +4 -1
- package/dist/core/value.d.ts +51 -28
- package/dist/core/value.js +15 -12
- package/dist/core/workbook-styles.d.ts +17 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook.d.ts +20 -3
- package/dist/core/workbook.js +26 -5
- package/dist/core/worksheet-merges.d.ts +9 -4
- package/dist/core/worksheet-merges.js +17 -11
- package/dist/core/worksheet-model.d.ts +1 -1
- package/dist/core/worksheet-model.js +4 -0
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +110 -21
- package/dist/core/worksheet.js +174 -49
- package/dist/entries/core.d.ts +7 -5
- package/dist/entries/core.js +4 -3
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/errors.js +1 -1
- package/dist/io/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +14 -2
- package/dist/io/csv/delimiter.js +12 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +46 -24
- package/dist/io/csv/write.d.ts +10 -3
- package/dist/io/csv/write.js +16 -7
- package/dist/io/opc/part-paths.js +4 -4
- package/dist/io/opc/read-opc.d.ts +4 -0
- package/dist/io/opc/read-opc.js +10 -4
- package/dist/io/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- package/dist/io/xlsb/formula.js +4 -2
- package/dist/io/xlsb/ptg-functions.js +110 -0
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-worksheet.d.ts +6 -0
- package/dist/io/xlsb/read-worksheet.js +95 -37
- package/dist/io/xlsb/read.js +26 -8
- package/dist/io/xlsb/record-types.d.ts +13 -0
- package/dist/io/xlsb/record-types.js +13 -0
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
- package/dist/io/xlsx/cell-accumulator.js +49 -19
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +16 -4
- package/dist/io/xlsx/cell-value.js +38 -21
- package/dist/io/xlsx/color-xml.js +1 -1
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +3 -28
- package/dist/io/xlsx/comments.js +3 -65
- package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
- package/dist/io/xlsx/conditional-formatting.js +424 -129
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +41 -29
- package/dist/io/xlsx/edit-vba.js +43 -36
- package/dist/io/xlsx/hyperlinks.d.ts +16 -50
- package/dist/io/xlsx/hyperlinks.js +8 -37
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +145 -44
- package/dist/io/xlsx/namespaces.d.ts +8 -0
- package/dist/io/xlsx/namespaces.js +4 -0
- package/dist/io/xlsx/package-plan.d.ts +48 -15
- package/dist/io/xlsx/package-plan.js +46 -9
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +23 -22
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +2 -2
- package/dist/io/xlsx/read-parts.js +44 -37
- package/dist/io/xlsx/read-pivot.js +9 -3
- package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
- package/dist/io/xlsx/read-rich-runs.js +18 -1
- package/dist/io/xlsx/read-rows.d.ts +18 -7
- package/dist/io/xlsx/read-rows.js +47 -37
- package/dist/io/xlsx/read-styles.d.ts +0 -25
- package/dist/io/xlsx/read-styles.js +39 -34
- package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
- package/dist/io/xlsx/read-workbook-xml.js +9 -4
- package/dist/io/xlsx/read-worksheet.d.ts +8 -3
- package/dist/io/xlsx/read-worksheet.js +45 -23
- package/dist/io/xlsx/read.d.ts +0 -4
- package/dist/io/xlsx/read.js +17 -21
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/row-position.d.ts +3 -1
- package/dist/io/xlsx/row-xml.d.ts +18 -7
- package/dist/io/xlsx/row-xml.js +54 -35
- package/dist/io/xlsx/sheet-properties.js +68 -42
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +0 -2
- package/dist/io/xlsx/style-elements.js +9 -29
- package/dist/io/xlsx/styles.d.ts +5 -0
- package/dist/io/xlsx/styles.js +11 -5
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +49 -42
- package/dist/io/xlsx/theme-xml.d.ts +6 -0
- package/dist/io/xlsx/theme-xml.js +16 -19
- package/dist/io/xlsx/threaded-comments.d.ts +6 -1
- package/dist/io/xlsx/threaded-comments.js +14 -7
- package/dist/io/xlsx/workbook-xml.d.ts +13 -24
- package/dist/io/xlsx/workbook-xml.js +15 -33
- package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
- package/dist/io/xlsx/worksheet-xml.js +37 -69
- package/dist/io/xlsx/write-stream.d.ts +28 -6
- package/dist/io/xlsx/write-stream.js +42 -17
- package/dist/io/xlsx/write.d.ts +11 -4
- package/dist/io/xlsx/write.js +153 -90
- package/dist/rel-type.d.ts +9 -0
- package/dist/rel-type.js +3 -0
- package/dist/vba/bytes.d.ts +2 -0
- package/dist/vba/bytes.js +11 -0
- package/dist/vba/cfb-format.d.ts +47 -0
- package/dist/vba/cfb-format.js +34 -0
- package/dist/vba/cfb-writer.d.ts +1 -2
- package/dist/vba/cfb-writer.js +33 -36
- package/dist/vba/cfb.d.ts +9 -4
- package/dist/vba/cfb.js +55 -43
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +4 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +15 -6
- package/dist/vba/project-editor.js +102 -87
- package/dist/vba/project.d.ts +4 -0
- package/dist/vba/project.js +12 -6
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +2 -15
- package/dist/xml/xml-read.d.ts +29 -1
- package/dist/xml/xml-read.js +56 -0
- package/dist/xml/xml-scan.d.ts +13 -0
- package/dist/xml/xml-scan.js +58 -7
- package/dist/xml/xml.d.ts +33 -1
- package/dist/xml/xml.js +22 -0
- package/package.json +5 -4
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
- package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
- package/dist/io/xlsx/cell-style-resolution.js +0 -29
- package/dist/io/xlsx/column-budget.d.ts +0 -26
- package/dist/io/xlsx/read-repair.d.ts +0 -26
- package/dist/io/xlsx/read-repair.js +0 -39
package/dist/io/csv/delimiter.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { quoted } from '../../errors.js';
|
|
2
|
+
const RESERVED = new Set(['"', '\r', '\n']);
|
|
2
3
|
export function assertDelimiter(delimiter) {
|
|
3
4
|
if (delimiter.length !== 1) {
|
|
4
5
|
throw new RangeError(`CSV delimiter must be a single character, got ${quoted(delimiter)}`);
|
|
5
6
|
}
|
|
7
|
+
if (RESERVED.has(delimiter)) {
|
|
8
|
+
throw new RangeError(`CSV delimiter ${quoted(delimiter)} is reserved: a quote opens a quoted field, and CR and LF end a row`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function assertRowDelimiter(rowDelimiter, delimiter) {
|
|
12
|
+
if (rowDelimiter === '') {
|
|
13
|
+
throw new RangeError('CSV row delimiter must not be empty');
|
|
14
|
+
}
|
|
15
|
+
if (rowDelimiter.includes(delimiter) || rowDelimiter.includes('"')) {
|
|
16
|
+
throw new RangeError(`CSV row delimiter ${quoted(rowDelimiter)} must contain neither the field delimiter ${quoted(delimiter)} nor a quote`);
|
|
17
|
+
}
|
|
6
18
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { XlsxError } from '../../errors.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when CSV text parses but cannot become a worksheet: a record with more fields than a sheet
|
|
4
|
+
* has columns, or more records than it has rows.
|
|
5
|
+
*
|
|
6
|
+
* CSV has no malformed markup to reject, since any text splits into fields somehow, so the only way a
|
|
7
|
+
* file fails is by not fitting the grid. That is still the file's fault rather than the caller's,
|
|
8
|
+
* which is why it is this error and not the `RangeError` the grid raises for an authored cell out of
|
|
9
|
+
* bounds.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CsvParseError extends XlsxError {
|
|
12
|
+
readonly name = "CsvParseError";
|
|
13
|
+
readonly code = "malformed-input";
|
|
14
|
+
}
|
package/dist/io/csv/read.d.ts
CHANGED
|
@@ -11,5 +11,10 @@ export interface CsvReadOptions {
|
|
|
11
11
|
/** Name for the single worksheet produced; defaults to `"Sheet1"`. */
|
|
12
12
|
readonly sheetName?: string;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Parse CSV text (or UTF-8 bytes) into a workbook holding a single worksheet.
|
|
16
|
+
*
|
|
17
|
+
* @throws {CsvParseError} if a record has more fields than a worksheet has columns, or the data
|
|
18
|
+
* records outnumber a worksheet's rows.
|
|
19
|
+
*/
|
|
15
20
|
export declare function readCsv(input: string | Uint8Array, options?: CsvReadOptions): Workbook;
|
package/dist/io/csv/read.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { MAX_COLUMN, MAX_ROW } from '../../core/address.js';
|
|
1
2
|
import { Workbook } from '../../core/workbook.js';
|
|
2
3
|
import { assertDelimiter } from './delimiter.js';
|
|
4
|
+
import { CsvParseError } from './errors.js';
|
|
3
5
|
export function readCsv(input, options = {}) {
|
|
4
6
|
const text = stripBom(typeof input === 'string' ? input : new TextDecoder('utf-8', { ignoreBOM: true }).decode(input));
|
|
5
7
|
const delimiter = options.delimiter ?? ',';
|
|
6
8
|
assertDelimiter(delimiter);
|
|
7
|
-
let rows = parseCsvRows(text, delimiter);
|
|
9
|
+
let rows = parseCsvRows(text, delimiter, MAX_ROW + (options.headers ? 1 : 0));
|
|
8
10
|
if (options.headers)
|
|
9
11
|
rows = rows.slice(1);
|
|
10
12
|
const coerce = options.map ?? defaultCsvCoerce;
|
|
@@ -16,11 +18,28 @@ export function readCsv(input, options = {}) {
|
|
|
16
18
|
function stripBom(text) {
|
|
17
19
|
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
18
20
|
}
|
|
19
|
-
function parseCsvRows(text, delimiter) {
|
|
21
|
+
function parseCsvRows(text, delimiter, maxRecords) {
|
|
20
22
|
const rows = [];
|
|
21
23
|
let row = [];
|
|
22
24
|
let field = '';
|
|
23
25
|
let inQuotes = false;
|
|
26
|
+
let atFieldStart = true;
|
|
27
|
+
const endField = () => {
|
|
28
|
+
if (row.length === MAX_COLUMN) {
|
|
29
|
+
throw new CsvParseError(`CSV record ${rows.length + 1} has more than ${MAX_COLUMN} fields, the most columns a worksheet holds`);
|
|
30
|
+
}
|
|
31
|
+
row.push(field);
|
|
32
|
+
field = '';
|
|
33
|
+
atFieldStart = true;
|
|
34
|
+
};
|
|
35
|
+
const endRow = () => {
|
|
36
|
+
endField();
|
|
37
|
+
if (rows.length === maxRecords) {
|
|
38
|
+
throw new CsvParseError(`CSV has more than ${MAX_ROW} data records, the most rows a worksheet holds`);
|
|
39
|
+
}
|
|
40
|
+
rows.push(row);
|
|
41
|
+
row = [];
|
|
42
|
+
};
|
|
24
43
|
for (let i = 0; i < text.length; i++) {
|
|
25
44
|
const ch = text.charAt(i);
|
|
26
45
|
if (inQuotes) {
|
|
@@ -38,45 +57,43 @@ function parseCsvRows(text, delimiter) {
|
|
|
38
57
|
}
|
|
39
58
|
continue;
|
|
40
59
|
}
|
|
41
|
-
if (ch === '"') {
|
|
60
|
+
if (ch === '"' && atFieldStart) {
|
|
42
61
|
inQuotes = true;
|
|
62
|
+
atFieldStart = false;
|
|
43
63
|
}
|
|
44
64
|
else if (ch === delimiter) {
|
|
45
|
-
|
|
46
|
-
field = '';
|
|
65
|
+
endField();
|
|
47
66
|
}
|
|
48
67
|
else if (ch === '\n') {
|
|
49
|
-
|
|
50
|
-
rows.push(row);
|
|
51
|
-
row = [];
|
|
52
|
-
field = '';
|
|
68
|
+
endRow();
|
|
53
69
|
}
|
|
54
70
|
else if (ch === '\r') {
|
|
55
|
-
if (text[i + 1] !== '\n')
|
|
56
|
-
|
|
57
|
-
rows.push(row);
|
|
58
|
-
row = [];
|
|
59
|
-
field = '';
|
|
60
|
-
}
|
|
71
|
+
if (text[i + 1] !== '\n')
|
|
72
|
+
endRow();
|
|
61
73
|
}
|
|
62
74
|
else {
|
|
63
75
|
field += ch;
|
|
76
|
+
atFieldStart = false;
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
|
-
row.push(field);
|
|
67
79
|
const trailingNewline = text.endsWith('\n') || text.endsWith('\r');
|
|
68
|
-
if (!(trailingNewline && row.length ===
|
|
69
|
-
|
|
80
|
+
if (!(trailingNewline && row.length === 0 && field === ''))
|
|
81
|
+
endRow();
|
|
70
82
|
return rows;
|
|
71
83
|
}
|
|
72
84
|
const NUMERIC = /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/;
|
|
73
85
|
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/;
|
|
86
|
+
const MAX_EXACT_SIGNIFICANT_DIGITS = 15;
|
|
74
87
|
function defaultCsvCoerce(field) {
|
|
75
88
|
if (field === '')
|
|
76
89
|
return null;
|
|
77
|
-
|
|
90
|
+
const numeric = NUMERIC.exec(field);
|
|
91
|
+
if (numeric) {
|
|
78
92
|
const value = Number(field);
|
|
79
|
-
|
|
93
|
+
const exact = Number.isFinite(value) &&
|
|
94
|
+
Math.abs(value) <= Number.MAX_SAFE_INTEGER &&
|
|
95
|
+
significantDigits(numeric[1] ?? '') <= MAX_EXACT_SIGNIFICANT_DIGITS;
|
|
96
|
+
return exact ? value : field;
|
|
80
97
|
}
|
|
81
98
|
const iso = ISO_DATE.exec(field);
|
|
82
99
|
if (iso) {
|
|
@@ -86,6 +103,9 @@ function defaultCsvCoerce(field) {
|
|
|
86
103
|
}
|
|
87
104
|
return field;
|
|
88
105
|
}
|
|
106
|
+
function significantDigits(mantissa) {
|
|
107
|
+
return mantissa.replace('.', '').replace(/^0+/, '').replace(/0+$/, '').length;
|
|
108
|
+
}
|
|
89
109
|
function isoToDate(match) {
|
|
90
110
|
const year = Number(match[1]);
|
|
91
111
|
const month = Number(match[2]);
|
|
@@ -93,9 +113,11 @@ function isoToDate(match) {
|
|
|
93
113
|
const hour = match[4] === undefined ? 0 : Number(match[4]);
|
|
94
114
|
const minute = match[5] === undefined ? 0 : Number(match[5]);
|
|
95
115
|
const second = match[6] === undefined ? 0 : Number(match[6]);
|
|
96
|
-
if (
|
|
116
|
+
if (hour > 23 || minute > 59 || second > 59)
|
|
97
117
|
return null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
const date = new Date(0);
|
|
119
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
120
|
+
date.setUTCHours(hour, minute, second, 0);
|
|
121
|
+
const real = date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
122
|
+
return real ? date : null;
|
|
101
123
|
}
|
package/dist/io/csv/write.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ export interface CsvWriteOptions {
|
|
|
14
14
|
/** Which worksheet to write; defaults to the first. A name matching no sheet throws rather than
|
|
15
15
|
* silently emitting an empty file. */
|
|
16
16
|
readonly sheetName?: string;
|
|
17
|
-
/** Field separator; defaults to a comma. */
|
|
17
|
+
/** Field separator; defaults to a comma. A single character other than a quote, CR or LF. */
|
|
18
18
|
readonly delimiter?: string;
|
|
19
|
-
/** Line separator between rows; defaults to `"\n"`.
|
|
19
|
+
/** Line separator between rows; defaults to `"\n"`. Not empty, and containing neither the field
|
|
20
|
+
* delimiter nor a quote. A field that contains it is quoted. */
|
|
20
21
|
readonly rowDelimiter?: string;
|
|
21
22
|
/**
|
|
22
23
|
* An Excel number-format code (e.g. `"yyyy-mm-dd"`, `"d mmm yy hh:mm"`) for Date cells; without it
|
|
@@ -39,12 +40,18 @@ export interface CsvWriteOptions {
|
|
|
39
40
|
* still applied to the returned text. */
|
|
40
41
|
readonly map?: (value: CellValue, index: number) => string;
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The logical CSV text of one worksheet: no BOM, no byte encoding. Line N holds row N, so an empty
|
|
45
|
+
* row before the last populated one is an empty line.
|
|
46
|
+
*
|
|
47
|
+
* @throws {RangeError} if `delimiter` or `rowDelimiter` is one the output could not be split on.
|
|
48
|
+
*/
|
|
43
49
|
export declare function writeCsvText(workbook: Workbook, options?: CsvWriteOptions): string;
|
|
44
50
|
/**
|
|
45
51
|
* The CSV bytes of one worksheet in the requested encoding, with a UTF-8 BOM by default.
|
|
46
52
|
*
|
|
47
53
|
* @throws {AuthoringError} if a field holds an unpaired surrogate and the encoding is UTF-8, which
|
|
48
54
|
* cannot represent one. The alternative is a silent U+FFFD substitution.
|
|
55
|
+
* @throws {RangeError} on a delimiter {@link writeCsvText} refuses.
|
|
49
56
|
*/
|
|
50
57
|
export declare function writeCsv(workbook: Workbook, options?: CsvWriteOptions): Uint8Array;
|
package/dist/io/csv/write.js
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
import { formatSerialDate } from '../../core/date-format.js';
|
|
2
|
-
import { cellValueToText, isDataTableFormulaValue, isFormulaValue, isSharedFormulaValue, } from '../../core/value.js';
|
|
2
|
+
import { cellValueToText, isArrayFormulaValue, isDataTableFormulaValue, isFormulaValue, isSharedFormulaValue, } from '../../core/value.js';
|
|
3
3
|
import { AuthoringError, unrepresentable } from '../../errors.js';
|
|
4
|
-
import { assertDelimiter } from './delimiter.js';
|
|
4
|
+
import { assertDelimiter, assertRowDelimiter } from './delimiter.js';
|
|
5
5
|
const UTF8_BOM = Uint8Array.of(0xef, 0xbb, 0xbf);
|
|
6
6
|
export function writeCsvText(workbook, options = {}) {
|
|
7
7
|
const sheet = selectSheet(workbook, options.sheetName);
|
|
8
8
|
const delimiter = options.delimiter ?? ',';
|
|
9
9
|
assertDelimiter(delimiter);
|
|
10
10
|
const rowDelimiter = options.rowDelimiter ?? '\n';
|
|
11
|
+
assertRowDelimiter(rowDelimiter, delimiter);
|
|
11
12
|
const lines = [];
|
|
12
|
-
for (const
|
|
13
|
+
for (const row of sheet.rows()) {
|
|
13
14
|
let width = 0;
|
|
14
15
|
const byColumn = new Map();
|
|
15
|
-
for (const cell of cells) {
|
|
16
|
+
for (const cell of row.cells) {
|
|
16
17
|
byColumn.set(cell.col, cell);
|
|
17
18
|
if (cell.col > width)
|
|
18
19
|
width = cell.col;
|
|
19
20
|
}
|
|
21
|
+
if (width === 0)
|
|
22
|
+
continue;
|
|
20
23
|
const fields = [];
|
|
21
24
|
for (let column = 1; column <= width; column++) {
|
|
22
25
|
const value = byColumn.get(column)?.value ?? null;
|
|
23
26
|
const text = options.map ? options.map(value, column - 1) : csvFieldText(value, options);
|
|
24
|
-
fields.push(quoteField(text, delimiter));
|
|
27
|
+
fields.push(quoteField(text, delimiter, rowDelimiter));
|
|
25
28
|
}
|
|
29
|
+
while (lines.length < row.number - 1)
|
|
30
|
+
lines.push('');
|
|
26
31
|
lines.push(fields.join(delimiter));
|
|
27
32
|
}
|
|
28
33
|
return lines.join(rowDelimiter);
|
|
@@ -79,13 +84,17 @@ function csvFieldText(value, options) {
|
|
|
79
84
|
if (value instanceof Date && options.dateFormat !== undefined) {
|
|
80
85
|
return formatSerialDate(value, options.dateFormat, options.dateUTC ?? false);
|
|
81
86
|
}
|
|
82
|
-
if (isFormulaValue(value) ||
|
|
87
|
+
if (isFormulaValue(value) ||
|
|
88
|
+
isSharedFormulaValue(value) ||
|
|
89
|
+
isArrayFormulaValue(value) ||
|
|
90
|
+
isDataTableFormulaValue(value)) {
|
|
83
91
|
return value.result === undefined ? '' : csvFieldText(value.result, options);
|
|
84
92
|
}
|
|
85
93
|
return cellValueToText(value);
|
|
86
94
|
}
|
|
87
|
-
function quoteField(field, delimiter) {
|
|
95
|
+
function quoteField(field, delimiter, rowDelimiter) {
|
|
88
96
|
if (field.includes(delimiter) ||
|
|
97
|
+
field.includes(rowDelimiter) ||
|
|
89
98
|
field.includes('"') ||
|
|
90
99
|
field.includes('\n') ||
|
|
91
100
|
field.includes('\r')) {
|
|
@@ -23,11 +23,11 @@ export function relativePartPath(fromPath, toPath) {
|
|
|
23
23
|
return [...Array(up).fill('..'), ...toSegments.slice(common)].join('/');
|
|
24
24
|
}
|
|
25
25
|
export function resolveRelativePart(basePart, target) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const joined = target.startsWith('/')
|
|
27
|
+
? target
|
|
28
|
+
: `${basePart.slice(0, basePart.lastIndexOf('/') + 1)}${target}`;
|
|
29
29
|
const out = [];
|
|
30
|
-
for (const segment of
|
|
30
|
+
for (const segment of joined.split('/')) {
|
|
31
31
|
if (segment === '' || segment === '.')
|
|
32
32
|
continue;
|
|
33
33
|
if (segment === '..')
|
|
@@ -5,6 +5,10 @@ export interface PackageAccessors {
|
|
|
5
5
|
partText: (path: string) => string | undefined;
|
|
6
6
|
/** A part's raw bytes, or undefined when the package holds no such part. */
|
|
7
7
|
partBytes: (path: string) => Uint8Array | undefined;
|
|
8
|
+
/** The package's own spelling of the part the other two would answer for `path`, or undefined when
|
|
9
|
+
* there is none: the key an editor replaces or deletes, so it acts on the entry a read found rather
|
|
10
|
+
* than adding a second one beside it. */
|
|
11
|
+
partKey: (path: string) => string | undefined;
|
|
8
12
|
}
|
|
9
13
|
/** A spreadsheet package opened for reading: inflated under the read bound, its parts bound to
|
|
10
14
|
* accessors, its office document located through the package's own relationship graph, and that
|
package/dist/io/opc/read-opc.js
CHANGED
|
@@ -4,6 +4,7 @@ import { openElements } from '../../xml/xml-read.js';
|
|
|
4
4
|
import { extensionOf, relsPathFor, resolveRelativePart } from './part-paths.js';
|
|
5
5
|
import { DEFAULT_MAX_UNCOMPRESSED } from './read-options.js';
|
|
6
6
|
import { inflateSpreadsheetPackage } from './sniff-format.js';
|
|
7
|
+
import { transitionalRelationshipType } from './strict-relationships.js';
|
|
7
8
|
export function openSpreadsheetPackage(data, maxUncompressedBytes) {
|
|
8
9
|
const files = inflateSpreadsheetPackage(data, maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED);
|
|
9
10
|
const pkg = packageAccessors(files);
|
|
@@ -20,18 +21,23 @@ function conventionalDocument(pkg) {
|
|
|
20
21
|
}
|
|
21
22
|
export function packageAccessors(files) {
|
|
22
23
|
const folded = new Map();
|
|
23
|
-
for (const
|
|
24
|
+
for (const path of Object.keys(files)) {
|
|
24
25
|
const key = path.toLowerCase();
|
|
25
26
|
if (!folded.has(key))
|
|
26
|
-
folded.set(key,
|
|
27
|
+
folded.set(key, path);
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
const partKey = (path) => files[path] === undefined ? folded.get(path.toLowerCase()) : path;
|
|
30
|
+
const lookup = (path) => {
|
|
31
|
+
const key = partKey(path);
|
|
32
|
+
return key === undefined ? undefined : files[key];
|
|
33
|
+
};
|
|
29
34
|
return {
|
|
30
35
|
partText: (path) => {
|
|
31
36
|
const bytes = lookup(path);
|
|
32
37
|
return bytes === undefined ? undefined : strFromU8(bytes);
|
|
33
38
|
},
|
|
34
39
|
partBytes: lookup,
|
|
40
|
+
partKey,
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
export function parseRelationshipRecords(xml) {
|
|
@@ -40,7 +46,7 @@ export function parseRelationshipRecords(xml) {
|
|
|
40
46
|
if (attrs.Id !== undefined && attrs.Type !== undefined && attrs.Target !== undefined) {
|
|
41
47
|
records.push({
|
|
42
48
|
id: attrs.Id,
|
|
43
|
-
type: attrs.Type,
|
|
49
|
+
type: transitionalRelationshipType(attrs.Type),
|
|
44
50
|
target: attrs.Target,
|
|
45
51
|
external: attrs.TargetMode === 'External',
|
|
46
52
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const STRICT_RELATIONSHIPS = 'http://purl.oclc.org/ooxml/officeDocument/relationships/';
|
|
2
|
+
const TRANSITIONAL_RELATIONSHIPS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
|
|
3
|
+
const RENAMED = new Map([
|
|
4
|
+
['extendedProperties', 'extended-properties'],
|
|
5
|
+
['customProperties', 'custom-properties'],
|
|
6
|
+
]);
|
|
7
|
+
export function transitionalRelationshipType(type) {
|
|
8
|
+
if (!type.startsWith(STRICT_RELATIONSHIPS))
|
|
9
|
+
return type;
|
|
10
|
+
const name = type.slice(STRICT_RELATIONSHIPS.length);
|
|
11
|
+
return `${TRANSITIONAL_RELATIONSHIPS}${RENAMED.get(name) ?? name}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-sheet allowance of column touches, spent by each column run the reader applies.
|
|
3
|
+
*
|
|
4
|
+
* A run past the allowance is truncated, and one starting past it is dropped, rather than the read
|
|
5
|
+
* being refused: that is the stance every reader already takes on an out-of-grid run, and it means a
|
|
6
|
+
* hostile file loses formatting it could not have meant while a real one is untouched.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ColumnRecordBudget {
|
|
9
|
+
#private;
|
|
10
|
+
/** The last column of `[first, last]` this sheet can still afford, or `undefined` when none is. */
|
|
11
|
+
take(first: number, last: number): number | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The columns of a declared run `[first, last]`, one-based, that a sheet actually applies, or
|
|
15
|
+
* `undefined` for a run that applies to none: one starting past the grid, an empty one, or one the
|
|
16
|
+
* budget can no longer afford.
|
|
17
|
+
*
|
|
18
|
+
* Beside the budget because the two decisions are one decision. The XML readers each had their own
|
|
19
|
+
* copy, and the copies had already drifted: one tested `first > MAX_COLUMN` and the other did not,
|
|
20
|
+
* which happened not to matter only because `budget.take` returns `undefined` when `first > last`, an
|
|
21
|
+
* accident of that method's contract rather than an agreement between the readers.
|
|
22
|
+
*/
|
|
23
|
+
export declare function clampColumnSpan(first: number, last: number, budget: ColumnRecordBudget): {
|
|
24
|
+
first: number;
|
|
25
|
+
last: number;
|
|
26
|
+
} | undefined;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { MAX_COLUMN } from '../../core/address.js';
|
|
2
|
-
import { numInteger } from '../../xml/xml-attrs.js';
|
|
3
|
-
import {} from '../../xml/xml-scan.js';
|
|
4
2
|
const MAX_COLUMN_RECORD_TOUCHES = MAX_COLUMN * 4;
|
|
5
3
|
export class ColumnRecordBudget {
|
|
6
4
|
#remaining = MAX_COLUMN_RECORD_TOUCHES;
|
|
@@ -12,11 +10,9 @@ export class ColumnRecordBudget {
|
|
|
12
10
|
return affordable;
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
|
-
export function
|
|
16
|
-
|
|
17
|
-
const declared = numInteger(attrs.max, 1);
|
|
18
|
-
if (first === undefined || declared === undefined || first > MAX_COLUMN)
|
|
13
|
+
export function clampColumnSpan(first, last, budget) {
|
|
14
|
+
if (first > MAX_COLUMN)
|
|
19
15
|
return undefined;
|
|
20
|
-
const
|
|
21
|
-
return
|
|
16
|
+
const affordable = budget.take(first, Math.min(last, MAX_COLUMN));
|
|
17
|
+
return affordable === undefined ? undefined : { first, last: affordable };
|
|
22
18
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type FormulaPlacement } from '../../core/array-formula-ranges.ts';
|
|
2
|
+
import type { Worksheet } from '../../core/worksheet.ts';
|
|
3
|
+
/**
|
|
4
|
+
* A sheet name from a file, rewritten into one the model will accept.
|
|
5
|
+
*
|
|
6
|
+
* Excel's own repair, in the order the constraints interact: forbidden characters go first (they
|
|
7
|
+
* can be anywhere), then the length ceiling, then the apostrophe rule *again*, because truncating
|
|
8
|
+
* can expose an interior apostrophe at the new edge. An empty result becomes `Sheet{n}`, and a
|
|
9
|
+
* collision takes a ` (2)` suffix that is itself made to fit inside the 31-character limit.
|
|
10
|
+
*
|
|
11
|
+
* @param taken the lower-cased names already in the workbook; sheet names collide
|
|
12
|
+
* case-insensitively, which is the comparison `Workbook.getWorksheet` makes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function repairSheetName(name: string, taken: ReadonlySet<string>): string;
|
|
15
|
+
/**
|
|
16
|
+
* A workbook's declared sheets in order, each carrying the name the model will accept: repaired by
|
|
17
|
+
* {@link repairSheetName} against every name repaired before it.
|
|
18
|
+
*
|
|
19
|
+
* Both codecs declare their sheets as an ordered list and both have to know the repaired names before
|
|
20
|
+
* anything cites a sheet by position, since a scoped defined name and a 3-D reference both index into
|
|
21
|
+
* that order. The loop threading the taken set through the list is one loop, so it is written once.
|
|
22
|
+
*/
|
|
23
|
+
export declare function repairedSheetNames<T extends {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
}>(declared: readonly T[]): T[];
|
|
26
|
+
/**
|
|
27
|
+
* Read a sheet whose array formulas cannot stand as Excel repairs it (`core/array-formula-ranges.ts`). A
|
|
28
|
+
* file is free to carry an array formula whose range holds another formula, or shares a cell with another
|
|
29
|
+
* array formula's range, and the writer refuses both, since Excel offers to repair such a package.
|
|
30
|
+
*
|
|
31
|
+
* Excel 16.0 keeps the array formula decided first. A formula inside its range loses its formula and its
|
|
32
|
+
* value, keeping its style, and a shared formula whose master that was loses its formula too, keeping its
|
|
33
|
+
* value; an array formula whose range only shares cells with it loses its formula, keeping its value. The
|
|
34
|
+
* value Excel then shows in a cell it emptied is what the kept array formula computes there, which the
|
|
35
|
+
* library does not compute, so the cell is left empty.
|
|
36
|
+
*/
|
|
37
|
+
export declare function admitArrayRanges(sheet: Worksheet): void;
|
|
38
|
+
/** What Excel's repair takes from a sheet whose array formulas cannot stand. */
|
|
39
|
+
export interface ArrayRangeRepair {
|
|
40
|
+
/** Formulas whose cell lies in the range of an array formula kept: each loses its whole cell. */
|
|
41
|
+
readonly cellsRemoved: FormulaPlacement[];
|
|
42
|
+
/**
|
|
43
|
+
* Array formulas whose own cell lies in no range kept but whose range shares a cell with one: each loses
|
|
44
|
+
* its formula and keeps the value it cached.
|
|
45
|
+
*/
|
|
46
|
+
readonly formulasRemoved: FormulaPlacement[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What Excel's repair takes from the sheet, deciding in reading order: an array formula keeps its range
|
|
50
|
+
* unless that range shares a cell with a range kept before it. A formula of any kind whose cell lies in a
|
|
51
|
+
* kept range loses the cell; an array formula whose range shares a cell with a kept range, its own cell
|
|
52
|
+
* outside it, loses its formula and keeps its value. What is left holds no conflict.
|
|
53
|
+
*
|
|
54
|
+
* Excel 16.0 (build 20326) repaired B1:B3 holding a plain formula, or an array formula of its own, in B2
|
|
55
|
+
* by keeping B1's range and removing B2's "cell information", and B1:C2 beside A2:B3 by keeping B1's range
|
|
56
|
+
* and removing A2's "formula", its value left (`test/corpus/fixtures/excel-oracle/array-formula-ranges.json`).
|
|
57
|
+
* An array formula's own cell is the first of its range in reading order, so a range is always decided
|
|
58
|
+
* before any cell it holds, and a range whose formula was taken claims none.
|
|
59
|
+
*/
|
|
60
|
+
export declare function arrayRangeRepair(placements: Iterable<FormulaPlacement>): ArrayRangeRepair;
|
|
61
|
+
/**
|
|
62
|
+
* Run a model call that is being handed foreign input, and answer `undefined` where the model
|
|
63
|
+
* refuses it.
|
|
64
|
+
*
|
|
65
|
+
* The three caught types are the three a model method raises *about its argument*: an
|
|
66
|
+
* `AuthoringError` for a composite that cannot exist, and the native `RangeError` / `SyntaxError`
|
|
67
|
+
* the taxonomy assigns to a single scalar that is out of range or does not parse. Every one of them
|
|
68
|
+
* is a statement about the caller, and on this path the caller is a file.
|
|
69
|
+
*
|
|
70
|
+
* Nothing else is caught. An `XlsxError` raised by a layer below is a real failure of the read and
|
|
71
|
+
* keeps its identity; an `InternalError` is a bug of ours and must not be swallowed by a reader
|
|
72
|
+
* being tolerant about somebody else's file.
|
|
73
|
+
*/
|
|
74
|
+
export declare function admitting<T>(build: () => T): T | undefined;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { tryDecodeAnchoredRange } from '../../core/address.js';
|
|
2
|
+
import { formulaPlacement } from '../../core/array-formula-ranges.js';
|
|
3
|
+
import { INVALID_SHEET_NAME_CHARS, MAX_SHEET_NAME_LENGTH } from '../../core/limits.js';
|
|
4
|
+
import { MergeIndex } from '../../core/merge-index.js';
|
|
5
|
+
import { isFormulaValue, isSharedFormulaValue } from '../../core/value.js';
|
|
6
|
+
import { AuthoringError } from '../../errors.js';
|
|
7
|
+
const INVALID_SHEET_NAME_CHARS_GLOBAL = new RegExp(INVALID_SHEET_NAME_CHARS.source, 'g');
|
|
8
|
+
export function repairSheetName(name, taken) {
|
|
9
|
+
const stripped = trimApostrophes(name.replace(INVALID_SHEET_NAME_CHARS_GLOBAL, '')).slice(0, MAX_SHEET_NAME_LENGTH);
|
|
10
|
+
const base = trimApostrophes(stripped);
|
|
11
|
+
if (base.length === 0)
|
|
12
|
+
return firstFree((n) => `Sheet${n}`, taken);
|
|
13
|
+
if (!taken.has(base.toLowerCase()))
|
|
14
|
+
return base;
|
|
15
|
+
return firstFree((n) => withSuffix(base, ` (${n})`), taken, 2);
|
|
16
|
+
}
|
|
17
|
+
export function repairedSheetNames(declared) {
|
|
18
|
+
const taken = new Set();
|
|
19
|
+
return declared.map((entry) => {
|
|
20
|
+
const name = repairSheetName(entry.name, taken);
|
|
21
|
+
taken.add(name.toLowerCase());
|
|
22
|
+
return { ...entry, name };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function trimApostrophes(name) {
|
|
26
|
+
return name.replace(/^'+/, '').replace(/'+$/, '');
|
|
27
|
+
}
|
|
28
|
+
function firstFree(candidate, taken, from = 1) {
|
|
29
|
+
for (let n = from;; n++) {
|
|
30
|
+
const name = candidate(n);
|
|
31
|
+
if (!taken.has(name.toLowerCase()))
|
|
32
|
+
return name;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function withSuffix(base, suffix) {
|
|
36
|
+
const room = MAX_SHEET_NAME_LENGTH - suffix.length;
|
|
37
|
+
return trimApostrophes(base.slice(0, room)) + suffix;
|
|
38
|
+
}
|
|
39
|
+
export function admitArrayRanges(sheet) {
|
|
40
|
+
const placements = [];
|
|
41
|
+
for (const { cells } of sheet.rows()) {
|
|
42
|
+
for (const cell of cells) {
|
|
43
|
+
const placement = formulaPlacement(cell.address, cell.col, cell.row, cell.value);
|
|
44
|
+
if (placement !== undefined)
|
|
45
|
+
placements.push(placement);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const { cellsRemoved, formulasRemoved } = arrayRangeRepair(placements);
|
|
49
|
+
if (cellsRemoved.length === 0 && formulasRemoved.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const mastersRemoved = new Set();
|
|
52
|
+
for (const { address } of cellsRemoved) {
|
|
53
|
+
const cell = sheet.getCell(address);
|
|
54
|
+
if (isFormulaValue(cell.value))
|
|
55
|
+
mastersRemoved.add(address);
|
|
56
|
+
cell.value = null;
|
|
57
|
+
}
|
|
58
|
+
for (const { address } of formulasRemoved) {
|
|
59
|
+
const cell = sheet.getCell(address);
|
|
60
|
+
cell.value = cachedResult(cell.value);
|
|
61
|
+
}
|
|
62
|
+
if (mastersRemoved.size === 0)
|
|
63
|
+
return;
|
|
64
|
+
for (const { cells } of sheet.rows()) {
|
|
65
|
+
for (const cell of cells) {
|
|
66
|
+
const value = cell.value;
|
|
67
|
+
if (isSharedFormulaValue(value) && mastersRemoved.has(value.sharedFormula)) {
|
|
68
|
+
cell.value = cachedResult(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function arrayRangeRepair(placements) {
|
|
74
|
+
const ordered = [...placements].sort((a, b) => a.row - b.row || a.col - b.col);
|
|
75
|
+
const keptRanges = [];
|
|
76
|
+
const kept = new MergeIndex(keptRanges);
|
|
77
|
+
const cellsRemoved = [];
|
|
78
|
+
const formulasRemoved = [];
|
|
79
|
+
for (const placement of ordered) {
|
|
80
|
+
const { col, row } = placement;
|
|
81
|
+
if (kept.overlapping({ top: row, left: col, bottom: row, right: col }) !== undefined) {
|
|
82
|
+
cellsRemoved.push(placement);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const range = placement.arrayRef === undefined
|
|
86
|
+
? undefined
|
|
87
|
+
: tryDecodeAnchoredRange(placement.arrayRef, col, row);
|
|
88
|
+
if (range === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
if (kept.overlapping(range) !== undefined) {
|
|
91
|
+
formulasRemoved.push(placement);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
keptRanges.push(range);
|
|
95
|
+
kept.note(range);
|
|
96
|
+
}
|
|
97
|
+
return { cellsRemoved, formulasRemoved };
|
|
98
|
+
}
|
|
99
|
+
function cachedResult(value) {
|
|
100
|
+
return typeof value === 'object' &&
|
|
101
|
+
value !== null &&
|
|
102
|
+
'result' in value &&
|
|
103
|
+
value.result !== undefined
|
|
104
|
+
? value.result
|
|
105
|
+
: null;
|
|
106
|
+
}
|
|
107
|
+
export function admitting(build) {
|
|
108
|
+
try {
|
|
109
|
+
return build();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error instanceof AuthoringError ||
|
|
113
|
+
error instanceof RangeError ||
|
|
114
|
+
error instanceof SyntaxError) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { XfStyle } from './xf-style.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The cell → row → column → xf 0 style resolution for one worksheet, fed in the order every reader
|
|
4
|
+
* already visits a sheet: column formats before any cell refers to them, a row before its own cells.
|
|
5
|
+
* Columns are 1-based, and a negative style index means "declares no format".
|
|
6
|
+
*/
|
|
7
|
+
export declare class CellStyleResolver {
|
|
8
|
+
#private;
|
|
9
|
+
/** Record the format a span of columns gives its cells. */
|
|
10
|
+
noteColumnSpan(first: number, last: number, styleIndex: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Open a row. Its format reaches its cells only when the row is marked `customFormat`; without that
|
|
13
|
+
* flag the format describes the row itself and is not inherited.
|
|
14
|
+
*/
|
|
15
|
+
openRow(styleIndex: number, customFormat: boolean): void;
|
|
16
|
+
/** Close the row, so a cell outside one never inherits the last row's format. */
|
|
17
|
+
closeRow(): void;
|
|
18
|
+
/**
|
|
19
|
+
* The style a cell resolves to: its own, else its row's, else its column's, else xf 0. `undefined`
|
|
20
|
+
* only when the index it resolves to names no record in `xfStyles`, a damaged or truncated table.
|
|
21
|
+
*/
|
|
22
|
+
styleFor(col: number, cellStyleIndex: number, xfStyles: ReadonlyArray<XfStyle>): XfStyle | undefined;
|
|
23
|
+
/** Whether the cell, its row or its column declares a format, rather than the cell falling to xf 0. */
|
|
24
|
+
declaresFormat(col: number, cellStyleIndex: number): boolean;
|
|
25
|
+
}
|