@shbernal/ts-xlsx 1.3.1 → 2.1.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 (186) 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 +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -1,5 +1,15 @@
1
- import type { CellValue } from '../../core/value.ts';
1
+ import { type CellValue } from '../../core/value.ts';
2
2
  import type { Workbook } from '../../core/workbook.ts';
3
+ /**
4
+ * A byte encoding {@link writeCsv} can produce, spelled the way Node's `Buffer` spells it and
5
+ * meaning the same bytes.
6
+ *
7
+ * Named here rather than taken from Node's `BufferEncoding`, which is what this option used to be:
8
+ * that type is part of `@types/node`, so it made a browser consumer's public API surface depend on
9
+ * Node's types, and it offered `base64` and `hex` as if they were output encodings for a text
10
+ * format. The list is what a CSV consumer actually asks for.
11
+ */
12
+ export type CsvEncoding = 'ascii' | 'latin1' | 'ucs-2' | 'ucs2' | 'utf-8' | 'utf-16le' | 'utf16le' | 'utf8';
3
13
  export interface CsvWriteOptions {
4
14
  /** Which worksheet to write; defaults to the first. A name matching no sheet throws rather than
5
15
  * silently emitting an empty file. */
@@ -14,7 +24,7 @@ export interface CsvWriteOptions {
14
24
  /** Render Date cells in UTC rather than the runner's local time. */
15
25
  readonly dateUTC?: boolean;
16
26
  /** Byte encoding for {@link writeCsv}; defaults to `"utf8"`. */
17
- readonly encoding?: BufferEncoding;
27
+ readonly encoding?: CsvEncoding;
18
28
  /** Prepend a UTF-8 byte-order mark (applies only to UTF-8); defaults to `true` for UTF-8. */
19
29
  readonly bom?: boolean;
20
30
  /** Per-field transform replacing the default value rendering; receives the cell's value (`null`
@@ -22,7 +32,12 @@ export interface CsvWriteOptions {
22
32
  * still applied to the returned text. */
23
33
  readonly map?: (value: CellValue, index: number) => string;
24
34
  }
25
- /** The logical CSV text of one worksheet no BOM, no byte encoding. */
35
+ /** The logical CSV text of one worksheet: no BOM, no byte encoding. */
26
36
  export declare function writeCsvText(workbook: Workbook, options?: CsvWriteOptions): string;
27
- /** The CSV bytes of one worksheet in the requested encoding, with a UTF-8 BOM by default. */
37
+ /**
38
+ * The CSV bytes of one worksheet in the requested encoding, with a UTF-8 BOM by default.
39
+ *
40
+ * @throws {AuthoringError} if a field holds an unpaired surrogate and the encoding is UTF-8, which
41
+ * cannot represent one. The alternative is a silent U+FFFD substitution.
42
+ */
28
43
  export declare function writeCsv(workbook: Workbook, options?: CsvWriteOptions): Uint8Array;
@@ -27,15 +27,45 @@ export function writeCsvText(workbook, options = {}) {
27
27
  export function writeCsv(workbook, options = {}) {
28
28
  const text = writeCsvText(workbook, options);
29
29
  const encoding = options.encoding ?? 'utf8';
30
- const body = Buffer.from(text, encoding);
31
- const wantBom = options.bom ?? encoding === 'utf8';
32
- if (!wantBom || encoding !== 'utf8')
33
- return Uint8Array.from(body);
30
+ const utf8 = isUtf8(encoding);
31
+ if (utf8)
32
+ assertEncodable(text);
33
+ const body = encode(text, encoding);
34
+ const wantBom = options.bom ?? utf8;
35
+ if (!wantBom || !utf8)
36
+ return body;
34
37
  const out = new Uint8Array(UTF8_BOM.length + body.length);
35
38
  out.set(UTF8_BOM, 0);
36
39
  out.set(body, UTF8_BOM.length);
37
40
  return out;
38
41
  }
42
+ function isUtf8(encoding) {
43
+ return encoding === 'utf8' || encoding === 'utf-8';
44
+ }
45
+ function encode(text, encoding) {
46
+ if (isUtf8(encoding))
47
+ return new TextEncoder().encode(text);
48
+ if (encoding === 'ascii' || encoding === 'latin1') {
49
+ const bytes = new Uint8Array(text.length);
50
+ for (let i = 0; i < text.length; i++)
51
+ bytes[i] = text.charCodeAt(i) & 0xff;
52
+ return bytes;
53
+ }
54
+ const bytes = new Uint8Array(text.length * 2);
55
+ const view = new DataView(bytes.buffer);
56
+ for (let i = 0; i < text.length; i++)
57
+ view.setUint16(i * 2, text.charCodeAt(i), true);
58
+ return bytes;
59
+ }
60
+ function assertEncodable(text) {
61
+ const found = LONE_SURROGATE.exec(text);
62
+ if (found === null)
63
+ return;
64
+ const codePoint = text.codePointAt(found.index).toString(16).toUpperCase();
65
+ throw new AuthoringError(`cannot write U+${codePoint} at offset ${found.index} of the CSV text: it is an unpaired ` +
66
+ 'surrogate, which UTF-8 cannot encode and CSV has no escape for');
67
+ }
68
+ const LONE_SURROGATE = /[\u{D800}-\u{DFFF}]/u;
39
69
  function selectSheet(workbook, name) {
40
70
  if (name === undefined) {
41
71
  const first = workbook.worksheets[0];
@@ -1,11 +1,11 @@
1
1
  import { XlsxError } from '../../errors.ts';
2
2
  /**
3
3
  * Which unsupported input the reader recognised:
4
- * - `'xls'` a legacy BIFF `.xls` (an OLE2/CFB compound file), detected by its magic bytes.
5
- * - `'xlsb'` a binary BIFF12 `.xlsb`: the same OPC/ZIP container as `.xlsx`, but its office document
4
+ * - `'xls'`: a legacy BIFF `.xls` (an OLE2/CFB compound file), detected by its magic bytes.
5
+ * - `'xlsb'`: a binary BIFF12 `.xlsb`, the same OPC/ZIP container as `.xlsx`, but its office document
6
6
  * is `xl/workbook.bin` rather than `xl/workbook.xml`. `readXlsx`/`readXlsb` read one; the entry points
7
7
  * that cannot yet (the row streamer) report it under this format with their own message.
8
- * - `'unknown'` not a recognised spreadsheet at all: not a ZIP, or a ZIP carrying no OOXML workbook
8
+ * - `'unknown'`: not a recognised spreadsheet at all, meaning not a ZIP, or a ZIP carrying no OOXML workbook
9
9
  * part (nor a `.xlsb` binary one).
10
10
  */
11
11
  export type UnsupportedFormat = 'xls' | 'xlsb' | 'unknown';
@@ -14,7 +14,7 @@ export type UnsupportedFormat = 'xls' | 'xlsb' | 'unknown';
14
14
  * caller keys on (rather than a subclass per format), so a `catch` can distinguish a legacy `.xls`, a
15
15
  * binary `.xlsb`, and an unrecognised blob without string-matching the message.
16
16
  *
17
- * The message never carries a filesystem path or the underlying zip library's internals the whole
17
+ * The message never carries a filesystem path or the underlying zip library's internals. The whole
18
18
  * point of the type is that the classification, not a leaked lower-layer string, is what the caller sees.
19
19
  *
20
20
  * {@link format} stays the branch for *which* unsupported input this was; the inherited
@@ -33,11 +33,11 @@ export declare class UnsupportedFormatError extends XlsxError {
33
33
  *
34
34
  * The neighbouring {@link UnsupportedFormatError} says the input is a different *kind* of thing; this
35
35
  * one says it is the right kind and we will not (or cannot) unpack it. Keeping them apart is what
36
- * lets a caller answer "should I try another reader, or reject this file?" and it is what replaced
36
+ * lets a caller answer "should I try another reader, or reject this file?", and it is what replaced
37
37
  * the message-prefix match the bomb refusal used to be recognised by.
38
38
  *
39
39
  * The zip library's own failure text never survives into either the message or `cause`: it can name
40
- * internals or an absolute filesystem path from the layer below, and this type carries the
40
+ * internals, or an absolute filesystem path, from the layer below, and this type carries the
41
41
  * classification precisely so no lower-layer string has to.
42
42
  */
43
43
  export declare class PackageReadError extends XlsxError {
@@ -6,7 +6,7 @@
6
6
  * @param cap Maximum total uncompressed output, in bytes, across all parts. Enforced
7
7
  * against bytes actually produced, never against the archive's declared sizes.
8
8
  * @returns A map of part path to inflated bytes.
9
- * @throws {PackageReadError} if inflation would exceed `cap` a probable zip bomb.
9
+ * @throws {PackageReadError} if inflation would exceed `cap`, a probable zip bomb.
10
10
  * @throws {Error} raised by the zip layer if the archive is malformed or a part uses an unsupported
11
11
  * compression method. Callers reach this through {@link inflateSpreadsheetPackage}, which is where
12
12
  * those are classified; the raw error is deliberately not re-typed here, because its text is the
@@ -1,4 +1,5 @@
1
1
  import { Unzip, UnzipInflate } from 'fflate';
2
+ import { concat } from '../../bytes.js';
2
3
  import { PackageReadError } from './errors.js';
3
4
  const INPUT_SLICE = 1 << 14;
4
5
  export function inflatePackage(data, cap) {
@@ -23,7 +24,7 @@ export function inflatePackage(data, cap) {
23
24
  chunks.push(chunk);
24
25
  size += chunk.length;
25
26
  if (final)
26
- files[file.name] = join(chunks, size);
27
+ files[file.name] = concat(chunks, size);
27
28
  };
28
29
  try {
29
30
  file.start();
@@ -43,14 +44,3 @@ export function inflatePackage(data, cap) {
43
44
  throw failure;
44
45
  return files;
45
46
  }
46
- function join(chunks, size) {
47
- if (chunks.length === 1)
48
- return chunks[0];
49
- const out = new Uint8Array(size);
50
- let at = 0;
51
- for (const chunk of chunks) {
52
- out.set(chunk, at);
53
- at += chunk.length;
54
- }
55
- return out;
56
- }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The namespace URIs the Open Packaging Conventions layer itself owns the ones that describe a
2
+ * The namespace URIs the Open Packaging Conventions layer itself owns: the ones that describe a
3
3
  * *package* rather than the spreadsheet inside it. Every OOXML package carries these regardless of
4
4
  * which serialisation its office document uses, so an `.xlsb` and an `.xlsx` agree on them exactly.
5
5
  *
@@ -2,14 +2,13 @@ import type { PreservedPart } from '../../core/preserved.ts';
2
2
  export interface PackageAccessors {
3
3
  /** A part's decoded text, or undefined when the package holds no such part. Decodes lazily, so a
4
4
  * part the reader never asks for is never stringified. */
5
- partText(path: string): string | undefined;
5
+ partText: (path: string) => string | undefined;
6
6
  /** A part's raw bytes, or undefined when the package holds no such part. */
7
- partBytes(path: string): Uint8Array | undefined;
7
+ partBytes: (path: string) => Uint8Array | undefined;
8
8
  }
9
9
  export declare function packageAccessors(files: Record<string, Uint8Array>): PackageAccessors;
10
10
  export declare function relationshipTargetByType(xml: string, suffix: string): string | undefined;
11
11
  export declare function relationshipTargetsByType(xml: string, suffix: string): string[];
12
- export declare function sheetRelTarget(sheetPath: string, partText: (path: string) => string | undefined, type: string): string | undefined;
13
12
  export declare function resolveRelativePart(basePart: string, target: string): string;
14
13
  export declare function resolveWorkbookPart(target: string): string;
15
14
  export declare function parseRelationships(xml: string): Map<string, string>;
@@ -20,5 +19,22 @@ export interface RelationshipRecord {
20
19
  readonly external: boolean;
21
20
  }
22
21
  export declare function parseRelationshipRecords(xml: string): RelationshipRecord[];
22
+ export interface PartRelationships {
23
+ /** Every relationship the part declares, in declaration order. */
24
+ readonly records: readonly RelationshipRecord[];
25
+ /** The relationship with this id, or undefined. Its `target` is raw: an external one is a URL, not a
26
+ * package path, so a caller that may see `TargetMode="External"` must read it before resolving. */
27
+ byId(id: string): RelationshipRecord | undefined;
28
+ /** Resolve one of this part's targets against the part's own directory. */
29
+ pathOf(target: string): string;
30
+ /** The package part reached through the first relationship whose Type ends with `/<suffix>`, or
31
+ * undefined when the part declares none: the single-part lookup (notes, printer settings, drawing,
32
+ * background) in one call. */
33
+ targetPath(suffix: string): string | undefined;
34
+ /** Every package part reached through a relationship of this type, in declaration order. For a part
35
+ * class one sheet may reference more than once (tables, pivot tables). */
36
+ targetPaths(suffix: string): string[];
37
+ }
38
+ export declare function readPartRelationships(partPath: string, partText: (path: string) => string | undefined): PartRelationships;
23
39
  export declare function contentTypeResolver(contentTypesXml: string): (path: string) => string;
24
40
  export declare function capturePartClosure(entryPath: string, partText: (path: string) => string | undefined, partBytes: (path: string) => Uint8Array | undefined, contentTypeOf: (path: string) => string): readonly PreservedPart[] | undefined;
@@ -3,7 +3,7 @@ import { openElements } from '../../xml/xml-read.js';
3
3
  import { extensionOf, relsPathFor } from './part-paths.js';
4
4
  export function packageAccessors(files) {
5
5
  return {
6
- partText(path) {
6
+ partText: (path) => {
7
7
  const bytes = files[path];
8
8
  return bytes === undefined ? undefined : strFromU8(bytes);
9
9
  },
@@ -24,13 +24,6 @@ export function relationshipTargetsByType(xml, suffix) {
24
24
  }
25
25
  return targets;
26
26
  }
27
- export function sheetRelTarget(sheetPath, partText, type) {
28
- const relsXml = partText(relsPathFor(sheetPath));
29
- if (relsXml === undefined)
30
- return undefined;
31
- const target = relationshipTargetByType(relsXml, type);
32
- return target === undefined ? undefined : resolveRelativePart(sheetPath, target);
33
- }
34
27
  export function resolveRelativePart(basePart, target) {
35
28
  if (target.startsWith('/'))
36
29
  return target.slice(1);
@@ -74,6 +67,19 @@ export function parseRelationshipRecords(xml) {
74
67
  }
75
68
  return records;
76
69
  }
70
+ export function readPartRelationships(partPath, partText) {
71
+ const records = parseRelationshipRecords(partText(relsPathFor(partPath)) ?? '');
72
+ const byId = new Map(records.map((record) => [record.id, record]));
73
+ const pathOf = (target) => resolveRelativePart(partPath, target);
74
+ const targetsOf = (suffix) => records.filter((record) => record.type.endsWith(`/${suffix}`)).map((record) => record.target);
75
+ return {
76
+ records,
77
+ byId: (id) => byId.get(id),
78
+ pathOf,
79
+ targetPath: (suffix) => targetsOf(suffix).map(pathOf)[0],
80
+ targetPaths: (suffix) => targetsOf(suffix).map(pathOf),
81
+ };
82
+ }
77
83
  export function contentTypeResolver(contentTypesXml) {
78
84
  const overrides = new Map();
79
85
  const defaults = new Map();
@@ -1,8 +1,8 @@
1
1
  export interface ReadXlsxOptions {
2
2
  /**
3
3
  * Maximum total uncompressed output, in bytes, produced while inflating the package.
4
- * The bound is enforced by a running counter as bytes are decompressed never read from
5
- * the archive's (untrusted, forgeable) size headers so a zip bomb that lies about its
4
+ * The bound is enforced by a running counter as bytes are decompressed, never read from
5
+ * the archive's (untrusted, forgeable) size headers, so a zip bomb that lies about its
6
6
  * uncompressed size is rejected all the same. Defaults to 512 MiB.
7
7
  */
8
8
  readonly maxUncompressedBytes?: number;
@@ -1,3 +1,14 @@
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;
@@ -2,13 +2,14 @@ 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
11
  export function preservedRelsXml(rels) {
11
- return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, escapeAttr(rel.target), rel.external ? { external: true } : {})));
12
+ return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target, rel.external ? { external: true } : {})));
12
13
  }
13
14
  export function relsPartXml(rels) {
14
15
  return relationshipsPart(rels.map((rel) => relationship(rel.id, rel.type, rel.target)));
@@ -1,10 +1,10 @@
1
1
  import { UnsupportedFormatError } from './errors.ts';
2
2
  /** The container kind a leading magic-byte sniff distinguishes, before any inflation. */
3
3
  export type Container = 'cfb' | 'zip' | 'other';
4
- /** Classify a blob's container by its leading magic bytes alone no allocation, no inflation. */
4
+ /** Classify a blob's container by its leading magic bytes alone: no allocation, no inflation. */
5
5
  export declare function sniffContainer(data: Uint8Array): Container;
6
6
  /**
7
- * Inflate a spreadsheet package (`.xlsx` or `.xlsb` the container is the same), translating input
7
+ * Inflate a spreadsheet package (`.xlsx` or `.xlsb`; the container is the same), translating input
8
8
  * that is neither into a typed error before or instead of a raw zip failure:
9
9
  * - a legacy `.xls` (CFB) blob → {@link UnsupportedFormatError} `'xls'`, caught by the magic sniff so
10
10
  * fflate never runs on it;
@@ -1,15 +1,20 @@
1
1
  import { type Cell } from '../../core/cell.ts';
2
- import type { CellStyle, Font, NamedCellStyle } from '../../core/style.ts';
2
+ import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle } 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;
@@ -21,7 +26,7 @@ export interface StyleTable {
21
26
  readonly cellXfs: ReadonlyArray<XfStyle>;
22
27
  readonly namedStyles: ReadonlyArray<NamedCellStyle>;
23
28
  /**
24
- * Font id 0 the workbook's declared default font, the face every cell naming no font renders in.
29
+ * Font id 0: the workbook's declared default font, the face every cell naming no font renders in.
25
30
  * Surfaced separately from the fonts it was flattened onto because it is workbook-level state, not a
26
31
  * cell format: a re-write must emit *this* face as font 0 rather than an assumed Calibri, or every
27
32
  * empty cell changes face and every character-unit column width changes meaning. Absent when the
@@ -31,17 +36,49 @@ export interface StyleTable {
31
36
  }
32
37
  /**
33
38
  * The format code a number-format id denotes: the file's own `<numFmt>`/`BrtFmt` declaration if it
34
- * has one, else the built-in Excel defines for that id. Id 0 is General the absence of a format
39
+ * has one, else the built-in Excel defines for that id. Id 0 is General, the absence of a format,
35
40
  * and resolves to nothing so an ordinary cell carries no `numFmt`.
36
41
  */
37
42
  export declare function numFmtCodeFor(id: number, custom: ReadonlyMap<number, string>): string | undefined;
38
43
  /**
39
- * Apply a resolved xf's non-value facets to a cell the six {@link CellStyle} facets through the
44
+ * Apply a resolved xf's non-value facets to a cell: the six {@link CellStyle} facets through the
40
45
  * shared {@link applyCellStyle}, plus the two links that live on the xf itself rather than in the
41
46
  * facet tuple (`quotePrefix`, and the `xfId` pointer into the named-style layer).
42
47
  *
43
48
  * Shared by every path that commits a cell: the XML reader's ordinary and shared-formula-clone paths,
44
- * and the BIFF12 reader so a styled cell keeps its look regardless of which serialisation it came
49
+ * and the BIFF12 reader, so a styled cell keeps its look regardless of which serialisation it came
45
50
  * from, and the two cannot drift on what "applying a style" means.
46
51
  */
47
52
  export declare function applyXfToCell(cell: Cell, style: XfStyle | undefined): void;
53
+ /**
54
+ * A `<cellStyle>` / `BrtStyle` label: the name and builtinId that title one `cellStyleXfs` entry,
55
+ * keyed to that entry's index.
56
+ */
57
+ export interface StyleLabel {
58
+ readonly xfId: number;
59
+ readonly name?: string;
60
+ readonly builtinId?: number;
61
+ }
62
+ /** The sub-tables an xf resolves its facet ids against, in whichever spelling the codec parsed them. */
63
+ export interface XfDeps {
64
+ readonly fonts: ReadonlyArray<Font | undefined>;
65
+ readonly fills: ReadonlyArray<Fill | undefined>;
66
+ readonly borders: ReadonlyArray<Border | undefined>;
67
+ readonly numFmtCodes: ReadonlyMap<number, string>;
68
+ }
69
+ /**
70
+ * Turn the four tables a style-sheet parse yields into the {@link StyleTable} both codecs hand back:
71
+ * layer each direct xf over the named style it links to, title the named layer with its labels, and
72
+ * carry font 0 out as the workbook default.
73
+ *
74
+ * A facet the direct xf sets wins; one it leaves unset falls through to the named base; and the
75
+ * `xfId` link is carried through so a re-write keeps it. None of that depends on whether the tables
76
+ * were parsed out of `xl/styles.xml` or `xl/styles.bin`, which is the point of stating it once: the
77
+ * two readers used to hold a copy each, cross-referenced by a comment saying they agreed.
78
+ */
79
+ export declare function resolveStyleTable(tables: {
80
+ readonly directXfs: ReadonlyArray<XfStyle>;
81
+ readonly namedXfs: ReadonlyArray<XfStyle>;
82
+ readonly labels: ReadonlyArray<StyleLabel>;
83
+ readonly fonts: ReadonlyArray<Font | undefined>;
84
+ }): StyleTable;
@@ -1,5 +1,6 @@
1
1
  import { applyCellStyle } from '../../core/cell.js';
2
2
  import { NAMED_STYLE_ID } from '../../core/internal.js';
3
+ import { assignStyleFacets, } from '../../core/style.js';
3
4
  const BUILTIN_NUMFMTS = new Map([
4
5
  [1, '0'],
5
6
  [2, '0.00'],
@@ -62,3 +63,29 @@ export function applyXfToCell(cell, style) {
62
63
  if (style.xfId !== undefined)
63
64
  cell[NAMED_STYLE_ID] = style.xfId;
64
65
  }
66
+ export function resolveStyleTable(tables) {
67
+ const { directXfs, namedXfs, labels, fonts } = tables;
68
+ const cellXfs = directXfs.map((xf) => {
69
+ if (xf.xfId === undefined)
70
+ return xf;
71
+ const named = namedXfs[xf.xfId];
72
+ return named === undefined ? xf : { ...named, ...xf };
73
+ });
74
+ const labelsByXfId = new Map();
75
+ for (const label of labels) {
76
+ if (!labelsByXfId.has(label.xfId))
77
+ labelsByXfId.set(label.xfId, label);
78
+ }
79
+ const namedStyles = namedXfs.map((xf, index) => {
80
+ const label = labelsByXfId.get(index);
81
+ const style = {};
82
+ assignStyleFacets(style, xf);
83
+ if (label?.name !== undefined)
84
+ style.name = label.name;
85
+ if (label?.builtinId !== undefined)
86
+ style.builtinId = label.builtinId;
87
+ return style;
88
+ });
89
+ const defaultFont = fonts[0];
90
+ return defaultFont === undefined ? { cellXfs, namedStyles } : { cellXfs, namedStyles, defaultFont };
91
+ }
@@ -1,6 +1,6 @@
1
1
  import { XlsxError } from '../../errors.ts';
2
2
  /**
3
- * Thrown when an `.xlsb` package's binary BIFF12 content cannot be parsed a record whose declared
3
+ * Thrown when an `.xlsb` package's binary BIFF12 content cannot be parsed: a record whose declared
4
4
  * size runs past the end of its part, a truncated record header, or a structure that does not conform
5
5
  * to [MS-XLSB].
6
6
  *
@@ -7,16 +7,16 @@ export interface ExternSheetRef {
7
7
  }
8
8
  /** The workbook-level tables a formula's references and names resolve through. */
9
9
  export interface FormulaScope {
10
- /** Sheet names in workbook (tab) order what an `Xti`'s sheet indices point into. */
10
+ /** Sheet names in workbook (tab) order: what an `Xti`'s sheet indices point into. */
11
11
  readonly sheetNames: readonly string[];
12
12
  /** The `BrtExternSheet` table, indexed by a 3-D token's `ixti`. */
13
13
  readonly externSheets: readonly ExternSheetRef[];
14
14
  /** The index of the supporting book that is this workbook, or `undefined` when the file declares a
15
15
  * supporting book this reader does not recognise. A workbook with no external links declares
16
- * exactly one itself so this is the ordinary case; anything else leaves the indices untrustworthy
16
+ * exactly one, itself, so this is the ordinary case; anything else leaves the indices untrustworthy
17
17
  * and no 3-D reference resolves, which drops those formulas rather than naming the wrong sheet. */
18
18
  readonly selfSupBook: number | undefined;
19
- /** Every `BrtName` in file order, function placeholders included a `PtgName` cites one by
19
+ /** Every `BrtName` in file order, function placeholders included. A `PtgName` cites one by
20
20
  * **1-based** index, so filtering this list would misaddress every name reference. */
21
21
  readonly names: readonly string[];
22
22
  }
@@ -28,7 +28,7 @@ export interface FormulaAnchor {
28
28
  }
29
29
  /**
30
30
  * Decode a `CellParsedFormula`'s token stream into formula text, in the same on-disk spelling the XML
31
- * form writes into `<f>` `_xlfn.`-prefixed function names included, so the caller applies the same
31
+ * form writes into `<f>`, `_xlfn.`-prefixed function names included, so the caller applies the same
32
32
  * `unmangleFunctions` normalisation to either serialisation.
33
33
  *
34
34
  * @param rgce the token stream.
@@ -7,7 +7,7 @@ export interface CellHeader {
7
7
  /** Zero-based index into the style sheet's cell-XF collection. */
8
8
  readonly styleIndex: number;
9
9
  }
10
- /** An `UncheckedRfX` ([MS-XLSB] 2.5.155) cell range all four bounds zero-based and inclusive. */
10
+ /** An `UncheckedRfX` ([MS-XLSB] 2.5.155) cell range, all four bounds zero-based and inclusive. */
11
11
  export interface RangeBounds {
12
12
  readonly rowFirst: number;
13
13
  readonly rowLast: number;
@@ -18,7 +18,7 @@ export interface RangeBounds {
18
18
  * A bounds-checked cursor over one record's payload.
19
19
  *
20
20
  * Each accessor advances the cursor by exactly the bytes it consumed, so a record is decoded by
21
- * naming its fields in order. Reading past the payload throws {@link XlsbParseError} a record that
21
+ * naming its fields in order. Reading past the payload throws {@link XlsbParseError}: a record that
22
22
  * is shorter than its own definition is a malformed file, not a case to guess through.
23
23
  */
24
24
  export declare class RecordReader {
@@ -28,10 +28,10 @@ export declare class RecordReader {
28
28
  get remaining(): number;
29
29
  /** Whether the whole payload has been consumed. */
30
30
  get done(): boolean;
31
- /** Advance past `count` bytes without decoding them a reserved or unmodelled field. */
31
+ /** Advance past `count` bytes without decoding them: a reserved or unmodelled field. */
32
32
  skip(count: number): void;
33
33
  /**
34
- * The next `count` bytes as a **view**, for a field whose own decoding happens elsewhere a formula
34
+ * The next `count` bytes as a **view**, for a field whose own decoding happens elsewhere: a formula
35
35
  * token stream, whose meaning depends on workbook tables this record knows nothing about. A view
36
36
  * rather than a copy for the same reason a record's payload is one: the declared length comes from
37
37
  * the file, so it must bound a read, never an allocation.
@@ -46,7 +46,7 @@ export declare class RecordReader {
46
46
  f64(): number;
47
47
  /**
48
48
  * An `RkNumber` ([MS-XLSB] 2.5.122): a number packed into 32 bits. Two flag bits steal the low end
49
- * of the word `fInt` says the remaining 30 bits are a signed integer rather than the *high* 30
49
+ * of the word: `fInt` says the remaining 30 bits are a signed integer rather than the *high* 30
50
50
  * bits of a double whose low 34 bits are zero, and `fX100` says the result was scaled up by 100 to
51
51
  * keep two decimal places in the integer form. It exists because most real spreadsheet numbers are
52
52
  * small integers or two-decimal currency, and this stores them in half the bytes of a double.
@@ -55,7 +55,7 @@ export declare class RecordReader {
55
55
  /** An `XLWideString` ([MS-XLSB] 2.5.169): a 4-byte character count then that many UTF-16LE units. */
56
56
  wideString(): string;
57
57
  /**
58
- * A UTF-16 string whose character count is 16-bit rather than 32-bit the form used *inside* a
58
+ * A UTF-16 string whose character count is 16-bit rather than 32-bit: the form used *inside* a
59
59
  * formula token stream (`PtgStr`, and the string elements of an array constant), where a 4-byte
60
60
  * count on every literal would be pure overhead.
61
61
  */
@@ -64,7 +64,7 @@ export declare class RecordReader {
64
64
  nullableWideString(): string | undefined;
65
65
  /**
66
66
  * A `RichStr` ([MS-XLSB] 2.5.124): a string that may carry per-run formatting and phonetic guides.
67
- * Only the text is returned the run and phonetic tails are left unread, which is safe because the
67
+ * Only the text is returned; the run and phonetic tails are left unread, which is safe because the
68
68
  * record's framing (not this cursor) bounds where the payload ends.
69
69
  */
70
70
  richString(): string;
@@ -77,7 +77,7 @@ export declare class RecordReader {
77
77
  *
78
78
  * The four encodings are mutually exclusive and the type tag picks which of the payload's fields
79
79
  * carry meaning; the rest are explicitly undefined. An *automatic* colour (type 0) names nothing at
80
- * all, and reads back as no colour the same absence the XML reader produces for `<color auto="1"/>`,
80
+ * all, and reads back as no colour: the same absence the XML reader produces for `<color auto="1"/>`,
81
81
  * so a cell whose font colour was never set does not gain one on read.
82
82
  */
83
83
  color(): Color | undefined;
@@ -5,7 +5,7 @@ export declare function functionNameFor(index: number): string | undefined;
5
5
  /**
6
6
  * How many arguments a fixed-arity function takes, or `undefined` for one whose argument count varies.
7
7
  *
8
- * A `PtgFunc` token states only *which* function is called, never with how many arguments it is the
8
+ * A `PtgFunc` token states only *which* function is called, never with how many arguments: it is the
9
9
  * token Excel emits precisely because the count is implied by the function. So decoding `ROUND(A1,2)`
10
10
  * back from its two operands and one call token is impossible without this: the arity is the only
11
11
  * thing that says which operands on the stack belong to the call. (`PtgFuncVar` carries its own count
@@ -1,5 +1,4 @@
1
- import { assignStyleFacets } from '../../core/style.js';
2
- import { numFmtCodeFor } from '../style/xf-style.js';
1
+ import { numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
3
2
  import { RecordReader } from './primitives.js';
4
3
  import { readRecords } from './record-stream.js';
5
4
  import { BRT } from './record-types.js';
@@ -74,24 +73,7 @@ export function parseStyleTable(part) {
74
73
  break;
75
74
  }
76
75
  }
77
- const cellXfs = directXfs.map((xf) => {
78
- if (xf.xfId === undefined)
79
- return xf;
80
- const named = namedXfs[xf.xfId];
81
- return named === undefined ? xf : { ...named, ...xf };
82
- });
83
- const namedStyles = namedXfs.map((xf, index) => {
84
- const label = labels.find((entry) => entry.xfId === index);
85
- const style = {};
86
- assignStyleFacets(style, xf);
87
- if (label?.name !== undefined)
88
- style.name = label.name;
89
- if (label?.builtinId !== undefined)
90
- style.builtinId = label.builtinId;
91
- return style;
92
- });
93
- const defaultFont = fonts[0];
94
- return defaultFont === undefined ? { cellXfs, namedStyles } : { cellXfs, namedStyles, defaultFont };
76
+ return resolveStyleTable({ directXfs, namedXfs, labels, fonts });
95
77
  }
96
78
  function readXf(reader, deps, isDirect) {
97
79
  const parent = reader.u16();
@@ -5,10 +5,10 @@ export declare const XLSB_WORKBOOK_PART = "xl/workbook.bin";
5
5
  /**
6
6
  * Read an `.xlsb` (binary BIFF12) package into a {@link Workbook}.
7
7
  *
8
- * @throws {UnsupportedFormatError} if the input is not an `.xlsb` package a legacy `.xls`
8
+ * @throws {UnsupportedFormatError} if the input is not an `.xlsb` package: a legacy `.xls`
9
9
  * (`.format === 'xls'`), an XML `.xlsx` or unrecognised blob (`'unknown'`).
10
10
  * @throws {XlsbParseError} if a binary part is malformed.
11
- * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked a corrupt or
11
+ * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked: a corrupt or
12
12
  * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
13
13
  */
14
14
  export declare function readXlsb(data: Uint8Array, options?: ReadXlsxOptions): Workbook;