@shbernal/ts-xlsx 1.3.1 → 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 (186) 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 +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -1,6 +1,7 @@
1
1
  import { encodeAddress } from '../../core/address.js';
2
+ import { isPageOrder, isPageOrientation, } from '../../core/page-setup.js';
2
3
  import { SHEET_PROTECTION_FLAGS } from '../../core/protection.js';
3
- import { attr, boolAttr, escapeAttr, escapeText, numberText } from '../../xml/xml.js';
4
+ import { boolAttr, checkedToken, escapeAttr, escapeSpreadsheetText, numAttr, numberText, } from '../../xml/xml.js';
4
5
  import { colorAttrs } from './color-xml.js';
5
6
  export function sheetViewsXml(view, active) {
6
7
  const selected = active ? ' tabSelected="1"' : '';
@@ -99,7 +100,7 @@ export function headerFooterXml(hf) {
99
100
  if (differentFirst)
100
101
  attrs += ' differentFirst="1"';
101
102
  const body = children
102
- .map(({ tag, key }) => `<${tag}>${escapeText(hf[key])}</${tag}>`)
103
+ .map(({ tag, key }) => `<${tag}>${escapeSpreadsheetText(hf[key])}</${tag}>`)
103
104
  .join('');
104
105
  return `<headerFooter${attrs}>${body}</headerFooter>`;
105
106
  }
@@ -127,12 +128,16 @@ export function pageMarginsXml(margins) {
127
128
  return `<pageMargins ${attrs}/>`;
128
129
  }
129
130
  export function pageSetupXml(pageSetup, printerSettingsRelId) {
130
- const attrs = attr('paperSize', pageSetup.paperSize) +
131
- attr('scale', pageSetup.scale) +
132
- attr('fitToWidth', pageSetup.fitToWidth) +
133
- attr('fitToHeight', pageSetup.fitToHeight) +
134
- (pageSetup.pageOrder !== undefined ? ` pageOrder="${pageSetup.pageOrder}"` : '') +
135
- (pageSetup.orientation !== undefined ? ` orientation="${pageSetup.orientation}"` : '') +
131
+ const attrs = numAttr('paperSize', pageSetup.paperSize) +
132
+ numAttr('scale', pageSetup.scale) +
133
+ numAttr('fitToWidth', pageSetup.fitToWidth) +
134
+ numAttr('fitToHeight', pageSetup.fitToHeight) +
135
+ (pageSetup.pageOrder === undefined
136
+ ? ''
137
+ : ` pageOrder="${checkedToken(pageSetup.pageOrder, isPageOrder, 'page order')}"`) +
138
+ (pageSetup.orientation === undefined
139
+ ? ''
140
+ : ` orientation="${checkedToken(pageSetup.orientation, isPageOrientation, 'page orientation')}"`) +
136
141
  (printerSettingsRelId !== null ? ` r:id="${printerSettingsRelId}"` : '');
137
142
  return attrs === '' ? '' : `<pageSetup${attrs}/>`;
138
143
  }
@@ -141,8 +146,7 @@ export function pageBreaksXml(breaks, element) {
141
146
  return '';
142
147
  const brks = breaks
143
148
  .map((brk) => {
144
- const maxAttr = brk.max !== undefined ? ` max="${brk.max}"` : '';
145
- return `<brk id="${brk.id}"${maxAttr} man="1"/>`;
149
+ return `<brk${numAttr('id', brk.id)}${numAttr('max', brk.max)} man="1"/>`;
146
150
  })
147
151
  .join('');
148
152
  return `<${element} count="${breaks.length}" manualBreakCount="${breaks.length}">${brks}</${element}>`;
@@ -1,22 +1,10 @@
1
- import { type Alignment, type Border, type DifferentialStyle, type Fill, type Font, type NamedCellStyle, type Protection, type TableStyleTable } from '../../core/style.ts';
1
+ import { type DifferentialStyle, type Font, type NamedCellStyle, type TableStyleTable } from '../../core/style.ts';
2
2
  import { type TableStyle } from '../../core/table-style.ts';
3
- /** A cell's style facets as the writer composes them: cell overrides atop row/column defaults. */
4
- export interface CellStyle {
5
- readonly fill?: Fill | undefined;
6
- readonly numFmt?: string | undefined;
7
- readonly font?: Font | undefined;
8
- readonly border?: Border | undefined;
9
- readonly alignment?: Alignment | undefined;
10
- readonly protection?: Protection | undefined;
11
- /** The quote-prefix flag — an attribute on the xf, not a shared sub-table entry. */
12
- readonly quotePrefix?: boolean | undefined;
13
- /** The `xfId` link into `cellStyleXfs` — the named cell style this format inherits from (0 = Normal). */
14
- readonly xfId?: number | undefined;
15
- }
3
+ import type { XfStyle } from '../style/xf-style.ts';
16
4
  /** What a {@link StyleRegistry} needs from its workbook before any style is interned. */
17
5
  export interface StyleRegistryOptions {
18
6
  /**
19
- * The font every cell naming none of its own renders in emitted as id 0. Take it from
7
+ * The font every cell naming none of its own renders in, emitted as id 0. Take it from
20
8
  * {@link Workbook.defaultFont}, which resolves the authored/declared/theme chain and guarantees a
21
9
  * complete entry. Omitted, the registry falls back to the Office default.
22
10
  */
@@ -28,7 +16,7 @@ export interface StyleRegistryOptions {
28
16
  *
29
17
  * The reader flattens font 0 onto every xf that names it, so a cell that merely inherited the
30
18
  * file's default arrives carrying it as a concrete face. That face is an artefact of reading, not
31
- * an authored intent in the source file the cell said nothing about its font so it must
19
+ * an authored intent (in the source file the cell said nothing about its font) so it must
32
20
  * collapse back to id 0 and follow the new default rather than pin itself to the old one through a
33
21
  * custom entry.
34
22
  */
@@ -41,7 +29,7 @@ export declare class StyleRegistry {
41
29
  * The `<cellXfs>` index for a composed cell/row/column style. A style with no facet needs
42
30
  * no entry and resolves to the default xf 0, so its owner emits no `s` attribute at all.
43
31
  */
44
- styleId(style: CellStyle): number;
32
+ styleId(style: XfStyle): number;
45
33
  /**
46
34
  * Seed the named cell styles (`<cellStyleXfs>`/`<cellStyles>`) read from a file, in place of the
47
35
  * lone default, interning each style's facets into the shared sub-tables so its `fillId`/`fontId`/…
@@ -59,7 +47,7 @@ export declare class StyleRegistry {
59
47
  * rule's `dxfId`, and every `<tableStyleElement dxfId="…">` inside a preserved `<tableStyle>` (see
60
48
  * {@link seedTableStyles}). Those constructs are carried as opaque XML precisely *because* the
61
49
  * indices they name do not move. Renumbering, reordering, or de-duplicating the seeded entries
62
- * would silently re-point every one of them at a different format a change no schema check and no
50
+ * would silently re-point every one of them at a different format: a change no schema check and no
63
51
  * round-trip of our own can catch, because the file stays perfectly valid and merely renders wrong.
64
52
  */
65
53
  seedDifferentialStyles(fragments: readonly string[]): void;
@@ -88,13 +76,13 @@ export declare class StyleRegistry {
88
76
  * {@link seedTableStyles}, whose preserved definitions these append after.
89
77
  *
90
78
  * A definition here **replaces** a preserved one of the same name. Two `<tableStyle>` elements
91
- * sharing a name is ambiguous a table's `tableStyleInfo/@name` would reach whichever a consumer
92
- * happened to index first so authoring a name the source already used is read as overriding it,
79
+ * sharing a name is ambiguous, since a table's `tableStyleInfo/@name` would reach whichever a
80
+ * consumer happened to index first, so authoring a name the source already used is read as overriding it,
93
81
  * which is what asking for it means.
94
82
  */
95
83
  addTableStyle(style: TableStyle): void;
96
84
  /**
97
- * Intern an authored differential style, returning the `<dxfs>` index that references it a
85
+ * Intern an authored differential style, returning the `<dxfs>` index that references it: a
98
86
  * conditional-formatting rule's `dxfId`, or a table style element's. Identical styles collapse to
99
87
  * one entry, whichever feature asked for them, so a highlight rule and a table style's header row
100
88
  * painted the same way share a single `<dxf>`.
@@ -107,4 +95,3 @@ export declare class StyleRegistry {
107
95
  toXml(): string;
108
96
  }
109
97
  export declare function fontXml(font: Font, nameTag?: 'name' | 'rFont'): string;
110
- export declare function dxfXml(style: DifferentialStyle): string;
@@ -1,8 +1,7 @@
1
1
  import { isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isHorizontalAlignment, isNamedUnderlineStyle, isVerticalAlignment, } from '../../core/style.js';
2
2
  import { TABLE_STYLE_ELEMENT_TYPES } from '../../core/table-style.js';
3
- import { AuthoringError } from '../../errors.js';
4
- import { escapeAttr, XML_DECLARATION } from '../../xml/xml.js';
5
3
  import { decodeEntities } from '../../xml/xml-read.js';
4
+ import { assertRepresentable, checkedToken, escapeAttr, numberText, XML_DECLARATION, } from '../../xml/xml.js';
6
5
  import { colorAttrs } from './color-xml.js';
7
6
  import { MARKUP_COMPATIBILITY_NS, SPREADSHEETML_NS } from './namespaces.js';
8
7
  const RESERVED_FILL_COUNT = 2;
@@ -21,6 +20,36 @@ const DEFAULT_FORMAT = {
21
20
  quotePrefix: false,
22
21
  xfId: 0,
23
22
  };
23
+ class InternTable {
24
+ #entries = [];
25
+ #idByKey = new Map();
26
+ #base;
27
+ constructor(base) {
28
+ this.#base = base;
29
+ }
30
+ get entries() {
31
+ return this.#entries;
32
+ }
33
+ get size() {
34
+ return this.#entries.length;
35
+ }
36
+ intern(key, entry) {
37
+ const existing = this.#idByKey.get(key);
38
+ if (existing !== undefined)
39
+ return existing;
40
+ const id = this.#base + this.#entries.length;
41
+ this.#entries.push(entry);
42
+ this.#idByKey.set(key, id);
43
+ return id;
44
+ }
45
+ seed(entry) {
46
+ const id = this.#base + this.#entries.length;
47
+ this.#entries.push(entry);
48
+ if (!this.#idByKey.has(entry))
49
+ this.#idByKey.set(entry, id);
50
+ return id;
51
+ }
52
+ }
24
53
  export class StyleRegistry {
25
54
  #defaultFontBody;
26
55
  #font0Bodies;
@@ -31,22 +60,17 @@ export class StyleRegistry {
31
60
  ? [this.#defaultFontBody]
32
61
  : [this.#defaultFontBody, fontXml(options.declaredDefaultFont)]);
33
62
  }
34
- #fillXml = [];
35
- #fillIdBySignature = new Map();
36
- #numFmtCodes = [];
37
- #numFmtIdByCode = new Map();
38
- #fontXml = [];
39
- #fontIdBySignature = new Map();
40
- #borderXml = [];
41
- #borderIdBySignature = new Map();
63
+ #fills = new InternTable(RESERVED_FILL_COUNT);
64
+ #numFmts = new InternTable(CUSTOM_NUMFMT_BASE);
65
+ #fonts = new InternTable(RESERVED_FONT_COUNT);
66
+ #borders = new InternTable(RESERVED_BORDER_COUNT);
42
67
  #formats = [DEFAULT_FORMAT];
43
68
  #xfIndexBySignature = new Map();
44
69
  #cellStyleXfs = [DEFAULT_FORMAT];
45
70
  #cellStyleNames = [
46
71
  { name: 'Normal', builtinId: 0, xfId: 0 },
47
72
  ];
48
- #dxfXml = [];
49
- #dxfIndexByFragment = new Map();
73
+ #dxfs = new InternTable(0);
50
74
  #indexedColors = [];
51
75
  #mruColors = [];
52
76
  #tableStyles = { styles: [] };
@@ -94,12 +118,8 @@ export class StyleRegistry {
94
118
  });
95
119
  }
96
120
  seedDifferentialStyles(fragments) {
97
- for (const fragment of fragments) {
98
- const index = this.#dxfXml.length;
99
- this.#dxfXml.push(fragment);
100
- if (!this.#dxfIndexByFragment.has(fragment))
101
- this.#dxfIndexByFragment.set(fragment, index);
102
- }
121
+ for (const fragment of fragments)
122
+ this.#dxfs.seed(fragment);
103
123
  }
104
124
  seedIndexedColors(fragments) {
105
125
  this.#indexedColors.length = 0;
@@ -128,69 +148,36 @@ export class StyleRegistry {
128
148
  }
129
149
  differentialStyleId(style) {
130
150
  const fragment = dxfXml(style);
131
- let index = this.#dxfIndexByFragment.get(fragment);
132
- if (index === undefined) {
133
- index = this.#dxfXml.length;
134
- this.#dxfXml.push(fragment);
135
- this.#dxfIndexByFragment.set(fragment, index);
136
- }
137
- return index;
151
+ return this.#dxfs.intern(fragment, fragment);
138
152
  }
139
153
  #internFill(fill) {
140
- const signature = fillSignature(fill);
141
- let id = this.#fillIdBySignature.get(signature);
142
- if (id === undefined) {
143
- id = RESERVED_FILL_COUNT + this.#fillXml.length;
144
- this.#fillXml.push(patternFillXml(fill, { solidBgFallback: true }));
145
- this.#fillIdBySignature.set(signature, id);
146
- }
147
- return id;
154
+ return this.#fills.intern(fillSignature(fill), patternFillXml(fill, { solidBgFallback: true }));
148
155
  }
149
156
  #internNumFmt(code) {
150
- let id = this.#numFmtIdByCode.get(code);
151
- if (id === undefined) {
152
- id = CUSTOM_NUMFMT_BASE + this.#numFmtCodes.length;
153
- this.#numFmtCodes.push(code);
154
- this.#numFmtIdByCode.set(code, id);
155
- }
156
- return id;
157
+ return this.#numFmts.intern(code, code);
157
158
  }
158
159
  #internFont(font) {
159
160
  const xml = fontXml(font);
160
161
  if (xml === '' || this.#font0Bodies.has(xml))
161
162
  return 0;
162
- let id = this.#fontIdBySignature.get(xml);
163
- if (id === undefined) {
164
- id = RESERVED_FONT_COUNT + this.#fontXml.length;
165
- this.#fontXml.push(`<font>${xml}</font>`);
166
- this.#fontIdBySignature.set(xml, id);
167
- }
168
- return id;
163
+ return this.#fonts.intern(xml, `<font>${xml}</font>`);
169
164
  }
170
165
  #internBorder(border) {
171
166
  const xml = borderXml(border);
172
- if (xml === DEFAULT_BORDER)
173
- return 0;
174
- let id = this.#borderIdBySignature.get(xml);
175
- if (id === undefined) {
176
- id = RESERVED_BORDER_COUNT + this.#borderXml.length;
177
- this.#borderXml.push(xml);
178
- this.#borderIdBySignature.set(xml, id);
179
- }
180
- return id;
167
+ return xml === DEFAULT_BORDER ? 0 : this.#borders.intern(xml, xml);
181
168
  }
182
169
  toXml() {
183
- const fillCount = RESERVED_FILL_COUNT + this.#fillXml.length;
170
+ const fillCount = RESERVED_FILL_COUNT + this.#fills.size;
184
171
  const fills = '<fill><patternFill patternType="none"/></fill>' +
185
172
  '<fill><patternFill patternType="gray125"/></fill>' +
186
- this.#fillXml.join('');
173
+ this.#fills.entries.join('');
187
174
  const cellXfs = this.#formats.map((format) => xfXml(format, format.xfId)).join('');
188
175
  const cellStyleXfs = this.#cellStyleXfs.map((format) => xfXml(format, null)).join('');
189
176
  const cellStyles = this.#cellStyleNames.map(cellStyleTag).join('');
190
- const fontCount = RESERVED_FONT_COUNT + this.#fontXml.length;
191
- const fonts = `<font>${this.#defaultFontBody}</font>` + this.#fontXml.join('');
192
- const borderCount = RESERVED_BORDER_COUNT + this.#borderXml.length;
193
- const borders = DEFAULT_BORDER + this.#borderXml.join('');
177
+ const fontCount = RESERVED_FONT_COUNT + this.#fonts.size;
178
+ const fonts = `<font>${this.#defaultFontBody}</font>${this.#fonts.entries.join('')}`;
179
+ const borderCount = RESERVED_BORDER_COUNT + this.#borders.size;
180
+ const borders = DEFAULT_BORDER + this.#borders.entries.join('');
194
181
  return (XML_DECLARATION +
195
182
  `<styleSheet xmlns="${SPREADSHEETML_NS}"${this.#foreignNamespaceAttrs()}>` +
196
183
  this.#numFmtsXml() +
@@ -245,17 +232,17 @@ export class StyleRegistry {
245
232
  return `<colors>${indexed}${mru}</colors>`;
246
233
  }
247
234
  #dxfsXml() {
248
- if (this.#dxfXml.length === 0)
235
+ if (this.#dxfs.size === 0)
249
236
  return '<dxfs count="0"/>';
250
- return `<dxfs count="${this.#dxfXml.length}">${this.#dxfXml.join('')}</dxfs>`;
237
+ return `<dxfs count="${this.#dxfs.size}">${this.#dxfs.entries.join('')}</dxfs>`;
251
238
  }
252
239
  #numFmtsXml() {
253
- if (this.#numFmtCodes.length === 0)
240
+ if (this.#numFmts.size === 0)
254
241
  return '';
255
- const entries = this.#numFmtCodes
242
+ const entries = this.#numFmts.entries
256
243
  .map((code, i) => `<numFmt numFmtId="${CUSTOM_NUMFMT_BASE + i}" formatCode="${escapeFormatCode(code)}"/>`)
257
244
  .join('');
258
- return `<numFmts count="${this.#numFmtCodes.length}">${entries}</numFmts>`;
245
+ return `<numFmts count="${this.#numFmts.size}">${entries}</numFmts>`;
259
246
  }
260
247
  }
261
248
  function isDefaultFormat(format) {
@@ -292,12 +279,6 @@ function cellStyleTag(entry) {
292
279
  const builtin = entry.builtinId === undefined ? '' : ` builtinId="${entry.builtinId}"`;
293
280
  return `<cellStyle name="${escapeAttr(entry.name)}" xfId="${entry.xfId}"${builtin}/>`;
294
281
  }
295
- function checkedToken(value, isValid, kind) {
296
- if (!isValid(value)) {
297
- throw new AuthoringError(`Invalid ${kind} ${JSON.stringify(value)}: not a value the OOXML enumeration allows`);
298
- }
299
- return value;
300
- }
301
282
  function alignmentAttrs(alignment) {
302
283
  const parts = [];
303
284
  if (alignment.horizontal !== undefined && alignment.horizontal !== 'general') {
@@ -307,17 +288,17 @@ function alignmentAttrs(alignment) {
307
288
  parts.push(`vertical="${checkedToken(alignment.vertical, isVerticalAlignment, 'vertical alignment')}"`);
308
289
  }
309
290
  if (alignment.textRotation !== undefined && alignment.textRotation !== 0) {
310
- parts.push(`textRotation="${numberAttr(alignment.textRotation)}"`);
291
+ parts.push(`textRotation="${numberText(alignment.textRotation)}"`);
311
292
  }
312
293
  if (alignment.wrapText)
313
294
  parts.push('wrapText="1"');
314
295
  if (alignment.indent !== undefined && alignment.indent !== 0) {
315
- parts.push(`indent="${numberAttr(alignment.indent)}"`);
296
+ parts.push(`indent="${numberText(alignment.indent)}"`);
316
297
  }
317
298
  if (alignment.shrinkToFit)
318
299
  parts.push('shrinkToFit="1"');
319
300
  if (alignment.readingOrder !== undefined && alignment.readingOrder !== 0) {
320
- parts.push(`readingOrder="${numberAttr(alignment.readingOrder)}"`);
301
+ parts.push(`readingOrder="${numberText(alignment.readingOrder)}"`);
321
302
  }
322
303
  return parts.join(' ');
323
304
  }
@@ -349,20 +330,20 @@ export function fontXml(font, nameTag = 'name') {
349
330
  parts.push(`<vertAlign val="${checkedToken(font.vertAlign, isFontVerticalAlignment, 'font vertical alignment')}"/>`);
350
331
  }
351
332
  if (font.size !== undefined)
352
- parts.push(`<sz val="${numberAttr(font.size)}"/>`);
333
+ parts.push(`<sz val="${numberText(font.size)}"/>`);
353
334
  if (font.color !== undefined)
354
335
  parts.push(`<color ${colorAttrs(font.color)}/>`);
355
336
  if (font.name !== undefined)
356
337
  parts.push(`<${nameTag} val="${escapeAttr(font.name)}"/>`);
357
338
  if (font.family !== undefined)
358
- parts.push(`<family val="${numberAttr(font.family)}"/>`);
339
+ parts.push(`<family val="${numberText(font.family)}"/>`);
359
340
  if (font.charset !== undefined)
360
- parts.push(`<charset val="${numberAttr(font.charset)}"/>`);
341
+ parts.push(`<charset val="${numberText(font.charset)}"/>`);
361
342
  if (font.scheme !== undefined && font.scheme !== 'none')
362
343
  parts.push(`<scheme val="${checkedToken(font.scheme, isFontScheme, 'font scheme')}"/>`);
363
344
  return parts.join('');
364
345
  }
365
- export function dxfXml(style) {
346
+ function dxfXml(style) {
366
347
  const parts = [];
367
348
  if (style.font !== undefined) {
368
349
  const font = fontXml(style.font);
@@ -380,18 +361,18 @@ export function dxfXml(style) {
380
361
  }
381
362
  function gradientFillXml(fill) {
382
363
  const attrs = (fill.gradient === 'path' ? ' type="path"' : '') +
383
- (fill.degree ? ` degree="${numberAttr(fill.degree)}"` : '') +
364
+ (fill.degree ? ` degree="${numberText(fill.degree)}"` : '') +
384
365
  insetAttr('left', fill.left) +
385
366
  insetAttr('right', fill.right) +
386
367
  insetAttr('top', fill.top) +
387
368
  insetAttr('bottom', fill.bottom);
388
369
  const stops = fill.stops
389
- .map((stop) => `<stop position="${numberAttr(stop.position)}"><color ${colorAttrs(stop.color)}/></stop>`)
370
+ .map((stop) => `<stop position="${numberText(stop.position)}"><color ${colorAttrs(stop.color)}/></stop>`)
390
371
  .join('');
391
372
  return `<gradientFill${attrs}>${stops}</gradientFill>`;
392
373
  }
393
374
  function insetAttr(name, value) {
394
- return value ? ` ${name}="${numberAttr(value)}"` : '';
375
+ return value ? ` ${name}="${numberText(value)}"` : '';
395
376
  }
396
377
  function underlineXml(underline) {
397
378
  if (underline === undefined || underline === false || underline === 'none')
@@ -400,12 +381,6 @@ function underlineXml(underline) {
400
381
  return '<u/>';
401
382
  return `<u val="${checkedToken(underline, isNamedUnderlineStyle, 'underline style')}"/>`;
402
383
  }
403
- function numberAttr(value) {
404
- if (!Number.isFinite(value)) {
405
- throw new AuthoringError(`cannot serialise a non-finite font metric (${value})`);
406
- }
407
- return String(value);
408
- }
409
384
  function borderXml(border) {
410
385
  const attrs = (border.diagonalUp ? ' diagonalUp="1"' : '') + (border.diagonalDown ? ' diagonalDown="1"' : '');
411
386
  return (`<border${attrs}>` +
@@ -425,6 +400,7 @@ function edgeXml(tag, edge) {
425
400
  return `<${tag} style="${style}"><color ${colorAttrs(edge.color)}/></${tag}>`;
426
401
  }
427
402
  function escapeFormatCode(code) {
403
+ assertRepresentable(code);
428
404
  return code
429
405
  .replace(/&/g, '&amp;')
430
406
  .replace(/</g, '&lt;')
@@ -2,8 +2,8 @@ import { type Table, type TableOptions } from '../../core/table.ts';
2
2
  export declare function tableXml(table: Table, id: number): string;
3
3
  /**
4
4
  * Parse a `<table>` part into the options that reconstruct it, or `undefined` when the XML is not a
5
- * usable table (no name, no ref, or no columns Excel treats such a part as corrupt, so we drop it
6
- * rather than fabricate a degenerate table). Duplicate column names are not resolved here the
5
+ * usable table (no name, no ref, or no columns: Excel treats such a part as corrupt, so we drop it
6
+ * rather than fabricate a degenerate table). Duplicate column names are not resolved here, since the
7
7
  * {@link Table} constructor disambiguates them, so authoring and loading share one implementation.
8
8
  */
9
9
  export declare function parseTable(xml: string): TableOptions | undefined;
@@ -1,7 +1,7 @@
1
- import { decodeRange, encodeAddress } from '../../core/address.js';
1
+ import { encodeAddress, tryDecodeRange } from '../../core/address.js';
2
2
  import { isTotalsRowFunction, } from '../../core/table.js';
3
+ import { boolPresent, localName, numInteger, parseXml, TextCapture } from '../../xml/xml-read.js';
3
4
  import { boolAttr, escapeAttr, escapeText, XML_DECLARATION } from '../../xml/xml.js';
4
- import { localName, parseXml } from '../../xml/xml-read.js';
5
5
  import { NS } from './relationships.js';
6
6
  export function tableXml(table, id) {
7
7
  const name = escapeAttr(table.name);
@@ -55,9 +55,6 @@ function tableColumnXml(column, id) {
55
55
  }
56
56
  return `<tableColumn ${attrs}/>`;
57
57
  }
58
- function parseOoxmlBool(value) {
59
- return value !== '0' && value !== 'false';
60
- }
61
58
  export function parseTable(xml) {
62
59
  let name;
63
60
  let displayName;
@@ -68,21 +65,18 @@ export function parseTable(xml) {
68
65
  let style;
69
66
  let hasAutoFilter = false;
70
67
  const columns = [];
71
- let inTotalsFormula = false;
72
- let totalsFormula = '';
68
+ const totalsFormula = new TextCapture('totalsRowFormula');
73
69
  parseXml(xml, {
74
- onOpen(elementName, attrs) {
70
+ onOpen(elementName, attrs, selfClosing) {
75
71
  switch (localName(elementName)) {
76
72
  case 'table':
77
73
  name = attrs.name ?? attrs.displayName;
78
74
  displayName = attrs.displayName ?? attrs.name;
79
75
  ref = attrs.ref;
80
- if (attrs.headerRowCount !== undefined)
81
- headerRowCount = Number(attrs.headerRowCount);
82
- if (attrs.totalsRowCount !== undefined)
83
- totalsRowCount = Number(attrs.totalsRowCount);
76
+ headerRowCount = numInteger(attrs.headerRowCount, 0) ?? headerRowCount;
77
+ totalsRowCount = numInteger(attrs.totalsRowCount, 0) ?? totalsRowCount;
84
78
  if (attrs.totalsRowShown !== undefined)
85
- totalsRowShown = parseOoxmlBool(attrs.totalsRowShown);
79
+ totalsRowShown = boolPresent(attrs.totalsRowShown);
86
80
  break;
87
81
  case 'autoFilter':
88
82
  hasAutoFilter = true;
@@ -92,13 +86,13 @@ export function parseTable(xml) {
92
86
  if (attrs.name !== undefined)
93
87
  captured.name = attrs.name;
94
88
  if (attrs.showFirstColumn !== undefined)
95
- captured.showFirstColumn = parseOoxmlBool(attrs.showFirstColumn);
89
+ captured.showFirstColumn = boolPresent(attrs.showFirstColumn);
96
90
  if (attrs.showLastColumn !== undefined)
97
- captured.showLastColumn = parseOoxmlBool(attrs.showLastColumn);
91
+ captured.showLastColumn = boolPresent(attrs.showLastColumn);
98
92
  if (attrs.showRowStripes !== undefined)
99
- captured.showRowStripes = parseOoxmlBool(attrs.showRowStripes);
93
+ captured.showRowStripes = boolPresent(attrs.showRowStripes);
100
94
  if (attrs.showColumnStripes !== undefined) {
101
- captured.showColumnStripes = parseOoxmlBool(attrs.showColumnStripes);
95
+ captured.showColumnStripes = boolPresent(attrs.showColumnStripes);
102
96
  }
103
97
  style = captured;
104
98
  break;
@@ -117,27 +111,28 @@ export function parseTable(xml) {
117
111
  break;
118
112
  }
119
113
  case 'totalsRowFormula':
120
- inTotalsFormula = true;
121
- totalsFormula = '';
114
+ totalsFormula.open('totalsRowFormula', selfClosing);
122
115
  break;
123
116
  }
124
117
  },
125
118
  onText(text) {
126
- if (inTotalsFormula)
127
- totalsFormula += text;
119
+ totalsFormula.text(text);
128
120
  },
129
121
  onClose(elementName) {
130
- if (localName(elementName) !== 'totalsRowFormula')
122
+ const formula = totalsFormula.close(localName(elementName));
123
+ if (formula === undefined)
131
124
  return;
132
- inTotalsFormula = false;
133
125
  const column = columns[columns.length - 1];
134
126
  if (column !== undefined)
135
- column.totalsRowFormula = totalsFormula;
127
+ column.totalsRowFormula = formula;
136
128
  },
137
129
  });
138
130
  if (name === undefined || ref === undefined || columns.length === 0)
139
131
  return undefined;
140
- const { top, left, bottom } = decodeRange(ref);
132
+ const decoded = tryDecodeRange(ref);
133
+ if (decoded === undefined)
134
+ return undefined;
135
+ const { top, left, bottom } = decoded;
141
136
  if (top === undefined || left === undefined || bottom === undefined)
142
137
  return undefined;
143
138
  const headerRow = headerRowCount !== 0;
@@ -0,0 +1,37 @@
1
+ import { type ThemeColorScheme, type ThemeFontScheme, type ThemeOverrides } from '../../core/theme.ts';
2
+ /**
3
+ * Extract the colour scheme from a theme part. Returns only the slots the part actually declares in a
4
+ * colour model this reader understands; an unrecognised one is dropped rather than guessed at, so a
5
+ * caller can tell "the theme says nothing here" from "the theme says black".
6
+ *
7
+ * Reads the `<a:clrScheme>` block alone. A theme carries a font scheme and a format scheme too, but
8
+ * neither participates in resolving a colour, and scanning the whole part would let a `<a:srgbClr>`
9
+ * buried in a gradient stop masquerade as a scheme slot.
10
+ */
11
+ export declare function parseThemeColorScheme(themeXml: string): ThemeColorScheme;
12
+ /** Extract the major/minor latin typefaces from a theme part's `<a:fontScheme>`. */
13
+ export declare function parseThemeFontScheme(themeXml: string): ThemeFontScheme;
14
+ /**
15
+ * Apply authored colour/font overrides to a theme part, returning the new part text.
16
+ *
17
+ * Surgical by design: the base part rides through untouched except for the `<a:clrScheme>` and
18
+ * `<a:fontScheme>` blocks, and within those, only what the caller actually named. The format scheme,
19
+ * the gradients, line styles and effect styles that make a theme look like a theme, is left exactly
20
+ * as it was, because nobody hand-authors `fillStyleLst` gradient stops from a spreadsheet API and
21
+ * regenerating it would replace a designer's work with the Office default.
22
+ *
23
+ * A slot the caller did not override keeps its **verbatim source element**, not a re-serialisation of
24
+ * its value. That matters for `dk1`/`lt1`, which Excel writes as `<a:sysClr val="windowText"
25
+ * lastClr="000000"/>`: rewriting those as `<a:srgbClr>` would pin them to one machine's resolved
26
+ * window colours and break dark-mode following.
27
+ */
28
+ export declare function applyThemeOverrides(baseXml: string, overrides: ThemeOverrides): string;
29
+ /**
30
+ * The theme part a workbook with no theme of its own ships: the standard Office theme.
31
+ *
32
+ * A spreadsheet must carry one even when nobody configured it: the stylesheet's own default font
33
+ * references `theme="1"`, which a consumer can only resolve against this part, so the two travel
34
+ * together. It is also the base {@link applyThemeOverrides} authors on top of when a workbook was
35
+ * built from scratch rather than read from a file.
36
+ */
37
+ export declare const DEFAULT_THEME_XML: string;