@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.
Files changed (106) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +11 -0
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/color-resolution.d.ts +1 -1
  9. package/dist/core/color-resolution.js +7 -8
  10. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  11. package/dist/core/conditional-formatting-overlay.js +11 -0
  12. package/dist/core/conditional-formatting.d.ts +45 -5
  13. package/dist/core/conditional-formatting.js +88 -0
  14. package/dist/core/data-validation-overlay.d.ts +11 -0
  15. package/dist/core/data-validation-overlay.js +21 -2
  16. package/dist/core/data-validation.d.ts +9 -2
  17. package/dist/core/data-validation.js +34 -0
  18. package/dist/core/grid-edits.d.ts +16 -0
  19. package/dist/core/grid-edits.js +26 -8
  20. package/dist/core/grid-shift.d.ts +19 -0
  21. package/dist/core/grid-shift.js +8 -0
  22. package/dist/core/image.d.ts +2 -0
  23. package/dist/core/image.js +4 -0
  24. package/dist/core/merge.d.ts +12 -1
  25. package/dist/core/merge.js +36 -2
  26. package/dist/core/page-setup.d.ts +16 -2
  27. package/dist/core/page-setup.js +8 -1
  28. package/dist/core/pivot-table.d.ts +2 -0
  29. package/dist/core/pivot-table.js +25 -16
  30. package/dist/core/protection.d.ts +3 -0
  31. package/dist/core/protection.js +20 -14
  32. package/dist/core/row-input.d.ts +7 -0
  33. package/dist/core/row-input.js +10 -8
  34. package/dist/core/style.d.ts +27 -0
  35. package/dist/core/style.js +82 -67
  36. package/dist/core/table.d.ts +25 -11
  37. package/dist/core/table.js +67 -31
  38. package/dist/core/theme.d.ts +3 -1
  39. package/dist/core/theme.js +4 -0
  40. package/dist/core/workbook.d.ts +2 -2
  41. package/dist/core/worksheet-comments.d.ts +8 -0
  42. package/dist/core/worksheet-comments.js +18 -0
  43. package/dist/core/worksheet.d.ts +15 -2
  44. package/dist/core/worksheet.js +28 -48
  45. package/dist/customui/ribbon.js +24 -23
  46. package/dist/entries/core.d.ts +3 -3
  47. package/dist/entries/csv.d.ts +1 -1
  48. package/dist/entries/node-unavailable.d.ts +15 -0
  49. package/dist/entries/node-unavailable.js +20 -0
  50. package/dist/entries/node.d.ts +1 -0
  51. package/dist/entries/node.js +1 -0
  52. package/dist/entries/xlsx.d.ts +0 -1
  53. package/dist/entries/xlsx.js +0 -1
  54. package/dist/io/csv/read.js +1 -1
  55. package/dist/io/csv/write.d.ts +12 -2
  56. package/dist/io/csv/write.js +17 -2
  57. package/dist/io/opc/inflate.js +2 -12
  58. package/dist/io/opc/rels.d.ts +11 -0
  59. package/dist/io/opc/rels.js +3 -2
  60. package/dist/io/style/xf-style.d.ts +7 -2
  61. package/dist/io/style/xf-style.js +6 -1
  62. package/dist/io/xlsx/cell-accumulator.d.ts +1 -3
  63. package/dist/io/xlsx/cell-accumulator.js +20 -58
  64. package/dist/io/xlsx/cell-value.d.ts +1 -1
  65. package/dist/io/xlsx/cell-value.js +7 -4
  66. package/dist/io/xlsx/color-xml.d.ts +1 -1
  67. package/dist/io/xlsx/color-xml.js +7 -6
  68. package/dist/io/xlsx/comments.js +4 -2
  69. package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
  70. package/dist/io/xlsx/conditional-formatting.js +60 -45
  71. package/dist/io/xlsx/data-validation.d.ts +1 -1
  72. package/dist/io/xlsx/data-validation.js +35 -33
  73. package/dist/io/xlsx/hyperlinks.js +6 -14
  74. package/dist/io/xlsx/images.js +19 -22
  75. package/dist/io/xlsx/read-pivot.js +2 -8
  76. package/dist/io/xlsx/read-shared-strings.js +8 -47
  77. package/dist/io/xlsx/read-styles.js +2 -1
  78. package/dist/io/xlsx/read-worksheet.js +14 -16
  79. package/dist/io/xlsx/read.d.ts +1 -1
  80. package/dist/io/xlsx/read.js +44 -74
  81. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  82. package/dist/io/xlsx/rich-runs.js +94 -30
  83. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  84. package/dist/io/xlsx/sheet-properties.js +13 -9
  85. package/dist/io/xlsx/styles.d.ts +3 -15
  86. package/dist/io/xlsx/styles.js +63 -88
  87. package/dist/io/xlsx/tables.js +13 -13
  88. package/dist/io/xlsx/theme-xml.js +7 -4
  89. package/dist/io/xlsx/threaded-comments.js +19 -19
  90. package/dist/io/xlsx/workbook-xml.js +14 -8
  91. package/dist/io/xlsx/worksheet-xml.js +34 -37
  92. package/dist/io/xlsx/write-stream.d.ts +8 -1
  93. package/dist/io/xlsx/write-stream.js +10 -11
  94. package/dist/sha512.d.ts +2 -0
  95. package/dist/sha512.js +146 -0
  96. package/dist/vba/bytes.d.ts +1 -2
  97. package/dist/vba/bytes.js +1 -12
  98. package/dist/vba/dir-records.d.ts +53 -0
  99. package/dist/vba/dir-records.js +28 -0
  100. package/dist/vba/project-editor.js +5 -34
  101. package/dist/vba/project.js +2 -18
  102. package/dist/xml/xml-read.d.ts +60 -0
  103. package/dist/xml/xml-read.js +47 -0
  104. package/dist/xml/xml.d.ts +43 -1
  105. package/dist/xml/xml.js +17 -5
  106. 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;
@@ -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 >= start && hi < start + count)
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
+ }
@@ -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 {
@@ -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
  }
@@ -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;
@@ -1,4 +1,5 @@
1
- import { decodeRange } from './address.js';
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 { top, left, bottom, right } = decodeRange(part);
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?: 'portrait' | 'landscape';
33
+ orientation?: PageOrientation;
20
34
  /** Order pages are numbered/printed in across a multi-page sheet. */
21
- pageOrder?: 'downThenOver' | 'overThenDown';
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
@@ -1 +1,8 @@
1
- export {};
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
@@ -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 = new Set([
5
- 'sum',
6
- 'count',
7
- 'countNums',
8
- 'average',
9
- 'max',
10
- 'min',
11
- 'product',
12
- 'stdDev',
13
- 'stdDevp',
14
- 'var',
15
- 'varp',
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 PIVOT_METRICS.has(value);
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 (!PIVOT_METRICS.has(metric)) {
38
- throw new AuthoringError(`unsupported pivot metric "${metric}": expected one of ${[...PIVOT_METRICS].join(', ')}`);
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;
@@ -1,4 +1,5 @@
1
- import { createHash, randomBytes } from 'node:crypto';
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 = randomBytes(SALT_BYTES);
25
- const secret = Buffer.from(password, 'utf16le');
26
- let hash = createHash(HASH)
27
- .update(Buffer.concat([salt, secret]))
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
- iteration.writeUInt32LE(i, 0);
32
- hash = createHash(HASH)
33
- .update(Buffer.concat([hash, iteration]))
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.toString('base64'),
39
- saltValue: salt.toString('base64'),
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
+ }
@@ -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
@@ -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
- const placements = [];
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) {
@@ -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