@shbernal/ts-xlsx 2.0.0 → 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 (106) 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 +11 -0
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/color-resolution.d.ts +1 -1
  9. package/dist/core/color-resolution.js +7 -8
  10. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  11. package/dist/core/conditional-formatting-overlay.js +11 -0
  12. package/dist/core/conditional-formatting.d.ts +45 -5
  13. package/dist/core/conditional-formatting.js +88 -0
  14. package/dist/core/data-validation-overlay.d.ts +11 -0
  15. package/dist/core/data-validation-overlay.js +21 -2
  16. package/dist/core/data-validation.d.ts +9 -2
  17. package/dist/core/data-validation.js +34 -0
  18. package/dist/core/grid-edits.d.ts +16 -0
  19. package/dist/core/grid-edits.js +26 -8
  20. package/dist/core/grid-shift.d.ts +19 -0
  21. package/dist/core/grid-shift.js +8 -0
  22. package/dist/core/image.d.ts +2 -0
  23. package/dist/core/image.js +4 -0
  24. package/dist/core/merge.d.ts +12 -1
  25. package/dist/core/merge.js +36 -2
  26. package/dist/core/page-setup.d.ts +16 -2
  27. package/dist/core/page-setup.js +8 -1
  28. package/dist/core/pivot-table.d.ts +2 -0
  29. package/dist/core/pivot-table.js +25 -16
  30. package/dist/core/protection.d.ts +3 -0
  31. package/dist/core/protection.js +20 -14
  32. package/dist/core/row-input.d.ts +7 -0
  33. package/dist/core/row-input.js +10 -8
  34. package/dist/core/style.d.ts +27 -0
  35. package/dist/core/style.js +82 -67
  36. package/dist/core/table.d.ts +25 -11
  37. package/dist/core/table.js +67 -31
  38. package/dist/core/theme.d.ts +3 -1
  39. package/dist/core/theme.js +4 -0
  40. package/dist/core/workbook.d.ts +2 -2
  41. package/dist/core/worksheet-comments.d.ts +8 -0
  42. package/dist/core/worksheet-comments.js +18 -0
  43. package/dist/core/worksheet.d.ts +15 -2
  44. package/dist/core/worksheet.js +28 -48
  45. package/dist/customui/ribbon.js +24 -23
  46. package/dist/entries/core.d.ts +3 -3
  47. package/dist/entries/csv.d.ts +1 -1
  48. package/dist/entries/node-unavailable.d.ts +15 -0
  49. package/dist/entries/node-unavailable.js +20 -0
  50. package/dist/entries/node.d.ts +1 -0
  51. package/dist/entries/node.js +1 -0
  52. package/dist/entries/xlsx.d.ts +0 -1
  53. package/dist/entries/xlsx.js +0 -1
  54. package/dist/io/csv/read.js +1 -1
  55. package/dist/io/csv/write.d.ts +12 -2
  56. package/dist/io/csv/write.js +17 -2
  57. package/dist/io/opc/inflate.js +2 -12
  58. package/dist/io/opc/rels.d.ts +11 -0
  59. package/dist/io/opc/rels.js +3 -2
  60. package/dist/io/style/xf-style.d.ts +7 -2
  61. package/dist/io/style/xf-style.js +6 -1
  62. package/dist/io/xlsx/cell-accumulator.d.ts +1 -3
  63. package/dist/io/xlsx/cell-accumulator.js +20 -58
  64. package/dist/io/xlsx/cell-value.d.ts +1 -1
  65. package/dist/io/xlsx/cell-value.js +7 -4
  66. package/dist/io/xlsx/color-xml.d.ts +1 -1
  67. package/dist/io/xlsx/color-xml.js +7 -6
  68. package/dist/io/xlsx/comments.js +4 -2
  69. package/dist/io/xlsx/conditional-formatting.d.ts +1 -1
  70. package/dist/io/xlsx/conditional-formatting.js +60 -45
  71. package/dist/io/xlsx/data-validation.d.ts +1 -1
  72. package/dist/io/xlsx/data-validation.js +35 -33
  73. package/dist/io/xlsx/hyperlinks.js +6 -14
  74. package/dist/io/xlsx/images.js +19 -22
  75. package/dist/io/xlsx/read-pivot.js +2 -8
  76. package/dist/io/xlsx/read-shared-strings.js +8 -47
  77. package/dist/io/xlsx/read-styles.js +2 -1
  78. package/dist/io/xlsx/read-worksheet.js +14 -16
  79. package/dist/io/xlsx/read.d.ts +1 -1
  80. package/dist/io/xlsx/read.js +44 -74
  81. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  82. package/dist/io/xlsx/rich-runs.js +94 -30
  83. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  84. package/dist/io/xlsx/sheet-properties.js +13 -9
  85. package/dist/io/xlsx/styles.d.ts +3 -15
  86. package/dist/io/xlsx/styles.js +63 -88
  87. package/dist/io/xlsx/tables.js +13 -13
  88. package/dist/io/xlsx/theme-xml.js +7 -4
  89. package/dist/io/xlsx/threaded-comments.js +19 -19
  90. package/dist/io/xlsx/workbook-xml.js +14 -8
  91. package/dist/io/xlsx/worksheet-xml.js +34 -37
  92. package/dist/io/xlsx/write-stream.d.ts +8 -1
  93. package/dist/io/xlsx/write-stream.js +10 -11
  94. package/dist/sha512.d.ts +2 -0
  95. package/dist/sha512.js +146 -0
  96. package/dist/vba/bytes.d.ts +1 -2
  97. package/dist/vba/bytes.js +1 -12
  98. package/dist/vba/dir-records.d.ts +53 -0
  99. package/dist/vba/dir-records.js +28 -0
  100. package/dist/vba/project-editor.js +5 -34
  101. package/dist/vba/project.js +2 -18
  102. package/dist/xml/xml-read.d.ts +60 -0
  103. package/dist/xml/xml-read.js +47 -0
  104. package/dist/xml/xml.d.ts +43 -1
  105. package/dist/xml/xml.js +17 -5
  106. package/package.json +21 -4
@@ -9,26 +9,29 @@ export function isCustomUiRelType(type) {
9
9
  return type.endsWith('/ui/extensibility');
10
10
  }
11
11
  const MAX_DEPTH = 256;
12
- const KNOWN_KINDS = new Set([
13
- 'button',
14
- 'toggleButton',
15
- 'checkBox',
16
- 'editBox',
17
- 'dropDown',
18
- 'comboBox',
19
- 'gallery',
20
- 'menu',
21
- 'dynamicMenu',
22
- 'splitButton',
23
- 'buttonGroup',
24
- 'box',
25
- 'labelControl',
26
- 'separator',
27
- 'menuSeparator',
28
- 'dialogBoxLauncher',
29
- 'control',
30
- 'item',
31
- ]);
12
+ const KNOWN_KINDS = {
13
+ button: true,
14
+ toggleButton: true,
15
+ checkBox: true,
16
+ editBox: true,
17
+ dropDown: true,
18
+ comboBox: true,
19
+ gallery: true,
20
+ menu: true,
21
+ dynamicMenu: true,
22
+ splitButton: true,
23
+ buttonGroup: true,
24
+ box: true,
25
+ labelControl: true,
26
+ separator: true,
27
+ menuSeparator: true,
28
+ dialogBoxLauncher: true,
29
+ control: true,
30
+ item: true,
31
+ };
32
+ function isKnownControlKind(local) {
33
+ return Object.hasOwn(KNOWN_KINDS, local);
34
+ }
32
35
  export function parseCustomUi(input) {
33
36
  const xml = typeof input === 'string' ? input : strFromU8(input);
34
37
  let root;
@@ -79,9 +82,7 @@ function toGroup(groupEl) {
79
82
  };
80
83
  }
81
84
  function toControl(el) {
82
- const kind = KNOWN_KINDS.has(el.local)
83
- ? el.local
84
- : 'unknown';
85
+ const kind = isKnownControlKind(el.local) ? el.local : 'unknown';
85
86
  return {
86
87
  kind,
87
88
  ...identity(el.attrs),
@@ -4,11 +4,11 @@ export { Cell } from '../core/cell.ts';
4
4
  export { applyTint, type ColorResolutionContext, DEFAULT_INDEXED_COLORS, resolveColor, SYSTEM_INDEXED_COLORS, } from '../core/color-resolution.ts';
5
5
  export { Column } from '../core/column.ts';
6
6
  export type { Comment, CommentThread, Mention, MentionRef, Person } from '../core/comment-thread.ts';
7
- export type { CfValueObject, ConditionalFormatting, ConditionalFormattingRule, } from '../core/conditional-formatting.ts';
7
+ export type { CfTimePeriod, CfValueObject, CfValueObjectType, ConditionalFormatting, ConditionalFormattingOperator, ConditionalFormattingRule, ConditionalFormattingType, IconSetType, } from '../core/conditional-formatting.ts';
8
8
  export type { DataValidation, DataValidationEntry, DataValidationErrorStyle, DataValidationOperator, DataValidationType, } from '../core/data-validation.ts';
9
9
  export { type AnchoredImage, type AnchorPoint, type Extent, type ImageAnchor, type ImageEditAs, isOneCellAnchor, type OneCellAnchor, type PortableImage, PX_TO_EMU, type TwoCellAnchor, type WorkbookImage, type WorksheetImages, } from '../core/image.ts';
10
10
  export { MAX_COLUMN_WIDTH, MAX_ROW_HEIGHT } from '../core/limits.ts';
11
- export type { HeaderFooter, PageBreak, PageMargins, PageSetup, PrintOptions, } from '../core/page-setup.ts';
11
+ export type { HeaderFooter, PageBreak, PageMargins, PageOrder, PageOrientation, PageSetup, PrintOptions, } from '../core/page-setup.ts';
12
12
  export { type ParsedPivotField, type ParsedPivotSource, type ParsedPivotTable, type PivotCacheField, type PivotItem, type PivotMetric, type PivotNumericSummary, type PivotRecordCell, type PivotSourceKind, PivotTable, type PivotTableOptions, } from '../core/pivot-table.ts';
13
13
  export type { PreservedPart, PreservedRelationship, PreservedRootReference, PreservedWorksheetReference, } from '../core/preserved.ts';
14
14
  export type { SheetProtection, SheetProtectionCredential, SheetProtectionFlags, SheetProtectionOptions, } from '../core/protection.ts';
@@ -22,4 +22,4 @@ export { DEFAULT_THEME_COLOR_SCHEME, DEFAULT_THEME_FONTS, THEME_COLOR_SLOTS, typ
22
22
  export { type CellValue, cellValueToText, coerceCellValue, type DataTableFormulaValue, detectValueType, ERROR_CODES, type ErrorCode, type ErrorValue, type FormulaResult, type FormulaValue, type HyperlinkValue, isDataTableFormulaValue, isErrorCode, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, type RichTextRun, type RichTextValue, richTextToPlain, type SharedFormulaValue, ValueType, } from '../core/value.ts';
23
23
  export { type AddImageOptions, type AddWorksheetOptions, DEFAULT_WORKBOOK_VIEW, type DefinedName, type PreservedWorkbookReference, Workbook, type WorkbookProperties, type WorkbookView, } from '../core/workbook.ts';
24
24
  export type { WorkbookProtection, WorkbookProtectionCredentialAttr, } from '../core/workbook-protection.ts';
25
- export { type CellModel, type ColumnProperties, type OutlineProperties, type RowInput, type RowProperties, type SheetView, Worksheet, type WorksheetModel, type WorksheetProperties, type WorksheetState, } from '../core/worksheet.ts';
25
+ export { type CellModel, type ColumnProperties, type OutlineProperties, type RowInput, type RowProperties, type SheetView, type Visibility, Worksheet, type WorksheetModel, type WorksheetProperties, type WorksheetState, } from '../core/worksheet.ts';
@@ -1,2 +1,2 @@
1
1
  export { type CsvReadOptions, readCsv } from '../io/csv/read.ts';
2
- export { type CsvWriteOptions, writeCsv, writeCsvText } from '../io/csv/write.ts';
2
+ export { type CsvEncoding, type CsvWriteOptions, writeCsv, writeCsvText } from '../io/csv/write.ts';
@@ -0,0 +1,15 @@
1
+ /** Not available outside Node; see {@link WorkbookStreamWriter}. */
2
+ export declare class StreamedRow {
3
+ constructor();
4
+ }
5
+ /** Not available outside Node; see {@link WorkbookStreamWriter}. */
6
+ export declare class WorksheetStreamWriter {
7
+ constructor();
8
+ }
9
+ /**
10
+ * Not available outside Node. The streaming writer needs `node:fs` and `node:stream`; in a browser,
11
+ * build the workbook in memory and call `writeXlsx`.
12
+ */
13
+ export declare class WorkbookStreamWriter {
14
+ constructor();
15
+ }
@@ -0,0 +1,20 @@
1
+ const WHY = 'the streaming writer opens files and pipes Node streams, which a browser has neither of. ' +
2
+ 'Use writeXlsx (or writeXlsxAsync), which produce the same package as bytes.';
3
+ function unavailable(name) {
4
+ throw new Error(`ts-xlsx: ${name} is not available in this environment: ${WHY}`);
5
+ }
6
+ export class StreamedRow {
7
+ constructor() {
8
+ unavailable('StreamedRow');
9
+ }
10
+ }
11
+ export class WorksheetStreamWriter {
12
+ constructor() {
13
+ unavailable('WorksheetStreamWriter');
14
+ }
15
+ }
16
+ export class WorkbookStreamWriter {
17
+ constructor() {
18
+ unavailable('WorkbookStreamWriter');
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ export { type CalcProperties, StreamedRow, WorkbookStreamWriter, type WorkbookStreamWriterOptions, WorksheetStreamWriter, } from '../io/xlsx/write-stream.ts';
@@ -0,0 +1 @@
1
+ export { StreamedRow, WorkbookStreamWriter, WorksheetStreamWriter, } from '../io/xlsx/write-stream.js';
@@ -3,4 +3,3 @@ export { type ReadXlsxOptions, readXlsx } from '../io/xlsx/read.ts';
3
3
  export { type ReadSheetRowsOptions, readSheetRows, readWorkbookStream, } from '../io/xlsx/read-rows.ts';
4
4
  export { DEFAULT_THEME_XML, parseThemeColorScheme, parseThemeFontScheme, } from '../io/xlsx/theme-xml.ts';
5
5
  export { type WriteOptions, writeXlsx, writeXlsxAsync } from '../io/xlsx/write.ts';
6
- export { type CalcProperties, StreamedRow, WorkbookStreamWriter, type WorkbookStreamWriterOptions, WorksheetStreamWriter, } from '../io/xlsx/write-stream.ts';
@@ -3,4 +3,3 @@ export { readXlsx } from '../io/xlsx/read.js';
3
3
  export { readSheetRows, readWorkbookStream, } from '../io/xlsx/read-rows.js';
4
4
  export { DEFAULT_THEME_XML, parseThemeColorScheme, parseThemeFontScheme, } from '../io/xlsx/theme-xml.js';
5
5
  export { writeXlsx, writeXlsxAsync } from '../io/xlsx/write.js';
6
- export { StreamedRow, WorkbookStreamWriter, WorksheetStreamWriter, } from '../io/xlsx/write-stream.js';
@@ -1,6 +1,6 @@
1
1
  import { Workbook } from '../../core/workbook.js';
2
2
  export function readCsv(input, options = {}) {
3
- const text = stripBom(typeof input === 'string' ? input : Buffer.from(input).toString('utf8'));
3
+ const text = stripBom(typeof input === 'string' ? input : new TextDecoder('utf-8', { ignoreBOM: true }).decode(input));
4
4
  const delimiter = options.delimiter ?? ',';
5
5
  if (delimiter.length !== 1) {
6
6
  throw new RangeError(`CSV delimiter must be a single character, got ${JSON.stringify(delimiter)}`);
@@ -1,5 +1,15 @@
1
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`
@@ -28,6 +38,6 @@ export declare function writeCsvText(workbook: Workbook, options?: CsvWriteOptio
28
38
  * The CSV bytes of one worksheet in the requested encoding, with a UTF-8 BOM by default.
29
39
  *
30
40
  * @throws {AuthoringError} if a field holds an unpaired surrogate and the encoding is UTF-8, which
31
- * cannot represent one. The alternative is `Buffer.from`'s silent U+FFFD substitution.
41
+ * cannot represent one. The alternative is a silent U+FFFD substitution.
32
42
  */
33
43
  export declare function writeCsv(workbook: Workbook, options?: CsvWriteOptions): Uint8Array;
@@ -30,10 +30,10 @@ export function writeCsv(workbook, options = {}) {
30
30
  const utf8 = isUtf8(encoding);
31
31
  if (utf8)
32
32
  assertEncodable(text);
33
- const body = Buffer.from(text, encoding);
33
+ const body = encode(text, encoding);
34
34
  const wantBom = options.bom ?? utf8;
35
35
  if (!wantBom || !utf8)
36
- return Uint8Array.from(body);
36
+ return body;
37
37
  const out = new Uint8Array(UTF8_BOM.length + body.length);
38
38
  out.set(UTF8_BOM, 0);
39
39
  out.set(body, UTF8_BOM.length);
@@ -42,6 +42,21 @@ export function writeCsv(workbook, options = {}) {
42
42
  function isUtf8(encoding) {
43
43
  return encoding === 'utf8' || encoding === 'utf-8';
44
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
+ }
45
60
  function assertEncodable(text) {
46
61
  const found = LONE_SURROGATE.exec(text);
47
62
  if (found === null)
@@ -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,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)));
@@ -6,10 +6,15 @@ import { type Border, type CellStyle, type Fill, type Font, type NamedCellStyle
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;
@@ -71,8 +71,13 @@ export function resolveStyleTable(tables) {
71
71
  const named = namedXfs[xf.xfId];
72
72
  return named === undefined ? xf : { ...named, ...xf };
73
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
+ }
74
79
  const namedStyles = namedXfs.map((xf, index) => {
75
- const label = labels.find((entry) => entry.xfId === index);
80
+ const label = labelsByXfId.get(index);
76
81
  const style = {};
77
82
  assignStyleFacets(style, xf);
78
83
  if (label?.name !== undefined)
@@ -3,7 +3,6 @@ import type { Worksheet } from '../../core/worksheet.ts';
3
3
  import { type XmlAttributes } from '../../xml/xml-read.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-read.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-read.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 { parseArgb } from '../../core/style.js';
1
2
  import { numFinite, numInteger } from '../../xml/xml-read.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,6 @@
1
1
  import { tryDecodeCellRef } from '../../core/address.js';
2
2
  import { decodeSpreadsheetText, localName, numInteger, parseXml } from '../../xml/xml-read.js';
3
- import { escapeAttr, escapeText, textElement, XML_DECLARATION } from '../../xml/xml.js';
3
+ import { escapeText, textAttr, textElement, XML_DECLARATION } from '../../xml/xml.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);
@@ -56,7 +56,7 @@ export function commentsXml(comments) {
56
56
  .map((comment) => {
57
57
  const { threadId } = comment;
58
58
  const authorId = threadId === undefined ? noteAuthorId : authorIdByThreadId.get(threadId);
59
- const uid = threadId === undefined ? '' : ` xr:uid="${escapeAttr(threadId)}"`;
59
+ const uid = textAttr('xr:uid', threadId);
60
60
  return (`<comment ref="${comment.ref}" authorId="${authorId}"${uid}>` +
61
61
  `<text><r>${textElement(comment.text)}</r></text>` +
62
62
  '</comment>');
@@ -161,6 +161,8 @@ export function applyNotes(sheet, comments) {
161
161
  for (const [ref, comment] of comments) {
162
162
  if (comment.threadId !== undefined && headIds.has(comment.threadId))
163
163
  continue;
164
+ if (tryDecodeCellRef(ref) === undefined)
165
+ continue;
164
166
  sheet.getCell(ref).note = comment.text;
165
167
  }
166
168
  }
@@ -1,4 +1,4 @@
1
- import type { ConditionalFormatting } from '../../core/conditional-formatting.ts';
1
+ import { type ConditionalFormatting } from '../../core/conditional-formatting.ts';
2
2
  import type { StyleRegistry } from './styles.ts';
3
3
  /**
4
4
  * Serialise every conditional formatting on a sheet into its `<conditionalFormatting>` blocks, in