@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.
- package/README.md +5 -0
- package/dist/core/address.d.ts +49 -2
- package/dist/core/address.js +31 -3
- package/dist/core/autofilter.d.ts +2 -2
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +7 -7
- package/dist/core/column.d.ts +8 -9
- package/dist/core/column.js +2 -4
- package/dist/core/comment-thread.d.ts +32 -23
- package/dist/core/comment-thread.js +4 -4
- package/dist/core/conditional-formatting-overlay.d.ts +1 -1
- package/dist/core/conditional-formatting.d.ts +3 -3
- package/dist/core/data-validation-overlay.d.ts +1 -1
- package/dist/core/data-validation.d.ts +3 -3
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.js +3 -3
- package/dist/core/image.d.ts +37 -2
- package/dist/core/image.js +6 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +6 -12
- package/dist/core/merge.js +0 -3
- package/dist/core/page-setup.d.ts +4 -4
- package/dist/core/pivot-table.d.ts +2 -4
- package/dist/core/pivot-table.js +5 -5
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +4 -4
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +2 -8
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +14 -14
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +30 -42
- package/dist/core/table.js +16 -10
- package/dist/core/theme.d.ts +13 -43
- package/dist/core/theme.js +2 -148
- package/dist/core/value.d.ts +7 -7
- package/dist/core/workbook-protection.d.ts +2 -2
- package/dist/core/workbook-styles.d.ts +26 -0
- package/dist/core/workbook-styles.js +48 -0
- package/dist/core/workbook-theme.d.ts +25 -0
- package/dist/core/workbook-theme.js +49 -0
- package/dist/core/workbook-vba.d.ts +17 -0
- package/dist/core/workbook-vba.js +79 -0
- package/dist/core/workbook.d.ts +141 -83
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +9 -0
- package/dist/core/worksheet-comments.js +52 -0
- package/dist/core/worksheet-model.d.ts +4 -4
- package/dist/core/worksheet-model.js +1 -0
- package/dist/core/worksheet-pictures.d.ts +29 -0
- package/dist/core/worksheet-pictures.js +42 -0
- package/dist/core/worksheet.d.ts +94 -77
- package/dist/core/worksheet.js +36 -95
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/entries/core.d.ts +4 -4
- package/dist/entries/core.js +1 -1
- package/dist/entries/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -0
- package/dist/entries/xlsx.js +1 -0
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +1 -2
- package/dist/io/csv/write.d.ts +8 -3
- package/dist/io/csv/write.js +17 -2
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/namespaces.d.ts +1 -1
- package/dist/io/opc/read-opc.d.ts +19 -3
- package/dist/io/opc/read-opc.js +14 -8
- package/dist/io/opc/read-options.d.ts +2 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/opc/zip-mtime.d.ts +5 -0
- package/dist/io/opc/zip-mtime.js +1 -0
- package/dist/io/style/xf-style.d.ts +38 -6
- package/dist/io/style/xf-style.js +22 -0
- package/dist/io/xlsb/errors.d.ts +1 -1
- package/dist/io/xlsb/formula.d.ts +4 -4
- package/dist/io/xlsb/primitives.d.ts +8 -8
- package/dist/io/xlsb/ptg-functions.d.ts +1 -1
- package/dist/io/xlsb/read-styles.js +2 -20
- package/dist/io/xlsb/read.d.ts +2 -2
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
- package/dist/io/xlsx/cell-accumulator.js +111 -30
- package/dist/io/xlsx/cell-value.d.ts +2 -2
- package/dist/io/xlsx/cell-value.js +3 -3
- package/dist/io/xlsx/color-xml.js +11 -17
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +13 -6
- package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
- package/dist/io/xlsx/conditional-formatting.js +7 -16
- package/dist/io/xlsx/data-validation.d.ts +2 -2
- package/dist/io/xlsx/data-validation.js +1 -1
- package/dist/io/xlsx/edit-vba.js +2 -1
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +5 -5
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +8 -8
- package/dist/io/xlsx/namespaces.d.ts +2 -2
- package/dist/io/xlsx/package-plan.js +5 -2
- package/dist/io/xlsx/read-pivot.js +2 -5
- package/dist/io/xlsx/read-rows.d.ts +13 -13
- package/dist/io/xlsx/read-rows.js +19 -55
- package/dist/io/xlsx/read-shared-strings.js +7 -5
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +40 -65
- package/dist/io/xlsx/read-worksheet.js +67 -115
- package/dist/io/xlsx/read.d.ts +3 -5
- package/dist/io/xlsx/read.js +94 -71
- package/dist/io/xlsx/rich-runs.d.ts +1 -1
- package/dist/io/xlsx/rich-runs.js +1 -1
- package/dist/io/xlsx/rich-text.d.ts +1 -1
- package/dist/io/xlsx/shared-strings.d.ts +2 -2
- package/dist/io/xlsx/sheet-properties.js +5 -4
- package/dist/io/xlsx/styles.d.ts +8 -9
- package/dist/io/xlsx/styles.js +4 -3
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +8 -13
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +142 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +9 -21
- package/dist/io/xlsx/workbook-xml.d.ts +1 -1
- package/dist/io/xlsx/workbook-xml.js +6 -2
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +7 -12
- package/dist/io/xlsx/write-stream.d.ts +15 -15
- package/dist/io/xlsx/write-stream.js +5 -3
- package/dist/io/xlsx/write.d.ts +12 -7
- package/dist/io/xlsx/write.js +10 -6
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/vba/bytes.d.ts +12 -0
- package/dist/vba/bytes.js +40 -0
- package/dist/vba/cfb-writer.d.ts +1 -1
- package/dist/vba/cfb.d.ts +1 -1
- package/dist/vba/cfb.js +15 -51
- package/dist/vba/codepage.d.ts +2 -2
- package/dist/vba/errors.d.ts +2 -2
- package/dist/vba/index.d.ts +2 -2
- package/dist/vba/index.js +2 -2
- package/dist/vba/ms-ovba.d.ts +1 -1
- package/dist/vba/ms-ovba.js +1 -3
- package/dist/vba/project-editor.d.ts +9 -9
- package/dist/vba/project-editor.js +2 -11
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +1 -10
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +38 -9
- package/dist/xml/xml-read.js +23 -0
- package/dist/xml/xml.d.ts +31 -9
- package/dist/xml/xml.js +23 -3
- 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
|
|
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:
|
|
14
|
-
*
|
|
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
|
|
17
|
-
* in `mentionpersonId
|
|
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
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
1
|
+
import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
|
|
2
2
|
import { MENTION_OFFSET_MAX, } from '../../core/comment-thread.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 =
|
|
90
|
-
const 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
|
-
|
|
131
|
-
|
|
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>${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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})
|
|
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
|
|
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>${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
96
|
-
* `<hyperlinks
|
|
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
|
|
104
|
-
* `<hyperlinks
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/dist/io/xlsx/write.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
75
|
-
* per-sheet XML, styles, theme, media, tables, and props
|
|
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
|
*
|