@shbernal/ts-xlsx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- package/package.json +117 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const NS: {
|
|
2
|
+
readonly contentTypes: "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
3
|
+
readonly packageRels: "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
4
|
+
readonly main: "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
5
|
+
readonly docRels: "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
6
|
+
readonly coreProps: "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
|
|
7
|
+
readonly extProps: "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
|
|
8
|
+
readonly dc: "http://purl.org/dc/elements/1.1/";
|
|
9
|
+
readonly dcterms: "http://purl.org/dc/terms/";
|
|
10
|
+
readonly dcmitype: "http://purl.org/dc/dcmitype/";
|
|
11
|
+
readonly xsi: "http://www.w3.org/2001/XMLSchema-instance";
|
|
12
|
+
};
|
|
13
|
+
export declare const REL: {
|
|
14
|
+
readonly worksheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
|
|
15
|
+
readonly styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
|
|
16
|
+
readonly theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
|
|
17
|
+
readonly officeDocument: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
|
|
18
|
+
readonly coreProps: "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
|
|
19
|
+
readonly extProps: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
|
|
20
|
+
readonly table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
|
|
21
|
+
readonly comments: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
|
|
22
|
+
readonly vmlDrawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
|
|
23
|
+
readonly drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
|
|
24
|
+
readonly printerSettings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings";
|
|
25
|
+
readonly image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
|
|
26
|
+
readonly hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
27
|
+
readonly sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
|
|
28
|
+
readonly pivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable";
|
|
29
|
+
readonly pivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition";
|
|
30
|
+
readonly pivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords";
|
|
31
|
+
readonly threadedComment: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment";
|
|
32
|
+
readonly person: "http://schemas.microsoft.com/office/2017/10/relationships/person";
|
|
33
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
import { CONTENT_TYPES_NS, PKG_RELS_NS, RELATIONSHIPS_NS } from "../opc/namespaces.js";
|
|
9
|
+
import { SPREADSHEETML_NS } from "./namespaces.js";
|
|
10
|
+
export const NS = {
|
|
11
|
+
contentTypes: CONTENT_TYPES_NS,
|
|
12
|
+
packageRels: PKG_RELS_NS,
|
|
13
|
+
main: SPREADSHEETML_NS,
|
|
14
|
+
docRels: RELATIONSHIPS_NS,
|
|
15
|
+
coreProps: 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
|
|
16
|
+
extProps: 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
|
17
|
+
dc: 'http://purl.org/dc/elements/1.1/',
|
|
18
|
+
dcterms: 'http://purl.org/dc/terms/',
|
|
19
|
+
dcmitype: 'http://purl.org/dc/dcmitype/',
|
|
20
|
+
xsi: 'http://www.w3.org/2001/XMLSchema-instance',
|
|
21
|
+
};
|
|
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
|
+
const MS_OFFICE_2017_RELS_NS = 'http://schemas.microsoft.com/office/2017/10/relationships';
|
|
25
|
+
export const REL = {
|
|
26
|
+
worksheet: `${NS.docRels}/worksheet`,
|
|
27
|
+
styles: `${NS.docRels}/styles`,
|
|
28
|
+
theme: `${NS.docRels}/theme`,
|
|
29
|
+
officeDocument: `${NS.docRels}/officeDocument`,
|
|
30
|
+
coreProps: `${NS.packageRels}/metadata/core-properties`,
|
|
31
|
+
extProps: `${NS.docRels}/extended-properties`,
|
|
32
|
+
table: `${NS.docRels}/table`,
|
|
33
|
+
comments: `${NS.docRels}/comments`,
|
|
34
|
+
vmlDrawing: `${NS.docRels}/vmlDrawing`,
|
|
35
|
+
drawing: `${NS.docRels}/drawing`,
|
|
36
|
+
printerSettings: `${NS.docRels}/printerSettings`,
|
|
37
|
+
image: `${NS.docRels}/image`,
|
|
38
|
+
hyperlink: `${NS.docRels}/hyperlink`,
|
|
39
|
+
sharedStrings: `${NS.docRels}/sharedStrings`,
|
|
40
|
+
pivotTable: `${NS.docRels}/pivotTable`,
|
|
41
|
+
pivotCacheDefinition: `${NS.docRels}/pivotCacheDefinition`,
|
|
42
|
+
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
|
+
threadedComment: `${MS_OFFICE_2017_RELS_NS}/threadedComment`,
|
|
46
|
+
person: `${MS_OFFICE_2017_RELS_NS}/person`,
|
|
47
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RichTextRun } from '../../core/value.ts';
|
|
2
|
+
import type { XmlAttributes } from '../../xml/xml-read.ts';
|
|
3
|
+
export declare class RunAccumulator {
|
|
4
|
+
#private;
|
|
5
|
+
reset(): void;
|
|
6
|
+
/** The runs gathered so far. */
|
|
7
|
+
get runs(): RichTextRun[];
|
|
8
|
+
beginRun(): void;
|
|
9
|
+
beginProperties(): void;
|
|
10
|
+
applyProperty(local: string, attrs: XmlAttributes): void;
|
|
11
|
+
appendText(text: string): boolean;
|
|
12
|
+
endRun(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
import { applyFontChild } from "./read-styles.js";
|
|
8
|
+
export class RunAccumulator {
|
|
9
|
+
#runs = [];
|
|
10
|
+
#font = null;
|
|
11
|
+
#text = '';
|
|
12
|
+
#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
|
+
reset() {
|
|
16
|
+
this.#runs = [];
|
|
17
|
+
this.#font = null;
|
|
18
|
+
this.#inRun = false;
|
|
19
|
+
}
|
|
20
|
+
/** The runs gathered so far. */
|
|
21
|
+
get runs() {
|
|
22
|
+
return this.#runs;
|
|
23
|
+
}
|
|
24
|
+
// Open a run: reset the per-run font and text so an unformatted run inherits nothing from the last.
|
|
25
|
+
beginRun() {
|
|
26
|
+
this.#inRun = true;
|
|
27
|
+
this.#font = null;
|
|
28
|
+
this.#text = '';
|
|
29
|
+
}
|
|
30
|
+
// Open the run's `<rPr>` font bundle; its children stream in via {@link applyProperty}.
|
|
31
|
+
beginProperties() {
|
|
32
|
+
if (this.#inRun)
|
|
33
|
+
this.#font = {};
|
|
34
|
+
}
|
|
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
|
+
applyProperty(local, attrs) {
|
|
38
|
+
if (this.#font !== null)
|
|
39
|
+
applyFontChild(this.#font, local, attrs);
|
|
40
|
+
}
|
|
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
|
+
appendText(text) {
|
|
44
|
+
if (!this.#inRun)
|
|
45
|
+
return false;
|
|
46
|
+
this.#text += text;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Commit the open run — with its font, only if that font set at least one facet — to the runs list.
|
|
50
|
+
endRun() {
|
|
51
|
+
if (!this.#inRun)
|
|
52
|
+
return;
|
|
53
|
+
const run = { text: this.#text };
|
|
54
|
+
if (this.#font !== null && Object.keys(this.#font).length > 0)
|
|
55
|
+
run.font = this.#font;
|
|
56
|
+
this.#runs.push(run);
|
|
57
|
+
this.#inRun = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RichTextRun } from '../../core/value.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Serialise a rich-text value's runs as the inner content of an `<is>` element. A zero-length run
|
|
4
|
+
* is dropped: an empty `<t/>` is schema-invalid — Excel flags the file as corrupt — and an empty
|
|
5
|
+
* run contributes nothing to the rendered text, so omitting it is loss-free.
|
|
6
|
+
*/
|
|
7
|
+
export declare function richTextRunsXml(runs: readonly RichTextRun[]): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
import { textElement } from "../../xml/xml.js";
|
|
9
|
+
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
|
+
export function richTextRunsXml(runs) {
|
|
16
|
+
return runs
|
|
17
|
+
.filter((run) => run.text !== '')
|
|
18
|
+
.map((run) => {
|
|
19
|
+
const rPr = run.font !== undefined ? `<rPr>${fontXml(run.font, 'rFont')}</rPr>` : '';
|
|
20
|
+
return `<r>${rPr}${textElement(run.text)}</r>`;
|
|
21
|
+
})
|
|
22
|
+
.join('');
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
import { encodeAddress } from "../../core/address.js";
|
|
4
|
+
import { isFormulaValue, isSharedFormulaValue } from "../../core/value.js";
|
|
5
|
+
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
|
+
export function planSharedFormulas(sheet) {
|
|
12
|
+
const groups = new Map();
|
|
13
|
+
for (const { cells } of sheet.rows()) {
|
|
14
|
+
for (const cell of cells) {
|
|
15
|
+
if (isSharedFormulaValue(cell.value)) {
|
|
16
|
+
const clones = groups.get(cell.value.sharedFormula);
|
|
17
|
+
if (clones !== undefined)
|
|
18
|
+
clones.push(cell);
|
|
19
|
+
else
|
|
20
|
+
groups.set(cell.value.sharedFormula, [cell]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const roles = new Map();
|
|
25
|
+
let si = 0;
|
|
26
|
+
for (const [masterAddress, clones] of groups) {
|
|
27
|
+
const master = sheet.getCell(masterAddress);
|
|
28
|
+
if (!isFormulaValue(master.value)) {
|
|
29
|
+
const offender = clones[0];
|
|
30
|
+
throw new AuthoringError(`shared-formula clone ${offender.address} names master ${masterAddress}, which holds no formula`);
|
|
31
|
+
}
|
|
32
|
+
let maxCol = master.col;
|
|
33
|
+
let maxRow = master.row;
|
|
34
|
+
for (const clone of clones) {
|
|
35
|
+
if (clone.col < master.col || clone.row < master.row) {
|
|
36
|
+
throw new AuthoringError(`shared-formula master ${masterAddress} must sit above and/or left of clone ${clone.address}`);
|
|
37
|
+
}
|
|
38
|
+
if (clone.col > maxCol)
|
|
39
|
+
maxCol = clone.col;
|
|
40
|
+
if (clone.row > maxRow)
|
|
41
|
+
maxRow = clone.row;
|
|
42
|
+
}
|
|
43
|
+
roles.set(masterAddress, {
|
|
44
|
+
si,
|
|
45
|
+
ref: `${encodeAddress(master.col, master.row)}:${encodeAddress(maxCol, maxRow)}`,
|
|
46
|
+
});
|
|
47
|
+
for (const clone of clones)
|
|
48
|
+
roles.set(clone.address, { si });
|
|
49
|
+
si += 1;
|
|
50
|
+
}
|
|
51
|
+
return roles;
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
import { localName, parseXml } from "../../xml/xml-read.js";
|
|
5
|
+
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
|
+
export function parseSharedStrings(xml) {
|
|
12
|
+
if (xml === '')
|
|
13
|
+
return [];
|
|
14
|
+
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
|
+
let plain = '';
|
|
18
|
+
const runs = new RunAccumulator();
|
|
19
|
+
let isRich = false;
|
|
20
|
+
let capture = false;
|
|
21
|
+
let text = '';
|
|
22
|
+
parseXml(xml, {
|
|
23
|
+
onOpen(name, attrs) {
|
|
24
|
+
const local = localName(name);
|
|
25
|
+
switch (local) {
|
|
26
|
+
case 'si':
|
|
27
|
+
plain = '';
|
|
28
|
+
runs.reset();
|
|
29
|
+
isRich = false;
|
|
30
|
+
break;
|
|
31
|
+
case 'r':
|
|
32
|
+
isRich = true;
|
|
33
|
+
runs.beginRun();
|
|
34
|
+
break;
|
|
35
|
+
case 'rPr':
|
|
36
|
+
runs.beginProperties();
|
|
37
|
+
break;
|
|
38
|
+
case 't':
|
|
39
|
+
capture = true;
|
|
40
|
+
text = '';
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
runs.applyProperty(local, attrs);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
onText(chunk) {
|
|
48
|
+
if (capture)
|
|
49
|
+
text += chunk;
|
|
50
|
+
},
|
|
51
|
+
onClose(name) {
|
|
52
|
+
const local = localName(name);
|
|
53
|
+
switch (local) {
|
|
54
|
+
case 't':
|
|
55
|
+
// A `<t>` inside a run is that run's text; a bare `<t>` directly in the `<si>` is plain.
|
|
56
|
+
if (!runs.appendText(text))
|
|
57
|
+
plain += text;
|
|
58
|
+
capture = false;
|
|
59
|
+
break;
|
|
60
|
+
case 'r':
|
|
61
|
+
runs.endRun();
|
|
62
|
+
break;
|
|
63
|
+
case 'si':
|
|
64
|
+
strings.push(isRich ? { richText: runs.runs } : plain);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return strings;
|
|
70
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RichTextValue } from '../../core/value.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Interns cell string values into the shared-strings pool. {@link intern} returns the index a
|
|
4
|
+
* `t="s"` cell writes as its `<v>`; identical entries collapse to one, so `count` (total references)
|
|
5
|
+
* and `uniqueCount` (distinct entries) diverge exactly as Excel records them. Each entry is stored as
|
|
6
|
+
* its rendered `<si>` inner XML, which is also its dedup key — a plain string (`<t>…`) and rich runs
|
|
7
|
+
* (`<r>…`) render to distinct markup, so the two kinds never collide in the pool.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SharedStringTable {
|
|
10
|
+
#private;
|
|
11
|
+
/** Intern a plain or rich string and return its `<si>` index, reusing the entry when it repeats. */
|
|
12
|
+
intern(value: string | RichTextValue): number;
|
|
13
|
+
/** Whether no string has been interned — the writer omits the part entirely when so. */
|
|
14
|
+
get isEmpty(): boolean;
|
|
15
|
+
/** Serialise the pool as the `xl/sharedStrings.xml` part. */
|
|
16
|
+
toXml(): string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The shared-strings table: `xl/sharedStrings.xml`, the workbook-wide pool a `t="s"` cell indexes.
|
|
2
|
+
//
|
|
3
|
+
// When the writer runs with `useSharedStrings`, a plain string cell value is interned here — an
|
|
4
|
+
// identical string is stored once and every cell holding it references the single `<si>` entry by
|
|
5
|
+
// index — rather than repeating the text inline in each cell. This trades a second part and an
|
|
6
|
+
// indirection for a smaller package when strings repeat, the storage Excel itself prefers.
|
|
7
|
+
//
|
|
8
|
+
// Both plain strings and rich text are pooled: a plain value becomes a `<si><t>…</t></si>` entry, a
|
|
9
|
+
// rich value a `<si><r>…</r>…</si>` entry carrying its per-run formatting — the rich `<si>` runs Excel
|
|
10
|
+
// itself writes. A `t="s"` cell then indexes either, and the reader reconstructs the runs, so pooled
|
|
11
|
+
// rich text round-trips its formatting rather than flattening to text.
|
|
12
|
+
import { textElement, XML_DECLARATION } from "../../xml/xml.js";
|
|
13
|
+
import { SPREADSHEETML_NS } from "./namespaces.js";
|
|
14
|
+
import { richTextRunsXml } from "./rich-text.js";
|
|
15
|
+
/**
|
|
16
|
+
* Interns cell string values into the shared-strings pool. {@link intern} returns the index a
|
|
17
|
+
* `t="s"` cell writes as its `<v>`; identical entries collapse to one, so `count` (total references)
|
|
18
|
+
* and `uniqueCount` (distinct entries) diverge exactly as Excel records them. Each entry is stored as
|
|
19
|
+
* its rendered `<si>` inner XML, which is also its dedup key — a plain string (`<t>…`) and rich runs
|
|
20
|
+
* (`<r>…`) render to distinct markup, so the two kinds never collide in the pool.
|
|
21
|
+
*/
|
|
22
|
+
export class SharedStringTable {
|
|
23
|
+
#indexByEntry = new Map();
|
|
24
|
+
#entries = [];
|
|
25
|
+
#references = 0;
|
|
26
|
+
/** Intern a plain or rich string and return its `<si>` index, reusing the entry when it repeats. */
|
|
27
|
+
intern(value) {
|
|
28
|
+
this.#references += 1;
|
|
29
|
+
const inner = typeof value === 'string' ? textElement(value) : richTextRunsXml(value.richText);
|
|
30
|
+
const existing = this.#indexByEntry.get(inner);
|
|
31
|
+
if (existing !== undefined)
|
|
32
|
+
return existing;
|
|
33
|
+
const index = this.#entries.length;
|
|
34
|
+
this.#indexByEntry.set(inner, index);
|
|
35
|
+
this.#entries.push(inner);
|
|
36
|
+
return index;
|
|
37
|
+
}
|
|
38
|
+
/** Whether no string has been interned — the writer omits the part entirely when so. */
|
|
39
|
+
get isEmpty() {
|
|
40
|
+
return this.#entries.length === 0;
|
|
41
|
+
}
|
|
42
|
+
/** Serialise the pool as the `xl/sharedStrings.xml` part. */
|
|
43
|
+
toXml() {
|
|
44
|
+
const items = this.#entries.map((inner) => `<si>${inner}</si>`).join('');
|
|
45
|
+
return (XML_DECLARATION +
|
|
46
|
+
`<sst xmlns="${SPREADSHEETML_NS}" count="${this.#references}" uniqueCount="${this.#entries.length}">` +
|
|
47
|
+
items +
|
|
48
|
+
'</sst>');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AutoFilter } from '../../core/autofilter.ts';
|
|
2
|
+
import type { HeaderFooter, PageBreak, PageMargins, PageSetup, PrintOptions } from '../../core/page-setup.ts';
|
|
3
|
+
import { type SheetProtection } from '../../core/protection.ts';
|
|
4
|
+
import type { SheetView, Worksheet } from '../../core/worksheet.ts';
|
|
5
|
+
export declare function sheetViewsXml(view: SheetView, active: boolean): string;
|
|
6
|
+
export declare function sheetPrXml(sheet: Worksheet): string;
|
|
7
|
+
export declare function sheetProtectionXml(protection: SheetProtection | undefined): string;
|
|
8
|
+
export declare function autoFilterXml(filter: AutoFilter | undefined): string;
|
|
9
|
+
export declare function headerFooterXml(hf: HeaderFooter): string;
|
|
10
|
+
export declare function printOptionsXml(printOptions: PrintOptions): string;
|
|
11
|
+
export declare function pageMarginsXml(margins: PageMargins): string;
|
|
12
|
+
export declare function pageSetupXml(pageSetup: PageSetup, printerSettingsRelId: string | null): string;
|
|
13
|
+
export declare function pageBreaksXml(breaks: readonly PageBreak[], element: 'rowBreaks' | 'colBreaks'): string;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// The worksheet's non-cell property blocks: `<sheetPr>`, `<sheetViews>`, `<sheetProtection>`,
|
|
2
|
+
// `<autoFilter>`, and the print settings (`<printOptions>`, `<pageMargins>`, `<pageSetup>`,
|
|
3
|
+
// `<headerFooter>`, `<rowBreaks>`/`<colBreaks>`). Each renders one CT_Worksheet child (or child
|
|
4
|
+
// group) independently of the row/cell body `worksheet-xml.ts` orchestrates them alongside.
|
|
5
|
+
import { encodeAddress } from "../../core/address.js";
|
|
6
|
+
import { SHEET_PROTECTION_FLAGS } from "../../core/protection.js";
|
|
7
|
+
import { attr, boolAttr, escapeAttr, escapeText, numberText } from "../../xml/xml.js";
|
|
8
|
+
import { colorAttrs } from "./styles.js";
|
|
9
|
+
// `<sheetViews>` holds the sheet's single view. A frozen view adds a `<pane>` recording the split
|
|
10
|
+
// and a `<selection>` naming the pane the split activates, exactly as Excel writes it — a normal
|
|
11
|
+
// view carries neither, so unfreezing leaves no leftover `<pane>` that would trip a repair prompt.
|
|
12
|
+
// The active pane is whichever scrolling region the freeze creates: bottom-right when both axes are
|
|
13
|
+
// frozen, else top-right (columns only) or bottom-left (rows only).
|
|
14
|
+
//
|
|
15
|
+
// `active` marks this sheet as the one selected on open (`tabSelected`). Exactly one sheet in a
|
|
16
|
+
// workbook carries it — with none, the consumer opens with no sheet view initialised; with several,
|
|
17
|
+
// the sheets form a *group selection*, where an edit to one is applied to all of them. The caller
|
|
18
|
+
// (`worksheetXml`, fed from `Workbook.activeTabIndex`) is what guarantees the "exactly one".
|
|
19
|
+
export function sheetViewsXml(view, active) {
|
|
20
|
+
const selected = active ? ' tabSelected="1"' : '';
|
|
21
|
+
const xSplit = view.xSplit ?? 0;
|
|
22
|
+
const ySplit = view.ySplit ?? 0;
|
|
23
|
+
if (view.state !== 'frozen' || (xSplit === 0 && ySplit === 0)) {
|
|
24
|
+
return `<sheetViews><sheetView${selected} workbookViewId="0"/></sheetViews>`;
|
|
25
|
+
}
|
|
26
|
+
const topLeftCell = view.topLeftCell ?? encodeAddress(xSplit + 1, ySplit + 1);
|
|
27
|
+
const activePane = xSplit > 0 && ySplit > 0 ? 'bottomRight' : xSplit > 0 ? 'topRight' : 'bottomLeft';
|
|
28
|
+
const pane = '<pane' +
|
|
29
|
+
(xSplit > 0 ? ` xSplit="${xSplit}"` : '') +
|
|
30
|
+
(ySplit > 0 ? ` ySplit="${ySplit}"` : '') +
|
|
31
|
+
` topLeftCell="${escapeAttr(topLeftCell)}" activePane="${activePane}" state="frozen"/>`;
|
|
32
|
+
const selection = `<selection pane="${activePane}" activeCell="${escapeAttr(topLeftCell)}" sqref="${escapeAttr(topLeftCell)}"/>`;
|
|
33
|
+
return `<sheetViews><sheetView${selected} workbookViewId="0">${pane}${selection}</sheetView></sheetViews>`;
|
|
34
|
+
}
|
|
35
|
+
// `<sheetPr>` carries the sheet's appearance properties: the tab colour, the outline
|
|
36
|
+
// summary-position flags, and the fit-to-page flag. It is the first child of `<worksheet>` in
|
|
37
|
+
// CT_Worksheet order; its own children follow CT_SheetPr order — `<tabColor>`, `<outlinePr>`, then
|
|
38
|
+
// `<pageSetUpPr>`. Omitted entirely when the sheet carries none, so an unadorned sheet stays
|
|
39
|
+
// byte-clean.
|
|
40
|
+
export function sheetPrXml(sheet) {
|
|
41
|
+
const children = (sheet.tabColor !== undefined ? `<tabColor ${colorAttrs(sheet.tabColor)}/>` : '') +
|
|
42
|
+
outlinePrXml(sheet.outline) +
|
|
43
|
+
pageSetUpPrXml(sheet.pageSetup);
|
|
44
|
+
return children === '' ? '' : `<sheetPr>${children}</sheetPr>`;
|
|
45
|
+
}
|
|
46
|
+
// `<pageSetUpPr>` holds the fit-to-page toggle, which lives on the sheet properties rather than on
|
|
47
|
+
// `<pageSetup>` — Excel reads it from here to decide whether the `fitToWidth`/`fitToHeight` counts
|
|
48
|
+
// or the fixed `scale` govern printing. Emitted only when the author set the flag.
|
|
49
|
+
function pageSetUpPrXml(pageSetup) {
|
|
50
|
+
return pageSetup.fitToPage ? '<pageSetUpPr fitToPage="1"/>' : '';
|
|
51
|
+
}
|
|
52
|
+
// `<outlinePr>` carries only the summary-position flags today. Each is emitted solely when the
|
|
53
|
+
// caller set it, so an inverted placement (`summaryBelow="0"`) is honoured while an untouched sheet
|
|
54
|
+
// keeps the element out of the file entirely.
|
|
55
|
+
function outlinePrXml(outline) {
|
|
56
|
+
const attrs = boolAttr('summaryBelow', outline.summaryBelow) + boolAttr('summaryRight', outline.summaryRight);
|
|
57
|
+
return attrs === '' ? '' : `<outlinePr${attrs}/>`;
|
|
58
|
+
}
|
|
59
|
+
// Each sheet-protection flag maps to a `<sheetProtection>` attribute whose value is INVERTED
|
|
60
|
+
// from the author-facing allow-flag: the attribute records that an operation is *forbidden*
|
|
61
|
+
// ("1"), so `allow: true` serialises as "0". Only a value that differs from OOXML's per-
|
|
62
|
+
// attribute default (see SHEET_PROTECTION_FLAGS) is written — most editing operations default
|
|
63
|
+
// to forbidden under protection, while selecting cells defaults to permitted.
|
|
64
|
+
//
|
|
65
|
+
// <sheetProtection> is what makes the per-cell locked/hidden flags bite. `sheet="1"` marks the
|
|
66
|
+
// sheet protected; the password credential (when present) guards lifting it; the flag attributes
|
|
67
|
+
// carve out the operations that stay available. base64 salt/hash use only XML-safe characters.
|
|
68
|
+
export function sheetProtectionXml(protection) {
|
|
69
|
+
if (protection === undefined)
|
|
70
|
+
return '';
|
|
71
|
+
const { flags, credential } = protection;
|
|
72
|
+
let attrs = '';
|
|
73
|
+
if (credential !== undefined) {
|
|
74
|
+
attrs +=
|
|
75
|
+
` algorithmName="${credential.algorithmName}"` +
|
|
76
|
+
` hashValue="${credential.hashValue}"` +
|
|
77
|
+
` saltValue="${credential.saltValue}"` +
|
|
78
|
+
` spinCount="${credential.spinCount}"`;
|
|
79
|
+
}
|
|
80
|
+
attrs += ' sheet="1"';
|
|
81
|
+
for (const { key, defaultForbidden } of SHEET_PROTECTION_FLAGS) {
|
|
82
|
+
const allow = flags[key];
|
|
83
|
+
if (allow === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
const forbidden = !allow;
|
|
86
|
+
if (forbidden === defaultForbidden)
|
|
87
|
+
continue;
|
|
88
|
+
attrs += boolAttr(key, forbidden);
|
|
89
|
+
}
|
|
90
|
+
return `<sheetProtection${attrs}/>`;
|
|
91
|
+
}
|
|
92
|
+
// The sheet's autofilter: `<autoFilter ref="A1:C10"/>` when it only draws dropdowns, or with nested
|
|
93
|
+
// `<filterColumn>` children when columns carry criteria. Its companion `_FilterDatabase` defined name
|
|
94
|
+
// (the range Excel derives filtering from) is written in the workbook part, so a sheet with no filter
|
|
95
|
+
// emits nothing here and nothing there.
|
|
96
|
+
export function autoFilterXml(filter) {
|
|
97
|
+
if (filter === undefined)
|
|
98
|
+
return '';
|
|
99
|
+
const ref = escapeAttr(filter.ref);
|
|
100
|
+
if (filter.columns.length === 0)
|
|
101
|
+
return `<autoFilter ref="${ref}"/>`;
|
|
102
|
+
return `<autoFilter ref="${ref}">${filter.columns.map(filterColumnXml).join('')}</autoFilter>`;
|
|
103
|
+
}
|
|
104
|
+
function filterColumnXml(column) {
|
|
105
|
+
return `<filterColumn colId="${column.colId}">${filterCriteriaXml(column.criteria)}</filterColumn>`;
|
|
106
|
+
}
|
|
107
|
+
// A values filter is `<filters>` with a `<filter val>` per allowed value (and `blank="1"` to admit
|
|
108
|
+
// empty cells); a custom filter is `<customFilters>` with one or two `<customFilter operator val>`
|
|
109
|
+
// predicates, `and="1"` when they are AND-combined rather than OR.
|
|
110
|
+
function filterCriteriaXml(criteria) {
|
|
111
|
+
if (criteria.kind === 'values') {
|
|
112
|
+
const blankAttr = criteria.blank ? ' blank="1"' : '';
|
|
113
|
+
const filters = criteria.values.map((value) => `<filter val="${escapeAttr(value)}"/>`).join('');
|
|
114
|
+
return `<filters${blankAttr}>${filters}</filters>`;
|
|
115
|
+
}
|
|
116
|
+
const andAttr = criteria.and ? ' and="1"' : '';
|
|
117
|
+
const predicates = criteria.predicates
|
|
118
|
+
.map((p) => `<customFilter operator="${p.operator}" val="${escapeAttr(p.val)}"/>`)
|
|
119
|
+
.join('');
|
|
120
|
+
return `<customFilters${andAttr}>${predicates}</customFilters>`;
|
|
121
|
+
}
|
|
122
|
+
// CT_HeaderFooter child order, paired with the flag their presence gates: the even- and
|
|
123
|
+
// first-page variants are silently ignored by Excel unless differentOddEven / differentFirst
|
|
124
|
+
// are set, so the writer derives each flag from whether any variant in its class was provided.
|
|
125
|
+
const HF_CHILDREN = [
|
|
126
|
+
{ tag: 'oddHeader', key: 'oddHeader' },
|
|
127
|
+
{ tag: 'oddFooter', key: 'oddFooter' },
|
|
128
|
+
{ tag: 'evenHeader', key: 'evenHeader' },
|
|
129
|
+
{ tag: 'evenFooter', key: 'evenFooter' },
|
|
130
|
+
{ tag: 'firstHeader', key: 'firstHeader' },
|
|
131
|
+
{ tag: 'firstFooter', key: 'firstFooter' },
|
|
132
|
+
];
|
|
133
|
+
export function headerFooterXml(hf) {
|
|
134
|
+
const children = HF_CHILDREN.filter(({ key }) => hf[key] !== undefined);
|
|
135
|
+
if (children.length === 0)
|
|
136
|
+
return '';
|
|
137
|
+
const differentOddEven = hf.evenHeader !== undefined || hf.evenFooter !== undefined;
|
|
138
|
+
const differentFirst = hf.firstHeader !== undefined || hf.firstFooter !== undefined;
|
|
139
|
+
let attrs = '';
|
|
140
|
+
if (differentOddEven)
|
|
141
|
+
attrs += ' differentOddEven="1"';
|
|
142
|
+
if (differentFirst)
|
|
143
|
+
attrs += ' differentFirst="1"';
|
|
144
|
+
const body = children
|
|
145
|
+
.map(({ tag, key }) => `<${tag}>${escapeText(hf[key])}</${tag}>`)
|
|
146
|
+
.join('');
|
|
147
|
+
return `<headerFooter${attrs}>${body}</headerFooter>`;
|
|
148
|
+
}
|
|
149
|
+
// Excel's "Normal" margins, in inches — the defaults Excel writes for an untouched sheet.
|
|
150
|
+
const DEFAULT_MARGINS = {
|
|
151
|
+
left: 0.7,
|
|
152
|
+
right: 0.7,
|
|
153
|
+
top: 0.75,
|
|
154
|
+
bottom: 0.75,
|
|
155
|
+
header: 0.3,
|
|
156
|
+
footer: 0.3,
|
|
157
|
+
};
|
|
158
|
+
const MARGIN_SIDES = ['left', 'right', 'top', 'bottom', 'header', 'footer'];
|
|
159
|
+
// `<printOptions>` carries the print-toggle flags and sits just before `<pageMargins>` in
|
|
160
|
+
// CT_Worksheet order. Each attribute is emitted only when the model carries it — as an explicit
|
|
161
|
+
// `="1"`/`="0"` so a caller can force a flag off against Excel's default — and an untouched sheet
|
|
162
|
+
// keeps the element out of the file entirely.
|
|
163
|
+
export function printOptionsXml(printOptions) {
|
|
164
|
+
const attrs = boolAttr('horizontalCentered', printOptions.horizontalCentered) +
|
|
165
|
+
boolAttr('verticalCentered', printOptions.verticalCentered) +
|
|
166
|
+
boolAttr('headings', printOptions.headings) +
|
|
167
|
+
boolAttr('gridLines', printOptions.gridLines) +
|
|
168
|
+
boolAttr('gridLinesSet', printOptions.gridLinesSet);
|
|
169
|
+
return attrs === '' ? '' : `<printOptions${attrs}/>`;
|
|
170
|
+
}
|
|
171
|
+
// OOXML's <pageMargins> is all-or-nothing: setting any one margin requires all six, or Excel
|
|
172
|
+
// repairs the file. So the element is emitted only when the caller set at least one, and the
|
|
173
|
+
// untouched sides fall back to the Normal-preset defaults.
|
|
174
|
+
export function pageMarginsXml(margins) {
|
|
175
|
+
if (MARGIN_SIDES.every((side) => margins[side] === undefined))
|
|
176
|
+
return '';
|
|
177
|
+
const attrs = MARGIN_SIDES.map((side) => `${side}="${numberText(margins[side] ?? DEFAULT_MARGINS[side])}"`).join(' ');
|
|
178
|
+
return `<pageMargins ${attrs}/>`;
|
|
179
|
+
}
|
|
180
|
+
// `<pageSetup>` carries the print-scaling attributes (all but `fitToPage`, which is a `<sheetPr>`
|
|
181
|
+
// flag). It sits between `<pageMargins>` and `<headerFooter>` in CT_Worksheet order. Each attribute
|
|
182
|
+
// is emitted only when the author set it, so an untouched sheet keeps the element out of the file
|
|
183
|
+
// and a partially-set one never fabricates the counts Excel would otherwise default. A non-null
|
|
184
|
+
// `printerSettingsRelId` links the sheet's opaque printer-settings blob and forces the element out
|
|
185
|
+
// even when no scaling attribute is set — the reference is the only thing the model has to carry.
|
|
186
|
+
export function pageSetupXml(pageSetup, printerSettingsRelId) {
|
|
187
|
+
const attrs = attr('paperSize', pageSetup.paperSize) +
|
|
188
|
+
attr('scale', pageSetup.scale) +
|
|
189
|
+
attr('fitToWidth', pageSetup.fitToWidth) +
|
|
190
|
+
attr('fitToHeight', pageSetup.fitToHeight) +
|
|
191
|
+
(pageSetup.pageOrder !== undefined ? ` pageOrder="${pageSetup.pageOrder}"` : '') +
|
|
192
|
+
(pageSetup.orientation !== undefined ? ` orientation="${pageSetup.orientation}"` : '') +
|
|
193
|
+
(printerSettingsRelId !== null ? ` r:id="${printerSettingsRelId}"` : '');
|
|
194
|
+
return attrs === '' ? '' : `<pageSetup${attrs}/>`;
|
|
195
|
+
}
|
|
196
|
+
// Manual page breaks (`<rowBreaks>`/`<colBreaks>`): one `<brk>` per row/column the layout splits
|
|
197
|
+
// before. Excel records both the running total (`count`) and the manual subset (`manualBreakCount`);
|
|
198
|
+
// every break the model carries is a manual, author-set one, so the two counts coincide. `max` bounds
|
|
199
|
+
// the break across the other axis (Excel writes the last row/column index); a break without one is
|
|
200
|
+
// emitted bare. Row and column breaks share this shape, differing only in the wrapping element.
|
|
201
|
+
export function pageBreaksXml(breaks, element) {
|
|
202
|
+
if (breaks.length === 0)
|
|
203
|
+
return '';
|
|
204
|
+
const brks = breaks
|
|
205
|
+
.map((brk) => {
|
|
206
|
+
const maxAttr = brk.max !== undefined ? ` max="${brk.max}"` : '';
|
|
207
|
+
return `<brk id="${brk.id}"${maxAttr} man="1"/>`;
|
|
208
|
+
})
|
|
209
|
+
.join('');
|
|
210
|
+
return `<${element} count="${breaks.length}" manualBreakCount="${breaks.length}">${brks}</${element}>`;
|
|
211
|
+
}
|