@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -0,0 +1,171 @@
1
+ import { XmlParseError } from '../../xml/errors.js';
2
+ import { NamespaceScope } from '../../xml/xml-namespaces.js';
3
+ import { tagRanges } from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
5
+ import { startTag } from '../../xml/xml.js';
6
+ const STRICT = 'http://purl.oclc.org/ooxml/';
7
+ const TRANSITIONAL = 'http://schemas.openxmlformats.org/';
8
+ const NAMESPACES = new Map([
9
+ 'drawingml/chart drawingml/2006/chart',
10
+ 'drawingml/chartDrawing drawingml/2006/chartDrawing',
11
+ 'drawingml/diagram drawingml/2006/diagram',
12
+ 'drawingml/lockedCanvas drawingml/2006/lockedCanvas',
13
+ 'drawingml/main drawingml/2006/main',
14
+ 'drawingml/picture drawingml/2006/picture',
15
+ 'drawingml/spreadsheetDrawing drawingml/2006/spreadsheetDrawing',
16
+ 'drawingml/wordprocessingDrawing drawingml/2006/wordprocessingDrawing',
17
+ 'officeDocument/bibliography officeDocument/2006/bibliography',
18
+ 'officeDocument/characteristics officeDocument/2006/characteristics',
19
+ 'officeDocument/customProperties officeDocument/2006/custom-properties',
20
+ 'officeDocument/customXml officeDocument/2006/customXml',
21
+ 'officeDocument/docPropsVTypes officeDocument/2006/docPropsVTypes',
22
+ 'officeDocument/extendedProperties officeDocument/2006/extended-properties',
23
+ 'officeDocument/math officeDocument/2006/math',
24
+ 'officeDocument/relationships officeDocument/2006/relationships',
25
+ 'officeDocument/sharedTypes officeDocument/2006/sharedTypes',
26
+ 'presentationml/main presentationml/2006/main',
27
+ 'schemaLibrary/main schemaLibrary/2006/main',
28
+ 'spreadsheetml/main spreadsheetml/2006/main',
29
+ 'wordprocessingml/main wordprocessingml/2006/main',
30
+ ].map((pair) => {
31
+ const [strict, transitional] = pair.split(' ');
32
+ return [`${STRICT}${strict}`, `${TRANSITIONAL}${transitional}`];
33
+ }));
34
+ const DRAWINGML = `${TRANSITIONAL}drawingml/2006/main`;
35
+ const PERCENTAGES = new Map([
36
+ [
37
+ DRAWINGML,
38
+ percentageTable(1000, {
39
+ a: 'alphaRepl',
40
+ amt: 'alphaModFix tint',
41
+ b: 'fillRect fillToRect scrgbClr srcRect tileRect',
42
+ baseline: 'defRPr endParaRPr rPr',
43
+ bright: 'lum',
44
+ contrast: 'lum',
45
+ d: 'ds',
46
+ endA: 'reflection',
47
+ endPos: 'reflection',
48
+ fontScale: 'normAutofit',
49
+ g: 'scrgbClr',
50
+ l: 'fillRect fillToRect srcRect tileRect',
51
+ lim: 'miter',
52
+ lnSpcReduction: 'normAutofit',
53
+ lum: 'hsl hslClr',
54
+ pos: 'gs',
55
+ r: 'fillRect fillToRect scrgbClr srcRect tileRect',
56
+ sat: 'hsl hslClr',
57
+ sp: 'ds',
58
+ stA: 'reflection',
59
+ stPos: 'reflection',
60
+ sx: 'outerShdw reflection tile xfrm',
61
+ sy: 'outerShdw reflection tile xfrm',
62
+ t: 'fillRect fillToRect srcRect tileRect',
63
+ thresh: 'alphaBiLevel biLevel',
64
+ tx: 'relOff',
65
+ ty: 'relOff',
66
+ val: 'alpha alphaMod alphaOff blue blueMod blueOff green greenMod greenOff hueMod lum lumMod ' +
67
+ 'lumOff red redMod redOff sat satMod satOff shade spcPct tint',
68
+ zoom: 'camera',
69
+ }),
70
+ ],
71
+ [
72
+ `${TRANSITIONAL}drawingml/2006/chart`,
73
+ percentageTable(1, {
74
+ val: 'bubbleScale depthPercent gapDepth gapWidth hPercent holeSize lblOffset overlap ' +
75
+ 'secondPieSize thickness',
76
+ }),
77
+ ],
78
+ [
79
+ `${TRANSITIONAL}drawingml/2006/diagram`,
80
+ percentageTable(1000, Object.fromEntries([
81
+ 'custLinFactNeighborX',
82
+ 'custLinFactNeighborY',
83
+ 'custLinFactX',
84
+ 'custLinFactY',
85
+ 'custRadScaleInc',
86
+ 'custRadScaleRad',
87
+ 'custScaleX',
88
+ 'custScaleY',
89
+ ].map((attribute) => [attribute, 'prSet']))),
90
+ ],
91
+ ]);
92
+ function percentageTable(scale, elementsByAttribute) {
93
+ const pairs = Object.entries(elementsByAttribute).flatMap(([attribute, elements]) => elements.split(' ').map((element) => `${element} ${attribute}`));
94
+ return { scale, pairs: new Set(pairs) };
95
+ }
96
+ const PERCENTAGE = /^-?[0-9]+(?:\.[0-9]+)?%$/;
97
+ const STRICT_BYTES = new TextEncoder().encode(STRICT);
98
+ export function transitionalPart(bytes, contentType) {
99
+ if (!isXmlContentType(contentType) || !includesBytes(bytes, STRICT_BYTES))
100
+ return bytes;
101
+ const source = new TextDecoder().decode(bytes);
102
+ let xml = '';
103
+ let copied = 0;
104
+ const scope = new NamespaceScope();
105
+ try {
106
+ for (const tag of tagRanges(source)) {
107
+ if (tag.close) {
108
+ scope.close();
109
+ continue;
110
+ }
111
+ scope.open(tag.attrs);
112
+ const translated = translatedAttributes(tag.name, tag.attrs, scope);
113
+ if (tag.selfClosing)
114
+ scope.close();
115
+ if (translated === undefined)
116
+ continue;
117
+ xml += source.slice(copied, tag.start) + startTag(tag.name, translated, tag.selfClosing);
118
+ copied = tag.end;
119
+ }
120
+ }
121
+ catch (error) {
122
+ if (error instanceof XmlParseError)
123
+ return bytes;
124
+ throw error;
125
+ }
126
+ return copied === 0 ? bytes : new TextEncoder().encode(xml + source.slice(copied));
127
+ }
128
+ function translatedAttributes(name, attrs, scope) {
129
+ let translated;
130
+ const set = (attribute, value) => {
131
+ translated ??= { ...attrs };
132
+ translated[attribute] = value;
133
+ };
134
+ const declared = scope.elementNamespace(name);
135
+ const namespace = (declared === undefined ? undefined : NAMESPACES.get(declared)) ?? declared;
136
+ const local = localName(name);
137
+ const percentages = PERCENTAGES.get(namespace ?? '');
138
+ for (const attribute in attrs) {
139
+ const value = attrs[attribute] ?? '';
140
+ if (attribute === 'xmlns' || attribute.startsWith('xmlns:')) {
141
+ const uri = NAMESPACES.get(value);
142
+ if (uri !== undefined)
143
+ set(attribute, uri);
144
+ }
145
+ else if (attribute === 'uri' && local === 'graphicData' && namespace === DRAWINGML) {
146
+ const uri = NAMESPACES.get(value);
147
+ if (uri !== undefined)
148
+ set(attribute, uri);
149
+ }
150
+ else if (percentages?.pairs.has(`${local} ${attribute}`) === true && PERCENTAGE.test(value)) {
151
+ set(attribute, String(Math.round(Number.parseFloat(value) * percentages.scale)));
152
+ }
153
+ }
154
+ return translated;
155
+ }
156
+ function isXmlContentType(contentType) {
157
+ const type = contentType.toLowerCase();
158
+ return type.endsWith('+xml') || type === 'application/xml' || type === 'text/xml';
159
+ }
160
+ function includesBytes(haystack, needle) {
161
+ const [first = 0] = needle;
162
+ const last = haystack.length - needle.length;
163
+ for (let i = haystack.indexOf(first); i !== -1 && i <= last; i = haystack.indexOf(first, i + 1)) {
164
+ let matched = 1;
165
+ while (matched < needle.length && haystack[i + matched] === needle[matched])
166
+ matched += 1;
167
+ if (matched === needle.length)
168
+ return true;
169
+ }
170
+ return false;
171
+ }
@@ -34,10 +34,8 @@ export declare function cellStyleTag(entry: {
34
34
  }): string;
35
35
  export declare function alignmentAttrs(alignment: Alignment): string;
36
36
  export declare function protectionAttrs(protection: Protection): string;
37
- export declare function tableStyleName(fragment: string): string;
38
37
  export declare function dxfXml(style: DifferentialStyle): string;
39
38
  export declare function borderXml(border: Border): string;
40
- export declare function fillSignature(fill: Fill): string;
41
39
  export declare function patternFillXml(fill: Fill, { solidBgFallback }: {
42
40
  solidBgFallback: boolean;
43
41
  }): string;
@@ -1,6 +1,5 @@
1
1
  import { ALIGNMENT_FACETS, isBorderStyle, isFillPatternType, } from '../../core/style.js';
2
- import { decodeEntities } from '../../xml/xml-scan.js';
3
- import { checkedToken, escapeAttr, escapeFormatCode, numberText } from '../../xml/xml.js';
2
+ import { checkedToken, escapeAttr, escapeFormatCode, nonDefaultNumAttr, numberText, } from '../../xml/xml.js';
4
3
  import { colorAttrs } from './color-xml.js';
5
4
  import { fontXml } from './font-xml.js';
6
5
  export const CUSTOM_NUMFMT_BASE = 164;
@@ -56,10 +55,9 @@ export function alignmentAttrs(alignment) {
56
55
  break;
57
56
  }
58
57
  case 'number': {
59
- const value = alignment[facet.key];
60
- if (value === undefined || value === 0)
61
- break;
62
- parts.push(`${facet.key}="${numberText(value)}"`);
58
+ const attr = nonDefaultNumAttr(facet.key, alignment[facet.key], 0);
59
+ if (attr !== '')
60
+ parts.push(attr.trimStart());
63
61
  break;
64
62
  }
65
63
  case 'flag':
@@ -78,9 +76,6 @@ export function protectionAttrs(protection) {
78
76
  parts.push('hidden="1"');
79
77
  return parts.join(' ');
80
78
  }
81
- export function tableStyleName(fragment) {
82
- return decodeEntities(/<tableStyle\b[^>]*\bname="([^"]*)"/.exec(fragment)?.[1] ?? '');
83
- }
84
79
  export function dxfXml(style) {
85
80
  const parts = [];
86
81
  if (style.font !== undefined) {
@@ -99,19 +94,16 @@ export function dxfXml(style) {
99
94
  }
100
95
  function gradientFillXml(fill) {
101
96
  const attrs = (fill.gradient === 'path' ? ' type="path"' : '') +
102
- (fill.degree ? ` degree="${numberText(fill.degree)}"` : '') +
103
- insetAttr('left', fill.left) +
104
- insetAttr('right', fill.right) +
105
- insetAttr('top', fill.top) +
106
- insetAttr('bottom', fill.bottom);
97
+ nonDefaultNumAttr('degree', fill.degree, 0) +
98
+ nonDefaultNumAttr('left', fill.left, 0) +
99
+ nonDefaultNumAttr('right', fill.right, 0) +
100
+ nonDefaultNumAttr('top', fill.top, 0) +
101
+ nonDefaultNumAttr('bottom', fill.bottom, 0);
107
102
  const stops = fill.stops
108
103
  .map((stop) => `<stop position="${numberText(stop.position)}"><color ${colorAttrs(stop.color)}/></stop>`)
109
104
  .join('');
110
105
  return `<gradientFill${attrs}>${stops}</gradientFill>`;
111
106
  }
112
- function insetAttr(name, value) {
113
- return value ? ` ${name}="${numberText(value)}"` : '';
114
- }
115
107
  export function borderXml(border) {
116
108
  const attrs = (border.diagonalUp ? ' diagonalUp="1"' : '') + (border.diagonalDown ? ' diagonalDown="1"' : '');
117
109
  return (`<border${attrs}>` +
@@ -130,18 +122,6 @@ function edgeXml(tag, edge) {
130
122
  return `<${tag} style="${style}"/>`;
131
123
  return `<${tag} style="${style}"><color ${colorAttrs(edge.color)}/></${tag}>`;
132
124
  }
133
- export function fillSignature(fill) {
134
- if (fill.type === 'gradient') {
135
- const stops = fill.stops.map((s) => `${s.position}:${colorSignature(s.color)}`).join(',');
136
- return `grad|${fill.gradient}|${fill.degree ?? ''}|${fill.left ?? ''}/${fill.right ?? ''}/${fill.top ?? ''}/${fill.bottom ?? ''}|${stops}`;
137
- }
138
- return `${fill.pattern}|${colorSignature(fill.fgColor)}|${colorSignature(fill.bgColor)}`;
139
- }
140
- function colorSignature(color) {
141
- if (color === undefined)
142
- return '';
143
- return `${color.argb ?? ''}/${color.theme ?? ''}/${color.tint ?? ''}/${color.indexed ?? ''}`;
144
- }
145
125
  export function patternFillXml(fill, { solidBgFallback }) {
146
126
  if (fill.type === 'gradient')
147
127
  return `<fill>${gradientFillXml(fill)}</fill>`;
@@ -101,6 +101,11 @@ export declare class StyleRegistry {
101
101
  * keeps every preserved reference pointing where it did.
102
102
  */
103
103
  differentialStyleId(style: DifferentialStyle): number;
104
+ /**
105
+ * The `<dxf>` fragment at a differential-style index, or `undefined` past the end of the table: what
106
+ * a conditional format in the extension form writes inline, since that form names no `dxfId`.
107
+ */
108
+ differentialStyleFragment(id: number): string | undefined;
104
109
  /** Serialise the accumulated table into a complete, valid styles.xml part. */
105
110
  toXml(): string;
106
111
  }
@@ -1,9 +1,9 @@
1
1
  import {} from '../../core/style.js';
2
2
  import { TABLE_STYLE_ELEMENT_TYPES } from '../../core/table-style.js';
3
- import { escapeAttr, escapeFormatCode, numAttr, XML_DECLARATION } from '../../xml/xml.js';
3
+ import { escapeAttr, escapeFormatCode, nonDefaultNumAttr, XML_DECLARATION } from '../../xml/xml.js';
4
4
  import { fontXml } from './font-xml.js';
5
5
  import { MARKUP_COMPATIBILITY_NS, SPREADSHEETML_NS } from './namespaces.js';
6
- import { alignmentAttrs, borderXml, cellStyleTag, CUSTOM_NUMFMT_BASE, DEFAULT_BORDER, DEFAULT_FONT_BODY, DEFAULT_FORMAT, dxfXml, fillSignature, formatSignature, isDefaultFormat, patternFillXml, protectionAttrs, tableStyleName, xfXml, } from './style-elements.js';
6
+ import { alignmentAttrs, borderXml, cellStyleTag, CUSTOM_NUMFMT_BASE, DEFAULT_BORDER, DEFAULT_FONT_BODY, DEFAULT_FORMAT, dxfXml, formatSignature, isDefaultFormat, patternFillXml, protectionAttrs, xfXml, } from './style-elements.js';
7
7
  const RESERVED_FILL_COUNT = 2;
8
8
  const RESERVED_FONT_COUNT = 1;
9
9
  const RESERVED_BORDER_COUNT = 1;
@@ -124,7 +124,7 @@ export class StyleRegistry {
124
124
  const element = style.elements[type];
125
125
  if (element === undefined)
126
126
  return [];
127
- const size = element.size === 1 ? '' : numAttr('size', element.size);
127
+ const size = nonDefaultNumAttr('size', element.size, 1);
128
128
  return [
129
129
  `<tableStyleElement type="${type}"${size} dxfId="${this.differentialStyleId(element)}"/>`,
130
130
  ];
@@ -137,8 +137,12 @@ export class StyleRegistry {
137
137
  const fragment = dxfXml(style);
138
138
  return this.#dxfs.intern(fragment, fragment);
139
139
  }
140
+ differentialStyleFragment(id) {
141
+ return this.#dxfs.entries[id];
142
+ }
140
143
  #internFill(fill) {
141
- return this.#fills.intern(fillSignature(fill), patternFillXml(fill, { solidBgFallback: true }));
144
+ const xml = patternFillXml(fill, { solidBgFallback: true });
145
+ return this.#fills.intern(xml, xml);
142
146
  }
143
147
  #internNumFmt(code) {
144
148
  return this.#numFmts.intern(code, code);
@@ -193,7 +197,9 @@ export class StyleRegistry {
193
197
  #tableStylesXml() {
194
198
  const { defaultTableStyle, defaultPivotStyle } = this.#tableStyles;
195
199
  const authored = this.#authoredTableStyles;
196
- const preserved = this.#tableStyles.styles.filter((fragment) => !authored.has(tableStyleName(fragment)));
200
+ const preserved = this.#tableStyles.styles
201
+ .filter((style) => !authored.has(style.name))
202
+ .map((style) => style.xml);
197
203
  const styles = [...preserved, ...authored.values()];
198
204
  if (styles.length === 0 && defaultTableStyle === undefined && defaultPivotStyle === undefined) {
199
205
  return '';
@@ -1,9 +1,17 @@
1
1
  import { type Table, type TableOptions } from '../../core/table.ts';
2
- export declare function tableXml(table: Table, id: number): string;
2
+ /**
3
+ * The table part. A column's calculated and totals formulas are stored with the function prefixes a
4
+ * cell formula takes, as Excel stores them; `formulaNames` is `formulaNamesInScope` for the table's
5
+ * sheet.
6
+ */
7
+ export declare function tableXml(table: Table, id: number, formulaNames: ReadonlySet<string>): string;
3
8
  /**
4
9
  * Parse a `<table>` part into the options that reconstruct it, or `undefined` when the XML is not a
5
10
  * usable table (no name, no ref, or no columns: Excel treats such a part as corrupt, so we drop it
6
11
  * rather than fabricate a degenerate table). Duplicate column names are not resolved here, since the
7
12
  * {@link Table} constructor disambiguates them, so authoring and loading share one implementation.
13
+ *
14
+ * A column's formulas shed their function prefixes as a cell formula's do, against `definedNames`,
15
+ * the workbook's names as `definedNameKeys` spells them.
8
16
  */
9
- export declare function parseTable(xml: string): TableOptions | undefined;
17
+ export declare function parseTable(xml: string, definedNames: ReadonlySet<string>): TableOptions | undefined;
@@ -1,21 +1,23 @@
1
- import { encodeAddress, MAX_COLUMN, tryDecodeRange } from '../../core/address.js';
2
- import { isTotalsRowFunction, } from '../../core/table.js';
1
+ import { encodeAddress, tryDecodeRange } from '../../core/address.js';
2
+ import { mangleFormula, unmangleFunctions } from '../../core/formula.js';
3
+ import { isTotalsRowFunction, TABLE_STYLE_FLAGS, } from '../../core/table.js';
3
4
  import { numInteger } from '../../xml/xml-attrs.js';
4
5
  import { parseXml, TextCapture } from '../../xml/xml-read.js';
5
- import { boolPresent, localName } from '../../xml/xml-scan.js';
6
+ import { boolTristate, localName } from '../../xml/xml-scan.js';
6
7
  import { boolAttr, checkedToken, escapeAttr, escapeText, XML_DECLARATION } from '../../xml/xml.js';
7
8
  import { NS } from './relationships.js';
8
- export function tableXml(table, id) {
9
+ export function tableXml(table, id, formulaNames) {
9
10
  const name = escapeAttr(table.name);
10
- const displayName = escapeAttr(table.displayName);
11
11
  const headerRowCount = table.headerRow ? '' : ' headerRowCount="0"';
12
12
  const totals = table.totalsRow
13
13
  ? ' totalsRowCount="1"'
14
14
  : boolAttr('totalsRowShown', table.totalsRowShown);
15
15
  const autoFilter = table.autoFilterRef !== undefined ? `<autoFilter ref="${table.autoFilterRef}"/>` : '';
16
- const columns = table.columns.map((column, i) => tableColumnXml(column, i + 1)).join('');
16
+ const columns = table.columns
17
+ .map((column, i) => tableColumnXml(column, i + 1, formulaNames))
18
+ .join('');
17
19
  return (XML_DECLARATION +
18
- `<table xmlns="${NS.main}" id="${id}" name="${name}" displayName="${displayName}" ` +
20
+ `<table xmlns="${NS.main}" id="${id}" name="${name}" displayName="${name}" ` +
19
21
  `ref="${table.range}"${headerRowCount}${totals}>` +
20
22
  autoFilter +
21
23
  `<tableColumns count="${table.columns.length}">${columns}</tableColumns>` +
@@ -30,14 +32,10 @@ function tableStyleInfoXml(style) {
30
32
  let attrs = '';
31
33
  if (style.name !== undefined)
32
34
  attrs += ` name="${escapeAttr(style.name)}"`;
33
- attrs +=
34
- boolAttr('showFirstColumn', style.showFirstColumn) +
35
- boolAttr('showLastColumn', style.showLastColumn) +
36
- boolAttr('showRowStripes', style.showRowStripes) +
37
- boolAttr('showColumnStripes', style.showColumnStripes);
35
+ attrs += TABLE_STYLE_FLAGS.map((flag) => boolAttr(flag, style[flag])).join('');
38
36
  return `<tableStyleInfo${attrs}/>`;
39
37
  }
40
- function tableColumnXml(column, id) {
38
+ function tableColumnXml(column, id, formulaNames) {
41
39
  let attrs = `id="${id}" name="${escapeAttr(column.name)}"`;
42
40
  if (column.totalsRowLabel !== undefined) {
43
41
  attrs += ` totalsRowLabel="${escapeAttr(column.totalsRowLabel)}"`;
@@ -45,14 +43,21 @@ function tableColumnXml(column, id) {
45
43
  if (column.totalsRowFunction !== undefined) {
46
44
  attrs += ` totalsRowFunction="${checkedToken(column.totalsRowFunction, isTotalsRowFunction, 'totals row function')}"`;
47
45
  }
46
+ let children = '';
47
+ if (column.calculatedColumnFormula !== undefined) {
48
+ const stored = mangleFormula(column.calculatedColumnFormula, formulaNames);
49
+ children += `<calculatedColumnFormula>${escapeText(stored)}</calculatedColumnFormula>`;
50
+ }
48
51
  if (column.totalsRowFormula !== undefined) {
49
- return `<tableColumn ${attrs}><totalsRowFormula>${escapeText(column.totalsRowFormula)}</totalsRowFormula></tableColumn>`;
52
+ const stored = mangleFormula(column.totalsRowFormula, formulaNames);
53
+ children += `<totalsRowFormula>${escapeText(stored)}</totalsRowFormula>`;
50
54
  }
51
- return `<tableColumn ${attrs}/>`;
55
+ return children === ''
56
+ ? `<tableColumn ${attrs}/>`
57
+ : `<tableColumn ${attrs}>${children}</tableColumn>`;
52
58
  }
53
- export function parseTable(xml) {
59
+ export function parseTable(xml, definedNames) {
54
60
  let name;
55
- let displayName;
56
61
  let ref;
57
62
  let headerRowCount = 1;
58
63
  let totalsRowCount = 0;
@@ -60,18 +65,17 @@ export function parseTable(xml) {
60
65
  let style;
61
66
  let hasAutoFilter = false;
62
67
  const columns = [];
63
- const totalsFormula = new TextCapture('totalsRowFormula');
68
+ let currentColumn;
69
+ const formula = new TextCapture(['totalsRowFormula', 'calculatedColumnFormula']);
64
70
  parseXml(xml, {
65
71
  onOpen(elementName, attrs, selfClosing) {
66
72
  switch (localName(elementName)) {
67
73
  case 'table':
68
- name = attrs.name ?? attrs.displayName;
69
- displayName = attrs.displayName ?? attrs.name;
74
+ name = attrs.displayName ?? attrs.name;
70
75
  ref = attrs.ref;
71
76
  headerRowCount = numInteger(attrs.headerRowCount, 0) ?? headerRowCount;
72
77
  totalsRowCount = numInteger(attrs.totalsRowCount, 0) ?? totalsRowCount;
73
- if (attrs.totalsRowShown !== undefined)
74
- totalsRowShown = boolPresent(attrs.totalsRowShown);
78
+ totalsRowShown = boolTristate(attrs.totalsRowShown);
75
79
  break;
76
80
  case 'autoFilter':
77
81
  hasAutoFilter = true;
@@ -80,22 +84,21 @@ export function parseTable(xml) {
80
84
  const captured = {};
81
85
  if (attrs.name !== undefined)
82
86
  captured.name = attrs.name;
83
- if (attrs.showFirstColumn !== undefined)
84
- captured.showFirstColumn = boolPresent(attrs.showFirstColumn);
85
- if (attrs.showLastColumn !== undefined)
86
- captured.showLastColumn = boolPresent(attrs.showLastColumn);
87
- if (attrs.showRowStripes !== undefined)
88
- captured.showRowStripes = boolPresent(attrs.showRowStripes);
89
- if (attrs.showColumnStripes !== undefined) {
90
- captured.showColumnStripes = boolPresent(attrs.showColumnStripes);
87
+ for (const flag of TABLE_STYLE_FLAGS) {
88
+ const value = boolTristate(attrs[flag]);
89
+ if (value !== undefined)
90
+ captured[flag] = value;
91
91
  }
92
92
  style = captured;
93
93
  break;
94
94
  }
95
95
  case 'tableColumn': {
96
- if (attrs.name === undefined)
96
+ if (attrs.name === undefined) {
97
+ currentColumn = undefined;
97
98
  break;
99
+ }
98
100
  const column = { name: attrs.name };
101
+ currentColumn = column;
99
102
  if (attrs.totalsRowLabel !== undefined)
100
103
  column.totalsRowLabel = attrs.totalsRowLabel;
101
104
  if (attrs.totalsRowFunction !== undefined &&
@@ -106,20 +109,24 @@ export function parseTable(xml) {
106
109
  break;
107
110
  }
108
111
  case 'totalsRowFormula':
109
- totalsFormula.open('totalsRowFormula', selfClosing);
112
+ case 'calculatedColumnFormula':
113
+ formula.open(localName(elementName), selfClosing);
110
114
  break;
111
115
  }
112
116
  },
113
117
  onText(text) {
114
- totalsFormula.text(text);
118
+ formula.text(text);
115
119
  },
116
120
  onClose(elementName) {
117
- const formula = totalsFormula.close(localName(elementName));
118
- if (formula === undefined)
121
+ const local = localName(elementName);
122
+ const text = formula.close(local);
123
+ if (text === undefined || currentColumn === undefined)
119
124
  return;
120
- const column = columns[columns.length - 1];
121
- if (column !== undefined)
122
- column.totalsRowFormula = formula;
125
+ const formulaText = unmangleFunctions(text, definedNames);
126
+ if (local === 'totalsRowFormula')
127
+ currentColumn.totalsRowFormula = formulaText;
128
+ else
129
+ currentColumn.calculatedColumnFormula = formulaText;
123
130
  },
124
131
  });
125
132
  if (name === undefined || ref === undefined || columns.length === 0)
@@ -127,17 +134,17 @@ export function parseTable(xml) {
127
134
  const decoded = tryDecodeRange(ref);
128
135
  if (decoded === undefined)
129
136
  return undefined;
130
- const { top, left, bottom } = decoded;
131
- if (top === undefined || left === undefined || bottom === undefined)
137
+ const { top, left, bottom, right } = decoded;
138
+ if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
132
139
  return undefined;
133
- if (left + columns.length - 1 > MAX_COLUMN)
140
+ }
141
+ if (columns.length !== right - left + 1)
134
142
  return undefined;
135
143
  const headerRow = headerRowCount !== 0;
136
144
  const totalsRow = totalsRowCount > 0;
137
145
  const dataRows = bottom - top + 1 - (headerRow ? 1 : 0) - (totalsRow ? 1 : 0);
138
146
  const options = {
139
147
  name,
140
- displayName: displayName ?? name,
141
148
  ref: encodeAddress(left, top),
142
149
  columns,
143
150
  rowCount: Math.max(0, dataRows),
@@ -12,6 +12,12 @@ import { type ThemeColorScheme, type ThemeColorSlot, type ThemeFontScheme, type
12
12
  export declare function parseThemeColorScheme(themeXml: string): ThemeColorScheme;
13
13
  /** Extract the major/minor latin typefaces from a theme part's `<fontScheme>`. */
14
14
  export declare function parseThemeFontScheme(themeXml: string): ThemeFontScheme;
15
+ /** {@link parseThemeColorScheme} and {@link parseThemeFontScheme} from one scan of the part, for the
16
+ * reader, which wants both. */
17
+ export declare function parseThemeSchemes(themeXml: string): {
18
+ readonly colors: ThemeColorScheme;
19
+ readonly fonts: ThemeFontScheme;
20
+ };
15
21
  declare const SCHEME_ELEMENT_ORDER: readonly ["dk1", "lt1", "dk2", "lt2", "accent1", "accent2", "accent3", "accent4", "accent5", "accent6", "hlink", "folHlink"];
16
22
  /**
17
23
  * The half of the proof the list above owes: `satisfies` covers "no invented slot", this covers "no
@@ -1,7 +1,7 @@
1
- import { DEFAULT_THEME_COLOR_SCHEME, isThemeColorSlot, normalizeThemeColor, } from '../../core/theme.js';
1
+ import { DEFAULT_THEME_COLOR_SCHEME, isThemeColorSlot, isThemeRgb, } from '../../core/theme.js';
2
2
  import { elementRange, parseXml } from '../../xml/xml-read.js';
3
3
  import { localName } from '../../xml/xml-scan.js';
4
- import { escapeAttr } from '../../xml/xml.js';
4
+ import { startTag } from '../../xml/xml.js';
5
5
  const COLOR_ELEMENTS = new Set(['srgbClr', 'sysClr']);
6
6
  function readThemeScheme(themeXml) {
7
7
  const colors = {};
@@ -12,7 +12,7 @@ function readThemeScheme(themeXml) {
12
12
  let slot;
13
13
  let fontSlot;
14
14
  parseXml(themeXml, {
15
- onOpen(name, attrs, _selfClosing, scope) {
15
+ onOpen(name, attrs) {
16
16
  const local = localName(name);
17
17
  if (local === 'theme') {
18
18
  const colon = name.indexOf(':');
@@ -29,9 +29,9 @@ function readThemeScheme(themeXml) {
29
29
  }
30
30
  else if (slot !== undefined && COLOR_ELEMENTS.has(local)) {
31
31
  const value = local === 'sysClr' ? attrs.lastClr : attrs.val;
32
- if (value !== undefined && /^[0-9a-fA-F]{6}$/.test(value))
33
- colors[slot] = value;
34
- elements[slot] = emptyElement(name, attrs);
32
+ if (value !== undefined && isThemeRgb(value))
33
+ colors[slot] = value.toUpperCase();
34
+ elements[slot] = startTag(name, attrs, true);
35
35
  slot = undefined;
36
36
  }
37
37
  return;
@@ -45,7 +45,6 @@ function readThemeScheme(themeXml) {
45
45
  fonts[fontSlot] ??= attrs.typeface;
46
46
  }
47
47
  }
48
- void scope;
49
48
  },
50
49
  onClose(name) {
51
50
  const local = localName(name);
@@ -69,18 +68,16 @@ function readThemeScheme(themeXml) {
69
68
  scheme.minor = fonts.minor;
70
69
  return { colors, elements, fonts: scheme, prefix };
71
70
  }
72
- function emptyElement(name, attrs) {
73
- let out = `<${name}`;
74
- for (const key in attrs)
75
- out += ` ${key}="${escapeAttr(attrs[key] ?? '')}"`;
76
- return `${out}/>`;
77
- }
78
71
  export function parseThemeColorScheme(themeXml) {
79
72
  return readThemeScheme(themeXml).colors;
80
73
  }
81
74
  export function parseThemeFontScheme(themeXml) {
82
75
  return readThemeScheme(themeXml).fonts;
83
76
  }
77
+ export function parseThemeSchemes(themeXml) {
78
+ const { colors, fonts } = readThemeScheme(themeXml);
79
+ return { colors, fonts };
80
+ }
84
81
  const SCHEME_ELEMENT_ORDER = [
85
82
  'dk1',
86
83
  'lt1',
@@ -105,7 +102,7 @@ export function applyThemeOverrides(baseXml, overrides) {
105
102
  const body = SCHEME_ELEMENT_ORDER.map((slot) => {
106
103
  const authored = colors[slot];
107
104
  const inner = authored !== undefined
108
- ? `<${prefix}srgbClr val="${normalizeThemeColor(authored)}"/>`
105
+ ? `<${prefix}srgbClr val="${authored}"/>`
109
106
  : (sourceElements[slot] ??
110
107
  `<${prefix}srgbClr val="${DEFAULT_THEME_COLOR_SCHEME[slot]}"/>`);
111
108
  return `<${prefix}${slot}>${inner}</${prefix}${slot}>`;
@@ -118,11 +115,11 @@ export function applyThemeOverrides(baseXml, overrides) {
118
115
  const found = elementRange(baseXml, [which, 'latin']);
119
116
  if (found === undefined)
120
117
  return;
121
- const attrs = { ...found.attrs, typeface };
122
- let rendered = `<${found.name}`;
123
- for (const key in attrs)
124
- rendered += ` ${key}="${escapeAttr(attrs[key] ?? '')}"`;
125
- edits.push({ start: found.start, end: found.end, text: `${rendered}/>` });
118
+ edits.push({
119
+ start: found.start,
120
+ end: found.end,
121
+ text: startTag(found.name, { ...found.attrs, typeface }, true),
122
+ });
126
123
  };
127
124
  if (major !== undefined)
128
125
  latin('majorFont', major);
@@ -78,8 +78,13 @@ export declare function buildCommentThreads(messages: readonly ParsedThreadedCom
78
78
  *
79
79
  * A thread with no messages writes nothing: it has neither text to say nor a head id for its replies and
80
80
  * its legacy fallback to hang off.
81
+ *
82
+ * A message with no author is written under the null GUID, Excel's own spelling for one. `personById` is
83
+ * the workbook's registry, and a message naming an id it does not hold is refused.
84
+ *
85
+ * @throws {AuthoringError} if a message's author is not a registered person.
81
86
  */
82
- export declare function threadedCommentsXml(threads: readonly CommentThread[]): string;
87
+ export declare function threadedCommentsXml(threads: readonly CommentThread[], personById: (id: string) => Person | undefined): string;
83
88
  /**
84
89
  * Serialise the workbook's identity registry into `xl/persons/person.xml`: singular and unnumbered,
85
90
  * unlike the per-sheet thread parts.