@shbernal/ts-xlsx 2.1.0 → 3.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 +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- package/dist/core/autofilter.d.ts +5 -4
- package/dist/core/autofilter.js +34 -35
- package/dist/core/axis-handle.d.ts +10 -0
- package/dist/core/axis-handle.js +17 -0
- package/dist/core/cell.d.ts +25 -0
- package/dist/core/cell.js +9 -3
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +2 -4
- package/dist/core/column.d.ts +5 -1
- package/dist/core/column.js +34 -35
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +14 -6
- package/dist/core/conditional-formatting.js +45 -43
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +9 -3
- package/dist/core/data-validation.js +21 -18
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +14 -10
- package/dist/core/image.js +9 -9
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +5 -14
- package/dist/core/merge.js +18 -27
- package/dist/core/page-setup.d.ts +72 -2
- package/dist/core/page-setup.js +34 -8
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +67 -53
- package/dist/core/protection.js +2 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +5 -1
- package/dist/core/row.js +22 -23
- package/dist/core/style.d.ts +134 -75
- package/dist/core/style.js +112 -53
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +28 -10
- package/dist/core/table.js +54 -39
- package/dist/core/theme.js +6 -5
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +72 -12
- package/dist/core/worksheet.js +68 -70
- package/dist/customui/ribbon.js +6 -7
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- package/dist/io/opc/inflate.js +15 -1
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +10 -0
- package/dist/io/opc/part-paths.js +16 -1
- package/dist/io/opc/read-opc.d.ts +69 -10
- package/dist/io/opc/read-opc.js +49 -55
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/rels.d.ts +1 -6
- package/dist/io/opc/rels.js +1 -4
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/style/xf-style.d.ts +48 -2
- package/dist/io/style/xf-style.js +14 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +49 -34
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +385 -596
- package/dist/io/xlsb/read-styles.js +50 -83
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +47 -29
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -31
- package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
- package/dist/io/xlsx/conditional-formatting.js +51 -46
- package/dist/io/xlsx/data-validation.d.ts +9 -7
- package/dist/io/xlsx/data-validation.js +38 -35
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +15 -29
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +41 -7
- package/dist/io/xlsx/hyperlinks.js +40 -28
- package/dist/io/xlsx/images.js +24 -25
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +23 -3
- package/dist/io/xlsx/package-plan.js +66 -25
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +3 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +50 -37
- package/dist/io/xlsx/read-shared-strings.js +5 -3
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +65 -109
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +11 -1
- package/dist/io/xlsx/read-worksheet.js +51 -193
- package/dist/io/xlsx/read.d.ts +5 -13
- package/dist/io/xlsx/read.js +111 -436
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +166 -37
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -204
- package/dist/io/xlsx/tables.js +11 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +3 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +84 -57
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +59 -242
- package/dist/io/xlsx/write-stream.d.ts +71 -16
- package/dist/io/xlsx/write-stream.js +79 -41
- package/dist/io/xlsx/write.d.ts +6 -2
- package/dist/io/xlsx/write.js +217 -153
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +28 -0
- package/dist/token-set.js +7 -0
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +32 -0
- package/dist/vba/cfb-format.js +13 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +28 -25
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -57
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +79 -23
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +40 -21
- package/dist/vba/vba-encoding.js +5 -6
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +128 -81
- package/dist/xml/xml-read.js +161 -178
- package/dist/xml/xml-scan.d.ts +78 -0
- package/dist/xml/xml-scan.js +180 -0
- package/dist/xml/xml.d.ts +29 -28
- package/dist/xml/xml.js +24 -24
- package/package.json +13 -9
|
@@ -1,41 +1,85 @@
|
|
|
1
1
|
import { DEFAULT_THEME_COLOR_SCHEME, isThemeColorSlot, normalizeThemeColor, } from '../../core/theme.js';
|
|
2
|
-
import {
|
|
2
|
+
import { elementRange, parseXml } from '../../xml/xml-read.js';
|
|
3
|
+
import { localName } from '../../xml/xml-scan.js';
|
|
3
4
|
import { escapeAttr } from '../../xml/xml.js';
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const COLOR_ELEMENTS = new Set(['srgbClr', 'sysClr']);
|
|
6
|
+
function readThemeScheme(themeXml) {
|
|
7
|
+
const colors = {};
|
|
8
|
+
const elements = {};
|
|
9
|
+
const fonts = {};
|
|
10
|
+
let prefix = '';
|
|
11
|
+
let container;
|
|
12
|
+
let slot;
|
|
13
|
+
let fontSlot;
|
|
14
|
+
parseXml(themeXml, {
|
|
15
|
+
onOpen(name, attrs, _selfClosing, scope) {
|
|
16
|
+
const local = localName(name);
|
|
17
|
+
if (local === 'theme') {
|
|
18
|
+
const colon = name.indexOf(':');
|
|
19
|
+
prefix = colon === -1 ? '' : name.slice(0, colon + 1);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (local === 'clrScheme' || local === 'fontScheme') {
|
|
23
|
+
container = local;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (container === 'clrScheme') {
|
|
27
|
+
if (isThemeColorSlot(local)) {
|
|
28
|
+
slot = local;
|
|
29
|
+
}
|
|
30
|
+
else if (slot !== undefined && COLOR_ELEMENTS.has(local)) {
|
|
31
|
+
const value = local === 'sysClr' ? attrs.lastClr : attrs.val;
|
|
32
|
+
if (value !== undefined && /^[0-9a-fA-F]{6}$/.test(value))
|
|
33
|
+
colors[slot] = value;
|
|
34
|
+
elements[slot] = emptyElement(name, attrs);
|
|
35
|
+
slot = undefined;
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (container === 'fontScheme') {
|
|
40
|
+
if (local === 'majorFont')
|
|
41
|
+
fontSlot = 'major';
|
|
42
|
+
else if (local === 'minorFont')
|
|
43
|
+
fontSlot = 'minor';
|
|
44
|
+
else if (local === 'latin' && fontSlot !== undefined && attrs.typeface !== undefined) {
|
|
45
|
+
fonts[fontSlot] ??= attrs.typeface;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
void scope;
|
|
49
|
+
},
|
|
50
|
+
onClose(name) {
|
|
51
|
+
const local = localName(name);
|
|
52
|
+
if (local === 'clrScheme' || local === 'fontScheme') {
|
|
53
|
+
container = undefined;
|
|
54
|
+
slot = undefined;
|
|
55
|
+
fontSlot = undefined;
|
|
56
|
+
}
|
|
57
|
+
else if (local === 'majorFont' || local === 'minorFont') {
|
|
58
|
+
fontSlot = undefined;
|
|
59
|
+
}
|
|
60
|
+
else if (slot !== undefined && local === slot) {
|
|
61
|
+
slot = undefined;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
9
65
|
const scheme = {};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return
|
|
66
|
+
if (fonts.major !== undefined)
|
|
67
|
+
scheme.major = fonts.major;
|
|
68
|
+
if (fonts.minor !== undefined)
|
|
69
|
+
scheme.minor = fonts.minor;
|
|
70
|
+
return { colors, elements, fonts: scheme, prefix };
|
|
71
|
+
}
|
|
72
|
+
function emptyElement(name, attrs) {
|
|
73
|
+
let out = `<${name}`;
|
|
74
|
+
for (const key in attrs)
|
|
75
|
+
out += ` ${key}="${escapeAttr(attrs[key] ?? '')}"`;
|
|
76
|
+
return `${out}/>`;
|
|
77
|
+
}
|
|
78
|
+
export function parseThemeColorScheme(themeXml) {
|
|
79
|
+
return readThemeScheme(themeXml).colors;
|
|
21
80
|
}
|
|
22
81
|
export function parseThemeFontScheme(themeXml) {
|
|
23
|
-
|
|
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;
|
|
82
|
+
return readThemeScheme(themeXml).fonts;
|
|
39
83
|
}
|
|
40
84
|
const SCHEME_ELEMENT_ORDER = [
|
|
41
85
|
'dk1',
|
|
@@ -52,49 +96,43 @@ const SCHEME_ELEMENT_ORDER = [
|
|
|
52
96
|
'folHlink',
|
|
53
97
|
];
|
|
54
98
|
export function applyThemeOverrides(baseXml, overrides) {
|
|
55
|
-
|
|
99
|
+
const { elements: sourceElements, prefix } = readThemeScheme(baseXml);
|
|
100
|
+
const edits = [];
|
|
56
101
|
const colors = overrides.colors ?? {};
|
|
57
102
|
if (Object.keys(colors).length > 0) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
103
|
+
const scheme = elementRange(baseXml, ['clrScheme']);
|
|
104
|
+
if (scheme !== undefined) {
|
|
105
|
+
const body = SCHEME_ELEMENT_ORDER.map((slot) => {
|
|
106
|
+
const authored = colors[slot];
|
|
107
|
+
const inner = authored !== undefined
|
|
108
|
+
? `<${prefix}srgbClr val="${normalizeThemeColor(authored)}"/>`
|
|
109
|
+
: (sourceElements[slot] ??
|
|
110
|
+
`<${prefix}srgbClr val="${DEFAULT_THEME_COLOR_SCHEME[slot]}"/>`);
|
|
111
|
+
return `<${prefix}${slot}>${inner}</${prefix}${slot}>`;
|
|
112
|
+
}).join('');
|
|
113
|
+
edits.push({ start: scheme.contentStart, end: scheme.contentEnd, text: body });
|
|
114
|
+
}
|
|
67
115
|
}
|
|
68
116
|
const { major, minor } = overrides.fonts ?? {};
|
|
117
|
+
const latin = (which, typeface) => {
|
|
118
|
+
const found = elementRange(baseXml, [which, 'latin']);
|
|
119
|
+
if (found === undefined)
|
|
120
|
+
return;
|
|
121
|
+
const attrs = { ...found.attrs, typeface };
|
|
122
|
+
let rendered = `<${found.name}`;
|
|
123
|
+
for (const key in attrs)
|
|
124
|
+
rendered += ` ${key}="${escapeAttr(attrs[key] ?? '')}"`;
|
|
125
|
+
edits.push({ start: found.start, end: found.end, text: `${rendered}/>` });
|
|
126
|
+
};
|
|
69
127
|
if (major !== undefined)
|
|
70
|
-
|
|
128
|
+
latin('majorFont', major);
|
|
71
129
|
if (minor !== undefined)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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;
|
|
130
|
+
latin('minorFont', minor);
|
|
131
|
+
let xml = baseXml;
|
|
132
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
133
|
+
xml = xml.slice(0, edit.start) + edit.text + xml.slice(edit.end);
|
|
88
134
|
}
|
|
89
|
-
return
|
|
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}`);
|
|
135
|
+
return xml;
|
|
98
136
|
}
|
|
99
137
|
export const DEFAULT_THEME_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' +
|
|
100
138
|
'<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">' +
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { type CommentThread, type MentionRef, type Person } from '../../core/comment-thread.ts';
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
readonly providerId?: string;
|
|
11
|
-
}
|
|
2
|
+
/**
|
|
3
|
+
* A registered author of threaded comments: one `<person>` of `xl/persons/person.xml`, which is the
|
|
4
|
+
* model's own {@link Person} with nothing added. Declared as an alias for the same reason
|
|
5
|
+
* {@link ParsedMention} is: this file's write half already emits `Person` directly, so re-declaring
|
|
6
|
+
* the four fields here made one feature module name one shape two ways, with nothing keeping the two
|
|
7
|
+
* in step.
|
|
8
|
+
*/
|
|
9
|
+
export type ParsedPerson = Person;
|
|
12
10
|
/**
|
|
13
11
|
* One `<mention>` of a message's `<mentions>` block: the model's own {@link MentionRef}, which is
|
|
14
12
|
* exactly the four wire attributes with nothing resolved yet. Declaring it a second time here would
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
|
|
2
2
|
import { MENTION_OFFSET_MAX, } from '../../core/comment-thread.js';
|
|
3
|
-
import {
|
|
3
|
+
import { decodeSpreadsheetText, numInteger } from '../../xml/xml-attrs.js';
|
|
4
|
+
import { parseXml, TextCapture } from '../../xml/xml-read.js';
|
|
5
|
+
import { boolStrict, localName } from '../../xml/xml-scan.js';
|
|
4
6
|
import { escapeAttr, escapeSpreadsheetText, textAttr, XML_DECLARATION } from '../../xml/xml.js';
|
|
5
7
|
import { THREADED_COMMENTS_NS } from './namespaces.js';
|
|
6
8
|
export function parsePersons(xml) {
|
|
@@ -3,10 +3,60 @@ import type { PivotPlan, PreservedPartPlan, PreservedRootReferencePlan, Preserve
|
|
|
3
3
|
export type PreservedWorkbookRel = PreservedWorkbookReferencePlan & {
|
|
4
4
|
readonly relId: string;
|
|
5
5
|
};
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The workbook part's relationship ids, drawn once by the writer's `assignWorkbookRelIds` and handed
|
|
8
|
+
* to every consumer rather than re-derived by any of them.
|
|
9
|
+
*
|
|
10
|
+
* Declared here because both consumers are here: the rels part wires the ids, and the workbook body
|
|
11
|
+
* *references* them from `<sheet r:id>`. The body used to spell that reference `rId${i + 1}`, which
|
|
12
|
+
* agreed with the allocator only because sheets happen to be the first ids it hands out. Anything
|
|
13
|
+
* laid ahead of them would have re-pointed every sheet at the wrong part, and the package would have
|
|
14
|
+
* stayed schema-valid while doing it -- a class of corruption no validator catches and no round-trip
|
|
15
|
+
* through this library notices, because the reader resolves the same wrong ids consistently.
|
|
16
|
+
*/
|
|
17
|
+
export interface WorkbookRelPlan {
|
|
18
|
+
readonly sheetRelIds: readonly string[];
|
|
19
|
+
readonly stylesRelId: string;
|
|
20
|
+
readonly themeRelId: string;
|
|
21
|
+
readonly sharedStringsRelId: string | null;
|
|
22
|
+
readonly personsRelId: string | null;
|
|
23
|
+
readonly preservedWorkbookRels: readonly PreservedWorkbookRel[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What `[Content_Types].xml` needs to know about a package: every part family it declares.
|
|
27
|
+
*
|
|
28
|
+
* One object rather than twelve positional parameters, eight of them arrays and four of those bare
|
|
29
|
+
* `readonly number[]`, which two adjacent ones could be silently transposed between. That is the same
|
|
30
|
+
* hazard `SheetReferences`, `SheetPlan` and `WorksheetXmlInputs` remove elsewhere in this writer, and
|
|
31
|
+
* it travels the whole way down: the two functions that render from this take it as it stands rather
|
|
32
|
+
* than re-scattering its fields across their own parameter lists.
|
|
33
|
+
*/
|
|
34
|
+
export interface ContentTypeInputs {
|
|
35
|
+
readonly sheetCount: number;
|
|
36
|
+
readonly tables: readonly TablePlan[];
|
|
37
|
+
readonly commentNumbers: readonly number[];
|
|
38
|
+
readonly drawingNumbers: readonly number[];
|
|
39
|
+
readonly printerSettingsNumbers: readonly number[];
|
|
40
|
+
readonly mediaExtensions: readonly string[];
|
|
41
|
+
readonly hasSharedStrings: boolean;
|
|
42
|
+
readonly preservedParts: readonly PreservedPartPlan[];
|
|
43
|
+
readonly pivots: readonly PivotPlan[];
|
|
44
|
+
readonly preservedWorkbookRefs: readonly PreservedWorkbookReferencePlan[];
|
|
45
|
+
readonly threadedCommentNumbers: readonly number[];
|
|
46
|
+
readonly hasPersons: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function contentTypesXml(inputs: ContentTypeInputs): string;
|
|
7
49
|
export declare function rootRelsXml(rootRefs: readonly PreservedRootReferencePlan[]): string;
|
|
8
|
-
export declare function workbookXml(workbook: Workbook,
|
|
9
|
-
|
|
10
|
-
|
|
50
|
+
export declare function workbookXml(workbook: Workbook, plan: WorkbookRelPlan, pivots: readonly PivotPlan[]): string;
|
|
51
|
+
/**
|
|
52
|
+
* The workbook part's `.rels`, rendered from the ids the planner drew.
|
|
53
|
+
*
|
|
54
|
+
* Every id arrives in `plan`; none is computed here. It used to re-derive the styles, theme and
|
|
55
|
+
* shared-strings ids from the sheet count and a shared constant while `write.ts` independently summed
|
|
56
|
+
* the same inputs to place everything after them, so one sequence was spelled in two files and only
|
|
57
|
+
* its fixed part was shared. The relationship *order* is still stated here, because that is what this
|
|
58
|
+
* function is; the numbering is not, because two numberings of one sequence is how they drift apart.
|
|
59
|
+
*/
|
|
60
|
+
export declare function workbookRelsXml(plan: WorkbookRelPlan, pivots: readonly PivotPlan[]): string;
|
|
11
61
|
export declare function corePropsXml(properties: WorkbookProperties): string;
|
|
12
62
|
export declare function appPropsXml(properties: WorkbookProperties): string;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { mangleFormula, quoteSheetName } from '../../core/formula.js';
|
|
2
2
|
import { WORKBOOK_PROTECTION_CREDENTIAL_ATTRS } from '../../core/workbook-protection.js';
|
|
3
3
|
import { isVisibility } from '../../core/worksheet.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { AuthoringError, InternalError, quoted } from '../../errors.js';
|
|
5
|
+
import { isRelType } from '../../rel-type.js';
|
|
6
|
+
import { assertWritableDate, checkedToken, escapeAttr, escapeText, numAttr, textAttr, XML_DECLARATION, } from '../../xml/xml.js';
|
|
7
|
+
import { extensionOf, THEME_PART_PATH } from '../opc/part-paths.js';
|
|
6
8
|
import { relationship, relationshipsPart } from '../opc/rels.js';
|
|
7
9
|
import { imageContentType } from './images.js';
|
|
8
10
|
import { SLICER_CACHES_EXT_URI } from './namespaces.js';
|
|
11
|
+
import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, SHARED_STRINGS_PART, STYLES_PART, tablePart, targetFromWorkbook, threadedCommentsPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
|
|
9
12
|
import { NS, REL } from './relationships.js';
|
|
10
13
|
import { x14Ext } from './x14-ext.js';
|
|
11
14
|
const CT = {
|
|
@@ -28,15 +31,16 @@ const CT = {
|
|
|
28
31
|
threadedComments: 'application/vnd.ms-excel.threadedcomments+xml',
|
|
29
32
|
person: 'application/vnd.ms-excel.person+xml',
|
|
30
33
|
};
|
|
31
|
-
export function contentTypesXml(
|
|
32
|
-
const extensionDefaults = buildExtensionDefaults(
|
|
34
|
+
export function contentTypesXml(inputs) {
|
|
35
|
+
const extensionDefaults = buildExtensionDefaults(inputs);
|
|
33
36
|
return (XML_DECLARATION +
|
|
34
37
|
`<Types xmlns="${NS.contentTypes}">` +
|
|
35
38
|
contentTypeDefaults(extensionDefaults) +
|
|
36
|
-
contentTypeOverrides(
|
|
39
|
+
contentTypeOverrides(inputs, extensionDefaults) +
|
|
37
40
|
'</Types>');
|
|
38
41
|
}
|
|
39
|
-
function buildExtensionDefaults(
|
|
42
|
+
function buildExtensionDefaults(inputs) {
|
|
43
|
+
const { commentNumbers, printerSettingsNumbers, mediaExtensions, preservedParts } = inputs;
|
|
40
44
|
const defaults = new Map();
|
|
41
45
|
const add = (extension, contentType) => {
|
|
42
46
|
const key = extension.toLowerCase();
|
|
@@ -53,71 +57,78 @@ function buildExtensionDefaults(commentNumbers, printerSettingsNumbers, mediaExt
|
|
|
53
57
|
add(ext, imageContentType(ext));
|
|
54
58
|
for (const part of preservedParts) {
|
|
55
59
|
const ext = extensionOf(part.path);
|
|
56
|
-
if (ext
|
|
60
|
+
if (ext === 'xml')
|
|
57
61
|
continue;
|
|
58
62
|
add(ext, part.contentType);
|
|
59
63
|
}
|
|
60
64
|
return defaults;
|
|
61
65
|
}
|
|
62
66
|
function isMacroEnabled(preservedWorkbookRefs) {
|
|
63
|
-
return preservedWorkbookRefs.some((ref) => ref.relType
|
|
67
|
+
return preservedWorkbookRefs.some((ref) => isRelType(ref.relType, 'vbaProject'));
|
|
64
68
|
}
|
|
65
69
|
function contentTypeDefaults(extensionDefaults) {
|
|
66
70
|
return [...extensionDefaults.values()]
|
|
67
71
|
.map(({ extension, contentType }) => defaultType(extension, contentType))
|
|
68
72
|
.join('');
|
|
69
73
|
}
|
|
70
|
-
function contentTypeOverrides(
|
|
74
|
+
function contentTypeOverrides(inputs, extensionDefaults) {
|
|
75
|
+
const { sheetCount, tables, drawingNumbers, commentNumbers, threadedCommentNumbers, pivots, hasSharedStrings, hasPersons, preservedParts, preservedWorkbookRefs, } = inputs;
|
|
71
76
|
const preservedOverrides = preservedParts
|
|
72
77
|
.filter((part) => part.path !== THEME_PART_PATH)
|
|
73
|
-
.filter((part) => extensionDefaults.get(extensionOf(part.path)
|
|
74
|
-
part.contentType)
|
|
75
|
-
.map((part) => override(`/${part.path}`, part.contentType));
|
|
78
|
+
.filter((part) => extensionDefaults.get(extensionOf(part.path))?.contentType !== part.contentType)
|
|
79
|
+
.map((part) => override(part.path, part.contentType));
|
|
76
80
|
return [
|
|
77
|
-
override(
|
|
78
|
-
...Array.from({ length: sheetCount }, (_, i) => override(
|
|
79
|
-
...tables.map(({ number }) => override(
|
|
80
|
-
...drawingNumbers.map((number) => override(
|
|
81
|
-
...commentNumbers.map((number) => override(
|
|
82
|
-
...threadedCommentNumbers.map((number) => override(
|
|
83
|
-
...pivots.map(({ number }) => override(
|
|
84
|
-
...pivots.map(({ number }) => override(
|
|
85
|
-
...pivots.map(({ number }) => override(
|
|
86
|
-
override(
|
|
87
|
-
override(
|
|
88
|
-
...(hasSharedStrings ? [override(
|
|
89
|
-
...(hasPersons ? [override(
|
|
90
|
-
override(
|
|
91
|
-
override(
|
|
81
|
+
override(WORKBOOK_PART, isMacroEnabled(preservedWorkbookRefs) ? CT.macroEnabledWorkbook : CT.workbook),
|
|
82
|
+
...Array.from({ length: sheetCount }, (_, i) => override(worksheetPart(i + 1), CT.worksheet)),
|
|
83
|
+
...tables.map(({ number }) => override(tablePart(number), CT.table)),
|
|
84
|
+
...drawingNumbers.map((number) => override(drawingPart(number), CT.drawing)),
|
|
85
|
+
...commentNumbers.map((number) => override(commentsPart(number), CT.comments)),
|
|
86
|
+
...threadedCommentNumbers.map((number) => override(threadedCommentsPart(number), CT.threadedComments)),
|
|
87
|
+
...pivots.map(({ number }) => override(pivotTablePart(number), CT.pivotTable)),
|
|
88
|
+
...pivots.map(({ number }) => override(pivotCacheDefinitionPart(number), CT.pivotCacheDefinition)),
|
|
89
|
+
...pivots.map(({ number }) => override(pivotCacheRecordsPart(number), CT.pivotCacheRecords)),
|
|
90
|
+
override(THEME_PART_PATH, CT.theme),
|
|
91
|
+
override(STYLES_PART, CT.styles),
|
|
92
|
+
...(hasSharedStrings ? [override(SHARED_STRINGS_PART, CT.sharedStrings)] : []),
|
|
93
|
+
...(hasPersons ? [override(PERSONS_PART, CT.person)] : []),
|
|
94
|
+
override(CORE_PROPS_PART, CT.core),
|
|
95
|
+
override(APP_PROPS_PART, CT.app),
|
|
92
96
|
...preservedOverrides,
|
|
93
97
|
].join('');
|
|
94
98
|
}
|
|
95
|
-
function override(
|
|
96
|
-
return `<Override PartName="
|
|
99
|
+
function override(partPath, contentType) {
|
|
100
|
+
return `<Override PartName="/${escapeAttr(partPath)}" ContentType="${escapeAttr(contentType)}"/>`;
|
|
97
101
|
}
|
|
98
102
|
function defaultType(extension, contentType) {
|
|
99
|
-
return `<Default Extension="${extension}" ContentType="${contentType}"/>`;
|
|
103
|
+
return `<Default Extension="${escapeAttr(extension)}" ContentType="${escapeAttr(contentType)}"/>`;
|
|
100
104
|
}
|
|
101
105
|
export function rootRelsXml(rootRefs) {
|
|
102
106
|
return relationshipsPart([
|
|
103
|
-
relationship('rId1', REL.officeDocument,
|
|
104
|
-
relationship('rId2', REL.coreProps,
|
|
105
|
-
relationship('rId3', REL.extProps,
|
|
107
|
+
relationship('rId1', REL.officeDocument, WORKBOOK_PART),
|
|
108
|
+
relationship('rId2', REL.coreProps, CORE_PROPS_PART),
|
|
109
|
+
relationship('rId3', REL.extProps, APP_PROPS_PART),
|
|
106
110
|
...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType, ref.entryPath)),
|
|
107
111
|
]);
|
|
108
112
|
}
|
|
109
|
-
export function workbookXml(workbook,
|
|
113
|
+
export function workbookXml(workbook, plan, pivots) {
|
|
114
|
+
const preservedRels = plan.preservedWorkbookRels;
|
|
110
115
|
const sheets = workbook.worksheets;
|
|
111
116
|
const entries = sheets
|
|
112
117
|
.map((sheet, i) => {
|
|
118
|
+
const relId = plan.sheetRelIds[i];
|
|
119
|
+
if (relId === undefined) {
|
|
120
|
+
throw new InternalError(`worksheet ${i + 1} of ${sheets.length} has no relationship id: the plan was drawn for ` +
|
|
121
|
+
`${plan.sheetRelIds.length} sheet(s)`);
|
|
122
|
+
}
|
|
113
123
|
const state = sheet.state === 'visible'
|
|
114
124
|
? ''
|
|
115
125
|
: ` state="${checkedToken(sheet.state, isVisibility, 'sheet visibility')}"`;
|
|
116
|
-
return `<sheet name="${escapeAttr(sheet.name)}" sheetId="${sheet.id}"${state} r:id="
|
|
126
|
+
return `<sheet name="${escapeAttr(sheet.name)}" sheetId="${sheet.id}"${state} r:id="${relId}"/>`;
|
|
117
127
|
})
|
|
118
128
|
.join('');
|
|
119
129
|
return (XML_DECLARATION +
|
|
120
130
|
`<workbook xmlns="${NS.main}" xmlns:r="${NS.docRels}">` +
|
|
131
|
+
workbookPrXml(workbook) +
|
|
121
132
|
workbookProtectionXml(workbook) +
|
|
122
133
|
bookViewsXml(workbook) +
|
|
123
134
|
`<sheets>${entries}</sheets>` +
|
|
@@ -128,6 +139,11 @@ export function workbookXml(workbook, preservedRels, pivots) {
|
|
|
128
139
|
workbookExtLstXml(preservedRels) +
|
|
129
140
|
'</workbook>');
|
|
130
141
|
}
|
|
142
|
+
function workbookPrXml(workbook) {
|
|
143
|
+
const attrs = (workbook.dateEpoch === 1904 ? ' date1904="1"' : '') +
|
|
144
|
+
(workbook.codeName === undefined ? '' : ` codeName="${escapeAttr(workbook.codeName)}"`);
|
|
145
|
+
return attrs === '' ? '' : `<workbookPr${attrs}/>`;
|
|
146
|
+
}
|
|
131
147
|
function bookViewsXml(workbook) {
|
|
132
148
|
const view = workbook.view;
|
|
133
149
|
const activeTab = workbook.activeTabIndex;
|
|
@@ -145,23 +161,23 @@ function bookViewsXml(workbook) {
|
|
|
145
161
|
function externalReferencesXml(preservedRels) {
|
|
146
162
|
const links = preservedRels
|
|
147
163
|
.filter((ref) => ref.externalReferenceIndex !== undefined)
|
|
148
|
-
.sort((a, b) => a.externalReferenceIndex - b.externalReferenceIndex);
|
|
164
|
+
.sort((a, b) => (a.externalReferenceIndex ?? 0) - (b.externalReferenceIndex ?? 0));
|
|
149
165
|
if (links.length === 0)
|
|
150
166
|
return '';
|
|
151
167
|
const entries = links.map((ref) => `<externalReference r:id="${ref.relId}"/>`).join('');
|
|
152
168
|
return `<externalReferences>${entries}</externalReferences>`;
|
|
153
169
|
}
|
|
154
170
|
function workbookExtLstXml(preservedRels) {
|
|
155
|
-
const caches = preservedRels.filter((ref) => ref.relType
|
|
171
|
+
const caches = preservedRels.filter((ref) => isRelType(ref.relType, 'slicerCache'));
|
|
156
172
|
if (caches.length === 0)
|
|
157
173
|
return '';
|
|
158
174
|
const entries = caches.map((ref) => `<x14:slicerCache r:id="${ref.relId}"/>`).join('');
|
|
159
175
|
return `<extLst>${x14Ext(SLICER_CACHES_EXT_URI, `<x14:slicerCaches>${entries}</x14:slicerCaches>`)}</extLst>`;
|
|
160
176
|
}
|
|
161
177
|
function pivotCachesXml(preservedRels, pivots) {
|
|
162
|
-
const preserved = preservedRels
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
const preserved = preservedRels.flatMap((ref) => ref.pivotCacheId === undefined
|
|
179
|
+
? []
|
|
180
|
+
: [`<pivotCache cacheId="${escapeAttr(ref.pivotCacheId)}" r:id="${ref.relId}"/>`]);
|
|
165
181
|
const generated = pivots.map((pivot) => `<pivotCache cacheId="${escapeAttr(pivot.cacheId)}" r:id="${pivot.workbookRelId}"/>`);
|
|
166
182
|
const entries = [...preserved, ...generated];
|
|
167
183
|
if (entries.length === 0)
|
|
@@ -192,12 +208,20 @@ const EXCEL_CALC_ID = '171027';
|
|
|
192
208
|
function calcPrXml(workbook) {
|
|
193
209
|
return workbook.fullCalcOnLoad ? `<calcPr calcId="${EXCEL_CALC_ID}" fullCalcOnLoad="1"/>` : '';
|
|
194
210
|
}
|
|
211
|
+
function localSheetId(sheets, name, scope) {
|
|
212
|
+
const wanted = scope.toLowerCase();
|
|
213
|
+
const index = sheets.findIndex((sheet) => sheet.name.toLowerCase() === wanted);
|
|
214
|
+
if (index === -1) {
|
|
215
|
+
throw new AuthoringError(`defined name ${quoted(name)} is scoped to worksheet ${quoted(scope)}, which this workbook does not contain`);
|
|
216
|
+
}
|
|
217
|
+
return index;
|
|
218
|
+
}
|
|
195
219
|
function definedNamesXml(workbook) {
|
|
196
220
|
const sheets = workbook.worksheets;
|
|
197
221
|
const userEntries = workbook.definedNames.map((name) => {
|
|
198
222
|
const scopeAttr = name.scope === undefined
|
|
199
223
|
? ''
|
|
200
|
-
: ` localSheetId="${sheets
|
|
224
|
+
: ` localSheetId="${localSheetId(sheets, name.name, name.scope)}"`;
|
|
201
225
|
const commentAttr = textAttr('comment', name.comment);
|
|
202
226
|
const hiddenAttr = name.hidden ? ' hidden="1"' : '';
|
|
203
227
|
return (`<definedName name="${escapeAttr(name.name)}"${scopeAttr}${commentAttr}${hiddenAttr}>` +
|
|
@@ -218,22 +242,23 @@ function filterDatabaseRefersTo(sheetName, range) {
|
|
|
218
242
|
const absolute = range.replace(/([A-Z]+)(\d+)/g, '$$$1$$$2');
|
|
219
243
|
return `${quoteSheetName(sheetName)}!${absolute}`;
|
|
220
244
|
}
|
|
221
|
-
export
|
|
222
|
-
|
|
245
|
+
export function workbookRelsXml(plan, pivots) {
|
|
246
|
+
const { personsRelId } = plan;
|
|
247
|
+
const preservedRels = plan.preservedWorkbookRels;
|
|
223
248
|
return relationshipsPart([
|
|
224
|
-
...
|
|
225
|
-
relationship(
|
|
226
|
-
relationship(
|
|
227
|
-
...(
|
|
228
|
-
? [
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
...plan.sheetRelIds.map((relId, i) => relationship(relId, REL.worksheet, targetFromWorkbook(worksheetPart(i + 1)))),
|
|
250
|
+
relationship(plan.stylesRelId, REL.styles, targetFromWorkbook(STYLES_PART)),
|
|
251
|
+
relationship(plan.themeRelId, REL.theme, targetFromWorkbook(THEME_PART_PATH)),
|
|
252
|
+
...(plan.sharedStringsRelId === null
|
|
253
|
+
? []
|
|
254
|
+
: [
|
|
255
|
+
relationship(plan.sharedStringsRelId, REL.sharedStrings, targetFromWorkbook(SHARED_STRINGS_PART)),
|
|
256
|
+
]),
|
|
232
257
|
...(personsRelId === null
|
|
233
258
|
? []
|
|
234
|
-
: [relationship(personsRelId, REL.person,
|
|
235
|
-
...preservedRels.map((ref) => relationship(ref.relId, ref.relType,
|
|
236
|
-
...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition,
|
|
259
|
+
: [relationship(personsRelId, REL.person, targetFromWorkbook(PERSONS_PART))]),
|
|
260
|
+
...preservedRels.map((ref) => relationship(ref.relId, ref.relType, targetFromWorkbook(ref.entryPath))),
|
|
261
|
+
...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition, targetFromWorkbook(pivotCacheDefinitionPart(pivot.number)))),
|
|
237
262
|
]);
|
|
238
263
|
}
|
|
239
264
|
export function corePropsXml(properties) {
|
|
@@ -247,10 +272,10 @@ export function corePropsXml(properties) {
|
|
|
247
272
|
if (properties.lastModifiedBy !== undefined) {
|
|
248
273
|
parts.push(`<cp:lastModifiedBy>${escapeText(properties.lastModifiedBy)}</cp:lastModifiedBy>`);
|
|
249
274
|
}
|
|
250
|
-
if (properties.created) {
|
|
275
|
+
if (properties.created !== undefined) {
|
|
251
276
|
parts.push(w3cdtf('created', properties.created));
|
|
252
277
|
}
|
|
253
|
-
if (properties.modified) {
|
|
278
|
+
if (properties.modified !== undefined) {
|
|
254
279
|
parts.push(w3cdtf('modified', properties.modified));
|
|
255
280
|
}
|
|
256
281
|
return (XML_DECLARATION +
|
|
@@ -260,7 +285,9 @@ export function corePropsXml(properties) {
|
|
|
260
285
|
'</cp:coreProperties>');
|
|
261
286
|
}
|
|
262
287
|
function w3cdtf(element, date) {
|
|
263
|
-
|
|
288
|
+
assertWritableDate(date, `the document property ${quoted(element)}`);
|
|
289
|
+
const stamp = date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
290
|
+
return `<dcterms:${element} xsi:type="dcterms:W3CDTF">${stamp}</dcterms:${element}>`;
|
|
264
291
|
}
|
|
265
292
|
export function appPropsXml(properties) {
|
|
266
293
|
const company = properties.company === undefined ? '' : `<Company>${escapeText(properties.company)}</Company>`;
|