@shbernal/ts-xlsx 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +26 -4
- package/dist/core/address.js +21 -9
- package/dist/core/autofilter.d.ts +11 -0
- package/dist/core/autofilter.js +35 -10
- package/dist/core/color-resolution.d.ts +1 -1
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/conditional-formatting-overlay.d.ts +5 -0
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +45 -5
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +11 -0
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +9 -2
- package/dist/core/data-validation.js +34 -0
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +26 -8
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +2 -0
- package/dist/core/image.js +4 -0
- package/dist/core/merge.d.ts +12 -1
- package/dist/core/merge.js +36 -2
- package/dist/core/page-setup.d.ts +16 -2
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +2 -0
- package/dist/core/pivot-table.js +25 -16
- package/dist/core/protection.d.ts +3 -0
- package/dist/core/protection.js +20 -14
- package/dist/core/row-input.d.ts +7 -0
- package/dist/core/row-input.js +10 -8
- package/dist/core/style.d.ts +27 -0
- package/dist/core/style.js +82 -67
- package/dist/core/table.d.ts +25 -11
- package/dist/core/table.js +67 -31
- package/dist/core/theme.d.ts +3 -1
- package/dist/core/theme.js +4 -0
- package/dist/core/workbook.d.ts +2 -2
- package/dist/core/worksheet-comments.d.ts +8 -0
- package/dist/core/worksheet-comments.js +18 -0
- package/dist/core/worksheet.d.ts +15 -2
- package/dist/core/worksheet.js +28 -48
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +3 -3
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/xlsx.d.ts +0 -1
- package/dist/entries/xlsx.js +0 -1
- package/dist/io/csv/read.js +1 -1
- package/dist/io/csv/write.d.ts +12 -2
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/inflate.js +2 -12
- package/dist/io/opc/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/style/xf-style.d.ts +7 -2
- package/dist/io/style/xf-style.js +6 -1
- package/dist/io/xlsx/cell-accumulator.d.ts +1 -3
- package/dist/io/xlsx/cell-accumulator.js +20 -58
- package/dist/io/xlsx/cell-value.d.ts +1 -1
- package/dist/io/xlsx/cell-value.js +7 -4
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +7 -6
- package/dist/io/xlsx/comments.js +4 -2
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +60 -45
- package/dist/io/xlsx/data-validation.d.ts +1 -1
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/hyperlinks.js +6 -14
- package/dist/io/xlsx/images.js +19 -22
- package/dist/io/xlsx/read-pivot.js +2 -8
- package/dist/io/xlsx/read-shared-strings.js +8 -47
- package/dist/io/xlsx/read-styles.js +2 -1
- package/dist/io/xlsx/read-worksheet.js +14 -16
- package/dist/io/xlsx/read.d.ts +1 -1
- package/dist/io/xlsx/read.js +44 -74
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +13 -9
- package/dist/io/xlsx/styles.d.ts +3 -15
- package/dist/io/xlsx/styles.js +63 -88
- package/dist/io/xlsx/tables.js +13 -13
- package/dist/io/xlsx/theme-xml.js +7 -4
- package/dist/io/xlsx/threaded-comments.js +19 -19
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.js +34 -37
- package/dist/io/xlsx/write-stream.d.ts +8 -1
- package/dist/io/xlsx/write-stream.js +10 -11
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +1 -2
- package/dist/vba/bytes.js +1 -12
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/project-editor.js +5 -34
- package/dist/vba/project.js +2 -18
- package/dist/xml/xml-read.d.ts +60 -0
- package/dist/xml/xml-read.js +47 -0
- package/dist/xml/xml.d.ts +43 -1
- package/dist/xml/xml.js +17 -5
- package/package.json +21 -4
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
const DATA_VALIDATION_TYPES = {
|
|
2
|
+
none: true,
|
|
3
|
+
list: true,
|
|
4
|
+
whole: true,
|
|
5
|
+
decimal: true,
|
|
6
|
+
date: true,
|
|
7
|
+
time: true,
|
|
8
|
+
textLength: true,
|
|
9
|
+
custom: true,
|
|
10
|
+
};
|
|
11
|
+
export function isDataValidationType(value) {
|
|
12
|
+
return Object.hasOwn(DATA_VALIDATION_TYPES, value);
|
|
13
|
+
}
|
|
14
|
+
const DATA_VALIDATION_OPERATORS = {
|
|
15
|
+
between: true,
|
|
16
|
+
notBetween: true,
|
|
17
|
+
equal: true,
|
|
18
|
+
notEqual: true,
|
|
19
|
+
greaterThan: true,
|
|
20
|
+
lessThan: true,
|
|
21
|
+
greaterThanOrEqual: true,
|
|
22
|
+
lessThanOrEqual: true,
|
|
23
|
+
};
|
|
24
|
+
export function isDataValidationOperator(value) {
|
|
25
|
+
return Object.hasOwn(DATA_VALIDATION_OPERATORS, value);
|
|
26
|
+
}
|
|
27
|
+
const DATA_VALIDATION_ERROR_STYLES = {
|
|
28
|
+
stop: true,
|
|
29
|
+
warning: true,
|
|
30
|
+
information: true,
|
|
31
|
+
};
|
|
32
|
+
export function isDataValidationErrorStyle(value) {
|
|
33
|
+
return Object.hasOwn(DATA_VALIDATION_ERROR_STYLES, value);
|
|
34
|
+
}
|
|
1
35
|
export function cloneDataValidation(rule) {
|
|
2
36
|
return {
|
|
3
37
|
...rule,
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import { type AutoFilter } from './autofilter.ts';
|
|
1
2
|
import { Cell } from './cell.ts';
|
|
3
|
+
import type { ConditionalFormattingOverlay } from './conditional-formatting-overlay.ts';
|
|
4
|
+
import type { DataValidationOverlay } from './data-validation-overlay.ts';
|
|
2
5
|
import { type AnchoredImage } from './image.ts';
|
|
3
6
|
import type { MergeRect } from './merge.ts';
|
|
4
7
|
import type { Table } from './table.ts';
|
|
5
8
|
import { type CellValue } from './value.ts';
|
|
9
|
+
import type { WorksheetComments } from './worksheet-comments.ts';
|
|
6
10
|
import type { ColumnProperties, RowProperties } from './worksheet.ts';
|
|
11
|
+
/**
|
|
12
|
+
* The sheet's autofilter, reached as a slot rather than held by reference like the containers beside
|
|
13
|
+
* it: it is a single replaceable value, and a splice that deletes every filtered line clears it.
|
|
14
|
+
*/
|
|
15
|
+
export interface AutoFilterSlot {
|
|
16
|
+
get(): AutoFilter | undefined;
|
|
17
|
+
set(next: AutoFilter | undefined): void;
|
|
18
|
+
}
|
|
7
19
|
interface GridStorage {
|
|
8
20
|
readonly rows: Map<number, Map<number, Cell>>;
|
|
9
21
|
readonly rowProperties: Map<number, RowProperties>;
|
|
@@ -12,6 +24,10 @@ interface GridStorage {
|
|
|
12
24
|
readonly mergeRects: MergeRect[];
|
|
13
25
|
readonly tables: Table[];
|
|
14
26
|
readonly images: AnchoredImage[];
|
|
27
|
+
readonly dataValidations: DataValidationOverlay;
|
|
28
|
+
readonly conditionalFormattings: ConditionalFormattingOverlay;
|
|
29
|
+
readonly comments: WorksheetComments;
|
|
30
|
+
readonly autoFilter: AutoFilterSlot;
|
|
15
31
|
}
|
|
16
32
|
export declare class GridEdits {
|
|
17
33
|
#private;
|
package/dist/core/grid-edits.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
|
|
2
|
+
import { shiftAutoFilter } from './autofilter.js';
|
|
2
3
|
import { Cell, copyCellContent } from './cell.js';
|
|
3
4
|
import { replaceContents } from './containers.js';
|
|
5
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
4
6
|
import { isOneCellAnchor } from './image.js';
|
|
5
7
|
import { isSharedFormulaValue } from './value.js';
|
|
6
|
-
function shiftIndex(v, start, count, delta) {
|
|
7
|
-
return v < start ? v : v >= start + count ? v + delta : start;
|
|
8
|
-
}
|
|
9
8
|
export class GridEdits {
|
|
10
9
|
#rows;
|
|
11
10
|
#rowProperties;
|
|
@@ -14,6 +13,10 @@ export class GridEdits {
|
|
|
14
13
|
#mergeRects;
|
|
15
14
|
#tables;
|
|
16
15
|
#images;
|
|
16
|
+
#dataValidations;
|
|
17
|
+
#conditionalFormattings;
|
|
18
|
+
#comments;
|
|
19
|
+
#autoFilter;
|
|
17
20
|
constructor(storage) {
|
|
18
21
|
this.#rows = storage.rows;
|
|
19
22
|
this.#rowProperties = storage.rowProperties;
|
|
@@ -22,6 +25,10 @@ export class GridEdits {
|
|
|
22
25
|
this.#mergeRects = storage.mergeRects;
|
|
23
26
|
this.#tables = storage.tables;
|
|
24
27
|
this.#images = storage.images;
|
|
28
|
+
this.#dataValidations = storage.dataValidations;
|
|
29
|
+
this.#conditionalFormattings = storage.conditionalFormattings;
|
|
30
|
+
this.#comments = storage.comments;
|
|
31
|
+
this.#autoFilter = storage.autoFilter;
|
|
25
32
|
}
|
|
26
33
|
spliceRows(start, count, inserted) {
|
|
27
34
|
const delta = inserted.length - count;
|
|
@@ -43,6 +50,7 @@ export class GridEdits {
|
|
|
43
50
|
this.#shiftTables('row', start, count, delta);
|
|
44
51
|
this.#shiftImages('row', start, count, delta);
|
|
45
52
|
this.#reanchorSharedFormulas('row', start, count, delta);
|
|
53
|
+
this.#shiftRangeBoundOverlays('row', start, count, delta);
|
|
46
54
|
}
|
|
47
55
|
spliceColumns(start, count, inserts) {
|
|
48
56
|
const delta = inserts.length - count;
|
|
@@ -74,6 +82,16 @@ export class GridEdits {
|
|
|
74
82
|
this.#shiftTables('col', start, count, delta);
|
|
75
83
|
this.#shiftImages('col', start, count, delta);
|
|
76
84
|
this.#reanchorSharedFormulas('col', start, count, delta);
|
|
85
|
+
this.#shiftRangeBoundOverlays('col', start, count, delta);
|
|
86
|
+
}
|
|
87
|
+
#shiftRangeBoundOverlays(axis, start, count, delta) {
|
|
88
|
+
this.#dataValidations.shift(axis, start, count, delta);
|
|
89
|
+
this.#conditionalFormattings.shift(axis, start, count, delta);
|
|
90
|
+
this.#comments.shift(axis, start, count, delta);
|
|
91
|
+
const filter = this.#autoFilter.get();
|
|
92
|
+
if (filter !== undefined) {
|
|
93
|
+
this.#autoFilter.set(shiftAutoFilter(filter, axis, start, count, delta));
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
#relocateRow(cols, destRow) {
|
|
79
97
|
const moved = new Map();
|
|
@@ -99,8 +117,8 @@ export class GridEdits {
|
|
|
99
117
|
if (master === undefined)
|
|
100
118
|
continue;
|
|
101
119
|
const anchored = axis === 'row'
|
|
102
|
-
? encodeAddress(master.col, shiftIndex(master.row, start, count, delta))
|
|
103
|
-
: encodeAddress(shiftIndex(master.col, start, count, delta), master.row);
|
|
120
|
+
? encodeAddress(master.col, shiftIndex(master.row, start, count, delta, 'row'))
|
|
121
|
+
: encodeAddress(shiftIndex(master.col, start, count, delta, 'col'), master.row);
|
|
104
122
|
if (anchored === value.sharedFormula)
|
|
105
123
|
continue;
|
|
106
124
|
const reanchored = { ...value, sharedFormula: anchored };
|
|
@@ -121,7 +139,7 @@ export class GridEdits {
|
|
|
121
139
|
map.set(index, value);
|
|
122
140
|
}
|
|
123
141
|
#shiftMerges(axis, start, count, delta) {
|
|
124
|
-
const shift = (v) => shiftIndex(v, start, count, delta);
|
|
142
|
+
const shift = (v) => shiftIndex(v, start, count, delta, axis);
|
|
125
143
|
const merges = [];
|
|
126
144
|
const rects = [];
|
|
127
145
|
for (const range of this.#merges) {
|
|
@@ -131,7 +149,7 @@ export class GridEdits {
|
|
|
131
149
|
continue;
|
|
132
150
|
}
|
|
133
151
|
const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
|
|
134
|
-
if (lo
|
|
152
|
+
if (isDeletedSpan(lo, hi, start, count))
|
|
135
153
|
continue;
|
|
136
154
|
const rect = axis === 'row'
|
|
137
155
|
? { top: shift(top), left, bottom: shift(bottom), right }
|
|
@@ -151,7 +169,7 @@ export class GridEdits {
|
|
|
151
169
|
#shiftImages(axis, start, count, delta) {
|
|
152
170
|
const shiftPoint = (point) => {
|
|
153
171
|
const zeroBased = axis === 'row' ? point.row : point.col;
|
|
154
|
-
const shifted = shiftIndex(zeroBased + 1, start, count, delta) - 1;
|
|
172
|
+
const shifted = shiftIndex(zeroBased + 1, start, count, delta, axis) - 1;
|
|
155
173
|
if (shifted === zeroBased)
|
|
156
174
|
return point;
|
|
157
175
|
return axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a 1-based coordinate on `axis` lands after `count` lines are deleted at `start` and the
|
|
3
|
+
* replacement lines shift what follows by `delta`: before the edit it stays put, at or after the
|
|
4
|
+
* edited span it shifts, and inside a deleted span it clamps to the cut line. The clamp is the best
|
|
5
|
+
* effort for a geometry straddling the cut; a caller that must instead *drop* what a delete swallowed
|
|
6
|
+
* whole tests {@link isDeletedSpan} first.
|
|
7
|
+
*
|
|
8
|
+
* A shift never leaves the grid: the result is clamped to the axis's last line, so a region already
|
|
9
|
+
* touching the bottom (or the right edge) keeps its edge there instead of naming a line the format
|
|
10
|
+
* has no room for. That shrinks such a region by what it could not move, which is the lesser of the
|
|
11
|
+
* two evils and is what Excel does to the same region on the same edit.
|
|
12
|
+
*/
|
|
13
|
+
export declare function shiftIndex(v: number, start: number, count: number, delta: number, axis: 'row' | 'col'): number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the inclusive span `lo..hi` lies entirely within the `count` lines deleted at `start`: the
|
|
16
|
+
* test that separates "this moved" from "this is gone". A single coordinate is the degenerate span
|
|
17
|
+
* `lo === hi`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isDeletedSpan(lo: number, hi: number, start: number, count: number): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MAX_COLUMN, MAX_ROW } from './address.js';
|
|
2
|
+
export function shiftIndex(v, start, count, delta, axis) {
|
|
3
|
+
const moved = v < start ? v : v >= start + count ? v + delta : start;
|
|
4
|
+
return Math.min(moved, axis === 'row' ? MAX_ROW : MAX_COLUMN);
|
|
5
|
+
}
|
|
6
|
+
export function isDeletedSpan(lo, hi, start, count) {
|
|
7
|
+
return lo >= start && hi < start + count;
|
|
8
|
+
}
|
package/dist/core/image.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const PX_TO_EMU = 9525;
|
|
|
17
17
|
* them; `oneCell` moves but keeps its size; `absolute` is pinned to the page and does neither. Excel
|
|
18
18
|
* defaults to `oneCell` when the attribute is omitted. */
|
|
19
19
|
export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
|
|
20
|
+
/** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
|
|
21
|
+
export declare function isImageEditAs(value: string): value is ImageEditAs;
|
|
20
22
|
/** A fixed image size in EMUs: the extent of a one-cell anchor, which pixel dimensions convert into
|
|
21
23
|
* via {@link PX_TO_EMU}. */
|
|
22
24
|
export interface Extent {
|
package/dist/core/image.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export const PX_TO_EMU = 9525;
|
|
2
|
+
const IMAGE_EDIT_AS = { oneCell: true, twoCell: true, absolute: true };
|
|
3
|
+
export function isImageEditAs(value) {
|
|
4
|
+
return Object.hasOwn(IMAGE_EDIT_AS, value);
|
|
5
|
+
}
|
|
2
6
|
export function isOneCellAnchor(anchor) {
|
|
3
7
|
return 'ext' in anchor;
|
|
4
8
|
}
|
package/dist/core/merge.d.ts
CHANGED
|
@@ -21,5 +21,16 @@ export declare function masterOf(rects: readonly MergeRect[], row: number, col:
|
|
|
21
21
|
export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>, rect: MergeRect): void;
|
|
22
22
|
/** Decode an OOXML `sqref` (one or more space-separated ranges) into containment rectangles. A whole
|
|
23
23
|
* column or row leaves one axis unbounded, so its missing edges open to `Infinity` rather than
|
|
24
|
-
* clamping: a cell anywhere down the column still resolves inside it.
|
|
24
|
+
* clamping: a cell anywhere down the column still resolves inside it. An area that names no region
|
|
25
|
+
* that can exist contributes no rectangle, the reader's rule for every other foreign attribute. */
|
|
25
26
|
export declare function decodeSqrefRects(sqref: string): MergeRect[];
|
|
27
|
+
/**
|
|
28
|
+
* Re-anchor an OOXML `sqref` through a splice of `count` lines at `start` on `axis`, the inverse of
|
|
29
|
+
* {@link decodeSqrefRects}. Returns `undefined` when the splice deleted every area the `sqref` named:
|
|
30
|
+
* an empty `sqref` is not writable, so the entry holding it goes too.
|
|
31
|
+
*
|
|
32
|
+
* Each space-separated area shifts on its own, and one the splice does not move is returned as the
|
|
33
|
+
* *original text*. That matters for a file the library did not author: `B:B` and `B1:B1048576` decode
|
|
34
|
+
* identically, so a re-encode would rewrite a foreign spelling and cost the byte-clean round trip.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shiftSqref(sqref: string, axis: 'row' | 'col', start: number, count: number, delta: number): string | undefined;
|
package/dist/core/merge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeCornerRef, tryDecodeRange } from './address.js';
|
|
2
|
+
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
2
3
|
export function masterOf(rects, row, col) {
|
|
3
4
|
for (const rect of rects) {
|
|
4
5
|
if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
|
|
@@ -26,7 +27,10 @@ export function decodeSqrefRects(sqref) {
|
|
|
26
27
|
for (const part of sqref.split(/\s+/)) {
|
|
27
28
|
if (part === '')
|
|
28
29
|
continue;
|
|
29
|
-
const
|
|
30
|
+
const decoded = tryDecodeRange(part);
|
|
31
|
+
if (decoded === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const { top, left, bottom, right } = decoded;
|
|
30
34
|
rects.push({
|
|
31
35
|
top: top ?? 1,
|
|
32
36
|
left: left ?? 1,
|
|
@@ -36,3 +40,33 @@ export function decodeSqrefRects(sqref) {
|
|
|
36
40
|
}
|
|
37
41
|
return rects;
|
|
38
42
|
}
|
|
43
|
+
export function shiftSqref(sqref, axis, start, count, delta) {
|
|
44
|
+
const areas = [];
|
|
45
|
+
for (const area of sqref.split(/\s+/)) {
|
|
46
|
+
if (area === '')
|
|
47
|
+
continue;
|
|
48
|
+
const shifted = shiftSqrefArea(area, axis, start, count, delta);
|
|
49
|
+
if (shifted !== undefined)
|
|
50
|
+
areas.push(shifted);
|
|
51
|
+
}
|
|
52
|
+
return areas.length > 0 ? areas.join(' ') : undefined;
|
|
53
|
+
}
|
|
54
|
+
function shiftSqrefArea(area, axis, start, count, delta) {
|
|
55
|
+
const decoded = tryDecodeRange(area);
|
|
56
|
+
if (decoded === undefined)
|
|
57
|
+
return area;
|
|
58
|
+
const { top, left, bottom, right } = decoded;
|
|
59
|
+
const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
|
|
60
|
+
if (lo === undefined || hi === undefined)
|
|
61
|
+
return area;
|
|
62
|
+
if (isDeletedSpan(lo, hi, start, count))
|
|
63
|
+
return undefined;
|
|
64
|
+
const movedLo = shiftIndex(lo, start, count, delta, axis);
|
|
65
|
+
const movedHi = shiftIndex(hi, start, count, delta, axis);
|
|
66
|
+
if (movedLo === lo && movedHi === hi)
|
|
67
|
+
return area;
|
|
68
|
+
const [tl, br] = axis === 'row'
|
|
69
|
+
? [encodeCornerRef(left, movedLo), encodeCornerRef(right, movedHi)]
|
|
70
|
+
: [encodeCornerRef(movedLo, top), encodeCornerRef(movedHi, bottom)];
|
|
71
|
+
return area.includes(':') ? `${tl}:${br}` : tl;
|
|
72
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paper orientation, as `<pageSetup orientation>` carries it.
|
|
3
|
+
*
|
|
4
|
+
* `ST_Orientation` has a third member, `default`, which means "whatever the printer decides" and is
|
|
5
|
+
* indistinguishable from the attribute being absent. The model spells that absence as an unset field,
|
|
6
|
+
* so a file carrying `default` reads back with no orientation and writes back without the attribute.
|
|
7
|
+
*/
|
|
8
|
+
export type PageOrientation = 'portrait' | 'landscape';
|
|
9
|
+
/** Narrow a raw `<pageSetup orientation>` token to a known {@link PageOrientation}. */
|
|
10
|
+
export declare function isPageOrientation(value: string): value is PageOrientation;
|
|
11
|
+
/** The order pages are numbered and printed in across a sheet wider and taller than one page. */
|
|
12
|
+
export type PageOrder = 'downThenOver' | 'overThenDown';
|
|
13
|
+
/** Narrow a raw `<pageSetup pageOrder>` token to a known {@link PageOrder}. */
|
|
14
|
+
export declare function isPageOrder(value: string): value is PageOrder;
|
|
1
15
|
/**
|
|
2
16
|
* Print-scaling and orientation settings. These map onto two OOXML elements: `fitToPage` is the
|
|
3
17
|
* `<pageSetUpPr>` flag (a `<sheetPr>` child) that switches Excel from fixed-zoom to fit-to-page
|
|
@@ -16,9 +30,9 @@ export interface PageSetup {
|
|
|
16
30
|
/** Fixed print zoom as a percentage; Excel honours it only when `fitToPage` is off. */
|
|
17
31
|
scale?: number;
|
|
18
32
|
/** Paper orientation. */
|
|
19
|
-
orientation?:
|
|
33
|
+
orientation?: PageOrientation;
|
|
20
34
|
/** Order pages are numbered/printed in across a multi-page sheet. */
|
|
21
|
-
pageOrder?:
|
|
35
|
+
pageOrder?: PageOrder;
|
|
22
36
|
/**
|
|
23
37
|
* Paper size as Excel's 1-based enumeration index (e.g. `9` = A4, `1` = US Letter). Carried as an
|
|
24
38
|
* opaque integer: the model does not map it to physical dimensions, only preserves whatever the
|
package/dist/core/page-setup.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
const PAGE_ORIENTATIONS = { portrait: true, landscape: true };
|
|
2
|
+
export function isPageOrientation(value) {
|
|
3
|
+
return Object.hasOwn(PAGE_ORIENTATIONS, value);
|
|
4
|
+
}
|
|
5
|
+
const PAGE_ORDERS = { downThenOver: true, overThenDown: true };
|
|
6
|
+
export function isPageOrder(value) {
|
|
7
|
+
return Object.hasOwn(PAGE_ORDERS, value);
|
|
8
|
+
}
|
|
@@ -17,6 +17,8 @@ export interface ParsedPivotField {
|
|
|
17
17
|
* data the reader does not model (an external connection, a range consolidation, or a scenario), and
|
|
18
18
|
* `unknown` covers a `type` the file declares that is none of these. */
|
|
19
19
|
export type PivotSourceKind = 'worksheet' | 'external' | 'consolidation' | 'scenario' | 'unknown';
|
|
20
|
+
/** Narrow a raw `<cacheSource type>` token to a {@link PivotSourceKind} a file may declare. */
|
|
21
|
+
export declare function isDeclarablePivotSourceKind(value: string): value is Exclude<PivotSourceKind, 'unknown'>;
|
|
20
22
|
/** Where a pivot cache draws its rows from. {@link kind} names the source type; {@link sheet} and
|
|
21
23
|
* {@link ref} locate the range only when it is `worksheet` and are empty strings otherwise, so a
|
|
22
24
|
* consumer can tell a genuinely non-worksheet source apart from a worksheet source that failed to
|
package/dist/core/pivot-table.js
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
import { AuthoringError, InternalError } from '../errors.js';
|
|
2
2
|
import { encodeAddress } from './address.js';
|
|
3
3
|
import { isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, } from './value.js';
|
|
4
|
-
const PIVOT_METRICS =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
const PIVOT_METRICS = {
|
|
5
|
+
sum: true,
|
|
6
|
+
count: true,
|
|
7
|
+
countNums: true,
|
|
8
|
+
average: true,
|
|
9
|
+
max: true,
|
|
10
|
+
min: true,
|
|
11
|
+
product: true,
|
|
12
|
+
stdDev: true,
|
|
13
|
+
stdDevp: true,
|
|
14
|
+
var: true,
|
|
15
|
+
varp: true,
|
|
16
|
+
};
|
|
17
17
|
function isPivotMetric(value) {
|
|
18
|
-
return
|
|
18
|
+
return Object.hasOwn(PIVOT_METRICS, value);
|
|
19
19
|
}
|
|
20
20
|
export function pivotMetricFromSubtotal(subtotal) {
|
|
21
21
|
if (subtotal === undefined)
|
|
22
22
|
return 'sum';
|
|
23
23
|
return isPivotMetric(subtotal) ? subtotal : 'sum';
|
|
24
24
|
}
|
|
25
|
+
const DECLARABLE_PIVOT_SOURCE_KINDS = {
|
|
26
|
+
worksheet: true,
|
|
27
|
+
external: true,
|
|
28
|
+
consolidation: true,
|
|
29
|
+
scenario: true,
|
|
30
|
+
};
|
|
31
|
+
export function isDeclarablePivotSourceKind(value) {
|
|
32
|
+
return Object.hasOwn(DECLARABLE_PIVOT_SOURCE_KINDS, value);
|
|
33
|
+
}
|
|
25
34
|
const BLANK = { kind: 'blank' };
|
|
26
35
|
export class PivotTable {
|
|
27
36
|
metric;
|
|
@@ -34,8 +43,8 @@ export class PivotTable {
|
|
|
34
43
|
valueField;
|
|
35
44
|
constructor(options) {
|
|
36
45
|
const metric = options.metric ?? 'sum';
|
|
37
|
-
if (!
|
|
38
|
-
throw new AuthoringError(`unsupported pivot metric "${metric}": expected one of ${
|
|
46
|
+
if (!Object.hasOwn(PIVOT_METRICS, metric)) {
|
|
47
|
+
throw new AuthoringError(`unsupported pivot metric "${metric}": expected one of ${Object.keys(PIVOT_METRICS).join(', ')}`);
|
|
39
48
|
}
|
|
40
49
|
this.metric = metric;
|
|
41
50
|
const source = options.source;
|
|
@@ -63,5 +63,8 @@ export declare const SHEET_PROTECTION_FLAGS: readonly {
|
|
|
63
63
|
* Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
|
|
64
64
|
* random salt, so protecting two sheets with the same password yields different credentials:
|
|
65
65
|
* the salt is real randomness, not a stub.
|
|
66
|
+
*
|
|
67
|
+
* The spin loop is the cost of the scheme rather than of this implementation: Excel's default
|
|
68
|
+
* 100000 iterations is 100000 chained SHA-512 digests, and no shortcut through them exists.
|
|
66
69
|
*/
|
|
67
70
|
export declare function deriveCredential(password: string, spinCount?: number): SheetProtectionCredential;
|
package/dist/core/protection.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { concat, toBase64 } from '../bytes.js';
|
|
2
|
+
import { sha512 } from '../sha512.js';
|
|
2
3
|
export const SHEET_PROTECTION_FLAGS = [
|
|
3
4
|
{ key: 'formatCells', defaultForbidden: true },
|
|
4
5
|
{ key: 'formatColumns', defaultForbidden: true },
|
|
@@ -17,26 +18,31 @@ export const SHEET_PROTECTION_FLAGS = [
|
|
|
17
18
|
{ key: 'selectUnlockedCells', defaultForbidden: false },
|
|
18
19
|
];
|
|
19
20
|
const ALGORITHM_NAME = 'SHA-512';
|
|
20
|
-
const HASH = 'sha512';
|
|
21
21
|
const DEFAULT_SPIN_COUNT = 100000;
|
|
22
22
|
const SALT_BYTES = 16;
|
|
23
23
|
export function deriveCredential(password, spinCount = DEFAULT_SPIN_COUNT) {
|
|
24
|
-
const salt =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.digest();
|
|
29
|
-
const iteration = Buffer.alloc(4);
|
|
24
|
+
const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
|
|
25
|
+
let hash = sha512(concat([salt, utf16le(password)]));
|
|
26
|
+
const spun = new Uint8Array(hash.length + 4);
|
|
27
|
+
const counter = new DataView(spun.buffer, hash.length, 4);
|
|
30
28
|
for (let i = 0; i < spinCount; i++) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.digest();
|
|
29
|
+
spun.set(hash, 0);
|
|
30
|
+
counter.setUint32(0, i, true);
|
|
31
|
+
hash = sha512(spun);
|
|
35
32
|
}
|
|
36
33
|
return {
|
|
37
34
|
algorithmName: ALGORITHM_NAME,
|
|
38
|
-
hashValue: hash
|
|
39
|
-
saltValue: salt
|
|
35
|
+
hashValue: toBase64(hash),
|
|
36
|
+
saltValue: toBase64(salt),
|
|
40
37
|
spinCount,
|
|
41
38
|
};
|
|
42
39
|
}
|
|
40
|
+
function utf16le(text) {
|
|
41
|
+
const bytes = new Uint8Array(text.length * 2);
|
|
42
|
+
for (let i = 0; i < text.length; i++) {
|
|
43
|
+
const unit = text.charCodeAt(i);
|
|
44
|
+
bytes[i * 2] = unit & 0xff;
|
|
45
|
+
bytes[i * 2 + 1] = unit >>> 8;
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
package/dist/core/row-input.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Cell } from './cell.ts';
|
|
2
2
|
import type { CellValue } from './value.ts';
|
|
3
3
|
import type { ColumnProperties, RowInput } from './worksheet.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a positional array of values to the (1-based index, value) placements it names: the first
|
|
6
|
+
* value lands at 1, and a hole or an explicit `undefined` places nothing, leaving that line
|
|
7
|
+
* untouched. The index is a column on the row axis and a row on the column axis; the reading is the
|
|
8
|
+
* same either way.
|
|
9
|
+
*/
|
|
10
|
+
export declare function positionalPlacements(values: readonly (CellValue | undefined)[]): Array<[number, CellValue]>;
|
|
4
11
|
/**
|
|
5
12
|
* Resolve a `RowInput` to the (1-based column, value) placements it names. A positional array maps
|
|
6
13
|
* each value to its column from A, skipping a hole or an explicit `undefined` so that column is left
|
package/dist/core/row-input.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { AuthoringError } from '../errors.js';
|
|
2
2
|
import { Cell } from './cell.js';
|
|
3
|
+
export function positionalPlacements(values) {
|
|
4
|
+
const placements = [];
|
|
5
|
+
values.forEach((value, index) => {
|
|
6
|
+
if (value !== undefined)
|
|
7
|
+
placements.push([index + 1, value]);
|
|
8
|
+
});
|
|
9
|
+
return placements;
|
|
10
|
+
}
|
|
3
11
|
export function rowPlacements(values, columns) {
|
|
4
|
-
if (Array.isArray(values))
|
|
5
|
-
|
|
6
|
-
values.forEach((value, index) => {
|
|
7
|
-
if (value !== undefined)
|
|
8
|
-
placements.push([index + 1, value]);
|
|
9
|
-
});
|
|
10
|
-
return placements;
|
|
11
|
-
}
|
|
12
|
+
if (Array.isArray(values))
|
|
13
|
+
return positionalPlacements(values);
|
|
12
14
|
return Object.entries(values).map(([key, value]) => [columnIndexByKey(columns, key), value]);
|
|
13
15
|
}
|
|
14
16
|
export function buildRowCells(number, values, columns) {
|
package/dist/core/style.d.ts
CHANGED
|
@@ -14,6 +14,26 @@ export interface Color {
|
|
|
14
14
|
* automatic placeholder `indexed="64"`; the visible colour lives on `fgColor`. */
|
|
15
15
|
readonly indexed?: number;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse a colour written in any of the shapes the API accepts into the bare 8-hex ARGB OOXML wants,
|
|
19
|
+
* or `undefined` if it is not one of them.
|
|
20
|
+
*
|
|
21
|
+
* Two conveniences are accepted, and nothing else: a leading `#` is a CSS habit and is stripped
|
|
22
|
+
* (`'#FFBFBFBF'` → `'FFBFBFBF'`), and a 6-hex RGB is promoted with a fully-opaque alpha (`'00FF00'` →
|
|
23
|
+
* `'FF00FF00'`), the common case of a colour written without its alpha channel. Casing is preserved,
|
|
24
|
+
* so a foreign file's lowercase value round-trips as it arrived.
|
|
25
|
+
*
|
|
26
|
+
* This states the grammar once for both directions. What a malformed value *means* differs by
|
|
27
|
+
* direction and is decided by the caller: on read it is foreign data and resolves to nothing, on
|
|
28
|
+
* write it is a caller's bug and throws (see `normalizeArgb` in `io/xlsx/color-xml.ts`). Neither can
|
|
29
|
+
* be a silently half-parsed value, because Excel does not report a malformed `rgb` at all; it
|
|
30
|
+
* renders flat black.
|
|
31
|
+
*
|
|
32
|
+
* `normalizeThemeColor` in `core/theme.ts` asks a similar question and stays separate: a theme
|
|
33
|
+
* slot is `<a:srgbClr val>`, which DrawingML gives no alpha channel, so the two differ in exactly the
|
|
34
|
+
* thing this one exists to add.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseArgb(value: string): string | undefined;
|
|
17
37
|
/**
|
|
18
38
|
* Fill pattern kinds, as OOXML's `ST_PatternType` enumerates them. `none` is the
|
|
19
39
|
* absence of a fill; `solid` paints the whole cell with the foreground colour (the
|
|
@@ -178,6 +198,13 @@ export declare const CELL_STYLE_FACETS: (keyof CellStyle)[];
|
|
|
178
198
|
* it joins, the same single-point-of-change the cell path gets.
|
|
179
199
|
*/
|
|
180
200
|
export declare function assignStyleFacets(target: CellStyle, source: Readonly<CellStyle>): void;
|
|
201
|
+
/**
|
|
202
|
+
* The {@link CellStyle} facets of `source` as a plain tuple of their own, for a source that carries
|
|
203
|
+
* more than the facets (a column's properties also hold width, hidden and outline state). The
|
|
204
|
+
* projection counterpart to {@link assignStyleFacets}, driven by the same list, so a facet added to
|
|
205
|
+
* the tuple reaches a `<col>` style without anyone remembering to widen a literal.
|
|
206
|
+
*/
|
|
207
|
+
export declare function pickStyleFacets(source: Readonly<CellStyle>): CellStyle;
|
|
181
208
|
/**
|
|
182
209
|
* A named cell style: the OOXML `cellStyleXfs`/`cellStyles` layer. A spreadsheet applies a built-in
|
|
183
210
|
* or custom style (e.g. "Normal", "Accent1") whose visual facets live in this shared, named layer
|