@shbernal/ts-xlsx 1.1.0 → 1.2.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 +21 -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.js +0 -380
- package/dist/core/worksheet-model.js +2 -23
- 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/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 +0 -54
- 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 +1 -88
- package/dist/io/xlsx/read.js +3 -211
- 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 +1 -64
- 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.js +0 -94
- package/dist/io/xlsx/worksheet-xml.js +0 -174
- package/dist/io/xlsx/write-stream.js +0 -137
- package/dist/io/xlsx/write.js +1 -134
- 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/images.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// Anchored images on the wire: the `xl/drawings/drawing{n}.xml` part (a DrawingML two-cell anchor
|
|
2
|
-
// per image), the drawing's own relationships to the `xl/media/` bytes, and the reader that turns a
|
|
3
|
-
// drawing back into anchors. The image bytes themselves are opaque here — the writer copies them
|
|
4
|
-
// verbatim into a media part and the reader hands them back untouched.
|
|
5
1
|
import { isOneCellAnchor, } from '../../core/image.js';
|
|
6
2
|
import { XML_DECLARATION } from '../../xml/xml.js';
|
|
7
3
|
import { localName, parseXml } from '../../xml/xml-read.js';
|
|
@@ -9,8 +5,6 @@ import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
|
|
|
9
5
|
import { relationship, relationshipsPart } from '../opc/rels.js';
|
|
10
6
|
import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
|
|
11
7
|
const IMAGE_REL_TYPE = `${RELATIONSHIPS_NS}/image`;
|
|
12
|
-
// The content type Excel expects for each image kind, keyed by lower-case extension. An unlisted
|
|
13
|
-
// extension falls back to `image/<ext>`, which is what a well-behaved consumer infers anyway.
|
|
14
8
|
const IMAGE_CONTENT_TYPES = {
|
|
15
9
|
png: 'image/png',
|
|
16
10
|
jpg: 'image/jpeg',
|
|
@@ -23,12 +17,10 @@ const IMAGE_CONTENT_TYPES = {
|
|
|
23
17
|
wmf: 'image/x-wmf',
|
|
24
18
|
svg: 'image/svg+xml',
|
|
25
19
|
};
|
|
26
|
-
/** The content type for a media part's `<Default Extension>` entry in `[Content_Types].xml`. */
|
|
27
20
|
export function imageContentType(extension) {
|
|
28
21
|
const ext = extension.toLowerCase();
|
|
29
22
|
return IMAGE_CONTENT_TYPES[ext] ?? `image/${ext}`;
|
|
30
23
|
}
|
|
31
|
-
/** The `xl/drawings/drawing{n}.xml` part: one anchor per image, two-cell or one-cell by its shape. */
|
|
32
24
|
export function drawingXml(images) {
|
|
33
25
|
const anchors = images.map((image, i) => anchorXml(image, i + 1)).join('');
|
|
34
26
|
return (XML_DECLARATION +
|
|
@@ -42,10 +34,6 @@ function anchorXml(image, id) {
|
|
|
42
34
|
? oneCellAnchorXml(anchor.from, anchor.ext, anchor.rotation, image.embedId, id)
|
|
43
35
|
: twoCellAnchorXml(anchor.from, anchor.to, anchor.editAs ?? 'oneCell', anchor.rotation, image.embedId, id);
|
|
44
36
|
}
|
|
45
|
-
// A picture anchored between two grid points. The geometry lives entirely in <xdr:from>/<xdr:to>, so
|
|
46
|
-
// the picture carries no absolute <a:xfrm> — a zeroed one would override the anchor and collapse the
|
|
47
|
-
// image to nothing in strict viewers (LibreOffice), while a non-zero one would fight the anchor. A
|
|
48
|
-
// rotation is the one transform kept: it can't be derived from the anchor, so it rides a rot-only xfrm.
|
|
49
37
|
function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
|
|
50
38
|
return (`<xdr:twoCellAnchor editAs="${editAs}">` +
|
|
51
39
|
`<xdr:from>${anchorPointXml(from)}</xdr:from>` +
|
|
@@ -54,8 +42,6 @@ function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
|
|
|
54
42
|
'<xdr:clientData/>' +
|
|
55
43
|
'</xdr:twoCellAnchor>');
|
|
56
44
|
}
|
|
57
|
-
// A picture pinned at one grid point with a fixed EMU extent. editAs is a two-cell-only attribute and
|
|
58
|
-
// the schema forbids it here, so a one-cell anchor never carries one.
|
|
59
45
|
function oneCellAnchorXml(from, ext, rotation, embedId, id) {
|
|
60
46
|
return ('<xdr:oneCellAnchor>' +
|
|
61
47
|
`<xdr:from>${anchorPointXml(from)}</xdr:from>` +
|
|
@@ -78,8 +64,6 @@ function anchorPointXml(point) {
|
|
|
78
64
|
return (`<xdr:col>${point.col}</xdr:col><xdr:colOff>${point.colOff ?? 0}</xdr:colOff>` +
|
|
79
65
|
`<xdr:row>${point.row}</xdr:row><xdr:rowOff>${point.rowOff ?? 0}</xdr:rowOff>`);
|
|
80
66
|
}
|
|
81
|
-
/** The drawing's `_rels/drawing{n}.xml.rels`: one image relationship per anchor, in `embedId` order
|
|
82
|
-
* (`rId1`, `rId2`, …), each pointing at the media part the anchor shows. */
|
|
83
67
|
export function drawingRelsXml(mediaTargets) {
|
|
84
68
|
return relationshipsPart(mediaTargets.map((target, i) => relationship(`rId${i + 1}`, IMAGE_REL_TYPE, target)));
|
|
85
69
|
}
|
|
@@ -87,9 +71,6 @@ function blankPoint() {
|
|
|
87
71
|
return { col: 0, row: 0, colOff: 0, rowOff: 0 };
|
|
88
72
|
}
|
|
89
73
|
const EDIT_AS = new Set(['oneCell', 'twoCell', 'absolute']);
|
|
90
|
-
/** Parse a drawing part into its image anchors (both `<xdr:twoCellAnchor>` and `<xdr:oneCellAnchor>`).
|
|
91
|
-
* Anchors that are not pictures (a chart, a shape) carry no `<a:blip r:embed>` and are skipped, so a
|
|
92
|
-
* mixed drawing yields only its images. */
|
|
93
74
|
export function parseDrawing(xml) {
|
|
94
75
|
const anchors = [];
|
|
95
76
|
let from = null;
|
|
@@ -98,12 +79,8 @@ export function parseDrawing(xml) {
|
|
|
98
79
|
let editAs;
|
|
99
80
|
let rotation;
|
|
100
81
|
let embed;
|
|
101
|
-
// The point (<xdr:from> or <xdr:to>) whose coordinate children are currently streaming in.
|
|
102
82
|
let target = null;
|
|
103
|
-
// Depth inside <xdr:pic>, so the anchor-level <xdr:ext> is not confused with the <a:ext> nested in
|
|
104
|
-
// a picture's spPr transform (both have local name "ext").
|
|
105
83
|
let picDepth = 0;
|
|
106
|
-
// Which coordinate child is open, so its text lands on the right field; '' between children.
|
|
107
84
|
let coord = '';
|
|
108
85
|
let text = '';
|
|
109
86
|
parseXml(xml, {
|
|
@@ -122,7 +99,6 @@ export function parseDrawing(xml) {
|
|
|
122
99
|
picDepth++;
|
|
123
100
|
}
|
|
124
101
|
else if (local === 'xfrm' && picDepth > 0) {
|
|
125
|
-
// The picture's own rotation — the one spPr transform that can't be derived from the anchor.
|
|
126
102
|
const rot = Number(attrs.rot);
|
|
127
103
|
if (Number.isFinite(rot) && rot !== 0)
|
|
128
104
|
rotation = rot;
|
|
@@ -186,15 +162,7 @@ export function parseDrawing(xml) {
|
|
|
186
162
|
});
|
|
187
163
|
return anchors;
|
|
188
164
|
}
|
|
189
|
-
// Anchor content a drawing can hold that the image model does not interpret: a chart
|
|
190
|
-
// (`<xdr:graphicFrame>`), a shape or text box (`<xdr:sp>`), a connector (`<xdr:cxnSp>`), or a group
|
|
191
|
-
// (`<xdr:grpSp>`). A drawing carrying any of these is preserved whole rather than modeled, so it is
|
|
192
|
-
// not re-serialised from its pictures alone (which would silently drop the chart/shape).
|
|
193
165
|
const UNMODELED_DRAWING_CONTENT = new Set(['graphicFrame', 'sp', 'cxnSp', 'grpSp']);
|
|
194
|
-
/** Whether a drawing part holds anchor content beyond plain pictures — a chart, shape, connector, or
|
|
195
|
-
* group. Excel packs every one of a sheet's anchors into a single drawing part, so a sheet with both a
|
|
196
|
-
* picture and a chart yields a mixed drawing; modeling only its pictures and re-serialising from them
|
|
197
|
-
* would drop the chart. The reader uses this to fall back to whole-drawing byte-preservation instead. */
|
|
198
166
|
export function drawingHasUnmodeledContent(xml) {
|
|
199
167
|
let found = false;
|
|
200
168
|
parseXml(xml, {
|
|
@@ -1,53 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical SpreadsheetML namespace URIs and well-known extension GUIDs.
|
|
3
|
-
*
|
|
4
|
-
* These are wire-format constants: Excel keys its parsing off the exact URI or
|
|
5
|
-
* GUID, so a producer must reproduce each one byte-for-byte. Centralizing them
|
|
6
|
-
* keeps the writer and reader from drifting apart and retires the
|
|
7
|
-
* `NS_MAIN`/`MAIN_NS`/`main` naming fork that had grown across the io modules.
|
|
8
|
-
*
|
|
9
|
-
* The package-level URIs (`.rels`, content types, the relationship vocabulary) describe the
|
|
10
|
-
* container rather than the spreadsheet inside it, and live in `../opc/namespaces.ts`.
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* SpreadsheetML main namespace — the default `xmlns` of the workbook,
|
|
14
|
-
* worksheet, styles, sharedStrings, comments, table and pivot parts.
|
|
15
|
-
*/
|
|
16
1
|
export const SPREADSHEETML_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
|
17
|
-
/**
|
|
18
|
-
* Markup-compatibility namespace (`mc:`), whose `mc:Ignorable` attribute lists the prefixes a
|
|
19
|
-
* consumer that does not know them may skip rather than reject the part over.
|
|
20
|
-
*/
|
|
21
2
|
export const MARKUP_COMPATIBILITY_NS = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
|
|
22
|
-
/**
|
|
23
|
-
* The 2014 revision namespace (`xr:`), which scopes the `xr:uid` Excel stamps on a comment. Declared
|
|
24
|
-
* `mc:Ignorable` wherever it appears, so a consumer that ignores it still reads the part.
|
|
25
|
-
*/
|
|
26
3
|
export const REVISION_NS = 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision';
|
|
27
|
-
/**
|
|
28
|
-
* The 2018 threaded-comments namespace, shared by both parts of the feature — a sheet's
|
|
29
|
-
* `threadedComment{n}.xml` and the workbook's `person.xml`. Note the plural `threadedcomments`, all
|
|
30
|
-
* lower-case: Excel matches the URI exactly and reads neither part under any other spelling.
|
|
31
|
-
*/
|
|
32
4
|
export const THREADED_COMMENTS_NS = 'http://schemas.microsoft.com/office/spreadsheetml/2018/threadedcomments';
|
|
33
|
-
/** DrawingML shared graphics namespace (`a:`). */
|
|
34
5
|
export const DRAWINGML_NS = 'http://schemas.openxmlformats.org/drawingml/2006/main';
|
|
35
|
-
/** Spreadsheet-drawing anchor namespace (`xdr:`) used by the worksheet drawing part. */
|
|
36
6
|
export const XDR_NS = 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing';
|
|
37
|
-
/**
|
|
38
|
-
* The 2009 Microsoft extension namespace. `x14` scopes the feature elements
|
|
39
|
-
* Excel tucks inside `<ext>` blocks (conditional formatting, data validation,
|
|
40
|
-
* slicers); it is declared inline on those elements exactly as Excel writes
|
|
41
|
-
* them, so a worksheet root never needs an extra namespace declaration.
|
|
42
|
-
*/
|
|
43
7
|
export const X14_NS = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main';
|
|
44
|
-
/** Scopes the `<xm:sqref>`/`<xm:f>` references the x14 feature elements carry. */
|
|
45
8
|
export const XM_NS = 'http://schemas.microsoft.com/office/excel/2006/main';
|
|
46
|
-
/**
|
|
47
|
-
* Well-known `<ext uri=…>` GUIDs. Each `<ext>` block is opaque to a consumer
|
|
48
|
-
* that does not recognize its GUID, so a producer must emit these exact values
|
|
49
|
-
* for Excel to rediscover the feature.
|
|
50
|
-
*/
|
|
51
9
|
export const CF_EXT_URI = '{78C0D931-6437-407d-A8EE-F0AAD7539E65}';
|
|
52
10
|
export const DATABAR_LINK_EXT_URI = '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}';
|
|
53
11
|
export const DATA_VALIDATION_EXT_URI = '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}';
|
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
// The plan layer of the writer: pure graph resolution that turns a Workbook model into the numbered,
|
|
2
|
-
// cross-referenced set of parts an `.xlsx` package needs — media, preserved (verbatim-carried) parts,
|
|
3
|
-
// and the sheet-/workbook-local relationship ids that wire them — before any XML is serialised.
|
|
4
1
|
import { AuthoringError } from '../../errors.js';
|
|
5
2
|
import { extensionOf, relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
|
|
6
3
|
import { preservedRelsXml } from '../opc/rels.js';
|
|
7
|
-
// A sheet's relationship-id allocator: hands out `rId1`, `rId2`, … in the one canonical order the
|
|
8
|
-
// package wires a sheet's parts (tables, drawing, comments, threaded comments, printer settings, external
|
|
9
|
-
// hyperlinks, background, preserved references, pivot tables). Every sheet-local id is drawn from here in
|
|
10
|
-
// sequence, so no plan step re-derives its starting offset by summing the counts of the steps before
|
|
11
|
-
// it — the arithmetic that, open-coded once per step with subtly different prefixes, could silently
|
|
12
|
-
// hand two parts the same id and corrupt the package. Monotonic by construction, so collisions cannot
|
|
13
|
-
// arise however the steps grow. One fresh allocator per sheet; the ids it yields are sheet-local.
|
|
14
4
|
export class SheetRelIds {
|
|
15
5
|
#next = 1;
|
|
16
|
-
/** The next relationship id (`rId1`, `rId2`, …), advancing the counter. */
|
|
17
6
|
next() {
|
|
18
7
|
return `rId${this.#next++}`;
|
|
19
8
|
}
|
|
20
9
|
}
|
|
21
|
-
// Gather the workbook images actually referenced by some sheet — either anchored in a drawing or set
|
|
22
|
-
// as a sheet background (an unreferenced image is not written) — number them in first-use order, and
|
|
23
|
-
// record the extensions in play. A sheet referencing an id with no registered image is a programming
|
|
24
|
-
// error the writer surfaces rather than emitting a dangling relationship.
|
|
25
10
|
export function planMedia(workbook, sheets) {
|
|
26
11
|
const usedIds = [];
|
|
27
12
|
const seen = new Set();
|
|
@@ -52,35 +37,14 @@ export function planMedia(workbook, sheets) {
|
|
|
52
37
|
});
|
|
53
38
|
return { parts, numberById, extensions: [...extensions] };
|
|
54
39
|
}
|
|
55
|
-
// Resolve every sheet's verbatim-preserved worksheet references (a vector-shape drawing, a
|
|
56
|
-
// header/footer image) into the parts to emit and the per-sheet reference data that wires them. Each
|
|
57
|
-
// reference's captured part closure is re-numbered onto collision-proof `preservedP{n}` paths — so
|
|
58
|
-
// preserved content never clobbers a generated drawing/VML/media part — with the closure's internal
|
|
59
|
-
// relationships rewritten to the new sibling paths. Part numbering is the only cross-sheet concern
|
|
60
|
-
// here; each reference's sheet-local relationship id is assigned by the caller from the sheet's
|
|
61
|
-
// {@link SheetRelIds} allocator, so this function stays free of the sheet-local id arithmetic.
|
|
62
40
|
export function planPreservedParts(workbook, generatedDrawingCount, generatedMediaCount) {
|
|
63
41
|
const sheets = workbook.worksheets;
|
|
64
|
-
// The writer generates drawings, VML, and media of its own, so a preserved part of one of those
|
|
65
|
-
// kinds is re-numbered past the generated ones (a preserved drawing never clobbers an anchored
|
|
66
|
-
// drawing, a preserved VML never clobbers a comment's VML). Comment VML is numbered by sheet index,
|
|
67
|
-
// so `sheets.length` bounds it. Every other kind (pivot tables, caches, slicers, charts) the writer
|
|
68
|
-
// never generates, so those keep their original path — see {@link preservedPartPath}.
|
|
69
42
|
const numbering = {
|
|
70
43
|
drawing: generatedDrawingCount,
|
|
71
44
|
vml: sheets.length,
|
|
72
45
|
media: generatedMediaCount,
|
|
73
46
|
};
|
|
74
|
-
// One package-wide remap and one emitted-parts map: a part reached through more than one reference
|
|
75
|
-
// (a pivot cache reached both from its pivot table and from the workbook) is numbered once and
|
|
76
|
-
// emitted once, so overlapping closures collapse instead of duplicating parts.
|
|
77
47
|
const remap = new Map();
|
|
78
|
-
// A preserved theme rides the same closure machinery as every other verbatim part — it can carry
|
|
79
|
-
// relationships of its own (a picture used as a themed fill) that need the same renumbering and
|
|
80
|
-
// rewiring. Its entry is pinned to the fixed theme path rather than left to {@link preservedPartPath},
|
|
81
|
-
// because the workbook's theme relationship and the content-type override name that path
|
|
82
|
-
// unconditionally; a source package that called its part `theme2.xml` would otherwise land it
|
|
83
|
-
// somewhere neither points.
|
|
84
48
|
const theme = workbook.themePart;
|
|
85
49
|
if (theme !== undefined)
|
|
86
50
|
remap.set(theme.entryPath, THEME_PART_PATH);
|
|
@@ -103,8 +67,6 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
|
|
|
103
67
|
if (emitted.has(newPath))
|
|
104
68
|
continue;
|
|
105
69
|
const rels = part.rels.flatMap((rel) => {
|
|
106
|
-
// An external relationship (a linked workbook) is emitted verbatim — its target is outside the
|
|
107
|
-
// package, so it is neither in the remap nor expressed relative to the new path.
|
|
108
70
|
if (rel.external) {
|
|
109
71
|
return [{ id: rel.id, type: rel.type, target: rel.targetPath, external: true }];
|
|
110
72
|
}
|
|
@@ -113,10 +75,6 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
|
|
|
113
75
|
? []
|
|
114
76
|
: [{ id: rel.id, type: rel.type, target: relativePartPath(newPath, target) }];
|
|
115
77
|
});
|
|
116
|
-
// The one preserved part whose *bytes* can change: a theme the caller authored over is
|
|
117
|
-
// regenerated from the source part (see `Workbook.authoredThemeXml`) rather than carried
|
|
118
|
-
// verbatim, so the format scheme, the unauthored slots' encoding, and the relationships below
|
|
119
|
-
// all still ride through — only the authored elements differ.
|
|
120
78
|
const authoredTheme = newPath === THEME_PART_PATH ? workbook.authoredThemeXml() : undefined;
|
|
121
79
|
emitted.set(newPath, {
|
|
122
80
|
path: newPath,
|
|
@@ -150,11 +108,6 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
|
|
|
150
108
|
themeEmitted: theme !== undefined,
|
|
151
109
|
};
|
|
152
110
|
}
|
|
153
|
-
// The path a preserved part is emitted at. A kind the writer generates of its own — a drawing, a VML,
|
|
154
|
-
// a media image — is re-numbered past the generated parts of that kind (see {@link planPreservedParts})
|
|
155
|
-
// so it never clobbers one. Every other kind (a pivot table, a pivot/slicer cache, a slicer, a chart)
|
|
156
|
-
// the writer never generates, so it keeps its original path — leaving the package's standard part
|
|
157
|
-
// names intact and letting overlapping closures agree on a single path for a shared part.
|
|
158
111
|
function preservedPartPath(originalPath, numbering) {
|
|
159
112
|
const ext = extensionOf(originalPath);
|
|
160
113
|
if (ext.toLowerCase() === 'vml')
|
package/dist/io/xlsx/pivot.js
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
// OOXML rendering for a modeled pivot table: the three parts a pivot needs — the cache definition
|
|
2
|
-
// (field catalogue), the cache records (a copy of the source rows), and the pivot table definition
|
|
3
|
-
// (the layout). The semantic computation lives in `core/pivot-table.ts`; this file only serialises.
|
|
4
|
-
//
|
|
5
|
-
// Every value that reaches an attribute is run through `escapeAttr`, so source strings carrying XML
|
|
6
|
-
// specials (`Smith & Co`, `<West>`, `It's "best"`) become well-formed markup rather than corrupting
|
|
7
|
-
// the package — the whole point of the shared-item escaping this module guarantees.
|
|
8
1
|
import { encodeAddress } from '../../core/address.js';
|
|
9
2
|
import { escapeAttr, XML_DECLARATION } from '../../xml/xml.js';
|
|
10
3
|
import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
|
|
11
4
|
import { SPREADSHEETML_NS } from './namespaces.js';
|
|
12
|
-
// Excel's default caption prefix for each aggregation ("Sum of Amount", "Average of Amount"). A
|
|
13
|
-
// metric's name is also its `subtotal` value, which is why the record key equals the enum member.
|
|
14
5
|
const METRIC_CAPTIONS = {
|
|
15
6
|
sum: 'Sum',
|
|
16
7
|
count: 'Count',
|
|
@@ -24,10 +15,6 @@ const METRIC_CAPTIONS = {
|
|
|
24
15
|
var: 'Var',
|
|
25
16
|
varp: 'Varp',
|
|
26
17
|
};
|
|
27
|
-
/** The `pivotCacheDefinition` part: the source reference and the field catalogue. `r:id="rId1"`
|
|
28
|
-
* names the cache-records part through this cache's own rels part. `refreshOnLoad="1"` tells Excel to
|
|
29
|
-
* rebuild the pivot body from the cache on open, so the layout stays correct without us computing it
|
|
30
|
-
* to the pixel. `recordCount` is the number of source data rows. */
|
|
31
18
|
export function pivotCacheDefinitionXml(table) {
|
|
32
19
|
const fields = table.cacheFields
|
|
33
20
|
.map((field) => {
|
|
@@ -59,8 +46,6 @@ export function pivotCacheDefinitionXml(table) {
|
|
|
59
46
|
`<cacheFields count="${table.cacheFields.length}">${fields}</cacheFields>` +
|
|
60
47
|
`</pivotCacheDefinition>`);
|
|
61
48
|
}
|
|
62
|
-
/** The `pivotCacheRecords` part: one `<r>` per source data row, each cell either an index into an
|
|
63
|
-
* axis field's shared-items catalogue (`<x>`) or an inline value (`<n>`/`<s>`/`<m>`). */
|
|
64
49
|
export function pivotCacheRecordsXml(table) {
|
|
65
50
|
const rows = table.records
|
|
66
51
|
.map((record) => `<r>${record.map(recordCellXml).join('')}</r>`)
|
|
@@ -70,16 +55,11 @@ export function pivotCacheRecordsXml(table) {
|
|
|
70
55
|
rows +
|
|
71
56
|
`</pivotCacheRecords>`);
|
|
72
57
|
}
|
|
73
|
-
/** The `pivotTableDefinition` part placed on the destination sheet: the field layout that binds the
|
|
74
|
-
* cache (by `cacheId`) to the row/column axes and the summed value field. */
|
|
75
58
|
export function pivotTableXml(table, name, cacheId) {
|
|
76
59
|
const rowField = table.rowFields[0];
|
|
77
60
|
const columnField = table.columnFields[0];
|
|
78
61
|
const rowGroups = table.cacheFields[rowField]?.sharedItems?.length ?? 1;
|
|
79
62
|
const columnGroups = table.cacheFields[columnField]?.sharedItems?.length ?? 1;
|
|
80
|
-
// A generous bounding box on the destination sheet: a row-label column plus one column per column
|
|
81
|
-
// group plus a grand-total column; two header rows plus one row per row group plus a grand total.
|
|
82
|
-
// Excel recomputes the exact extent from the cache on refresh, so this only has to be valid.
|
|
83
63
|
const location = `A1:${encodeAddress(2 + columnGroups, 3 + rowGroups)}`;
|
|
84
64
|
const pivotFields = table.cacheFields
|
|
85
65
|
.map((field, index) => {
|
|
@@ -116,8 +96,6 @@ export function pivotTableXml(table, name, cacheId) {
|
|
|
116
96
|
`showRowStripes="0" showColStripes="0" showLastColumn="1"/>` +
|
|
117
97
|
`</pivotTableDefinition>`);
|
|
118
98
|
}
|
|
119
|
-
/** The `<dataField>` that names the aggregated column and selects its function. `sum` is Excel's
|
|
120
|
-
* implicit default, so its `subtotal` attribute is omitted; every other metric names itself. */
|
|
121
99
|
function dataFieldXml(table) {
|
|
122
100
|
const caption = `${METRIC_CAPTIONS[table.metric]} of ${table.valueFieldName}`;
|
|
123
101
|
const subtotal = table.metric === 'sum' ? '' : ` subtotal="${table.metric}"`;
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
// Reading a pivot table back from its OOXML parts — the inverse of `pivot.ts`.
|
|
2
|
-
//
|
|
3
|
-
// A written pivot round-trips today by byte-preservation: the reader keeps the pivot parts verbatim
|
|
4
|
-
// and re-emits them. That keeps the file faithful, but leaves the pivot opaque to the model — a
|
|
5
|
-
// `.model` copy cannot carry a pivot it cannot see. This module reconstructs the *semantic* shape of
|
|
6
|
-
// a pivot (its source, field roles, value field, and aggregation) from the `pivotTableDefinition`
|
|
7
|
-
// and its `pivotCacheDefinition`, so a loaded pivot becomes inspectable data rather than an opaque
|
|
8
|
-
// blob.
|
|
9
|
-
//
|
|
10
|
-
// Read leniently: these parts describe a file that already exists, so a missing or unrecognised
|
|
11
|
-
// attribute yields a sensible default rather than a throw — the strict invariants belong on the
|
|
12
|
-
// authoring path (`core/pivot-table.ts`), not here. A hostile part therefore degrades to an
|
|
13
|
-
// incomplete model; it never crashes the reader.
|
|
14
1
|
import { pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
|
|
15
2
|
import { localName, parseXml } from '../../xml/xml-read.js';
|
|
16
|
-
/** Reconstruct a pivot's semantic model from its two definition parts. The records part is not
|
|
17
|
-
* consulted: the cache's field catalogue and the table's field roles fully describe the pivot's
|
|
18
|
-
* shape, and the aggregated values are Excel's to compute on refresh. */
|
|
19
3
|
export function parsePivotTable(tableXml, cacheXml) {
|
|
20
4
|
const { fields, source } = parsePivotCacheDefinition(cacheXml);
|
|
21
5
|
const def = parsePivotTableDefinition(tableXml);
|
|
@@ -32,13 +16,8 @@ export function parsePivotTable(tableXml, cacheXml) {
|
|
|
32
16
|
metric: def.metric,
|
|
33
17
|
};
|
|
34
18
|
}
|
|
35
|
-
/** The field catalogue and worksheet source from a `pivotCacheDefinition`. Fields are collected in
|
|
36
|
-
* document order — the order a table's `fld`/`x` indices address them by. */
|
|
37
19
|
function parsePivotCacheDefinition(cacheXml) {
|
|
38
20
|
const fields = [];
|
|
39
|
-
// A worksheet source is the assumed default until proven otherwise: it is what our writer emits and
|
|
40
|
-
// the overwhelmingly common shape, and its `<worksheetSource>` child fills in the coordinates. A
|
|
41
|
-
// `<cacheSource type>` we recognise overrides the kind; an unrecognised one degrades to `unknown`.
|
|
42
21
|
let source = { kind: 'worksheet', sheet: '', ref: '' };
|
|
43
22
|
parseXml(cacheXml, {
|
|
44
23
|
onOpen(name, attrs) {
|
|
@@ -56,10 +35,6 @@ function parsePivotCacheDefinition(cacheXml) {
|
|
|
56
35
|
});
|
|
57
36
|
return { fields, source };
|
|
58
37
|
}
|
|
59
|
-
/** The layout half of a pivot: its name, cache id, axis field roles, and the single value field.
|
|
60
|
-
* `<field x>` appears identically inside `<rowFields>` and `<colFields>`, so the current container is
|
|
61
|
-
* tracked to route each into the right axis. Only the first `<dataField>` is modeled — the authoring
|
|
62
|
-
* model supports one value field — and any further ones are ignored rather than rejected. */
|
|
63
38
|
function parsePivotTableDefinition(tableXml) {
|
|
64
39
|
let name = '';
|
|
65
40
|
let cacheId = '';
|
|
@@ -116,16 +91,11 @@ const SOURCE_KINDS = new Set([
|
|
|
116
91
|
'consolidation',
|
|
117
92
|
'scenario',
|
|
118
93
|
]);
|
|
119
|
-
/** Map a `<cacheSource type>` to a known kind. Absent reads as `worksheet` (the spec default and what
|
|
120
|
-
* our writer emits); an unrecognised value reads as `unknown` rather than throwing, keeping the read
|
|
121
|
-
* lenient while still telling a consumer the declared source is not one we model. */
|
|
122
94
|
function sourceKind(type) {
|
|
123
95
|
if (type === undefined)
|
|
124
96
|
return 'worksheet';
|
|
125
97
|
return SOURCE_KINDS.has(type) ? type : 'unknown';
|
|
126
98
|
}
|
|
127
|
-
/** Parse a non-negative field index attribute, or -1 when it is absent or not a whole number — a
|
|
128
|
-
* hostile `x="../etc"` can never become a wild array index this way. */
|
|
129
99
|
function toIndex(value) {
|
|
130
100
|
if (value === undefined)
|
|
131
101
|
return -1;
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
// Streaming row reader: yield a worksheet's rows one at a time, without ever building the whole
|
|
2
|
-
// {@link Workbook} model.
|
|
3
|
-
//
|
|
4
|
-
// `readXlsx` materialises every cell of every sheet as a live `Cell` object held in nested Maps —
|
|
5
|
-
// fine for editing, but for a large sheet read purely to extract its data it holds the entire grid
|
|
6
|
-
// in memory at once. This reader instead *pulls* the sheet's XML through `xmlEvents` and yields a
|
|
7
|
-
// plain {@link StreamedRow} at each `</row>`, retaining only the row currently in hand. Peak model
|
|
8
|
-
// memory is one row, not the sheet.
|
|
9
|
-
//
|
|
10
|
-
// Two entry points sit on the same scanner:
|
|
11
|
-
// - {@link readSheetRows} streams a single selected sheet's rows (the terse data-extraction case).
|
|
12
|
-
// - {@link readWorkbookStream} yields a {@link StreamedSheet} per worksheet in workbook order, so a
|
|
13
|
-
// caller can walk every sheet — each sheet's rows still stream one at a time.
|
|
14
|
-
//
|
|
15
|
-
// Scope of this slice: the package is still inflated whole (bounded by the running counter in
|
|
16
|
-
// `./inflate.ts`) and shared strings / styles are read as whole parts — both are legitimately
|
|
17
|
-
// document-sized and cheap. What this avoids is retaining N materialised cells. A later slice can
|
|
18
|
-
// make the inflate itself per-part lazy; the pull primitive this stands on (`xmlEvents`) is the
|
|
19
|
-
// same one that path will use.
|
|
20
1
|
import { MAX_COLUMN } from '../../core/address.js';
|
|
21
2
|
import { AuthoringError } from '../../errors.js';
|
|
22
3
|
import { boolStrict, closeEmptyElements, localName, xmlEvents } from '../../xml/xml-read.js';
|
|
@@ -25,50 +6,18 @@ import { inflateSpreadsheetPackage, unsupportedWorkbookPart } from '../opc/sniff
|
|
|
25
6
|
import { CellAccumulator } from './cell-accumulator.js';
|
|
26
7
|
import { XlsxParseError } from './errors.js';
|
|
27
8
|
import { DEFAULT_MAX_UNCOMPRESSED, parseRelationships, parseStyleTable, parseWorkbookSheets, resolveWorkbookPart, } from './read.js';
|
|
28
|
-
import { parseSharedStrings } from './shared-strings
|
|
29
|
-
/**
|
|
30
|
-
* Stream a worksheet's rows from an `.xlsx` package, yielding each in sheet order without building
|
|
31
|
-
* the workbook model. Only rows the sheet actually declares are yielded, and within a row only its
|
|
32
|
-
* non-empty cells — a blank or style-only cell contributes nothing, matching the intent of a data
|
|
33
|
-
* read.
|
|
34
|
-
*
|
|
35
|
-
* @param data The raw `.xlsx` bytes.
|
|
36
|
-
* @param options Sheet selector and the inflate bound (see {@link ReadSheetRowsOptions}).
|
|
37
|
-
* @throws {UnsupportedFormatError} if the input is not a readable `.xlsx` package (a legacy `.xls`, a
|
|
38
|
-
* binary `.xlsb`, or an unrecognised/non-ZIP blob — branch on `.format`).
|
|
39
|
-
* @throws {PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
|
|
40
|
-
* truncated archive, or one exceeding the inflate bound (a probable zip bomb).
|
|
41
|
-
* @throws {XlsxParseError} if the package's workbook part declares no worksheets.
|
|
42
|
-
* @throws {RangeError} / {@link AuthoringError} if `options.sheet` selects a position, or a name,
|
|
43
|
-
* that no worksheet has.
|
|
44
|
-
*/
|
|
9
|
+
import { parseSharedStrings } from './read-shared-strings.js';
|
|
45
10
|
export function* readSheetRows(data, options = {}) {
|
|
46
11
|
const pkg = openPackage(data, options.maxUncompressedBytes);
|
|
47
12
|
const chosen = pickSheet(pkg.sheets, options.sheet);
|
|
48
13
|
const sheetXml = pkg.sheetXml(chosen.relId);
|
|
49
|
-
// The sheet is named but its part is missing (a truncated or foreign package) — it has no rows.
|
|
50
14
|
if (sheetXml === undefined)
|
|
51
15
|
return;
|
|
52
16
|
yield* scanSheet(sheetXml, pkg.sharedStrings, pkg.xfStyles, new Set(), []);
|
|
53
17
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Stream every worksheet of an `.xlsx` package in workbook order, without building the workbook
|
|
56
|
-
* model. Each yielded {@link StreamedSheet} carries the declared sheet name and lets the caller
|
|
57
|
-
* stream that sheet's rows and read its hidden-column and merge summaries — the streaming analogue
|
|
58
|
-
* of walking `readXlsx(data).worksheets`.
|
|
59
|
-
*
|
|
60
|
-
* @param data The raw `.xlsx` bytes.
|
|
61
|
-
* @param options The inflate bound (see {@link ReadXlsxOptions}).
|
|
62
|
-
* @throws {UnsupportedFormatError} if the input is not a readable `.xlsx` package (a legacy `.xls`, a
|
|
63
|
-
* binary `.xlsb`, or an unrecognised/non-ZIP blob — branch on `.format`).
|
|
64
|
-
* @throws {PackageReadError} if the input is a ZIP that cannot be unpacked — a corrupt or
|
|
65
|
-
* truncated archive, or one exceeding the inflate bound (a probable zip bomb).
|
|
66
|
-
*/
|
|
67
18
|
export function* readWorkbookStream(data, options = {}) {
|
|
68
19
|
const pkg = openPackage(data, options.maxUncompressedBytes);
|
|
69
20
|
for (const sheet of pkg.sheets) {
|
|
70
|
-
// A named sheet whose part is missing (truncated/foreign package) still surfaces — with no rows,
|
|
71
|
-
// no hidden columns, and no merges — rather than vanishing from the workbook's sheet list.
|
|
72
21
|
const xml = pkg.sheetXml(sheet.relId) ?? '';
|
|
73
22
|
yield new StreamedSheetReader(sheet.name, xml, pkg.sharedStrings, pkg.xfStyles);
|
|
74
23
|
}
|
|
@@ -77,9 +26,6 @@ function openPackage(data, maxUncompressedBytes) {
|
|
|
77
26
|
const cap = maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
|
|
78
27
|
const { partText: text } = packageAccessors(inflateSpreadsheetPackage(data, cap));
|
|
79
28
|
const workbookXml = text('xl/workbook.xml');
|
|
80
|
-
// A binary `.xlsb` is a workbook this library *can* read — just not through here. Row streaming is
|
|
81
|
-
// built on the XML worksheet parser, so the binary cell table has no streaming path yet; say so,
|
|
82
|
-
// rather than reporting the format as unreadable when `readXlsx` would take the very same bytes.
|
|
83
29
|
if (workbookXml === undefined) {
|
|
84
30
|
throw unsupportedWorkbookPart(text, 'the binary .xlsb format (BIFF12) cannot be row-streamed yet; read it with readXlsx or readXlsb');
|
|
85
31
|
}
|
|
@@ -115,10 +61,6 @@ function pickSheet(sheets, selector) {
|
|
|
115
61
|
throw new AuthoringError(`no worksheet named ${JSON.stringify(selector)}`);
|
|
116
62
|
return sheet;
|
|
117
63
|
}
|
|
118
|
-
// A single worksheet exposed by readWorkbookStream. Its rows() re-scans on each call (a fresh pass,
|
|
119
|
-
// so it is safely re-iterable); the hidden-column and merge accessors reuse a completed scan or, if
|
|
120
|
-
// the rows were never drained, drive one of their own. The hidden/merge state is filled in by the
|
|
121
|
-
// same scanSheet pass that yields the rows.
|
|
122
64
|
class StreamedSheetReader {
|
|
123
65
|
name;
|
|
124
66
|
#xml;
|
|
@@ -148,37 +90,19 @@ class StreamedSheetReader {
|
|
|
148
90
|
this.#ensureScanned();
|
|
149
91
|
return this.#merges;
|
|
150
92
|
}
|
|
151
|
-
// Drain a scan purely for its summaries when the caller reads them without (or before) iterating
|
|
152
|
-
// rows. A completed row iteration already set #scanned, so this re-scans nothing in the common
|
|
153
|
-
// streaming idiom.
|
|
154
93
|
#ensureScanned() {
|
|
155
94
|
if (this.#scanned)
|
|
156
95
|
return;
|
|
157
96
|
for (const _row of this.rows()) {
|
|
158
|
-
// The rows themselves are irrelevant here — we only want the hidden/merge side effects.
|
|
159
97
|
}
|
|
160
98
|
}
|
|
161
99
|
}
|
|
162
|
-
// A formatted-but-empty `<c/>` is expanded to open+close so it finalises once on close, matching
|
|
163
|
-
// the buffered reader; the text-bearing `<f/>`/`<v/>`/`<t/>` are excluded so an empty one never
|
|
164
|
-
// commits (their close captures text, which an empty tag has none of).
|
|
165
100
|
const CELL_EMPTY_CLOSE = new Set(['c']);
|
|
166
|
-
// Pull the sheet XML through the event stream, yielding a StreamedRow at each `</row>`, while
|
|
167
|
-
// recording the sheet's hidden columns (from `<col hidden>`, before <sheetData>) and merged ranges
|
|
168
|
-
// (from `<mergeCells>`, after <sheetData>) into the caller-supplied collectors. The cell state
|
|
169
|
-
// mirrors the buffered reader's `parseWorksheet` (same self-closing-`<c/>` handling, same capture
|
|
170
|
-
// flags), but commits into a row buffer that is handed off and discarded per row rather than into a
|
|
171
|
-
// persistent Worksheet — that hand-off is what bounds retained memory to one row.
|
|
172
101
|
function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
|
|
173
102
|
let rowNumber = 0;
|
|
174
103
|
let lastRow = 0;
|
|
175
104
|
let rowHidden = false;
|
|
176
105
|
let cells = [];
|
|
177
|
-
// The in-flight `<c>`, gathered exactly as the buffered reader gathers it. This reader drives the
|
|
178
|
-
// same beginCell/setFormula/setValue/appendText methods, then takes only the cell's plain decoded
|
|
179
|
-
// value (via decode) — never the shared-formula / data-table resolution the buffered finalize adds,
|
|
180
|
-
// which a data read does not want. Rich `<r>` runs are deliberately not opened here, so a rich
|
|
181
|
-
// inline string flattens to its concatenated text as a streamed value always has.
|
|
182
106
|
const cell = new CellAccumulator();
|
|
183
107
|
let inInlineString = false;
|
|
184
108
|
let capture = false;
|
|
@@ -188,8 +112,6 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
|
|
|
188
112
|
return;
|
|
189
113
|
const style = cell.styleIndex >= 0 ? xfStyles[cell.styleIndex] : undefined;
|
|
190
114
|
const value = cell.decode(sharedStrings, style);
|
|
191
|
-
// A blank or purely style-only cell decodes to null; a data read wants only cells that carry
|
|
192
|
-
// something (a formula object, an empty string, a false, and a 0 all count — only null drops).
|
|
193
115
|
if (value !== null) {
|
|
194
116
|
const { col, ref } = cell;
|
|
195
117
|
cells.push(style ? { col, address: ref, value, style } : { col, address: ref, value });
|
|
@@ -243,7 +165,6 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
|
|
|
243
165
|
capture = false;
|
|
244
166
|
continue;
|
|
245
167
|
}
|
|
246
|
-
// close
|
|
247
168
|
const local = localName(event.name);
|
|
248
169
|
switch (local) {
|
|
249
170
|
case 'f':
|
|
@@ -270,9 +191,6 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
|
|
|
270
191
|
capture = false;
|
|
271
192
|
}
|
|
272
193
|
}
|
|
273
|
-
// Record the hidden columns a `<col min max hidden>` element declares. The span is clamped to the
|
|
274
|
-
// format's column ceiling and gathered into a Set, so even a hostile file full of full-width hidden
|
|
275
|
-
// spans can add at most MAX_COLUMN distinct entries — never an unbounded allocation.
|
|
276
194
|
function collectHiddenColumn(attrs, hiddenColumns) {
|
|
277
195
|
if (attrs.hidden !== '1' && attrs.hidden !== 'true')
|
|
278
196
|
return;
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
// Parser for the shared-string table (`xl/sharedStrings.xml`), the pool that `t="s"` cells index into.
|
|
2
|
-
// Split out of read.ts beside its sibling parsers (read-styles.ts, rich-runs.ts) so read.ts stays
|
|
3
|
-
// orchestration; the run structure it decodes is owned by RunAccumulator, shared with inline strings.
|
|
4
1
|
import { localName, parseXml } from '../../xml/xml-read.js';
|
|
5
2
|
import { RunAccumulator } from './rich-runs.js';
|
|
6
|
-
// Shared strings resolve `t="s"` cells. Each `<si>` is one entry: a plain `<si><t>…</t>` decodes to a
|
|
7
|
-
// string, while a rich `<si><r><rPr>…</rPr><t>…</t></r>…` decodes to a {@link RichTextValue} whose runs
|
|
8
|
-
// carry their per-run fonts — so rich text Excel pooled reads back formatted, not flattened to text.
|
|
9
|
-
// The run structure inside an `<si>` is identical to an inline string's `<is>`, so it is parsed the
|
|
10
|
-
// same way (see the inline-run accumulation in `parseWorksheet`).
|
|
11
3
|
export function parseSharedStrings(xml) {
|
|
12
4
|
if (xml === '')
|
|
13
5
|
return [];
|
|
14
6
|
const strings = [];
|
|
15
|
-
// Per-`<si>` accumulation: `plain` gathers a bare `<t>`; `runs` gathers `<r>` runs. An `<si>` is
|
|
16
|
-
// rich the moment it holds one `<r>`, at which point its runs — not `plain` — become the entry.
|
|
17
7
|
let plain = '';
|
|
18
8
|
const runs = new RunAccumulator();
|
|
19
9
|
let isRich = false;
|
|
@@ -52,7 +42,6 @@ export function parseSharedStrings(xml) {
|
|
|
52
42
|
const local = localName(name);
|
|
53
43
|
switch (local) {
|
|
54
44
|
case 't':
|
|
55
|
-
// A `<t>` inside a run is that run's text; a bare `<t>` directly in the `<si>` is plain.
|
|
56
45
|
if (!runs.appendText(text))
|
|
57
46
|
plain += text;
|
|
58
47
|
capture = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Font } from '../../core/style.ts';
|
|
1
|
+
import { type Font, type TableStyleTable } from '../../core/style.ts';
|
|
2
2
|
import { type XmlAttributes } from '../../xml/xml-read.ts';
|
|
3
3
|
import { type StyleTable } from '../style/xf-style.ts';
|
|
4
4
|
export type FontDraft = {
|
|
@@ -6,3 +6,27 @@ export type FontDraft = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function parseStyleTable(xml: string): StyleTable;
|
|
8
8
|
export declare function applyFontChild(draft: FontDraft, local: string, attrs: XmlAttributes): void;
|
|
9
|
+
/**
|
|
10
|
+
* Extract the custom indexed-color palette (`<colors><indexedColors>`) from styles.xml as verbatim
|
|
11
|
+
* `<rgbColor rgb="…"/>` fragments, or an empty list when the file rides the default palette. Kept raw
|
|
12
|
+
* — rather than parsed into RGB and re-serialised — so the exact entries (count, order, casing) a
|
|
13
|
+
* source file declared survive a round-trip and every `indexed="…"` reference keeps its RGB.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseIndexedColors(stylesXml: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Extract the most-recently-used colour swatches (`<colors><mruColors>`) from styles.xml as verbatim
|
|
18
|
+
* `<color .../>` fragments, or an empty list when the file declares none. Kept raw for the same reason
|
|
19
|
+
* the indexed palette is: the list is the author's own working set of colours and the model has no
|
|
20
|
+
* use for its contents, only for not losing them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseMruColors(stylesXml: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Extract the `<tableStyles>` block from styles.xml: each `<tableStyle>` definition verbatim, plus the
|
|
25
|
+
* container's nominated `defaultTableStyle`/`defaultPivotStyle`. See {@link TableStyleTable} for why
|
|
26
|
+
* the definitions stay raw while the two names are decoded.
|
|
27
|
+
*
|
|
28
|
+
* A file with no such block — or with the self-closing `count="0"` container Excel writes when it has
|
|
29
|
+
* only defaults to state — yields an empty {@link TableStyleTable.styles} and whichever names it did
|
|
30
|
+
* carry.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseTableStyles(stylesXml: string): TableStyleTable;
|