@shbernal/ts-xlsx 1.0.3 → 1.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/dist/core/cell.d.ts +10 -0
- package/dist/core/cell.js +13 -1
- package/dist/core/column.d.ts +9 -1
- package/dist/core/column.js +9 -1
- package/dist/core/limits.d.ts +30 -0
- package/dist/core/limits.js +47 -0
- package/dist/core/pivot-table.js +4 -0
- package/dist/core/row.d.ts +9 -1
- package/dist/core/row.js +9 -1
- package/dist/core/text-metrics.d.ts +20 -0
- package/dist/core/text-metrics.js +50 -0
- package/dist/core/value.d.ts +40 -0
- package/dist/core/value.js +76 -0
- package/dist/core/workbook.d.ts +13 -0
- package/dist/core/workbook.js +23 -0
- package/dist/core/worksheet.d.ts +22 -0
- package/dist/core/worksheet.js +28 -0
- package/dist/entries/core.d.ts +3 -1
- package/dist/entries/core.js +3 -1
- package/dist/io/csv/write.js +11 -23
- package/package.json +2 -2
package/dist/core/cell.d.ts
CHANGED
|
@@ -22,6 +22,16 @@ export declare class Cell {
|
|
|
22
22
|
set value(value: CellValue | undefined);
|
|
23
23
|
/** The observable {@link ValueType} of the current value. */
|
|
24
24
|
get type(): ValueType;
|
|
25
|
+
/**
|
|
26
|
+
* The cell's value as plain text ({@link cellValueToText}), `""` when it is empty — so a reader
|
|
27
|
+
* that only wants strings never has to narrow the value union itself.
|
|
28
|
+
*
|
|
29
|
+
* Read-only, because text is a *rendering* of the value and not a second place to store one:
|
|
30
|
+
* writing `"3"` here could only mean the string `"3"`, which is exactly `value = '3'` and reads
|
|
31
|
+
* nothing like it. The number format is not applied either — the style is not the cell's value,
|
|
32
|
+
* so a currency cell's text carries no currency sign.
|
|
33
|
+
*/
|
|
34
|
+
get text(): string;
|
|
25
35
|
/**
|
|
26
36
|
* Assign rich text whose runs **inherit this cell's font**, so a run needs to state only what it
|
|
27
37
|
* changes: `setRichText([{text: 'Note:', font: {bold: true}}, {text: ' the rest'}])` keeps the
|
package/dist/core/cell.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { encodeAddress } from './address.js';
|
|
8
8
|
import { NAMED_STYLE_ID } from './internal.js';
|
|
9
9
|
import { assignStyleFacets, } from './style.js';
|
|
10
|
-
import { coerceCellValue, detectValueType, } from './value.js';
|
|
10
|
+
import { cellValueToText, coerceCellValue, detectValueType, } from './value.js';
|
|
11
11
|
/**
|
|
12
12
|
* A single cell owns its value and every style facet outright. Each facet below — fill, number format,
|
|
13
13
|
* font, border, alignment, protection, quote-prefix, and note — is held in the cell's own field and
|
|
@@ -54,6 +54,18 @@ export class Cell {
|
|
|
54
54
|
get type() {
|
|
55
55
|
return detectValueType(this.#value);
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The cell's value as plain text ({@link cellValueToText}), `""` when it is empty — so a reader
|
|
59
|
+
* that only wants strings never has to narrow the value union itself.
|
|
60
|
+
*
|
|
61
|
+
* Read-only, because text is a *rendering* of the value and not a second place to store one:
|
|
62
|
+
* writing `"3"` here could only mean the string `"3"`, which is exactly `value = '3'` and reads
|
|
63
|
+
* nothing like it. The number format is not applied either — the style is not the cell's value,
|
|
64
|
+
* so a currency cell's text carries no currency sign.
|
|
65
|
+
*/
|
|
66
|
+
get text() {
|
|
67
|
+
return cellValueToText(this.#value);
|
|
68
|
+
}
|
|
57
69
|
/**
|
|
58
70
|
* Assign rich text whose runs **inherit this cell's font**, so a run needs to state only what it
|
|
59
71
|
* changes: `setRichText([{text: 'Note:', font: {bold: true}}, {text: ' the rest'}])` keeps the
|
package/dist/core/column.d.ts
CHANGED
|
@@ -23,7 +23,15 @@ export declare class Column {
|
|
|
23
23
|
*/
|
|
24
24
|
get key(): string | undefined;
|
|
25
25
|
set key(key: string | undefined);
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Column width in character units — digits of the workbook default font's maximum digit width,
|
|
28
|
+
* so what one unit measures moves with that font. `undefined` leaves the sheet default in force.
|
|
29
|
+
*
|
|
30
|
+
* Not bounded here, for the same reason {@link Row.height} is not, and for a stronger one:
|
|
31
|
+
* {@link MAX_COLUMN_WIDTH} bounds what Excel accepts being set, not what a file may hold. Excel
|
|
32
|
+
* reads a wider column back unchanged and re-saves it verbatim, so a bound here would refuse a
|
|
33
|
+
* width Excel itself preserves.
|
|
34
|
+
*/
|
|
27
35
|
get width(): number | undefined;
|
|
28
36
|
set width(width: number | undefined);
|
|
29
37
|
/** Whether the column is hidden. */
|
package/dist/core/column.js
CHANGED
|
@@ -44,7 +44,15 @@ export class Column {
|
|
|
44
44
|
set key(key) {
|
|
45
45
|
this.#write('key', key);
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Column width in character units — digits of the workbook default font's maximum digit width,
|
|
49
|
+
* so what one unit measures moves with that font. `undefined` leaves the sheet default in force.
|
|
50
|
+
*
|
|
51
|
+
* Not bounded here, for the same reason {@link Row.height} is not, and for a stronger one:
|
|
52
|
+
* {@link MAX_COLUMN_WIDTH} bounds what Excel accepts being set, not what a file may hold. Excel
|
|
53
|
+
* reads a wider column back unchanged and re-saves it verbatim, so a bound here would refuse a
|
|
54
|
+
* width Excel itself preserves.
|
|
55
|
+
*/
|
|
48
56
|
get width() {
|
|
49
57
|
return this.#read('width');
|
|
50
58
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tallest row Excel accepts being set to, in points: it refuses 409.6 and takes 409.5. A row
|
|
3
|
+
* asked to hold more wrapped text than this cannot grow to fit it, and the overflow is simply not
|
|
4
|
+
* shown.
|
|
5
|
+
*
|
|
6
|
+
* A file may state more, and stating more loses the value rather than the file. Excel opens such a
|
|
7
|
+
* package without complaint and silently clamps the row — to 409.6, a tick *above* what it lets
|
|
8
|
+
* you assign, being 8192 twentieths of a point and so the width of the field it is read into — and
|
|
9
|
+
* writes 409.6 back on its next save. Check against this constant to keep a stated height from
|
|
10
|
+
* quietly becoming a different one.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_ROW_HEIGHT = 409.5;
|
|
13
|
+
/**
|
|
14
|
+
* The widest column Excel accepts being set to, in character units of the workbook's default font.
|
|
15
|
+
* Excel refuses 255.4, so unlike the row-height ceiling this one is exactly integral.
|
|
16
|
+
*
|
|
17
|
+
* It is also the weaker of the two ceilings: it binds assignment only, and not a file at all.
|
|
18
|
+
* Excel honours a `width` of 1000 read from a package, renders the column at it, and round-trips
|
|
19
|
+
* it verbatim through its own save — where an over-limit row height is clamped away. So a width
|
|
20
|
+
* above this is a column no Excel user could have produced by dragging, not a value at risk.
|
|
21
|
+
*
|
|
22
|
+
* Character units, not points or pixels: a width is a count of digits of the default font's
|
|
23
|
+
* *maximum digit width*, which is why there is no companion `DEFAULT_COLUMN_WIDTH` constant here.
|
|
24
|
+
* The width a column takes when it states none is a function of that font — the familiar 8.43 holds
|
|
25
|
+
* for Calibri 11 and not for a workbook whose normal style says otherwise (Excel reports 8.09 for
|
|
26
|
+
* Aptos Narrow 11). `sheet.properties.defaultColWidth` is what a file declares, and
|
|
27
|
+
* docs/knowledge/specs/default-font-must-not-be-assumed-for-column-widths.md is why assuming a
|
|
28
|
+
* value for it is a bug rather than a shortcut.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MAX_COLUMN_WIDTH = 255;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Excel's limits on the grid's *geometry* — the other half of the bounds `address.ts` states.
|
|
2
|
+
//
|
|
3
|
+
// `MAX_ROW`/`MAX_COLUMN` bound where a cell can be; these bound how big a line can be *set*. The
|
|
4
|
+
// difference that matters is who enforces them: the addressing bounds are structural (a reference
|
|
5
|
+
// past XFD is not a reference), so the model refuses them outright, while these are limits on
|
|
6
|
+
// *assignment* in Excel's own UI and object model. They are not limits on what a package may
|
|
7
|
+
// carry. The schema types `ht` and `width` as a bare `xsd:double` with no ceiling on either, and
|
|
8
|
+
// Excel opens an over-limit file clean — no repair prompt, no repair log — so a reader that threw
|
|
9
|
+
// on one would refuse a file Excel accepts. Nothing here is enforced, therefore, on either the
|
|
10
|
+
// read or the write path; these are for a caller that wants the size it states to be the size
|
|
11
|
+
// Excel uses, and the doc comments on `Row.height`/`Column.width` point here for that reason.
|
|
12
|
+
//
|
|
13
|
+
// What Excel does with an over-limit file is not symmetric, and the difference is why only one of
|
|
14
|
+
// these two numbers describes a value that survives: a row is silently clamped, a column is not.
|
|
15
|
+
// Both ceilings and both file behaviours were measured rather than quoted — Microsoft's own
|
|
16
|
+
// specifications page rounds the row-height ceiling to "409 points", and Excel takes 409.5. See
|
|
17
|
+
// docs/knowledge/specs/grid-geometry-limits-are-excels-not-the-schemas.md for the probes.
|
|
18
|
+
/**
|
|
19
|
+
* The tallest row Excel accepts being set to, in points: it refuses 409.6 and takes 409.5. A row
|
|
20
|
+
* asked to hold more wrapped text than this cannot grow to fit it, and the overflow is simply not
|
|
21
|
+
* shown.
|
|
22
|
+
*
|
|
23
|
+
* A file may state more, and stating more loses the value rather than the file. Excel opens such a
|
|
24
|
+
* package without complaint and silently clamps the row — to 409.6, a tick *above* what it lets
|
|
25
|
+
* you assign, being 8192 twentieths of a point and so the width of the field it is read into — and
|
|
26
|
+
* writes 409.6 back on its next save. Check against this constant to keep a stated height from
|
|
27
|
+
* quietly becoming a different one.
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_ROW_HEIGHT = 409.5;
|
|
30
|
+
/**
|
|
31
|
+
* The widest column Excel accepts being set to, in character units of the workbook's default font.
|
|
32
|
+
* Excel refuses 255.4, so unlike the row-height ceiling this one is exactly integral.
|
|
33
|
+
*
|
|
34
|
+
* It is also the weaker of the two ceilings: it binds assignment only, and not a file at all.
|
|
35
|
+
* Excel honours a `width` of 1000 read from a package, renders the column at it, and round-trips
|
|
36
|
+
* it verbatim through its own save — where an over-limit row height is clamped away. So a width
|
|
37
|
+
* above this is a column no Excel user could have produced by dragging, not a value at risk.
|
|
38
|
+
*
|
|
39
|
+
* Character units, not points or pixels: a width is a count of digits of the default font's
|
|
40
|
+
* *maximum digit width*, which is why there is no companion `DEFAULT_COLUMN_WIDTH` constant here.
|
|
41
|
+
* The width a column takes when it states none is a function of that font — the familiar 8.43 holds
|
|
42
|
+
* for Calibri 11 and not for a workbook whose normal style says otherwise (Excel reports 8.09 for
|
|
43
|
+
* Aptos Narrow 11). `sheet.properties.defaultColWidth` is what a file declares, and
|
|
44
|
+
* docs/knowledge/specs/default-font-must-not-be-assumed-for-column-widths.md is why assuming a
|
|
45
|
+
* value for it is a bug rather than a shortcut.
|
|
46
|
+
*/
|
|
47
|
+
export const MAX_COLUMN_WIDTH = 255;
|
package/dist/core/pivot-table.js
CHANGED
|
@@ -216,6 +216,10 @@ function textOf(item) {
|
|
|
216
216
|
* Reduce any cell value to the scalar a pivot cache can hold: a number, a string, or a blank. Only
|
|
217
217
|
* finite numbers stay numeric (a NaN would corrupt the cache); every other kind is flattened to its
|
|
218
218
|
* displayed text so hostile or exotic source content can never throw or leak an object into the XML.
|
|
219
|
+
*
|
|
220
|
+
* Deliberately not `cellValueToText`, close as the two look: this classifies rather than renders
|
|
221
|
+
* (a number must reach the cache *as a number*), and its no-throw promise is the opposite of that
|
|
222
|
+
* function's, which rejects a value outside the union rather than quietly caching a blank.
|
|
219
223
|
*/
|
|
220
224
|
function scalarOf(value) {
|
|
221
225
|
if (value === null)
|
package/dist/core/row.d.ts
CHANGED
|
@@ -15,7 +15,15 @@ export declare class Row {
|
|
|
15
15
|
* formatted, and they create the record on first write.
|
|
16
16
|
*/
|
|
17
17
|
get properties(): Readonly<RowProperties> | undefined;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Row height in points; `undefined` leaves the sheet default in force.
|
|
20
|
+
*
|
|
21
|
+
* Not bounded here, deliberately: {@link MAX_ROW_HEIGHT} is what Excel accepts *being set*, but
|
|
22
|
+
* the schema puts no ceiling on `ht` and this setter is also how the reader loads a foreign
|
|
23
|
+
* file, so refusing a taller row would mean refusing a file Excel opens clean. Check against the
|
|
24
|
+
* constant when authoring — Excel silently clamps a taller row on read, so a height above it is
|
|
25
|
+
* one you state and do not get.
|
|
26
|
+
*/
|
|
19
27
|
get height(): number | undefined;
|
|
20
28
|
set height(height: number | undefined);
|
|
21
29
|
/** Whether the row is hidden. */
|
package/dist/core/row.js
CHANGED
|
@@ -35,7 +35,15 @@ export class Row {
|
|
|
35
35
|
get properties() {
|
|
36
36
|
return this.#sheet[INTERNAL].rowPropertiesOf(this.number);
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Row height in points; `undefined` leaves the sheet default in force.
|
|
40
|
+
*
|
|
41
|
+
* Not bounded here, deliberately: {@link MAX_ROW_HEIGHT} is what Excel accepts *being set*, but
|
|
42
|
+
* the schema puts no ceiling on `ht` and this setter is also how the reader loads a foreign
|
|
43
|
+
* file, so refusing a taller row would mean refusing a file Excel opens clean. Check against the
|
|
44
|
+
* constant when authoring — Excel silently clamps a taller row on read, so a height above it is
|
|
45
|
+
* one you state and do not get.
|
|
46
|
+
*/
|
|
39
47
|
get height() {
|
|
40
48
|
return this.#read('height');
|
|
41
49
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The number of lines `text` occupies when wrapped at `width` character units - the width unit a
|
|
3
|
+
* column states, so `estimateWrappedLines(cell, sheet.getColumn(2).width ?? 8.43)` is the shape of
|
|
4
|
+
* the call.
|
|
5
|
+
*
|
|
6
|
+
* A hard break opens a line of its own and what follows wraps independently, matching how Excel
|
|
7
|
+
* lays a wrapped cell out. The empty string is one line, not zero: a cell always occupies its row.
|
|
8
|
+
*
|
|
9
|
+
* An estimate, and only ever that. It counts characters, so it is exact for a monospaced face that
|
|
10
|
+
* wraps mid-word and approximate for every other - a run of `W`s wraps sooner on screen than this
|
|
11
|
+
* predicts, a run of `i`s later. Against Excel it reads a shade *low*, because Excel breaks at word
|
|
12
|
+
* boundaries and its usable width is about 0.64 character units under the stated one: measured at
|
|
13
|
+
* 5 lines where Excel laid out 6, 25 where Excel laid out 26. This exists so that a writer can
|
|
14
|
+
* state *a* height rather than leave one to the application that opens the file, and being within a
|
|
15
|
+
* line of the truth is what that needs.
|
|
16
|
+
*
|
|
17
|
+
* @throws {RangeError} if `width` is not a positive finite number - a column of zero width wraps
|
|
18
|
+
* nothing, and silently answering `Infinity` or `NaN` would put that straight into a row height.
|
|
19
|
+
*/
|
|
20
|
+
export declare function estimateWrappedLines(text: string, width: number): number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// How many lines a wrapped cell takes - the one measurement a writer needs and the format does
|
|
2
|
+
// not record.
|
|
3
|
+
//
|
|
4
|
+
// A row that states no height records no geometry, so the height is whoever opens the file's
|
|
5
|
+
// answer. Excel Desktop's answer is an auto-fit computed on open - promptly and correctly, but
|
|
6
|
+
// saturating at MAX_ROW_HEIGHT, so past ~28 lines it stops answering the question - and no answer
|
|
7
|
+
// at all is what a consumer that does not implement that auto-fit gets, this library's reader
|
|
8
|
+
// included. Stating a height is how the geometry stops depending on the reader, and to state one
|
|
9
|
+
// you have to know the line count. Measured in
|
|
10
|
+
// docs/knowledge/specs/rows-with-no-stated-height-are-autofitted-on-open.md, which also buries the
|
|
11
|
+
// claim this comment used to make, that such a sheet opens with blank unpainted bands. It does not.
|
|
12
|
+
//
|
|
13
|
+
// This counts *characters against a character-unit width*. It does not measure glyphs, and it is
|
|
14
|
+
// not the deferred font-metric question that
|
|
15
|
+
// docs/knowledge/specs/default-font-must-not-be-assumed-for-column-widths.md leaves open: a column
|
|
16
|
+
// width is already expressed in character units, so counting characters is dimensionally honest
|
|
17
|
+
// and needs no metric table. It is also, for the same reason, an approximation for any
|
|
18
|
+
// proportional face - `WWW` and `iii` are one character unit each here and are not on screen.
|
|
19
|
+
/**
|
|
20
|
+
* The number of lines `text` occupies when wrapped at `width` character units - the width unit a
|
|
21
|
+
* column states, so `estimateWrappedLines(cell, sheet.getColumn(2).width ?? 8.43)` is the shape of
|
|
22
|
+
* the call.
|
|
23
|
+
*
|
|
24
|
+
* A hard break opens a line of its own and what follows wraps independently, matching how Excel
|
|
25
|
+
* lays a wrapped cell out. The empty string is one line, not zero: a cell always occupies its row.
|
|
26
|
+
*
|
|
27
|
+
* An estimate, and only ever that. It counts characters, so it is exact for a monospaced face that
|
|
28
|
+
* wraps mid-word and approximate for every other - a run of `W`s wraps sooner on screen than this
|
|
29
|
+
* predicts, a run of `i`s later. Against Excel it reads a shade *low*, because Excel breaks at word
|
|
30
|
+
* boundaries and its usable width is about 0.64 character units under the stated one: measured at
|
|
31
|
+
* 5 lines where Excel laid out 6, 25 where Excel laid out 26. This exists so that a writer can
|
|
32
|
+
* state *a* height rather than leave one to the application that opens the file, and being within a
|
|
33
|
+
* line of the truth is what that needs.
|
|
34
|
+
*
|
|
35
|
+
* @throws {RangeError} if `width` is not a positive finite number - a column of zero width wraps
|
|
36
|
+
* nothing, and silently answering `Infinity` or `NaN` would put that straight into a row height.
|
|
37
|
+
*/
|
|
38
|
+
export function estimateWrappedLines(text, width) {
|
|
39
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
40
|
+
throw new RangeError(`wrap width ${width} is not a positive finite number of character units`);
|
|
41
|
+
}
|
|
42
|
+
let lines = 0;
|
|
43
|
+
// A lone \r is not a break Excel writes, but a value assembled by a caller on Windows may carry
|
|
44
|
+
// one; counting it as a break costs nothing and mis-counting it as a character would show up as
|
|
45
|
+
// a row one line short.
|
|
46
|
+
for (const segment of text.split(/\r\n?|\n/)) {
|
|
47
|
+
lines += Math.max(1, Math.ceil(segment.length / width));
|
|
48
|
+
}
|
|
49
|
+
return lines;
|
|
50
|
+
}
|
package/dist/core/value.d.ts
CHANGED
|
@@ -81,11 +81,32 @@ export interface DataTableFormulaValue {
|
|
|
81
81
|
}
|
|
82
82
|
/** Everything a cell's value can be. `null` is the empty cell. */
|
|
83
83
|
export type CellValue = null | number | string | boolean | Date | ErrorValue | FormulaValue | SharedFormulaValue | DataTableFormulaValue | RichTextValue | HyperlinkValue;
|
|
84
|
+
/**
|
|
85
|
+
* Whether a value is an in-cell error ({@link ErrorValue}). The narrowing counterpart of
|
|
86
|
+
* `detectValueType(value) === ValueType.Error`: use this one when the branch goes on to read
|
|
87
|
+
* `.error`, and {@link detectValueType} when it dispatches over all nine kinds at once.
|
|
88
|
+
*/
|
|
84
89
|
export declare function isErrorValue(value: CellValue): value is ErrorValue;
|
|
90
|
+
/**
|
|
91
|
+
* Whether a value is a cell's own formula ({@link FormulaValue}) — a master, or a formula
|
|
92
|
+
* belonging to no shared group. A shared-formula clone is **not** one of these; see
|
|
93
|
+
* {@link isSharedFormulaValue}. Both report as `ValueType.Formula`, so a caller that means "any
|
|
94
|
+
* formula-shaped cell" wants {@link detectValueType}, not this.
|
|
95
|
+
*/
|
|
85
96
|
export declare function isFormulaValue(value: CellValue): value is FormulaValue;
|
|
97
|
+
/** Whether a value is a clone participating in a shared formula ({@link SharedFormulaValue}). */
|
|
86
98
|
export declare function isSharedFormulaValue(value: CellValue): value is SharedFormulaValue;
|
|
99
|
+
/** Whether a value is a What-If-Analysis data-table formula ({@link DataTableFormulaValue}). */
|
|
87
100
|
export declare function isDataTableFormulaValue(value: CellValue): value is DataTableFormulaValue;
|
|
101
|
+
/**
|
|
102
|
+
* Whether a value is composed of formatted runs ({@link RichTextValue}). This is the test to
|
|
103
|
+
* make before {@link richTextToPlain}, which accepts nothing else.
|
|
104
|
+
*/
|
|
88
105
|
export declare function isRichTextValue(value: CellValue): value is RichTextValue;
|
|
106
|
+
/**
|
|
107
|
+
* Whether a value is a hyperlink ({@link HyperlinkValue}). Note that its `text` is itself either
|
|
108
|
+
* a string or a {@link RichTextValue}, so reading the label out means one more narrowing.
|
|
109
|
+
*/
|
|
89
110
|
export declare function isHyperlinkValue(value: CellValue): value is HyperlinkValue;
|
|
90
111
|
/**
|
|
91
112
|
* Flatten a rich-text value to its plain text by concatenating every run's text in order. This is the
|
|
@@ -93,6 +114,25 @@ export declare function isHyperlinkValue(value: CellValue): value is HyperlinkVa
|
|
|
93
114
|
* the string a rich cell reads as when its formatting is discarded.
|
|
94
115
|
*/
|
|
95
116
|
export declare function richTextToPlain(value: RichTextValue): string;
|
|
117
|
+
/**
|
|
118
|
+
* The plain text of any cell value — total over {@link CellValue}, so a caller reading a sheet
|
|
119
|
+
* whose cells it did not write never has to switch on the union itself.
|
|
120
|
+
*
|
|
121
|
+
* This is the *value's* text, not the cell's *display* text: a number renders as JavaScript
|
|
122
|
+
* renders it, with no number format applied (`0.1 + 0.2` is `"0.30000000000000004"`, a currency
|
|
123
|
+
* cell has no currency sign), because the format lives on the style and this function is given
|
|
124
|
+
* only the value. What each kind yields:
|
|
125
|
+
*
|
|
126
|
+
* - the empty cell (`null`) and an invalid `Date` → `""`, the two ways a cell has no text
|
|
127
|
+
* - a boolean → `"TRUE"` / `"FALSE"`, Excel's own literals rather than JavaScript's
|
|
128
|
+
* - a `Date` → a full ISO-8601 timestamp
|
|
129
|
+
* - an error → its literal, e.g. `"#REF!"` — the same string the grid shows
|
|
130
|
+
* - rich text → every run concatenated ({@link richTextToPlain})
|
|
131
|
+
* - a hyperlink → its label, never its destination
|
|
132
|
+
* - any of the three formula kinds → the text of the *cached result*, and `""` when the cell
|
|
133
|
+
* carries no cached result: the formula source is not text the sheet ever displayed
|
|
134
|
+
*/
|
|
135
|
+
export declare function cellValueToText(value: CellValue): string;
|
|
96
136
|
/**
|
|
97
137
|
* Classify a value into its observable {@link ValueType}. This is total over
|
|
98
138
|
* {@link CellValue}: every legal value has exactly one type. A `Date` is a date even
|
package/dist/core/value.js
CHANGED
|
@@ -38,23 +38,44 @@ const ERROR_SET = new Set(ERROR_CODES);
|
|
|
38
38
|
function hasKey(value, key) {
|
|
39
39
|
return typeof value === 'object' && value !== null && key in value;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether a value is an in-cell error ({@link ErrorValue}). The narrowing counterpart of
|
|
43
|
+
* `detectValueType(value) === ValueType.Error`: use this one when the branch goes on to read
|
|
44
|
+
* `.error`, and {@link detectValueType} when it dispatches over all nine kinds at once.
|
|
45
|
+
*/
|
|
41
46
|
export function isErrorValue(value) {
|
|
42
47
|
return hasKey(value, 'error');
|
|
43
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether a value is a cell's own formula ({@link FormulaValue}) — a master, or a formula
|
|
51
|
+
* belonging to no shared group. A shared-formula clone is **not** one of these; see
|
|
52
|
+
* {@link isSharedFormulaValue}. Both report as `ValueType.Formula`, so a caller that means "any
|
|
53
|
+
* formula-shaped cell" wants {@link detectValueType}, not this.
|
|
54
|
+
*/
|
|
44
55
|
export function isFormulaValue(value) {
|
|
45
56
|
// A shared-formula clone resolved on read carries both its master address (`sharedFormula`) and the
|
|
46
57
|
// translated `formula`; it is a SharedFormulaValue, so exclude it here to keep the two kinds distinct.
|
|
47
58
|
return hasKey(value, 'formula') && !('sharedFormula' in value);
|
|
48
59
|
}
|
|
60
|
+
/** Whether a value is a clone participating in a shared formula ({@link SharedFormulaValue}). */
|
|
49
61
|
export function isSharedFormulaValue(value) {
|
|
50
62
|
return hasKey(value, 'sharedFormula');
|
|
51
63
|
}
|
|
64
|
+
/** Whether a value is a What-If-Analysis data-table formula ({@link DataTableFormulaValue}). */
|
|
52
65
|
export function isDataTableFormulaValue(value) {
|
|
53
66
|
return hasKey(value, 'shareType') && value.shareType === 'dataTable';
|
|
54
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a value is composed of formatted runs ({@link RichTextValue}). This is the test to
|
|
70
|
+
* make before {@link richTextToPlain}, which accepts nothing else.
|
|
71
|
+
*/
|
|
55
72
|
export function isRichTextValue(value) {
|
|
56
73
|
return hasKey(value, 'richText');
|
|
57
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether a value is a hyperlink ({@link HyperlinkValue}). Note that its `text` is itself either
|
|
77
|
+
* a string or a {@link RichTextValue}, so reading the label out means one more narrowing.
|
|
78
|
+
*/
|
|
58
79
|
export function isHyperlinkValue(value) {
|
|
59
80
|
return hasKey(value, 'hyperlink');
|
|
60
81
|
}
|
|
@@ -66,6 +87,54 @@ export function isHyperlinkValue(value) {
|
|
|
66
87
|
export function richTextToPlain(value) {
|
|
67
88
|
return value.richText.map((run) => run.text).join('');
|
|
68
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* The plain text of any cell value — total over {@link CellValue}, so a caller reading a sheet
|
|
92
|
+
* whose cells it did not write never has to switch on the union itself.
|
|
93
|
+
*
|
|
94
|
+
* This is the *value's* text, not the cell's *display* text: a number renders as JavaScript
|
|
95
|
+
* renders it, with no number format applied (`0.1 + 0.2` is `"0.30000000000000004"`, a currency
|
|
96
|
+
* cell has no currency sign), because the format lives on the style and this function is given
|
|
97
|
+
* only the value. What each kind yields:
|
|
98
|
+
*
|
|
99
|
+
* - the empty cell (`null`) and an invalid `Date` → `""`, the two ways a cell has no text
|
|
100
|
+
* - a boolean → `"TRUE"` / `"FALSE"`, Excel's own literals rather than JavaScript's
|
|
101
|
+
* - a `Date` → a full ISO-8601 timestamp
|
|
102
|
+
* - an error → its literal, e.g. `"#REF!"` — the same string the grid shows
|
|
103
|
+
* - rich text → every run concatenated ({@link richTextToPlain})
|
|
104
|
+
* - a hyperlink → its label, never its destination
|
|
105
|
+
* - any of the three formula kinds → the text of the *cached result*, and `""` when the cell
|
|
106
|
+
* carries no cached result: the formula source is not text the sheet ever displayed
|
|
107
|
+
*/
|
|
108
|
+
export function cellValueToText(value) {
|
|
109
|
+
if (value === null)
|
|
110
|
+
return '';
|
|
111
|
+
switch (typeof value) {
|
|
112
|
+
case 'number':
|
|
113
|
+
return String(value);
|
|
114
|
+
case 'string':
|
|
115
|
+
return value;
|
|
116
|
+
case 'boolean':
|
|
117
|
+
return value ? 'TRUE' : 'FALSE';
|
|
118
|
+
default:
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (value instanceof Date)
|
|
122
|
+
return Number.isNaN(value.getTime()) ? '' : value.toISOString();
|
|
123
|
+
// Same precedence as detectValueType, and for the same reason: the outer shape wins, so a
|
|
124
|
+
// hyperlink whose label is rich text renders as a hyperlink's label, not as rich text.
|
|
125
|
+
if (isHyperlinkValue(value)) {
|
|
126
|
+
return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
|
|
127
|
+
}
|
|
128
|
+
// Every formula kind carries a `result` of the same optional shape; one recursion renders it.
|
|
129
|
+
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
130
|
+
return value.result === undefined ? '' : cellValueToText(value.result);
|
|
131
|
+
}
|
|
132
|
+
if (isRichTextValue(value))
|
|
133
|
+
return richTextToPlain(value);
|
|
134
|
+
if (isErrorValue(value))
|
|
135
|
+
return value.error;
|
|
136
|
+
return unsupportedValue(value);
|
|
137
|
+
}
|
|
69
138
|
/**
|
|
70
139
|
* Classify a value into its observable {@link ValueType}. This is total over
|
|
71
140
|
* {@link CellValue}: every legal value has exactly one type. A `Date` is a date even
|
|
@@ -98,6 +167,13 @@ export function detectValueType(value) {
|
|
|
98
167
|
return ValueType.RichText;
|
|
99
168
|
if (isErrorValue(value))
|
|
100
169
|
return ValueType.Error;
|
|
170
|
+
return unsupportedValue(value);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The verdict "this is not a cell value at all", raised from one place so that every function
|
|
174
|
+
* total over {@link CellValue} rejects the same inputs with the same message.
|
|
175
|
+
*/
|
|
176
|
+
function unsupportedValue(value) {
|
|
101
177
|
throw new TypeError(`unsupported cell value: ${describe(value)}`);
|
|
102
178
|
}
|
|
103
179
|
/** Whether a string is one of Excel's canonical error literals. */
|
package/dist/core/workbook.d.ts
CHANGED
|
@@ -444,6 +444,19 @@ export declare class Workbook {
|
|
|
444
444
|
addWorksheet(name: string, options?: AddWorksheetOptions): Worksheet;
|
|
445
445
|
/** Look up a worksheet by name (case-insensitive) or by numeric id. */
|
|
446
446
|
getWorksheet(nameOrId: string | number): Worksheet | undefined;
|
|
447
|
+
/**
|
|
448
|
+
* {@link getWorksheet}, for a caller who knows the sheet is there — the miss throws instead of
|
|
449
|
+
* returning `undefined`, and the message names every sheet the workbook does have.
|
|
450
|
+
*
|
|
451
|
+
* The partial lookup is the right primitive for asking *whether* a sheet exists, and the wrong
|
|
452
|
+
* one for reaching a sheet a template is expected to carry: `undefined` flows on into a `?.`
|
|
453
|
+
* chain and fails several steps later with nothing left to say about which name was missing.
|
|
454
|
+
* That listing is the whole point — a lookup miss is a typo, a stale template or a renamed tab,
|
|
455
|
+
* and all three are answered by seeing the real names.
|
|
456
|
+
*
|
|
457
|
+
* @throws {AuthoringError} if no worksheet has that name (case-insensitive) or numeric id.
|
|
458
|
+
*/
|
|
459
|
+
requireWorksheet(nameOrId: string | number): Worksheet;
|
|
447
460
|
/**
|
|
448
461
|
* The codec's channel into this workbook — see `core/internal.ts` for why these are not public
|
|
449
462
|
* methods. Declared last so every private field it closes over is already in scope.
|
package/dist/core/workbook.js
CHANGED
|
@@ -673,6 +673,29 @@ export class Workbook {
|
|
|
673
673
|
const target = nameOrId.toLowerCase();
|
|
674
674
|
return this.#worksheets.find((sheet) => sheet.name.toLowerCase() === target);
|
|
675
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* {@link getWorksheet}, for a caller who knows the sheet is there — the miss throws instead of
|
|
678
|
+
* returning `undefined`, and the message names every sheet the workbook does have.
|
|
679
|
+
*
|
|
680
|
+
* The partial lookup is the right primitive for asking *whether* a sheet exists, and the wrong
|
|
681
|
+
* one for reaching a sheet a template is expected to carry: `undefined` flows on into a `?.`
|
|
682
|
+
* chain and fails several steps later with nothing left to say about which name was missing.
|
|
683
|
+
* That listing is the whole point — a lookup miss is a typo, a stale template or a renamed tab,
|
|
684
|
+
* and all three are answered by seeing the real names.
|
|
685
|
+
*
|
|
686
|
+
* @throws {AuthoringError} if no worksheet has that name (case-insensitive) or numeric id.
|
|
687
|
+
*/
|
|
688
|
+
requireWorksheet(nameOrId) {
|
|
689
|
+
const sheet = this.getWorksheet(nameOrId);
|
|
690
|
+
if (sheet !== undefined)
|
|
691
|
+
return sheet;
|
|
692
|
+
const wanted = typeof nameOrId === 'number' ? `id ${nameOrId}` : JSON.stringify(nameOrId);
|
|
693
|
+
if (this.#worksheets.length === 0) {
|
|
694
|
+
throw new AuthoringError(`no worksheet ${wanted}: this workbook has no worksheets`);
|
|
695
|
+
}
|
|
696
|
+
const have = this.#worksheets.map((sheet) => JSON.stringify(sheet.name)).join(', ');
|
|
697
|
+
throw new AuthoringError(`no worksheet ${wanted}; this workbook has ${have}`);
|
|
698
|
+
}
|
|
676
699
|
#assertValidSheetName(name) {
|
|
677
700
|
if (name.length === 0) {
|
|
678
701
|
throw new AuthoringError('worksheet name cannot be empty');
|
package/dist/core/worksheet.d.ts
CHANGED
|
@@ -259,6 +259,28 @@ export declare class Worksheet {
|
|
|
259
259
|
* other axis: a value in column E makes this 5 even if columns B–D are empty.
|
|
260
260
|
*/
|
|
261
261
|
get columnCount(): number;
|
|
262
|
+
/**
|
|
263
|
+
* The sheet's used range as one handle — `A1` through the last row and column that carry
|
|
264
|
+
* anything — or `undefined` when there is no rectangle to name.
|
|
265
|
+
*
|
|
266
|
+
* This is {@link rowCount} and {@link columnCount} said once, so a caller stops reassembling
|
|
267
|
+
* `A1:${numberToColumn(sheet.columnCount)}${sheet.rowCount}` by hand. That is what an
|
|
268
|
+
* {@link autoFilter} covering the whole sheet wants — `sheet.autoFilter = sheet.usedRange.address`
|
|
269
|
+
* — and Excel writes exactly that ref for a filter it applies itself. A header-only ref filters
|
|
270
|
+
* nothing, which is the bug this exists to make hard to write.
|
|
271
|
+
*
|
|
272
|
+
* It inherits both counts' definition of *used*, so it spans gaps (a value in `E5` and nothing
|
|
273
|
+
* else still gives `A1:E5`) and includes a line carrying only its own formatting — a set column
|
|
274
|
+
* width, an outline level, a merge reaching past the last value. `undefined` therefore means
|
|
275
|
+
* strictly "no rectangle": an empty sheet, or one carrying only row formatting and no columns at
|
|
276
|
+
* all (or the reverse), where an axis has no extent to bound the other against.
|
|
277
|
+
*
|
|
278
|
+
* Not the same thing as the `<dimension>` a written package records. That is the *tight* box —
|
|
279
|
+
* top-left at the first used cell, formatting-only rows excluded — because Excel writes it to
|
|
280
|
+
* describe where the data is, not what the grid spans. This handle is anchored at `A1`, because
|
|
281
|
+
* a caller asking for the used range means the block to read, style or filter.
|
|
282
|
+
*/
|
|
283
|
+
get usedRange(): Range | undefined;
|
|
262
284
|
/** The columns carrying format properties, as handles, in ascending index order. */
|
|
263
285
|
columns(): IterableIterator<Column>;
|
|
264
286
|
/**
|
package/dist/core/worksheet.js
CHANGED
|
@@ -260,6 +260,34 @@ export class Worksheet {
|
|
|
260
260
|
}
|
|
261
261
|
return last;
|
|
262
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The sheet's used range as one handle — `A1` through the last row and column that carry
|
|
265
|
+
* anything — or `undefined` when there is no rectangle to name.
|
|
266
|
+
*
|
|
267
|
+
* This is {@link rowCount} and {@link columnCount} said once, so a caller stops reassembling
|
|
268
|
+
* `A1:${numberToColumn(sheet.columnCount)}${sheet.rowCount}` by hand. That is what an
|
|
269
|
+
* {@link autoFilter} covering the whole sheet wants — `sheet.autoFilter = sheet.usedRange.address`
|
|
270
|
+
* — and Excel writes exactly that ref for a filter it applies itself. A header-only ref filters
|
|
271
|
+
* nothing, which is the bug this exists to make hard to write.
|
|
272
|
+
*
|
|
273
|
+
* It inherits both counts' definition of *used*, so it spans gaps (a value in `E5` and nothing
|
|
274
|
+
* else still gives `A1:E5`) and includes a line carrying only its own formatting — a set column
|
|
275
|
+
* width, an outline level, a merge reaching past the last value. `undefined` therefore means
|
|
276
|
+
* strictly "no rectangle": an empty sheet, or one carrying only row formatting and no columns at
|
|
277
|
+
* all (or the reverse), where an axis has no extent to bound the other against.
|
|
278
|
+
*
|
|
279
|
+
* Not the same thing as the `<dimension>` a written package records. That is the *tight* box —
|
|
280
|
+
* top-left at the first used cell, formatting-only rows excluded — because Excel writes it to
|
|
281
|
+
* describe where the data is, not what the grid spans. This handle is anchored at `A1`, because
|
|
282
|
+
* a caller asking for the used range means the block to read, style or filter.
|
|
283
|
+
*/
|
|
284
|
+
get usedRange() {
|
|
285
|
+
const bottom = this.rowCount;
|
|
286
|
+
const right = this.columnCount;
|
|
287
|
+
if (bottom === 0 || right === 0)
|
|
288
|
+
return undefined;
|
|
289
|
+
return new Range(this, 1, 1, bottom, right);
|
|
290
|
+
}
|
|
263
291
|
/** The columns carrying format properties, as handles, in ascending index order. */
|
|
264
292
|
*columns() {
|
|
265
293
|
for (const index of [...this.#columns.keys()].sort((a, b) => a - b)) {
|
package/dist/entries/core.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type { Comment, CommentThread, Mention, Person } from '../core/comment-th
|
|
|
7
7
|
export type { CfValueObject, ConditionalFormatting, ConditionalFormattingRule, } from '../core/conditional-formatting.ts';
|
|
8
8
|
export type { DataValidation, DataValidationEntry, DataValidationErrorStyle, DataValidationOperator, DataValidationType, } from '../core/data-validation.ts';
|
|
9
9
|
export { type AnchoredImage, type AnchorPoint, type Extent, type ImageAnchor, type ImageEditAs, isOneCellAnchor, type OneCellAnchor, PX_TO_EMU, type TwoCellAnchor, type WorkbookImage, } from '../core/image.ts';
|
|
10
|
+
export { MAX_COLUMN_WIDTH, MAX_ROW_HEIGHT } from '../core/limits.ts';
|
|
10
11
|
export type { HeaderFooter, PageBreak, PageMargins, PageSetup, PrintOptions, } from '../core/page-setup.ts';
|
|
11
12
|
export { type ParsedPivotField, type ParsedPivotSource, type ParsedPivotTable, type PivotCacheField, type PivotItem, type PivotMetric, type PivotNumericSummary, type PivotRecordCell, type PivotSourceKind, PivotTable, type PivotTableOptions, } from '../core/pivot-table.ts';
|
|
12
13
|
export type { PreservedPart, PreservedRelationship, PreservedRootReference, PreservedWorksheetReference, } from '../core/preserved.ts';
|
|
@@ -16,8 +17,9 @@ export { Row } from '../core/row.ts';
|
|
|
16
17
|
export type { Alignment, Border, BorderEdge, BorderStyle, CellStyle, Color, DifferentialStyle, Fill, FillPatternType, Font, FontScheme, FontVerticalAlignment, GradientFill, GradientStop, HorizontalAlignment, NamedCellStyle, PatternFill, Protection, TableStyleNamespace, TableStyleTable, UnderlineStyle, VerticalAlignment, } from '../core/style.ts';
|
|
17
18
|
export { Table, type TableColumn, type TableColumnStyle, type TableOptions, type TableRegion, type TableStyleInfo, } from '../core/table.ts';
|
|
18
19
|
export { isTableStyleElementType, STRIPE_ELEMENT_TYPES, TABLE_STYLE_ELEMENT_TYPES, type TableStyle, type TableStyleElement, type TableStyleElementType, } from '../core/table-style.ts';
|
|
20
|
+
export { estimateWrappedLines } from '../core/text-metrics.ts';
|
|
19
21
|
export { DEFAULT_THEME_COLOR_SCHEME, DEFAULT_THEME_FONTS, parseThemeColorScheme, THEME_COLOR_SLOTS, type ThemeColorScheme, type ThemeColorSlot, type ThemeFontScheme, type ThemeOverrides, } from '../core/theme.ts';
|
|
20
|
-
export { type CellValue, coerceCellValue, type DataTableFormulaValue, detectValueType, ERROR_CODES, type ErrorCode, type ErrorValue, type FormulaResult, type FormulaValue, type HyperlinkValue, isErrorCode, type RichTextRun, type RichTextValue, richTextToPlain, type SharedFormulaValue, ValueType, } from '../core/value.ts';
|
|
22
|
+
export { type CellValue, cellValueToText, coerceCellValue, type DataTableFormulaValue, detectValueType, ERROR_CODES, type ErrorCode, type ErrorValue, type FormulaResult, type FormulaValue, type HyperlinkValue, isDataTableFormulaValue, isErrorCode, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, type RichTextRun, type RichTextValue, richTextToPlain, type SharedFormulaValue, ValueType, } from '../core/value.ts';
|
|
21
23
|
export { type AddImageOptions, type AddWorksheetOptions, DEFAULT_WORKBOOK_VIEW, type DefinedName, type PreservedWorkbookReference, Workbook, type WorkbookProperties, type WorkbookView, } from '../core/workbook.ts';
|
|
22
24
|
export type { WorkbookProtection, WorkbookProtectionCredentialAttr, } from '../core/workbook-protection.ts';
|
|
23
25
|
export { type CellModel, type ColumnProperties, type OutlineProperties, type RowInput, type RowProperties, type SheetView, Worksheet, type WorksheetModel, type WorksheetProperties, type WorksheetState, } from '../core/worksheet.ts';
|
package/dist/entries/core.js
CHANGED
|
@@ -9,12 +9,14 @@ export { Cell } from '../core/cell.js';
|
|
|
9
9
|
export { applyTint, DEFAULT_INDEXED_COLORS, resolveColor, SYSTEM_INDEXED_COLORS, } from '../core/color-resolution.js';
|
|
10
10
|
export { Column } from '../core/column.js';
|
|
11
11
|
export { isOneCellAnchor, PX_TO_EMU, } from '../core/image.js';
|
|
12
|
+
export { MAX_COLUMN_WIDTH, MAX_ROW_HEIGHT } from '../core/limits.js';
|
|
12
13
|
export { PivotTable, } from '../core/pivot-table.js';
|
|
13
14
|
export { Range } from '../core/range.js';
|
|
14
15
|
export { Row } from '../core/row.js';
|
|
15
16
|
export { Table, } from '../core/table.js';
|
|
16
17
|
export { isTableStyleElementType, STRIPE_ELEMENT_TYPES, TABLE_STYLE_ELEMENT_TYPES, } from '../core/table-style.js';
|
|
18
|
+
export { estimateWrappedLines } from '../core/text-metrics.js';
|
|
17
19
|
export { DEFAULT_THEME_COLOR_SCHEME, DEFAULT_THEME_FONTS, parseThemeColorScheme, THEME_COLOR_SLOTS, } from '../core/theme.js';
|
|
18
|
-
export { coerceCellValue, detectValueType, ERROR_CODES, isErrorCode, richTextToPlain, ValueType, } from '../core/value.js';
|
|
20
|
+
export { cellValueToText, coerceCellValue, detectValueType, ERROR_CODES, isDataTableFormulaValue, isErrorCode, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, ValueType, } from '../core/value.js';
|
|
19
21
|
export { DEFAULT_WORKBOOK_VIEW, Workbook, } from '../core/workbook.js';
|
|
20
22
|
export { Worksheet, } from '../core/worksheet.js';
|
package/dist/io/csv/write.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
// text. The lossy direction (styles, formulas-as-formulas, multiple sheets) is inherent to the
|
|
5
5
|
// format, so this writer makes the honest choices explicit: one selected sheet, each row sized to
|
|
6
6
|
// its own populated extent (never clamped to a sibling row's width), a formula rendered as its
|
|
7
|
-
// cached result, a Date rendered by a caller-supplied format or a full ISO-8601 timestamp.
|
|
7
|
+
// cached result, a Date rendered by a caller-supplied format or a full ISO-8601 timestamp. What a
|
|
8
|
+
// value reads as is `cellValueToText`'s answer, not a private one — a CSV field and `cell.text`
|
|
9
|
+
// disagreeing about the same cell would be a bug in one of them.
|
|
8
10
|
//
|
|
9
11
|
// `writeCsvText` yields the logical text; `writeCsv` encodes it to bytes and — for UTF-8, the
|
|
10
12
|
// default — prepends a byte-order mark so a consumer such as Excel detects the encoding and does
|
|
11
13
|
// not mangle non-ASCII on open. The BOM is a byte-level marker, not part of the logical text.
|
|
12
|
-
import {
|
|
14
|
+
import { cellValueToText, isDataTableFormulaValue, isFormulaValue, isSharedFormulaValue, } from '../../core/value.js';
|
|
13
15
|
import { AuthoringError } from '../../errors.js';
|
|
14
16
|
const UTF8_BOM = Uint8Array.of(0xef, 0xbb, 0xbf);
|
|
15
17
|
/** The logical CSV text of one worksheet — no BOM, no byte encoding. */
|
|
@@ -56,33 +58,19 @@ function selectSheet(workbook, name) {
|
|
|
56
58
|
throw new AuthoringError('workbook has no worksheet to write as CSV');
|
|
57
59
|
return first;
|
|
58
60
|
}
|
|
59
|
-
|
|
60
|
-
if (sheet === undefined)
|
|
61
|
-
throw new AuthoringError(`no worksheet named "${name}" to write as CSV`);
|
|
62
|
-
return sheet;
|
|
61
|
+
return workbook.requireWorksheet(name);
|
|
63
62
|
}
|
|
63
|
+
// A field is the value's plain text, with one CSV-only deviation: `dateFormat`/`dateUTC` let a
|
|
64
|
+
// caller render dates in something other than ISO-8601. That reaches inside a formula's cached
|
|
65
|
+
// result too, which is why the recursion is here rather than delegated wholesale.
|
|
64
66
|
function csvFieldText(value, options) {
|
|
65
|
-
if (value
|
|
66
|
-
return '';
|
|
67
|
-
if (typeof value === 'number')
|
|
68
|
-
return String(value);
|
|
69
|
-
if (typeof value === 'boolean')
|
|
70
|
-
return value ? 'TRUE' : 'FALSE';
|
|
71
|
-
if (typeof value === 'string')
|
|
72
|
-
return value;
|
|
73
|
-
if (value instanceof Date)
|
|
67
|
+
if (value instanceof Date && options.dateFormat !== undefined) {
|
|
74
68
|
return formatDate(value, options.dateFormat, options.dateUTC ?? false);
|
|
75
|
-
if (isErrorValue(value))
|
|
76
|
-
return value.error;
|
|
77
|
-
if (isRichTextValue(value))
|
|
78
|
-
return richTextToPlain(value);
|
|
79
|
-
if (isHyperlinkValue(value)) {
|
|
80
|
-
return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
|
|
81
69
|
}
|
|
82
|
-
if (isFormulaValue(value) || isSharedFormulaValue(value)) {
|
|
70
|
+
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
83
71
|
return value.result === undefined ? '' : csvFieldText(value.result, options);
|
|
84
72
|
}
|
|
85
|
-
return
|
|
73
|
+
return cellValueToText(value);
|
|
86
74
|
}
|
|
87
75
|
const DATE_TOKENS = /YYYY|YY|MM|DD|HH|mm|ss|M|D|H|m|s/g;
|
|
88
76
|
function formatDate(date, format, utc) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shbernal/ts-xlsx",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A TypeScript-first library for reading and writing xlsx (OOXML) spreadsheets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "shbernal",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"README.md"
|
|
65
65
|
],
|
|
66
66
|
"scripts": {
|
|
67
|
-
"prepare": "
|
|
67
|
+
"prepare": "node scripts/install-hooks.ts",
|
|
68
68
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
69
69
|
"build": "pnpm run clean && tsc -p tsconfig.build.json",
|
|
70
70
|
"size": "node scripts/size-budget.ts",
|