@shbernal/ts-xlsx 1.2.0 → 2.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 (161) hide show
  1. package/README.md +5 -0
  2. package/dist/core/address.d.ts +49 -2
  3. package/dist/core/address.js +31 -3
  4. package/dist/core/autofilter.d.ts +2 -2
  5. package/dist/core/cell.d.ts +17 -16
  6. package/dist/core/cell.js +3 -7
  7. package/dist/core/color-resolution.d.ts +7 -7
  8. package/dist/core/column.d.ts +8 -9
  9. package/dist/core/column.js +2 -4
  10. package/dist/core/comment-thread.d.ts +32 -23
  11. package/dist/core/comment-thread.js +4 -4
  12. package/dist/core/conditional-formatting-overlay.d.ts +1 -1
  13. package/dist/core/conditional-formatting.d.ts +3 -3
  14. package/dist/core/data-validation-overlay.d.ts +1 -1
  15. package/dist/core/data-validation.d.ts +3 -3
  16. package/dist/core/date.d.ts +3 -3
  17. package/dist/core/formula.d.ts +6 -6
  18. package/dist/core/grid-edits.js +3 -3
  19. package/dist/core/image.d.ts +37 -2
  20. package/dist/core/image.js +6 -0
  21. package/dist/core/internal.d.ts +9 -1
  22. package/dist/core/limits.d.ts +4 -4
  23. package/dist/core/merge.d.ts +6 -12
  24. package/dist/core/merge.js +0 -3
  25. package/dist/core/page-setup.d.ts +4 -4
  26. package/dist/core/pivot-table.d.ts +2 -4
  27. package/dist/core/pivot-table.js +5 -5
  28. package/dist/core/preserved.d.ts +3 -3
  29. package/dist/core/protection.d.ts +4 -4
  30. package/dist/core/range.d.ts +16 -14
  31. package/dist/core/range.js +5 -5
  32. package/dist/core/row-input.d.ts +2 -8
  33. package/dist/core/row-input.js +2 -2
  34. package/dist/core/row.d.ts +7 -8
  35. package/dist/core/row.js +2 -4
  36. package/dist/core/style.d.ts +14 -14
  37. package/dist/core/table-style.d.ts +9 -9
  38. package/dist/core/table-style.js +1 -1
  39. package/dist/core/table.d.ts +30 -42
  40. package/dist/core/table.js +16 -10
  41. package/dist/core/theme.d.ts +13 -43
  42. package/dist/core/theme.js +2 -148
  43. package/dist/core/value.d.ts +7 -7
  44. package/dist/core/workbook-protection.d.ts +2 -2
  45. package/dist/core/workbook-styles.d.ts +26 -0
  46. package/dist/core/workbook-styles.js +48 -0
  47. package/dist/core/workbook-theme.d.ts +25 -0
  48. package/dist/core/workbook-theme.js +49 -0
  49. package/dist/core/workbook-vba.d.ts +17 -0
  50. package/dist/core/workbook-vba.js +79 -0
  51. package/dist/core/workbook.d.ts +141 -83
  52. package/dist/core/workbook.js +74 -133
  53. package/dist/core/worksheet-comments.d.ts +9 -0
  54. package/dist/core/worksheet-comments.js +52 -0
  55. package/dist/core/worksheet-model.d.ts +4 -4
  56. package/dist/core/worksheet-model.js +1 -0
  57. package/dist/core/worksheet-pictures.d.ts +29 -0
  58. package/dist/core/worksheet-pictures.js +42 -0
  59. package/dist/core/worksheet.d.ts +94 -77
  60. package/dist/core/worksheet.js +36 -95
  61. package/dist/customui/errors.d.ts +1 -1
  62. package/dist/customui/ribbon.d.ts +3 -3
  63. package/dist/entries/core.d.ts +4 -4
  64. package/dist/entries/core.js +1 -1
  65. package/dist/entries/vba.d.ts +1 -1
  66. package/dist/entries/vba.js +1 -1
  67. package/dist/entries/xlsx.d.ts +1 -0
  68. package/dist/entries/xlsx.js +1 -0
  69. package/dist/errors.d.ts +12 -12
  70. package/dist/io/csv/read.js +1 -2
  71. package/dist/io/csv/write.d.ts +8 -3
  72. package/dist/io/csv/write.js +17 -2
  73. package/dist/io/opc/errors.d.ts +6 -6
  74. package/dist/io/opc/inflate.d.ts +1 -1
  75. package/dist/io/opc/namespaces.d.ts +1 -1
  76. package/dist/io/opc/read-opc.d.ts +19 -3
  77. package/dist/io/opc/read-opc.js +14 -8
  78. package/dist/io/opc/read-options.d.ts +2 -2
  79. package/dist/io/opc/sniff-format.d.ts +2 -2
  80. package/dist/io/opc/zip-mtime.d.ts +5 -0
  81. package/dist/io/opc/zip-mtime.js +1 -0
  82. package/dist/io/style/xf-style.d.ts +38 -6
  83. package/dist/io/style/xf-style.js +22 -0
  84. package/dist/io/xlsb/errors.d.ts +1 -1
  85. package/dist/io/xlsb/formula.d.ts +4 -4
  86. package/dist/io/xlsb/primitives.d.ts +8 -8
  87. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  88. package/dist/io/xlsb/read-styles.js +2 -20
  89. package/dist/io/xlsb/read.d.ts +2 -2
  90. package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
  91. package/dist/io/xlsx/cell-accumulator.js +111 -30
  92. package/dist/io/xlsx/cell-value.d.ts +2 -2
  93. package/dist/io/xlsx/cell-value.js +3 -3
  94. package/dist/io/xlsx/color-xml.js +11 -17
  95. package/dist/io/xlsx/comments.d.ts +5 -5
  96. package/dist/io/xlsx/comments.js +13 -6
  97. package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
  98. package/dist/io/xlsx/conditional-formatting.js +7 -16
  99. package/dist/io/xlsx/data-validation.d.ts +2 -2
  100. package/dist/io/xlsx/data-validation.js +1 -1
  101. package/dist/io/xlsx/edit-vba.js +2 -1
  102. package/dist/io/xlsx/errors.d.ts +3 -3
  103. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  104. package/dist/io/xlsx/hyperlinks.js +5 -5
  105. package/dist/io/xlsx/images.d.ts +1 -1
  106. package/dist/io/xlsx/images.js +8 -8
  107. package/dist/io/xlsx/namespaces.d.ts +2 -2
  108. package/dist/io/xlsx/package-plan.js +5 -2
  109. package/dist/io/xlsx/read-pivot.js +2 -5
  110. package/dist/io/xlsx/read-rows.d.ts +13 -13
  111. package/dist/io/xlsx/read-rows.js +19 -55
  112. package/dist/io/xlsx/read-shared-strings.js +7 -5
  113. package/dist/io/xlsx/read-styles.d.ts +3 -3
  114. package/dist/io/xlsx/read-styles.js +40 -65
  115. package/dist/io/xlsx/read-worksheet.js +67 -115
  116. package/dist/io/xlsx/read.d.ts +3 -5
  117. package/dist/io/xlsx/read.js +94 -71
  118. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  119. package/dist/io/xlsx/rich-runs.js +1 -1
  120. package/dist/io/xlsx/rich-text.d.ts +1 -1
  121. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  122. package/dist/io/xlsx/sheet-properties.js +5 -4
  123. package/dist/io/xlsx/styles.d.ts +8 -9
  124. package/dist/io/xlsx/styles.js +4 -3
  125. package/dist/io/xlsx/tables.d.ts +2 -2
  126. package/dist/io/xlsx/tables.js +8 -13
  127. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  128. package/dist/io/xlsx/theme-xml.js +142 -0
  129. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  130. package/dist/io/xlsx/threaded-comments.js +9 -21
  131. package/dist/io/xlsx/workbook-xml.d.ts +1 -1
  132. package/dist/io/xlsx/workbook-xml.js +6 -2
  133. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  134. package/dist/io/xlsx/worksheet-xml.js +7 -12
  135. package/dist/io/xlsx/write-stream.d.ts +15 -15
  136. package/dist/io/xlsx/write-stream.js +5 -3
  137. package/dist/io/xlsx/write.d.ts +12 -7
  138. package/dist/io/xlsx/write.js +10 -6
  139. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  140. package/dist/vba/bytes.d.ts +12 -0
  141. package/dist/vba/bytes.js +40 -0
  142. package/dist/vba/cfb-writer.d.ts +1 -1
  143. package/dist/vba/cfb.d.ts +1 -1
  144. package/dist/vba/cfb.js +15 -51
  145. package/dist/vba/codepage.d.ts +2 -2
  146. package/dist/vba/errors.d.ts +2 -2
  147. package/dist/vba/index.d.ts +2 -2
  148. package/dist/vba/index.js +2 -2
  149. package/dist/vba/ms-ovba.d.ts +1 -1
  150. package/dist/vba/ms-ovba.js +1 -3
  151. package/dist/vba/project-editor.d.ts +9 -9
  152. package/dist/vba/project-editor.js +2 -11
  153. package/dist/vba/project.d.ts +5 -5
  154. package/dist/vba/project.js +1 -10
  155. package/dist/vba/vba-encoding.d.ts +1 -1
  156. package/dist/xml/errors.d.ts +1 -1
  157. package/dist/xml/xml-read.d.ts +38 -9
  158. package/dist/xml/xml-read.js +23 -0
  159. package/dist/xml/xml.d.ts +31 -9
  160. package/dist/xml/xml.js +23 -3
  161. package/package.json +41 -35
@@ -0,0 +1,142 @@
1
+ import { DEFAULT_THEME_COLOR_SCHEME, normalizeThemeColor, } from '../../core/theme.js';
2
+ import { decodeEntities } from '../../xml/xml-read.js';
3
+ import { escapeAttr } from '../../xml/xml.js';
4
+ const SCHEME_SLOT = new RegExp('<a:(dk1|lt1|dk2|lt2|accent[1-6]|hlink|folHlink)\\b[^>]*>' + '\\s*<a:(srgbClr|sysClr)\\b([^>]*)>', 'g');
5
+ export function parseThemeColorScheme(themeXml) {
6
+ const block = /<a:clrScheme\b[^>]*>([\s\S]*?)<\/a:clrScheme>/.exec(themeXml);
7
+ if (block === null)
8
+ return {};
9
+ const scheme = {};
10
+ for (const match of (block[1] ?? '').matchAll(SCHEME_SLOT)) {
11
+ const slot = match[1];
12
+ const attrs = match[3] ?? '';
13
+ const source = match[2] === 'sysClr' ? /\blastClr="([^"]*)"/ : /\bval="([^"]*)"/;
14
+ const value = source.exec(attrs)?.[1];
15
+ if (value !== undefined && /^[0-9a-fA-F]{6}$/.test(value))
16
+ scheme[slot] = value;
17
+ }
18
+ return scheme;
19
+ }
20
+ export function parseThemeFontScheme(themeXml) {
21
+ const block = /<a:fontScheme\b[^>]*>([\s\S]*?)<\/a:fontScheme>/.exec(themeXml);
22
+ if (block === null)
23
+ return {};
24
+ const face = (which) => {
25
+ const font = new RegExp(`<a:${which}\\b[^>]*>([\\s\\S]*?)</a:${which}>`).exec(block[1] ?? '');
26
+ const typeface = /<a:latin\b[^>]*\btypeface="([^"]*)"/.exec(font?.[1] ?? '')?.[1];
27
+ return typeface === undefined ? undefined : decodeEntities(typeface);
28
+ };
29
+ const scheme = {};
30
+ const major = face('majorFont');
31
+ const minor = face('minorFont');
32
+ if (major !== undefined)
33
+ scheme.major = major;
34
+ if (minor !== undefined)
35
+ scheme.minor = minor;
36
+ return scheme;
37
+ }
38
+ const SCHEME_ELEMENT_ORDER = [
39
+ 'dk1',
40
+ 'lt1',
41
+ 'dk2',
42
+ 'lt2',
43
+ 'accent1',
44
+ 'accent2',
45
+ 'accent3',
46
+ 'accent4',
47
+ 'accent5',
48
+ 'accent6',
49
+ 'hlink',
50
+ 'folHlink',
51
+ ];
52
+ export function applyThemeOverrides(baseXml, overrides) {
53
+ let xml = baseXml;
54
+ const colors = overrides.colors ?? {};
55
+ if (Object.keys(colors).length > 0) {
56
+ const sourceElements = parseThemeColorElements(baseXml);
57
+ const body = SCHEME_ELEMENT_ORDER.map((slot) => {
58
+ const authored = colors[slot];
59
+ const inner = authored !== undefined
60
+ ? `<a:srgbClr val="${normalizeThemeColor(authored)}"/>`
61
+ : (sourceElements[slot] ??
62
+ `<a:srgbClr val="${DEFAULT_THEME_COLOR_SCHEME[slot]}"/>`);
63
+ return `<a:${slot}>${inner}</a:${slot}>`;
64
+ }).join('');
65
+ xml = replaceBlockBody(xml, 'clrScheme', body);
66
+ }
67
+ const { major, minor } = overrides.fonts ?? {};
68
+ if (major !== undefined)
69
+ xml = replaceLatinTypeface(xml, 'majorFont', major);
70
+ if (minor !== undefined)
71
+ xml = replaceLatinTypeface(xml, 'minorFont', minor);
72
+ return xml;
73
+ }
74
+ function parseThemeColorElements(themeXml) {
75
+ const block = /<a:clrScheme\b[^>]*>([\s\S]*?)<\/a:clrScheme>/.exec(themeXml);
76
+ if (block === null)
77
+ return {};
78
+ const elements = {};
79
+ const pattern = /<a:(dk1|lt1|dk2|lt2|accent[1-6]|hlink|folHlink)>([\s\S]*?)<\/a:\1>|<a:(dk1|lt1|dk2|lt2|accent[1-6]|hlink|folHlink)\/>/g;
80
+ for (const match of (block[1] ?? '').matchAll(pattern)) {
81
+ const slot = (match[1] ?? match[3]);
82
+ const inner = match[2];
83
+ if (inner !== undefined && inner !== '')
84
+ elements[slot] = inner;
85
+ }
86
+ return elements;
87
+ }
88
+ function replaceBlockBody(xml, name, body) {
89
+ const pattern = new RegExp(`(<a:${name}\\b[^>]*>)[\\s\\S]*?(</a:${name}>)`);
90
+ return xml.replace(pattern, (_all, open, close) => `${open}${body}${close}`);
91
+ }
92
+ function replaceLatinTypeface(xml, which, typeface) {
93
+ const pattern = new RegExp(`(<a:${which}\\b[^>]*>[\\s\\S]*?<a:latin\\b)[^>]*(/>)`);
94
+ return xml.replace(pattern, (_all, open, close) => `${open} typeface="${escapeAttr(typeface)}"${close}`);
95
+ }
96
+ export const DEFAULT_THEME_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
97
+ '<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">' +
98
+ '<a:themeElements>' +
99
+ '<a:clrScheme name="Office">' +
100
+ '<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>' +
101
+ '<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>' +
102
+ '<a:dk2><a:srgbClr val="44546A"/></a:dk2>' +
103
+ '<a:lt2><a:srgbClr val="E7E6E6"/></a:lt2>' +
104
+ '<a:accent1><a:srgbClr val="4472C4"/></a:accent1>' +
105
+ '<a:accent2><a:srgbClr val="ED7D31"/></a:accent2>' +
106
+ '<a:accent3><a:srgbClr val="A5A5A5"/></a:accent3>' +
107
+ '<a:accent4><a:srgbClr val="FFC000"/></a:accent4>' +
108
+ '<a:accent5><a:srgbClr val="5B9BD5"/></a:accent5>' +
109
+ '<a:accent6><a:srgbClr val="70AD47"/></a:accent6>' +
110
+ '<a:hlink><a:srgbClr val="0563C1"/></a:hlink>' +
111
+ '<a:folHlink><a:srgbClr val="954F72"/></a:folHlink>' +
112
+ '</a:clrScheme>' +
113
+ '<a:fontScheme name="Office">' +
114
+ '<a:majorFont><a:latin typeface="Calibri Light" panose="020F0302020204030204"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>' +
115
+ '<a:minorFont><a:latin typeface="Calibri" panose="020F0502020204030204"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>' +
116
+ '</a:fontScheme>' +
117
+ '<a:fmtScheme name="Office">' +
118
+ '<a:fillStyleLst>' +
119
+ '<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>' +
120
+ '<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill>' +
121
+ '<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill>' +
122
+ '</a:fillStyleLst>' +
123
+ '<a:lnStyleLst>' +
124
+ '<a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>' +
125
+ '<a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>' +
126
+ '<a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln>' +
127
+ '</a:lnStyleLst>' +
128
+ '<a:effectStyleLst>' +
129
+ '<a:effectStyle><a:effectLst/></a:effectStyle>' +
130
+ '<a:effectStyle><a:effectLst/></a:effectStyle>' +
131
+ '<a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle>' +
132
+ '</a:effectStyleLst>' +
133
+ '<a:bgFillStyleLst>' +
134
+ '<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>' +
135
+ '<a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill>' +
136
+ '<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill>' +
137
+ '</a:bgFillStyleLst>' +
138
+ '</a:fmtScheme>' +
139
+ '</a:themeElements>' +
140
+ '<a:objectDefaults/>' +
141
+ '<a:extraClrSchemeLst/>' +
142
+ '</a:theme>';
@@ -1,5 +1,5 @@
1
- import { type CommentThread, type Person } from '../../core/comment-thread.ts';
2
- /** A registered author of threaded comments one `<person>` of `xl/persons/person.xml`. */
1
+ import { type CommentThread, type MentionRef, type Person } from '../../core/comment-thread.ts';
2
+ /** A registered author of threaded comments: one `<person>` of `xl/persons/person.xml`. */
3
3
  export interface ParsedPerson {
4
4
  /** Brace-wrapped GUID a message's `personId` points at. */
5
5
  readonly id: string;
@@ -10,26 +10,17 @@ export interface ParsedPerson {
10
10
  readonly providerId?: string;
11
11
  }
12
12
  /**
13
- * One `<mention>` of a message's `<mentions>` block: who was named, and the run of the message text
14
- * that renders as the mention chip.
13
+ * One `<mention>` of a message's `<mentions>` block: the model's own {@link MentionRef}, which is
14
+ * exactly the four wire attributes with nothing resolved yet. Declaring it a second time here would
15
+ * be two places for the offset convention to drift, and the offsets are what a spreadsheet app
16
+ * highlights with.
15
17
  *
16
- * All four wire attributes are required (Excel rejects a file missing any), and note the lowercase `p`
17
- * in `mentionpersonId` the capitalised spelling is not a declared attribute.
18
+ * All four attributes are required (Excel rejects a file missing any), and note the lowercase `p`
19
+ * in `mentionpersonId`; the capitalised spelling is not a declared attribute. The person id is that
20
+ * attribute's value; every other field is named as the model names it.
18
21
  */
19
- export interface ParsedMention {
20
- /** The mentioned {@link ParsedPerson.id}, from `mentionpersonId`. */
21
- readonly personId: string;
22
- /** Excel's own id for the mention itself. Absent only in a file that omitted it. */
23
- readonly mentionId?: string;
24
- /**
25
- * 0-based character offset into the message text. Verified against desktop Excel by rendering: the
26
- * chip covers exactly `[startIndex, startIndex + length)` of the text.
27
- */
28
- readonly startIndex: number;
29
- /** The mention's length in characters, **including the leading `@`** (`@Grace Hopper` is 13). */
30
- readonly length: number;
31
- }
32
- /** One message of a threaded conversation — a `<threadedComment>` of a `threadedComment{n}.xml`. */
22
+ export type ParsedMention = MentionRef;
23
+ /** One message of a threaded conversation: a `<threadedComment>` of a `threadedComment{n}.xml`. */
33
24
  export interface ParsedThreadedComment {
34
25
  /** A1 reference of the cell the whole thread anchors to; every message of a thread repeats it. */
35
26
  readonly ref: string;
@@ -39,7 +30,7 @@ export interface ParsedThreadedComment {
39
30
  readonly personId?: string;
40
31
  /**
41
32
  * The `dT` timestamp verbatim. Excel writes local wall-clock with fractional seconds and no
42
- * timezone (`2026-07-24T10:56:41.72`), which is not a round-trippable instant keeping the
33
+ * timezone (`2026-07-24T10:56:41.72`), which is not a round-trippable instant. Keeping the
43
34
  * string spares the reader from inventing a zone the file never stated.
44
35
  */
45
36
  readonly date?: string;
@@ -48,7 +39,7 @@ export interface ParsedThreadedComment {
48
39
  readonly parentId?: string;
49
40
  /**
50
41
  * The `done` flag exactly as written. Excel puts it on the head alone and omits it entirely on an
51
- * open thread (never `done="0"`), so read a thread's resolved state off its head a reply's
42
+ * open thread (never `done="0"`), so read a thread's resolved state off its head: a reply's
52
43
  * `false` here means "did not say", not "not resolved".
53
44
  */
54
45
  readonly done: boolean;
@@ -57,12 +48,12 @@ export interface ParsedThreadedComment {
57
48
  }
58
49
  /**
59
50
  * Parse `xl/persons/person.xml` into its registered authors, in document order. Order carries no
60
- * meaning Excel re-sorts the list by person id when it saves so nothing may depend on it. An
51
+ * meaning, because Excel re-sorts the list by person id when it saves, so nothing may depend on it. An
61
52
  * entry without an `id` is skipped: no message could reference it.
62
53
  */
63
54
  export declare function parsePersons(xml: string): ParsedPerson[];
64
55
  /**
65
- * Parse a `threadedComment{n}.xml` part into its messages, in document order thread order, with
56
+ * Parse a `threadedComment{n}.xml` part into its messages, in document order: thread order, with
66
57
  * each thread's replies following its head. Grouping into threads is {@link buildCommentThreads}'s
67
58
  * job; this stays faithful to the part. A message without a `ref` or `id` cannot be anchored or
68
59
  * replied to and is skipped.
@@ -74,14 +65,14 @@ export declare function parseThreadedComments(xml: string): ParsedThreadedCommen
74
65
  *
75
66
  * Document order is thread order with each thread's replies following its head, so one pass suffices: a
76
67
  * message with no `parentId` opens a thread, and a reply joins the thread its `parentId` names. A reply
77
- * whose parent is unknown a dangling `parentId` no Excel file produces opens a thread of its own
68
+ * whose parent is unknown (a dangling `parentId` no Excel file produces) opens a thread of its own
78
69
  * rather than being dropped, so a foreign generator's damage costs structure, never content.
79
70
  */
80
71
  export declare function buildCommentThreads(messages: readonly ParsedThreadedComment[], personById: (id: string) => Person | undefined): CommentThread[];
81
72
  /**
82
73
  * Serialise one sheet's conversations into its `xl/threadedComments/threadedComment{n}.xml` part.
83
74
  *
84
- * Messages are written flat, in thread order with each thread's replies after its head the shape
75
+ * Messages are written flat, in thread order with each thread's replies after its head: the shape
85
76
  * {@link parseThreadedComments} reads back. The head/reply distinction the model holds as array position
86
77
  * becomes `parentId` on every reply but the head, and `done="1"` goes on the head alone: only the head
87
78
  * carries the flag on the wire, so a reply can never contradict the thread it belongs to. An open thread
@@ -92,7 +83,7 @@ export declare function buildCommentThreads(messages: readonly ParsedThreadedCom
92
83
  */
93
84
  export declare function threadedCommentsXml(threads: readonly CommentThread[]): string;
94
85
  /**
95
- * Serialise the workbook's identity registry into `xl/persons/person.xml` singular and unnumbered,
86
+ * Serialise the workbook's identity registry into `xl/persons/person.xml`: singular and unnumbered,
96
87
  * unlike the per-sheet thread parts.
97
88
  *
98
89
  * Entries are written in registry order, which carries no meaning: Excel re-sorts the list by person id
@@ -1,7 +1,7 @@
1
- import { decodeAddress } from '../../core/address.js';
1
+ import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
2
2
  import { MENTION_OFFSET_MAX, } from '../../core/comment-thread.js';
3
- import { escapeAttr, escapeText, XML_DECLARATION } from '../../xml/xml.js';
4
- import { boolStrict, localName, parseXml } from '../../xml/xml-read.js';
3
+ import { boolStrict, decodeSpreadsheetText, localName, numInteger, parseXml, } from '../../xml/xml-read.js';
4
+ import { escapeAttr, escapeSpreadsheetText, XML_DECLARATION } from '../../xml/xml.js';
5
5
  import { THREADED_COMMENTS_NS } from './namespaces.js';
6
6
  export function parsePersons(xml) {
7
7
  const persons = [];
@@ -76,7 +76,7 @@ function threadedCommentFrom(attrs, text, mentions) {
76
76
  return {
77
77
  ref,
78
78
  id,
79
- text,
79
+ text: decodeSpreadsheetText(text),
80
80
  done: boolStrict(attrs.done),
81
81
  mentions: [...mentions],
82
82
  ...(personId !== undefined ? { personId } : {}),
@@ -86,8 +86,8 @@ function threadedCommentFrom(attrs, text, mentions) {
86
86
  }
87
87
  function mentionFrom(attrs) {
88
88
  const personId = attrs.mentionpersonId;
89
- const startIndex = integerAttribute(attrs.startIndex);
90
- const length = integerAttribute(attrs.length);
89
+ const startIndex = numInteger(attrs.startIndex);
90
+ const length = numInteger(attrs.length);
91
91
  if (personId === undefined || startIndex === undefined || length === undefined)
92
92
  return undefined;
93
93
  if (startIndex < 0 || length <= 0)
@@ -101,12 +101,6 @@ function mentionFrom(attrs) {
101
101
  ...(attrs.mentionId !== undefined ? { mentionId: attrs.mentionId } : {}),
102
102
  };
103
103
  }
104
- function integerAttribute(raw) {
105
- if (raw === undefined || raw.trim() === '')
106
- return undefined;
107
- const value = Number(raw);
108
- return Number.isInteger(value) ? value : undefined;
109
- }
110
104
  export function buildCommentThreads(messages, personById) {
111
105
  const threads = [];
112
106
  const commentsByHeadId = new Map();
@@ -127,14 +121,8 @@ export function buildCommentThreads(messages, personById) {
127
121
  return threads;
128
122
  }
129
123
  function anchorRef(reference) {
130
- let decoded;
131
- try {
132
- decoded = decodeAddress(reference);
133
- }
134
- catch {
135
- return undefined;
136
- }
137
- return decoded.col === undefined || decoded.row === undefined ? undefined : decoded.address;
124
+ const cell = tryDecodeCellRef(reference);
125
+ return cell === undefined ? undefined : encodeAddress(cell.col, cell.row);
138
126
  }
139
127
  function commentFrom(message, personById) {
140
128
  const author = message.personId === undefined ? undefined : personById(message.personId);
@@ -174,7 +162,7 @@ function threadedCommentXml(ref, comment, tail) {
174
162
  const date = comment.date === undefined ? '' : ` dT="${escapeAttr(comment.date)}"`;
175
163
  const person = comment.personId === undefined ? '' : ` personId="${escapeAttr(comment.personId)}"`;
176
164
  return (`<threadedComment ref="${escapeAttr(ref)}"${date}${person} id="${escapeAttr(comment.id)}"${tail}>` +
177
- `<text>${escapeText(comment.text)}</text>` +
165
+ `<text>${escapeSpreadsheetText(comment.text)}</text>` +
178
166
  mentionsXml(comment.mentions) +
179
167
  '</threadedComment>');
180
168
  }
@@ -9,4 +9,4 @@ export declare function workbookXml(workbook: Workbook, preservedRels: readonly
9
9
  export declare const FIXED_WORKBOOK_REL_COUNT = 2;
10
10
  export declare function workbookRelsXml(sheetCount: number, hasSharedStrings: boolean, personsRelId: string | null, preservedRels: readonly PreservedWorkbookRel[], pivots: readonly PivotPlan[]): string;
11
11
  export declare function corePropsXml(properties: WorkbookProperties): string;
12
- export declare function appPropsXml(): string;
12
+ export declare function appPropsXml(properties: WorkbookProperties): string;
@@ -232,6 +232,9 @@ export function workbookRelsXml(sheetCount, hasSharedStrings, personsRelId, pres
232
232
  }
233
233
  export function corePropsXml(properties) {
234
234
  const parts = [];
235
+ if (properties.title !== undefined) {
236
+ parts.push(`<dc:title>${escapeText(properties.title)}</dc:title>`);
237
+ }
235
238
  if (properties.creator !== undefined) {
236
239
  parts.push(`<dc:creator>${escapeText(properties.creator)}</dc:creator>`);
237
240
  }
@@ -253,7 +256,8 @@ export function corePropsXml(properties) {
253
256
  function w3cdtf(element, date) {
254
257
  return `<dcterms:${element} xsi:type="dcterms:W3CDTF">${date.toISOString()}</dcterms:${element}>`;
255
258
  }
256
- export function appPropsXml() {
259
+ export function appPropsXml(properties) {
260
+ const company = properties.company === undefined ? '' : `<Company>${escapeText(properties.company)}</Company>`;
257
261
  return (XML_DECLARATION +
258
- `<Properties xmlns="${NS.extProps}"><Application>ts-xlsx</Application></Properties>`);
262
+ `<Properties xmlns="${NS.extProps}"><Application>ts-xlsx</Application>${company}</Properties>`);
259
263
  }
@@ -6,7 +6,7 @@ import { type SharedFormulaRole } from './shared-formulas.ts';
6
6
  import type { SharedStringTable } from './shared-strings.ts';
7
7
  import type { StyleRegistry } from './styles.ts';
8
8
  /**
9
- * The used-cell extent of a sheet the top-left/bottom-right grid bounds that fold into the
9
+ * The used-cell extent of a sheet: the top-left/bottom-right grid bounds that fold into the
10
10
  * `<dimension>`. Rows carrying only formatting (a row height, an outline level) do not extend the
11
11
  * used range, matching how Excel records `<dimension>`, so {@link add} ignores them. A fresh extent
12
12
  * holds the `Infinity`/`-Infinity` sentinels; {@link isEmpty} reports that no used cell has been seen.
@@ -17,7 +17,7 @@ export declare class Extent {
17
17
  bottom: number;
18
18
  right: number;
19
19
  constructor(seed?: Extent);
20
- /** Whether no used cell has been folded in yet the sheet's dimension is then the lone cell `A1`. */
20
+ /** Whether no used cell has been folded in yet, in which case the dimension is the lone cell `A1`. */
21
21
  get isEmpty(): boolean;
22
22
  /** Fold a rendered row's used-column span into the extent. `minCol` is `Infinity` when the row
23
23
  * carried no cells (only formatting), which extends nothing. */
@@ -1,10 +1,10 @@
1
- import { decodeRange, encodeAddress, MAX_COLUMN } from '../../core/address.js';
1
+ import { decodeRange, encodeAddress } from '../../core/address.js';
2
2
  import { DEFAULT_DATE_NUMFMT, dateToSerial } from '../../core/date.js';
3
3
  import { mangleFormula } from '../../core/formula.js';
4
4
  import { NAMED_STYLE_ID } from '../../core/internal.js';
5
5
  import { detectValueType, isDataTableFormulaValue, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
6
6
  import { AuthoringError, InternalError } from '../../errors.js';
7
- import { escapeAttr, escapeText, numberText, textElement, XML_DECLARATION } from '../../xml/xml.js';
7
+ import { escapeAttr, escapeSpreadsheetText, escapeText, numberText, textElement, XML_DECLARATION, } from '../../xml/xml.js';
8
8
  import { relativePartPath } from '../opc/part-paths.js';
9
9
  import { relationship, relationshipsPart } from '../opc/rels.js';
10
10
  import { conditionalFormattingsExtXml, conditionalFormattingsXml } from './conditional-formatting.js';
@@ -175,7 +175,7 @@ function validateMerges(sheet) {
175
175
  top <= region.bottom &&
176
176
  bottom >= region.top;
177
177
  if (overlaps) {
178
- throw new AuthoringError(`merged range ${merge} overlaps table "${table.name}" (${table.range}) Excel forbids a merge inside a table`);
178
+ throw new AuthoringError(`merged range ${merge} overlaps table "${table.name}" (${table.range}): Excel forbids a merge inside a table`);
179
179
  }
180
180
  }
181
181
  }
@@ -251,18 +251,13 @@ function sheetFormatPr(properties, outlineLevel) {
251
251
  }
252
252
  function maxColumnOutlineLevel(sheet) {
253
253
  let max = 0;
254
- for (const column of sheet.columns()) {
255
- if (column.index > MAX_COLUMN)
256
- continue;
254
+ for (const column of sheet.columns())
257
255
  max = Math.max(max, column.outlineLevel ?? 0);
258
- }
259
256
  return max;
260
257
  }
261
258
  function colsXml(sheet, styles) {
262
259
  const runs = [];
263
260
  for (const { index, properties } of sheet.columns()) {
264
- if (index > MAX_COLUMN)
265
- continue;
266
261
  const body = colBody(properties ?? {}, styles);
267
262
  if (body === null)
268
263
  continue;
@@ -410,7 +405,7 @@ function cellXml(cell, style, shared, sharedStrings) {
410
405
  }
411
406
  if (value === null)
412
407
  return `<c r="${ref}"${s}/>`;
413
- throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm every CellValue kind is handled above`);
408
+ throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm: every CellValue kind is handled above`);
414
409
  }
415
410
  function hasOwnStyle(cell) {
416
411
  return (cell.fill !== undefined ||
@@ -455,7 +450,7 @@ function formulaBodyXml(ref, s, f, result) {
455
450
  return `<c r="${ref}"${s} t="b">${f}<v>${result ? 1 : 0}</v></c>`;
456
451
  }
457
452
  if (typeof result === 'string') {
458
- return `<c r="${ref}"${s} t="str">${f}<v>${escapeText(result)}</v></c>`;
453
+ return `<c r="${ref}"${s} t="str">${f}<v>${escapeSpreadsheetText(result)}</v></c>`;
459
454
  }
460
455
  if (isErrorValue(result)) {
461
456
  return `<c r="${ref}"${s} t="e">${f}<v>${result.error}</v></c>`;
@@ -465,5 +460,5 @@ function formulaBodyXml(ref, s, f, result) {
465
460
  return `<c r="${ref}"${s}>${f}</c>`;
466
461
  return `<c r="${ref}"${s}>${f}<v>${numberText(dateToSerial(result))}</v></c>`;
467
462
  }
468
- throw new InternalError('writing a non-primitive formula result has no arm every FormulaResult kind is handled above');
463
+ throw new InternalError('writing a non-primitive formula result has no arm: every FormulaResult kind is handled above');
469
464
  }
@@ -12,12 +12,12 @@ import type { StyleRegistry } from './styles.ts';
12
12
  import { type FlushedSheet } from './write.ts';
13
13
  /** Calculation settings applied to the streamed workbook. Mirrors the {@link Workbook} flags. */
14
14
  export interface CalcProperties {
15
- /** Ask the consumer to recalculate every formula on open the OOXML `fullCalcOnLoad` flag. */
15
+ /** Ask the consumer to recalculate every formula on open: the OOXML `fullCalcOnLoad` flag. */
16
16
  fullCalcOnLoad?: boolean;
17
17
  }
18
18
  type SinkOptions = {
19
19
  /**
20
- * Write the package to a caller-owned {@link Writable} sink an outbound upload, a cloud-SDK
20
+ * Write the package to a caller-owned {@link Writable} sink: an outbound upload, a cloud-SDK
21
21
  * stream, any destination the caller controls. {@link WorkbookStreamWriter.commit} pushes every
22
22
  * chunk into it and settles only once the sink has finished (or rejects if it errors), so a
23
23
  * caller can deterministically sequence work after the upload completes. Mutually exclusive
@@ -41,7 +41,7 @@ type SinkOptions = {
41
41
  /** Options fixed at construction that shape the whole streamed package. */
42
42
  export type WorkbookStreamWriterOptions = SinkOptions & {
43
43
  /**
44
- * Pool plain string cell values into a shared-strings table rather than storing each inline the
44
+ * Pool plain string cell values into a shared-strings table rather than storing each inline: the
45
45
  * same {@link WriteOptions.useSharedStrings} the buffered writer exposes. Off by default.
46
46
  */
47
47
  readonly useSharedStrings?: boolean;
@@ -58,12 +58,12 @@ export declare class StreamedRow {
58
58
  /** The cells this row materialised, for styling before it is committed. */
59
59
  get cells(): readonly Cell[];
60
60
  /** Finalise the row: an eager writer serialises it now and releases its cells; otherwise a no-op.
61
- * Committing twice is harmless the second call does nothing rather than re-emitting the row. */
61
+ * Committing twice is harmless: the second call does nothing rather than re-emitting the row. */
62
62
  commit(): void;
63
63
  }
64
64
  /**
65
65
  * A worksheet being written incrementally. Append rows with {@link addRow}/{@link addRows}, style
66
- * cells through {@link getCell}, then {@link commit} to freeze it after which any further mutation
66
+ * cells through {@link getCell}, then {@link commit} to freeze it, after which any further mutation
67
67
  * is rejected with a legible error rather than silently accepted or crashing.
68
68
  */
69
69
  export declare class WorksheetStreamWriter {
@@ -71,7 +71,7 @@ export declare class WorksheetStreamWriter {
71
71
  constructor(sheet: Worksheet, eager: boolean, styles: StyleRegistry);
72
72
  /** The sheet's name. */
73
73
  get name(): string;
74
- /** The number of rows written so far spans gaps and formatted-only rows, like the model, and
74
+ /** The number of rows written so far. Spans gaps and formatted-only rows, like the model, and
75
75
  * survives the eviction of eagerly-flushed rows. */
76
76
  get rowCount(): number;
77
77
  /** Append one row of values after the last used row; the cells are returned for styling. */
@@ -83,7 +83,7 @@ export declare class WorksheetStreamWriter {
83
83
  * {@link StreamedRow.commit}; the row's `<row>` XML is retained (interned into the workbook's live
84
84
  * style registry so its ids stay valid) and the cell graph is dropped, bounding peak memory.
85
85
  *
86
- * @throws {AuthoringError} if the row carries a shared-formula cell a finished row cannot join the
86
+ * @throws {AuthoringError} if the row carries a shared-formula cell: a finished row cannot join the
87
87
  * whole-sheet formula planning, so shared formulas must be authored through {@link getCell}.
88
88
  */
89
89
  flushRow(number: number, cells: readonly Cell[]): void;
@@ -92,23 +92,23 @@ export declare class WorksheetStreamWriter {
92
92
  getCell(reference: string): Cell;
93
93
  /**
94
94
  * Attach a data validation to a range before the sheet is committed. Delegates to the model, so the
95
- * streamed package emits the `<dataValidations>` block in its CT_Worksheet position before
96
- * `<hyperlinks>` because both writers share one worksheet serializer.
95
+ * streamed package emits the `<dataValidations>` block in its CT_Worksheet position, before
96
+ * `<hyperlinks>`, because both writers share one worksheet serializer.
97
97
  */
98
98
  addDataValidation(sqref: string, rule: DataValidation, options?: {
99
99
  extended?: boolean;
100
100
  }): void;
101
101
  /**
102
102
  * Attach a conditional formatting to a range before the sheet is committed. Like every other block,
103
- * it lands in its schema-mandated slot after `<mergeCells>`, before `<dataValidations>` and
104
- * `<hyperlinks>` since the streamed sheet is serialized through the same path as a buffered write.
103
+ * it lands in its schema-mandated slot, after `<mergeCells>` and before `<dataValidations>` and
104
+ * `<hyperlinks>`, since the streamed sheet is serialized through the same path as a buffered write.
105
105
  */
106
106
  addConditionalFormatting(formatting: ConditionalFormatting): void;
107
107
  /**
108
108
  * Anchor a workbook image (the id from {@link WorkbookStreamWriter.addImage}) to this sheet,
109
109
  * spanning the rectangle from the top-left grid point `tl` to the bottom-right `br`. The streamed
110
110
  * package emits the drawing part, its media relationship, and the sheet's `<drawing>` reference
111
- * exactly as a buffered write does both writers share `buildPackageParts`.
111
+ * exactly as a buffered write does: both writers share `buildPackageParts`.
112
112
  */
113
113
  addImage(imageId: number, anchor: {
114
114
  readonly tl: AnchorPoint;
@@ -116,7 +116,7 @@ export declare class WorksheetStreamWriter {
116
116
  }): void;
117
117
  /**
118
118
  * Apply the sheet's autofilter before it is committed; mirrors {@link Worksheet.autoFilter}. The
119
- * streamed package emits `<autoFilter>` in its CT_Worksheet slot after `<sheetProtection>` and
119
+ * streamed package emits `<autoFilter>` in its CT_Worksheet slot, after `<sheetProtection>`, and
120
120
  * contributes the hidden `_FilterDatabase` defined name, exactly as a buffered write does.
121
121
  */
122
122
  set autoFilter(filter: string | AutoFilter | undefined);
@@ -147,8 +147,8 @@ export declare class WorkbookStreamWriter {
147
147
  /** Document-level metadata written to the package's core properties. */
148
148
  get properties(): Workbook['properties'];
149
149
  /**
150
- * The output stream carrying the package bytes. A caller drives it with Node's standard idiom
151
- * `writer.stream.pipe(out)` which composes because `pipe` returns its destination. The stream is
150
+ * The output stream carrying the package bytes. A caller drives it with Node's standard idiom,
151
+ * `writer.stream.pipe(out)`, which composes because `pipe` returns its destination. The stream is
152
152
  * created lazily on first access so a caller handing the writer its own sink is still free to
153
153
  * ignore this one.
154
154
  */
@@ -6,6 +6,7 @@ import { INTERNAL } from '../../core/internal.js';
6
6
  import { isSharedFormulaValue } from '../../core/value.js';
7
7
  import { Workbook } from '../../core/workbook.js';
8
8
  import { AuthoringError } from '../../errors.js';
9
+ import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
9
10
  import { buildColumnDefaults, buildPackageParts, createStyleRegistry, Extent, renderRow, } from './write.js';
10
11
  export class StreamedRow {
11
12
  #cells;
@@ -145,7 +146,7 @@ export class WorksheetStreamWriter {
145
146
  }
146
147
  #assertOpen() {
147
148
  if (this.#committed) {
148
- throw new AuthoringError(`worksheet "${this.#sheet.name}" is already committed its rows are finalised and no more can be added`);
149
+ throw new AuthoringError(`worksheet "${this.#sheet.name}" is already committed: its rows are finalised and no more can be added`);
149
150
  }
150
151
  }
151
152
  }
@@ -178,13 +179,13 @@ export class WorkbookStreamWriter {
178
179
  }
179
180
  addImage(options) {
180
181
  if (this.#committed) {
181
- throw new AuthoringError('the workbook is already committed no more images can be registered');
182
+ throw new AuthoringError('the workbook is already committed: no more images can be registered');
182
183
  }
183
184
  return this.#workbook.addImage(options);
184
185
  }
185
186
  addWorksheet(name, options = {}) {
186
187
  if (this.#committed) {
187
- throw new AuthoringError('the workbook is already committed no more worksheets can be added');
188
+ throw new AuthoringError('the workbook is already committed: no more worksheets can be added');
188
189
  }
189
190
  const sheet = new WorksheetStreamWriter(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles);
190
191
  this.#sheets.push(sheet);
@@ -244,6 +245,7 @@ function streamZipPackage(parts, onChunk) {
244
245
  });
245
246
  for (const [name, data] of Object.entries(parts)) {
246
247
  const entry = new ZipDeflate(name, { level: 6 });
248
+ entry.mtime = FIXED_ENTRY_MTIME;
247
249
  zip.add(entry);
248
250
  entry.push(data, true);
249
251
  }
@@ -16,13 +16,14 @@ export interface WriteOptions {
16
16
  /**
17
17
  * {@link WriteOptions} plus the streaming writer's internal wiring, so a buffered caller's options
18
18
  * object can never carry fields meant only for {@link WorkbookStreamWriter}'s own use. Not exported
19
- * from the public barrel {@link buildPackageParts} is the only consumer outside this module.
19
+ * from the public barrel: {@link WorkbookStreamWriter} is the only caller that fills these fields,
20
+ * and it reaches them through {@link buildPackageParts}.
20
21
  */
21
22
  export interface InternalWriteOptions extends WriteOptions {
22
23
  /**
23
24
  * The style registry to intern into, in place of a freshly-seeded one. The streaming writer
24
25
  * serialises each committed row eagerly (freeing its cells), so those rows' style ids must be
25
- * assigned by the very same registry that later emits `xl/styles.xml` otherwise the ids in the
26
+ * assigned by the very same registry that later emits `xl/styles.xml`; otherwise the ids in the
26
27
  * pre-rendered rows would not match the styles part. When omitted the buffered path seeds its own,
27
28
  * so its output is unchanged.
28
29
  */
@@ -37,6 +38,10 @@ export interface InternalWriteOptions extends WriteOptions {
37
38
  /**
38
39
  * Serialise a workbook into an `.xlsx` package.
39
40
  *
41
+ * The bytes are a pure function of the workbook: an unchanged model written twice produces two
42
+ * identical archives, because entry timestamps are pinned to a fixed date rather than taken from the
43
+ * clock. A committed `.xlsx` therefore only changes when something about it changed.
44
+ *
40
45
  * @throws {AuthoringError} if the workbook has no worksheets (a zero-sheet package is corrupt),
41
46
  * or holds a value the writer cannot yet represent.
42
47
  */
@@ -44,19 +49,19 @@ export declare function writeXlsx(workbook: Workbook, options?: WriteOptions): U
44
49
  /**
45
50
  * Serialise a workbook into an `.xlsx` package, deflating off the calling thread.
46
51
  *
47
- * Produces the same package {@link writeXlsx} does every part compresses to identical bytes and
52
+ * Produces the same package {@link writeXlsx} does, byte for byte and entry timestamps included, and
48
53
  * exists for one reason: DEFLATE dominates the cost of writing a large workbook, and {@link writeXlsx}
49
54
  * spends all of it on the caller's thread. Here `fflate` deflates each part in a worker, so the event
50
55
  * loop keeps turning (stalls drop from the whole write to tens of milliseconds) and parts compress in
51
56
  * parallel, which on a multi-sheet workbook also finishes sooner. On a single-sheet workbook there is
52
57
  * only one part to deflate, so expect responsiveness rather than speed.
53
58
  *
54
- * Building the parts still happens on the calling thread only compression moves. That is why there
59
+ * Building the parts still happens on the calling thread; only compression moves. That is why there
55
60
  * is no `readXlsxAsync` mirroring this: reading is dominated by XML parsing and model building, which
56
61
  * no worker can take, and the reader's zip-bomb ceiling is enforced by counting output between
57
62
  * synchronous input slices. See ADR-0024.
58
63
  *
59
- * @throws {AuthoringError} as a rejection under the same conditions as {@link writeXlsx};
64
+ * @throws {AuthoringError}, as a rejection, under the same conditions as {@link writeXlsx};
60
65
  * the part-building it shares happens before any worker is involved. A failure raised by the zip
61
66
  * layer itself (including an environment that cannot spawn a worker) propagates unwrapped, exactly
62
67
  * as it does from {@link writeXlsx}.
@@ -71,8 +76,8 @@ export declare function writeXlsxAsync(workbook: Workbook, options?: WriteOption
71
76
  export declare function createStyleRegistry(workbook: Workbook): StyleRegistry;
72
77
  /**
73
78
  * Assemble a workbook into the map of OPC package parts (part name → bytes) that make up an `.xlsx`,
74
- * short of zipping them. This is the whole serialisation content types, relationships, workbook,
75
- * per-sheet XML, styles, theme, media, tables, and props factored out of {@link writeXlsx} so the
79
+ * short of zipping them. This is the whole serialisation (content types, relationships, workbook,
80
+ * per-sheet XML, styles, theme, media, tables, and props) factored out of {@link writeXlsx} so the
76
81
  * streaming writer can drive the identical parts through a streamed zip container rather than
77
82
  * `zipSync`. Neither writer duplicates a byte of serialisation.
78
83
  *