@shbernal/ts-xlsx 2.1.0 → 3.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/dist/core/autofilter.d.ts +1 -1
- package/dist/core/autofilter.js +3 -5
- package/dist/core/axis-handle.d.ts +8 -0
- package/dist/core/axis-handle.js +14 -0
- package/dist/core/cell.d.ts +24 -0
- package/dist/core/cell.js +9 -1
- package/dist/core/column.d.ts +4 -1
- package/dist/core/column.js +31 -35
- package/dist/core/conditional-formatting.d.ts +5 -5
- package/dist/core/conditional-formatting.js +11 -25
- package/dist/core/data-validation.d.ts +3 -3
- package/dist/core/data-validation.js +7 -15
- package/dist/core/image.d.ts +1 -1
- package/dist/core/image.js +2 -4
- package/dist/core/merge-index.d.ts +31 -0
- package/dist/core/merge-index.js +60 -0
- package/dist/core/merge.d.ts +0 -10
- package/dist/core/merge.js +0 -8
- package/dist/core/page-setup.d.ts +72 -2
- package/dist/core/page-setup.js +34 -8
- package/dist/core/pivot-table.d.ts +1 -1
- package/dist/core/pivot-table.js +4 -8
- package/dist/core/row.d.ts +4 -1
- package/dist/core/row.js +19 -23
- package/dist/core/style.d.ts +51 -7
- package/dist/core/style.js +29 -34
- package/dist/core/table.d.ts +1 -1
- package/dist/core/table.js +3 -5
- package/dist/core/used-extent.d.ts +44 -0
- package/dist/core/used-extent.js +102 -0
- package/dist/core/worksheet.d.ts +17 -6
- package/dist/core/worksheet.js +48 -42
- package/dist/customui/ribbon.js +4 -6
- package/dist/io/opc/inflate.js +1 -1
- package/dist/io/opc/part-paths.d.ts +1 -0
- package/dist/io/opc/part-paths.js +15 -0
- package/dist/io/opc/read-opc.d.ts +22 -3
- package/dist/io/opc/read-opc.js +11 -39
- package/dist/io/opc/rels.d.ts +1 -6
- package/dist/io/opc/rels.js +1 -4
- package/dist/io/style/xf-style.d.ts +26 -2
- package/dist/io/style/xf-style.js +6 -0
- package/dist/io/xlsb/read-styles.js +6 -3
- package/dist/io/xlsb/read.d.ts +1 -1
- package/dist/io/xlsb/read.js +5 -8
- package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
- package/dist/io/xlsx/cell-accumulator.js +1 -1
- package/dist/io/xlsx/cell-value.js +1 -1
- package/dist/io/xlsx/color-xml.js +1 -1
- package/dist/io/xlsx/comments.js +2 -1
- package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
- package/dist/io/xlsx/conditional-formatting.js +22 -21
- package/dist/io/xlsx/data-validation.d.ts +9 -7
- package/dist/io/xlsx/data-validation.js +10 -9
- package/dist/io/xlsx/edit-vba.js +4 -11
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +20 -17
- package/dist/io/xlsx/images.js +15 -14
- package/dist/io/xlsx/package-plan.d.ts +9 -1
- package/dist/io/xlsx/package-plan.js +29 -16
- package/dist/io/xlsx/part-names.d.ts +41 -0
- package/dist/io/xlsx/part-names.js +46 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-rows.js +12 -13
- package/dist/io/xlsx/read-shared-strings.js +2 -1
- package/dist/io/xlsx/read-styles.d.ts +1 -1
- package/dist/io/xlsx/read-styles.js +63 -42
- package/dist/io/xlsx/read-worksheet.d.ts +10 -1
- package/dist/io/xlsx/read-worksheet.js +43 -56
- package/dist/io/xlsx/read.d.ts +2 -3
- package/dist/io/xlsx/read.js +81 -73
- package/dist/io/xlsx/rich-runs.d.ts +1 -1
- package/dist/io/xlsx/rich-runs.js +2 -1
- package/dist/io/xlsx/sheet-properties.js +21 -28
- package/dist/io/xlsx/styles.js +23 -20
- package/dist/io/xlsx/tables.js +2 -1
- package/dist/io/xlsx/theme-xml.js +1 -1
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.js +29 -28
- package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
- package/dist/io/xlsx/worksheet-xml.js +14 -16
- package/dist/io/xlsx/write-stream.d.ts +6 -0
- package/dist/io/xlsx/write-stream.js +30 -22
- package/dist/io/xlsx/write.d.ts +5 -0
- package/dist/io/xlsx/write.js +155 -131
- package/dist/token-set.d.ts +15 -0
- package/dist/token-set.js +4 -0
- package/dist/vba/cfb-format.d.ts +24 -0
- package/dist/vba/cfb-format.js +12 -0
- package/dist/vba/cfb-writer.js +17 -17
- package/dist/vba/cfb.js +1 -7
- package/dist/vba/ms-ovba.js +32 -11
- package/dist/vba/project.js +13 -13
- package/dist/vba/vba-encoding.js +1 -1
- package/dist/xml/xml-read.d.ts +59 -74
- package/dist/xml/xml-read.js +83 -176
- package/dist/xml/xml-scan.d.ts +100 -0
- package/dist/xml/xml-scan.js +208 -0
- package/dist/xml/xml.d.ts +1 -1
- package/package.json +2 -3
package/dist/io/xlsb/read.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Workbook } from '../../core/workbook.ts';
|
|
2
|
-
import {
|
|
2
|
+
import type { ReadXlsxOptions } from '../opc/read-options.ts';
|
|
3
3
|
/** The office-document part every `.xlsb` package is entered through. */
|
|
4
4
|
export declare const XLSB_WORKBOOK_PART = "xl/workbook.bin";
|
|
5
5
|
/**
|
package/dist/io/xlsb/read.js
CHANGED
|
@@ -2,9 +2,7 @@ import { unmangleFunctions } from '../../core/formula.js';
|
|
|
2
2
|
import { INTERNAL } from '../../core/internal.js';
|
|
3
3
|
import { Workbook } from '../../core/workbook.js';
|
|
4
4
|
import { UnsupportedFormatError } from '../opc/errors.js';
|
|
5
|
-
import {
|
|
6
|
-
import { DEFAULT_MAX_UNCOMPRESSED } from '../opc/read-options.js';
|
|
7
|
-
import { inflateSpreadsheetPackage } from '../opc/sniff-format.js';
|
|
5
|
+
import { openSpreadsheetPackage, packageAccessors, readPartRelationships } from '../opc/read-opc.js';
|
|
8
6
|
import { decodeFormula } from './formula.js';
|
|
9
7
|
import { RecordReader } from './primitives.js';
|
|
10
8
|
import { parseSharedStrings } from './read-shared-strings.js';
|
|
@@ -14,8 +12,7 @@ import { readRecords } from './record-stream.js';
|
|
|
14
12
|
import { BRT } from './record-types.js';
|
|
15
13
|
export const XLSB_WORKBOOK_PART = 'xl/workbook.bin';
|
|
16
14
|
export function readXlsb(data, options = {}) {
|
|
17
|
-
|
|
18
|
-
return readXlsbPackage(inflateSpreadsheetPackage(data, cap));
|
|
15
|
+
return readXlsbPackage(openSpreadsheetPackage(data, options.maxUncompressedBytes).files);
|
|
19
16
|
}
|
|
20
17
|
export function readXlsbPackage(files) {
|
|
21
18
|
const { partText, partBytes } = packageAccessors(files);
|
|
@@ -23,7 +20,7 @@ export function readXlsbPackage(files) {
|
|
|
23
20
|
if (workbookPart === undefined) {
|
|
24
21
|
throw new UnsupportedFormatError('unknown', `not a valid .xlsb package: ${XLSB_WORKBOOK_PART} is missing`);
|
|
25
22
|
}
|
|
26
|
-
const rels =
|
|
23
|
+
const rels = readPartRelationships(XLSB_WORKBOOK_PART, partText);
|
|
27
24
|
const sharedStrings = parseSharedStrings(partBytes('xl/sharedStrings.bin'));
|
|
28
25
|
const { cellXfs, namedStyles, defaultFont } = parseStyleTable(partBytes('xl/styles.bin'));
|
|
29
26
|
const workbook = new Workbook();
|
|
@@ -39,8 +36,8 @@ export function readXlsbPackage(files) {
|
|
|
39
36
|
};
|
|
40
37
|
for (const declared of declaration.sheets) {
|
|
41
38
|
const sheet = workbook.addWorksheet(declared.name, { state: declared.state });
|
|
42
|
-
const target = declared.relId === undefined ? undefined : rels.
|
|
43
|
-
const part = target === undefined ? undefined : partBytes(
|
|
39
|
+
const target = declared.relId === undefined ? undefined : rels.byId(declared.relId)?.target;
|
|
40
|
+
const part = target === undefined ? undefined : partBytes(rels.pathOf(target));
|
|
44
41
|
if (part !== undefined)
|
|
45
42
|
parseWorksheet(part, sheet, sharedStrings, cellXfs, scope);
|
|
46
43
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CellValue } from '../../core/value.ts';
|
|
2
2
|
import type { Worksheet } from '../../core/worksheet.ts';
|
|
3
|
-
import { type XmlAttributes } from '../../xml/xml-
|
|
3
|
+
import { type XmlAttributes } from '../../xml/xml-scan.ts';
|
|
4
4
|
import { type XfStyle } from '../style/xf-style.ts';
|
|
5
5
|
import { type SharedString } from './cell-value.ts';
|
|
6
6
|
export declare class CellAccumulator {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
|
|
2
2
|
import { translateFormula, unmangleFunctions } from '../../core/formula.js';
|
|
3
|
-
import { boolStrict, numInteger } from '../../xml/xml-
|
|
3
|
+
import { boolStrict, numInteger } from '../../xml/xml-scan.js';
|
|
4
4
|
import { applyXfToCell } from '../style/xf-style.js';
|
|
5
5
|
import { decodeCellContent, decodeFormulaResult, } from './cell-value.js';
|
|
6
6
|
import { RunAccumulator } from './rich-runs.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDateFormat, serialToDate } from '../../core/date.js';
|
|
2
2
|
import { unmangleFunctions } from '../../core/formula.js';
|
|
3
3
|
import { isErrorCode, } from '../../core/value.js';
|
|
4
|
-
import { boolStrict, decodeSpreadsheetText, numFinite } from '../../xml/xml-
|
|
4
|
+
import { boolStrict, decodeSpreadsheetText, numFinite } from '../../xml/xml-scan.js';
|
|
5
5
|
export function decodeCellContent(raw, sharedStrings, numFmt) {
|
|
6
6
|
if (raw.hasFormula) {
|
|
7
7
|
const stored = unmangleFunctions(raw.formula);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseArgb } from '../../core/style.js';
|
|
2
|
-
import { numFinite, numInteger } from '../../xml/xml-
|
|
2
|
+
import { numFinite, numInteger } from '../../xml/xml-scan.js';
|
|
3
3
|
import { numberText } from '../../xml/xml.js';
|
|
4
4
|
function normalizeArgb(argb) {
|
|
5
5
|
const rgb = parseArgb(argb);
|
package/dist/io/xlsx/comments.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tryDecodeCellRef } from '../../core/address.js';
|
|
2
|
-
import {
|
|
2
|
+
import { parseXml } from '../../xml/xml-read.js';
|
|
3
|
+
import { decodeSpreadsheetText, localName, numInteger } from '../../xml/xml-scan.js';
|
|
3
4
|
import { escapeText, textAttr, textElement, XML_DECLARATION } from '../../xml/xml.js';
|
|
4
5
|
import { MARKUP_COMPATIBILITY_NS, REVISION_NS, SPREADSHEETML_NS } from './namespaces.js';
|
|
5
6
|
export function collectComments(sheet, threads) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ConditionalFormatting } from '../../core/conditional-formatting.ts';
|
|
2
|
+
import { type CollectingPass } from '../../xml/xml-read.ts';
|
|
2
3
|
import type { StyleRegistry } from './styles.ts';
|
|
3
4
|
/**
|
|
4
5
|
* Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in
|
|
@@ -15,14 +16,14 @@ export declare function conditionalFormattingsXml(formattings: readonly Conditio
|
|
|
15
16
|
*/
|
|
16
17
|
export declare function conditionalFormattingsExtXml(formattings: readonly ConditionalFormatting[]): string;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* A pass reading a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
|
|
19
20
|
* blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
|
|
20
21
|
* read only to enrich a classic data bar with the facets the classic element cannot carry (the
|
|
21
22
|
* gradient flag and the negative-fill and axis colours) matched by the shared id the two ends link
|
|
22
23
|
* on. An extension rule with no classic counterpart (a rule that lives only in x14) is ignored, so it
|
|
23
24
|
* is never half-read into a broken classic rule.
|
|
24
25
|
*/
|
|
25
|
-
export declare function
|
|
26
|
+
export declare function conditionalFormattingPass(): CollectingPass<ConditionalFormatting[]>;
|
|
26
27
|
/**
|
|
27
28
|
* Extract the differential-style (`<dxf>`) fragments from styles.xml, each verbatim. Preserving the
|
|
28
29
|
* raw XML, rather than reparsing and re-serialising, is what keeps a foreign dxf's number format a
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isCfValueObjectType, isConditionalFormattingOperator, isConditionalFormattingType, isIconSetType, isCfTimePeriod, } from '../../core/conditional-formatting.js';
|
|
2
|
-
import {
|
|
2
|
+
import { elementSubtrees, TextCapture, } from '../../xml/xml-read.js';
|
|
3
|
+
import { boolPresent, boolStrict, coerceNumericLiteral, enumToken, localName, numFinite, numInteger, } from '../../xml/xml-scan.js';
|
|
3
4
|
import { boolAttr, checkedToken, escapeAttr, escapeText, numberText, stripFormulaEquals, textAttr, } from '../../xml/xml.js';
|
|
4
5
|
import { colorAttrs, parseColor } from './color-xml.js';
|
|
5
6
|
import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from './namespaces.js';
|
|
@@ -161,7 +162,7 @@ function cfvoXml(cfvo) {
|
|
|
161
162
|
const val = cfvo.value === undefined ? '' : textAttr('val', String(cfvo.value));
|
|
162
163
|
return `<cfvo type="${type}"${val}/>`;
|
|
163
164
|
}
|
|
164
|
-
export function
|
|
165
|
+
export function conditionalFormattingPass() {
|
|
165
166
|
const blocks = [];
|
|
166
167
|
let block;
|
|
167
168
|
let draft;
|
|
@@ -172,7 +173,7 @@ export function parseConditionalFormattings(xml) {
|
|
|
172
173
|
let x14Ext;
|
|
173
174
|
let x14ExtId;
|
|
174
175
|
const x14IdCapture = new TextCapture('id');
|
|
175
|
-
|
|
176
|
+
const handlers = {
|
|
176
177
|
onOpen(name, attrs, selfClosing) {
|
|
177
178
|
const ln = localName(name);
|
|
178
179
|
if (name.includes(':')) {
|
|
@@ -270,29 +271,29 @@ export function parseConditionalFormattings(xml) {
|
|
|
270
271
|
block = undefined;
|
|
271
272
|
}
|
|
272
273
|
},
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
274
|
+
};
|
|
275
|
+
const result = () => {
|
|
276
|
+
for (const { rule, id } of linked) {
|
|
277
|
+
const ext = extById.get(id);
|
|
278
|
+
if (ext === undefined)
|
|
279
|
+
continue;
|
|
280
|
+
if (ext.gradient !== undefined)
|
|
281
|
+
rule.gradient = ext.gradient;
|
|
282
|
+
if (ext.negativeFillColor !== undefined)
|
|
283
|
+
rule.negativeFillColor = ext.negativeFillColor;
|
|
284
|
+
if (ext.axisColor !== undefined)
|
|
285
|
+
rule.axisColor = ext.axisColor;
|
|
286
|
+
}
|
|
287
|
+
return blocks;
|
|
288
|
+
};
|
|
289
|
+
return { handlers, result };
|
|
286
290
|
}
|
|
287
291
|
function emptyExt() {
|
|
288
292
|
return { gradient: undefined, negativeFillColor: undefined, axisColor: undefined };
|
|
289
293
|
}
|
|
290
294
|
export function parseDxfs(stylesXml) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
return [];
|
|
294
|
-
const inner = block[1] ?? '';
|
|
295
|
-
return [...inner.matchAll(/<dxf\b[^>]*>[\s\S]*?<\/dxf>|<dxf\b[^>]*\/>/g)].map((m) => m[0] ?? '');
|
|
295
|
+
const { fragments } = elementSubtrees(stylesXml, new Map([['dxfs', 'dxf']]));
|
|
296
|
+
return [...(fragments.get('dxfs') ?? [])];
|
|
296
297
|
}
|
|
297
298
|
function newDraft(attrs) {
|
|
298
299
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DataValidationEntry } from '../../core/data-validation.ts';
|
|
2
2
|
import type { Worksheet } from '../../core/worksheet.ts';
|
|
3
|
+
import { type CollectingPass } from '../../xml/xml-read.ts';
|
|
3
4
|
/** The standard `<dataValidations>` element for the rules stored in the legacy form, or '' when the
|
|
4
5
|
* sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
|
|
5
6
|
* The extended rules are emitted separately by {@link dataValidationsExtXml}. */
|
|
@@ -8,13 +9,14 @@ export declare function dataValidationsXml(entries: readonly DataValidationEntry
|
|
|
8
9
|
* none. Emitted bare (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single
|
|
9
10
|
* `<extLst>` beside the conditional-formatting extension: a worksheet may carry at most one. */
|
|
10
11
|
export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
12
|
+
/** A pass gathering the standard `<dataValidation>` elements of a worksheet part, for a caller
|
|
13
|
+
* reading the part alongside its other readers in one parse. */
|
|
14
|
+
export declare function dataValidationPass(): CollectingPass<DataValidationEntry[]>;
|
|
15
|
+
/** A pass gathering the extended `<x14:dataValidation>` elements of a worksheet's `<extLst>`, so a
|
|
16
|
+
* cross-sheet or whole-column list validation Excel stored only in the 2009 extension form is read
|
|
17
|
+
* back rather than dropped. The standard pass ignores these (they are prefixed); this one,
|
|
18
|
+
* symmetrically, handles only the prefixed elements. */
|
|
19
|
+
export declare function extendedDataValidationPass(): CollectingPass<DataValidationEntry[]>;
|
|
18
20
|
/** Fold parsed validations onto a sheet, each bound to its original range and carrying its form: an
|
|
19
21
|
* `extended` entry is re-attached as extended so a round-trip writes it back to the x14 block. */
|
|
20
22
|
export declare function applyDataValidations(sheet: Worksheet, entries: readonly DataValidationEntry[]): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDataValidationErrorStyle, isDataValidationOperator, isDataValidationType, } from '../../core/data-validation.js';
|
|
2
2
|
import { decodeSqrefRects } from '../../core/merge.js';
|
|
3
|
-
import {
|
|
3
|
+
import { TextCapture } from '../../xml/xml-read.js';
|
|
4
|
+
import { boolStrict, coerceNumericLiteral, localName } from '../../xml/xml-scan.js';
|
|
4
5
|
import { checkedToken, escapeAttr, escapeText, stripFormulaEquals, textAttr } from '../../xml/xml.js';
|
|
5
6
|
import { DATA_VALIDATION_EXT_URI, XM_NS } from './namespaces.js';
|
|
6
7
|
import { x14Ext } from './x14-ext.js';
|
|
@@ -57,11 +58,11 @@ function extendedDataValidationXml(sqref, rule) {
|
|
|
57
58
|
`<xm:sqref>${escapeText(sqref)}</xm:sqref>`;
|
|
58
59
|
return `<x14:dataValidation${ruleAttrs(rule)}>${body}</x14:dataValidation>`;
|
|
59
60
|
}
|
|
60
|
-
export function
|
|
61
|
+
export function dataValidationPass() {
|
|
61
62
|
const entries = [];
|
|
62
63
|
let current;
|
|
63
64
|
let slot;
|
|
64
|
-
|
|
65
|
+
const handlers = {
|
|
65
66
|
onOpen(name, attrs) {
|
|
66
67
|
const ln = localName(name);
|
|
67
68
|
if (ln === 'dataValidation' && !name.includes(':')) {
|
|
@@ -94,8 +95,8 @@ export function parseDataValidations(xml) {
|
|
|
94
95
|
current = undefined;
|
|
95
96
|
}
|
|
96
97
|
},
|
|
97
|
-
}
|
|
98
|
-
return entries;
|
|
98
|
+
};
|
|
99
|
+
return { handlers, result: () => entries };
|
|
99
100
|
}
|
|
100
101
|
function buildEntry(attrs, formulae) {
|
|
101
102
|
const { sqref } = attrs;
|
|
@@ -139,12 +140,12 @@ function buildRule(attrs, formulae) {
|
|
|
139
140
|
rule.formulae = parsed;
|
|
140
141
|
return rule;
|
|
141
142
|
}
|
|
142
|
-
export function
|
|
143
|
+
export function extendedDataValidationPass() {
|
|
143
144
|
const entries = [];
|
|
144
145
|
let current;
|
|
145
146
|
let slot;
|
|
146
147
|
const capture = new TextCapture(['f', 'sqref']);
|
|
147
|
-
|
|
148
|
+
const handlers = {
|
|
148
149
|
onOpen(name, attrs, selfClosing) {
|
|
149
150
|
const ln = localName(name);
|
|
150
151
|
const prefixed = name.includes(':');
|
|
@@ -186,8 +187,8 @@ export function parseExtendedDataValidations(xml) {
|
|
|
186
187
|
current = undefined;
|
|
187
188
|
}
|
|
188
189
|
},
|
|
189
|
-
}
|
|
190
|
-
return entries;
|
|
190
|
+
};
|
|
191
|
+
return { handlers, result: () => entries };
|
|
191
192
|
}
|
|
192
193
|
function buildExtendedEntry(attrs, formulae, sqref) {
|
|
193
194
|
if (sqref === '')
|
package/dist/io/xlsx/edit-vba.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { strFromU8, strToU8, unzipSync, zipSync } from 'fflate';
|
|
2
2
|
import { VbaAuthorError } from '../../vba/errors.js';
|
|
3
3
|
import { addVbaReference, removeVbaModule, } from '../../vba/project-editor.js';
|
|
4
|
-
import { relsPathFor } from '../opc/part-paths.js';
|
|
5
|
-
import { parseRelationshipRecords, relationshipTargetByType
|
|
4
|
+
import { relsPathFor, resolveRelativePart } from '../opc/part-paths.js';
|
|
5
|
+
import { parseRelationshipRecords, relationshipTargetByType } from '../opc/read-opc.js';
|
|
6
6
|
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
7
7
|
const OFFICE_DOCUMENT_REL = 'officeDocument';
|
|
8
8
|
const VBA_PROJECT_REL = 'vbaProject';
|
|
@@ -14,7 +14,7 @@ export function editXlsxVbaAddReference(xlsx, ref) {
|
|
|
14
14
|
return applyToVbaProjectPart(xlsx, (bin) => addVbaReference(bin, ref));
|
|
15
15
|
}
|
|
16
16
|
function applyToVbaProjectPart(xlsx, apply) {
|
|
17
|
-
const files = unzipSync(xlsx);
|
|
17
|
+
const files = Object.assign(Object.create(null), unzipSync(xlsx));
|
|
18
18
|
const binPath = locateVbaProjectPart(files);
|
|
19
19
|
const bin = binPath === undefined ? undefined : files[binPath];
|
|
20
20
|
if (binPath === undefined || bin === undefined) {
|
|
@@ -38,14 +38,7 @@ function locateVbaProjectPart(files) {
|
|
|
38
38
|
const vbaTarget = relationshipTargetByType(workbookRels, VBA_PROJECT_REL);
|
|
39
39
|
if (vbaTarget === undefined)
|
|
40
40
|
return undefined;
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
function resolveVbaTarget(workbookPath, target) {
|
|
44
|
-
if (target.startsWith('/'))
|
|
45
|
-
return target.slice(1);
|
|
46
|
-
if (workbookPath.startsWith('xl/'))
|
|
47
|
-
return resolveWorkbookPart(target);
|
|
48
|
-
return resolveRelativePart(workbookPath, target);
|
|
41
|
+
return resolveRelativePart(workbookPath, vbaTarget);
|
|
49
42
|
}
|
|
50
43
|
function dropStaleSignature(files, binPath) {
|
|
51
44
|
const binRelsPath = relsPathFor(binPath);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Worksheet } from '../../core/worksheet.ts';
|
|
2
|
+
import { type CollectingPass } from '../../xml/xml-read.ts';
|
|
2
3
|
import type { SheetRelIds } from './package-plan.ts';
|
|
3
4
|
/** A hyperlink gathered from a sheet for serialisation: the cell it sits on, its target, and an
|
|
4
5
|
* optional tooltip. The visible label is the cell's own value and is serialised as that value. */
|
|
@@ -34,8 +35,9 @@ interface ParsedHyperlink {
|
|
|
34
35
|
readonly location?: string;
|
|
35
36
|
readonly tooltip?: string;
|
|
36
37
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
38
|
+
/** A pass gathering every `<hyperlink>` element of a worksheet part, for a caller reading the part
|
|
39
|
+
* alongside its other readers in one parse. */
|
|
40
|
+
export declare function sheetHyperlinkPass(): CollectingPass<ParsedHyperlink[]>;
|
|
39
41
|
/** Fold parsed hyperlinks onto a sheet's cells, wrapping each cell's existing value (its visible
|
|
40
42
|
* label) into a {@link HyperlinkValue}. `targetOf` resolves a relationship id to its raw Target: a
|
|
41
43
|
* URL for the external links hyperlinks almost always are, so it must stay unresolved against the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { tryDecodeRange } from '../../core/address.js';
|
|
2
2
|
import { isHyperlinkValue, isRichTextValue } from '../../core/value.js';
|
|
3
|
-
import {
|
|
3
|
+
import {} from '../../xml/xml-read.js';
|
|
4
|
+
import { localName } from '../../xml/xml-scan.js';
|
|
4
5
|
import { escapeAttr, textAttr } from '../../xml/xml.js';
|
|
5
6
|
export function collectHyperlinks(sheet) {
|
|
6
7
|
const links = [];
|
|
@@ -40,24 +41,26 @@ export function hyperlinksXml(links) {
|
|
|
40
41
|
.join('');
|
|
41
42
|
return `<hyperlinks>${items}</hyperlinks>`;
|
|
42
43
|
}
|
|
43
|
-
export function
|
|
44
|
+
export function sheetHyperlinkPass() {
|
|
44
45
|
const links = [];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
return {
|
|
47
|
+
handlers: {
|
|
48
|
+
onOpen(name, attrs) {
|
|
49
|
+
if (localName(name) !== 'hyperlink')
|
|
50
|
+
return;
|
|
51
|
+
const ref = attrs.ref;
|
|
52
|
+
if (ref === undefined)
|
|
53
|
+
return;
|
|
54
|
+
links.push({
|
|
55
|
+
ref,
|
|
56
|
+
...(attrs['r:id'] !== undefined ? { rid: attrs['r:id'] } : {}),
|
|
57
|
+
...(attrs.location !== undefined ? { location: attrs.location } : {}),
|
|
58
|
+
...(attrs.tooltip !== undefined ? { tooltip: attrs.tooltip } : {}),
|
|
59
|
+
});
|
|
60
|
+
},
|
|
58
61
|
},
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
result: () => links,
|
|
63
|
+
};
|
|
61
64
|
}
|
|
62
65
|
export function applyHyperlinks(sheet, links, targetOf) {
|
|
63
66
|
for (const link of links) {
|
package/dist/io/xlsx/images.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { isImageEditAs, isOneCellAnchor, } from '../../core/image.js';
|
|
2
|
-
import {
|
|
2
|
+
import { parseXml, TextCapture } from '../../xml/xml-read.js';
|
|
3
|
+
import { enumToken, localName, numFinite } from '../../xml/xml-scan.js';
|
|
3
4
|
import { checkedToken, numAttr, numberText, XML_DECLARATION } from '../../xml/xml.js';
|
|
4
5
|
import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
|
|
5
6
|
import { relationship, relationshipsPart } from '../opc/rels.js';
|
|
6
7
|
import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
|
|
7
8
|
const IMAGE_REL_TYPE = `${RELATIONSHIPS_NS}/image`;
|
|
8
|
-
const IMAGE_CONTENT_TYPES =
|
|
9
|
-
png
|
|
10
|
-
jpg
|
|
11
|
-
jpeg
|
|
12
|
-
gif
|
|
13
|
-
bmp
|
|
14
|
-
tif
|
|
15
|
-
tiff
|
|
16
|
-
emf
|
|
17
|
-
wmf
|
|
18
|
-
svg
|
|
19
|
-
|
|
9
|
+
const IMAGE_CONTENT_TYPES = new Map([
|
|
10
|
+
['png', 'image/png'],
|
|
11
|
+
['jpg', 'image/jpeg'],
|
|
12
|
+
['jpeg', 'image/jpeg'],
|
|
13
|
+
['gif', 'image/gif'],
|
|
14
|
+
['bmp', 'image/bmp'],
|
|
15
|
+
['tif', 'image/tiff'],
|
|
16
|
+
['tiff', 'image/tiff'],
|
|
17
|
+
['emf', 'image/x-emf'],
|
|
18
|
+
['wmf', 'image/x-wmf'],
|
|
19
|
+
['svg', 'image/svg+xml'],
|
|
20
|
+
]);
|
|
20
21
|
export function imageContentType(extension) {
|
|
21
22
|
const ext = extension.toLowerCase();
|
|
22
|
-
return IMAGE_CONTENT_TYPES
|
|
23
|
+
return IMAGE_CONTENT_TYPES.get(ext) ?? `image/${ext}`;
|
|
23
24
|
}
|
|
24
25
|
export function drawingXml(images) {
|
|
25
26
|
const anchors = images.map((image, i) => anchorXml(image, i + 1)).join('');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CommentThread } from '../../core/comment-thread.ts';
|
|
2
|
+
import type { WorkbookImage } from '../../core/image.ts';
|
|
2
3
|
import type { PivotTable } from '../../core/pivot-table.ts';
|
|
3
4
|
import type { Table } from '../../core/table.ts';
|
|
4
5
|
import type { Workbook } from '../../core/workbook.ts';
|
|
@@ -94,10 +95,17 @@ export interface MediaPart {
|
|
|
94
95
|
readonly extension: string;
|
|
95
96
|
readonly data: Uint8Array;
|
|
96
97
|
}
|
|
98
|
+
export interface ResolvedMedia {
|
|
99
|
+
readonly number: number;
|
|
100
|
+
readonly image: WorkbookImage;
|
|
101
|
+
}
|
|
97
102
|
export interface MediaPlan {
|
|
98
103
|
readonly parts: readonly MediaPart[];
|
|
99
|
-
readonly numberById: ReadonlyMap<number, number>;
|
|
100
104
|
readonly extensions: readonly string[];
|
|
105
|
+
/** The media part number and registered image for a referenced id. Total over the ids
|
|
106
|
+
* {@link planMedia} saw on the sheets it planned, because planning already threw on any it could
|
|
107
|
+
* not resolve; an id from anywhere else is a caller bug and is reported as one. */
|
|
108
|
+
resolve(id: number): ResolvedMedia;
|
|
101
109
|
}
|
|
102
110
|
export declare function planMedia(workbook: Workbook, sheets: readonly Worksheet[]): MediaPlan;
|
|
103
111
|
export declare function planPreservedParts(workbook: Workbook, generatedDrawingCount: number, generatedMediaCount: number): PreservedPlan;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthoringError } from '../../errors.js';
|
|
2
2
|
import { extensionOf, relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
|
|
3
|
-
import {
|
|
3
|
+
import { relsPartXml } from '../opc/rels.js';
|
|
4
|
+
import { drawingPart, mediaPart, vmlDrawingPart } from './part-names.js';
|
|
4
5
|
import { applyThemeOverrides } from './theme-xml.js';
|
|
5
6
|
export class SheetRelIds {
|
|
6
7
|
#next = 1;
|
|
@@ -9,34 +10,46 @@ export class SheetRelIds {
|
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
export function planMedia(workbook, sheets) {
|
|
12
|
-
const
|
|
13
|
+
const uses = [];
|
|
13
14
|
const seen = new Set();
|
|
14
|
-
const use = (id) => {
|
|
15
|
+
const use = (id, sheetName, role) => {
|
|
15
16
|
if (!seen.has(id)) {
|
|
16
17
|
seen.add(id);
|
|
17
|
-
|
|
18
|
+
uses.push({ id, sheetName, role });
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
for (const sheet of sheets) {
|
|
21
22
|
for (const image of sheet.images)
|
|
22
|
-
use(image.imageId);
|
|
23
|
-
if (sheet.backgroundImageId !== undefined)
|
|
24
|
-
use(sheet.backgroundImageId);
|
|
23
|
+
use(image.imageId, sheet.name, 'anchor');
|
|
24
|
+
if (sheet.backgroundImageId !== undefined) {
|
|
25
|
+
use(sheet.backgroundImageId, sheet.name, 'background');
|
|
26
|
+
}
|
|
25
27
|
}
|
|
26
28
|
const parts = [];
|
|
27
|
-
const
|
|
29
|
+
const byId = new Map();
|
|
28
30
|
const extensions = new Set();
|
|
29
|
-
|
|
31
|
+
uses.forEach(({ id, sheetName, role }, i) => {
|
|
30
32
|
const image = workbook.getImage(id);
|
|
31
33
|
if (image === undefined) {
|
|
32
|
-
|
|
34
|
+
const reference = role === 'anchor' ? `anchors image id ${id}` : `sets background image id ${id}`;
|
|
35
|
+
throw new AuthoringError(`sheet "${sheetName}" ${reference}, which is not registered on the workbook`);
|
|
33
36
|
}
|
|
34
37
|
const number = i + 1;
|
|
35
38
|
parts.push({ number, extension: image.extension, data: image.data });
|
|
36
|
-
|
|
39
|
+
byId.set(id, { number, image });
|
|
37
40
|
extensions.add(image.extension);
|
|
38
41
|
});
|
|
39
|
-
return {
|
|
42
|
+
return {
|
|
43
|
+
parts,
|
|
44
|
+
extensions: [...extensions],
|
|
45
|
+
resolve(id) {
|
|
46
|
+
const resolved = byId.get(id);
|
|
47
|
+
if (resolved === undefined) {
|
|
48
|
+
throw new AuthoringError(`image id ${id} was not planned into this workbook's media`);
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
40
53
|
}
|
|
41
54
|
export function planPreservedParts(workbook, generatedDrawingCount, generatedMediaCount) {
|
|
42
55
|
const sheets = workbook.worksheets;
|
|
@@ -84,7 +97,7 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
|
|
|
84
97
|
: new TextEncoder().encode(applyThemeOverrides(new TextDecoder().decode(part.bytes), overrides)),
|
|
85
98
|
contentType: part.contentType,
|
|
86
99
|
relsPath: rels.length === 0 ? null : relsPathFor(newPath),
|
|
87
|
-
relsXml: rels.length === 0 ? null :
|
|
100
|
+
relsXml: rels.length === 0 ? null : relsPartXml(rels),
|
|
88
101
|
});
|
|
89
102
|
}
|
|
90
103
|
}
|
|
@@ -114,11 +127,11 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
|
|
|
114
127
|
function preservedPartPath(originalPath, numbering) {
|
|
115
128
|
const ext = extensionOf(originalPath);
|
|
116
129
|
if (ext.toLowerCase() === 'vml')
|
|
117
|
-
return
|
|
130
|
+
return vmlDrawingPart(++numbering.vml);
|
|
118
131
|
if (originalPath.startsWith('xl/media/'))
|
|
119
|
-
return
|
|
132
|
+
return mediaPart(++numbering.media, ext);
|
|
120
133
|
if (originalPath.startsWith('xl/drawings/') && ext.toLowerCase() === 'xml') {
|
|
121
|
-
return
|
|
134
|
+
return drawingPart(++numbering.drawing);
|
|
122
135
|
}
|
|
123
136
|
return originalPath;
|
|
124
137
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The office document part: the workbook every other part hangs off. */
|
|
2
|
+
export declare const WORKBOOK_PART = "xl/workbook.xml";
|
|
3
|
+
export declare const STYLES_PART = "xl/styles.xml";
|
|
4
|
+
export declare const SHARED_STRINGS_PART = "xl/sharedStrings.xml";
|
|
5
|
+
/** Singular and unnumbered, unlike the per-sheet thread parts: one identity registry serves the
|
|
6
|
+
* whole workbook's threaded comments. */
|
|
7
|
+
export declare const PERSONS_PART = "xl/persons/person.xml";
|
|
8
|
+
export declare const CORE_PROPS_PART = "docProps/core.xml";
|
|
9
|
+
export declare const APP_PROPS_PART = "docProps/app.xml";
|
|
10
|
+
/** `xl/worksheets/sheet{n}.xml`, numbered by the sheet's position in the workbook (1-based). */
|
|
11
|
+
export declare function worksheetPart(number: number): string;
|
|
12
|
+
/** `xl/tables/table{n}.xml`, numbered across the whole workbook rather than per sheet. */
|
|
13
|
+
export declare function tablePart(number: number): string;
|
|
14
|
+
/** `xl/media/image{n}.{ext}`: the one part name that carries the source file's own extension, since
|
|
15
|
+
* the `<Default>` content type is registered per extension. */
|
|
16
|
+
export declare function mediaPart(number: number, extension: string): string;
|
|
17
|
+
/** `xl/drawings/drawing{n}.xml`: the DrawingML anchors for one sheet's images. */
|
|
18
|
+
export declare function drawingPart(number: number): string;
|
|
19
|
+
/** `xl/drawings/vmlDrawing{n}.vml`: the legacy shape a note's box is drawn as. Numbered with the
|
|
20
|
+
* comments part it accompanies, not with the DrawingML drawings it shares a directory with. */
|
|
21
|
+
export declare function vmlDrawingPart(number: number): string;
|
|
22
|
+
/** `xl/comments{n}.xml`: directly under `xl/`, unlike every other numbered part. */
|
|
23
|
+
export declare function commentsPart(number: number): string;
|
|
24
|
+
/** `xl/threadedComments/threadedComment{n}.xml`: one sheet's modern conversations. */
|
|
25
|
+
export declare function threadedCommentsPart(number: number): string;
|
|
26
|
+
/** `xl/printerSettings/printerSettings{n}.bin`: an opaque blob carried through verbatim. */
|
|
27
|
+
export declare function printerSettingsPart(number: number): string;
|
|
28
|
+
/** `xl/pivotTables/pivotTable{n}.xml`: the pivot as its host sheet reaches it. */
|
|
29
|
+
export declare function pivotTablePart(number: number): string;
|
|
30
|
+
/** `xl/pivotCache/pivotCacheDefinition{n}.xml`: the field catalogue and source reference. */
|
|
31
|
+
export declare function pivotCacheDefinitionPart(number: number): string;
|
|
32
|
+
/** `xl/pivotCache/pivotCacheRecords{n}.xml`: the cached rows the definition points at. */
|
|
33
|
+
export declare function pivotCacheRecordsPart(number: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* A part named as a Target of a relationship carried by a *worksheet*. Every worksheet part lives in
|
|
36
|
+
* `xl/worksheets/`, so one sheet's view of a target is every sheet's, and the sheet number the
|
|
37
|
+
* arithmetic runs against does not matter.
|
|
38
|
+
*/
|
|
39
|
+
export declare function targetFromWorksheet(partPath: string): string;
|
|
40
|
+
/** A part named as a Target of a relationship carried by the workbook part. */
|
|
41
|
+
export declare function targetFromWorkbook(partPath: string): string;
|