@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.
Files changed (100) hide show
  1. package/dist/core/autofilter.d.ts +1 -1
  2. package/dist/core/autofilter.js +3 -5
  3. package/dist/core/axis-handle.d.ts +8 -0
  4. package/dist/core/axis-handle.js +14 -0
  5. package/dist/core/cell.d.ts +24 -0
  6. package/dist/core/cell.js +9 -1
  7. package/dist/core/column.d.ts +4 -1
  8. package/dist/core/column.js +31 -35
  9. package/dist/core/conditional-formatting.d.ts +5 -5
  10. package/dist/core/conditional-formatting.js +11 -25
  11. package/dist/core/data-validation.d.ts +3 -3
  12. package/dist/core/data-validation.js +7 -15
  13. package/dist/core/image.d.ts +1 -1
  14. package/dist/core/image.js +2 -4
  15. package/dist/core/merge-index.d.ts +31 -0
  16. package/dist/core/merge-index.js +60 -0
  17. package/dist/core/merge.d.ts +0 -10
  18. package/dist/core/merge.js +0 -8
  19. package/dist/core/page-setup.d.ts +72 -2
  20. package/dist/core/page-setup.js +34 -8
  21. package/dist/core/pivot-table.d.ts +1 -1
  22. package/dist/core/pivot-table.js +4 -8
  23. package/dist/core/row.d.ts +4 -1
  24. package/dist/core/row.js +19 -23
  25. package/dist/core/style.d.ts +51 -7
  26. package/dist/core/style.js +29 -34
  27. package/dist/core/table.d.ts +1 -1
  28. package/dist/core/table.js +3 -5
  29. package/dist/core/used-extent.d.ts +44 -0
  30. package/dist/core/used-extent.js +102 -0
  31. package/dist/core/worksheet.d.ts +17 -6
  32. package/dist/core/worksheet.js +48 -42
  33. package/dist/customui/ribbon.js +4 -6
  34. package/dist/io/opc/inflate.js +1 -1
  35. package/dist/io/opc/part-paths.d.ts +1 -0
  36. package/dist/io/opc/part-paths.js +15 -0
  37. package/dist/io/opc/read-opc.d.ts +22 -3
  38. package/dist/io/opc/read-opc.js +11 -39
  39. package/dist/io/opc/rels.d.ts +1 -6
  40. package/dist/io/opc/rels.js +1 -4
  41. package/dist/io/style/xf-style.d.ts +26 -2
  42. package/dist/io/style/xf-style.js +6 -0
  43. package/dist/io/xlsb/read-styles.js +6 -3
  44. package/dist/io/xlsb/read.d.ts +1 -1
  45. package/dist/io/xlsb/read.js +5 -8
  46. package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
  47. package/dist/io/xlsx/cell-accumulator.js +1 -1
  48. package/dist/io/xlsx/cell-value.js +1 -1
  49. package/dist/io/xlsx/color-xml.js +1 -1
  50. package/dist/io/xlsx/comments.js +2 -1
  51. package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
  52. package/dist/io/xlsx/conditional-formatting.js +22 -21
  53. package/dist/io/xlsx/data-validation.d.ts +9 -7
  54. package/dist/io/xlsx/data-validation.js +10 -9
  55. package/dist/io/xlsx/edit-vba.js +4 -11
  56. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  57. package/dist/io/xlsx/hyperlinks.js +20 -17
  58. package/dist/io/xlsx/images.js +15 -14
  59. package/dist/io/xlsx/package-plan.d.ts +9 -1
  60. package/dist/io/xlsx/package-plan.js +29 -16
  61. package/dist/io/xlsx/part-names.d.ts +41 -0
  62. package/dist/io/xlsx/part-names.js +46 -0
  63. package/dist/io/xlsx/read-pivot.js +2 -1
  64. package/dist/io/xlsx/read-rows.js +12 -13
  65. package/dist/io/xlsx/read-shared-strings.js +2 -1
  66. package/dist/io/xlsx/read-styles.d.ts +1 -1
  67. package/dist/io/xlsx/read-styles.js +63 -42
  68. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  69. package/dist/io/xlsx/read-worksheet.js +43 -56
  70. package/dist/io/xlsx/read.d.ts +2 -3
  71. package/dist/io/xlsx/read.js +81 -73
  72. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  73. package/dist/io/xlsx/rich-runs.js +2 -1
  74. package/dist/io/xlsx/sheet-properties.js +21 -28
  75. package/dist/io/xlsx/styles.js +23 -20
  76. package/dist/io/xlsx/tables.js +2 -1
  77. package/dist/io/xlsx/theme-xml.js +1 -1
  78. package/dist/io/xlsx/threaded-comments.js +2 -1
  79. package/dist/io/xlsx/workbook-xml.js +29 -28
  80. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  81. package/dist/io/xlsx/worksheet-xml.js +14 -16
  82. package/dist/io/xlsx/write-stream.d.ts +6 -0
  83. package/dist/io/xlsx/write-stream.js +30 -22
  84. package/dist/io/xlsx/write.d.ts +5 -0
  85. package/dist/io/xlsx/write.js +155 -131
  86. package/dist/token-set.d.ts +15 -0
  87. package/dist/token-set.js +4 -0
  88. package/dist/vba/cfb-format.d.ts +24 -0
  89. package/dist/vba/cfb-format.js +12 -0
  90. package/dist/vba/cfb-writer.js +17 -17
  91. package/dist/vba/cfb.js +1 -7
  92. package/dist/vba/ms-ovba.js +32 -11
  93. package/dist/vba/project.js +13 -13
  94. package/dist/vba/vba-encoding.js +1 -1
  95. package/dist/xml/xml-read.d.ts +59 -74
  96. package/dist/xml/xml-read.js +83 -176
  97. package/dist/xml/xml-scan.d.ts +100 -0
  98. package/dist/xml/xml-scan.js +208 -0
  99. package/dist/xml/xml.d.ts +1 -1
  100. package/package.json +2 -3
@@ -1,5 +1,5 @@
1
1
  import { Workbook } from '../../core/workbook.ts';
2
- import { type ReadXlsxOptions } from '../opc/read-options.ts';
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
  /**
@@ -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 { packageAccessors, parseRelationships, resolveWorkbookPart } from '../opc/read-opc.js';
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
- const cap = options.maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
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 = parseRelationships(partText('xl/_rels/workbook.bin.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.get(declared.relId);
43
- const part = target === undefined ? undefined : partBytes(resolveWorkbookPart(target));
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-read.ts';
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-read.js';
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-read.js';
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-read.js';
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);
@@ -1,5 +1,6 @@
1
1
  import { tryDecodeCellRef } from '../../core/address.js';
2
- import { decodeSpreadsheetText, localName, numInteger, parseXml } from '../../xml/xml-read.js';
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
- * Parse a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
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 parseConditionalFormattings(xml: string): ConditionalFormatting[];
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 { boolPresent, boolStrict, coerceNumericLiteral, enumToken, localName, numFinite, numInteger, parseXml, TextCapture, } from '../../xml/xml-read.js';
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 parseConditionalFormattings(xml) {
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
- parseXml(xml, {
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
- for (const { rule, id } of linked) {
275
- const ext = extById.get(id);
276
- if (ext === undefined)
277
- continue;
278
- if (ext.gradient !== undefined)
279
- rule.gradient = ext.gradient;
280
- if (ext.negativeFillColor !== undefined)
281
- rule.negativeFillColor = ext.negativeFillColor;
282
- if (ext.axisColor !== undefined)
283
- rule.axisColor = ext.axisColor;
284
- }
285
- return blocks;
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 block = /<dxfs\b[^>]*>([\s\S]*?)<\/dxfs>/.exec(stylesXml);
292
- if (block === null)
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
- /** Parse every standard `<dataValidation>` out of a worksheet part into range-bound rules. */
12
- export declare function parseDataValidations(xml: string): DataValidationEntry[];
13
- /** Parse every extended `<x14:dataValidation>` out of a worksheet's `<extLst>` into range-bound
14
- * rules tagged `extended`, so a cross-sheet or whole-column list validation Excel stored only in the
15
- * 2009 extension form is read back rather than dropped. The standard parser ignores these (they are
16
- * prefixed); this one, symmetrically, handles only the prefixed elements. */
17
- export declare function parseExtendedDataValidations(xml: string): DataValidationEntry[];
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 { boolStrict, coerceNumericLiteral, localName, parseXml, TextCapture, } from '../../xml/xml-read.js';
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 parseDataValidations(xml) {
61
+ export function dataValidationPass() {
61
62
  const entries = [];
62
63
  let current;
63
64
  let slot;
64
- parseXml(xml, {
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 parseExtendedDataValidations(xml) {
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
- parseXml(xml, {
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 === '')
@@ -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, resolveRelativePart, resolveWorkbookPart, } from '../opc/read-opc.js';
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 resolveVbaTarget(workbookPath, vbaTarget);
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
- /** Parse every `<hyperlink>` element out of a worksheet part. */
38
- export declare function parseSheetHyperlinks(xml: string): ParsedHyperlink[];
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 { localName, parseXml } from '../../xml/xml-read.js';
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 parseSheetHyperlinks(xml) {
44
+ export function sheetHyperlinkPass() {
44
45
  const links = [];
45
- parseXml(xml, {
46
- onOpen(name, attrs) {
47
- if (localName(name) !== 'hyperlink')
48
- return;
49
- const ref = attrs.ref;
50
- if (ref === undefined)
51
- return;
52
- links.push({
53
- ref,
54
- ...(attrs['r:id'] !== undefined ? { rid: attrs['r:id'] } : {}),
55
- ...(attrs.location !== undefined ? { location: attrs.location } : {}),
56
- ...(attrs.tooltip !== undefined ? { tooltip: attrs.tooltip } : {}),
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
- return links;
62
+ result: () => links,
63
+ };
61
64
  }
62
65
  export function applyHyperlinks(sheet, links, targetOf) {
63
66
  for (const link of links) {
@@ -1,25 +1,26 @@
1
1
  import { isImageEditAs, isOneCellAnchor, } from '../../core/image.js';
2
- import { enumToken, localName, numFinite, parseXml, TextCapture } from '../../xml/xml-read.js';
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: 'image/png',
10
- jpg: 'image/jpeg',
11
- jpeg: 'image/jpeg',
12
- gif: 'image/gif',
13
- bmp: 'image/bmp',
14
- tif: 'image/tiff',
15
- tiff: 'image/tiff',
16
- emf: 'image/x-emf',
17
- wmf: 'image/x-wmf',
18
- svg: 'image/svg+xml',
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[ext] ?? `image/${ext}`;
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 { preservedRelsXml } from '../opc/rels.js';
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 usedIds = [];
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
- usedIds.push(id);
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 numberById = new Map();
29
+ const byId = new Map();
28
30
  const extensions = new Set();
29
- usedIds.forEach((id, i) => {
31
+ uses.forEach(({ id, sheetName, role }, i) => {
30
32
  const image = workbook.getImage(id);
31
33
  if (image === undefined) {
32
- throw new AuthoringError(`a worksheet anchors image id ${id}, which is not registered on the workbook`);
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
- numberById.set(id, number);
39
+ byId.set(id, { number, image });
37
40
  extensions.add(image.extension);
38
41
  });
39
- return { parts, numberById, extensions: [...extensions] };
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 : preservedRelsXml(rels),
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 `xl/drawings/vmlDrawing${++numbering.vml}.vml`;
130
+ return vmlDrawingPart(++numbering.vml);
118
131
  if (originalPath.startsWith('xl/media/'))
119
- return `xl/media/image${++numbering.media}.${ext}`;
132
+ return mediaPart(++numbering.media, ext);
120
133
  if (originalPath.startsWith('xl/drawings/') && ext.toLowerCase() === 'xml') {
121
- return `xl/drawings/drawing${++numbering.drawing}.xml`;
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;