@shbernal/ts-xlsx 1.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 (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,243 @@
1
+ // Cell comments — the `xl/comments{n}.xml` part, its `xl/drawings/vmlDrawing{n}.vml` companion, and the
2
+ // reader that maps a comment back onto its cell.
3
+ //
4
+ // A comment is anchored to a cell by A1 reference and rendered by Excel as a floating box. The box's
5
+ // geometry lives in a legacy VML drawing (the pre-DrawingML shape format Excel still requires here);
6
+ // the text lives in the comments part. Both are emitted together — a comments part with no matching
7
+ // `<legacyDrawing>`/VML reads as text but renders nothing, so we never split them.
8
+ //
9
+ // Two different things share this one wire form:
10
+ // • a user's **note** (`cell.note`) — a single anonymous annotation, the whole of what the part held
11
+ // before 2018;
12
+ // • the legacy **fallback** Excel writes beside every modern threaded comment (see
13
+ // `threaded-comments.ts`), so a pre-2018 reader still sees the conversation. Its text is a fixed
14
+ // boilerplate wrapping a copy of the thread, and its author is a synthetic `tc={headId}` entry.
15
+ //
16
+ // That `tc=` author and the comment's `xr:uid` are how Excel binds a cell back to its thread — not
17
+ // decoration. Verified against desktop Excel: a package whose threadedComment part, persons registry,
18
+ // relationships and content types all survive intact still reads back as ordinary notes with zero
19
+ // threads once those two are lost. So the fallback is *derived from the thread model* on write and
20
+ // *suppressed on read*, rather than round-tripped as a plain note.
21
+ import { decodeAddress } from "../../core/address.js";
22
+ import { escapeAttr, escapeText, textElement, XML_DECLARATION } from "../../xml/xml.js";
23
+ import { localName, parseXml } from "../../xml/xml-read.js";
24
+ import { MARKUP_COMPATIBILITY_NS, REVISION_NS, SPREADSHEETML_NS } from "./namespaces.js";
25
+ /**
26
+ * Gather every comment a sheet must write: its cells' notes, plus one legacy fallback per conversation
27
+ * in `threads`. A comment anchors to its cell regardless of the cell's value, so a note (or a thread) on
28
+ * an otherwise-empty cell is collected too.
29
+ *
30
+ * `threads` is the conversations the *package* will carry, not simply the ones the sheet holds — the
31
+ * caller decides, because a fallback beside a thread whose `threadedComment` part is missing is worse
32
+ * than no fallback at all: verified against desktop Excel, such a comment shows as neither a thread nor
33
+ * a note, so the text disappears entirely.
34
+ *
35
+ * Ordered by cell, row-major, the way Excel writes the list — so a fallback lands interleaved among the
36
+ * notes rather than appended after them, and the VML shapes follow the same order.
37
+ */
38
+ export function collectComments(sheet, threads) {
39
+ const fallbacks = threadFallbacks(threads);
40
+ const anchored = new Set(fallbacks.map((fallback) => fallback.ref));
41
+ const comments = [...fallbacks];
42
+ for (const { cells } of sheet.rows()) {
43
+ for (const cell of cells) {
44
+ // Excel refuses to put a note and a thread on one cell, so a file carrying both (only a foreign
45
+ // generator or a hand-edit makes one) is written back as the thread alone: two comments on one ref
46
+ // is a shape Excel repairs by dropping both, which would lose the conversation as well as the note.
47
+ if (cell.note === undefined || anchored.has(cell.address))
48
+ continue;
49
+ comments.push({ ref: cell.address, row: cell.row, col: cell.col, text: cell.note });
50
+ }
51
+ }
52
+ return comments.sort((a, b) => a.row - b.row || a.col - b.col);
53
+ }
54
+ // One legacy fallback per conversation, keyed to the thread head whose id binds it. A thread with no
55
+ // messages has nothing to write and no id to bind by, so it contributes none.
56
+ function threadFallbacks(threads) {
57
+ const fallbacks = [];
58
+ for (const thread of threads) {
59
+ const head = thread.comments[0];
60
+ const { col, row } = decodeAddress(thread.ref);
61
+ if (head === undefined || col === undefined || row === undefined)
62
+ continue;
63
+ fallbacks.push({ ref: thread.ref, row, col, text: fallbackText(thread), threadId: head.id });
64
+ }
65
+ return fallbacks;
66
+ }
67
+ // The boilerplate Excel puts in front of every fallback, captured verbatim from an Excel-authored file.
68
+ // It is what a pre-2018 reader shows the user, so it is reproduced exactly rather than paraphrased.
69
+ const FALLBACK_PREAMBLE = '[Threaded comment]\n\nYour version of Excel allows you to read this threaded comment; however, any ' +
70
+ 'edits to it will get removed if the file is opened in a newer version of Excel. Learn more: ' +
71
+ 'https://go.microsoft.com/fwlink/?linkid=870924\n\n';
72
+ // A whole conversation flattened into the one comment a pre-2018 reader can render: the opening message
73
+ // under `Comment:`, then each reply under its own `Reply:`, every body indented four spaces. Verified
74
+ // against desktop Excel for a thread with three replies — `Reply:` repeats per reply rather than the
75
+ // replies being joined under one heading.
76
+ function fallbackText(thread) {
77
+ const [head, ...replies] = thread.comments;
78
+ const body = replies.map((reply) => `\nReply:\n ${reply.text}`).join('');
79
+ return `${FALLBACK_PREAMBLE}Comment:\n ${head?.text ?? ''}${body}`;
80
+ }
81
+ // `xr:uid` lives in the 2014 revision namespace, declared `mc:Ignorable` exactly as Excel declares it so
82
+ // a consumer that does not know the prefix skips the attribute instead of rejecting the part.
83
+ const REVISION_NS_ATTRS = ` xmlns:mc="${MARKUP_COMPATIBILITY_NS}" mc:Ignorable="xr" xmlns:xr="${REVISION_NS}"`;
84
+ /**
85
+ * The `xl/comments{n}.xml` part.
86
+ *
87
+ * Authors are laid out the way Excel lays them out: one synthetic `tc={headId}` entry per threaded
88
+ * conversation first, then a single anonymous author shared by every note (the model carries no note
89
+ * author). Each comment points at its own author by index, and a fallback additionally carries the
90
+ * `xr:uid` naming its thread — the pair that keeps Excel treating the cell as threaded.
91
+ */
92
+ export function commentsXml(comments) {
93
+ const authorIdByThreadId = new Map();
94
+ for (const { threadId } of comments) {
95
+ if (threadId !== undefined && !authorIdByThreadId.has(threadId)) {
96
+ authorIdByThreadId.set(threadId, authorIdByThreadId.size);
97
+ }
98
+ }
99
+ const noteAuthorId = authorIdByThreadId.size;
100
+ const hasNote = comments.some((comment) => comment.threadId === undefined);
101
+ const authors = [...authorIdByThreadId.keys()].map((id) => `<author>tc=${escapeText(id)}</author>`).join('') +
102
+ (hasNote ? '<author></author>' : '');
103
+ const list = comments
104
+ .map((comment) => {
105
+ const { threadId } = comment;
106
+ const authorId = threadId === undefined ? noteAuthorId : authorIdByThreadId.get(threadId);
107
+ const uid = threadId === undefined ? '' : ` xr:uid="${escapeAttr(threadId)}"`;
108
+ return (`<comment ref="${comment.ref}" authorId="${authorId}"${uid}>` +
109
+ `<text><r>${textElement(comment.text)}</r></text>` +
110
+ '</comment>');
111
+ })
112
+ .join('');
113
+ return (XML_DECLARATION +
114
+ `<comments xmlns="${SPREADSHEETML_NS}"${authorIdByThreadId.size > 0 ? REVISION_NS_ATTRS : ''}>` +
115
+ `<authors>${authors}</authors>` +
116
+ `<commentList>${list}</commentList>` +
117
+ '</comments>');
118
+ }
119
+ // VML namespaces and the one shape type (a text box) every comment reuses.
120
+ const VML_HEADER = '<xml xmlns:v="urn:schemas-microsoft-com:vml" ' +
121
+ 'xmlns:o="urn:schemas-microsoft-com:office:office" ' +
122
+ 'xmlns:x="urn:schemas-microsoft-com:office:excel">' +
123
+ '<o:shapelayout v:ext="edit"><o:idmap v:ext="edit" data="1"/></o:shapelayout>' +
124
+ '<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" ' +
125
+ 'path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/>' +
126
+ '<v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>';
127
+ /** The `xl/drawings/vmlDrawing{n}.vml` companion: one hidden text-box shape per comment, in the same
128
+ * order as the comments part. Anchor coordinates place the box a couple of cells down-and-right of its
129
+ * owner; Excel refines them on open, so the values are a sensible starting geometry rather than a
130
+ * pixel-exact layout. A thread's fallback shape is `ObjectType="Note"` like any other — Excel draws the
131
+ * threaded-comment card itself and only needs the shape to exist. */
132
+ export function vmlDrawingXml(comments) {
133
+ const shapes = comments
134
+ .map((comment, i) => {
135
+ const row0 = comment.row - 1;
136
+ const col0 = comment.col - 1;
137
+ const anchor = `${col0 + 1}, 15, ${row0}, 2, ${col0 + 3}, 15, ${row0 + 4}, 4`;
138
+ return (`<v:shape id="_x0000_s${1025 + i}" type="#_x0000_t202" ` +
139
+ 'style="position:absolute;margin-left:59.25pt;margin-top:1.5pt;width:108pt;height:59.25pt;' +
140
+ `z-index:${i + 1};visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto">` +
141
+ '<v:fill color2="#ffffe1"/><v:shadow on="t" color="black" obscured="t"/>' +
142
+ '<v:path o:connecttype="none"/>' +
143
+ '<v:textbox style="mso-direction-alt:auto;mso-fit-shape-to-text:t"><div style="text-align:left"></div></v:textbox>' +
144
+ '<x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/>' +
145
+ `<x:Anchor>${anchor}</x:Anchor><x:AutoFill>False</x:AutoFill>` +
146
+ `<x:Row>${row0}</x:Row><x:Column>${col0}</x:Column></x:ClientData></v:shape>`);
147
+ })
148
+ .join('');
149
+ return `${VML_HEADER}${shapes}</xml>`;
150
+ }
151
+ // A comment names its author by index into `<authors>`, so an empty entry must still occupy its slot —
152
+ // presenting the self-closing `<author/>` an author-less file writes as an empty element gives it the
153
+ // close that pushes it. Without this every later index would shift by one and a note could inherit a
154
+ // thread's `tc=` author.
155
+ const COMMENT_EMPTY_CLOSES = new Set(['author']);
156
+ // The author string marking a comment as a thread's legacy fallback: `tc={headThreadId}`.
157
+ const THREAD_AUTHOR_PREFIX = 'tc=';
158
+ /**
159
+ * Parse a `comments{n}.xml` part into a map of A1 reference → comment. Text runs within one comment are
160
+ * concatenated; an author-name run is Excel's own convention and is not stripped, so a note reads back
161
+ * as exactly the text that was written.
162
+ */
163
+ export function parseComments(xml) {
164
+ const comments = new Map();
165
+ const authors = [];
166
+ let currentRef;
167
+ let currentAuthorId;
168
+ let capture;
169
+ let buffer = '';
170
+ parseXml(xml, {
171
+ onOpen(name, attrs) {
172
+ const local = localName(name);
173
+ if (local === 'comment') {
174
+ currentRef = attrs.ref;
175
+ currentAuthorId = attrs.authorId;
176
+ buffer = '';
177
+ }
178
+ else if (local === 'author') {
179
+ capture = 'author';
180
+ buffer = '';
181
+ }
182
+ else if (local === 'text') {
183
+ capture = 'text';
184
+ }
185
+ },
186
+ onText(text) {
187
+ if (capture !== undefined)
188
+ buffer += text;
189
+ },
190
+ onClose(name) {
191
+ const local = localName(name);
192
+ if (local === 'author') {
193
+ authors.push(buffer);
194
+ capture = undefined;
195
+ buffer = '';
196
+ }
197
+ else if (local === 'text') {
198
+ capture = undefined;
199
+ }
200
+ else if (local === 'comment' && currentRef !== undefined) {
201
+ const threadId = threadIdOf(authors[Number(currentAuthorId)]);
202
+ comments.set(currentRef, {
203
+ text: buffer,
204
+ ...(threadId !== undefined ? { threadId } : {}),
205
+ });
206
+ currentRef = undefined;
207
+ currentAuthorId = undefined;
208
+ }
209
+ },
210
+ }, { closeEmptyElements: COMMENT_EMPTY_CLOSES });
211
+ return comments;
212
+ }
213
+ // A missing or non-numeric `authorId` indexes nothing, so `authors[NaN]` is undefined and the comment
214
+ // reads as a plain note — the safe direction, since mistaking a note for a fallback would delete it.
215
+ function threadIdOf(author) {
216
+ if (author === undefined || !author.startsWith(THREAD_AUTHOR_PREFIX))
217
+ return undefined;
218
+ const id = author.slice(THREAD_AUTHOR_PREFIX.length);
219
+ return id === '' ? undefined : id;
220
+ }
221
+ /**
222
+ * Apply a parsed comments part onto a sheet's cells as notes, addressing each by its A1 reference.
223
+ *
224
+ * A thread's legacy fallback is not a note and does not become one: its text is boilerplate wrapping a
225
+ * copy of the conversation, so surfacing it as `cell.note` hands the caller garbage — and on write it
226
+ * would be re-emitted as a plain note, destroying the `tc=`/`xr:uid` binding and leaving Excel unable to
227
+ * see the thread at all.
228
+ *
229
+ * Suppressed only for a conversation the reader actually holds: a file whose thread part is missing or
230
+ * damaged has nothing else left, so there the boilerplate is kept rather than the content lost. Call
231
+ * after the sheet's threads are restored, since that is what this reads to decide.
232
+ */
233
+ export function applyNotes(sheet, comments) {
234
+ const headIds = new Set(sheet.commentThreads.flatMap((thread) => {
235
+ const head = thread.comments[0];
236
+ return head === undefined ? [] : [head.id];
237
+ }));
238
+ for (const [ref, comment] of comments) {
239
+ if (comment.threadId !== undefined && headIds.has(comment.threadId))
240
+ continue;
241
+ sheet.getCell(ref).note = comment.text;
242
+ }
243
+ }
@@ -0,0 +1,32 @@
1
+ import type { ConditionalFormatting } from '../../core/conditional-formatting.ts';
2
+ import { type StyleRegistry } from './styles.ts';
3
+ /**
4
+ * Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in
5
+ * insertion order. Returns '' when the sheet has none. A rule missing a `priority` is assigned the
6
+ * next free one, so the output always satisfies Excel's requirement that every cfRule carry one.
7
+ */
8
+ export declare function conditionalFormattingsXml(formattings: readonly ConditionalFormatting[], styles: StyleRegistry): string;
9
+ /**
10
+ * The worksheet `<extLst>` `<ext>` carrying the x14 data-bar extensions, or '' when no data bar needs
11
+ * one. Each extension's `<x14:cfRule id>` is read from the same {@link dataBarExtLinks} map the classic
12
+ * pass uses for the `<x14:id>` its cfRule carries, so the two ends of a link always match. Emitted bare
13
+ * (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single `<extLst>` beside the
14
+ * data-validation extension.
15
+ */
16
+ export declare function conditionalFormattingsExtXml(formattings: readonly ConditionalFormatting[]): string;
17
+ /**
18
+ * Parse a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
19
+ * blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
20
+ * read only to enrich a classic data bar with the facets the classic element cannot carry — the
21
+ * gradient flag and the negative-fill and axis colours — matched by the shared id the two ends link
22
+ * on. An extension rule with no classic counterpart (a rule that lives only in x14) is ignored, so it
23
+ * is never half-read into a broken classic rule.
24
+ */
25
+ export declare function parseConditionalFormattings(xml: string): ConditionalFormatting[];
26
+ /**
27
+ * Extract the differential-style (`<dxf>`) fragments from styles.xml, each verbatim. Preserving the
28
+ * raw XML — rather than reparsing and re-serialising — is what keeps a foreign dxf's number format a
29
+ * real format code on re-write instead of a coerced `"[object Object]"`, and keeps every conditional
30
+ * formatting's `dxfId` index pointing at the same style it did in the source file.
31
+ */
32
+ export declare function parseDxfs(stylesXml: string): string[];