@shbernal/ts-xlsx 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/autofilter.d.ts +1 -1
- package/dist/core/autofilter.js +3 -5
- package/dist/core/axis-handle.d.ts +8 -0
- package/dist/core/axis-handle.js +14 -0
- package/dist/core/cell.d.ts +24 -0
- package/dist/core/cell.js +9 -1
- package/dist/core/column.d.ts +4 -1
- package/dist/core/column.js +31 -35
- package/dist/core/conditional-formatting.d.ts +5 -5
- package/dist/core/conditional-formatting.js +11 -25
- package/dist/core/data-validation.d.ts +3 -3
- package/dist/core/data-validation.js +7 -15
- package/dist/core/image.d.ts +1 -1
- package/dist/core/image.js +2 -4
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +0 -10
- package/dist/core/merge.js +0 -8
- 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 +4 -8
- package/dist/core/row.d.ts +4 -1
- package/dist/core/row.js +19 -23
- package/dist/core/style.d.ts +51 -7
- package/dist/core/style.js +29 -34
- package/dist/core/table.d.ts +1 -1
- package/dist/core/table.js +3 -5
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/worksheet.d.ts +17 -6
- package/dist/core/worksheet.js +48 -42
- package/dist/customui/ribbon.js +4 -6
- package/dist/io/opc/inflate.js +1 -1
- package/dist/io/opc/part-paths.d.ts +1 -0
- package/dist/io/opc/part-paths.js +15 -0
- package/dist/io/opc/read-opc.d.ts +22 -3
- package/dist/io/opc/read-opc.js +11 -39
- package/dist/io/opc/rels.d.ts +1 -6
- package/dist/io/opc/rels.js +1 -4
- package/dist/io/style/xf-style.d.ts +26 -2
- package/dist/io/style/xf-style.js +6 -0
- package/dist/io/xlsb/read-styles.js +6 -3
- package/dist/io/xlsb/read.d.ts +1 -1
- package/dist/io/xlsb/read.js +5 -8
- package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
- package/dist/io/xlsx/cell-accumulator.js +1 -1
- package/dist/io/xlsx/cell-value.js +1 -1
- package/dist/io/xlsx/color-xml.js +1 -1
- package/dist/io/xlsx/comments.js +2 -1
- package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
- package/dist/io/xlsx/conditional-formatting.js +22 -21
- package/dist/io/xlsx/data-validation.d.ts +9 -7
- package/dist/io/xlsx/data-validation.js +10 -9
- package/dist/io/xlsx/edit-vba.js +4 -11
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +20 -17
- package/dist/io/xlsx/images.js +15 -14
- package/dist/io/xlsx/package-plan.d.ts +9 -1
- package/dist/io/xlsx/package-plan.js +29 -16
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-rows.js +12 -13
- package/dist/io/xlsx/read-shared-strings.js +2 -1
- package/dist/io/xlsx/read-styles.d.ts +1 -1
- package/dist/io/xlsx/read-styles.js +63 -42
- package/dist/io/xlsx/read-worksheet.d.ts +10 -1
- package/dist/io/xlsx/read-worksheet.js +43 -56
- package/dist/io/xlsx/read.d.ts +2 -3
- package/dist/io/xlsx/read.js +81 -73
- package/dist/io/xlsx/rich-runs.d.ts +1 -1
- package/dist/io/xlsx/rich-runs.js +2 -1
- package/dist/io/xlsx/sheet-properties.js +21 -28
- package/dist/io/xlsx/styles.js +23 -20
- package/dist/io/xlsx/tables.js +2 -1
- package/dist/io/xlsx/theme-xml.js +1 -1
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.js +29 -28
- package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
- package/dist/io/xlsx/worksheet-xml.js +14 -16
- package/dist/io/xlsx/write-stream.d.ts +6 -0
- package/dist/io/xlsx/write-stream.js +30 -22
- package/dist/io/xlsx/write.d.ts +5 -0
- package/dist/io/xlsx/write.js +155 -131
- package/dist/token-set.d.ts +15 -0
- package/dist/token-set.js +4 -0
- package/dist/vba/cfb-format.d.ts +24 -0
- package/dist/vba/cfb-format.js +12 -0
- package/dist/vba/cfb-writer.js +17 -17
- package/dist/vba/cfb.js +1 -7
- package/dist/vba/ms-ovba.js +32 -11
- package/dist/vba/project.js +13 -13
- package/dist/vba/vba-encoding.js +1 -1
- package/dist/xml/xml-read.d.ts +59 -74
- package/dist/xml/xml-read.js +83 -176
- package/dist/xml/xml-scan.d.ts +100 -0
- package/dist/xml/xml-scan.js +208 -0
- package/dist/xml/xml.d.ts +1 -1
- package/package.json +2 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { cellCarriesContent } from './cell.js';
|
|
2
|
+
function rowIsUsed(cols) {
|
|
3
|
+
for (const cell of cols.values()) {
|
|
4
|
+
if (cellCarriesContent(cell))
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
export class UsedExtent {
|
|
10
|
+
#storage;
|
|
11
|
+
#topRowKey = 0;
|
|
12
|
+
#topColumnKey = 0;
|
|
13
|
+
#topDeclaredRow = 0;
|
|
14
|
+
#topDeclaredColumn = 0;
|
|
15
|
+
#stale = false;
|
|
16
|
+
constructor(storage) {
|
|
17
|
+
this.#storage = storage;
|
|
18
|
+
}
|
|
19
|
+
noteCell(row, col) {
|
|
20
|
+
if (row > this.#topRowKey)
|
|
21
|
+
this.#topRowKey = row;
|
|
22
|
+
if (col > this.#topColumnKey)
|
|
23
|
+
this.#topColumnKey = col;
|
|
24
|
+
}
|
|
25
|
+
noteDeclaredRow(row) {
|
|
26
|
+
if (row > this.#topDeclaredRow)
|
|
27
|
+
this.#topDeclaredRow = row;
|
|
28
|
+
}
|
|
29
|
+
noteDeclaredColumn(col) {
|
|
30
|
+
if (col > this.#topDeclaredColumn)
|
|
31
|
+
this.#topDeclaredColumn = col;
|
|
32
|
+
}
|
|
33
|
+
noteMerge(rect) {
|
|
34
|
+
this.noteDeclaredRow(rect.bottom);
|
|
35
|
+
this.noteDeclaredColumn(rect.right);
|
|
36
|
+
}
|
|
37
|
+
invalidate() {
|
|
38
|
+
this.#stale = true;
|
|
39
|
+
}
|
|
40
|
+
reset() {
|
|
41
|
+
this.#topRowKey = 0;
|
|
42
|
+
this.#topColumnKey = 0;
|
|
43
|
+
this.#topDeclaredRow = 0;
|
|
44
|
+
this.#topDeclaredColumn = 0;
|
|
45
|
+
this.#stale = false;
|
|
46
|
+
}
|
|
47
|
+
get lastRow() {
|
|
48
|
+
this.#refresh();
|
|
49
|
+
if (this.#topRowKey <= this.#topDeclaredRow)
|
|
50
|
+
return this.#topDeclaredRow;
|
|
51
|
+
const top = this.#storage.rows.get(this.#topRowKey);
|
|
52
|
+
if (top !== undefined && rowIsUsed(top))
|
|
53
|
+
return this.#topRowKey;
|
|
54
|
+
let last = this.#topDeclaredRow;
|
|
55
|
+
for (const [number, cols] of this.#storage.rows) {
|
|
56
|
+
if (number > last && rowIsUsed(cols))
|
|
57
|
+
last = number;
|
|
58
|
+
}
|
|
59
|
+
return last;
|
|
60
|
+
}
|
|
61
|
+
get lastColumn() {
|
|
62
|
+
this.#refresh();
|
|
63
|
+
if (this.#topColumnKey <= this.#topDeclaredColumn)
|
|
64
|
+
return this.#topDeclaredColumn;
|
|
65
|
+
for (const cols of this.#storage.rows.values()) {
|
|
66
|
+
const cell = cols.get(this.#topColumnKey);
|
|
67
|
+
if (cell !== undefined && cellCarriesContent(cell))
|
|
68
|
+
return this.#topColumnKey;
|
|
69
|
+
}
|
|
70
|
+
let last = this.#topDeclaredColumn;
|
|
71
|
+
for (const cols of this.#storage.rows.values()) {
|
|
72
|
+
for (const [col, cell] of cols) {
|
|
73
|
+
if (col > last && cellCarriesContent(cell))
|
|
74
|
+
last = col;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return last;
|
|
78
|
+
}
|
|
79
|
+
#refresh() {
|
|
80
|
+
if (!this.#stale)
|
|
81
|
+
return;
|
|
82
|
+
this.#stale = false;
|
|
83
|
+
this.#topRowKey = 0;
|
|
84
|
+
this.#topColumnKey = 0;
|
|
85
|
+
this.#topDeclaredRow = 0;
|
|
86
|
+
this.#topDeclaredColumn = 0;
|
|
87
|
+
for (const [number, cols] of this.#storage.rows) {
|
|
88
|
+
if (number > this.#topRowKey)
|
|
89
|
+
this.#topRowKey = number;
|
|
90
|
+
for (const col of cols.keys()) {
|
|
91
|
+
if (col > this.#topColumnKey)
|
|
92
|
+
this.#topColumnKey = col;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const number of this.#storage.rowProperties.keys())
|
|
96
|
+
this.noteDeclaredRow(number);
|
|
97
|
+
for (const index of this.#storage.columns.keys())
|
|
98
|
+
this.noteDeclaredColumn(index);
|
|
99
|
+
for (const rect of this.#storage.mergeRects)
|
|
100
|
+
this.noteMerge(rect);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/core/worksheet.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { CellValue } from './value.ts';
|
|
|
24
24
|
*/
|
|
25
25
|
export type Visibility = 'visible' | 'hidden' | 'veryHidden';
|
|
26
26
|
/** Narrow a raw `<sheet state>` or `<workbookView visibility>` token to a known {@link Visibility}. */
|
|
27
|
-
export declare
|
|
27
|
+
export declare const isVisibility: (value: string) => value is Visibility;
|
|
28
28
|
export interface WorksheetState {
|
|
29
29
|
/** Sheet visibility, as Excel models it. Defaults to `visible`. */
|
|
30
30
|
readonly state: Visibility;
|
|
@@ -276,14 +276,21 @@ export declare class Worksheet {
|
|
|
276
276
|
* or 0 for an empty sheet. Spans gaps: a value in row 5 makes this 5 even if rows 2–4
|
|
277
277
|
* are empty. This is the used-range extent, not a populated-row tally (see
|
|
278
278
|
* {@link actualRowCount}).
|
|
279
|
+
*
|
|
280
|
+
* *Carrying anything* is deliberately wide: a value, a cell's own style facet, quote-prefix flag
|
|
281
|
+
* or named-style link, a note, a row height or outline level, or a merge reaching down. Styling a
|
|
282
|
+
* band of empty rows is how a template is laid out, so those rows are used and {@link addRow}
|
|
283
|
+
* appends past them. A cell merely materialised by {@link getCell} and left untouched carries
|
|
284
|
+
* nothing, so reading a far address never grows the sheet.
|
|
279
285
|
*/
|
|
280
286
|
get rowCount(): number;
|
|
281
287
|
/** The number of rows that hold at least one non-empty cell, ignoring gaps and formatting-only rows. */
|
|
282
288
|
get actualRowCount(): number;
|
|
283
289
|
/**
|
|
284
|
-
* The 1-based index of the last column carrying anything (a
|
|
290
|
+
* The 1-based index of the last column carrying anything (a used cell or the column's own format
|
|
285
291
|
* properties), or 0 for an empty sheet. The used-range width, mirroring {@link rowCount} for the
|
|
286
|
-
* other axis: a value in column E makes this 5 even if
|
|
292
|
+
* other axis, down to what *carrying anything* means: a value in column E makes this 5 even if
|
|
293
|
+
* columns B–D are empty, and a column holding nothing but a styled empty cell is still used.
|
|
287
294
|
*/
|
|
288
295
|
get columnCount(): number;
|
|
289
296
|
/**
|
|
@@ -548,9 +555,13 @@ export declare class Worksheet {
|
|
|
548
555
|
/**
|
|
549
556
|
* Copy the row at the 1-based `start`, `options.count` times (default 1). With `options.insert`
|
|
550
557
|
* (the default) the copies are inserted directly after the source, shifting the rows below, and
|
|
551
|
-
* any merged range there, down by `count`; otherwise the copies
|
|
552
|
-
* below without shifting.
|
|
553
|
-
*
|
|
558
|
+
* any merged range there, down by `count`; otherwise the copies *replace* the rows immediately
|
|
559
|
+
* below without shifting. A destination row is not overlaid but wholly re-made, so a cell it held
|
|
560
|
+
* in a column the source leaves empty is dropped, exactly as it would be with a shifting insert.
|
|
561
|
+
*
|
|
562
|
+
* Each copy is a faithful duplicate of the source: its cell values, its per-cell styles, and its
|
|
563
|
+
* row properties (height, hidden, outline level, row fill). It carries no merge of its own, so a
|
|
564
|
+
* range can be merged onto a duplicated row afterwards.
|
|
554
565
|
*
|
|
555
566
|
* @throws {RangeError} if `start` is not a positive integer or `count` is negative.
|
|
556
567
|
*/
|
package/dist/core/worksheet.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthoringError } from '../errors.js';
|
|
2
|
-
import {
|
|
2
|
+
import { tokenSet } from '../token-set.js';
|
|
3
|
+
import { decodeCellRef, decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
|
|
3
4
|
import { canonicalizeAutoFilter } from './autofilter.js';
|
|
4
5
|
import { applyCellStyle, Cell, copyCellContent } from './cell.js';
|
|
5
6
|
import { Column } from './column.js';
|
|
@@ -7,20 +8,19 @@ import { ConditionalFormattingOverlay } from './conditional-formatting-overlay.j
|
|
|
7
8
|
import { DataValidationOverlay } from './data-validation-overlay.js';
|
|
8
9
|
import { GridEdits } from './grid-edits.js';
|
|
9
10
|
import { INTERNAL } from './internal.js';
|
|
10
|
-
import {
|
|
11
|
+
import { MergeIndex } from './merge-index.js';
|
|
12
|
+
import { clearCoveredValues } from './merge.js';
|
|
11
13
|
import { PivotTable } from './pivot-table.js';
|
|
12
14
|
import { deriveCredential, } from './protection.js';
|
|
13
15
|
import { Range, rangeFrom } from './range.js';
|
|
14
16
|
import { buildRowCells, positionalPlacements, rowPlacements } from './row-input.js';
|
|
15
17
|
import { Row } from './row.js';
|
|
16
18
|
import { Table } from './table.js';
|
|
19
|
+
import { UsedExtent } from './used-extent.js';
|
|
17
20
|
import { WorksheetComments } from './worksheet-comments.js';
|
|
18
21
|
import { WORKSHEET_MODEL_FACETS } from './worksheet-model.js';
|
|
19
22
|
import { WorksheetPictures } from './worksheet-pictures.js';
|
|
20
|
-
const
|
|
21
|
-
export function isVisibility(value) {
|
|
22
|
-
return Object.hasOwn(VISIBILITIES, value);
|
|
23
|
-
}
|
|
23
|
+
export const isVisibility = tokenSet({ visible: true, hidden: true, veryHidden: true });
|
|
24
24
|
export class Worksheet {
|
|
25
25
|
name;
|
|
26
26
|
id;
|
|
@@ -54,10 +54,19 @@ export class Worksheet {
|
|
|
54
54
|
#protection;
|
|
55
55
|
#autoFilter;
|
|
56
56
|
#edits;
|
|
57
|
+
#extent;
|
|
58
|
+
#mergeIndex;
|
|
57
59
|
constructor(name, id, state = 'visible') {
|
|
58
60
|
this.name = name;
|
|
59
61
|
this.id = id;
|
|
60
62
|
this.state = state;
|
|
63
|
+
this.#mergeIndex = new MergeIndex(this.#mergeRects);
|
|
64
|
+
this.#extent = new UsedExtent({
|
|
65
|
+
rows: this.#rows,
|
|
66
|
+
rowProperties: this.#rowProperties,
|
|
67
|
+
columns: this.#columns,
|
|
68
|
+
mergeRects: this.#mergeRects,
|
|
69
|
+
});
|
|
61
70
|
this.#edits = new GridEdits({
|
|
62
71
|
rows: this.#rows,
|
|
63
72
|
rowProperties: this.#rowProperties,
|
|
@@ -79,7 +88,7 @@ export class Worksheet {
|
|
|
79
88
|
}
|
|
80
89
|
getCell(reference) {
|
|
81
90
|
const { col, row } = decodeCellRef(reference);
|
|
82
|
-
const master = masterOf(
|
|
91
|
+
const master = this.#mergeIndex.masterOf(row, col);
|
|
83
92
|
return this.#cellAt(master.row, master.col);
|
|
84
93
|
}
|
|
85
94
|
hasCell(row, col) {
|
|
@@ -97,30 +106,17 @@ export class Worksheet {
|
|
|
97
106
|
return new Range(this, referenceOrTop, left ?? 0, bottom ?? 0, right ?? 0);
|
|
98
107
|
}
|
|
99
108
|
get rowCount() {
|
|
100
|
-
|
|
101
|
-
for (const [number, cols] of this.#rows) {
|
|
102
|
-
if (number > last && this.#rowHasContent(cols))
|
|
103
|
-
last = number;
|
|
104
|
-
}
|
|
105
|
-
for (const number of this.#rowProperties.keys()) {
|
|
106
|
-
if (number > last)
|
|
107
|
-
last = number;
|
|
108
|
-
}
|
|
109
|
-
for (const rect of this.#mergeRects) {
|
|
110
|
-
if (rect.bottom > last)
|
|
111
|
-
last = rect.bottom;
|
|
112
|
-
}
|
|
113
|
-
return last;
|
|
109
|
+
return this.#extent.lastRow;
|
|
114
110
|
}
|
|
115
111
|
get actualRowCount() {
|
|
116
112
|
let count = 0;
|
|
117
113
|
for (const cols of this.#rows.values()) {
|
|
118
|
-
if (this.#
|
|
114
|
+
if (this.#rowIsPopulated(cols))
|
|
119
115
|
count++;
|
|
120
116
|
}
|
|
121
117
|
return count;
|
|
122
118
|
}
|
|
123
|
-
#
|
|
119
|
+
#rowIsPopulated(cols) {
|
|
124
120
|
for (const cell of cols.values()) {
|
|
125
121
|
if (cell.value !== null)
|
|
126
122
|
return true;
|
|
@@ -128,22 +124,7 @@ export class Worksheet {
|
|
|
128
124
|
return false;
|
|
129
125
|
}
|
|
130
126
|
get columnCount() {
|
|
131
|
-
|
|
132
|
-
for (const cols of this.#rows.values()) {
|
|
133
|
-
for (const [col, cell] of cols) {
|
|
134
|
-
if (cell.value !== null && col > last)
|
|
135
|
-
last = col;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
for (const index of this.#columns.keys()) {
|
|
139
|
-
if (index > last)
|
|
140
|
-
last = index;
|
|
141
|
-
}
|
|
142
|
-
for (const rect of this.#mergeRects) {
|
|
143
|
-
if (rect.right > last)
|
|
144
|
-
last = rect.right;
|
|
145
|
-
}
|
|
146
|
-
return last;
|
|
127
|
+
return this.#extent.lastColumn;
|
|
147
128
|
}
|
|
148
129
|
get usedRange() {
|
|
149
130
|
const bottom = this.rowCount;
|
|
@@ -231,11 +212,12 @@ export class Worksheet {
|
|
|
231
212
|
const { top, left, bottom, right } = decodeRange(range);
|
|
232
213
|
if (top !== undefined && left !== undefined && bottom !== undefined && right !== undefined) {
|
|
233
214
|
const rect = { top, left, bottom, right };
|
|
234
|
-
|
|
235
|
-
if (clash) {
|
|
215
|
+
if (this.#mergeIndex.overlapping(rect) !== undefined) {
|
|
236
216
|
throw new AuthoringError(`merged range "${range}" overlaps an existing merged region`);
|
|
237
217
|
}
|
|
238
218
|
this.#mergeRects.push(rect);
|
|
219
|
+
this.#mergeIndex.note(rect);
|
|
220
|
+
this.#extent.noteMerge(rect);
|
|
239
221
|
clearCoveredValues(this.#rows, rect);
|
|
240
222
|
}
|
|
241
223
|
this.#merges.push(range);
|
|
@@ -257,8 +239,11 @@ export class Worksheet {
|
|
|
257
239
|
const { top, left, bottom, right } = decodeRange(range);
|
|
258
240
|
if (top !== undefined && left !== undefined && bottom !== undefined && right !== undefined) {
|
|
259
241
|
const rectIndex = this.#mergeRects.findIndex((r) => r.top === top && r.left === left && r.bottom === bottom && r.right === right);
|
|
260
|
-
if (rectIndex !== -1)
|
|
242
|
+
if (rectIndex !== -1) {
|
|
261
243
|
this.#mergeRects.splice(rectIndex, 1);
|
|
244
|
+
this.#mergeIndex.invalidate();
|
|
245
|
+
this.#extent.invalidate();
|
|
246
|
+
}
|
|
262
247
|
}
|
|
263
248
|
return true;
|
|
264
249
|
}
|
|
@@ -282,6 +267,8 @@ export class Worksheet {
|
|
|
282
267
|
assertStartAndCount('splice', 'row', start, count);
|
|
283
268
|
const inserted = inserts.map((values, i) => buildRowCells(start + i, values, this.#columns));
|
|
284
269
|
this.#edits.spliceRows(start, count, inserted);
|
|
270
|
+
this.#mergeIndex.invalidate();
|
|
271
|
+
this.#extent.invalidate();
|
|
285
272
|
}
|
|
286
273
|
insertRow(pos, values) {
|
|
287
274
|
this.spliceRows(pos, 0, values);
|
|
@@ -323,6 +310,7 @@ export class Worksheet {
|
|
|
323
310
|
const { count = 1, insert = true } = options;
|
|
324
311
|
assertStartAndCount('duplicate', 'row', start, count);
|
|
325
312
|
const source = this.#rows.get(start);
|
|
313
|
+
const sourceProperties = this.#rowProperties.get(start);
|
|
326
314
|
const snapshot = (destRow) => {
|
|
327
315
|
const row = new Map();
|
|
328
316
|
if (source) {
|
|
@@ -334,18 +322,30 @@ export class Worksheet {
|
|
|
334
322
|
}
|
|
335
323
|
return row;
|
|
336
324
|
};
|
|
325
|
+
const copyProperties = (destRow) => {
|
|
326
|
+
if (sourceProperties === undefined)
|
|
327
|
+
this.#rowProperties.delete(destRow);
|
|
328
|
+
else
|
|
329
|
+
this.#rowProperties.set(destRow, { ...sourceProperties });
|
|
330
|
+
};
|
|
337
331
|
if (insert) {
|
|
338
332
|
const copies = Array.from({ length: count }, () => snapshot(start));
|
|
339
333
|
this.#edits.spliceRows(start + 1, 0, copies);
|
|
334
|
+
this.#mergeIndex.invalidate();
|
|
340
335
|
}
|
|
341
336
|
else {
|
|
342
337
|
for (let i = 1; i <= count; i++)
|
|
343
338
|
this.#rows.set(start + i, snapshot(start + i));
|
|
344
339
|
}
|
|
340
|
+
for (let i = 1; i <= count; i++)
|
|
341
|
+
copyProperties(start + i);
|
|
342
|
+
this.#extent.invalidate();
|
|
345
343
|
}
|
|
346
344
|
spliceColumns(start, count, ...inserts) {
|
|
347
345
|
assertStartAndCount('splice', 'column', start, count);
|
|
348
346
|
this.#edits.spliceColumns(start, count, inserts);
|
|
347
|
+
this.#mergeIndex.invalidate();
|
|
348
|
+
this.#extent.invalidate();
|
|
349
349
|
}
|
|
350
350
|
insertColumn(pos, values) {
|
|
351
351
|
this.spliceColumns(pos, 0, values);
|
|
@@ -379,6 +379,8 @@ export class Worksheet {
|
|
|
379
379
|
this.#dataValidations.clear();
|
|
380
380
|
this.#conditionalFormattings.clear();
|
|
381
381
|
this.#tables.length = 0;
|
|
382
|
+
this.#mergeIndex.invalidate();
|
|
383
|
+
this.#extent.reset();
|
|
382
384
|
}
|
|
383
385
|
set model(model) {
|
|
384
386
|
this.#resetContent();
|
|
@@ -412,12 +414,14 @@ export class Worksheet {
|
|
|
412
414
|
cell = new Cell(row, col);
|
|
413
415
|
cols.set(col, cell);
|
|
414
416
|
}
|
|
417
|
+
this.#extent.noteCell(row, col);
|
|
415
418
|
return cell;
|
|
416
419
|
}
|
|
417
420
|
[INTERNAL] = {
|
|
418
421
|
evictRow: (number) => {
|
|
419
422
|
this.#rows.delete(number);
|
|
420
423
|
this.#rowProperties.delete(number);
|
|
424
|
+
this.#extent.invalidate();
|
|
421
425
|
},
|
|
422
426
|
addLoadedPivotTable: (pivot) => {
|
|
423
427
|
this.#loadedPivotTables.push(pivot);
|
|
@@ -439,6 +443,7 @@ export class Worksheet {
|
|
|
439
443
|
properties = {};
|
|
440
444
|
this.#rowProperties.set(number, properties);
|
|
441
445
|
}
|
|
446
|
+
this.#extent.noteDeclaredRow(number);
|
|
442
447
|
return properties;
|
|
443
448
|
},
|
|
444
449
|
rowCells: (number) => {
|
|
@@ -452,6 +457,7 @@ export class Worksheet {
|
|
|
452
457
|
properties = {};
|
|
453
458
|
this.#columns.set(index, properties);
|
|
454
459
|
}
|
|
460
|
+
this.#extent.noteDeclaredColumn(index);
|
|
455
461
|
return properties;
|
|
456
462
|
},
|
|
457
463
|
columnCells: (index) => {
|
package/dist/customui/ribbon.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { strFromU8 } from 'fflate';
|
|
2
|
-
import {
|
|
2
|
+
import { tokenSet } from '../token-set.js';
|
|
3
|
+
import { boolStrict, localName, xmlEvents } from '../xml/xml-scan.js';
|
|
3
4
|
import { CustomUiParseError } from './errors.js';
|
|
4
5
|
export const CUSTOMUI_2006_NAMESPACE = 'http://schemas.microsoft.com/office/2006/01/customui';
|
|
5
6
|
export const CUSTOMUI_2009_NAMESPACE = 'http://schemas.microsoft.com/office/2009/07/customui';
|
|
@@ -9,7 +10,7 @@ export function isCustomUiRelType(type) {
|
|
|
9
10
|
return type.endsWith('/ui/extensibility');
|
|
10
11
|
}
|
|
11
12
|
const MAX_DEPTH = 256;
|
|
12
|
-
const
|
|
13
|
+
const isKnownControlKind = tokenSet({
|
|
13
14
|
button: true,
|
|
14
15
|
toggleButton: true,
|
|
15
16
|
checkBox: true,
|
|
@@ -28,10 +29,7 @@ const KNOWN_KINDS = {
|
|
|
28
29
|
dialogBoxLauncher: true,
|
|
29
30
|
control: true,
|
|
30
31
|
item: true,
|
|
31
|
-
};
|
|
32
|
-
function isKnownControlKind(local) {
|
|
33
|
-
return Object.hasOwn(KNOWN_KINDS, local);
|
|
34
|
-
}
|
|
32
|
+
});
|
|
35
33
|
export function parseCustomUi(input) {
|
|
36
34
|
const xml = typeof input === 'string' ? input : strFromU8(input);
|
|
37
35
|
let root;
|
package/dist/io/opc/inflate.js
CHANGED
|
@@ -3,7 +3,7 @@ import { concat } from '../../bytes.js';
|
|
|
3
3
|
import { PackageReadError } from './errors.js';
|
|
4
4
|
const INPUT_SLICE = 1 << 14;
|
|
5
5
|
export function inflatePackage(data, cap) {
|
|
6
|
-
const files =
|
|
6
|
+
const files = Object.create(null);
|
|
7
7
|
let total = 0;
|
|
8
8
|
let failure;
|
|
9
9
|
const unzip = new Unzip((file) => {
|
|
@@ -2,3 +2,4 @@ export declare const THEME_PART_PATH = "xl/theme/theme1.xml";
|
|
|
2
2
|
export declare function extensionOf(partPath: string): string;
|
|
3
3
|
export declare function relsPathFor(partPath: string): string;
|
|
4
4
|
export declare function relativePartPath(fromPath: string, toPath: string): string;
|
|
5
|
+
export declare function resolveRelativePart(basePart: string, target: string): string;
|
|
@@ -22,3 +22,18 @@ export function relativePartPath(fromPath, toPath) {
|
|
|
22
22
|
const up = fromDir.length - common;
|
|
23
23
|
return [...Array(up).fill('..'), ...toSegments.slice(common)].join('/');
|
|
24
24
|
}
|
|
25
|
+
export function resolveRelativePart(basePart, target) {
|
|
26
|
+
if (target.startsWith('/'))
|
|
27
|
+
return target.slice(1);
|
|
28
|
+
const baseDir = basePart.slice(0, basePart.lastIndexOf('/') + 1);
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const segment of `${baseDir}${target}`.split('/')) {
|
|
31
|
+
if (segment === '' || segment === '.')
|
|
32
|
+
continue;
|
|
33
|
+
if (segment === '..')
|
|
34
|
+
out.pop();
|
|
35
|
+
else
|
|
36
|
+
out.push(segment);
|
|
37
|
+
}
|
|
38
|
+
return out.join('/');
|
|
39
|
+
}
|
|
@@ -6,12 +6,31 @@ export interface PackageAccessors {
|
|
|
6
6
|
/** A part's raw bytes, or undefined when the package holds no such part. */
|
|
7
7
|
partBytes: (path: string) => Uint8Array | undefined;
|
|
8
8
|
}
|
|
9
|
+
/** A spreadsheet package opened for reading: inflated under the read bound, its parts bound to
|
|
10
|
+
* accessors, and its XML office document read if it has one. */
|
|
11
|
+
export interface OpenedSpreadsheet {
|
|
12
|
+
/** The inflated parts, for a reader that hands the whole package on to another codec. */
|
|
13
|
+
readonly files: Record<string, Uint8Array>;
|
|
14
|
+
readonly pkg: PackageAccessors;
|
|
15
|
+
/** `xl/workbook.xml`, or undefined when the package carries no XML office document (a `.xlsb`
|
|
16
|
+
* carries `xl/workbook.bin` instead). Each entry point answers that case for itself: they
|
|
17
|
+
* genuinely want different answers, and the difference is documented where they diverge. */
|
|
18
|
+
readonly workbookXml: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Open a spreadsheet package: the six-step preamble every reader shares, and in particular the two
|
|
22
|
+
* decisions worth having exactly one of. The inflate bound is a security decision (an unbounded
|
|
23
|
+
* inflate is a zip bomb) and "does this package carry an XML office document" is the dispatch
|
|
24
|
+
* decision between the two codecs, so neither should be restated once per entry point.
|
|
25
|
+
*
|
|
26
|
+
* It deliberately stops short of the shared strings and the style table: those are codec-specific
|
|
27
|
+
* (`xl/sharedStrings.xml` against BIFF12's own record stream), and lifting them here would put
|
|
28
|
+
* SpreadsheetML knowledge into the container layer.
|
|
29
|
+
*/
|
|
30
|
+
export declare function openSpreadsheetPackage(data: Uint8Array, maxUncompressedBytes: number | undefined): OpenedSpreadsheet;
|
|
9
31
|
export declare function packageAccessors(files: Record<string, Uint8Array>): PackageAccessors;
|
|
10
32
|
export declare function relationshipTargetByType(xml: string, suffix: string): string | undefined;
|
|
11
33
|
export declare function relationshipTargetsByType(xml: string, suffix: string): string[];
|
|
12
|
-
export declare function resolveRelativePart(basePart: string, target: string): string;
|
|
13
|
-
export declare function resolveWorkbookPart(target: string): string;
|
|
14
|
-
export declare function parseRelationships(xml: string): Map<string, string>;
|
|
15
34
|
export interface RelationshipRecord {
|
|
16
35
|
readonly id: string;
|
|
17
36
|
readonly type: string;
|
package/dist/io/opc/read-opc.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { strFromU8 } from 'fflate';
|
|
2
2
|
import { openElements } from '../../xml/xml-read.js';
|
|
3
|
-
import { extensionOf, relsPathFor } from './part-paths.js';
|
|
3
|
+
import { extensionOf, relsPathFor, resolveRelativePart } from './part-paths.js';
|
|
4
|
+
import { DEFAULT_MAX_UNCOMPRESSED } from './read-options.js';
|
|
5
|
+
import { inflateSpreadsheetPackage } from './sniff-format.js';
|
|
6
|
+
export function openSpreadsheetPackage(data, maxUncompressedBytes) {
|
|
7
|
+
const files = inflateSpreadsheetPackage(data, maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED);
|
|
8
|
+
const pkg = packageAccessors(files);
|
|
9
|
+
return { files, pkg, workbookXml: pkg.partText('xl/workbook.xml') };
|
|
10
|
+
}
|
|
4
11
|
export function packageAccessors(files) {
|
|
5
12
|
return {
|
|
6
13
|
partText: (path) => {
|
|
@@ -10,48 +17,13 @@ export function packageAccessors(files) {
|
|
|
10
17
|
partBytes: (path) => files[path],
|
|
11
18
|
};
|
|
12
19
|
}
|
|
13
|
-
function matchesType(attrs, suffix) {
|
|
14
|
-
return (attrs.Type !== undefined && attrs.Target !== undefined && attrs.Type.endsWith(`/${suffix}`));
|
|
15
|
-
}
|
|
16
20
|
export function relationshipTargetByType(xml, suffix) {
|
|
17
21
|
return relationshipTargetsByType(xml, suffix)[0];
|
|
18
22
|
}
|
|
19
23
|
export function relationshipTargetsByType(xml, suffix) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
targets.push(attrs.Target);
|
|
24
|
-
}
|
|
25
|
-
return targets;
|
|
26
|
-
}
|
|
27
|
-
export function resolveRelativePart(basePart, target) {
|
|
28
|
-
if (target.startsWith('/'))
|
|
29
|
-
return target.slice(1);
|
|
30
|
-
const baseDir = basePart.slice(0, basePart.lastIndexOf('/') + 1);
|
|
31
|
-
const out = [];
|
|
32
|
-
for (const segment of `${baseDir}${target}`.split('/')) {
|
|
33
|
-
if (segment === '' || segment === '.')
|
|
34
|
-
continue;
|
|
35
|
-
if (segment === '..')
|
|
36
|
-
out.pop();
|
|
37
|
-
else
|
|
38
|
-
out.push(segment);
|
|
39
|
-
}
|
|
40
|
-
return out.join('/');
|
|
41
|
-
}
|
|
42
|
-
export function resolveWorkbookPart(target) {
|
|
43
|
-
if (target.startsWith('/'))
|
|
44
|
-
return target.slice(1);
|
|
45
|
-
return `xl/${target.replace(/^\.\//, '')}`;
|
|
46
|
-
}
|
|
47
|
-
export function parseRelationships(xml) {
|
|
48
|
-
const rels = new Map();
|
|
49
|
-
for (const { attrs } of openElements(xml, 'Relationship')) {
|
|
50
|
-
if (attrs.Id !== undefined && attrs.Target !== undefined) {
|
|
51
|
-
rels.set(attrs.Id, attrs.Target);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return rels;
|
|
24
|
+
return parseRelationshipRecords(xml)
|
|
25
|
+
.filter((record) => record.type.endsWith(`/${suffix}`))
|
|
26
|
+
.map((record) => record.target);
|
|
55
27
|
}
|
|
56
28
|
export function parseRelationshipRecords(xml) {
|
|
57
29
|
const records = [];
|
package/dist/io/opc/rels.d.ts
CHANGED
|
@@ -13,14 +13,9 @@ export declare function relationship(id: string, type: string, target: string, o
|
|
|
13
13
|
external?: boolean;
|
|
14
14
|
}): string;
|
|
15
15
|
export declare function relationshipsPart(relationships: readonly string[]): string;
|
|
16
|
-
export declare function preservedRelsXml(rels: readonly {
|
|
17
|
-
id: string;
|
|
18
|
-
type: string;
|
|
19
|
-
target: string;
|
|
20
|
-
external?: boolean;
|
|
21
|
-
}[]): string;
|
|
22
16
|
export declare function relsPartXml(rels: readonly {
|
|
23
17
|
id: string;
|
|
24
18
|
type: string;
|
|
25
19
|
target: string;
|
|
20
|
+
external?: boolean;
|
|
26
21
|
}[]): string;
|
package/dist/io/opc/rels.js
CHANGED
|
@@ -8,9 +8,6 @@ export function relationship(id, type, target, options) {
|
|
|
8
8
|
export function relationshipsPart(relationships) {
|
|
9
9
|
return `${XML_DECLARATION}<Relationships xmlns="${PKG_RELS_NS}">${relationships.join('')}</Relationships>`;
|
|
10
10
|
}
|
|
11
|
-
export function preservedRelsXml(rels) {
|
|
12
|
-
return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target, rel.external ? { external: true } : {})));
|
|
13
|
-
}
|
|
14
11
|
export function relsPartXml(rels) {
|
|
15
|
-
return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target)));
|
|
12
|
+
return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target, rel.external ? { external: true } : {})));
|
|
16
13
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Cell } from '../../core/cell.ts';
|
|
2
|
-
import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle } from '../../core/style.ts';
|
|
2
|
+
import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle, type TableStyleTable } from '../../core/style.ts';
|
|
3
3
|
/**
|
|
4
4
|
* What an xf resolves to: the {@link CellStyle} facet tuple, plus the two flags an xf carries that
|
|
5
5
|
* are not facets. Absent facets stay undefined, matching the contract that an unset facet is simply
|
|
@@ -33,7 +33,31 @@ export interface StyleTable {
|
|
|
33
33
|
* file declares no font table.
|
|
34
34
|
*/
|
|
35
35
|
readonly defaultFont?: Font;
|
|
36
|
+
/**
|
|
37
|
+
* The stylesheet's preserved sub-tables, captured verbatim in the same scan that read the xfs: the
|
|
38
|
+
* differential styles a conditional format's `dxfId` indexes, a custom indexed palette, the
|
|
39
|
+
* author's recent-colour swatches, and the custom table-style definitions.
|
|
40
|
+
*
|
|
41
|
+
* They ride here rather than being extracted separately because they come out of the same part,
|
|
42
|
+
* and the reader that wanted them four more times over that part is what this replaced. Each is
|
|
43
|
+
* raw source text: re-parsing and re-serialising is exactly what would cost a foreign `<dxf>` its
|
|
44
|
+
* number format on a round trip.
|
|
45
|
+
*/
|
|
46
|
+
readonly preserved: PreservedStyleTables;
|
|
47
|
+
}
|
|
48
|
+
/** The stylesheet content the model does not interpret and re-emits byte for byte. */
|
|
49
|
+
export interface PreservedStyleTables {
|
|
50
|
+
readonly dxfs: readonly string[];
|
|
51
|
+
readonly indexedColors: readonly string[];
|
|
52
|
+
readonly mruColors: readonly string[];
|
|
53
|
+
readonly tableStyles: TableStyleTable;
|
|
36
54
|
}
|
|
55
|
+
/** The preserved sub-tables of a stylesheet that carries none: a package with no `styles.xml`, and
|
|
56
|
+
* the BIFF12 stylesheet, whose binary records hold no verbatim XML there would be anything to keep. */
|
|
57
|
+
export declare const NO_PRESERVED_STYLE_TABLES: PreservedStyleTables;
|
|
58
|
+
/** The xf layer of a stylesheet: what {@link resolveStyleTable} assembles out of the format tables,
|
|
59
|
+
* before the verbatim sub-tables that ride alongside it in a {@link StyleTable}. */
|
|
60
|
+
export type ResolvedXfTables = Omit<StyleTable, 'preserved'>;
|
|
37
61
|
/**
|
|
38
62
|
* The format code a number-format id denotes: the file's own `<numFmt>`/`BrtFmt` declaration if it
|
|
39
63
|
* has one, else the built-in Excel defines for that id. Id 0 is General, the absence of a format,
|
|
@@ -81,4 +105,4 @@ export declare function resolveStyleTable(tables: {
|
|
|
81
105
|
readonly namedXfs: ReadonlyArray<XfStyle>;
|
|
82
106
|
readonly labels: ReadonlyArray<StyleLabel>;
|
|
83
107
|
readonly fonts: ReadonlyArray<Font | undefined>;
|
|
84
|
-
}):
|
|
108
|
+
}): ResolvedXfTables;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { applyCellStyle } from '../../core/cell.js';
|
|
2
2
|
import { NAMED_STYLE_ID } from '../../core/internal.js';
|
|
3
3
|
import { assignStyleFacets, } from '../../core/style.js';
|
|
4
|
+
export const NO_PRESERVED_STYLE_TABLES = {
|
|
5
|
+
dxfs: [],
|
|
6
|
+
indexedColors: [],
|
|
7
|
+
mruColors: [],
|
|
8
|
+
tableStyles: { styles: [] },
|
|
9
|
+
};
|
|
4
10
|
const BUILTIN_NUMFMTS = new Map([
|
|
5
11
|
[1, '0'],
|
|
6
12
|
[2, '0.00'],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
|
|
1
|
+
import { numFmtCodeFor, NO_PRESERVED_STYLE_TABLES, resolveStyleTable, } from '../style/xf-style.js';
|
|
2
2
|
import { RecordReader } from './primitives.js';
|
|
3
3
|
import { readRecords } from './record-stream.js';
|
|
4
4
|
import { BRT } from './record-types.js';
|
|
@@ -22,7 +22,7 @@ const COLLECTION_ENDS = new Set([
|
|
|
22
22
|
]);
|
|
23
23
|
export function parseStyleTable(part) {
|
|
24
24
|
if (part === undefined)
|
|
25
|
-
return { cellXfs: [], namedStyles: [] };
|
|
25
|
+
return { cellXfs: [], namedStyles: [], preserved: NO_PRESERVED_STYLE_TABLES };
|
|
26
26
|
const numFmtCodes = new Map();
|
|
27
27
|
const fonts = [];
|
|
28
28
|
const fills = [];
|
|
@@ -73,7 +73,10 @@ export function parseStyleTable(part) {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
...resolveStyleTable({ directXfs, namedXfs, labels, fonts }),
|
|
78
|
+
preserved: NO_PRESERVED_STYLE_TABLES,
|
|
79
|
+
};
|
|
77
80
|
}
|
|
78
81
|
function readXf(reader, deps, isDirect) {
|
|
79
82
|
const parent = reader.u16();
|