@shbernal/ts-xlsx 1.2.0 → 2.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 (161) hide show
  1. package/README.md +5 -0
  2. package/dist/core/address.d.ts +49 -2
  3. package/dist/core/address.js +31 -3
  4. package/dist/core/autofilter.d.ts +2 -2
  5. package/dist/core/cell.d.ts +17 -16
  6. package/dist/core/cell.js +3 -7
  7. package/dist/core/color-resolution.d.ts +7 -7
  8. package/dist/core/column.d.ts +8 -9
  9. package/dist/core/column.js +2 -4
  10. package/dist/core/comment-thread.d.ts +32 -23
  11. package/dist/core/comment-thread.js +4 -4
  12. package/dist/core/conditional-formatting-overlay.d.ts +1 -1
  13. package/dist/core/conditional-formatting.d.ts +3 -3
  14. package/dist/core/data-validation-overlay.d.ts +1 -1
  15. package/dist/core/data-validation.d.ts +3 -3
  16. package/dist/core/date.d.ts +3 -3
  17. package/dist/core/formula.d.ts +6 -6
  18. package/dist/core/grid-edits.js +3 -3
  19. package/dist/core/image.d.ts +37 -2
  20. package/dist/core/image.js +6 -0
  21. package/dist/core/internal.d.ts +9 -1
  22. package/dist/core/limits.d.ts +4 -4
  23. package/dist/core/merge.d.ts +6 -12
  24. package/dist/core/merge.js +0 -3
  25. package/dist/core/page-setup.d.ts +4 -4
  26. package/dist/core/pivot-table.d.ts +2 -4
  27. package/dist/core/pivot-table.js +5 -5
  28. package/dist/core/preserved.d.ts +3 -3
  29. package/dist/core/protection.d.ts +4 -4
  30. package/dist/core/range.d.ts +16 -14
  31. package/dist/core/range.js +5 -5
  32. package/dist/core/row-input.d.ts +2 -8
  33. package/dist/core/row-input.js +2 -2
  34. package/dist/core/row.d.ts +7 -8
  35. package/dist/core/row.js +2 -4
  36. package/dist/core/style.d.ts +14 -14
  37. package/dist/core/table-style.d.ts +9 -9
  38. package/dist/core/table-style.js +1 -1
  39. package/dist/core/table.d.ts +30 -42
  40. package/dist/core/table.js +16 -10
  41. package/dist/core/theme.d.ts +13 -43
  42. package/dist/core/theme.js +2 -148
  43. package/dist/core/value.d.ts +7 -7
  44. package/dist/core/workbook-protection.d.ts +2 -2
  45. package/dist/core/workbook-styles.d.ts +26 -0
  46. package/dist/core/workbook-styles.js +48 -0
  47. package/dist/core/workbook-theme.d.ts +25 -0
  48. package/dist/core/workbook-theme.js +49 -0
  49. package/dist/core/workbook-vba.d.ts +17 -0
  50. package/dist/core/workbook-vba.js +79 -0
  51. package/dist/core/workbook.d.ts +141 -83
  52. package/dist/core/workbook.js +74 -133
  53. package/dist/core/worksheet-comments.d.ts +9 -0
  54. package/dist/core/worksheet-comments.js +52 -0
  55. package/dist/core/worksheet-model.d.ts +4 -4
  56. package/dist/core/worksheet-model.js +1 -0
  57. package/dist/core/worksheet-pictures.d.ts +29 -0
  58. package/dist/core/worksheet-pictures.js +42 -0
  59. package/dist/core/worksheet.d.ts +94 -77
  60. package/dist/core/worksheet.js +36 -95
  61. package/dist/customui/errors.d.ts +1 -1
  62. package/dist/customui/ribbon.d.ts +3 -3
  63. package/dist/entries/core.d.ts +4 -4
  64. package/dist/entries/core.js +1 -1
  65. package/dist/entries/vba.d.ts +1 -1
  66. package/dist/entries/vba.js +1 -1
  67. package/dist/entries/xlsx.d.ts +1 -0
  68. package/dist/entries/xlsx.js +1 -0
  69. package/dist/errors.d.ts +12 -12
  70. package/dist/io/csv/read.js +1 -2
  71. package/dist/io/csv/write.d.ts +8 -3
  72. package/dist/io/csv/write.js +17 -2
  73. package/dist/io/opc/errors.d.ts +6 -6
  74. package/dist/io/opc/inflate.d.ts +1 -1
  75. package/dist/io/opc/namespaces.d.ts +1 -1
  76. package/dist/io/opc/read-opc.d.ts +19 -3
  77. package/dist/io/opc/read-opc.js +14 -8
  78. package/dist/io/opc/read-options.d.ts +2 -2
  79. package/dist/io/opc/sniff-format.d.ts +2 -2
  80. package/dist/io/opc/zip-mtime.d.ts +5 -0
  81. package/dist/io/opc/zip-mtime.js +1 -0
  82. package/dist/io/style/xf-style.d.ts +38 -6
  83. package/dist/io/style/xf-style.js +22 -0
  84. package/dist/io/xlsb/errors.d.ts +1 -1
  85. package/dist/io/xlsb/formula.d.ts +4 -4
  86. package/dist/io/xlsb/primitives.d.ts +8 -8
  87. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  88. package/dist/io/xlsb/read-styles.js +2 -20
  89. package/dist/io/xlsb/read.d.ts +2 -2
  90. package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
  91. package/dist/io/xlsx/cell-accumulator.js +111 -30
  92. package/dist/io/xlsx/cell-value.d.ts +2 -2
  93. package/dist/io/xlsx/cell-value.js +3 -3
  94. package/dist/io/xlsx/color-xml.js +11 -17
  95. package/dist/io/xlsx/comments.d.ts +5 -5
  96. package/dist/io/xlsx/comments.js +13 -6
  97. package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
  98. package/dist/io/xlsx/conditional-formatting.js +7 -16
  99. package/dist/io/xlsx/data-validation.d.ts +2 -2
  100. package/dist/io/xlsx/data-validation.js +1 -1
  101. package/dist/io/xlsx/edit-vba.js +2 -1
  102. package/dist/io/xlsx/errors.d.ts +3 -3
  103. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  104. package/dist/io/xlsx/hyperlinks.js +5 -5
  105. package/dist/io/xlsx/images.d.ts +1 -1
  106. package/dist/io/xlsx/images.js +8 -8
  107. package/dist/io/xlsx/namespaces.d.ts +2 -2
  108. package/dist/io/xlsx/package-plan.js +5 -2
  109. package/dist/io/xlsx/read-pivot.js +2 -5
  110. package/dist/io/xlsx/read-rows.d.ts +13 -13
  111. package/dist/io/xlsx/read-rows.js +19 -55
  112. package/dist/io/xlsx/read-shared-strings.js +7 -5
  113. package/dist/io/xlsx/read-styles.d.ts +3 -3
  114. package/dist/io/xlsx/read-styles.js +40 -65
  115. package/dist/io/xlsx/read-worksheet.js +67 -115
  116. package/dist/io/xlsx/read.d.ts +3 -5
  117. package/dist/io/xlsx/read.js +94 -71
  118. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  119. package/dist/io/xlsx/rich-runs.js +1 -1
  120. package/dist/io/xlsx/rich-text.d.ts +1 -1
  121. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  122. package/dist/io/xlsx/sheet-properties.js +5 -4
  123. package/dist/io/xlsx/styles.d.ts +8 -9
  124. package/dist/io/xlsx/styles.js +4 -3
  125. package/dist/io/xlsx/tables.d.ts +2 -2
  126. package/dist/io/xlsx/tables.js +8 -13
  127. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  128. package/dist/io/xlsx/theme-xml.js +142 -0
  129. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  130. package/dist/io/xlsx/threaded-comments.js +9 -21
  131. package/dist/io/xlsx/workbook-xml.d.ts +1 -1
  132. package/dist/io/xlsx/workbook-xml.js +6 -2
  133. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  134. package/dist/io/xlsx/worksheet-xml.js +7 -12
  135. package/dist/io/xlsx/write-stream.d.ts +15 -15
  136. package/dist/io/xlsx/write-stream.js +5 -3
  137. package/dist/io/xlsx/write.d.ts +12 -7
  138. package/dist/io/xlsx/write.js +10 -6
  139. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  140. package/dist/vba/bytes.d.ts +12 -0
  141. package/dist/vba/bytes.js +40 -0
  142. package/dist/vba/cfb-writer.d.ts +1 -1
  143. package/dist/vba/cfb.d.ts +1 -1
  144. package/dist/vba/cfb.js +15 -51
  145. package/dist/vba/codepage.d.ts +2 -2
  146. package/dist/vba/errors.d.ts +2 -2
  147. package/dist/vba/index.d.ts +2 -2
  148. package/dist/vba/index.js +2 -2
  149. package/dist/vba/ms-ovba.d.ts +1 -1
  150. package/dist/vba/ms-ovba.js +1 -3
  151. package/dist/vba/project-editor.d.ts +9 -9
  152. package/dist/vba/project-editor.js +2 -11
  153. package/dist/vba/project.d.ts +5 -5
  154. package/dist/vba/project.js +1 -10
  155. package/dist/vba/vba-encoding.d.ts +1 -1
  156. package/dist/xml/errors.d.ts +1 -1
  157. package/dist/xml/xml-read.d.ts +38 -9
  158. package/dist/xml/xml-read.js +23 -0
  159. package/dist/xml/xml.d.ts +31 -9
  160. package/dist/xml/xml.js +23 -3
  161. package/package.json +41 -35
@@ -20,12 +20,12 @@ export interface CommentCell {
20
20
  * in `threads`. A comment anchors to its cell regardless of the cell's value, so a note (or a thread) on
21
21
  * an otherwise-empty cell is collected too.
22
22
  *
23
- * `threads` is the conversations the *package* will carry, not simply the ones the sheet holds — the
23
+ * `threads` is the conversations the *package* will carry, not simply the ones the sheet holds. The
24
24
  * caller decides, because a fallback beside a thread whose `threadedComment` part is missing is worse
25
25
  * than no fallback at all: verified against desktop Excel, such a comment shows as neither a thread nor
26
26
  * a note, so the text disappears entirely.
27
27
  *
28
- * Ordered by cell, row-major, the way Excel writes the list so a fallback lands interleaved among the
28
+ * Ordered by cell, row-major, the way Excel writes the list, so a fallback lands interleaved among the
29
29
  * notes rather than appended after them, and the VML shapes follow the same order.
30
30
  */
31
31
  export declare function collectComments(sheet: Worksheet, threads: readonly CommentThread[]): CommentCell[];
@@ -35,13 +35,13 @@ export declare function collectComments(sheet: Worksheet, threads: readonly Comm
35
35
  * Authors are laid out the way Excel lays them out: one synthetic `tc={headId}` entry per threaded
36
36
  * conversation first, then a single anonymous author shared by every note (the model carries no note
37
37
  * author). Each comment points at its own author by index, and a fallback additionally carries the
38
- * `xr:uid` naming its thread the pair that keeps Excel treating the cell as threaded.
38
+ * `xr:uid` naming its thread: the pair that keeps Excel treating the cell as threaded.
39
39
  */
40
40
  export declare function commentsXml(comments: readonly CommentCell[]): string;
41
41
  /** The `xl/drawings/vmlDrawing{n}.vml` companion: one hidden text-box shape per comment, in the same
42
42
  * order as the comments part. Anchor coordinates place the box a couple of cells down-and-right of its
43
43
  * owner; Excel refines them on open, so the values are a sensible starting geometry rather than a
44
- * pixel-exact layout. A thread's fallback shape is `ObjectType="Note"` like any other Excel draws the
44
+ * pixel-exact layout. A thread's fallback shape is `ObjectType="Note"` like any other: Excel draws the
45
45
  * threaded-comment card itself and only needs the shape to exist. */
46
46
  export declare function vmlDrawingXml(comments: readonly CommentCell[]): string;
47
47
  /** One `<comment>` read back from a comments part. */
@@ -63,7 +63,7 @@ export declare function parseComments(xml: string): Map<string, ParsedComment>;
63
63
  * Apply a parsed comments part onto a sheet's cells as notes, addressing each by its A1 reference.
64
64
  *
65
65
  * A thread's legacy fallback is not a note and does not become one: its text is boilerplate wrapping a
66
- * copy of the conversation, so surfacing it as `cell.note` hands the caller garbage and on write it
66
+ * copy of the conversation, so surfacing it as `cell.note` hands the caller garbage, and on write it
67
67
  * would be re-emitted as a plain note, destroying the `tc=`/`xr:uid` binding and leaving Excel unable to
68
68
  * see the thread at all.
69
69
  *
@@ -1,6 +1,6 @@
1
- import { decodeAddress } from '../../core/address.js';
1
+ import { tryDecodeCellRef } from '../../core/address.js';
2
+ import { decodeSpreadsheetText, localName, numInteger, parseXml } from '../../xml/xml-read.js';
2
3
  import { escapeAttr, escapeText, textElement, XML_DECLARATION } from '../../xml/xml.js';
3
- import { localName, parseXml } from '../../xml/xml-read.js';
4
4
  import { MARKUP_COMPATIBILITY_NS, REVISION_NS, SPREADSHEETML_NS } from './namespaces.js';
5
5
  export function collectComments(sheet, threads) {
6
6
  const fallbacks = threadFallbacks(threads);
@@ -19,10 +19,16 @@ function threadFallbacks(threads) {
19
19
  const fallbacks = [];
20
20
  for (const thread of threads) {
21
21
  const head = thread.comments[0];
22
- const { col, row } = decodeAddress(thread.ref);
23
- if (head === undefined || col === undefined || row === undefined)
22
+ const cell = tryDecodeCellRef(thread.ref);
23
+ if (head === undefined || cell === undefined)
24
24
  continue;
25
- fallbacks.push({ ref: thread.ref, row, col, text: fallbackText(thread), threadId: head.id });
25
+ fallbacks.push({
26
+ ref: thread.ref,
27
+ row: cell.row,
28
+ col: cell.col,
29
+ text: fallbackText(thread),
30
+ threadId: head.id,
31
+ });
26
32
  }
27
33
  return fallbacks;
28
34
  }
@@ -125,10 +131,11 @@ export function parseComments(xml) {
125
131
  buffer = '';
126
132
  }
127
133
  else if (local === 'text') {
134
+ buffer = decodeSpreadsheetText(buffer);
128
135
  capture = undefined;
129
136
  }
130
137
  else if (local === 'comment' && currentRef !== undefined) {
131
- const threadId = threadIdOf(authors[Number(currentAuthorId)]);
138
+ const threadId = threadIdOf(authors[numInteger(currentAuthorId, 0) ?? -1]);
132
139
  comments.set(currentRef, {
133
140
  text: buffer,
134
141
  ...(threadId !== undefined ? { threadId } : {}),
@@ -17,15 +17,15 @@ export declare function conditionalFormattingsExtXml(formattings: readonly Condi
17
17
  /**
18
18
  * Parse a worksheet's conditional formatting into the model. The classic `<conditionalFormatting>`
19
19
  * blocks supply every rule; the x14 extension (`<x14:conditionalFormatting>` inside `<extLst>`) is
20
- * read only to enrich a classic data bar with the facets the classic element cannot carry the
21
- * gradient flag and the negative-fill and axis colours matched by the shared id the two ends link
20
+ * read only to enrich a classic data bar with the facets the classic element cannot carry (the
21
+ * gradient flag and the negative-fill and axis colours) matched by the shared id the two ends link
22
22
  * on. An extension rule with no classic counterpart (a rule that lives only in x14) is ignored, so it
23
23
  * is never half-read into a broken classic rule.
24
24
  */
25
25
  export declare function parseConditionalFormattings(xml: string): ConditionalFormatting[];
26
26
  /**
27
27
  * Extract the differential-style (`<dxf>`) fragments from styles.xml, each verbatim. Preserving the
28
- * raw XML rather than reparsing and re-serialising is what keeps a foreign dxf's number format a
28
+ * raw XML, rather than reparsing and re-serialising, is what keeps a foreign dxf's number format a
29
29
  * real format code on re-write instead of a coerced `"[object Object]"`, and keeps every conditional
30
30
  * formatting's `dxfId` index pointing at the same style it did in the source file.
31
31
  */
@@ -1,5 +1,5 @@
1
+ import { boolPresent, boolStrict, coerceNumericLiteral, localName, numFinite, numInteger, parseXml, } from '../../xml/xml-read.js';
1
2
  import { boolAttr, escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
2
- import { boolStrict, coerceNumericLiteral, localName, parseXml } from '../../xml/xml-read.js';
3
3
  import { colorAttrs, parseColor } from './color-xml.js';
4
4
  import { CF_EXT_URI, DATABAR_LINK_EXT_URI, XM_NS } from './namespaces.js';
5
5
  import { x14Ext } from './x14-ext.js';
@@ -176,7 +176,7 @@ export function parseConditionalFormattings(xml) {
176
176
  x14ExtId = attrs.id;
177
177
  }
178
178
  else if (x14Ext !== undefined && ln === 'dataBar') {
179
- x14Ext.gradient = attrs.gradient !== '0';
179
+ x14Ext.gradient = boolPresent(attrs.gradient);
180
180
  }
181
181
  else if (x14Ext !== undefined && ln === 'negativeFillColor') {
182
182
  x14Ext.negativeFillColor = parseColor(attrs);
@@ -288,16 +288,16 @@ export function parseDxfs(stylesXml) {
288
288
  function newDraft(attrs) {
289
289
  return {
290
290
  type: attrs.type ?? '',
291
- priority: parseFiniteAttr(attrs.priority),
291
+ priority: numFinite(attrs.priority),
292
292
  stopIfTrue: boolStrict(attrs.stopIfTrue),
293
293
  operator: attrs.operator,
294
294
  text: attrs.text,
295
295
  timePeriod: attrs.timePeriod,
296
- rank: parseFiniteAttr(attrs.rank),
297
- stdDev: parseFiniteAttr(attrs.stdDev),
296
+ rank: numFinite(attrs.rank),
297
+ stdDev: numFinite(attrs.stdDev),
298
298
  percent: boolStrict(attrs.percent),
299
299
  bottom: boolStrict(attrs.bottom),
300
- aboveAverage: attrs.aboveAverage === undefined ? undefined : attrs.aboveAverage !== '0',
300
+ aboveAverage: attrs.aboveAverage === undefined ? undefined : boolPresent(attrs.aboveAverage),
301
301
  equalAverage: boolStrict(attrs.equalAverage),
302
302
  dxfId: parseIndexAttr(attrs.dxfId),
303
303
  iconSet: undefined,
@@ -308,17 +308,8 @@ function newDraft(attrs) {
308
308
  x14Id: undefined,
309
309
  };
310
310
  }
311
- function parseFiniteAttr(value) {
312
- if (value === undefined)
313
- return undefined;
314
- const n = Number(value);
315
- return Number.isFinite(n) ? n : undefined;
316
- }
317
311
  function parseIndexAttr(value) {
318
- if (value === undefined)
319
- return undefined;
320
- const n = Number(value);
321
- return Number.isInteger(n) && n >= 0 ? value : undefined;
312
+ return numInteger(value, 0) === undefined ? undefined : value;
322
313
  }
323
314
  function finalizeRule(draft) {
324
315
  const rule = { type: draft.type };
@@ -1,12 +1,12 @@
1
1
  import type { DataValidationEntry } from '../../core/data-validation.ts';
2
2
  import type { Worksheet } from '../../core/worksheet.ts';
3
3
  /** The standard `<dataValidations>` element for the rules stored in the legacy form, or '' when the
4
- * sheet has none of them so a sheet with only extended (or no) validations stays byte-clean here.
4
+ * sheet has none of them, so a sheet with only extended (or no) validations stays byte-clean here.
5
5
  * The extended rules are emitted separately by {@link dataValidationsExtXml}. */
6
6
  export declare function dataValidationsXml(entries: readonly DataValidationEntry[]): string;
7
7
  /** The `<ext>` carrying the extended (`<x14:dataValidation>`) rules, or '' when the sheet declares
8
8
  * none. Emitted bare (no `<extLst>` wrapper) so the worksheet serialiser can gather it into a single
9
- * `<extLst>` beside the conditional-formatting extension a worksheet may carry at most one. */
9
+ * `<extLst>` beside the conditional-formatting extension: a worksheet may carry at most one. */
10
10
  export declare function dataValidationsExtXml(entries: readonly DataValidationEntry[]): string;
11
11
  /** Parse every standard `<dataValidation>` out of a worksheet part into range-bound rules. */
12
12
  export declare function parseDataValidations(xml: string): DataValidationEntry[];
@@ -1,5 +1,5 @@
1
- import { escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
2
1
  import { boolStrict, coerceNumericLiteral, localName, parseXml } from '../../xml/xml-read.js';
2
+ import { escapeAttr, escapeText, stripFormulaEquals } from '../../xml/xml.js';
3
3
  import { DATA_VALIDATION_EXT_URI, XM_NS } from './namespaces.js';
4
4
  import { x14Ext } from './x14-ext.js';
5
5
  const TYPED = new Set(['whole', 'decimal', 'date', 'time', 'textLength']);
@@ -3,6 +3,7 @@ import { VbaAuthorError } from '../../vba/errors.js';
3
3
  import { addVbaReference, removeVbaModule, } from '../../vba/project-editor.js';
4
4
  import { relsPathFor } from '../opc/part-paths.js';
5
5
  import { parseRelationshipRecords, relationshipTargetByType, resolveRelativePart, resolveWorkbookPart, } from '../opc/read-opc.js';
6
+ import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
6
7
  const OFFICE_DOCUMENT_REL = 'officeDocument';
7
8
  const VBA_PROJECT_REL = 'vbaProject';
8
9
  const VBA_SIGNATURE_REL_INFIX = 'vbaProjectSignature';
@@ -21,7 +22,7 @@ function applyToVbaProjectPart(xlsx, apply) {
21
22
  }
22
23
  files[binPath] = apply(bin);
23
24
  dropStaleSignature(files, binPath);
24
- return zipSync(files);
25
+ return zipSync(files, { mtime: FIXED_ENTRY_MTIME });
25
26
  }
26
27
  function locateVbaProjectPart(files) {
27
28
  const rootRels = textPart(files, '_rels/.rels');
@@ -1,14 +1,14 @@
1
1
  import { XlsxError } from '../../errors.ts';
2
2
  /**
3
3
  * Thrown when an `.xlsx` package's XML content is well-formed but does not describe a workbook this
4
- * library can act on a `xl/workbook.xml` that declares no worksheets, say.
4
+ * library can act on: a `xl/workbook.xml` that declares no worksheets, say.
5
5
  *
6
6
  * It sits between two neighbours. {@link XmlParseError} reports that the *markup* did not parse;
7
7
  * {@link UnsupportedFormatError} reports that the *container* is not one we read at all. This one
8
8
  * fires when both of those were fine and the document itself is nonetheless not a workbook.
9
9
  *
10
- * The reader is deliberately lenient about content it does not recognise an unknown element is
11
- * skipped, not fatal so this is rare by design, and reaching it means something a workbook cannot
10
+ * The reader is deliberately lenient about content it does not recognise (an unknown element is
11
+ * skipped, not fatal) so this is rare by design, and reaching it means something a workbook cannot
12
12
  * do without being corrupt.
13
13
  */
14
14
  export declare class XlsxParseError extends XlsxError {
@@ -37,6 +37,8 @@ interface ParsedHyperlink {
37
37
  /** Parse every `<hyperlink>` element out of a worksheet part. */
38
38
  export declare function parseSheetHyperlinks(xml: string): ParsedHyperlink[];
39
39
  /** Fold parsed hyperlinks onto a sheet's cells, wrapping each cell's existing value (its visible
40
- * label) into a {@link HyperlinkValue}. `rels` maps a relationship id to its target URL. */
41
- export declare function applyHyperlinks(sheet: Worksheet, links: readonly ParsedHyperlink[], rels: Map<string, string>): void;
40
+ * label) into a {@link HyperlinkValue}. `targetOf` resolves a relationship id to its raw Target: a
41
+ * URL for the external links hyperlinks almost always are, so it must stay unresolved against the
42
+ * package rather than being handed over as a part path. */
43
+ export declare function applyHyperlinks(sheet: Worksheet, links: readonly ParsedHyperlink[], targetOf: (relId: string) => string | undefined): void;
42
44
  export {};
@@ -1,7 +1,7 @@
1
1
  import { decodeRange } from '../../core/address.js';
2
2
  import { isHyperlinkValue, isRichTextValue } from '../../core/value.js';
3
- import { escapeAttr } from '../../xml/xml.js';
4
3
  import { localName, parseXml } from '../../xml/xml-read.js';
4
+ import { escapeAttr } from '../../xml/xml.js';
5
5
  export function collectHyperlinks(sheet) {
6
6
  const links = [];
7
7
  for (const { cells } of sheet.rows()) {
@@ -59,9 +59,9 @@ export function parseSheetHyperlinks(xml) {
59
59
  });
60
60
  return links;
61
61
  }
62
- export function applyHyperlinks(sheet, links, rels) {
62
+ export function applyHyperlinks(sheet, links, targetOf) {
63
63
  for (const link of links) {
64
- const target = resolveTarget(link, rels);
64
+ const target = resolveTarget(link, targetOf);
65
65
  if (target === undefined)
66
66
  continue;
67
67
  const decoded = decodeRefSafe(link.ref);
@@ -88,9 +88,9 @@ function decodeRefSafe(ref) {
88
88
  return undefined;
89
89
  }
90
90
  }
91
- function resolveTarget(link, rels) {
91
+ function resolveTarget(link, targetOf) {
92
92
  if (link.rid !== undefined) {
93
- const base = rels.get(link.rid);
93
+ const base = targetOf(link.rid);
94
94
  if (base === undefined)
95
95
  return undefined;
96
96
  return link.location !== undefined ? `${base}#${link.location}` : base;
@@ -27,7 +27,7 @@ export interface ParsedImageAnchor {
27
27
  * Anchors that are not pictures (a chart, a shape) carry no `<a:blip r:embed>` and are skipped, so a
28
28
  * mixed drawing yields only its images. */
29
29
  export declare function parseDrawing(xml: string): ParsedImageAnchor[];
30
- /** Whether a drawing part holds anchor content beyond plain pictures a chart, shape, connector, or
30
+ /** Whether a drawing part holds anchor content beyond plain pictures: a chart, shape, connector, or
31
31
  * group. Excel packs every one of a sheet's anchors into a single drawing part, so a sheet with both a
32
32
  * picture and a chart yields a mixed drawing; modeling only its pictures and re-serialising from them
33
33
  * would drop the chart. The reader uses this to fall back to whole-drawing byte-preservation instead. */
@@ -1,6 +1,6 @@
1
1
  import { isOneCellAnchor, } from '../../core/image.js';
2
+ import { localName, numFinite, parseXml } from '../../xml/xml-read.js';
2
3
  import { XML_DECLARATION } from '../../xml/xml.js';
3
- import { localName, parseXml } from '../../xml/xml-read.js';
4
4
  import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
5
5
  import { relationship, relationshipsPart } from '../opc/rels.js';
6
6
  import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
@@ -99,8 +99,8 @@ export function parseDrawing(xml) {
99
99
  picDepth++;
100
100
  }
101
101
  else if (local === 'xfrm' && picDepth > 0) {
102
- const rot = Number(attrs.rot);
103
- if (Number.isFinite(rot) && rot !== 0)
102
+ const rot = numFinite(attrs.rot);
103
+ if (rot !== undefined && rot !== 0)
104
104
  rotation = rot;
105
105
  }
106
106
  else if (local === 'from') {
@@ -110,9 +110,9 @@ export function parseDrawing(xml) {
110
110
  target = to;
111
111
  }
112
112
  else if (local === 'ext' && picDepth === 0) {
113
- const cx = Number(attrs.cx);
114
- const cy = Number(attrs.cy);
115
- if (Number.isFinite(cx) && Number.isFinite(cy))
113
+ const cx = numFinite(attrs.cx, 0);
114
+ const cy = numFinite(attrs.cy, 0);
115
+ if (cx !== undefined && cy !== undefined)
116
116
  ext = { cx, cy };
117
117
  }
118
118
  else if (local === 'blip') {
@@ -132,8 +132,8 @@ export function parseDrawing(xml) {
132
132
  onClose(name) {
133
133
  const local = localName(name);
134
134
  if (target !== null && coord === local && COORDINATES.has(local)) {
135
- const value = Number(text);
136
- if (Number.isFinite(value))
135
+ const value = numFinite(text);
136
+ if (value !== undefined)
137
137
  setCoordinate(target, local, value);
138
138
  coord = '';
139
139
  }
@@ -10,7 +10,7 @@
10
10
  * container rather than the spreadsheet inside it, and live in `../opc/namespaces.ts`.
11
11
  */
12
12
  /**
13
- * SpreadsheetML main namespace the default `xmlns` of the workbook,
13
+ * SpreadsheetML main namespace: the default `xmlns` of the workbook,
14
14
  * worksheet, styles, sharedStrings, comments, table and pivot parts.
15
15
  */
16
16
  export declare const SPREADSHEETML_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
@@ -25,7 +25,7 @@ export declare const MARKUP_COMPATIBILITY_NS = "http://schemas.openxmlformats.or
25
25
  */
26
26
  export declare const REVISION_NS = "http://schemas.microsoft.com/office/spreadsheetml/2014/revision";
27
27
  /**
28
- * The 2018 threaded-comments namespace, shared by both parts of the feature a sheet's
28
+ * The 2018 threaded-comments namespace, shared by both parts of the feature: a sheet's
29
29
  * `threadedComment{n}.xml` and the workbook's `person.xml`. Note the plural `threadedcomments`, all
30
30
  * lower-case: Excel matches the URI exactly and reads neither part under any other spelling.
31
31
  */
@@ -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
3
  import { preservedRelsXml } from '../opc/rels.js';
4
+ import { applyThemeOverrides } from './theme-xml.js';
4
5
  export class SheetRelIds {
5
6
  #next = 1;
6
7
  next() {
@@ -75,10 +76,12 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
75
76
  ? []
76
77
  : [{ id: rel.id, type: rel.type, target: relativePartPath(newPath, target) }];
77
78
  });
78
- const authoredTheme = newPath === THEME_PART_PATH ? workbook.authoredThemeXml() : undefined;
79
+ const overrides = newPath === THEME_PART_PATH ? workbook.themeOverrides : undefined;
79
80
  emitted.set(newPath, {
80
81
  path: newPath,
81
- bytes: authoredTheme === undefined ? part.bytes : new TextEncoder().encode(authoredTheme),
82
+ bytes: overrides === undefined
83
+ ? part.bytes
84
+ : new TextEncoder().encode(applyThemeOverrides(new TextDecoder().decode(part.bytes), overrides)),
82
85
  contentType: part.contentType,
83
86
  relsPath: rels.length === 0 ? null : relsPathFor(newPath),
84
87
  relsXml: rels.length === 0 ? null : preservedRelsXml(rels),
@@ -1,5 +1,5 @@
1
1
  import { pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
2
- import { localName, parseXml } from '../../xml/xml-read.js';
2
+ import { localName, numInteger, parseXml } from '../../xml/xml-read.js';
3
3
  export function parsePivotTable(tableXml, cacheXml) {
4
4
  const { fields, source } = parsePivotCacheDefinition(cacheXml);
5
5
  const def = parsePivotTableDefinition(tableXml);
@@ -97,8 +97,5 @@ function sourceKind(type) {
97
97
  return SOURCE_KINDS.has(type) ? type : 'unknown';
98
98
  }
99
99
  function toIndex(value) {
100
- if (value === undefined)
101
- return -1;
102
- const index = Number(value);
103
- return Number.isInteger(index) && index >= 0 ? index : -1;
100
+ return numInteger(value, 0) ?? -1;
104
101
  }
@@ -8,7 +8,7 @@ export interface ReadSheetRowsOptions extends ReadXlsxOptions {
8
8
  readonly sheet?: string | number;
9
9
  }
10
10
  /**
11
- * The resolved style facets of a streamed cell its own `<c s>` cell format, flattened exactly as
11
+ * The resolved style facets of a streamed cell: its own `<c s>` cell format, flattened exactly as
12
12
  * the buffered reader resolves it. Present only when the cell carries a format; a consumer can copy
13
13
  * these straight onto a writer cell to preserve its look through a streaming read→write.
14
14
  */
@@ -19,7 +19,7 @@ export interface StreamedCell {
19
19
  readonly col: number;
20
20
  /** Canonical A1 address (`"B3"`). */
21
21
  readonly address: string;
22
- /** The decoded value identical to what `readXlsx` would produce for the same cell. */
22
+ /** The decoded value, identical to what `readXlsx` would produce for the same cell. */
23
23
  readonly value: CellValue;
24
24
  /** The cell's resolved style facets, or absent when the cell carries no format of its own. */
25
25
  readonly style?: StreamedCellStyle;
@@ -39,14 +39,14 @@ export interface StreamedRow {
39
39
  *
40
40
  * The two summaries are resolved lazily: reading either accessor drives a full scan of the sheet if
41
41
  * its rows have not already been consumed, so their order relative to `rows()` never matters. (When
42
- * rows *are* consumed first the streaming idiom the accessors reuse that pass and re-scan
42
+ * rows *are* consumed first, the streaming idiom, the accessors reuse that pass and re-scan
43
43
  * nothing.)
44
44
  */
45
45
  export interface StreamedSheet {
46
- /** The worksheet's declared name, joined from the workbook part never a positional placeholder. */
46
+ /** The worksheet's declared name, joined from the workbook part. Never a positional placeholder. */
47
47
  readonly name: string;
48
48
  /** Stream this sheet's rows, one at a time, in sheet order. */
49
- rows(): Generator<StreamedRow>;
49
+ rows(): Generator<StreamedRow, void, undefined>;
50
50
  /** 1-based indices of columns the sheet declares hidden, ascending. */
51
51
  readonly hiddenColumns: readonly number[];
52
52
  /** The sheet's merged ranges, as canonical A1 range strings, in declaration order. */
@@ -55,31 +55,31 @@ export interface StreamedSheet {
55
55
  /**
56
56
  * Stream a worksheet's rows from an `.xlsx` package, yielding each in sheet order without building
57
57
  * the workbook model. Only rows the sheet actually declares are yielded, and within a row only its
58
- * non-empty cells a blank or style-only cell contributes nothing, matching the intent of a data
58
+ * non-empty cells: a blank or style-only cell contributes nothing, matching the intent of a data
59
59
  * read.
60
60
  *
61
61
  * @param data The raw `.xlsx` bytes.
62
62
  * @param options Sheet selector and the inflate bound (see {@link ReadSheetRowsOptions}).
63
63
  * @throws {UnsupportedFormatError} if the input is not a readable `.xlsx` package (a legacy `.xls`, a
64
- * binary `.xlsb`, or an unrecognised/non-ZIP blob branch on `.format`).
65
- * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked a corrupt or
64
+ * binary `.xlsb`, or an unrecognised/non-ZIP blob; branch on `.format`).
65
+ * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked: a corrupt or
66
66
  * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
67
67
  * @throws {XlsxParseError} if the package's workbook part declares no worksheets.
68
68
  * @throws {RangeError} / {@link AuthoringError} if `options.sheet` selects a position, or a name,
69
69
  * that no worksheet has.
70
70
  */
71
- export declare function readSheetRows(data: Uint8Array, options?: ReadSheetRowsOptions): Generator<StreamedRow>;
71
+ export declare function readSheetRows(data: Uint8Array, options?: ReadSheetRowsOptions): Generator<StreamedRow, void, undefined>;
72
72
  /**
73
73
  * Stream every worksheet of an `.xlsx` package in workbook order, without building the workbook
74
74
  * model. Each yielded {@link StreamedSheet} carries the declared sheet name and lets the caller
75
- * stream that sheet's rows and read its hidden-column and merge summaries the streaming analogue
75
+ * stream that sheet's rows and read its hidden-column and merge summaries: the streaming analogue
76
76
  * of walking `readXlsx(data).worksheets`.
77
77
  *
78
78
  * @param data The raw `.xlsx` bytes.
79
79
  * @param options The inflate bound (see {@link ReadXlsxOptions}).
80
80
  * @throws {UnsupportedFormatError} if the input is not a readable `.xlsx` package (a legacy `.xls`, a
81
- * binary `.xlsb`, or an unrecognised/non-ZIP blob branch on `.format`).
82
- * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked a corrupt or
81
+ * binary `.xlsb`, or an unrecognised/non-ZIP blob; branch on `.format`).
82
+ * @throws {PackageReadError} if the input is a ZIP that cannot be unpacked: a corrupt or
83
83
  * truncated archive, or one exceeding the inflate bound (a probable zip bomb).
84
84
  */
85
- export declare function readWorkbookStream(data: Uint8Array, options?: ReadXlsxOptions): Generator<StreamedSheet>;
85
+ export declare function readWorkbookStream(data: Uint8Array, options?: ReadXlsxOptions): Generator<StreamedSheet, void, undefined>;
@@ -1,12 +1,12 @@
1
- import { MAX_COLUMN } from '../../core/address.js';
1
+ import { MAX_COLUMN, MAX_ROW } from '../../core/address.js';
2
2
  import { AuthoringError } from '../../errors.js';
3
- import { boolStrict, closeEmptyElements, localName, xmlEvents } from '../../xml/xml-read.js';
3
+ import { boolStrict, closeEmptyElements, localName, numInteger, xmlEvents, } from '../../xml/xml-read.js';
4
4
  import { packageAccessors } from '../opc/read-opc.js';
5
5
  import { inflateSpreadsheetPackage, unsupportedWorkbookPart } from '../opc/sniff-format.js';
6
6
  import { CellAccumulator } from './cell-accumulator.js';
7
7
  import { XlsxParseError } from './errors.js';
8
- import { DEFAULT_MAX_UNCOMPRESSED, parseRelationships, parseStyleTable, parseWorkbookSheets, resolveWorkbookPart, } from './read.js';
9
8
  import { parseSharedStrings } from './read-shared-strings.js';
9
+ import { DEFAULT_MAX_UNCOMPRESSED, parseRelationships, parseStyleTable, parseWorkbookSheets, resolveWorkbookPart, } from './read.js';
10
10
  export function* readSheetRows(data, options = {}) {
11
11
  const pkg = openPackage(data, options.maxUncompressedBytes);
12
12
  const chosen = pickSheet(pkg.sheets, options.sheet);
@@ -102,13 +102,11 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
102
102
  let rowNumber = 0;
103
103
  let lastRow = 0;
104
104
  let rowHidden = false;
105
+ let rowInGrid = true;
105
106
  let cells = [];
106
- const cell = new CellAccumulator();
107
- let inInlineString = false;
108
- let capture = false;
109
- let text = '';
107
+ const cell = new CellAccumulator({ richRuns: false });
110
108
  const finalizeCell = () => {
111
- if (cell.ref === '' || cell.col < 0)
109
+ if (cell.ref === '' || cell.col < 0 || !rowInGrid)
112
110
  return;
113
111
  const style = cell.styleIndex >= 0 ? xfStyles[cell.styleIndex] : undefined;
114
112
  const value = cell.decode(sharedStrings, style);
@@ -119,19 +117,18 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
119
117
  };
120
118
  for (const event of closeEmptyElements(xmlEvents(xml), CELL_EMPTY_CLOSE)) {
121
119
  if (event.kind === 'text') {
122
- if (capture)
123
- text += event.text;
120
+ cell.appendChunk(event.text);
124
121
  continue;
125
122
  }
126
123
  if (event.kind === 'open') {
127
124
  const local = localName(event.name);
128
- text = '';
129
- capture = false;
125
+ if (cell.openElement(local, event.attrs, event.selfClosing))
126
+ continue;
130
127
  switch (local) {
131
128
  case 'row': {
132
- const declared = Number(event.attrs.r);
133
- rowNumber = Number.isInteger(declared) && declared >= 1 ? declared : lastRow + 1;
129
+ rowNumber = numInteger(event.attrs.r, 1) ?? lastRow + 1;
134
130
  lastRow = rowNumber;
131
+ rowInGrid = rowNumber <= MAX_ROW;
135
132
  rowHidden = boolStrict(event.attrs.hidden);
136
133
  cells = [];
137
134
  break;
@@ -143,60 +140,27 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
143
140
  if (event.attrs.ref !== undefined)
144
141
  merges.push(event.attrs.ref);
145
142
  break;
146
- case 'c':
147
- cell.beginCell(event.attrs);
148
- break;
149
- case 'is':
150
- inInlineString = true;
151
- cell.beginInlineString();
152
- break;
153
- case 'f':
154
- capture = true;
155
- cell.beginFormula(event.attrs, event.selfClosing);
156
- break;
157
- case 'v':
158
- case 't':
159
- capture = true;
160
- break;
161
143
  default:
162
144
  break;
163
145
  }
164
- if (event.selfClosing && (local === 'f' || local === 'v'))
165
- capture = false;
166
146
  continue;
167
147
  }
168
148
  const local = localName(event.name);
169
- switch (local) {
170
- case 'f':
171
- cell.setFormula(text);
172
- break;
173
- case 'v':
174
- cell.setValue(text);
175
- break;
176
- case 't':
177
- cell.appendText(text, inInlineString);
178
- break;
179
- case 'is':
180
- inInlineString = false;
181
- break;
182
- case 'c':
183
- finalizeCell();
184
- break;
185
- case 'row':
149
+ const claimed = cell.closeElement(local);
150
+ if (claimed === 'cell')
151
+ finalizeCell();
152
+ else if (claimed === 'other' && local === 'row') {
153
+ if (rowInGrid)
186
154
  yield { number: rowNumber, hidden: rowHidden, cells };
187
- break;
188
- default:
189
- break;
190
155
  }
191
- capture = false;
192
156
  }
193
157
  }
194
158
  function collectHiddenColumn(attrs, hiddenColumns) {
195
159
  if (attrs.hidden !== '1' && attrs.hidden !== 'true')
196
160
  return;
197
- const min = Number(attrs.min);
198
- const max = Number(attrs.max);
199
- if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1)
161
+ const min = numInteger(attrs.min, 1);
162
+ const max = numInteger(attrs.max, 1);
163
+ if (min === undefined || max === undefined)
200
164
  return;
201
165
  const last = Math.min(max, MAX_COLUMN);
202
166
  for (let index = min; index <= last; index++)
@@ -1,4 +1,4 @@
1
- import { localName, parseXml } from '../../xml/xml-read.js';
1
+ import { decodeSpreadsheetText, localName, parseXml } from '../../xml/xml-read.js';
2
2
  import { RunAccumulator } from './rich-runs.js';
3
3
  export function parseSharedStrings(xml) {
4
4
  if (xml === '')
@@ -15,7 +15,7 @@ export function parseSharedStrings(xml) {
15
15
  switch (local) {
16
16
  case 'si':
17
17
  plain = '';
18
- runs.reset();
18
+ runs.beginContainer();
19
19
  isRich = false;
20
20
  break;
21
21
  case 'r':
@@ -41,11 +41,13 @@ export function parseSharedStrings(xml) {
41
41
  onClose(name) {
42
42
  const local = localName(name);
43
43
  switch (local) {
44
- case 't':
45
- if (!runs.appendText(text))
46
- plain += text;
44
+ case 't': {
45
+ const decoded = decodeSpreadsheetText(text);
46
+ if (!runs.appendText(decoded))
47
+ plain += decoded;
47
48
  capture = false;
48
49
  break;
50
+ }
49
51
  case 'r':
50
52
  runs.endRun();
51
53
  break;
@@ -9,7 +9,7 @@ export declare function applyFontChild(draft: FontDraft, local: string, attrs: X
9
9
  /**
10
10
  * Extract the custom indexed-color palette (`<colors><indexedColors>`) from styles.xml as verbatim
11
11
  * `<rgbColor rgb="…"/>` fragments, or an empty list when the file rides the default palette. Kept raw
12
- * rather than parsed into RGB and re-serialised so the exact entries (count, order, casing) a
12
+ * rather than parsed into RGB and re-serialised, so the exact entries (count, order, casing) a
13
13
  * source file declared survive a round-trip and every `indexed="…"` reference keeps its RGB.
14
14
  */
15
15
  export declare function parseIndexedColors(stylesXml: string): string[];
@@ -25,8 +25,8 @@ export declare function parseMruColors(stylesXml: string): string[];
25
25
  * container's nominated `defaultTableStyle`/`defaultPivotStyle`. See {@link TableStyleTable} for why
26
26
  * the definitions stay raw while the two names are decoded.
27
27
  *
28
- * A file with no such block or with the self-closing `count="0"` container Excel writes when it has
29
- * only defaults to state yields an empty {@link TableStyleTable.styles} and whichever names it did
28
+ * A file with no such block, or with the self-closing `count="0"` container Excel writes when it has
29
+ * only defaults to state, yields an empty {@link TableStyleTable.styles} and whichever names it did
30
30
  * carry.
31
31
  */
32
32
  export declare function parseTableStyles(stylesXml: string): TableStyleTable;