@shbernal/ts-xlsx 2.0.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.
Files changed (147) 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 +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -1,3 +1,18 @@
1
+ import type { AssertNever } from './internal.ts';
2
+ /**
3
+ * Paper orientation, as `<pageSetup orientation>` carries it.
4
+ *
5
+ * `ST_Orientation` has a third member, `default`, which means "whatever the printer decides" and is
6
+ * indistinguishable from the attribute being absent. The model spells that absence as an unset field,
7
+ * so a file carrying `default` reads back with no orientation and writes back without the attribute.
8
+ */
9
+ export type PageOrientation = 'portrait' | 'landscape';
10
+ /** Narrow a raw `<pageSetup orientation>` token to a known {@link PageOrientation}. */
11
+ export declare const isPageOrientation: (value: string) => value is PageOrientation;
12
+ /** The order pages are numbered and printed in across a sheet wider and taller than one page. */
13
+ export type PageOrder = 'downThenOver' | 'overThenDown';
14
+ /** Narrow a raw `<pageSetup pageOrder>` token to a known {@link PageOrder}. */
15
+ export declare const isPageOrder: (value: string) => value is PageOrder;
1
16
  /**
2
17
  * Print-scaling and orientation settings. These map onto two OOXML elements: `fitToPage` is the
3
18
  * `<pageSetUpPr>` flag (a `<sheetPr>` child) that switches Excel from fixed-zoom to fit-to-page
@@ -16,9 +31,9 @@ export interface PageSetup {
16
31
  /** Fixed print zoom as a percentage; Excel honours it only when `fitToPage` is off. */
17
32
  scale?: number;
18
33
  /** Paper orientation. */
19
- orientation?: 'portrait' | 'landscape';
34
+ orientation?: PageOrientation;
20
35
  /** Order pages are numbered/printed in across a multi-page sheet. */
21
- pageOrder?: 'downThenOver' | 'overThenDown';
36
+ pageOrder?: PageOrder;
22
37
  /**
23
38
  * Paper size as Excel's 1-based enumeration index (e.g. `9` = A4, `1` = US Letter). Carried as an
24
39
  * opaque integer: the model does not map it to physical dimensions, only preserves whatever the
@@ -33,6 +48,61 @@ export interface PageSetup {
33
48
  */
34
49
  printerSettings?: Uint8Array;
35
50
  }
51
+ /**
52
+ * How one `<pageSetup>` attribute encodes: which model key it is and what kind of value it carries.
53
+ *
54
+ * Format-blind on purpose, the same way {@link AlignmentFacet} is: `PageSetup` is a core type and
55
+ * the layering gate forbids core importing a serialisation, so the table states what an attribute
56
+ * *is* and each codec supplies the reading and the writing off the `kind`. The OOXML attribute name
57
+ * is the model key throughout, so it is not restated.
58
+ */
59
+ export type PageSetupFacet = {
60
+ readonly key: 'paperSize' | 'scale' | 'fitToWidth' | 'fitToHeight';
61
+ /** A non-negative integer: a page count, a percentage, or a paper-size id. */
62
+ readonly kind: 'count';
63
+ } | {
64
+ readonly key: 'pageOrder' | 'orientation';
65
+ readonly kind: 'token';
66
+ /** The enumeration guard, and what to call it in the error when a value fails it. */
67
+ readonly isValid: (value: string) => boolean;
68
+ readonly label: string;
69
+ };
70
+ /**
71
+ * The six `<pageSetup>` attributes, declared once, in CT_PageSetup order. Both directions key off
72
+ * this list, so an attribute written but not read (it survives a re-write and vanishes on load) or
73
+ * read but not written is a compile error rather than something a reviewer has to notice.
74
+ */
75
+ export declare const PAGE_SETUP_FACETS: readonly [{
76
+ readonly key: 'paperSize';
77
+ readonly kind: 'count';
78
+ }, {
79
+ readonly key: 'scale';
80
+ readonly kind: 'count';
81
+ }, {
82
+ readonly key: 'fitToWidth';
83
+ readonly kind: 'count';
84
+ }, {
85
+ readonly key: 'fitToHeight';
86
+ readonly kind: 'count';
87
+ }, {
88
+ readonly key: 'pageOrder';
89
+ readonly kind: 'token';
90
+ readonly isValid: (value: string) => value is PageOrder;
91
+ readonly label: 'page order';
92
+ }, {
93
+ readonly key: 'orientation';
94
+ readonly kind: 'token';
95
+ readonly isValid: (value: string) => value is PageOrientation;
96
+ readonly label: 'page orientation';
97
+ }];
98
+ /**
99
+ * Compile-time proof that {@link PAGE_SETUP_FACETS} covers every `<pageSetup>` attribute.
100
+ *
101
+ * `fitToPage` and `printerSettings` are excluded because neither is one: `fitToPage` is a
102
+ * `<sheetPr>` child's flag and `printerSettings` is the blob behind an `r:id`, so both are written
103
+ * and read somewhere else entirely and a table entry for them would describe nothing.
104
+ */
105
+ export type EveryPageSetupFacetIsDeclared = AssertNever<Exclude<keyof PageSetup, 'fitToPage' | 'printerSettings' | (typeof PAGE_SETUP_FACETS)[number]['key']>>;
36
106
  /**
37
107
  * Print-toggle flags from the `<printOptions>` element. Each maps to a boolean OOXML attribute that
38
108
  * defaults false, except `gridLinesSet`, which defaults true and gates whether `gridLines` is
@@ -51,6 +121,11 @@ export interface PrintOptions {
51
121
  /** Whether the `gridLines` flag is authoritative; when `false`, Excel ignores `gridLines`. */
52
122
  gridLinesSet?: boolean;
53
123
  }
124
+ /** The `<printOptions>` flags, in CT_PrintOptions attribute order. Each is a plain OOXML boolean, so
125
+ * the list is the whole of what either direction needs to know. */
126
+ export declare const PRINT_OPTION_FLAGS: readonly ["horizontalCentered", "verticalCentered", "headings", "gridLines", "gridLinesSet"];
127
+ /** Compile-time proof that {@link PRINT_OPTION_FLAGS} covers every {@link PrintOptions} flag. */
128
+ export type EveryPrintOptionFlagIsDeclared = AssertNever<Exclude<keyof PrintOptions, (typeof PRINT_OPTION_FLAGS)[number]>>;
54
129
  /**
55
130
  * A manual page break (`<brk>`). For a row break, `id` is the row the layout splits *before*; for a
56
131
  * column break it is the column. `max` bounds the break's extent across the other axis (Excel writes
@@ -78,6 +153,10 @@ export interface PageMargins {
78
153
  header?: number;
79
154
  footer?: number;
80
155
  }
156
+ /** The `<pageMargins>` sides, in the order CT_PageMargins declares them. */
157
+ export declare const MARGIN_SIDES: readonly ["left", "right", "top", "bottom", "header", "footer"];
158
+ /** Compile-time proof that {@link MARGIN_SIDES} covers every {@link PageMargins} side. */
159
+ export type EveryMarginSideIsDeclared = AssertNever<Exclude<keyof PageMargins, (typeof MARGIN_SIDES)[number]>>;
81
160
  /**
82
161
  * Page header/footer text, one string per page class. Excel only honours the even- and
83
162
  * first-page variants when the writer also sets the gating flags (`differentOddEven`,
@@ -92,3 +171,8 @@ export interface HeaderFooter {
92
171
  firstHeader?: string;
93
172
  firstFooter?: string;
94
173
  }
174
+ /** The `<headerFooter>` children, in CT_HeaderFooter child order. The element name is the model key
175
+ * throughout, so one list serves the reader's capture, the reader's commit, and the writer. */
176
+ export declare const HEADER_FOOTER_ELEMENTS: readonly ["oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter"];
177
+ /** Compile-time proof that {@link HEADER_FOOTER_ELEMENTS} covers every {@link HeaderFooter} slot. */
178
+ export type EveryHeaderFooterElementIsDeclared = AssertNever<Exclude<keyof HeaderFooter, (typeof HEADER_FOOTER_ELEMENTS)[number]>>;
@@ -1 +1,34 @@
1
- export {};
1
+ import { tokenSet } from '../token-set.js';
2
+ export const isPageOrientation = tokenSet({ portrait: true, landscape: true });
3
+ export const isPageOrder = tokenSet({ downThenOver: true, overThenDown: true });
4
+ export const PAGE_SETUP_FACETS = [
5
+ { key: 'paperSize', kind: 'count' },
6
+ { key: 'scale', kind: 'count' },
7
+ { key: 'fitToWidth', kind: 'count' },
8
+ { key: 'fitToHeight', kind: 'count' },
9
+ { key: 'pageOrder', kind: 'token', isValid: isPageOrder, label: 'page order' },
10
+ { key: 'orientation', kind: 'token', isValid: isPageOrientation, label: 'page orientation' },
11
+ ];
12
+ export const PRINT_OPTION_FLAGS = [
13
+ 'horizontalCentered',
14
+ 'verticalCentered',
15
+ 'headings',
16
+ 'gridLines',
17
+ 'gridLinesSet',
18
+ ];
19
+ export const MARGIN_SIDES = [
20
+ 'left',
21
+ 'right',
22
+ 'top',
23
+ 'bottom',
24
+ 'header',
25
+ 'footer',
26
+ ];
27
+ export const HEADER_FOOTER_ELEMENTS = [
28
+ 'oddHeader',
29
+ 'oddFooter',
30
+ 'evenHeader',
31
+ 'evenFooter',
32
+ 'firstHeader',
33
+ 'firstFooter',
34
+ ];
@@ -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 const isDeclarablePivotSourceKind: (value: string) => value is "consolidation" | "external" | "scenario" | "worksheet";
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,32 @@
1
1
  import { AuthoringError, InternalError } from '../errors.js';
2
+ import { tokenSet } from '../token-set.js';
2
3
  import { encodeAddress } from './address.js';
3
4
  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
- ]);
17
- function isPivotMetric(value) {
18
- return PIVOT_METRICS.has(value);
19
- }
5
+ const PIVOT_METRICS = {
6
+ sum: true,
7
+ count: true,
8
+ countNums: true,
9
+ average: true,
10
+ max: true,
11
+ min: true,
12
+ product: true,
13
+ stdDev: true,
14
+ stdDevp: true,
15
+ var: true,
16
+ varp: true,
17
+ };
18
+ const isPivotMetric = tokenSet(PIVOT_METRICS);
20
19
  export function pivotMetricFromSubtotal(subtotal) {
21
20
  if (subtotal === undefined)
22
21
  return 'sum';
23
22
  return isPivotMetric(subtotal) ? subtotal : 'sum';
24
23
  }
24
+ export const isDeclarablePivotSourceKind = tokenSet({
25
+ worksheet: true,
26
+ external: true,
27
+ consolidation: true,
28
+ scenario: true,
29
+ });
25
30
  const BLANK = { kind: 'blank' };
26
31
  export class PivotTable {
27
32
  metric;
@@ -34,8 +39,8 @@ export class PivotTable {
34
39
  valueField;
35
40
  constructor(options) {
36
41
  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(', ')}`);
42
+ if (!Object.hasOwn(PIVOT_METRICS, metric)) {
43
+ throw new AuthoringError(`unsupported pivot metric "${metric}": expected one of ${Object.keys(PIVOT_METRICS).join(', ')}`);
39
44
  }
40
45
  this.metric = metric;
41
46
  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) {
@@ -1,9 +1,10 @@
1
+ import { AxisHandle } from './axis-handle.ts';
1
2
  import type { Cell } from './cell.ts';
2
3
  import { type AssertNever } from './internal.ts';
3
4
  import type { Fill } from './style.ts';
4
5
  import type { CellValue } from './value.ts';
5
6
  import type { RowProperties, Worksheet } from './worksheet.ts';
6
- export declare class Row {
7
+ export declare class Row extends AxisHandle<RowProperties> {
7
8
  #private;
8
9
  /** 1-based row number. Fixed for this handle's lifetime. */
9
10
  readonly number: number;
@@ -16,6 +17,8 @@ export declare class Row {
16
17
  * formatted, and they create the record on first write.
17
18
  */
18
19
  get properties(): Readonly<RowProperties> | undefined;
20
+ protected propertiesOf(): RowProperties | undefined;
21
+ protected ensureProperties(): RowProperties;
19
22
  /**
20
23
  * Row height in points; `undefined` leaves the sheet default in force.
21
24
  *
package/dist/core/row.js CHANGED
@@ -1,45 +1,53 @@
1
1
  import { assertRowInBounds, columnToNumber, encodeAddress } from './address.js';
2
+ import { AxisHandle } from './axis-handle.js';
2
3
  import { INTERNAL } from './internal.js';
3
- export class Row {
4
+ export class Row extends AxisHandle {
4
5
  #sheet;
5
6
  number;
6
7
  constructor(sheet, number) {
8
+ super();
7
9
  assertRowInBounds(number);
8
10
  this.#sheet = sheet;
9
11
  this.number = number;
10
12
  }
11
13
  get properties() {
14
+ return this.propertiesOf();
15
+ }
16
+ propertiesOf() {
12
17
  return this.#sheet[INTERNAL].rowPropertiesOf(this.number);
13
18
  }
19
+ ensureProperties() {
20
+ return this.#sheet[INTERNAL].ensureRowProperties(this.number);
21
+ }
14
22
  get height() {
15
- return this.#read('height');
23
+ return this.read('height');
16
24
  }
17
25
  set height(height) {
18
- this.#write('height', height);
26
+ this.write('height', height);
19
27
  }
20
28
  get hidden() {
21
- return this.#read('hidden');
29
+ return this.read('hidden');
22
30
  }
23
31
  set hidden(hidden) {
24
- this.#write('hidden', hidden);
32
+ this.write('hidden', hidden);
25
33
  }
26
34
  get outlineLevel() {
27
- return this.#read('outlineLevel');
35
+ return this.read('outlineLevel');
28
36
  }
29
37
  set outlineLevel(outlineLevel) {
30
- this.#write('outlineLevel', outlineLevel);
38
+ this.write('outlineLevel', outlineLevel);
31
39
  }
32
40
  get collapsed() {
33
- return this.#read('collapsed');
41
+ return this.read('collapsed');
34
42
  }
35
43
  set collapsed(collapsed) {
36
- this.#write('collapsed', collapsed);
44
+ this.write('collapsed', collapsed);
37
45
  }
38
46
  get fill() {
39
- return this.#read('fill');
47
+ return this.read('fill');
40
48
  }
41
49
  set fill(fill) {
42
- this.#write('fill', fill);
50
+ this.write('fill', fill);
43
51
  }
44
52
  getCell(column) {
45
53
  const index = typeof column === 'number' ? column : columnToNumber(column);
@@ -60,16 +68,4 @@ export class Row {
60
68
  this.getCell(index + 1).value = value;
61
69
  });
62
70
  }
63
- #read(key) {
64
- return this.#sheet[INTERNAL].rowPropertiesOf(this.number)?.[key];
65
- }
66
- #write(key, value) {
67
- if (value === undefined) {
68
- const properties = this.#sheet[INTERNAL].rowPropertiesOf(this.number);
69
- if (properties !== undefined)
70
- delete properties[key];
71
- return;
72
- }
73
- this.#sheet[INTERNAL].ensureRowProperties(this.number)[key] = value;
74
- }
75
71
  }
@@ -1,7 +1,8 @@
1
+ import type { AssertNever } from './internal.ts';
1
2
  /** Underline can be a plain flag or one of Excel's named underline styles. */
2
3
  export type UnderlineStyle = boolean | 'none' | 'single' | 'double' | 'singleAccounting' | 'doubleAccounting';
3
4
  /** Narrow a raw `<u val>` token to a named {@link UnderlineStyle} (the non-boolean members). */
4
- export declare function isNamedUnderlineStyle(value: string): value is Exclude<UnderlineStyle, boolean>;
5
+ export declare const isNamedUnderlineStyle: (value: string) => value is "double" | "doubleAccounting" | "none" | "single" | "singleAccounting";
5
6
  /** A colour, expressed as an ARGB hex string (`"FF0000FF"`) or an indexed theme colour. */
6
7
  export interface Color {
7
8
  /** 8-digit ARGB hex, uppercase, no leading `#`. */
@@ -14,6 +15,26 @@ export interface Color {
14
15
  * automatic placeholder `indexed="64"`; the visible colour lives on `fgColor`. */
15
16
  readonly indexed?: number;
16
17
  }
18
+ /**
19
+ * Parse a colour written in any of the shapes the API accepts into the bare 8-hex ARGB OOXML wants,
20
+ * or `undefined` if it is not one of them.
21
+ *
22
+ * Two conveniences are accepted, and nothing else: a leading `#` is a CSS habit and is stripped
23
+ * (`'#FFBFBFBF'` → `'FFBFBFBF'`), and a 6-hex RGB is promoted with a fully-opaque alpha (`'00FF00'` →
24
+ * `'FF00FF00'`), the common case of a colour written without its alpha channel. Casing is preserved,
25
+ * so a foreign file's lowercase value round-trips as it arrived.
26
+ *
27
+ * This states the grammar once for both directions. What a malformed value *means* differs by
28
+ * direction and is decided by the caller: on read it is foreign data and resolves to nothing, on
29
+ * write it is a caller's bug and throws (see `normalizeArgb` in `io/xlsx/color-xml.ts`). Neither can
30
+ * be a silently half-parsed value, because Excel does not report a malformed `rgb` at all; it
31
+ * renders flat black.
32
+ *
33
+ * `normalizeThemeColor` in `core/theme.ts` asks a similar question and stays separate: a theme
34
+ * slot is `<a:srgbClr val>`, which DrawingML gives no alpha channel, so the two differ in exactly the
35
+ * thing this one exists to add.
36
+ */
37
+ export declare function parseArgb(value: string): string | undefined;
17
38
  /**
18
39
  * Fill pattern kinds, as OOXML's `ST_PatternType` enumerates them. `none` is the
19
40
  * absence of a fill; `solid` paints the whole cell with the foreground colour (the
@@ -21,7 +42,7 @@ export interface Color {
21
42
  */
22
43
  export type FillPatternType = 'none' | 'solid' | 'gray125' | 'darkGray' | 'mediumGray' | 'lightGray' | 'gray0625' | 'darkHorizontal' | 'darkVertical' | 'darkDown' | 'darkUp' | 'darkGrid' | 'darkTrellis' | 'lightHorizontal' | 'lightVertical' | 'lightDown' | 'lightUp' | 'lightGrid' | 'lightTrellis';
23
44
  /** Narrow a raw `<patternFill patternType>` token to a known {@link FillPatternType}. */
24
- export declare function isFillPatternType(value: string): value is FillPatternType;
45
+ export declare const isFillPatternType: (value: string) => value is FillPatternType;
25
46
  /**
26
47
  * A pattern fill. For a `solid` fill the visible colour is the pattern *foreground*
27
48
  * (`fgColor`), OOXML's counter-intuitive rule, while `bgColor` is the automatic
@@ -64,7 +85,7 @@ export type Fill = PatternFill | GradientFill;
64
85
  */
65
86
  export type BorderStyle = 'thin' | 'medium' | 'thick' | 'dashed' | 'dotted' | 'double' | 'hair' | 'mediumDashed' | 'dashDot' | 'mediumDashDot' | 'dashDotDot' | 'mediumDashDotDot' | 'slantDashDot';
66
87
  /** Narrow a raw border-edge `style` attribute to a known {@link BorderStyle}. */
67
- export declare function isBorderStyle(value: string): value is BorderStyle;
88
+ export declare const isBorderStyle: (value: string) => value is BorderStyle;
68
89
  /** One edge of a cell border: its line style, and optionally the line colour. */
69
90
  export interface BorderEdge {
70
91
  readonly style: BorderStyle;
@@ -88,12 +109,12 @@ export interface Border {
88
109
  /** Vertical alignment of a font relative to the baseline (super/subscript). */
89
110
  export type FontVerticalAlignment = 'superscript' | 'subscript';
90
111
  /** Narrow a raw `<vertAlign val>` token to a known {@link FontVerticalAlignment}. */
91
- export declare function isFontVerticalAlignment(value: string): value is FontVerticalAlignment;
112
+ export declare const isFontVerticalAlignment: (value: string) => value is FontVerticalAlignment;
92
113
  /** The theme-font role a `<scheme val>` names: `"minor"`/`"major"` bind the font to whichever
93
114
  * face the workbook theme assigns that role, `"none"` leaves it a literal, unbound face. */
94
115
  export type FontScheme = 'minor' | 'major' | 'none';
95
116
  /** Narrow a raw `<scheme val>` token to a known {@link FontScheme}. */
96
- export declare function isFontScheme(value: string): value is FontScheme;
117
+ export declare const isFontScheme: (value: string) => value is FontScheme;
97
118
  /** A font, as it applies to a cell or a single rich-text run. Every facet is optional and
98
119
  * independent, like {@link Border}/{@link Alignment}/{@link Protection}: a font sets only the
99
120
  * facets it overrides (Excel's own default font backs the rest), so no consumer ever holds every
@@ -117,12 +138,12 @@ export interface Font {
117
138
  * back as no explicit horizontal alignment. */
118
139
  export type HorizontalAlignment = 'general' | 'left' | 'center' | 'right' | 'fill' | 'justify' | 'centerContinuous' | 'distributed';
119
140
  /** Narrow a raw `<alignment horizontal>` token to a known {@link HorizontalAlignment}. */
120
- export declare function isHorizontalAlignment(value: string): value is HorizontalAlignment;
141
+ export declare const isHorizontalAlignment: (value: string) => value is HorizontalAlignment;
121
142
  /** How a cell's content sits vertically within its bounds, as OOXML's `ST_VerticalAlignment`
122
143
  * enumerates it. */
123
144
  export type VerticalAlignment = 'top' | 'center' | 'bottom' | 'justify' | 'distributed';
124
145
  /** Narrow a raw `<alignment vertical>` token to a known {@link VerticalAlignment}. */
125
- export declare function isVerticalAlignment(value: string): value is VerticalAlignment;
146
+ export declare const isVerticalAlignment: (value: string) => value is VerticalAlignment;
126
147
  /**
127
148
  * A cell's alignment. Every facet is optional and independent; an absent facet means the cell
128
149
  * takes Excel's default for it. The boolean flags default to off, so a cell that never enabled
@@ -138,6 +159,49 @@ export interface Alignment {
138
159
  readonly shrinkToFit?: boolean;
139
160
  readonly readingOrder?: number;
140
161
  }
162
+ /**
163
+ * How one `<alignment>` facet encodes: which model key it is, what kind of value it carries, and the
164
+ * value that means "default", which the writer omits and the reader drops.
165
+ *
166
+ * Format-blind on purpose. `Alignment` is a core type and the layering gate forbids core importing a
167
+ * serialisation, so the table states what a facet *is* and each codec supplies the reading and the
168
+ * writing off the `kind`. That is where {@link SHEET_PROTECTION_FLAGS} sits and how it is consumed,
169
+ * and it is the shape the BIFF12 codec would want if alignment ever reaches it.
170
+ *
171
+ * The OOXML attribute name is the model key for all seven facets, so it is not restated here: a
172
+ * second list that is always identical is a second list to keep in step. A future facet whose
173
+ * attribute differs from its key is the one that would have to add the field.
174
+ */
175
+ export type AlignmentFacet = {
176
+ readonly key: 'horizontal' | 'vertical';
177
+ readonly kind: 'token';
178
+ /** The enumeration guard, and what to call it in the error when a value fails it. */
179
+ readonly isValid: (value: string) => boolean;
180
+ readonly label: string;
181
+ /** The token that means the OOXML default, expressed by omitting the attribute. */
182
+ readonly omit?: string;
183
+ } | {
184
+ readonly key: 'textRotation' | 'indent' | 'readingOrder';
185
+ readonly kind: 'number';
186
+ } | {
187
+ readonly key: 'wrapText' | 'shrinkToFit';
188
+ readonly kind: 'flag';
189
+ };
190
+ /**
191
+ * The seven `<alignment>` facets, declared once. Both directions key off this list, so a facet
192
+ * written but not read (it survives a re-write and vanishes on load) or read but not written (the
193
+ * reverse) is no longer something a reviewer has to notice: {@link EveryAlignmentFacetIsDeclared}
194
+ * makes a facet added to {@link Alignment} and forgotten here a compile error.
195
+ *
196
+ * In ECMA-376 CT_CellAlignment order, which is the order the writer emits.
197
+ */
198
+ export declare const ALIGNMENT_FACETS: readonly AlignmentFacet[];
199
+ /**
200
+ * Compile-time proof that {@link ALIGNMENT_FACETS} covers every {@link Alignment} facet. A facet
201
+ * added to the type without a table entry resolves this to that facet's name, which does not satisfy
202
+ * `never`, so the error names what is missing.
203
+ */
204
+ export type EveryAlignmentFacetIsDeclared = AssertNever<Exclude<keyof Alignment, (typeof ALIGNMENT_FACETS)[number]['key']>>;
141
205
  /**
142
206
  * A cell's protection state, enforced only when the worksheet itself is protected. The flags
143
207
  * do nothing on an unprotected sheet. `locked` defaults to TRUE in OOXML (every cell is locked
@@ -178,6 +242,13 @@ export declare const CELL_STYLE_FACETS: (keyof CellStyle)[];
178
242
  * it joins, the same single-point-of-change the cell path gets.
179
243
  */
180
244
  export declare function assignStyleFacets(target: CellStyle, source: Readonly<CellStyle>): void;
245
+ /**
246
+ * The {@link CellStyle} facets of `source` as a plain tuple of their own, for a source that carries
247
+ * more than the facets (a column's properties also hold width, hidden and outline state). The
248
+ * projection counterpart to {@link assignStyleFacets}, driven by the same list, so a facet added to
249
+ * the tuple reaches a `<col>` style without anyone remembering to widen a literal.
250
+ */
251
+ export declare function pickStyleFacets(source: Readonly<CellStyle>): CellStyle;
181
252
  /**
182
253
  * A named cell style: the OOXML `cellStyleXfs`/`cellStyles` layer. A spreadsheet applies a built-in
183
254
  * or custom style (e.g. "Normal", "Accent1") whose visual facets live in this shared, named layer