@shbernal/ts-xlsx 1.3.1 → 2.1.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 +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +72 -3
- package/dist/core/address.js +50 -10
- package/dist/core/autofilter.d.ts +13 -2
- package/dist/core/autofilter.js +35 -10
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +8 -8
- package/dist/core/color-resolution.js +7 -8
- 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 +6 -1
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +48 -8
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +12 -1
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +12 -5
- package/dist/core/data-validation.js +34 -0
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +29 -11
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +39 -2
- package/dist/core/image.js +10 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +17 -12
- package/dist/core/merge.js +36 -5
- package/dist/core/page-setup.d.ts +20 -6
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +4 -4
- package/dist/core/pivot-table.js +29 -20
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +7 -4
- package/dist/core/protection.js +20 -14
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +9 -8
- package/dist/core/row-input.js +12 -10
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +41 -14
- package/dist/core/style.js +82 -67
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +53 -51
- package/dist/core/table.js +83 -41
- package/dist/core/theme.d.ts +16 -44
- package/dist/core/theme.js +6 -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 +133 -87
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +17 -0
- package/dist/core/worksheet-comments.js +70 -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 +100 -79
- package/dist/core/worksheet.js +63 -142
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +7 -7
- package/dist/entries/core.js +1 -1
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/entries/xlsx.js +1 -1
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +2 -3
- package/dist/io/csv/write.d.ts +19 -4
- package/dist/io/csv/write.js +34 -4
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/inflate.js +2 -12
- 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/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/style/xf-style.d.ts +44 -7
- package/dist/io/style/xf-style.js +27 -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 +26 -9
- package/dist/io/xlsx/cell-accumulator.js +89 -46
- package/dist/io/xlsx/cell-value.d.ts +3 -3
- package/dist/io/xlsx/cell-value.js +9 -6
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +18 -23
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +17 -8
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
- package/dist/io/xlsx/conditional-formatting.js +64 -58
- package/dist/io/xlsx/data-validation.d.ts +3 -3
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +10 -18
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +25 -28
- 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 +4 -13
- 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 -44
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +42 -66
- package/dist/io/xlsx/read-worksheet.js +73 -129
- package/dist/io/xlsx/read.d.ts +4 -6
- package/dist/io/xlsx/read.js +104 -142
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- 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.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +14 -10
- package/dist/io/xlsx/styles.d.ts +9 -22
- package/dist/io/xlsx/styles.js +65 -89
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +20 -25
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +145 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +26 -38
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +40 -48
- package/dist/io/xlsx/write-stream.d.ts +23 -16
- package/dist/io/xlsx/write-stream.js +13 -14
- package/dist/io/xlsx/write.d.ts +8 -7
- package/dist/io/xlsx/write.js +6 -3
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +11 -0
- package/dist/vba/bytes.js +29 -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/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- 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 +6 -44
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +3 -28
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +98 -9
- package/dist/xml/xml-read.js +70 -0
- package/dist/xml/xml.d.ts +74 -10
- package/dist/xml/xml.js +39 -7
- package/package.json +59 -36
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { DEFAULT_THEME_COLOR_SCHEME, isThemeColorSlot, 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
|
+
if (slot === undefined || !isThemeColorSlot(slot))
|
|
13
|
+
continue;
|
|
14
|
+
const attrs = match[3] ?? '';
|
|
15
|
+
const source = match[2] === 'sysClr' ? /\blastClr="([^"]*)"/ : /\bval="([^"]*)"/;
|
|
16
|
+
const value = source.exec(attrs)?.[1];
|
|
17
|
+
if (value !== undefined && /^[0-9a-fA-F]{6}$/.test(value))
|
|
18
|
+
scheme[slot] = value;
|
|
19
|
+
}
|
|
20
|
+
return scheme;
|
|
21
|
+
}
|
|
22
|
+
export function parseThemeFontScheme(themeXml) {
|
|
23
|
+
const block = /<a:fontScheme\b[^>]*>([\s\S]*?)<\/a:fontScheme>/.exec(themeXml);
|
|
24
|
+
if (block === null)
|
|
25
|
+
return {};
|
|
26
|
+
const face = (which) => {
|
|
27
|
+
const font = new RegExp(`<a:${which}\\b[^>]*>([\\s\\S]*?)</a:${which}>`).exec(block[1] ?? '');
|
|
28
|
+
const typeface = /<a:latin\b[^>]*\btypeface="([^"]*)"/.exec(font?.[1] ?? '')?.[1];
|
|
29
|
+
return typeface === undefined ? undefined : decodeEntities(typeface);
|
|
30
|
+
};
|
|
31
|
+
const scheme = {};
|
|
32
|
+
const major = face('majorFont');
|
|
33
|
+
const minor = face('minorFont');
|
|
34
|
+
if (major !== undefined)
|
|
35
|
+
scheme.major = major;
|
|
36
|
+
if (minor !== undefined)
|
|
37
|
+
scheme.minor = minor;
|
|
38
|
+
return scheme;
|
|
39
|
+
}
|
|
40
|
+
const SCHEME_ELEMENT_ORDER = [
|
|
41
|
+
'dk1',
|
|
42
|
+
'lt1',
|
|
43
|
+
'dk2',
|
|
44
|
+
'lt2',
|
|
45
|
+
'accent1',
|
|
46
|
+
'accent2',
|
|
47
|
+
'accent3',
|
|
48
|
+
'accent4',
|
|
49
|
+
'accent5',
|
|
50
|
+
'accent6',
|
|
51
|
+
'hlink',
|
|
52
|
+
'folHlink',
|
|
53
|
+
];
|
|
54
|
+
export function applyThemeOverrides(baseXml, overrides) {
|
|
55
|
+
let xml = baseXml;
|
|
56
|
+
const colors = overrides.colors ?? {};
|
|
57
|
+
if (Object.keys(colors).length > 0) {
|
|
58
|
+
const sourceElements = parseThemeColorElements(baseXml);
|
|
59
|
+
const body = SCHEME_ELEMENT_ORDER.map((slot) => {
|
|
60
|
+
const authored = colors[slot];
|
|
61
|
+
const inner = authored !== undefined
|
|
62
|
+
? `<a:srgbClr val="${normalizeThemeColor(authored)}"/>`
|
|
63
|
+
: (sourceElements[slot] ?? `<a:srgbClr val="${DEFAULT_THEME_COLOR_SCHEME[slot]}"/>`);
|
|
64
|
+
return `<a:${slot}>${inner}</a:${slot}>`;
|
|
65
|
+
}).join('');
|
|
66
|
+
xml = replaceBlockBody(xml, 'clrScheme', body);
|
|
67
|
+
}
|
|
68
|
+
const { major, minor } = overrides.fonts ?? {};
|
|
69
|
+
if (major !== undefined)
|
|
70
|
+
xml = replaceLatinTypeface(xml, 'majorFont', major);
|
|
71
|
+
if (minor !== undefined)
|
|
72
|
+
xml = replaceLatinTypeface(xml, 'minorFont', minor);
|
|
73
|
+
return xml;
|
|
74
|
+
}
|
|
75
|
+
function parseThemeColorElements(themeXml) {
|
|
76
|
+
const block = /<a:clrScheme\b[^>]*>([\s\S]*?)<\/a:clrScheme>/.exec(themeXml);
|
|
77
|
+
if (block === null)
|
|
78
|
+
return {};
|
|
79
|
+
const elements = {};
|
|
80
|
+
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;
|
|
81
|
+
for (const match of (block[1] ?? '').matchAll(pattern)) {
|
|
82
|
+
const slot = match[1] ?? match[3];
|
|
83
|
+
if (slot === undefined || !isThemeColorSlot(slot))
|
|
84
|
+
continue;
|
|
85
|
+
const inner = match[2];
|
|
86
|
+
if (inner !== undefined && inner !== '')
|
|
87
|
+
elements[slot] = inner;
|
|
88
|
+
}
|
|
89
|
+
return elements;
|
|
90
|
+
}
|
|
91
|
+
function replaceBlockBody(xml, name, body) {
|
|
92
|
+
const pattern = new RegExp(`(<a:${name}\\b[^>]*>)[\\s\\S]*?(</a:${name}>)`);
|
|
93
|
+
return xml.replace(pattern, (_all, open, close) => `${open}${body}${close}`);
|
|
94
|
+
}
|
|
95
|
+
function replaceLatinTypeface(xml, which, typeface) {
|
|
96
|
+
const pattern = new RegExp(`(<a:${which}\\b[^>]*>[\\s\\S]*?<a:latin\\b)[^>]*(/>)`);
|
|
97
|
+
return xml.replace(pattern, (_all, open, close) => `${open} typeface="${escapeAttr(typeface)}"${close}`);
|
|
98
|
+
}
|
|
99
|
+
export const DEFAULT_THEME_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
100
|
+
'<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">' +
|
|
101
|
+
'<a:themeElements>' +
|
|
102
|
+
'<a:clrScheme name="Office">' +
|
|
103
|
+
'<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>' +
|
|
104
|
+
'<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>' +
|
|
105
|
+
'<a:dk2><a:srgbClr val="44546A"/></a:dk2>' +
|
|
106
|
+
'<a:lt2><a:srgbClr val="E7E6E6"/></a:lt2>' +
|
|
107
|
+
'<a:accent1><a:srgbClr val="4472C4"/></a:accent1>' +
|
|
108
|
+
'<a:accent2><a:srgbClr val="ED7D31"/></a:accent2>' +
|
|
109
|
+
'<a:accent3><a:srgbClr val="A5A5A5"/></a:accent3>' +
|
|
110
|
+
'<a:accent4><a:srgbClr val="FFC000"/></a:accent4>' +
|
|
111
|
+
'<a:accent5><a:srgbClr val="5B9BD5"/></a:accent5>' +
|
|
112
|
+
'<a:accent6><a:srgbClr val="70AD47"/></a:accent6>' +
|
|
113
|
+
'<a:hlink><a:srgbClr val="0563C1"/></a:hlink>' +
|
|
114
|
+
'<a:folHlink><a:srgbClr val="954F72"/></a:folHlink>' +
|
|
115
|
+
'</a:clrScheme>' +
|
|
116
|
+
'<a:fontScheme name="Office">' +
|
|
117
|
+
'<a:majorFont><a:latin typeface="Calibri Light" panose="020F0302020204030204"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>' +
|
|
118
|
+
'<a:minorFont><a:latin typeface="Calibri" panose="020F0502020204030204"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>' +
|
|
119
|
+
'</a:fontScheme>' +
|
|
120
|
+
'<a:fmtScheme name="Office">' +
|
|
121
|
+
'<a:fillStyleLst>' +
|
|
122
|
+
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>' +
|
|
123
|
+
'<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>' +
|
|
124
|
+
'<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>' +
|
|
125
|
+
'</a:fillStyleLst>' +
|
|
126
|
+
'<a:lnStyleLst>' +
|
|
127
|
+
'<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>' +
|
|
128
|
+
'<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>' +
|
|
129
|
+
'<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>' +
|
|
130
|
+
'</a:lnStyleLst>' +
|
|
131
|
+
'<a:effectStyleLst>' +
|
|
132
|
+
'<a:effectStyle><a:effectLst/></a:effectStyle>' +
|
|
133
|
+
'<a:effectStyle><a:effectLst/></a:effectStyle>' +
|
|
134
|
+
'<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>' +
|
|
135
|
+
'</a:effectStyleLst>' +
|
|
136
|
+
'<a:bgFillStyleLst>' +
|
|
137
|
+
'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>' +
|
|
138
|
+
'<a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill>' +
|
|
139
|
+
'<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>' +
|
|
140
|
+
'</a:bgFillStyleLst>' +
|
|
141
|
+
'</a:fmtScheme>' +
|
|
142
|
+
'</a:themeElements>' +
|
|
143
|
+
'<a:objectDefaults/>' +
|
|
144
|
+
'<a:extraClrSchemeLst/>' +
|
|
145
|
+
'</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, TextCapture, } from '../../xml/xml-read.js';
|
|
4
|
+
import { escapeAttr, escapeSpreadsheetText, textAttr, 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 = [];
|
|
@@ -27,33 +27,33 @@ export function parseThreadedComments(xml) {
|
|
|
27
27
|
const messages = [];
|
|
28
28
|
let open;
|
|
29
29
|
let mentions = [];
|
|
30
|
-
let inText = false;
|
|
31
30
|
let text = '';
|
|
31
|
+
const capture = new TextCapture('text');
|
|
32
32
|
parseXml(xml, {
|
|
33
|
-
onOpen(name, attrs) {
|
|
33
|
+
onOpen(name, attrs, selfClosing) {
|
|
34
34
|
const local = localName(name);
|
|
35
35
|
if (local === 'threadedComment') {
|
|
36
36
|
open = attrs;
|
|
37
37
|
text = '';
|
|
38
38
|
mentions = [];
|
|
39
39
|
}
|
|
40
|
-
else if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
else if (open !== undefined) {
|
|
41
|
+
capture.open(local, selfClosing);
|
|
42
|
+
if (local === 'mention') {
|
|
43
|
+
const mention = mentionFrom(attrs);
|
|
44
|
+
if (mention !== undefined)
|
|
45
|
+
mentions.push(mention);
|
|
46
|
+
}
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
onText(chunk) {
|
|
50
|
-
|
|
51
|
-
text += chunk;
|
|
50
|
+
capture.text(chunk);
|
|
52
51
|
},
|
|
53
52
|
onClose(name) {
|
|
54
53
|
const local = localName(name);
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const body = capture.close(local);
|
|
55
|
+
if (body !== undefined) {
|
|
56
|
+
text = body;
|
|
57
57
|
}
|
|
58
58
|
else if (local === 'threadedComment') {
|
|
59
59
|
if (open !== undefined) {
|
|
@@ -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);
|
|
@@ -171,10 +159,10 @@ export function threadedCommentsXml(threads) {
|
|
|
171
159
|
`<ThreadedComments xmlns="${THREADED_COMMENTS_NS}">${messages.join('')}</ThreadedComments>`);
|
|
172
160
|
}
|
|
173
161
|
function threadedCommentXml(ref, comment, tail) {
|
|
174
|
-
const date =
|
|
175
|
-
const person =
|
|
162
|
+
const date = textAttr('dT', comment.date);
|
|
163
|
+
const person = textAttr('personId', 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
|
}
|
|
@@ -196,8 +184,8 @@ function writableOffset(value) {
|
|
|
196
184
|
}
|
|
197
185
|
export function personsXml(persons) {
|
|
198
186
|
const entries = persons.map((person) => {
|
|
199
|
-
const userId =
|
|
200
|
-
const providerId =
|
|
187
|
+
const userId = textAttr('userId', person.userId);
|
|
188
|
+
const providerId = textAttr('providerId', person.providerId);
|
|
201
189
|
return (`<person displayName="${escapeAttr(person.displayName)}"` +
|
|
202
190
|
` id="${escapeAttr(person.id)}"${userId}${providerId}/>`);
|
|
203
191
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mangleFormula, quoteSheetName } from '../../core/formula.js';
|
|
2
2
|
import { WORKBOOK_PROTECTION_CREDENTIAL_ATTRS } from '../../core/workbook-protection.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isVisibility } from '../../core/worksheet.js';
|
|
4
|
+
import { checkedToken, escapeAttr, escapeText, numAttr, textAttr, XML_DECLARATION, } from '../../xml/xml.js';
|
|
4
5
|
import { extensionOf, relativePartPath, THEME_PART_PATH } from '../opc/part-paths.js';
|
|
5
6
|
import { relationship, relationshipsPart } from '../opc/rels.js';
|
|
6
7
|
import { imageContentType } from './images.js';
|
|
@@ -102,14 +103,16 @@ export function rootRelsXml(rootRefs) {
|
|
|
102
103
|
relationship('rId1', REL.officeDocument, 'xl/workbook.xml'),
|
|
103
104
|
relationship('rId2', REL.coreProps, 'docProps/core.xml'),
|
|
104
105
|
relationship('rId3', REL.extProps, 'docProps/app.xml'),
|
|
105
|
-
...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType,
|
|
106
|
+
...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType, ref.entryPath)),
|
|
106
107
|
]);
|
|
107
108
|
}
|
|
108
109
|
export function workbookXml(workbook, preservedRels, pivots) {
|
|
109
110
|
const sheets = workbook.worksheets;
|
|
110
111
|
const entries = sheets
|
|
111
112
|
.map((sheet, i) => {
|
|
112
|
-
const state = sheet.state === 'visible'
|
|
113
|
+
const state = sheet.state === 'visible'
|
|
114
|
+
? ''
|
|
115
|
+
: ` state="${checkedToken(sheet.state, isVisibility, 'sheet visibility')}"`;
|
|
113
116
|
return `<sheet name="${escapeAttr(sheet.name)}" sheetId="${sheet.id}"${state} r:id="rId${i + 1}"/>`;
|
|
114
117
|
})
|
|
115
118
|
.join('');
|
|
@@ -128,11 +131,14 @@ export function workbookXml(workbook, preservedRels, pivots) {
|
|
|
128
131
|
function bookViewsXml(workbook) {
|
|
129
132
|
const view = workbook.view;
|
|
130
133
|
const activeTab = workbook.activeTabIndex;
|
|
131
|
-
const attrs = (view.visibility
|
|
132
|
-
?
|
|
133
|
-
: '') +
|
|
134
|
+
const attrs = (view.visibility === undefined || view.visibility === 'visible'
|
|
135
|
+
? ''
|
|
136
|
+
: ` visibility="${checkedToken(view.visibility, isVisibility, 'window visibility')}"`) +
|
|
134
137
|
(view.minimized ? ' minimized="1"' : '') +
|
|
135
|
-
|
|
138
|
+
numAttr('xWindow', view.x) +
|
|
139
|
+
numAttr('yWindow', view.y) +
|
|
140
|
+
numAttr('windowWidth', view.width) +
|
|
141
|
+
numAttr('windowHeight', view.height) +
|
|
136
142
|
(activeTab === 0 ? '' : ` activeTab="${activeTab}"`);
|
|
137
143
|
return `<bookViews><workbookView${attrs}/></bookViews>`;
|
|
138
144
|
}
|
|
@@ -192,7 +198,7 @@ function definedNamesXml(workbook) {
|
|
|
192
198
|
const scopeAttr = name.scope === undefined
|
|
193
199
|
? ''
|
|
194
200
|
: ` localSheetId="${sheets.findIndex((sheet) => sheet.name === name.scope)}"`;
|
|
195
|
-
const commentAttr =
|
|
201
|
+
const commentAttr = textAttr('comment', name.comment);
|
|
196
202
|
const hiddenAttr = name.hidden ? ' hidden="1"' : '';
|
|
197
203
|
return (`<definedName name="${escapeAttr(name.name)}"${scopeAttr}${commentAttr}${hiddenAttr}>` +
|
|
198
204
|
`${escapeText(mangleFormula(name.refersTo))}</definedName>`);
|
|
@@ -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,11 @@
|
|
|
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
|
+
import { CELL_STYLE_FACETS, pickStyleFacets } from '../../core/style.js';
|
|
5
6
|
import { detectValueType, isDataTableFormulaValue, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
|
|
6
7
|
import { AuthoringError, InternalError } from '../../errors.js';
|
|
7
|
-
import { escapeAttr, escapeText, numberText, textElement, XML_DECLARATION } from '../../xml/xml.js';
|
|
8
|
+
import { assertWritableNumber, escapeAttr, escapeSpreadsheetText, escapeText, numberText, textAttr, textElement, XML_DECLARATION, } from '../../xml/xml.js';
|
|
8
9
|
import { relativePartPath } from '../opc/part-paths.js';
|
|
9
10
|
import { relationship, relationshipsPart } from '../opc/rels.js';
|
|
10
11
|
import { conditionalFormattingsExtXml, conditionalFormattingsXml } from './conditional-formatting.js';
|
|
@@ -136,16 +137,20 @@ export function renderRow(entry, ctx) {
|
|
|
136
137
|
return { xml: `<row r="${number}"${attrs}>${cellsXml}</row>`, minCol, maxCol };
|
|
137
138
|
}
|
|
138
139
|
function composeCellStyle(cell, rowFill, colDef) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
const style = {};
|
|
141
|
+
for (const facet of CELL_STYLE_FACETS)
|
|
142
|
+
inheritFacet(style, facet, cell, colDef);
|
|
143
|
+
style.fill = cell.fill ?? rowFill ?? colDef?.fill;
|
|
144
|
+
style.numFmt = cell.numFmt ?? colDef?.numFmt ?? dateDefaultNumFmt(cell.value);
|
|
145
|
+
if (cell.quotePrefix !== undefined)
|
|
146
|
+
style.quotePrefix = cell.quotePrefix;
|
|
147
|
+
const xfId = cell[NAMED_STYLE_ID];
|
|
148
|
+
if (xfId !== undefined)
|
|
149
|
+
style.xfId = xfId;
|
|
150
|
+
return style;
|
|
151
|
+
}
|
|
152
|
+
function inheritFacet(target, key, cell, colDef) {
|
|
153
|
+
target[key] = cell[key] ?? colDef?.[key];
|
|
149
154
|
}
|
|
150
155
|
function worksheetExtLstXml(sheet, slicerRelIds) {
|
|
151
156
|
const exts = [
|
|
@@ -175,7 +180,7 @@ function validateMerges(sheet) {
|
|
|
175
180
|
top <= region.bottom &&
|
|
176
181
|
bottom >= region.top;
|
|
177
182
|
if (overlaps) {
|
|
178
|
-
throw new AuthoringError(`merged range ${merge} overlaps table "${table.name}" (${table.range})
|
|
183
|
+
throw new AuthoringError(`merged range ${merge} overlaps table "${table.name}" (${table.range}): Excel forbids a merge inside a table`);
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
}
|
|
@@ -228,7 +233,7 @@ export function worksheetRelsXml(tables, drawing, comments, threadedComments, pr
|
|
|
228
233
|
...preservedReferences.map((reference) => relationship(reference.relId, reference.relType, escapeAttr(relativePartPath('xl/worksheets/sheet1.xml', reference.entryPath)))),
|
|
229
234
|
...hyperlinks
|
|
230
235
|
.filter((link) => link.relId !== undefined && link.target !== undefined)
|
|
231
|
-
.map((link) => relationship(link.relId, REL.hyperlink,
|
|
236
|
+
.map((link) => relationship(link.relId, REL.hyperlink, link.target, {
|
|
232
237
|
external: true,
|
|
233
238
|
})),
|
|
234
239
|
];
|
|
@@ -243,26 +248,25 @@ function sheetFormatPr(properties, outlineLevel) {
|
|
|
243
248
|
}
|
|
244
249
|
if (properties.defaultRowHeight !== undefined)
|
|
245
250
|
attrs += ' customHeight="1"';
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (outlineLevel.row > 0)
|
|
249
|
-
attrs += ` outlineLevelRow="${outlineLevel.row}"`;
|
|
251
|
+
attrs += outlineAttr('outlineLevelCol', outlineLevel.col);
|
|
252
|
+
attrs += outlineAttr('outlineLevelRow', outlineLevel.row);
|
|
250
253
|
return `<sheetFormatPr${attrs}/>`;
|
|
251
254
|
}
|
|
255
|
+
function outlineAttr(name, level) {
|
|
256
|
+
if (level === undefined)
|
|
257
|
+
return '';
|
|
258
|
+
const text = numberText(level);
|
|
259
|
+
return level > 0 ? ` ${name}="${text}"` : '';
|
|
260
|
+
}
|
|
252
261
|
function maxColumnOutlineLevel(sheet) {
|
|
253
262
|
let max = 0;
|
|
254
|
-
for (const column of sheet.columns())
|
|
255
|
-
if (column.index > MAX_COLUMN)
|
|
256
|
-
continue;
|
|
263
|
+
for (const column of sheet.columns())
|
|
257
264
|
max = Math.max(max, column.outlineLevel ?? 0);
|
|
258
|
-
}
|
|
259
265
|
return max;
|
|
260
266
|
}
|
|
261
267
|
function colsXml(sheet, styles) {
|
|
262
268
|
const runs = [];
|
|
263
269
|
for (const { index, properties } of sheet.columns()) {
|
|
264
|
-
if (index > MAX_COLUMN)
|
|
265
|
-
continue;
|
|
266
270
|
const body = colBody(properties ?? {}, styles);
|
|
267
271
|
if (body === null)
|
|
268
272
|
continue;
|
|
@@ -290,22 +294,16 @@ function colBody(properties, styles) {
|
|
|
290
294
|
attrs += ' hidden="1"';
|
|
291
295
|
meaningful = true;
|
|
292
296
|
}
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
const outline = outlineAttr('outlineLevel', properties.outlineLevel);
|
|
298
|
+
if (outline !== '') {
|
|
299
|
+
attrs += outline;
|
|
295
300
|
meaningful = true;
|
|
296
301
|
}
|
|
297
302
|
if (properties.collapsed) {
|
|
298
303
|
attrs += ' collapsed="1"';
|
|
299
304
|
meaningful = true;
|
|
300
305
|
}
|
|
301
|
-
const style = styles.styleId(
|
|
302
|
-
fill: properties.fill,
|
|
303
|
-
numFmt: properties.numFmt,
|
|
304
|
-
font: properties.font,
|
|
305
|
-
border: properties.border,
|
|
306
|
-
alignment: properties.alignment,
|
|
307
|
-
protection: properties.protection,
|
|
308
|
-
});
|
|
306
|
+
const style = styles.styleId(pickStyleFacets(properties));
|
|
309
307
|
if (style !== 0) {
|
|
310
308
|
attrs += ` style="${style}"`;
|
|
311
309
|
meaningful = true;
|
|
@@ -320,9 +318,7 @@ function rowAttrs(properties, styles, collapsedSummary) {
|
|
|
320
318
|
attrs += ` ht="${numberText(properties.height)}" customHeight="1"`;
|
|
321
319
|
if (properties.hidden)
|
|
322
320
|
attrs += ' hidden="1"';
|
|
323
|
-
|
|
324
|
-
attrs += ` outlineLevel="${properties.outlineLevel}"`;
|
|
325
|
-
}
|
|
321
|
+
attrs += outlineAttr('outlineLevel', properties.outlineLevel);
|
|
326
322
|
if (properties.collapsed || collapsedSummary)
|
|
327
323
|
attrs += ' collapsed="1"';
|
|
328
324
|
const style = styles.styleId({ fill: properties.fill });
|
|
@@ -336,6 +332,7 @@ function scanRowOutline(sheet) {
|
|
|
336
332
|
let maxLevel = 0;
|
|
337
333
|
for (const { number, properties } of sheet.rows()) {
|
|
338
334
|
const rowLevel = properties?.outlineLevel ?? 0;
|
|
335
|
+
assertWritableNumber(rowLevel);
|
|
339
336
|
level.set(number, rowLevel);
|
|
340
337
|
hidden.set(number, properties?.hidden ?? false);
|
|
341
338
|
if (rowLevel > maxLevel)
|
|
@@ -410,15 +407,10 @@ function cellXml(cell, style, shared, sharedStrings) {
|
|
|
410
407
|
}
|
|
411
408
|
if (value === null)
|
|
412
409
|
return `<c r="${ref}"${s}/>`;
|
|
413
|
-
throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm
|
|
410
|
+
throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm: every CellValue kind is handled above`);
|
|
414
411
|
}
|
|
415
412
|
function hasOwnStyle(cell) {
|
|
416
|
-
return (
|
|
417
|
-
cell.numFmt !== undefined ||
|
|
418
|
-
cell.font !== undefined ||
|
|
419
|
-
cell.border !== undefined ||
|
|
420
|
-
cell.alignment !== undefined ||
|
|
421
|
-
cell.protection !== undefined ||
|
|
413
|
+
return (CELL_STYLE_FACETS.some((facet) => cell[facet] !== undefined) ||
|
|
422
414
|
cell.quotePrefix === true ||
|
|
423
415
|
cell[NAMED_STYLE_ID] !== undefined);
|
|
424
416
|
}
|
|
@@ -435,8 +427,8 @@ function cellFormulaXml(ref, s, value, shared) {
|
|
|
435
427
|
const attrs = `ref="${escapeAttr(value.ref)}"` +
|
|
436
428
|
` dt2D="${value.dataTable2D ? 1 : 0}"` +
|
|
437
429
|
` dtr="${value.dataTableRow ? 1 : 0}"` +
|
|
438
|
-
(
|
|
439
|
-
(
|
|
430
|
+
textAttr('r1', value.r1) +
|
|
431
|
+
textAttr('r2', value.r2);
|
|
440
432
|
return formulaBodyXml(ref, s, `<f t="dataTable" ${attrs}/>`, value.result);
|
|
441
433
|
}
|
|
442
434
|
if (isFormulaValue(value)) {
|
|
@@ -455,7 +447,7 @@ function formulaBodyXml(ref, s, f, result) {
|
|
|
455
447
|
return `<c r="${ref}"${s} t="b">${f}<v>${result ? 1 : 0}</v></c>`;
|
|
456
448
|
}
|
|
457
449
|
if (typeof result === 'string') {
|
|
458
|
-
return `<c r="${ref}"${s} t="str">${f}<v>${
|
|
450
|
+
return `<c r="${ref}"${s} t="str">${f}<v>${escapeSpreadsheetText(result)}</v></c>`;
|
|
459
451
|
}
|
|
460
452
|
if (isErrorValue(result)) {
|
|
461
453
|
return `<c r="${ref}"${s} t="e">${f}<v>${result.error}</v></c>`;
|
|
@@ -465,5 +457,5 @@ function formulaBodyXml(ref, s, f, result) {
|
|
|
465
457
|
return `<c r="${ref}"${s}>${f}</c>`;
|
|
466
458
|
return `<c r="${ref}"${s}>${f}<v>${numberText(dateToSerial(result))}</v></c>`;
|
|
467
459
|
}
|
|
468
|
-
throw new InternalError('writing a non-primitive formula result has no arm
|
|
460
|
+
throw new InternalError('writing a non-primitive formula result has no arm: every FormulaResult kind is handled above');
|
|
469
461
|
}
|