@shbernal/ts-xlsx 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -1,7 +1,8 @@
1
- import { decodeRange } from '../../core/address.js';
1
+ import { tryDecodeRange } from '../../core/address.js';
2
2
  import { isHyperlinkValue, isRichTextValue } from '../../core/value.js';
3
- import { localName, parseXml } from '../../xml/xml-read.js';
4
- import { escapeAttr } from '../../xml/xml.js';
3
+ import {} from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
5
+ import { escapeAttr, textAttr } from '../../xml/xml.js';
5
6
  export function collectHyperlinks(sheet) {
6
7
  const links = [];
7
8
  for (const { cells } of sheet.rows()) {
@@ -33,39 +34,41 @@ export function hyperlinksXml(links) {
33
34
  const items = links
34
35
  .map((link) => {
35
36
  const rid = link.relId !== undefined ? ` r:id="${link.relId}"` : '';
36
- const location = link.location !== undefined ? ` location="${escapeAttr(link.location)}"` : '';
37
- const tooltip = link.tooltip !== undefined ? ` tooltip="${escapeAttr(link.tooltip)}"` : '';
37
+ const location = textAttr('location', link.location);
38
+ const tooltip = textAttr('tooltip', link.tooltip);
38
39
  return `<hyperlink ref="${escapeAttr(link.ref)}"${rid}${location}${tooltip}/>`;
39
40
  })
40
41
  .join('');
41
42
  return `<hyperlinks>${items}</hyperlinks>`;
42
43
  }
43
- export function parseSheetHyperlinks(xml) {
44
+ export function sheetHyperlinkPass() {
44
45
  const links = [];
45
- parseXml(xml, {
46
- onOpen(name, attrs) {
47
- if (localName(name) !== 'hyperlink')
48
- return;
49
- const ref = attrs.ref;
50
- if (ref === undefined)
51
- return;
52
- links.push({
53
- ref,
54
- ...(attrs['r:id'] !== undefined ? { rid: attrs['r:id'] } : {}),
55
- ...(attrs.location !== undefined ? { location: attrs.location } : {}),
56
- ...(attrs.tooltip !== undefined ? { tooltip: attrs.tooltip } : {}),
57
- });
46
+ return {
47
+ handlers: {
48
+ onOpen(name, attrs) {
49
+ if (localName(name) !== 'hyperlink')
50
+ return;
51
+ const ref = attrs.ref;
52
+ if (ref === undefined)
53
+ return;
54
+ links.push({
55
+ ref,
56
+ ...(attrs['r:id'] !== undefined ? { rid: attrs['r:id'] } : {}),
57
+ ...(attrs.location !== undefined ? { location: attrs.location } : {}),
58
+ ...(attrs.tooltip !== undefined ? { tooltip: attrs.tooltip } : {}),
59
+ });
60
+ },
58
61
  },
59
- });
60
- return links;
62
+ result: () => links,
63
+ };
61
64
  }
62
65
  export function applyHyperlinks(sheet, links, targetOf) {
63
66
  for (const link of links) {
64
67
  const target = resolveTarget(link, targetOf);
65
68
  if (target === undefined)
66
69
  continue;
67
- const decoded = decodeRefSafe(link.ref);
68
- if (decoded === undefined)
70
+ const decoded = tryDecodeRange(link.ref);
71
+ if (decoded === undefined || decoded.tl.col === undefined || decoded.tl.row === undefined)
69
72
  continue;
70
73
  const cell = sheet.getCell(decoded.tl.address);
71
74
  const cellValue = cell.value;
@@ -80,14 +83,6 @@ export function applyHyperlinks(sheet, links, targetOf) {
80
83
  cell.value = value;
81
84
  }
82
85
  }
83
- function decodeRefSafe(ref) {
84
- try {
85
- return decodeRange(ref);
86
- }
87
- catch {
88
- return undefined;
89
- }
90
- }
91
86
  function resolveTarget(link, targetOf) {
92
87
  if (link.rid !== undefined) {
93
88
  const base = targetOf(link.rid);
@@ -1,25 +1,26 @@
1
- import { isOneCellAnchor, } from '../../core/image.js';
2
- import { localName, numFinite, parseXml } from '../../xml/xml-read.js';
3
- import { XML_DECLARATION } from '../../xml/xml.js';
1
+ import { isImageEditAs, isOneCellAnchor, } from '../../core/image.js';
2
+ import { parseXml, TextCapture } from '../../xml/xml-read.js';
3
+ import { enumToken, localName, numFinite } from '../../xml/xml-scan.js';
4
+ import { checkedToken, numAttr, numberText, XML_DECLARATION } from '../../xml/xml.js';
4
5
  import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
5
6
  import { relationship, relationshipsPart } from '../opc/rels.js';
6
7
  import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
7
8
  const IMAGE_REL_TYPE = `${RELATIONSHIPS_NS}/image`;
8
- const IMAGE_CONTENT_TYPES = {
9
- png: 'image/png',
10
- jpg: 'image/jpeg',
11
- jpeg: 'image/jpeg',
12
- gif: 'image/gif',
13
- bmp: 'image/bmp',
14
- tif: 'image/tiff',
15
- tiff: 'image/tiff',
16
- emf: 'image/x-emf',
17
- wmf: 'image/x-wmf',
18
- svg: 'image/svg+xml',
19
- };
9
+ const IMAGE_CONTENT_TYPES = new Map([
10
+ ['png', 'image/png'],
11
+ ['jpg', 'image/jpeg'],
12
+ ['jpeg', 'image/jpeg'],
13
+ ['gif', 'image/gif'],
14
+ ['bmp', 'image/bmp'],
15
+ ['tif', 'image/tiff'],
16
+ ['tiff', 'image/tiff'],
17
+ ['emf', 'image/x-emf'],
18
+ ['wmf', 'image/x-wmf'],
19
+ ['svg', 'image/svg+xml'],
20
+ ]);
20
21
  export function imageContentType(extension) {
21
22
  const ext = extension.toLowerCase();
22
- return IMAGE_CONTENT_TYPES[ext] ?? `image/${ext}`;
23
+ return IMAGE_CONTENT_TYPES.get(ext) ?? `image/${ext}`;
23
24
  }
24
25
  export function drawingXml(images) {
25
26
  const anchors = images.map((image, i) => anchorXml(image, i + 1)).join('');
@@ -35,7 +36,7 @@ function anchorXml(image, id) {
35
36
  : twoCellAnchorXml(anchor.from, anchor.to, anchor.editAs ?? 'oneCell', anchor.rotation, image.embedId, id);
36
37
  }
37
38
  function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
38
- return (`<xdr:twoCellAnchor editAs="${editAs}">` +
39
+ return (`<xdr:twoCellAnchor editAs="${checkedToken(editAs, isImageEditAs, 'image anchor edit mode')}">` +
39
40
  `<xdr:from>${anchorPointXml(from)}</xdr:from>` +
40
41
  `<xdr:to>${anchorPointXml(to)}</xdr:to>` +
41
42
  picXml(embedId, id, rotation) +
@@ -45,13 +46,13 @@ function twoCellAnchorXml(from, to, editAs, rotation, embedId, id) {
45
46
  function oneCellAnchorXml(from, ext, rotation, embedId, id) {
46
47
  return ('<xdr:oneCellAnchor>' +
47
48
  `<xdr:from>${anchorPointXml(from)}</xdr:from>` +
48
- `<xdr:ext cx="${ext.cx}" cy="${ext.cy}"/>` +
49
+ `<xdr:ext${numAttr('cx', ext.cx)}${numAttr('cy', ext.cy)}/>` +
49
50
  picXml(embedId, id, rotation) +
50
51
  '<xdr:clientData/>' +
51
52
  '</xdr:oneCellAnchor>');
52
53
  }
53
54
  function picXml(embedId, id, rotation) {
54
- const xfrm = rotation !== undefined ? `<a:xfrm rot="${rotation}"/>` : '';
55
+ const xfrm = rotation !== undefined ? `<a:xfrm${numAttr('rot', rotation)}/>` : '';
55
56
  return ('<xdr:pic>' +
56
57
  `<xdr:nvPicPr><xdr:cNvPr id="${id}" name="Picture ${id}"/>` +
57
58
  '<xdr:cNvPicPr><a:picLocks noChangeAspect="1"/></xdr:cNvPicPr></xdr:nvPicPr>' +
@@ -61,8 +62,10 @@ function picXml(embedId, id, rotation) {
61
62
  '</xdr:pic>');
62
63
  }
63
64
  function anchorPointXml(point) {
64
- return (`<xdr:col>${point.col}</xdr:col><xdr:colOff>${point.colOff ?? 0}</xdr:colOff>` +
65
- `<xdr:row>${point.row}</xdr:row><xdr:rowOff>${point.rowOff ?? 0}</xdr:rowOff>`);
65
+ return (`<xdr:col>${numberText(point.col)}</xdr:col>` +
66
+ `<xdr:colOff>${numberText(point.colOff ?? 0)}</xdr:colOff>` +
67
+ `<xdr:row>${numberText(point.row)}</xdr:row>` +
68
+ `<xdr:rowOff>${numberText(point.rowOff ?? 0)}</xdr:rowOff>`);
66
69
  }
67
70
  export function drawingRelsXml(mediaTargets) {
68
71
  return relationshipsPart(mediaTargets.map((target, i) => relationship(`rId${i + 1}`, IMAGE_REL_TYPE, target)));
@@ -70,7 +73,6 @@ export function drawingRelsXml(mediaTargets) {
70
73
  function blankPoint() {
71
74
  return { col: 0, row: 0, colOff: 0, rowOff: 0 };
72
75
  }
73
- const EDIT_AS = new Set(['oneCell', 'twoCell', 'absolute']);
74
76
  export function parseDrawing(xml) {
75
77
  const anchors = [];
76
78
  let from = null;
@@ -81,10 +83,9 @@ export function parseDrawing(xml) {
81
83
  let embed;
82
84
  let target = null;
83
85
  let picDepth = 0;
84
- let coord = '';
85
- let text = '';
86
+ const coord = new TextCapture(COORDINATES);
86
87
  parseXml(xml, {
87
- onOpen(name, attrs) {
88
+ onOpen(name, attrs, selfClosing) {
88
89
  const local = localName(name);
89
90
  if (local === 'twoCellAnchor' || local === 'oneCellAnchor') {
90
91
  from = blankPoint();
@@ -92,8 +93,7 @@ export function parseDrawing(xml) {
92
93
  ext = undefined;
93
94
  rotation = undefined;
94
95
  embed = undefined;
95
- const mode = attrs.editAs;
96
- editAs = mode !== undefined && EDIT_AS.has(mode) ? mode : undefined;
96
+ editAs = enumToken(attrs.editAs, isImageEditAs);
97
97
  }
98
98
  else if (local === 'pic') {
99
99
  picDepth++;
@@ -120,22 +120,20 @@ export function parseDrawing(xml) {
120
120
  if (value !== undefined)
121
121
  embed = value;
122
122
  }
123
- else if (target !== null && COORDINATES.has(local)) {
124
- coord = local;
125
- text = '';
123
+ else if (target !== null) {
124
+ coord.open(local, selfClosing);
126
125
  }
127
126
  },
128
127
  onText(chunk) {
129
- if (coord !== '')
130
- text += chunk;
128
+ coord.text(chunk);
131
129
  },
132
130
  onClose(name) {
133
131
  const local = localName(name);
134
- if (target !== null && coord === local && COORDINATES.has(local)) {
132
+ const text = coord.close(local);
133
+ if (text !== undefined) {
135
134
  const value = numFinite(text);
136
- if (value !== undefined)
135
+ if (target !== null && value !== undefined)
137
136
  setCoordinate(target, local, value);
138
- coord = '';
139
137
  }
140
138
  else if (local === 'from' || local === 'to') {
141
139
  target = null;
@@ -1,4 +1,5 @@
1
1
  import type { CommentThread } from '../../core/comment-thread.ts';
2
+ import type { WorkbookImage } from '../../core/image.ts';
2
3
  import type { PivotTable } from '../../core/pivot-table.ts';
3
4
  import type { Table } from '../../core/table.ts';
4
5
  import type { Workbook } from '../../core/workbook.ts';
@@ -94,10 +95,17 @@ export interface MediaPart {
94
95
  readonly extension: string;
95
96
  readonly data: Uint8Array;
96
97
  }
98
+ export interface ResolvedMedia {
99
+ readonly number: number;
100
+ readonly image: WorkbookImage;
101
+ }
97
102
  export interface MediaPlan {
98
103
  readonly parts: readonly MediaPart[];
99
- readonly numberById: ReadonlyMap<number, number>;
100
104
  readonly extensions: readonly string[];
105
+ /** The media part number and registered image for a referenced id. Total over the ids
106
+ * {@link planMedia} saw on the sheets it planned, because planning already threw on any it could
107
+ * not resolve; an id from anywhere else is a caller bug and is reported as one. */
108
+ resolve(id: number): ResolvedMedia;
101
109
  }
102
110
  export declare function planMedia(workbook: Workbook, sheets: readonly Worksheet[]): MediaPlan;
103
111
  export declare function planPreservedParts(workbook: Workbook, generatedDrawingCount: number, generatedMediaCount: number): PreservedPlan;
@@ -1,6 +1,7 @@
1
1
  import { AuthoringError } from '../../errors.js';
2
2
  import { extensionOf, relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
3
- import { preservedRelsXml } from '../opc/rels.js';
3
+ import { relsPartXml } from '../opc/rels.js';
4
+ import { drawingPart, mediaPart, vmlDrawingPart } from './part-names.js';
4
5
  import { applyThemeOverrides } from './theme-xml.js';
5
6
  export class SheetRelIds {
6
7
  #next = 1;
@@ -9,34 +10,46 @@ export class SheetRelIds {
9
10
  }
10
11
  }
11
12
  export function planMedia(workbook, sheets) {
12
- const usedIds = [];
13
+ const uses = [];
13
14
  const seen = new Set();
14
- const use = (id) => {
15
+ const use = (id, sheetName, role) => {
15
16
  if (!seen.has(id)) {
16
17
  seen.add(id);
17
- usedIds.push(id);
18
+ uses.push({ id, sheetName, role });
18
19
  }
19
20
  };
20
21
  for (const sheet of sheets) {
21
22
  for (const image of sheet.images)
22
- use(image.imageId);
23
- if (sheet.backgroundImageId !== undefined)
24
- use(sheet.backgroundImageId);
23
+ use(image.imageId, sheet.name, 'anchor');
24
+ if (sheet.backgroundImageId !== undefined) {
25
+ use(sheet.backgroundImageId, sheet.name, 'background');
26
+ }
25
27
  }
26
28
  const parts = [];
27
- const numberById = new Map();
29
+ const byId = new Map();
28
30
  const extensions = new Set();
29
- usedIds.forEach((id, i) => {
31
+ uses.forEach(({ id, sheetName, role }, i) => {
30
32
  const image = workbook.getImage(id);
31
33
  if (image === undefined) {
32
- throw new AuthoringError(`a worksheet anchors image id ${id}, which is not registered on the workbook`);
34
+ const reference = role === 'anchor' ? `anchors image id ${id}` : `sets background image id ${id}`;
35
+ throw new AuthoringError(`sheet "${sheetName}" ${reference}, which is not registered on the workbook`);
33
36
  }
34
37
  const number = i + 1;
35
38
  parts.push({ number, extension: image.extension, data: image.data });
36
- numberById.set(id, number);
39
+ byId.set(id, { number, image });
37
40
  extensions.add(image.extension);
38
41
  });
39
- return { parts, numberById, extensions: [...extensions] };
42
+ return {
43
+ parts,
44
+ extensions: [...extensions],
45
+ resolve(id) {
46
+ const resolved = byId.get(id);
47
+ if (resolved === undefined) {
48
+ throw new AuthoringError(`image id ${id} was not planned into this workbook's media`);
49
+ }
50
+ return resolved;
51
+ },
52
+ };
40
53
  }
41
54
  export function planPreservedParts(workbook, generatedDrawingCount, generatedMediaCount) {
42
55
  const sheets = workbook.worksheets;
@@ -84,7 +97,7 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
84
97
  : new TextEncoder().encode(applyThemeOverrides(new TextDecoder().decode(part.bytes), overrides)),
85
98
  contentType: part.contentType,
86
99
  relsPath: rels.length === 0 ? null : relsPathFor(newPath),
87
- relsXml: rels.length === 0 ? null : preservedRelsXml(rels),
100
+ relsXml: rels.length === 0 ? null : relsPartXml(rels),
88
101
  });
89
102
  }
90
103
  }
@@ -114,11 +127,11 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
114
127
  function preservedPartPath(originalPath, numbering) {
115
128
  const ext = extensionOf(originalPath);
116
129
  if (ext.toLowerCase() === 'vml')
117
- return `xl/drawings/vmlDrawing${++numbering.vml}.vml`;
130
+ return vmlDrawingPart(++numbering.vml);
118
131
  if (originalPath.startsWith('xl/media/'))
119
- return `xl/media/image${++numbering.media}.${ext}`;
132
+ return mediaPart(++numbering.media, ext);
120
133
  if (originalPath.startsWith('xl/drawings/') && ext.toLowerCase() === 'xml') {
121
- return `xl/drawings/drawing${++numbering.drawing}.xml`;
134
+ return drawingPart(++numbering.drawing);
122
135
  }
123
136
  return originalPath;
124
137
  }
@@ -0,0 +1,41 @@
1
+ /** The office document part: the workbook every other part hangs off. */
2
+ export declare const WORKBOOK_PART = "xl/workbook.xml";
3
+ export declare const STYLES_PART = "xl/styles.xml";
4
+ export declare const SHARED_STRINGS_PART = "xl/sharedStrings.xml";
5
+ /** Singular and unnumbered, unlike the per-sheet thread parts: one identity registry serves the
6
+ * whole workbook's threaded comments. */
7
+ export declare const PERSONS_PART = "xl/persons/person.xml";
8
+ export declare const CORE_PROPS_PART = "docProps/core.xml";
9
+ export declare const APP_PROPS_PART = "docProps/app.xml";
10
+ /** `xl/worksheets/sheet{n}.xml`, numbered by the sheet's position in the workbook (1-based). */
11
+ export declare function worksheetPart(number: number): string;
12
+ /** `xl/tables/table{n}.xml`, numbered across the whole workbook rather than per sheet. */
13
+ export declare function tablePart(number: number): string;
14
+ /** `xl/media/image{n}.{ext}`: the one part name that carries the source file's own extension, since
15
+ * the `<Default>` content type is registered per extension. */
16
+ export declare function mediaPart(number: number, extension: string): string;
17
+ /** `xl/drawings/drawing{n}.xml`: the DrawingML anchors for one sheet's images. */
18
+ export declare function drawingPart(number: number): string;
19
+ /** `xl/drawings/vmlDrawing{n}.vml`: the legacy shape a note's box is drawn as. Numbered with the
20
+ * comments part it accompanies, not with the DrawingML drawings it shares a directory with. */
21
+ export declare function vmlDrawingPart(number: number): string;
22
+ /** `xl/comments{n}.xml`: directly under `xl/`, unlike every other numbered part. */
23
+ export declare function commentsPart(number: number): string;
24
+ /** `xl/threadedComments/threadedComment{n}.xml`: one sheet's modern conversations. */
25
+ export declare function threadedCommentsPart(number: number): string;
26
+ /** `xl/printerSettings/printerSettings{n}.bin`: an opaque blob carried through verbatim. */
27
+ export declare function printerSettingsPart(number: number): string;
28
+ /** `xl/pivotTables/pivotTable{n}.xml`: the pivot as its host sheet reaches it. */
29
+ export declare function pivotTablePart(number: number): string;
30
+ /** `xl/pivotCache/pivotCacheDefinition{n}.xml`: the field catalogue and source reference. */
31
+ export declare function pivotCacheDefinitionPart(number: number): string;
32
+ /** `xl/pivotCache/pivotCacheRecords{n}.xml`: the cached rows the definition points at. */
33
+ export declare function pivotCacheRecordsPart(number: number): string;
34
+ /**
35
+ * A part named as a Target of a relationship carried by a *worksheet*. Every worksheet part lives in
36
+ * `xl/worksheets/`, so one sheet's view of a target is every sheet's, and the sheet number the
37
+ * arithmetic runs against does not matter.
38
+ */
39
+ export declare function targetFromWorksheet(partPath: string): string;
40
+ /** A part named as a Target of a relationship carried by the workbook part. */
41
+ export declare function targetFromWorkbook(partPath: string): string;
@@ -0,0 +1,46 @@
1
+ import { relativePartPath } from '../opc/part-paths.js';
2
+ export const WORKBOOK_PART = 'xl/workbook.xml';
3
+ export const STYLES_PART = 'xl/styles.xml';
4
+ export const SHARED_STRINGS_PART = 'xl/sharedStrings.xml';
5
+ export const PERSONS_PART = 'xl/persons/person.xml';
6
+ export const CORE_PROPS_PART = 'docProps/core.xml';
7
+ export const APP_PROPS_PART = 'docProps/app.xml';
8
+ export function worksheetPart(number) {
9
+ return `xl/worksheets/sheet${number}.xml`;
10
+ }
11
+ export function tablePart(number) {
12
+ return `xl/tables/table${number}.xml`;
13
+ }
14
+ export function mediaPart(number, extension) {
15
+ return `xl/media/image${number}.${extension}`;
16
+ }
17
+ export function drawingPart(number) {
18
+ return `xl/drawings/drawing${number}.xml`;
19
+ }
20
+ export function vmlDrawingPart(number) {
21
+ return `xl/drawings/vmlDrawing${number}.vml`;
22
+ }
23
+ export function commentsPart(number) {
24
+ return `xl/comments${number}.xml`;
25
+ }
26
+ export function threadedCommentsPart(number) {
27
+ return `xl/threadedComments/threadedComment${number}.xml`;
28
+ }
29
+ export function printerSettingsPart(number) {
30
+ return `xl/printerSettings/printerSettings${number}.bin`;
31
+ }
32
+ export function pivotTablePart(number) {
33
+ return `xl/pivotTables/pivotTable${number}.xml`;
34
+ }
35
+ export function pivotCacheDefinitionPart(number) {
36
+ return `xl/pivotCache/pivotCacheDefinition${number}.xml`;
37
+ }
38
+ export function pivotCacheRecordsPart(number) {
39
+ return `xl/pivotCache/pivotCacheRecords${number}.xml`;
40
+ }
41
+ export function targetFromWorksheet(partPath) {
42
+ return relativePartPath(worksheetPart(1), partPath);
43
+ }
44
+ export function targetFromWorkbook(partPath) {
45
+ return relativePartPath(WORKBOOK_PART, partPath);
46
+ }
@@ -1,5 +1,6 @@
1
- import { pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
2
- import { localName, numInteger, parseXml } from '../../xml/xml-read.js';
1
+ import { isDeclarablePivotSourceKind, pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
2
+ import { parseXml } from '../../xml/xml-read.js';
3
+ import { localName, numInteger } from '../../xml/xml-scan.js';
3
4
  export function parsePivotTable(tableXml, cacheXml) {
4
5
  const { fields, source } = parsePivotCacheDefinition(cacheXml);
5
6
  const def = parsePivotTableDefinition(tableXml);
@@ -85,16 +86,10 @@ function parsePivotTableDefinition(tableXml) {
85
86
  });
86
87
  return { name, cacheId, rowFields, columnFields, valueField, valueCaption, metric };
87
88
  }
88
- const SOURCE_KINDS = new Set([
89
- 'worksheet',
90
- 'external',
91
- 'consolidation',
92
- 'scenario',
93
- ]);
94
89
  function sourceKind(type) {
95
90
  if (type === undefined)
96
91
  return 'worksheet';
97
- return SOURCE_KINDS.has(type) ? type : 'unknown';
92
+ return isDeclarablePivotSourceKind(type) ? type : 'unknown';
98
93
  }
99
94
  function toIndex(value) {
100
95
  return numInteger(value, 0) ?? -1;
@@ -1,12 +1,13 @@
1
1
  import { MAX_COLUMN, MAX_ROW } from '../../core/address.js';
2
2
  import { AuthoringError } from '../../errors.js';
3
- import { boolStrict, closeEmptyElements, localName, numInteger, xmlEvents, } from '../../xml/xml-read.js';
4
- import { packageAccessors } from '../opc/read-opc.js';
5
- import { inflateSpreadsheetPackage, unsupportedWorkbookPart } from '../opc/sniff-format.js';
3
+ import { closeEmptyElements } from '../../xml/xml-read.js';
4
+ import { boolStrict, localName, numInteger, xmlEvents } from '../../xml/xml-scan.js';
5
+ import { openSpreadsheetPackage, readPartRelationships } from '../opc/read-opc.js';
6
+ import { unsupportedWorkbookPart } from '../opc/sniff-format.js';
6
7
  import { CellAccumulator } from './cell-accumulator.js';
7
8
  import { XlsxParseError } from './errors.js';
8
9
  import { parseSharedStrings } from './read-shared-strings.js';
9
- import { DEFAULT_MAX_UNCOMPRESSED, parseRelationships, parseStyleTable, parseWorkbookSheets, resolveWorkbookPart, } from './read.js';
10
+ import { parseStyleTable, parseWorkbookSheets, } from './read.js';
10
11
  export function* readSheetRows(data, options = {}) {
11
12
  const pkg = openPackage(data, options.maxUncompressedBytes);
12
13
  const chosen = pickSheet(pkg.sheets, options.sheet);
@@ -23,14 +24,13 @@ export function* readWorkbookStream(data, options = {}) {
23
24
  }
24
25
  }
25
26
  function openPackage(data, maxUncompressedBytes) {
26
- const cap = maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
27
- const { partText: text } = packageAccessors(inflateSpreadsheetPackage(data, cap));
28
- const workbookXml = text('xl/workbook.xml');
27
+ const { pkg, workbookXml } = openSpreadsheetPackage(data, maxUncompressedBytes);
28
+ const { partText: text } = pkg;
29
29
  if (workbookXml === undefined) {
30
30
  throw unsupportedWorkbookPart(text, 'the binary .xlsb format (BIFF12) cannot be row-streamed yet; read it with readXlsx or readXlsb');
31
31
  }
32
32
  const sheets = parseWorkbookSheets(workbookXml);
33
- const rels = parseRelationships(text('xl/_rels/workbook.xml.rels') ?? '');
33
+ const rels = readPartRelationships('xl/workbook.xml', text);
34
34
  const sharedStrings = parseSharedStrings(text('xl/sharedStrings.xml') ?? '');
35
35
  const { cellXfs: xfStyles } = parseStyleTable(text('xl/styles.xml') ?? '');
36
36
  return {
@@ -38,9 +38,8 @@ function openPackage(data, maxUncompressedBytes) {
38
38
  sharedStrings,
39
39
  xfStyles,
40
40
  sheetXml(relId) {
41
- const target = rels.get(relId);
42
- const path = target === undefined ? undefined : resolveWorkbookPart(target);
43
- return path === undefined ? undefined : text(path);
41
+ const target = rels.byId(relId)?.target;
42
+ return target === undefined ? undefined : text(rels.pathOf(target));
44
43
  },
45
44
  };
46
45
  }
@@ -88,7 +87,7 @@ class StreamedSheetReader {
88
87
  }
89
88
  get merges() {
90
89
  this.#ensureScanned();
91
- return this.#merges;
90
+ return [...this.#merges];
92
91
  }
93
92
  #ensureScanned() {
94
93
  if (this.#scanned)
@@ -156,7 +155,7 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
156
155
  }
157
156
  }
158
157
  function collectHiddenColumn(attrs, hiddenColumns) {
159
- if (attrs.hidden !== '1' && attrs.hidden !== 'true')
158
+ if (!boolStrict(attrs.hidden))
160
159
  return;
161
160
  const min = numInteger(attrs.min, 1);
162
161
  const max = numInteger(attrs.max, 1);
@@ -1,60 +1,22 @@
1
- import { decodeSpreadsheetText, localName, parseXml } from '../../xml/xml-read.js';
1
+ import { parseXml } from '../../xml/xml-read.js';
2
+ import { localName } from '../../xml/xml-scan.js';
2
3
  import { RunAccumulator } from './rich-runs.js';
3
4
  export function parseSharedStrings(xml) {
4
5
  if (xml === '')
5
6
  return [];
6
7
  const strings = [];
7
- let plain = '';
8
- const runs = new RunAccumulator();
9
- let isRich = false;
10
- let capture = false;
11
- let text = '';
8
+ const runs = new RunAccumulator({ container: 'si', readRuns: true });
12
9
  parseXml(xml, {
13
- onOpen(name, attrs) {
14
- const local = localName(name);
15
- switch (local) {
16
- case 'si':
17
- plain = '';
18
- runs.beginContainer();
19
- isRich = false;
20
- break;
21
- case 'r':
22
- isRich = true;
23
- runs.beginRun();
24
- break;
25
- case 'rPr':
26
- runs.beginProperties();
27
- break;
28
- case 't':
29
- capture = true;
30
- text = '';
31
- break;
32
- default:
33
- runs.applyProperty(local, attrs);
34
- break;
35
- }
10
+ onOpen(name, attrs, selfClosing) {
11
+ runs.open(localName(name), attrs, selfClosing);
36
12
  },
37
13
  onText(chunk) {
38
- if (capture)
39
- text += chunk;
14
+ runs.text(chunk);
40
15
  },
41
16
  onClose(name) {
42
- const local = localName(name);
43
- switch (local) {
44
- case 't': {
45
- const decoded = decodeSpreadsheetText(text);
46
- if (!runs.appendText(decoded))
47
- plain += decoded;
48
- capture = false;
49
- break;
50
- }
51
- case 'r':
52
- runs.endRun();
53
- break;
54
- case 'si':
55
- strings.push(isRich ? { richText: runs.runs } : plain);
56
- break;
57
- }
17
+ if (runs.close(localName(name)) !== 'container')
18
+ return;
19
+ strings.push(runs.isRich ? { richText: runs.runs } : runs.plainText);
58
20
  },
59
21
  });
60
22
  return strings;
@@ -1,5 +1,5 @@
1
1
  import { type Font, type TableStyleTable } from '../../core/style.ts';
2
- import { type XmlAttributes } from '../../xml/xml-read.ts';
2
+ import { type XmlAttributes } from '../../xml/xml-scan.ts';
3
3
  import { type StyleTable } from '../style/xf-style.ts';
4
4
  export type FontDraft = {
5
5
  -readonly [K in keyof Font]?: Font[K];