@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.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- 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[];
|