@shbernal/ts-xlsx 1.1.0 → 1.3.1
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 +26 -0
- package/dist/core/address.js +0 -33
- package/dist/core/autofilter.js +0 -9
- package/dist/core/cell.js +0 -131
- package/dist/core/color-resolution.js +0 -60
- package/dist/core/column.js +0 -60
- package/dist/core/comment-thread.js +0 -45
- package/dist/core/conditional-formatting-overlay.js +0 -11
- package/dist/core/conditional-formatting.js +0 -10
- package/dist/core/containers.js +0 -15
- package/dist/core/data-validation-overlay.js +0 -25
- package/dist/core/data-validation.js +0 -7
- package/dist/core/date.js +0 -39
- package/dist/core/formula.js +0 -117
- package/dist/core/grid-edits.js +0 -38
- package/dist/core/image.js +0 -25
- package/dist/core/internal.js +0 -27
- package/dist/core/limits.js +0 -45
- package/dist/core/merge.d.ts +18 -0
- package/dist/core/merge.js +22 -7
- package/dist/core/modern-functions.js +0 -17
- package/dist/core/page-setup.js +0 -5
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +0 -48
- package/dist/core/preserved.js +0 -4
- package/dist/core/protection.js +0 -26
- package/dist/core/range.js +0 -112
- package/dist/core/row-input.d.ts +24 -0
- package/dist/core/row-input.js +29 -0
- package/dist/core/row.js +0 -66
- package/dist/core/style.js +0 -26
- package/dist/core/table-style.js +0 -28
- package/dist/core/table.js +0 -97
- package/dist/core/text-metrics.js +0 -40
- package/dist/core/theme.js +0 -85
- package/dist/core/value.js +0 -94
- package/dist/core/workbook-protection.js +0 -13
- package/dist/core/workbook.d.ts +13 -1
- package/dist/core/workbook.js +0 -380
- package/dist/core/worksheet-model.js +2 -23
- package/dist/core/worksheet.d.ts +9 -0
- package/dist/core/worksheet.js +7 -506
- package/dist/customui/errors.js +0 -10
- package/dist/customui/index.js +0 -5
- package/dist/customui/ribbon.js +0 -43
- package/dist/entries/core.js +0 -6
- package/dist/entries/csv.js +0 -4
- package/dist/entries/customui.js +0 -2
- package/dist/entries/errors.js +0 -13
- package/dist/entries/vba.js +0 -5
- package/dist/entries/xlsb.js +0 -6
- package/dist/entries/xlsx.js +0 -13
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +5 -56
- package/dist/index.js +0 -12
- package/dist/io/csv/read.js +0 -18
- package/dist/io/csv/write.js +0 -18
- package/dist/io/opc/errors.js +0 -30
- package/dist/io/opc/inflate.js +0 -35
- package/dist/io/opc/namespaces.js +0 -15
- package/dist/io/opc/part-paths.js +0 -11
- package/dist/io/opc/read-opc.js +0 -31
- package/dist/io/opc/read-options.js +0 -6
- package/dist/io/opc/rels.js +0 -13
- package/dist/io/opc/sniff-format.js +0 -50
- 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.js +0 -31
- package/dist/io/xlsb/errors.js +0 -10
- package/dist/io/xlsb/formula.js +1 -99
- package/dist/io/xlsb/primitives.js +1 -83
- package/dist/io/xlsb/ptg-functions.js +0 -34
- package/dist/io/xlsb/read-shared-strings.js +0 -7
- package/dist/io/xlsb/read-styles.js +3 -66
- package/dist/io/xlsb/read-worksheet.js +4 -87
- package/dist/io/xlsb/read.js +2 -60
- package/dist/io/xlsb/record-stream.js +0 -28
- package/dist/io/xlsb/record-types.js +0 -19
- package/dist/io/xlsx/cell-accumulator.js +0 -32
- package/dist/io/xlsx/cell-value.js +0 -29
- package/dist/io/xlsx/color-xml.d.ts +6 -0
- package/dist/io/xlsx/color-xml.js +42 -0
- package/dist/io/xlsx/comments.js +0 -84
- package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
- package/dist/io/xlsx/conditional-formatting.js +1 -97
- package/dist/io/xlsx/data-validation.js +0 -56
- package/dist/io/xlsx/edit-vba.js +2 -55
- package/dist/io/xlsx/errors.js +0 -12
- package/dist/io/xlsx/hyperlinks.js +0 -40
- package/dist/io/xlsx/images.js +0 -32
- package/dist/io/xlsx/namespaces.js +0 -42
- package/dist/io/xlsx/package-plan.js +0 -47
- package/dist/io/xlsx/pivot.js +0 -22
- package/dist/io/xlsx/{pivot-read.js → read-pivot.js} +0 -30
- package/dist/io/xlsx/read-rows.js +1 -83
- package/dist/io/xlsx/{shared-strings-read.js → read-shared-strings.js} +0 -11
- package/dist/io/xlsx/read-styles.d.ts +25 -1
- package/dist/io/xlsx/read-styles.js +61 -108
- package/dist/io/xlsx/read-worksheet.js +7 -88
- package/dist/io/xlsx/read.js +36 -213
- package/dist/io/xlsx/relationships.js +0 -11
- package/dist/io/xlsx/rich-runs.js +0 -16
- package/dist/io/xlsx/rich-text.js +0 -12
- package/dist/io/xlsx/shared-formulas.js +0 -7
- package/dist/io/xlsx/shared-strings.js +0 -21
- package/dist/io/xlsx/sheet-properties.js +4 -66
- package/dist/io/xlsx/styles.d.ts +1 -29
- package/dist/io/xlsx/styles.js +2 -366
- package/dist/io/xlsx/tables.js +5 -52
- package/dist/io/xlsx/threaded-comments.js +0 -103
- package/dist/io/xlsx/workbook-xml.d.ts +1 -1
- package/dist/io/xlsx/workbook-xml.js +6 -96
- package/dist/io/xlsx/worksheet-xml.js +0 -174
- package/dist/io/xlsx/write-stream.js +2 -137
- package/dist/io/xlsx/write.d.ts +5 -1
- package/dist/io/xlsx/write.js +5 -137
- package/dist/io/xlsx/x14-ext.js +0 -8
- package/dist/vba/cfb-writer.js +12 -59
- package/dist/vba/cfb.js +2 -32
- package/dist/vba/codepage.js +1 -23
- package/dist/vba/errors.js +0 -15
- package/dist/vba/index.js +0 -10
- package/dist/vba/ms-ovba.js +3 -46
- package/dist/vba/project-editor.js +4 -89
- package/dist/vba/project.js +2 -34
- package/dist/vba/vba-encoding.js +1 -17
- package/dist/xml/errors.js +0 -10
- package/dist/xml/xml-read.js +3 -82
- package/dist/xml/xml.js +0 -39
- package/package.json +8 -9
- package/skills/ts-xlsx-upstream/SKILL.md +178 -0
- /package/dist/io/xlsx/{pivot-read.d.ts → read-pivot.d.ts} +0 -0
- /package/dist/io/xlsx/{shared-strings-read.d.ts → read-shared-strings.d.ts} +0 -0
package/dist/io/xlsx/read.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
// The buffered `.xlsx` reader: an OPC zip package in, a Workbook model out.
|
|
2
|
-
//
|
|
3
|
-
// It reconstructs the part of the model the writer emits today — sheet names and order,
|
|
4
|
-
// cells holding a number, string, boolean, or formula, per-column width/visibility,
|
|
5
|
-
// per-row height/visibility, merged ranges, page margins, and cell styles (pattern fills,
|
|
6
|
-
// number formats, fonts, borders, alignment, and protection — per cell, or inherited from a
|
|
7
|
-
// formatted row/column). Shared-formula slaves and the richer value kinds land as the model
|
|
8
|
-
// grows; an unrecognised construct is skipped rather than guessed, so a foreign file reads
|
|
9
|
-
// without crashing even where a facet is not yet materialised.
|
|
10
|
-
//
|
|
11
|
-
// This module is the orchestrator: it wires the parsed package parts together — the OPC/rel
|
|
12
|
-
// resolution (`./read-opc.ts`), the style table (`./read-styles.ts`), and each worksheet body
|
|
13
|
-
// (`./read-worksheet.ts`) — and owns the sheet-part discovery (notes, images, tables, pivots) and
|
|
14
|
-
// preserved-reference capture that a faithful round-trip depends on.
|
|
15
|
-
//
|
|
16
|
-
// Untrusted input: inflate is bounded by a running byte counter (`./inflate.ts`) that caps
|
|
17
|
-
// actual decompressed output rather than trusting the archive's forgeable size headers, and
|
|
18
|
-
// the parser (ADR 0004) never expands entities.
|
|
19
1
|
import { decodeRange } from '../../core/address.js';
|
|
20
2
|
import { unmangleFunctions } from '../../core/formula.js';
|
|
21
3
|
import { INTERNAL } from '../../core/internal.js';
|
|
@@ -33,36 +15,15 @@ import { parseConditionalFormattings, parseDxfs } from './conditional-formatting
|
|
|
33
15
|
import { applyDataValidations, parseDataValidations, parseExtendedDataValidations, } from './data-validation.js';
|
|
34
16
|
import { applyHyperlinks, parseSheetHyperlinks } from './hyperlinks.js';
|
|
35
17
|
import { drawingHasUnmodeledContent, parseDrawing } from './images.js';
|
|
36
|
-
import { parsePivotTable } from './pivot
|
|
37
|
-
import {
|
|
18
|
+
import { parsePivotTable } from './read-pivot.js';
|
|
19
|
+
import { parseSharedStrings } from './read-shared-strings.js';
|
|
20
|
+
import { parseIndexedColors, parseMruColors, parseStyleTable, parseTableStyles, } from './read-styles.js';
|
|
38
21
|
import { parseWorksheet } from './read-worksheet.js';
|
|
39
|
-
import { parseSharedStrings } from './shared-strings-read.js';
|
|
40
|
-
import { parseIndexedColors, parseMruColors, parseTableStyles } from './styles.js';
|
|
41
22
|
import { parseTable } from './tables.js';
|
|
42
23
|
import { buildCommentThreads, parsePersons, parseThreadedComments } from './threaded-comments.js';
|
|
43
|
-
// Re-exported for the streaming reader (`./read-rows.ts`) and the public barrel, which import these
|
|
44
|
-
// from here: the split into per-part parsers is internal, so the reader's import surface is stable.
|
|
45
24
|
export { parseRelationships, resolveWorkbookPart } from '../opc/read-opc.js';
|
|
46
|
-
// The inflate bound and its option bag are shared with the `.xlsb` reader and the row streamer, so
|
|
47
|
-
// they are declared apart from all three; they stay reachable here because this is the entry point
|
|
48
|
-
// callers reach for.
|
|
49
25
|
export { DEFAULT_MAX_UNCOMPRESSED } from '../opc/read-options.js';
|
|
50
26
|
export { parseStyleTable } from './read-styles.js';
|
|
51
|
-
/**
|
|
52
|
-
* Read a spreadsheet package into a {@link Workbook}.
|
|
53
|
-
*
|
|
54
|
-
* Both OOXML serialisations are accepted: an XML `.xlsx`, and a binary `.xlsb` (BIFF12), which is the
|
|
55
|
-
* same OPC container with binary office-document parts. The two are auto-detected from the package
|
|
56
|
-
* itself rather than from a file extension, so a caller never branches on which form it holds — and
|
|
57
|
-
* the model produced is the same either way. See `../xlsb/read.ts` for what the binary path does not
|
|
58
|
-
* yet decode.
|
|
59
|
-
*
|
|
60
|
-
* @throws {UnsupportedFormatError} if the input is neither — a legacy `.xls` (`.format === 'xls'`) or
|
|
61
|
-
* an unrecognised/non-ZIP blob (`'unknown'`).
|
|
62
|
-
* @throws {XlsbParseError} if a binary `.xlsb` part is malformed.
|
|
63
|
-
* @throws {PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
|
|
64
|
-
* truncated archive, or one exceeding the inflate bound (a probable zip bomb).
|
|
65
|
-
*/
|
|
66
27
|
export function readXlsx(data, options = {}) {
|
|
67
28
|
const cap = options.maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
|
|
68
29
|
const files = inflateSpreadsheetPackage(data, cap);
|
|
@@ -70,59 +31,34 @@ export function readXlsx(data, options = {}) {
|
|
|
70
31
|
const { partText } = pkg;
|
|
71
32
|
const workbookXml = partText('xl/workbook.xml');
|
|
72
33
|
if (workbookXml === undefined) {
|
|
73
|
-
// No XML office document. A binary one means this is an `.xlsb`, which reads through the BIFF12
|
|
74
|
-
// codec over the very same model — the package is already inflated, so it is handed over as-is.
|
|
75
34
|
if (files[XLSB_WORKBOOK_PART] !== undefined)
|
|
76
35
|
return readXlsbPackage(files);
|
|
77
36
|
throw new UnsupportedFormatError('unknown');
|
|
78
37
|
}
|
|
79
|
-
// A part's content type is needed to faithfully re-declare any part preserved verbatim for
|
|
80
|
-
// round-tripping (a vector-shape drawing, a header/footer image and its VML). Resolve it the way
|
|
81
|
-
// OPC does: an explicit `<Override>` for the exact part, else the `<Default>` for its extension.
|
|
82
38
|
const contentTypeOf = contentTypeResolver(partText('[Content_Types].xml') ?? '');
|
|
83
39
|
const workbookRelsXml = partText('xl/_rels/workbook.xml.rels') ?? '';
|
|
84
40
|
const rels = parseRelationships(workbookRelsXml);
|
|
85
41
|
const sharedStrings = parseSharedStrings(partText('xl/sharedStrings.xml') ?? '');
|
|
86
|
-
// The style table resolves a cell/row/column style index to its facets (fill, number
|
|
87
|
-
// format); a package without one (a hand-rolled foreign file) yields an empty table and
|
|
88
|
-
// every index reads as unstyled.
|
|
89
42
|
const stylesXml = partText('xl/styles.xml') ?? '';
|
|
90
43
|
const { cellXfs: xfStyles, namedStyles, defaultFont } = parseStyleTable(stylesXml);
|
|
91
44
|
const workbook = new Workbook();
|
|
92
|
-
// Preserve the differential-style table verbatim so conditional formatting's dxfId references stay
|
|
93
|
-
// valid — and a foreign dxf's number format stays a real format code — across a re-write.
|
|
94
45
|
workbook[INTERNAL].restoreDifferentialStyles(parseDxfs(stylesXml));
|
|
95
|
-
// Preserve a custom indexed-color palette verbatim so an `indexed="…"` colour reference keeps its
|
|
96
|
-
// intended RGB across a re-write instead of resolving to a different default-palette entry.
|
|
97
46
|
workbook[INTERNAL].restoreIndexedColors(parseIndexedColors(stylesXml));
|
|
98
|
-
// Preserve the author's "Recent Colors" swatches, which the model never reads but re-writing would
|
|
99
|
-
// otherwise discard.
|
|
100
47
|
workbook[INTERNAL].restoreMruColors(parseMruColors(stylesXml));
|
|
101
|
-
// Preserve the custom table-style definitions so a table referencing one by name still resolves to
|
|
102
|
-
// a real definition after a re-write instead of rendering unstyled.
|
|
103
48
|
workbook[INTERNAL].restoreTableStyles(parseTableStyles(stylesXml));
|
|
104
|
-
// Preserve the theme part so a branded colour/font scheme is not overwritten by the default theme
|
|
105
|
-
// the writer emits for a workbook that has none.
|
|
106
49
|
readWorkbookTheme(workbookRelsXml, pkg, contentTypeOf, workbook);
|
|
107
|
-
// Preserve the named cell-style layer only when a file declares one beyond the Normal default, so an
|
|
108
|
-
// ordinary workbook keeps an empty named-style table and emits just the default on write.
|
|
109
50
|
if (namedStyles.length > 1)
|
|
110
51
|
workbook[INTERNAL].restoreNamedStyles(namedStyles);
|
|
111
|
-
// Preserve the declared default font (font id 0) so a re-write emits the face the file itself named
|
|
112
|
-
// rather than an assumed Calibri — which would change every empty cell and the metric every
|
|
113
|
-
// character-unit column width is expressed in.
|
|
114
52
|
workbook[INTERNAL].restoreDefaultFont(defaultFont);
|
|
115
53
|
const core = partText('docProps/core.xml');
|
|
116
54
|
if (core !== undefined)
|
|
117
55
|
applyCoreProperties(workbook, core);
|
|
56
|
+
const app = partText('docProps/app.xml');
|
|
57
|
+
if (app !== undefined)
|
|
58
|
+
applyAppProperties(workbook, app);
|
|
118
59
|
workbook.protection = parseWorkbookProtection(workbookXml);
|
|
119
60
|
applyWorkbookView(workbook.view, workbookXml);
|
|
120
|
-
// The threaded-comment author registry is workbook-level, and every conversation on every sheet
|
|
121
|
-
// resolves its authors and @mentions through it — so it is restored before the sheet loop that reads
|
|
122
|
-
// those conversations, not alongside the other workbook-level parts below.
|
|
123
61
|
readWorkbookPersons(workbookRelsXml, pkg, workbook);
|
|
124
|
-
// A picture used on more than one sheet is one media part; caching by media path keeps it a single
|
|
125
|
-
// workbook image so a re-write does not duplicate the bytes.
|
|
126
62
|
const imageIdByMediaPath = new Map();
|
|
127
63
|
const sheetOrder = [];
|
|
128
64
|
for (const { name, relId, state } of parseWorkbookSheets(workbookXml)) {
|
|
@@ -144,8 +80,6 @@ export function readXlsx(data, options = {}) {
|
|
|
144
80
|
for (const cf of parseConditionalFormattings(sheetXml))
|
|
145
81
|
sheet.addConditionalFormatting(cf);
|
|
146
82
|
}
|
|
147
|
-
// Threads before notes: a threaded cell's comments-part entry is the thread's legacy fallback, not
|
|
148
|
-
// a note, and `applyNotes` reads the sheet's restored threads to tell the two apart.
|
|
149
83
|
const threads = readSheetCommentThreads(path, pkg, workbook);
|
|
150
84
|
if (threads.length > 0)
|
|
151
85
|
sheet[INTERNAL].restoreCommentThreads(threads);
|
|
@@ -166,16 +100,11 @@ export function readXlsx(data, options = {}) {
|
|
|
166
100
|
}
|
|
167
101
|
readWorkbookPreservedReferences(workbookXml, pkg, contentTypeOf, workbook);
|
|
168
102
|
readRootPreservedReferences(pkg, contentTypeOf, workbook);
|
|
169
|
-
// Defined names follow the sheets: a scoped name's `localSheetId` indexes the sheet order, which
|
|
170
|
-
// is why the names are read only once every sheet is registered.
|
|
171
103
|
for (const name of parseWorkbookDefinedNames(workbookXml, sheetOrder)) {
|
|
172
104
|
workbook.defineName(name);
|
|
173
105
|
}
|
|
174
106
|
return workbook;
|
|
175
107
|
}
|
|
176
|
-
// A sheet's comments live in a comments part reached through the sheet's own relationships: the sheet
|
|
177
|
-
// declares a relationship of type `.../comments` whose target resolves (relative to the sheet's
|
|
178
|
-
// directory) to the comments part. A sheet with no rels part or no such relationship simply has none.
|
|
179
108
|
function readSheetComments(sheetPath, pkg) {
|
|
180
109
|
const commentsPath = sheetRelTarget(sheetPath, pkg.partText, 'comments');
|
|
181
110
|
if (commentsPath === undefined)
|
|
@@ -185,26 +114,12 @@ function readSheetComments(sheetPath, pkg) {
|
|
|
185
114
|
return undefined;
|
|
186
115
|
return parseComments(commentsXml);
|
|
187
116
|
}
|
|
188
|
-
// The workbook's threaded-comment identity registry: a relationship of type `.../person` names
|
|
189
|
-
// `xl/persons/person.xml`, whose entries every message's `personId` and every mention's
|
|
190
|
-
// `mentionpersonId` resolve through. A workbook with no threaded comments declares no such
|
|
191
|
-
// relationship and keeps an empty registry.
|
|
192
117
|
function readWorkbookPersons(workbookRelsXml, pkg, workbook) {
|
|
193
118
|
const target = relationshipTargetByType(workbookRelsXml, 'person');
|
|
194
119
|
const xml = target === undefined ? undefined : pkg.partText(resolveWorkbookPart(target));
|
|
195
120
|
if (xml !== undefined)
|
|
196
121
|
workbook[INTERNAL].restorePersons(parsePersons(xml));
|
|
197
122
|
}
|
|
198
|
-
// The workbook's theme part: the `<clrScheme>`/`<fontScheme>`/`<fmtScheme>` every `theme="n"` colour
|
|
199
|
-
// and every `scheme="major|minor"` font in the package resolves against. It is reached through the
|
|
200
|
-
// workbook's `.../theme` relationship rather than assumed at `xl/theme/theme1.xml`, because the target
|
|
201
|
-
// is rel-relative and a foreign package is free to name the part anything.
|
|
202
|
-
//
|
|
203
|
-
// Captured with its transitive part closure, not as a lone string: a theme can carry its own
|
|
204
|
-
// relationships (a picture used as a themed fill, wired by an `r:embed` into the theme's rels part),
|
|
205
|
-
// and re-emitting the theme body without them would leave that reference dangling — which Excel
|
|
206
|
-
// reports as a package needing repair. A package that declares no theme leaves the workbook on the
|
|
207
|
-
// library's default, which is also what a dangling relationship target degrades to.
|
|
208
123
|
function readWorkbookTheme(workbookRelsXml, pkg, contentTypeOf, workbook) {
|
|
209
124
|
const target = relationshipTargetByType(workbookRelsXml, 'theme');
|
|
210
125
|
if (target === undefined)
|
|
@@ -214,16 +129,6 @@ function readWorkbookTheme(workbookRelsXml, pkg, contentTypeOf, workbook) {
|
|
|
214
129
|
if (parts !== undefined)
|
|
215
130
|
workbook[INTERNAL].restoreThemePart({ entryPath, parts });
|
|
216
131
|
}
|
|
217
|
-
// A sheet's threaded conversations live in a `xl/threadedComments/threadedComment{n}.xml` part reached
|
|
218
|
-
// through a relationship of type `.../threadedComment` on the sheet's own rels — the same discovery
|
|
219
|
-
// shape as the notes part above, and deliberately separate from it: a thread and a legacy note are
|
|
220
|
-
// different features that happen to share a sheet. The messages are grouped into threads and their
|
|
221
|
-
// authors resolved against the workbook registry, so each thread lands self-contained.
|
|
222
|
-
//
|
|
223
|
-
// What lands here IS what a re-write emits: the thread part is re-serialised from these threads, and so is
|
|
224
|
-
// the legacy fallback `<comment>` that binds each cell to its conversation (see `comments.ts`). Anything
|
|
225
|
-
// this reader drops is therefore dropped from the file — which is why a message too damaged to place is
|
|
226
|
-
// still kept wherever it can be, and why the anchor is canonicalised here rather than trusted downstream.
|
|
227
132
|
function readSheetCommentThreads(sheetPath, pkg, workbook) {
|
|
228
133
|
const path = sheetRelTarget(sheetPath, pkg.partText, 'threadedComment');
|
|
229
134
|
const xml = path === undefined ? undefined : pkg.partText(path);
|
|
@@ -231,19 +136,10 @@ function readSheetCommentThreads(sheetPath, pkg, workbook) {
|
|
|
231
136
|
return [];
|
|
232
137
|
return buildCommentThreads(parseThreadedComments(xml), (id) => workbook.getPerson(id));
|
|
233
138
|
}
|
|
234
|
-
// A sheet's printer-settings blob is an opaque binary part linked from `<pageSetup r:id>`: the sheet
|
|
235
|
-
// declares a relationship of type `.../printerSettings` whose target resolves to a `.bin` part. We
|
|
236
|
-
// keep the raw bytes verbatim — the DEVMODE inside is platform-specific and the model never
|
|
237
|
-
// interprets it, only round-trips it so re-writing the file preserves the user's print configuration.
|
|
238
|
-
// A sheet with no rels part or no such relationship simply has none.
|
|
239
139
|
function readSheetPrinterSettings(sheetPath, pkg) {
|
|
240
140
|
const path = sheetRelTarget(sheetPath, pkg.partText, 'printerSettings');
|
|
241
141
|
return path === undefined ? undefined : pkg.partBytes(path);
|
|
242
142
|
}
|
|
243
|
-
// A sheet's anchored images live in a drawing part reached through the sheet's own relationships: a
|
|
244
|
-
// relationship of type `.../drawing` names the drawing part, whose own relationships map each
|
|
245
|
-
// picture's embed id to a media part under `xl/media/`. Each anchor becomes a workbook image (deduped
|
|
246
|
-
// by media path) placed back on the sheet at its two-cell anchor.
|
|
247
143
|
function readSheetImages(sheetPath, pkg, workbook, sheet, imageIdByMediaPath) {
|
|
248
144
|
const { partText, partBytes } = pkg;
|
|
249
145
|
const drawingPath = sheetRelTarget(sheetPath, partText, 'drawing');
|
|
@@ -252,10 +148,6 @@ function readSheetImages(sheetPath, pkg, workbook, sheet, imageIdByMediaPath) {
|
|
|
252
148
|
const drawingXml = partText(drawingPath);
|
|
253
149
|
if (drawingXml === undefined)
|
|
254
150
|
return;
|
|
255
|
-
// A drawing that also holds a chart or shape is preserved whole (see readSheetPreservedReferences),
|
|
256
|
-
// so its pictures must not be modeled here: modeling them would leave the sheet with images, which
|
|
257
|
-
// suppresses that preservation and drops the chart. Leaving `sheet.images` empty routes the entire
|
|
258
|
-
// drawing — pictures included — through byte-preservation, keeping every anchor faithful.
|
|
259
151
|
if (drawingHasUnmodeledContent(drawingXml))
|
|
260
152
|
return;
|
|
261
153
|
const drawingRels = parseRelationships(partText(relsPathFor(drawingPath)) ?? '');
|
|
@@ -282,11 +174,6 @@ function readSheetImages(sheetPath, pkg, workbook, sheet, imageIdByMediaPath) {
|
|
|
282
174
|
}
|
|
283
175
|
}
|
|
284
176
|
}
|
|
285
|
-
// A sheet background is a workbook image referenced by the worksheet's `<picture>` element through a
|
|
286
|
-
// sheet-local relationship of type `.../image`. Unlike an anchored image (whose image relationships
|
|
287
|
-
// live in the drawing part's own rels), the background's relationship sits directly on the sheet, so
|
|
288
|
-
// it is the sheet rels' sole image relationship. The bytes are deduped against images shared with a
|
|
289
|
-
// drawing, keeping one media part per picture across a re-write.
|
|
290
177
|
function readSheetBackground(sheetPath, pkg, workbook, sheet, imageIdByMediaPath) {
|
|
291
178
|
const mediaPath = sheetRelTarget(sheetPath, pkg.partText, 'image');
|
|
292
179
|
if (mediaPath === undefined)
|
|
@@ -301,16 +188,6 @@ function readSheetBackground(sheetPath, pkg, workbook, sheet, imageIdByMediaPath
|
|
|
301
188
|
}
|
|
302
189
|
sheet.addBackgroundImage(id);
|
|
303
190
|
}
|
|
304
|
-
// Capture the worksheet-level references to package content the model does not interpret, so a
|
|
305
|
-
// round-trip re-emits them verbatim instead of dropping them:
|
|
306
|
-
// • `<drawing>` — but only when the reader modeled no anchored image from it: either a drawing that
|
|
307
|
-
// holds no pictures at all (a chart or shape), or a mixed drawing whose pictures the reader
|
|
308
|
-
// declined to model precisely so the whole part (chart included) rides here verbatim. A drawing
|
|
309
|
-
// whose pictures were modeled is owned by the model and re-serialised from it; capturing it here
|
|
310
|
-
// too would double-emit those pictures.
|
|
311
|
-
// • `<legacyDrawingHF>` — a header/footer image's VML, which the model never interprets.
|
|
312
|
-
// Each reference's target part and the transitive closure of parts it reaches (a VML's image, a
|
|
313
|
-
// drawing's media) are captured with their bytes, content types, and relationships.
|
|
314
191
|
function readSheetPreservedReferences(sheetPath, sheetXml, pkg, contentTypeOf, sheet) {
|
|
315
192
|
const { partText, partBytes } = pkg;
|
|
316
193
|
const relsXml = partText(relsPathFor(sheetPath));
|
|
@@ -324,10 +201,6 @@ function readSheetPreservedReferences(sheetPath, sheetXml, pkg, contentTypeOf, s
|
|
|
324
201
|
if (parts !== undefined)
|
|
325
202
|
sheet[INTERNAL].addPreservedReference({ element, relType, entryPath, parts });
|
|
326
203
|
};
|
|
327
|
-
// Element-wired references: a `<drawing>`/`<legacyDrawingHF>` names its part by an `r:id` in the
|
|
328
|
-
// sheet body. A `<drawing>` is preserved only when the reader modeled no picture from it — a
|
|
329
|
-
// chart/shape-only drawing, or a mixed one the reader left unmodeled — since one whose pictures are
|
|
330
|
-
// modeled is re-serialised from the model.
|
|
331
204
|
const referenceElements = sheet.images.length === 0 ? ['drawing', 'legacyDrawingHF'] : ['legacyDrawingHF'];
|
|
332
205
|
for (const element of referenceElements) {
|
|
333
206
|
const relId = worksheetReferenceRelId(sheetXml, element);
|
|
@@ -335,9 +208,6 @@ function readSheetPreservedReferences(sheetPath, sheetXml, pkg, contentTypeOf, s
|
|
|
335
208
|
if (record !== undefined && !record.external)
|
|
336
209
|
capture(element, record.type, record.target);
|
|
337
210
|
}
|
|
338
|
-
// Relationship-wired references: a pivot table or slicer is reached through a sheet relationship
|
|
339
|
-
// with no worksheet child pointing at it — Excel discovers it by scanning the sheet's rels. Preserve
|
|
340
|
-
// each so the pivots/slicers a fill-and-save workflow does not touch are not dropped.
|
|
341
211
|
for (const record of records) {
|
|
342
212
|
if (record.external)
|
|
343
213
|
continue;
|
|
@@ -345,19 +215,9 @@ function readSheetPreservedReferences(sheetPath, sheetXml, pkg, contentTypeOf, s
|
|
|
345
215
|
capture(undefined, record.type, record.target);
|
|
346
216
|
}
|
|
347
217
|
}
|
|
348
|
-
// A sheet relationship the model does not consume but must round-trip: a pivot table or a slicer. Every
|
|
349
|
-
// other sheet rel kind (drawing, printerSettings, table, comments, threadedComment, hyperlinks, background
|
|
350
|
-
// image, the comment VML) is modeled and re-serialised from the model, so preserving it here would emit
|
|
351
|
-
// the part twice.
|
|
352
218
|
function isPreservedSheetRelType(type) {
|
|
353
219
|
return type.endsWith('/pivotTable') || type.endsWith('/slicer');
|
|
354
220
|
}
|
|
355
|
-
// Capture the workbook-level references to package content the model does not interpret — pivot
|
|
356
|
-
// caches (`pivotCacheDefinition`), slicer caches (`slicerCache`), and external links (`externalLink`,
|
|
357
|
-
// each a link to a source workbook) — so a round-trip re-emits them instead of dropping the pivots,
|
|
358
|
-
// slicers, and linked-workbook references they back. A pivot cache's `<pivotCaches>` registration (its
|
|
359
|
-
// `cacheId`) and an external link's `<externalReferences>` position (its `[n]` index) are captured
|
|
360
|
-
// alongside so the wiring a pivot table or a formula resolves through survives too.
|
|
361
221
|
function readWorkbookPreservedReferences(workbookXml, pkg, contentTypeOf, workbook) {
|
|
362
222
|
const { partText, partBytes } = pkg;
|
|
363
223
|
const relsXml = partText('xl/_rels/workbook.xml.rels');
|
|
@@ -383,11 +243,6 @@ function readWorkbookPreservedReferences(workbookXml, pkg, contentTypeOf, workbo
|
|
|
383
243
|
});
|
|
384
244
|
}
|
|
385
245
|
}
|
|
386
|
-
// Content wired from the package's own `_rels/.rels` that the writer does not regenerate from the
|
|
387
|
-
// model — the ribbon customUI parts, custom document properties, a thumbnail. The writer rebuilds the
|
|
388
|
-
// root rels for the parts it models (the workbook, and core/app properties), so every other root
|
|
389
|
-
// relationship's target would be dropped on write; capturing its closure here re-declares it verbatim.
|
|
390
|
-
// External targets and the three regenerated relationship types are skipped.
|
|
391
246
|
function readRootPreservedReferences(pkg, contentTypeOf, workbook) {
|
|
392
247
|
const { partText, partBytes } = pkg;
|
|
393
248
|
const relsXml = partText('_rels/.rels');
|
|
@@ -403,31 +258,17 @@ function readRootPreservedReferences(pkg, contentTypeOf, workbook) {
|
|
|
403
258
|
workbook[INTERNAL].addPreservedRootReference({ relType: record.type, entryPath, parts });
|
|
404
259
|
}
|
|
405
260
|
}
|
|
406
|
-
// The three root relationships the writer regenerates from the model on every write: the office
|
|
407
|
-
// document and the core/extended document properties. Every other root relationship is unmodeled and
|
|
408
|
-
// is preserved verbatim by {@link readRootPreservedReferences} rather than dropped.
|
|
409
261
|
function isRegeneratedRootRelType(type) {
|
|
410
262
|
return (type.endsWith('/officeDocument') ||
|
|
411
263
|
type.endsWith('/core-properties') ||
|
|
412
264
|
type.endsWith('/extended-properties'));
|
|
413
265
|
}
|
|
414
|
-
// A workbook relationship the model does not consume but must round-trip: a pivot cache, a slicer
|
|
415
|
-
// cache, an external link (the pointer to a linked source workbook), or a macro-enabled workbook's VBA
|
|
416
|
-
// project. Worksheets, styles, theme, shared strings, and the threaded-comment person registry are modeled
|
|
417
|
-
// and re-serialised from the model. Preserving vbaProject here — rather than silently dropping it, as an
|
|
418
|
-
// unrecognised relationship type otherwise would — is what keeps loading and re-saving a .xlsm from
|
|
419
|
-
// discarding its macros; the content-type override in workbook-xml.ts is the other half, so the re-emitted
|
|
420
|
-
// package still declares itself macro-enabled. Preserving externalLink is what keeps a formula's `[n]`
|
|
421
|
-
// external reference from dangling: the link part and its `<externalReferences>` registration are both
|
|
422
|
-
// re-emitted.
|
|
423
266
|
function isPreservedWorkbookRelType(type) {
|
|
424
267
|
return (type.endsWith('/pivotCacheDefinition') ||
|
|
425
268
|
type.endsWith('/slicerCache') ||
|
|
426
269
|
type.endsWith('/vbaProject') ||
|
|
427
270
|
type.endsWith('/externalLink'));
|
|
428
271
|
}
|
|
429
|
-
// Map each `<pivotCache>` registration in the workbook's `<pivotCaches>` to the relationship id that
|
|
430
|
-
// reaches its cache definition, so a preserved cache carries the `cacheId` a pivot table refers to.
|
|
431
272
|
function parsePivotCacheRegistrations(workbookXml) {
|
|
432
273
|
const byRelId = new Map();
|
|
433
274
|
for (const { attrs } of openElements(workbookXml, 'pivotCache')) {
|
|
@@ -437,10 +278,6 @@ function parsePivotCacheRegistrations(workbookXml) {
|
|
|
437
278
|
}
|
|
438
279
|
return byRelId;
|
|
439
280
|
}
|
|
440
|
-
// Map each `<externalReference>` in the workbook's `<externalReferences>` to its 0-based position, keyed
|
|
441
|
-
// by the relationship id it wires. That position is the `[n]` index a formula or defined name resolves
|
|
442
|
-
// an external cell through (`[1]Sheet!$A$1`), so preserving it lets the writer re-emit the block in the
|
|
443
|
-
// original order and keep every `[n]` pointing at the same linked workbook.
|
|
444
281
|
function parseExternalReferenceRegistrations(workbookXml) {
|
|
445
282
|
const byRelId = new Map();
|
|
446
283
|
let index = 0;
|
|
@@ -450,10 +287,6 @@ function parseExternalReferenceRegistrations(workbookXml) {
|
|
|
450
287
|
}
|
|
451
288
|
return byRelId;
|
|
452
289
|
}
|
|
453
|
-
// The `r:id` of the first `<drawing>` / `<legacyDrawingHF>` element in a worksheet, or undefined when
|
|
454
|
-
// the sheet declares none. The reference lives in the worksheet XML (not distinguishable by
|
|
455
|
-
// relationship Type — a header/footer VML and a comment VML share the `vmlDrawing` type), so the
|
|
456
|
-
// specific relationship is found by reading the element's `r:id` here.
|
|
457
290
|
function worksheetReferenceRelId(sheetXml, element) {
|
|
458
291
|
for (const { attrs } of openElements(sheetXml, element)) {
|
|
459
292
|
if (attrs['r:id'] !== undefined)
|
|
@@ -461,10 +294,6 @@ function worksheetReferenceRelId(sheetXml, element) {
|
|
|
461
294
|
}
|
|
462
295
|
return undefined;
|
|
463
296
|
}
|
|
464
|
-
// A sheet's tables live in `xl/tables/table{n}.xml` parts, each reached through a relationship of
|
|
465
|
-
// type `.../table` on the sheet's own rels. The writer emits one relationship per table; each part
|
|
466
|
-
// is parsed back into the model and re-registered in definition order. A part that fails to parse
|
|
467
|
-
// (missing name/ref/columns — Excel corruption) is skipped rather than crashing the whole read.
|
|
468
297
|
function readSheetTables(sheetPath, pkg, sheet) {
|
|
469
298
|
const relsXml = pkg.partText(relsPathFor(sheetPath));
|
|
470
299
|
if (relsXml === undefined)
|
|
@@ -479,13 +308,6 @@ function readSheetTables(sheetPath, pkg, sheet) {
|
|
|
479
308
|
}
|
|
480
309
|
dropMergesInsideTables(sheet);
|
|
481
310
|
}
|
|
482
|
-
// Reconstruct an inspectable model of each pivot table hosted on a sheet. A pivot is reached by a
|
|
483
|
-
// sheet relationship of type `.../pivotTable`; the pivot-table part carries its own relationship of
|
|
484
|
-
// type `.../pivotCacheDefinition` to the cache holding the field catalogue and source range. Both
|
|
485
|
-
// parts are parsed and combined into a read-only view registered on the sheet — separate from the
|
|
486
|
-
// byte-preservation that actually round-trips the pivot, so this never changes what is re-emitted.
|
|
487
|
-
// The read is lenient: a pivot whose cache is missing still yields a (partial) model rather than
|
|
488
|
-
// throwing, matching Excel's tolerance for a damaged package on load.
|
|
489
311
|
function readSheetPivotTables(sheetPath, pkg, sheet) {
|
|
490
312
|
const { partText } = pkg;
|
|
491
313
|
const relsXml = partText(relsPathFor(sheetPath));
|
|
@@ -503,10 +325,6 @@ function readSheetPivotTables(sheetPath, pkg, sheet) {
|
|
|
503
325
|
sheet[INTERNAL].addLoadedPivotTable(parsePivotTable(tableXml, cacheXml));
|
|
504
326
|
}
|
|
505
327
|
}
|
|
506
|
-
// Excel forbids a merged range inside a formatted table and repairs such a file on load by dropping
|
|
507
|
-
// the merge. A worksheet's merges are read before its tables, so a real file carrying that invalid
|
|
508
|
-
// geometry lands in the model intact; this applies the same repair once the tables are known, so a
|
|
509
|
-
// re-write does not surface the Excel-invalid geometry the writer (correctly) rejects.
|
|
510
328
|
function dropMergesInsideTables(sheet) {
|
|
511
329
|
const regions = sheet.tables.map((table) => table.region);
|
|
512
330
|
if (regions.length === 0)
|
|
@@ -536,10 +354,6 @@ export function parseWorkbookSheets(xml) {
|
|
|
536
354
|
}
|
|
537
355
|
return sheets;
|
|
538
356
|
}
|
|
539
|
-
// Read the workbook's structure/window protection (`<workbookProtection>`). The three lock flags are
|
|
540
|
-
// decoded as booleans (an absent or "0" attribute stays unlocked), and only the whitelisted
|
|
541
|
-
// password/agile-hash attributes are preserved verbatim — a hostile or unknown attribute is dropped
|
|
542
|
-
// rather than echoed back on write. Returns undefined when the workbook declares no protection.
|
|
543
357
|
export function parseWorkbookProtection(xml) {
|
|
544
358
|
let result;
|
|
545
359
|
parseXml(xml, {
|
|
@@ -566,14 +380,6 @@ export function parseWorkbookProtection(xml) {
|
|
|
566
380
|
});
|
|
567
381
|
return result;
|
|
568
382
|
}
|
|
569
|
-
// Restore the workbook's saved window state from `<bookViews><workbookView/>` onto the model's view,
|
|
570
|
-
// so a round-trip hands back the geometry and active tab the author left rather than stamping the
|
|
571
|
-
// library's defaults over them. Only the first `<workbookView>` is read — the model carries one view,
|
|
572
|
-
// which is all Excel writes and all a single consuming window can restore.
|
|
573
|
-
//
|
|
574
|
-
// Each attribute is applied only when the source carried a usable value; an absent or non-numeric one
|
|
575
|
-
// leaves the default in place, so a truncated or hostile element degrades to a valid window rather
|
|
576
|
-
// than a NaN geometry that would serialise as garbage.
|
|
577
383
|
export function applyWorkbookView(view, xml) {
|
|
578
384
|
for (const { attrs } of openElements(xml, 'workbookView')) {
|
|
579
385
|
applyViewNumber(attrs.xWindow, (value) => (view.x = value));
|
|
@@ -596,10 +402,6 @@ function applyViewNumber(raw, assign) {
|
|
|
596
402
|
if (Number.isFinite(value))
|
|
597
403
|
assign(Math.trunc(value));
|
|
598
404
|
}
|
|
599
|
-
// Reconstruct the workbook's defined names. Each `<definedName>` carries its name (and optional
|
|
600
|
-
// comment/hidden flag) as attributes and its refersTo formula as text content; a `localSheetId`
|
|
601
|
-
// maps back through the sheet order to the scope sheet's name. A name whose localSheetId is out of
|
|
602
|
-
// range (a foreign file referencing a sheet we did not load) is left global rather than dropped.
|
|
603
405
|
function parseWorkbookDefinedNames(xml, sheetOrder) {
|
|
604
406
|
const names = [];
|
|
605
407
|
let capture = false;
|
|
@@ -609,9 +411,6 @@ function parseWorkbookDefinedNames(xml, sheetOrder) {
|
|
|
609
411
|
onOpen(name, attrs) {
|
|
610
412
|
if (localName(name) !== 'definedName' || attrs.name === undefined)
|
|
611
413
|
return;
|
|
612
|
-
// `_xlnm._FilterDatabase` is the built-in Excel derives from a sheet's autofilter, not a
|
|
613
|
-
// user-defined name: it is reconstructed from the sheet's `<autoFilter>` element, so skip it
|
|
614
|
-
// here to keep it off `Workbook.definedNames` and out of a duplicating round-trip.
|
|
615
414
|
if (attrs.name === '_xlnm._FilterDatabase')
|
|
616
415
|
return;
|
|
617
416
|
capture = true;
|
|
@@ -633,8 +432,6 @@ function parseWorkbookDefinedNames(xml, sheetOrder) {
|
|
|
633
432
|
onClose(name) {
|
|
634
433
|
if (localName(name) !== 'definedName' || pending === undefined)
|
|
635
434
|
return;
|
|
636
|
-
// Strip the `_xlfn.`/`_xlpm.` prefixes back to the readable name, the same normalisation the
|
|
637
|
-
// reader applies to a cell formula, so the model never holds the on-disk mangling.
|
|
638
435
|
names.push({ ...pending, refersTo: unmangleFunctions(refersTo) });
|
|
639
436
|
capture = false;
|
|
640
437
|
pending = undefined;
|
|
@@ -642,9 +439,13 @@ function parseWorkbookDefinedNames(xml, sheetOrder) {
|
|
|
642
439
|
});
|
|
643
440
|
return names;
|
|
644
441
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
442
|
+
const CORE_PROPERTY_LOCAL_NAMES = new Set([
|
|
443
|
+
'title',
|
|
444
|
+
'creator',
|
|
445
|
+
'lastModifiedBy',
|
|
446
|
+
'created',
|
|
447
|
+
'modified',
|
|
448
|
+
]);
|
|
648
449
|
function applyCoreProperties(workbook, xml) {
|
|
649
450
|
let capture = '';
|
|
650
451
|
let text = '';
|
|
@@ -661,7 +462,9 @@ function applyCoreProperties(workbook, xml) {
|
|
|
661
462
|
onClose(name) {
|
|
662
463
|
if (capture === '' || localName(name) !== capture)
|
|
663
464
|
return;
|
|
664
|
-
if (capture === '
|
|
465
|
+
if (capture === 'title')
|
|
466
|
+
workbook.properties.title = text;
|
|
467
|
+
else if (capture === 'creator')
|
|
665
468
|
workbook.properties.creator = text;
|
|
666
469
|
else if (capture === 'lastModifiedBy')
|
|
667
470
|
workbook.properties.lastModifiedBy = text;
|
|
@@ -678,3 +481,23 @@ function applyCoreProperties(workbook, xml) {
|
|
|
678
481
|
},
|
|
679
482
|
});
|
|
680
483
|
}
|
|
484
|
+
function applyAppProperties(workbook, xml) {
|
|
485
|
+
let capture = false;
|
|
486
|
+
let text = '';
|
|
487
|
+
parseXml(xml, {
|
|
488
|
+
onOpen(name) {
|
|
489
|
+
capture = localName(name) === 'Company';
|
|
490
|
+
text = '';
|
|
491
|
+
},
|
|
492
|
+
onText(chunk) {
|
|
493
|
+
if (capture)
|
|
494
|
+
text += chunk;
|
|
495
|
+
},
|
|
496
|
+
onClose(name) {
|
|
497
|
+
if (!capture || localName(name) !== 'Company')
|
|
498
|
+
return;
|
|
499
|
+
workbook.properties.company = text;
|
|
500
|
+
capture = false;
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
// The relationship-type URIs and namespace groupings the writer's serialisers reference. The
|
|
2
|
-
// canonical namespace set lives in `namespaces.ts`; the writer-local groupings here (the props
|
|
3
|
-
// vocabularies, the content-types namespace, and the rel-type URIs derived from them) are plumbing
|
|
4
|
-
// every part of the writer references.
|
|
5
|
-
//
|
|
6
|
-
// The `.rels` part envelope these types are written into is container-level, not SpreadsheetML, and
|
|
7
|
-
// lives in `../opc/rels.ts`.
|
|
8
1
|
import { CONTENT_TYPES_NS, PKG_RELS_NS, RELATIONSHIPS_NS } from '../opc/namespaces.js';
|
|
9
2
|
import { SPREADSHEETML_NS } from './namespaces.js';
|
|
10
3
|
export const NS = {
|
|
@@ -19,8 +12,6 @@ export const NS = {
|
|
|
19
12
|
dcmitype: 'http://purl.org/dc/dcmitype/',
|
|
20
13
|
xsi: 'http://www.w3.org/2001/XMLSchema-instance',
|
|
21
14
|
};
|
|
22
|
-
// The Office extension relationships namespace the 2018 threaded-comment feature wires both of its parts
|
|
23
|
-
// through. Local to this table: nothing outside it needs the base URI.
|
|
24
15
|
const MS_OFFICE_2017_RELS_NS = 'http://schemas.microsoft.com/office/2017/10/relationships';
|
|
25
16
|
export const REL = {
|
|
26
17
|
worksheet: `${NS.docRels}/worksheet`,
|
|
@@ -40,8 +31,6 @@ export const REL = {
|
|
|
40
31
|
pivotTable: `${NS.docRels}/pivotTable`,
|
|
41
32
|
pivotCacheDefinition: `${NS.docRels}/pivotCacheDefinition`,
|
|
42
33
|
pivotCacheRecords: `${NS.docRels}/pivotCacheRecords`,
|
|
43
|
-
// Threaded comments are a Microsoft extension, so both types live under the 2017/10 Office
|
|
44
|
-
// relationships namespace rather than the standard officeDocument one.
|
|
45
34
|
threadedComment: `${MS_OFFICE_2017_RELS_NS}/threadedComment`,
|
|
46
35
|
person: `${MS_OFFICE_2017_RELS_NS}/person`,
|
|
47
36
|
};
|
|
@@ -1,52 +1,36 @@
|
|
|
1
|
-
// Rich-text run accumulation, shared by the two readers that parse `<r>` runs identically: an inline
|
|
2
|
-
// string's `<is>` in a worksheet body and a pooled `<si>` in sharedStrings.xml. One `<r>` opens a run
|
|
3
|
-
// (resetting its font and text so an unformatted run inherits nothing from the last), an `<rPr>` opens
|
|
4
|
-
// the run's font bundle whose self-closing children each set one facet, and a `<t>` appends the run's
|
|
5
|
-
// text; the run commits on `</r>`. The surrounding parser owns only whether it is inside an `<is>`/`<si>`
|
|
6
|
-
// and where a bare (non-run) `<t>` goes.
|
|
7
1
|
import { applyFontChild } from './read-styles.js';
|
|
8
2
|
export class RunAccumulator {
|
|
9
3
|
#runs = [];
|
|
10
4
|
#font = null;
|
|
11
5
|
#text = '';
|
|
12
6
|
#inRun = false;
|
|
13
|
-
// Discard accumulated runs, readying the accumulator for a fresh `<is>`/`<si>`. A new array is
|
|
14
|
-
// installed, so a value already built from a previous reset's runs keeps its own array.
|
|
15
7
|
reset() {
|
|
16
8
|
this.#runs = [];
|
|
17
9
|
this.#font = null;
|
|
18
10
|
this.#inRun = false;
|
|
19
11
|
}
|
|
20
|
-
/** The runs gathered so far. */
|
|
21
12
|
get runs() {
|
|
22
13
|
return this.#runs;
|
|
23
14
|
}
|
|
24
|
-
// Open a run: reset the per-run font and text so an unformatted run inherits nothing from the last.
|
|
25
15
|
beginRun() {
|
|
26
16
|
this.#inRun = true;
|
|
27
17
|
this.#font = null;
|
|
28
18
|
this.#text = '';
|
|
29
19
|
}
|
|
30
|
-
// Open the run's `<rPr>` font bundle; its children stream in via {@link applyProperty}.
|
|
31
20
|
beginProperties() {
|
|
32
21
|
if (this.#inRun)
|
|
33
22
|
this.#font = {};
|
|
34
23
|
}
|
|
35
|
-
// Apply one `<rPr>` child (`<b/>`, `<sz>`, `<color>`, `<rFont>`, …) to the open run's font; a no-op
|
|
36
|
-
// when no `<rPr>` is open.
|
|
37
24
|
applyProperty(local, attrs) {
|
|
38
25
|
if (this.#font !== null)
|
|
39
26
|
applyFontChild(this.#font, local, attrs);
|
|
40
27
|
}
|
|
41
|
-
// Append a `<t>`'s text to the open run. Returns false when no run is open, so the caller routes the
|
|
42
|
-
// text to its surrounding plain/inline string instead.
|
|
43
28
|
appendText(text) {
|
|
44
29
|
if (!this.#inRun)
|
|
45
30
|
return false;
|
|
46
31
|
this.#text += text;
|
|
47
32
|
return true;
|
|
48
33
|
}
|
|
49
|
-
// Commit the open run — with its font, only if that font set at least one facet — to the runs list.
|
|
50
34
|
endRun() {
|
|
51
35
|
if (!this.#inRun)
|
|
52
36
|
return;
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
// Rich-text runs — a cell value composed of independently-formatted text runs.
|
|
2
|
-
//
|
|
3
|
-
// OOXML stores rich text as a sequence of `<r>` runs, each an optional `<rPr>` (the run's font, in
|
|
4
|
-
// the CT_RPrElt shape — identical to a styles `<font>` except the face element is `<rFont>`, not
|
|
5
|
-
// `<name>`) followed by a `<t>` text element. The writer serialises a rich-text value inline
|
|
6
|
-
// (`t="inlineStr"`), never into the shared-strings table, matching how it writes every other string
|
|
7
|
-
// value; the reader reconstructs the runs while scanning the inline string.
|
|
8
1
|
import { textElement } from '../../xml/xml.js';
|
|
9
2
|
import { fontXml } from './styles.js';
|
|
10
|
-
/**
|
|
11
|
-
* Serialise a rich-text value's runs as the inner content of an `<is>` element. A zero-length run
|
|
12
|
-
* is dropped: an empty `<t/>` is schema-invalid — Excel flags the file as corrupt — and an empty
|
|
13
|
-
* run contributes nothing to the rendered text, so omitting it is loss-free.
|
|
14
|
-
*/
|
|
15
3
|
export function richTextRunsXml(runs) {
|
|
16
4
|
return runs
|
|
17
5
|
.filter((run) => run.text !== '')
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
// Shared-formula group planning: assigning each master/clone group a sheet-unique index and the
|
|
2
|
-
// master's `ref` range before the row loop serialises a single cell.
|
|
3
1
|
import { encodeAddress } from '../../core/address.js';
|
|
4
2
|
import { isFormulaValue, isSharedFormulaValue } from '../../core/value.js';
|
|
5
3
|
import { AuthoringError } from '../../errors.js';
|
|
6
|
-
// Plan a sheet's shared-formula groups: every clone cell (a {@link SharedFormulaValue}) names its
|
|
7
|
-
// master by address, so group the clones by master, assign each group a sheet-unique `si`, and record
|
|
8
|
-
// the `ref` range (master through the furthest clone) on the master. Excel requires the master to sit
|
|
9
|
-
// at the top-left of that range, so a clone above or left of its master — or a master with no formula
|
|
10
|
-
// (an orphan) — is rejected here, named, rather than emitted as a package Excel repairs on open.
|
|
11
4
|
export function planSharedFormulas(sheet) {
|
|
12
5
|
const groups = new Map();
|
|
13
6
|
for (const { cells } of sheet.rows()) {
|