@shbernal/ts-xlsx 2.0.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 (147) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -2,3 +2,4 @@ export declare const THEME_PART_PATH = "xl/theme/theme1.xml";
2
2
  export declare function extensionOf(partPath: string): string;
3
3
  export declare function relsPathFor(partPath: string): string;
4
4
  export declare function relativePartPath(fromPath: string, toPath: string): string;
5
+ export declare function resolveRelativePart(basePart: string, target: string): string;
@@ -22,3 +22,18 @@ export function relativePartPath(fromPath, toPath) {
22
22
  const up = fromDir.length - common;
23
23
  return [...Array(up).fill('..'), ...toSegments.slice(common)].join('/');
24
24
  }
25
+ export function resolveRelativePart(basePart, target) {
26
+ if (target.startsWith('/'))
27
+ return target.slice(1);
28
+ const baseDir = basePart.slice(0, basePart.lastIndexOf('/') + 1);
29
+ const out = [];
30
+ for (const segment of `${baseDir}${target}`.split('/')) {
31
+ if (segment === '' || segment === '.')
32
+ continue;
33
+ if (segment === '..')
34
+ out.pop();
35
+ else
36
+ out.push(segment);
37
+ }
38
+ return out.join('/');
39
+ }
@@ -6,12 +6,31 @@ export interface PackageAccessors {
6
6
  /** A part's raw bytes, or undefined when the package holds no such part. */
7
7
  partBytes: (path: string) => Uint8Array | undefined;
8
8
  }
9
+ /** A spreadsheet package opened for reading: inflated under the read bound, its parts bound to
10
+ * accessors, and its XML office document read if it has one. */
11
+ export interface OpenedSpreadsheet {
12
+ /** The inflated parts, for a reader that hands the whole package on to another codec. */
13
+ readonly files: Record<string, Uint8Array>;
14
+ readonly pkg: PackageAccessors;
15
+ /** `xl/workbook.xml`, or undefined when the package carries no XML office document (a `.xlsb`
16
+ * carries `xl/workbook.bin` instead). Each entry point answers that case for itself: they
17
+ * genuinely want different answers, and the difference is documented where they diverge. */
18
+ readonly workbookXml: string | undefined;
19
+ }
20
+ /**
21
+ * Open a spreadsheet package: the six-step preamble every reader shares, and in particular the two
22
+ * decisions worth having exactly one of. The inflate bound is a security decision (an unbounded
23
+ * inflate is a zip bomb) and "does this package carry an XML office document" is the dispatch
24
+ * decision between the two codecs, so neither should be restated once per entry point.
25
+ *
26
+ * It deliberately stops short of the shared strings and the style table: those are codec-specific
27
+ * (`xl/sharedStrings.xml` against BIFF12's own record stream), and lifting them here would put
28
+ * SpreadsheetML knowledge into the container layer.
29
+ */
30
+ export declare function openSpreadsheetPackage(data: Uint8Array, maxUncompressedBytes: number | undefined): OpenedSpreadsheet;
9
31
  export declare function packageAccessors(files: Record<string, Uint8Array>): PackageAccessors;
10
32
  export declare function relationshipTargetByType(xml: string, suffix: string): string | undefined;
11
33
  export declare function relationshipTargetsByType(xml: string, suffix: string): string[];
12
- export declare function resolveRelativePart(basePart: string, target: string): string;
13
- export declare function resolveWorkbookPart(target: string): string;
14
- export declare function parseRelationships(xml: string): Map<string, string>;
15
34
  export interface RelationshipRecord {
16
35
  readonly id: string;
17
36
  readonly type: string;
@@ -1,6 +1,13 @@
1
1
  import { strFromU8 } from 'fflate';
2
2
  import { openElements } from '../../xml/xml-read.js';
3
- import { extensionOf, relsPathFor } from './part-paths.js';
3
+ import { extensionOf, relsPathFor, resolveRelativePart } from './part-paths.js';
4
+ import { DEFAULT_MAX_UNCOMPRESSED } from './read-options.js';
5
+ import { inflateSpreadsheetPackage } from './sniff-format.js';
6
+ export function openSpreadsheetPackage(data, maxUncompressedBytes) {
7
+ const files = inflateSpreadsheetPackage(data, maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED);
8
+ const pkg = packageAccessors(files);
9
+ return { files, pkg, workbookXml: pkg.partText('xl/workbook.xml') };
10
+ }
4
11
  export function packageAccessors(files) {
5
12
  return {
6
13
  partText: (path) => {
@@ -10,48 +17,13 @@ export function packageAccessors(files) {
10
17
  partBytes: (path) => files[path],
11
18
  };
12
19
  }
13
- function matchesType(attrs, suffix) {
14
- return (attrs.Type !== undefined && attrs.Target !== undefined && attrs.Type.endsWith(`/${suffix}`));
15
- }
16
20
  export function relationshipTargetByType(xml, suffix) {
17
21
  return relationshipTargetsByType(xml, suffix)[0];
18
22
  }
19
23
  export function relationshipTargetsByType(xml, suffix) {
20
- const targets = [];
21
- for (const { attrs } of openElements(xml, 'Relationship')) {
22
- if (matchesType(attrs, suffix))
23
- targets.push(attrs.Target);
24
- }
25
- return targets;
26
- }
27
- export function resolveRelativePart(basePart, target) {
28
- if (target.startsWith('/'))
29
- return target.slice(1);
30
- const baseDir = basePart.slice(0, basePart.lastIndexOf('/') + 1);
31
- const out = [];
32
- for (const segment of `${baseDir}${target}`.split('/')) {
33
- if (segment === '' || segment === '.')
34
- continue;
35
- if (segment === '..')
36
- out.pop();
37
- else
38
- out.push(segment);
39
- }
40
- return out.join('/');
41
- }
42
- export function resolveWorkbookPart(target) {
43
- if (target.startsWith('/'))
44
- return target.slice(1);
45
- return `xl/${target.replace(/^\.\//, '')}`;
46
- }
47
- export function parseRelationships(xml) {
48
- const rels = new Map();
49
- for (const { attrs } of openElements(xml, 'Relationship')) {
50
- if (attrs.Id !== undefined && attrs.Target !== undefined) {
51
- rels.set(attrs.Id, attrs.Target);
52
- }
53
- }
54
- return rels;
24
+ return parseRelationshipRecords(xml)
25
+ .filter((record) => record.type.endsWith(`/${suffix}`))
26
+ .map((record) => record.target);
55
27
  }
56
28
  export function parseRelationshipRecords(xml) {
57
29
  const records = [];
@@ -1,15 +1,21 @@
1
+ /**
2
+ * A single `<Relationship>`. An `external` target lives outside the package (a hyperlink URL), so the
3
+ * element carries `TargetMode="External"`; a package-internal target (the default) omits it.
4
+ *
5
+ * All three values are escaped here, unconditionally. The obligation used to sit in a comment saying
6
+ * the caller escaped a target it did not control, which made the safety of this function a property
7
+ * of its call sites rather than of itself, on the one boundary in the tree where `xml.ts` says that is
8
+ * explicitly not how it is done. Escaping a writer-controlled package path is the identity, so the
9
+ * generated chains emit the same bytes and the guarantee holds by construction instead of by
10
+ * inspecting every caller. `TargetMode` stays a fixed token, not a value.
11
+ */
1
12
  export declare function relationship(id: string, type: string, target: string, options?: {
2
13
  external?: boolean;
3
14
  }): string;
4
15
  export declare function relationshipsPart(relationships: readonly string[]): string;
5
- export declare function preservedRelsXml(rels: readonly {
6
- id: string;
7
- type: string;
8
- target: string;
9
- external?: boolean;
10
- }[]): string;
11
16
  export declare function relsPartXml(rels: readonly {
12
17
  id: string;
13
18
  type: string;
14
19
  target: string;
20
+ external?: boolean;
15
21
  }[]): string;
@@ -2,14 +2,12 @@ import { escapeAttr, XML_DECLARATION } from '../../xml/xml.js';
2
2
  import { PKG_RELS_NS } from './namespaces.js';
3
3
  export function relationship(id, type, target, options) {
4
4
  const mode = options?.external ? ' TargetMode="External"' : '';
5
- return `<Relationship Id="${id}" Type="${type}" Target="${target}"${mode}/>`;
5
+ return (`<Relationship Id="${escapeAttr(id)}" Type="${escapeAttr(type)}" ` +
6
+ `Target="${escapeAttr(target)}"${mode}/>`);
6
7
  }
7
8
  export function relationshipsPart(relationships) {
8
9
  return `${XML_DECLARATION}<Relationships xmlns="${PKG_RELS_NS}">${relationships.join('')}</Relationships>`;
9
10
  }
10
- export function preservedRelsXml(rels) {
11
- return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, escapeAttr(rel.target), rel.external ? { external: true } : {})));
12
- }
13
11
  export function relsPartXml(rels) {
14
- return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target)));
12
+ return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target, rel.external ? { external: true } : {})));
15
13
  }
@@ -1,15 +1,20 @@
1
1
  import { type Cell } from '../../core/cell.ts';
2
- import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle } from '../../core/style.ts';
2
+ import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle, type TableStyleTable } from '../../core/style.ts';
3
3
  /**
4
4
  * What an xf resolves to: the {@link CellStyle} facet tuple, plus the two flags an xf carries that
5
5
  * are not facets. Absent facets stay undefined, matching the contract that an unset facet is simply
6
6
  * not present on the reconstructed cell.
7
7
  *
8
8
  * It *derives* the facets rather than listing them, so a seventh facet added to `CellStyle` reaches
9
- * both readers the moment it joins. Re-declaring them here, the shape this replaced, meant a new
9
+ * every codec the moment it joins. Re-declaring them here, the shape this replaced, meant a new
10
10
  * facet silently stopped at the model and never appeared in a file we read back.
11
+ *
12
+ * Read-only throughout: an xf is a resolved snapshot, assembled once and then only consulted. Both
13
+ * readers already build theirs through an explicitly-mutable draft (`{-readonly [K in keyof
14
+ * XfStyle]?: …}`), so the constraint costs nothing and stops a consumer editing a table entry that
15
+ * other cells share.
11
16
  */
12
- export interface XfStyle extends CellStyle {
17
+ export interface XfStyle extends Readonly<CellStyle> {
13
18
  readonly quotePrefix?: boolean;
14
19
  /** The `xfId` link into the named-style layer (`cellStyleXfs`); absent for the Normal default (0). */
15
20
  readonly xfId?: number;
@@ -28,7 +33,31 @@ export interface StyleTable {
28
33
  * file declares no font table.
29
34
  */
30
35
  readonly defaultFont?: Font;
36
+ /**
37
+ * The stylesheet's preserved sub-tables, captured verbatim in the same scan that read the xfs: the
38
+ * differential styles a conditional format's `dxfId` indexes, a custom indexed palette, the
39
+ * author's recent-colour swatches, and the custom table-style definitions.
40
+ *
41
+ * They ride here rather than being extracted separately because they come out of the same part,
42
+ * and the reader that wanted them four more times over that part is what this replaced. Each is
43
+ * raw source text: re-parsing and re-serialising is exactly what would cost a foreign `<dxf>` its
44
+ * number format on a round trip.
45
+ */
46
+ readonly preserved: PreservedStyleTables;
47
+ }
48
+ /** The stylesheet content the model does not interpret and re-emits byte for byte. */
49
+ export interface PreservedStyleTables {
50
+ readonly dxfs: readonly string[];
51
+ readonly indexedColors: readonly string[];
52
+ readonly mruColors: readonly string[];
53
+ readonly tableStyles: TableStyleTable;
31
54
  }
55
+ /** The preserved sub-tables of a stylesheet that carries none: a package with no `styles.xml`, and
56
+ * the BIFF12 stylesheet, whose binary records hold no verbatim XML there would be anything to keep. */
57
+ export declare const NO_PRESERVED_STYLE_TABLES: PreservedStyleTables;
58
+ /** The xf layer of a stylesheet: what {@link resolveStyleTable} assembles out of the format tables,
59
+ * before the verbatim sub-tables that ride alongside it in a {@link StyleTable}. */
60
+ export type ResolvedXfTables = Omit<StyleTable, 'preserved'>;
32
61
  /**
33
62
  * The format code a number-format id denotes: the file's own `<numFmt>`/`BrtFmt` declaration if it
34
63
  * has one, else the built-in Excel defines for that id. Id 0 is General, the absence of a format,
@@ -76,4 +105,4 @@ export declare function resolveStyleTable(tables: {
76
105
  readonly namedXfs: ReadonlyArray<XfStyle>;
77
106
  readonly labels: ReadonlyArray<StyleLabel>;
78
107
  readonly fonts: ReadonlyArray<Font | undefined>;
79
- }): StyleTable;
108
+ }): ResolvedXfTables;
@@ -1,6 +1,12 @@
1
1
  import { applyCellStyle } from '../../core/cell.js';
2
2
  import { NAMED_STYLE_ID } from '../../core/internal.js';
3
3
  import { assignStyleFacets, } from '../../core/style.js';
4
+ export const NO_PRESERVED_STYLE_TABLES = {
5
+ dxfs: [],
6
+ indexedColors: [],
7
+ mruColors: [],
8
+ tableStyles: { styles: [] },
9
+ };
4
10
  const BUILTIN_NUMFMTS = new Map([
5
11
  [1, '0'],
6
12
  [2, '0.00'],
@@ -71,8 +77,13 @@ export function resolveStyleTable(tables) {
71
77
  const named = namedXfs[xf.xfId];
72
78
  return named === undefined ? xf : { ...named, ...xf };
73
79
  });
80
+ const labelsByXfId = new Map();
81
+ for (const label of labels) {
82
+ if (!labelsByXfId.has(label.xfId))
83
+ labelsByXfId.set(label.xfId, label);
84
+ }
74
85
  const namedStyles = namedXfs.map((xf, index) => {
75
- const label = labels.find((entry) => entry.xfId === index);
86
+ const label = labelsByXfId.get(index);
76
87
  const style = {};
77
88
  assignStyleFacets(style, xf);
78
89
  if (label?.name !== undefined)
@@ -1,4 +1,4 @@
1
- import { numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
1
+ import { numFmtCodeFor, NO_PRESERVED_STYLE_TABLES, resolveStyleTable, } from '../style/xf-style.js';
2
2
  import { RecordReader } from './primitives.js';
3
3
  import { readRecords } from './record-stream.js';
4
4
  import { BRT } from './record-types.js';
@@ -22,7 +22,7 @@ const COLLECTION_ENDS = new Set([
22
22
  ]);
23
23
  export function parseStyleTable(part) {
24
24
  if (part === undefined)
25
- return { cellXfs: [], namedStyles: [] };
25
+ return { cellXfs: [], namedStyles: [], preserved: NO_PRESERVED_STYLE_TABLES };
26
26
  const numFmtCodes = new Map();
27
27
  const fonts = [];
28
28
  const fills = [];
@@ -73,7 +73,10 @@ export function parseStyleTable(part) {
73
73
  break;
74
74
  }
75
75
  }
76
- return resolveStyleTable({ directXfs, namedXfs, labels, fonts });
76
+ return {
77
+ ...resolveStyleTable({ directXfs, namedXfs, labels, fonts }),
78
+ preserved: NO_PRESERVED_STYLE_TABLES,
79
+ };
77
80
  }
78
81
  function readXf(reader, deps, isDirect) {
79
82
  const parent = reader.u16();
@@ -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,9 +1,8 @@
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
- import { RunAccumulator } from './rich-runs.ts';
7
6
  export declare class CellAccumulator {
8
7
  #private;
9
8
  constructor(options: {
@@ -15,8 +14,6 @@ export declare class CellAccumulator {
15
14
  get styleIndex(): number;
16
15
  /** This cell's 1-based column, or -1 when its address was absent or unparseable. */
17
16
  get col(): number;
18
- /** The rich-text run accumulator, driven by the surrounding parser's `<r>`/`<rPr>` handling. */
19
- get runs(): RunAccumulator;
20
17
  /**
21
18
  * The text gathered since the current element opened. A caller reads it for the elements it
22
19
  * captures itself (see {@link capture}); the machine reads it for its own.
@@ -40,5 +37,6 @@ export declare class CellAccumulator {
40
37
  */
41
38
  closeElement(local: string): 'cell' | 'claimed' | 'other';
42
39
  finalize(sheet: Worksheet, sharedStrings: readonly SharedString[], style: XfStyle | undefined): void;
40
+ private cachedResult;
43
41
  decode(sharedStrings: readonly SharedString[], style: XfStyle | undefined): CellValue;
44
42
  }
@@ -1,6 +1,6 @@
1
- import { decodeAddress, encodeAddress } from '../../core/address.js';
1
+ import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
2
2
  import { translateFormula, unmangleFunctions } from '../../core/formula.js';
3
- import { boolStrict, decodeSpreadsheetText, 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';
@@ -16,17 +16,14 @@ export class CellAccumulator {
16
16
  #sharedClone = false;
17
17
  #dataTable = null;
18
18
  #valueText = '';
19
- #inlineText = '';
20
19
  #hasFormula = false;
21
20
  #hasValue = false;
22
- #runs = new RunAccumulator();
21
+ #runs;
23
22
  #masters = new Map();
24
- #inInlineString = false;
25
23
  #capture = false;
26
24
  #text = '';
27
- #richRuns;
28
25
  constructor(options) {
29
- this.#richRuns = options.richRuns;
26
+ this.#runs = new RunAccumulator({ container: 'is', readRuns: options.richRuns });
30
27
  }
31
28
  get ref() {
32
29
  return this.#ref;
@@ -37,19 +34,15 @@ export class CellAccumulator {
37
34
  get col() {
38
35
  return this.#col;
39
36
  }
40
- get runs() {
41
- return this.#runs;
42
- }
43
37
  #beginCell(attrs) {
44
- this.#ref = attrs.r ?? '';
45
38
  this.#type = attrs.t ?? '';
46
39
  this.#style = numInteger(attrs.s, 0) ?? -1;
47
- const decoded = this.#ref === '' ? undefined : decodeAddress(this.#ref);
40
+ const decoded = tryDecodeCellRef(attrs.r ?? '');
41
+ this.#ref = decoded === undefined ? '' : (attrs.r ?? '');
48
42
  this.#col = decoded?.col ?? -1;
49
43
  this.#row = decoded?.row ?? -1;
50
44
  this.#formula = '';
51
45
  this.#valueText = '';
52
- this.#inlineText = '';
53
46
  this.#runs.beginContainer();
54
47
  this.#hasFormula = false;
55
48
  this.#hasValue = false;
@@ -81,15 +74,6 @@ export class CellAccumulator {
81
74
  this.#valueText = text;
82
75
  this.#hasValue = true;
83
76
  }
84
- #beginInlineString() {
85
- this.#inlineText = '';
86
- this.#runs.beginContainer();
87
- }
88
- #appendText(text) {
89
- const decoded = decodeSpreadsheetText(text);
90
- if (!this.#runs.appendText(decoded) && this.#inInlineString)
91
- this.#inlineText += decoded;
92
- }
93
77
  get capturedText() {
94
78
  return this.#text;
95
79
  }
@@ -99,14 +83,12 @@ export class CellAccumulator {
99
83
  openElement(local, attrs, selfClosing) {
100
84
  this.#text = '';
101
85
  this.#capture = false;
86
+ if (this.#runs.open(local, attrs, selfClosing))
87
+ return true;
102
88
  switch (local) {
103
89
  case 'c':
104
90
  this.#beginCell(attrs);
105
91
  return true;
106
- case 'is':
107
- this.#inInlineString = true;
108
- this.#beginInlineString();
109
- return true;
110
92
  case 'f':
111
93
  this.#capture = !selfClosing;
112
94
  this.#beginFormula(attrs, selfClosing);
@@ -114,25 +96,12 @@ export class CellAccumulator {
114
96
  case 'v':
115
97
  this.#capture = !selfClosing;
116
98
  return true;
117
- case 't':
118
- this.#capture = true;
119
- return true;
120
- case 'r':
121
- if (!this.#richRuns)
122
- return false;
123
- if (this.#inInlineString)
124
- this.#runs.beginRun();
125
- return true;
126
- case 'rPr':
127
- if (!this.#richRuns)
128
- return false;
129
- this.#runs.beginProperties();
130
- return true;
131
99
  default:
132
100
  return false;
133
101
  }
134
102
  }
135
103
  appendChunk(chunk) {
104
+ this.#runs.text(chunk);
136
105
  if (this.#capture)
137
106
  this.#text += chunk;
138
107
  }
@@ -142,6 +111,8 @@ export class CellAccumulator {
142
111
  return verdict;
143
112
  }
144
113
  #closeElement(local) {
114
+ if (this.#runs.close(local) !== 'other')
115
+ return 'claimed';
145
116
  switch (local) {
146
117
  case 'f':
147
118
  this.#setFormula(this.#text);
@@ -149,17 +120,6 @@ export class CellAccumulator {
149
120
  case 'v':
150
121
  this.#setValue(this.#text);
151
122
  return 'claimed';
152
- case 't':
153
- this.#appendText(this.#text);
154
- return 'claimed';
155
- case 'r':
156
- if (!this.#richRuns)
157
- return 'other';
158
- this.#runs.endRun();
159
- return 'claimed';
160
- case 'is':
161
- this.#inInlineString = false;
162
- return 'claimed';
163
123
  case 'c':
164
124
  return 'cell';
165
125
  default:
@@ -183,9 +143,7 @@ export class CellAccumulator {
183
143
  ...(boolStrict(this.#dataTable.dtr) ? { dataTableRow: true } : {}),
184
144
  ...(this.#dataTable.r1 !== undefined ? { r1: this.#dataTable.r1 } : {}),
185
145
  ...(this.#dataTable.r2 !== undefined ? { r2: this.#dataTable.r2 } : {}),
186
- ...(this.#hasValue
187
- ? { result: decodeFormulaResult(this.#type, this.#valueText, style?.numFmt) }
188
- : {}),
146
+ ...this.cachedResult(style),
189
147
  };
190
148
  }
191
149
  if (this.#hasFormula && this.#formulaShared && this.#formulaSi >= 0) {
@@ -198,14 +156,18 @@ export class CellAccumulator {
198
156
  return {
199
157
  sharedFormula: encodeAddress(master.col, master.row),
200
158
  formula: unmangleFunctions(translated),
201
- ...(this.#hasValue
202
- ? { result: decodeFormulaResult(this.#type, this.#valueText, style?.numFmt) }
203
- : {}),
159
+ ...this.cachedResult(style),
204
160
  };
205
161
  }
206
162
  }
207
163
  return this.decode(sharedStrings, style);
208
164
  }
165
+ cachedResult(style) {
166
+ if (!this.#hasValue)
167
+ return {};
168
+ const result = decodeFormulaResult(this.#type, this.#valueText, style?.numFmt);
169
+ return result === undefined ? {} : { result };
170
+ }
209
171
  decode(sharedStrings, style) {
210
172
  const raw = {
211
173
  type: this.#type,
@@ -213,7 +175,7 @@ export class CellAccumulator {
213
175
  formula: this.#formula,
214
176
  hasValue: this.#hasValue,
215
177
  valueText: this.#valueText,
216
- inlineText: this.#inlineText,
178
+ inlineText: this.#runs.plainText,
217
179
  richTextRuns: this.#runs.runs,
218
180
  };
219
181
  return decodeCellContent(raw, sharedStrings, style?.numFmt);
@@ -29,4 +29,4 @@ export declare function decodeCellContent(raw: RawCell, sharedStrings: readonly
29
29
  * `numFmt` to a {@link Date} exactly as a bare numeric cell is, so a date-valued formula result
30
30
  * (e.g. `TODAY()`) reads back as a Date, not a serial. Shared by the buffered reader's shared-formula
31
31
  * clone resolution, which caches a result the same way a plain formula cell does. */
32
- export declare function decodeFormulaResult(type: string, valueText: string, numFmt?: string): FormulaResult;
32
+ export declare function decodeFormulaResult(type: string, valueText: string, numFmt?: string): FormulaResult | undefined;
@@ -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 } 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);
@@ -32,8 +32,11 @@ function decodeValue(type, valueText, inlineText, hasValue, sharedStrings) {
32
32
  return boolStrict(valueText);
33
33
  case 'e':
34
34
  return isErrorCode(valueText) ? { error: valueText } : valueText;
35
- default:
36
- return hasValue ? Number(valueText) : null;
35
+ default: {
36
+ if (!hasValue)
37
+ return null;
38
+ return numFinite(valueText) ?? null;
39
+ }
37
40
  }
38
41
  }
39
42
  export function decodeFormulaResult(type, valueText, numFmt) {
@@ -51,6 +54,6 @@ function decodeResult(type, valueText) {
51
54
  case 'e':
52
55
  return isErrorCode(valueText) ? { error: valueText } : valueText;
53
56
  default:
54
- return Number(valueText);
57
+ return numFinite(valueText);
55
58
  }
56
59
  }
@@ -1,4 +1,4 @@
1
- import type { Color } from '../../core/style.ts';
1
+ import { type Color } from '../../core/style.ts';
2
2
  /** Serialise a {@link Color} as the attribute list a `<color>`-shaped element carries. */
3
3
  export declare function colorAttrs(color: Color): string;
4
4
  export declare function parseColor(attrs: {
@@ -1,8 +1,9 @@
1
- import { numFinite, numInteger } from '../../xml/xml-read.js';
1
+ import { parseArgb } from '../../core/style.js';
2
+ import { numFinite, numInteger } from '../../xml/xml-scan.js';
3
+ import { numberText } from '../../xml/xml.js';
2
4
  function normalizeArgb(argb) {
3
- const hex = argb.startsWith('#') ? argb.slice(1) : argb;
4
- const rgb = hex.length === 6 ? `FF${hex}` : hex;
5
- if (!/^[0-9a-fA-F]{8}$/.test(rgb)) {
5
+ const rgb = parseArgb(argb);
6
+ if (rgb === undefined) {
6
7
  throw new SyntaxError(`Invalid ARGB colour ${JSON.stringify(argb)}: expected 6 or 8 hexadecimal digits`);
7
8
  }
8
9
  return rgb;
@@ -12,11 +13,11 @@ export function colorAttrs(color) {
12
13
  if (color.argb !== undefined)
13
14
  parts.push(`rgb="${normalizeArgb(color.argb)}"`);
14
15
  if (color.theme !== undefined)
15
- parts.push(`theme="${color.theme}"`);
16
+ parts.push(`theme="${numberText(color.theme)}"`);
16
17
  if (color.tint !== undefined)
17
- parts.push(`tint="${color.tint}"`);
18
+ parts.push(`tint="${numberText(color.tint)}"`);
18
19
  if (color.indexed !== undefined)
19
- parts.push(`indexed="${color.indexed}"`);
20
+ parts.push(`indexed="${numberText(color.indexed)}"`);
20
21
  return parts.join(' ');
21
22
  }
22
23
  export function parseColor(attrs) {
@@ -1,6 +1,7 @@
1
1
  import { tryDecodeCellRef } from '../../core/address.js';
2
- import { decodeSpreadsheetText, localName, numInteger, parseXml } from '../../xml/xml-read.js';
3
- import { escapeAttr, escapeText, textElement, XML_DECLARATION } from '../../xml/xml.js';
2
+ import { parseXml } from '../../xml/xml-read.js';
3
+ import { decodeSpreadsheetText, localName, numInteger } from '../../xml/xml-scan.js';
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) {
6
7
  const fallbacks = threadFallbacks(threads);
@@ -56,7 +57,7 @@ export function commentsXml(comments) {
56
57
  .map((comment) => {
57
58
  const { threadId } = comment;
58
59
  const authorId = threadId === undefined ? noteAuthorId : authorIdByThreadId.get(threadId);
59
- const uid = threadId === undefined ? '' : ` xr:uid="${escapeAttr(threadId)}"`;
60
+ const uid = textAttr('xr:uid', threadId);
60
61
  return (`<comment ref="${comment.ref}" authorId="${authorId}"${uid}>` +
61
62
  `<text><r>${textElement(comment.text)}</r></text>` +
62
63
  '</comment>');
@@ -161,6 +162,8 @@ export function applyNotes(sheet, comments) {
161
162
  for (const [ref, comment] of comments) {
162
163
  if (comment.threadId !== undefined && headIds.has(comment.threadId))
163
164
  continue;
165
+ if (tryDecodeCellRef(ref) === undefined)
166
+ continue;
164
167
  sheet.getCell(ref).note = comment.text;
165
168
  }
166
169
  }