@shbernal/ts-xlsx 2.1.0 → 3.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 (225) hide show
  1. package/README.md +4 -1
  2. package/dist/bytes.d.ts +7 -0
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +60 -2
  5. package/dist/core/address.js +40 -24
  6. package/dist/core/autofilter.d.ts +5 -4
  7. package/dist/core/autofilter.js +34 -35
  8. package/dist/core/axis-handle.d.ts +10 -0
  9. package/dist/core/axis-handle.js +17 -0
  10. package/dist/core/cell.d.ts +25 -0
  11. package/dist/core/cell.js +9 -3
  12. package/dist/core/clone.d.ts +29 -0
  13. package/dist/core/clone.js +24 -0
  14. package/dist/core/color-resolution.js +2 -4
  15. package/dist/core/column.d.ts +5 -1
  16. package/dist/core/column.js +34 -35
  17. package/dist/core/comment-thread.js +2 -1
  18. package/dist/core/conditional-formatting-overlay.d.ts +2 -1
  19. package/dist/core/conditional-formatting-overlay.js +2 -2
  20. package/dist/core/conditional-formatting.d.ts +14 -6
  21. package/dist/core/conditional-formatting.js +45 -43
  22. package/dist/core/containers.d.ts +12 -0
  23. package/dist/core/containers.js +5 -0
  24. package/dist/core/data-validation-overlay.d.ts +2 -1
  25. package/dist/core/data-validation-overlay.js +4 -4
  26. package/dist/core/data-validation.d.ts +9 -3
  27. package/dist/core/data-validation.js +21 -18
  28. package/dist/core/date-format.d.ts +20 -0
  29. package/dist/core/date-format.js +160 -0
  30. package/dist/core/date.d.ts +43 -8
  31. package/dist/core/date.js +18 -2
  32. package/dist/core/formula.d.ts +26 -0
  33. package/dist/core/formula.js +20 -4
  34. package/dist/core/grid-edits.d.ts +2 -3
  35. package/dist/core/grid-edits.js +69 -70
  36. package/dist/core/grid-shift.d.ts +58 -9
  37. package/dist/core/grid-shift.js +26 -4
  38. package/dist/core/image.d.ts +14 -10
  39. package/dist/core/image.js +9 -9
  40. package/dist/core/internal.d.ts +8 -2
  41. package/dist/core/limits.d.ts +23 -0
  42. package/dist/core/limits.js +4 -0
  43. package/dist/core/merge-index.d.ts +31 -0
  44. package/dist/core/merge-index.js +60 -0
  45. package/dist/core/merge.d.ts +5 -14
  46. package/dist/core/merge.js +18 -27
  47. package/dist/core/page-setup.d.ts +72 -2
  48. package/dist/core/page-setup.js +34 -8
  49. package/dist/core/pivot-table.d.ts +1 -1
  50. package/dist/core/pivot-table.js +67 -53
  51. package/dist/core/protection.js +2 -11
  52. package/dist/core/range.js +34 -30
  53. package/dist/core/row-input.js +2 -2
  54. package/dist/core/row.d.ts +5 -1
  55. package/dist/core/row.js +22 -23
  56. package/dist/core/style.d.ts +134 -75
  57. package/dist/core/style.js +112 -53
  58. package/dist/core/table-style.d.ts +1 -1
  59. package/dist/core/table-style.js +4 -4
  60. package/dist/core/table.d.ts +28 -10
  61. package/dist/core/table.js +54 -39
  62. package/dist/core/theme.js +6 -5
  63. package/dist/core/used-extent.d.ts +44 -0
  64. package/dist/core/used-extent.js +102 -0
  65. package/dist/core/value.d.ts +9 -0
  66. package/dist/core/value.js +36 -34
  67. package/dist/core/workbook-media.d.ts +34 -0
  68. package/dist/core/workbook-media.js +44 -0
  69. package/dist/core/workbook-styles.d.ts +67 -1
  70. package/dist/core/workbook-vba.js +3 -2
  71. package/dist/core/workbook.d.ts +23 -2
  72. package/dist/core/workbook.js +24 -40
  73. package/dist/core/worksheet-comments.d.ts +2 -1
  74. package/dist/core/worksheet-comments.js +20 -13
  75. package/dist/core/worksheet-merges.d.ts +44 -0
  76. package/dist/core/worksheet-merges.js +59 -0
  77. package/dist/core/worksheet-model.d.ts +9 -1
  78. package/dist/core/worksheet-model.js +24 -9
  79. package/dist/core/worksheet.d.ts +72 -12
  80. package/dist/core/worksheet.js +68 -70
  81. package/dist/customui/ribbon.js +6 -7
  82. package/dist/entries/core.d.ts +18 -13
  83. package/dist/entries/core.js +12 -6
  84. package/dist/entries/xlsx.d.ts +1 -1
  85. package/dist/errors.d.ts +64 -0
  86. package/dist/errors.js +22 -0
  87. package/dist/hex.d.ts +8 -0
  88. package/dist/hex.js +3 -0
  89. package/dist/io/csv/delimiter.d.ts +9 -0
  90. package/dist/io/csv/delimiter.js +6 -0
  91. package/dist/io/csv/read.js +11 -4
  92. package/dist/io/csv/write.d.ts +9 -2
  93. package/dist/io/csv/write.js +9 -38
  94. package/dist/io/opc/inflate.js +15 -1
  95. package/dist/io/opc/namespaces.d.ts +16 -0
  96. package/dist/io/opc/namespaces.js +4 -0
  97. package/dist/io/opc/part-paths.d.ts +10 -0
  98. package/dist/io/opc/part-paths.js +16 -1
  99. package/dist/io/opc/read-opc.d.ts +69 -10
  100. package/dist/io/opc/read-opc.js +49 -55
  101. package/dist/io/opc/read-options.d.ts +5 -1
  102. package/dist/io/opc/rels.d.ts +1 -6
  103. package/dist/io/opc/rels.js +1 -4
  104. package/dist/io/opc/sniff-format.d.ts +3 -3
  105. package/dist/io/opc/sniff-format.js +2 -2
  106. package/dist/io/style/xf-style.d.ts +48 -2
  107. package/dist/io/style/xf-style.js +14 -0
  108. package/dist/io/xlsb/formula.d.ts +2 -2
  109. package/dist/io/xlsb/formula.js +49 -34
  110. package/dist/io/xlsb/primitives.js +8 -14
  111. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  112. package/dist/io/xlsb/ptg-functions.js +385 -596
  113. package/dist/io/xlsb/read-styles.js +50 -83
  114. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  115. package/dist/io/xlsb/read-worksheet.js +73 -89
  116. package/dist/io/xlsb/read.d.ts +6 -3
  117. package/dist/io/xlsb/read.js +47 -29
  118. package/dist/io/xlsb/record-stream.d.ts +30 -0
  119. package/dist/io/xlsb/record-stream.js +20 -0
  120. package/dist/io/xlsb/record-types.d.ts +1 -5
  121. package/dist/io/xlsb/record-types.js +1 -5
  122. package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
  123. package/dist/io/xlsx/cell-accumulator.js +32 -9
  124. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  125. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  126. package/dist/io/xlsx/cell-value.d.ts +5 -3
  127. package/dist/io/xlsx/cell-value.js +13 -15
  128. package/dist/io/xlsx/color-xml.js +3 -2
  129. package/dist/io/xlsx/column-budget.d.ts +26 -0
  130. package/dist/io/xlsx/column-budget.js +22 -0
  131. package/dist/io/xlsx/comments.d.ts +6 -2
  132. package/dist/io/xlsx/comments.js +36 -31
  133. package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
  134. package/dist/io/xlsx/conditional-formatting.js +51 -46
  135. package/dist/io/xlsx/data-validation.d.ts +9 -7
  136. package/dist/io/xlsx/data-validation.js +38 -35
  137. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  138. package/dist/io/xlsx/edit-vba.js +15 -29
  139. package/dist/io/xlsx/font-xml.d.ts +7 -0
  140. package/dist/io/xlsx/font-xml.js +104 -0
  141. package/dist/io/xlsx/hyperlinks.d.ts +41 -7
  142. package/dist/io/xlsx/hyperlinks.js +40 -28
  143. package/dist/io/xlsx/images.js +24 -25
  144. package/dist/io/xlsx/namespaces.d.ts +16 -0
  145. package/dist/io/xlsx/namespaces.js +7 -0
  146. package/dist/io/xlsx/package-plan.d.ts +23 -3
  147. package/dist/io/xlsx/package-plan.js +66 -25
  148. package/dist/io/xlsx/part-names.d.ts +41 -0
  149. package/dist/io/xlsx/part-names.js +46 -0
  150. package/dist/io/xlsx/pivot.js +8 -7
  151. package/dist/io/xlsx/read-parts.d.ts +59 -0
  152. package/dist/io/xlsx/read-parts.js +252 -0
  153. package/dist/io/xlsx/read-pivot.js +3 -1
  154. package/dist/io/xlsx/read-repair.d.ts +26 -0
  155. package/dist/io/xlsx/read-repair.js +39 -0
  156. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
  157. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
  158. package/dist/io/xlsx/read-rows.d.ts +18 -5
  159. package/dist/io/xlsx/read-rows.js +50 -37
  160. package/dist/io/xlsx/read-shared-strings.js +5 -3
  161. package/dist/io/xlsx/read-styles.d.ts +1 -6
  162. package/dist/io/xlsx/read-styles.js +65 -109
  163. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  164. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +11 -1
  166. package/dist/io/xlsx/read-worksheet.js +51 -193
  167. package/dist/io/xlsx/read.d.ts +5 -13
  168. package/dist/io/xlsx/read.js +111 -436
  169. package/dist/io/xlsx/rich-text.js +1 -1
  170. package/dist/io/xlsx/row-position.d.ts +18 -0
  171. package/dist/io/xlsx/row-position.js +11 -0
  172. package/dist/io/xlsx/row-xml.d.ts +128 -0
  173. package/dist/io/xlsx/row-xml.js +203 -0
  174. package/dist/io/xlsx/shared-formulas.js +2 -2
  175. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  176. package/dist/io/xlsx/sheet-properties.js +166 -37
  177. package/dist/io/xlsx/style-elements.d.ts +43 -0
  178. package/dist/io/xlsx/style-elements.js +156 -0
  179. package/dist/io/xlsx/styles.d.ts +12 -3
  180. package/dist/io/xlsx/styles.js +5 -204
  181. package/dist/io/xlsx/tables.js +11 -14
  182. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  183. package/dist/io/xlsx/theme-xml.js +107 -69
  184. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  185. package/dist/io/xlsx/threaded-comments.js +3 -1
  186. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  187. package/dist/io/xlsx/workbook-xml.js +84 -57
  188. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  189. package/dist/io/xlsx/worksheet-xml.js +59 -242
  190. package/dist/io/xlsx/write-stream.d.ts +71 -16
  191. package/dist/io/xlsx/write-stream.js +79 -41
  192. package/dist/io/xlsx/write.d.ts +6 -2
  193. package/dist/io/xlsx/write.js +217 -153
  194. package/dist/rel-type.d.ts +16 -0
  195. package/dist/rel-type.js +6 -0
  196. package/dist/token-set.d.ts +28 -0
  197. package/dist/token-set.js +7 -0
  198. package/dist/vba/bytes.d.ts +14 -5
  199. package/dist/vba/bytes.js +16 -6
  200. package/dist/vba/cfb-format.d.ts +32 -0
  201. package/dist/vba/cfb-format.js +13 -0
  202. package/dist/vba/cfb-writer.d.ts +9 -0
  203. package/dist/vba/cfb-writer.js +28 -25
  204. package/dist/vba/cfb.d.ts +13 -1
  205. package/dist/vba/cfb.js +73 -57
  206. package/dist/vba/codepage.js +2 -1
  207. package/dist/vba/errors.d.ts +7 -0
  208. package/dist/vba/ms-ovba.js +79 -23
  209. package/dist/vba/project-editor.js +35 -40
  210. package/dist/vba/project.d.ts +11 -1
  211. package/dist/vba/project.js +40 -21
  212. package/dist/vba/vba-encoding.js +5 -6
  213. package/dist/xml/xml-attrs.d.ts +37 -0
  214. package/dist/xml/xml-attrs.js +30 -0
  215. package/dist/xml/xml-chars.d.ts +40 -0
  216. package/dist/xml/xml-chars.js +12 -0
  217. package/dist/xml/xml-namespaces.d.ts +31 -0
  218. package/dist/xml/xml-namespaces.js +50 -0
  219. package/dist/xml/xml-read.d.ts +128 -81
  220. package/dist/xml/xml-read.js +161 -178
  221. package/dist/xml/xml-scan.d.ts +78 -0
  222. package/dist/xml/xml-scan.js +180 -0
  223. package/dist/xml/xml.d.ts +29 -28
  224. package/dist/xml/xml.js +24 -24
  225. package/package.json +13 -9
@@ -1,23 +1,32 @@
1
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';
3
+ import {} from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
4
5
  import { escapeAttr, textAttr } from '../../xml/xml.js';
5
- export function collectHyperlinks(sheet) {
6
+ import { relAttr } from '../opc/namespaces.js';
7
+ export function collectHyperlinks(cells) {
6
8
  const links = [];
7
- for (const { cells } of sheet.rows()) {
8
- for (const cell of cells) {
9
- const value = cell.value;
10
- if (isHyperlinkValue(value)) {
11
- links.push({
12
- ref: value.range ?? cell.address,
13
- target: value.hyperlink,
14
- ...(value.tooltip !== undefined ? { tooltip: value.tooltip } : {}),
15
- });
16
- }
9
+ for (const cell of cells) {
10
+ const value = cell.value;
11
+ if (isHyperlinkValue(value)) {
12
+ links.push({
13
+ ref: value.range ?? cell.address,
14
+ row: cell.row,
15
+ col: cell.col,
16
+ target: value.hyperlink,
17
+ ...(value.tooltip !== undefined ? { tooltip: value.tooltip } : {}),
18
+ });
17
19
  }
18
20
  }
19
21
  return links;
20
22
  }
23
+ export function* liveCells(sheet) {
24
+ for (const { cells } of sheet.rows())
25
+ yield* cells;
26
+ }
27
+ export function isExternalHyperlink(link) {
28
+ return link.relId !== undefined && link.target !== undefined;
29
+ }
21
30
  export function planHyperlinks(links, rels) {
22
31
  return links.map((link) => {
23
32
  const tooltip = link.tooltip !== undefined ? { tooltip: link.tooltip } : {};
@@ -40,24 +49,27 @@ export function hyperlinksXml(links) {
40
49
  .join('');
41
50
  return `<hyperlinks>${items}</hyperlinks>`;
42
51
  }
43
- export function parseSheetHyperlinks(xml) {
52
+ export function sheetHyperlinkPass() {
44
53
  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
- });
54
+ return {
55
+ handlers: {
56
+ onOpen(name, attrs, _selfClosing, scope) {
57
+ if (localName(name) !== 'hyperlink')
58
+ return;
59
+ const ref = attrs.ref;
60
+ if (ref === undefined)
61
+ return;
62
+ const rid = relAttr(scope, attrs, 'id');
63
+ links.push({
64
+ ref,
65
+ ...(rid !== undefined ? { rid } : {}),
66
+ ...(attrs.location !== undefined ? { location: attrs.location } : {}),
67
+ ...(attrs.tooltip !== undefined ? { tooltip: attrs.tooltip } : {}),
68
+ });
69
+ },
58
70
  },
59
- });
60
- return links;
71
+ result: () => links,
72
+ };
61
73
  }
62
74
  export function applyHyperlinks(sheet, links, targetOf) {
63
75
  for (const link of links) {
@@ -1,25 +1,27 @@
1
1
  import { isImageEditAs, isOneCellAnchor, } from '../../core/image.js';
2
- import { enumToken, localName, numFinite, parseXml, TextCapture } from '../../xml/xml-read.js';
2
+ import { enumToken, numFinite } from '../../xml/xml-attrs.js';
3
+ import { openElements, parseXml, TextCapture } from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
3
5
  import { checkedToken, numAttr, numberText, XML_DECLARATION } from '../../xml/xml.js';
4
- import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
6
+ import { relAttr, RELATIONSHIPS_NS } from '../opc/namespaces.js';
5
7
  import { relationship, relationshipsPart } from '../opc/rels.js';
6
8
  import { DRAWINGML_NS, XDR_NS } from './namespaces.js';
7
9
  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
- };
10
+ const IMAGE_CONTENT_TYPES = new Map([
11
+ ['png', 'image/png'],
12
+ ['jpg', 'image/jpeg'],
13
+ ['jpeg', 'image/jpeg'],
14
+ ['gif', 'image/gif'],
15
+ ['bmp', 'image/bmp'],
16
+ ['tif', 'image/tiff'],
17
+ ['tiff', 'image/tiff'],
18
+ ['emf', 'image/x-emf'],
19
+ ['wmf', 'image/x-wmf'],
20
+ ['svg', 'image/svg+xml'],
21
+ ]);
20
22
  export function imageContentType(extension) {
21
23
  const ext = extension.toLowerCase();
22
- return IMAGE_CONTENT_TYPES[ext] ?? `image/${ext}`;
24
+ return IMAGE_CONTENT_TYPES.get(ext) ?? `image/${ext}`;
23
25
  }
24
26
  export function drawingXml(images) {
25
27
  const anchors = images.map((image, i) => anchorXml(image, i + 1)).join('');
@@ -84,7 +86,7 @@ export function parseDrawing(xml) {
84
86
  let picDepth = 0;
85
87
  const coord = new TextCapture(COORDINATES);
86
88
  parseXml(xml, {
87
- onOpen(name, attrs, selfClosing) {
89
+ onOpen(name, attrs, selfClosing, scope) {
88
90
  const local = localName(name);
89
91
  if (local === 'twoCellAnchor' || local === 'oneCellAnchor') {
90
92
  from = blankPoint();
@@ -115,7 +117,7 @@ export function parseDrawing(xml) {
115
117
  ext = { cx, cy };
116
118
  }
117
119
  else if (local === 'blip') {
118
- const value = attrs['r:embed'] ?? attrs.embed;
120
+ const value = relAttr(scope, attrs, 'embed');
119
121
  if (value !== undefined)
120
122
  embed = value;
121
123
  }
@@ -161,14 +163,11 @@ export function parseDrawing(xml) {
161
163
  }
162
164
  const UNMODELED_DRAWING_CONTENT = new Set(['graphicFrame', 'sp', 'cxnSp', 'grpSp']);
163
165
  export function drawingHasUnmodeledContent(xml) {
164
- let found = false;
165
- parseXml(xml, {
166
- onOpen(name) {
167
- if (!found && UNMODELED_DRAWING_CONTENT.has(localName(name)))
168
- found = true;
169
- },
170
- });
171
- return found;
166
+ for (const { local } of openElements(xml)) {
167
+ if (UNMODELED_DRAWING_CONTENT.has(local))
168
+ return true;
169
+ }
170
+ return false;
172
171
  }
173
172
  const COORDINATES = new Set(['col', 'colOff', 'row', 'rowOff']);
174
173
  function setCoordinate(point, coord, value) {
@@ -9,6 +9,7 @@
9
9
  * The package-level URIs (`.rels`, content types, the relationship vocabulary) describe the
10
10
  * container rather than the spreadsheet inside it, and live in `../opc/namespaces.ts`.
11
11
  */
12
+ import type { NamespaceScope } from '../../xml/xml-namespaces.ts';
12
13
  /**
13
14
  * SpreadsheetML main namespace: the default `xmlns` of the workbook,
14
15
  * worksheet, styles, sharedStrings, comments, table and pivot parts.
@@ -53,3 +54,18 @@ export declare const DATABAR_LINK_EXT_URI = "{B025F937-C7B1-47D3-B67F-A62EFF666E
53
54
  export declare const DATA_VALIDATION_EXT_URI = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}";
54
55
  export declare const SLICER_LIST_EXT_URI = "{A8765BA9-456A-4dab-B4F3-ACF838C121DE}";
55
56
  export declare const SLICER_CACHES_EXT_URI = "{BBE1A952-AA13-448e-AADC-164F8A28A991}";
57
+ /**
58
+ * Whether an element belongs to the standard SpreadsheetML vocabulary rather than to an extension.
59
+ *
60
+ * This used to be spelled `!name.includes(':')`, on the assumption that only extension elements carry
61
+ * a prefix. A worksheet may bind the *main* namespace to a prefix instead, which is legal and which
62
+ * real toolchains emit, and then every element has a colon in it: such a file had every one of its
63
+ * data validations and conditional formats read as an unknown extension and dropped, silently.
64
+ *
65
+ * An unprefixed element counts even when the part declares no default namespace. A part with no
66
+ * `xmlns` at all is not conforming OOXML, but this scanner does not reject it, such parts were read
67
+ * before, and a fix that gains files by resolving namespaces should not lose files that have none.
68
+ */
69
+ export declare function isMainNamespaceElement(scope: NamespaceScope, name: string): boolean;
70
+ /** The complement of {@link isMainNamespaceElement}: an element of one of the extension vocabularies. */
71
+ export declare function isExtensionElement(scope: NamespaceScope, name: string): boolean;
@@ -11,3 +11,10 @@ export const DATABAR_LINK_EXT_URI = '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}';
11
11
  export const DATA_VALIDATION_EXT_URI = '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}';
12
12
  export const SLICER_LIST_EXT_URI = '{A8765BA9-456A-4dab-B4F3-ACF838C121DE}';
13
13
  export const SLICER_CACHES_EXT_URI = '{BBE1A952-AA13-448e-AADC-164F8A28A991}';
14
+ export function isMainNamespaceElement(scope, name) {
15
+ const namespace = scope.elementNamespace(name);
16
+ return namespace === SPREADSHEETML_NS || (namespace === undefined && !name.includes(':'));
17
+ }
18
+ export function isExtensionElement(scope, name) {
19
+ return !isMainNamespaceElement(scope, name);
20
+ }
@@ -1,11 +1,13 @@
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';
5
6
  import type { Worksheet } from '../../core/worksheet.ts';
6
7
  import type { CommentCell } from './comments.ts';
8
+ import type { HyperlinkPlan } from './hyperlinks.ts';
7
9
  import type { DrawingImage } from './images.ts';
8
- export declare class SheetRelIds {
10
+ export declare class RelIdAllocator {
9
11
  #private;
10
12
  /** The next relationship id (`rId1`, `rId2`, …), advancing the counter. */
11
13
  next(): string;
@@ -94,10 +96,28 @@ export interface MediaPart {
94
96
  readonly extension: string;
95
97
  readonly data: Uint8Array;
96
98
  }
99
+ export interface ResolvedMedia {
100
+ readonly number: number;
101
+ readonly image: WorkbookImage;
102
+ }
97
103
  export interface MediaPlan {
98
104
  readonly parts: readonly MediaPart[];
99
- readonly numberById: ReadonlyMap<number, number>;
100
105
  readonly extensions: readonly string[];
106
+ /** The media part number and registered image for a referenced id. Total over the ids
107
+ * {@link planMedia} saw on the sheets it planned, because planning already threw on any it could
108
+ * not resolve; an id from anywhere else is a caller bug and is reported as one. */
109
+ resolve(id: number): ResolvedMedia;
101
110
  }
102
111
  export declare function planMedia(workbook: Workbook, sheets: readonly Worksheet[]): MediaPlan;
103
- export declare function planPreservedParts(workbook: Workbook, generatedDrawingCount: number, generatedMediaCount: number): PreservedPlan;
112
+ export declare function planPreservedParts(workbook: Workbook, generatedDrawingCount: number, generatedMediaCount: number, generatedPivotCount: number): PreservedPlan;
113
+ export interface SheetPlan {
114
+ readonly tables: TablePlan[];
115
+ readonly drawing: DrawingPlan | null;
116
+ readonly comments: CommentPlan | null;
117
+ readonly threadedComments: ThreadedCommentPlan | null;
118
+ readonly printerSettings: PrinterSettingsPlan | null;
119
+ readonly hyperlinks: HyperlinkPlan[];
120
+ readonly background: BackgroundPlan | null;
121
+ readonly preservedRefs: PreservedReferencePlan[];
122
+ readonly pivots: PivotPlan[];
123
+ }
@@ -1,49 +1,65 @@
1
- import { AuthoringError } from '../../errors.js';
1
+ import { AuthoringError, InternalError, quoted } 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, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, vmlDrawingPart, } from './part-names.js';
4
5
  import { applyThemeOverrides } from './theme-xml.js';
5
- export class SheetRelIds {
6
+ export class RelIdAllocator {
6
7
  #next = 1;
7
8
  next() {
8
9
  return `rId${this.#next++}`;
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 ${quoted(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
- export function planPreservedParts(workbook, generatedDrawingCount, generatedMediaCount) {
54
+ export function planPreservedParts(workbook, generatedDrawingCount, generatedMediaCount, generatedPivotCount) {
42
55
  const sheets = workbook.worksheets;
43
56
  const numbering = {
44
57
  drawing: generatedDrawingCount,
45
58
  vml: sheets.length,
46
59
  media: generatedMediaCount,
60
+ pivotTable: generatedPivotCount,
61
+ pivotCacheDefinition: generatedPivotCount,
62
+ pivotCacheRecords: generatedPivotCount,
47
63
  };
48
64
  const remap = new Map();
49
65
  const theme = workbook.themePart;
@@ -64,7 +80,7 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
64
80
  const emitted = new Map();
65
81
  for (const reference of allReferences) {
66
82
  for (const part of reference.parts) {
67
- const newPath = remap.get(part.path);
83
+ const newPath = resolveRemapped(remap, part.path);
68
84
  if (emitted.has(newPath))
69
85
  continue;
70
86
  const rels = part.rels.flatMap((rel) => {
@@ -84,24 +100,24 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
84
100
  : new TextEncoder().encode(applyThemeOverrides(new TextDecoder().decode(part.bytes), overrides)),
85
101
  contentType: part.contentType,
86
102
  relsPath: rels.length === 0 ? null : relsPathFor(newPath),
87
- relsXml: rels.length === 0 ? null : preservedRelsXml(rels),
103
+ relsXml: rels.length === 0 ? null : relsPartXml(rels),
88
104
  });
89
105
  }
90
106
  }
91
107
  const perSheet = sheets.map((sheet) => sheet.preservedReferences.map((reference) => ({
92
108
  element: reference.element,
93
109
  relType: reference.relType,
94
- entryPath: remap.get(reference.entryPath),
110
+ entryPath: resolveRemapped(remap, reference.entryPath),
95
111
  })));
96
112
  const workbookRefs = workbook.preservedReferences.map((reference) => ({
97
113
  relType: reference.relType,
98
- entryPath: remap.get(reference.entryPath),
114
+ entryPath: resolveRemapped(remap, reference.entryPath),
99
115
  pivotCacheId: reference.pivotCacheId,
100
116
  externalReferenceIndex: reference.externalReferenceIndex,
101
117
  }));
102
118
  const rootRefs = workbook.preservedRootReferences.map((reference) => ({
103
119
  relType: reference.relType,
104
- entryPath: remap.get(reference.entryPath),
120
+ entryPath: resolveRemapped(remap, reference.entryPath),
105
121
  }));
106
122
  return {
107
123
  perSheet,
@@ -111,14 +127,39 @@ export function planPreservedParts(workbook, generatedDrawingCount, generatedMed
111
127
  themeEmitted: theme !== undefined,
112
128
  };
113
129
  }
130
+ function resolveRemapped(remap, path) {
131
+ const resolved = remap.get(path);
132
+ if (resolved === undefined) {
133
+ throw new InternalError(`preserved part ${quoted(path)} was never assigned a new path`);
134
+ }
135
+ return resolved;
136
+ }
114
137
  function preservedPartPath(originalPath, numbering) {
115
138
  const ext = extensionOf(originalPath);
116
- if (ext.toLowerCase() === 'vml')
117
- return `xl/drawings/vmlDrawing${++numbering.vml}.vml`;
139
+ if (ext === 'vml')
140
+ return vmlDrawingPart(++numbering.vml);
118
141
  if (originalPath.startsWith('xl/media/'))
119
- return `xl/media/image${++numbering.media}.${ext}`;
120
- if (originalPath.startsWith('xl/drawings/') && ext.toLowerCase() === 'xml') {
121
- return `xl/drawings/drawing${++numbering.drawing}.xml`;
142
+ return mediaPart(++numbering.media, ext);
143
+ if (originalPath.startsWith('xl/drawings/') && ext === 'xml') {
144
+ return drawingPart(++numbering.drawing);
145
+ }
146
+ if (isNumberedPart(originalPath, PIVOT_TABLE_PREFIX)) {
147
+ return pivotTablePart(++numbering.pivotTable);
148
+ }
149
+ if (isNumberedPart(originalPath, PIVOT_CACHE_DEFINITION_PREFIX)) {
150
+ return pivotCacheDefinitionPart(++numbering.pivotCacheDefinition);
151
+ }
152
+ if (isNumberedPart(originalPath, PIVOT_CACHE_RECORDS_PREFIX)) {
153
+ return pivotCacheRecordsPart(++numbering.pivotCacheRecords);
122
154
  }
123
155
  return originalPath;
124
156
  }
157
+ const PIVOT_TABLE_PREFIX = numberedPartPrefix(pivotTablePart);
158
+ const PIVOT_CACHE_DEFINITION_PREFIX = numberedPartPrefix(pivotCacheDefinitionPart);
159
+ const PIVOT_CACHE_RECORDS_PREFIX = numberedPartPrefix(pivotCacheRecordsPart);
160
+ function numberedPartPrefix(part) {
161
+ return part(1).slice(0, -'1.xml'.length);
162
+ }
163
+ function isNumberedPart(path, prefix) {
164
+ return path.startsWith(prefix) && /^\d+\.xml$/.test(path.slice(prefix.length));
165
+ }
@@ -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,5 @@
1
1
  import { encodeAddress } from '../../core/address.js';
2
- import { escapeAttr, XML_DECLARATION } from '../../xml/xml.js';
2
+ import { boolAttr, escapeAttr, numberText, XML_DECLARATION } from '../../xml/xml.js';
3
3
  import { RELATIONSHIPS_NS } from '../opc/namespaces.js';
4
4
  import { SPREADSHEETML_NS } from './namespaces.js';
5
5
  const METRIC_CAPTIONS = {
@@ -30,9 +30,10 @@ export function pivotCacheDefinitionXml(table) {
30
30
  const numeric = field.numeric;
31
31
  const descriptor = numeric === null
32
32
  ? `<sharedItems${blank}/>`
33
- : `<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" ` +
34
- `containsInteger="${numeric.allInteger ? 1 : 0}"${blank} ` +
35
- `minValue="${numeric.min}" maxValue="${numeric.max}"/>`;
33
+ : `<sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1"` +
34
+ boolAttr('containsInteger', numeric.allInteger) +
35
+ blank +
36
+ ` minValue="${numberText(numeric.min)}" maxValue="${numberText(numeric.max)}"/>`;
36
37
  return `<cacheField name="${escapeAttr(field.name)}" numFmtId="0">${descriptor}</cacheField>`;
37
38
  })
38
39
  .join('');
@@ -56,8 +57,8 @@ export function pivotCacheRecordsXml(table) {
56
57
  `</pivotCacheRecords>`);
57
58
  }
58
59
  export function pivotTableXml(table, name, cacheId) {
59
- const rowField = table.rowFields[0];
60
- const columnField = table.columnFields[0];
60
+ const rowField = table.rowFields[0] ?? 0;
61
+ const columnField = table.columnFields[0] ?? 0;
61
62
  const rowGroups = table.cacheFields[rowField]?.sharedItems?.length ?? 1;
62
63
  const columnGroups = table.cacheFields[columnField]?.sharedItems?.length ?? 1;
63
64
  const location = `A1:${encodeAddress(2 + columnGroups, 3 + rowGroups)}`;
@@ -107,7 +108,7 @@ function sharedItemXml(item) {
107
108
  case 'string':
108
109
  return `<s v="${escapeAttr(item.value)}"/>`;
109
110
  case 'number':
110
- return `<n v="${item.value}"/>`;
111
+ return `<n v="${numberText(item.value)}"/>`;
111
112
  case 'blank':
112
113
  return '<m/>';
113
114
  }
@@ -0,0 +1,59 @@
1
+ import type { CommentThread } from '../../core/comment-thread.ts';
2
+ import { Workbook } from '../../core/workbook.ts';
3
+ import type { Worksheet } from '../../core/worksheet.ts';
4
+ import type { CollectingPass } from '../../xml/xml-read.ts';
5
+ import { type PackageAccessors, type PartRelationships } from '../opc/read-opc.ts';
6
+ import { type ParsedComment } from './comments.ts';
7
+ /**
8
+ * What every part reader in one package read shares: the inflated package, the model being built, the
9
+ * content-type resolver a preserved part's closure is captured through, and the interning map that
10
+ * makes one workbook image per media part however many places point at it.
11
+ *
12
+ * None of the four varies within a read, and threading them positionally is what let `internImage`
13
+ * end up taking the same three in a different order from both of its callers. What genuinely varies
14
+ * -- a part's relationships, the sheet being filled -- stays a positional argument, and the sheet's
15
+ * rels in particular are deliberately *not* in here: they are per sheet, and a stale copy of them
16
+ * would send every sheet's parts to the first sheet's.
17
+ */
18
+ export interface PackageReadContext {
19
+ readonly pkg: PackageAccessors;
20
+ readonly workbook: Workbook;
21
+ readonly contentTypeOf: (path: string) => string;
22
+ readonly imageIdByMediaPath: Map<string, number>;
23
+ }
24
+ export declare function readSheetComments(sheetRels: PartRelationships): Map<string, ParsedComment> | undefined;
25
+ export declare function readWorkbookPersons({ workbook }: PackageReadContext, workbookRels: PartRelationships): void;
26
+ export declare function readWorkbookTheme(context: PackageReadContext, workbookRels: PartRelationships): void;
27
+ export declare function readSheetCommentThreads({ workbook }: PackageReadContext, sheetRels: PartRelationships): CommentThread[];
28
+ export declare function readSheetPrinterSettings(sheetRels: PartRelationships): Uint8Array | undefined;
29
+ export declare function readSheetImages(context: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;
30
+ export declare function readSheetBackground(context: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;
31
+ export declare function readSheetPreservedReferences(context: PackageReadContext, sheetRels: PartRelationships, referenceRelIds: WorksheetReferenceRelIds, sheet: Worksheet): void;
32
+ export declare function readWorkbookPreservedReferences(context: PackageReadContext, workbookRels: PartRelationships, registrations: WorkbookRegistrations): void;
33
+ export declare function readRootPreservedReferences(context: PackageReadContext): void;
34
+ /** The two `<pivotCaches>`/`<externalReferences>` registries a preserved reference is wired by,
35
+ * gathered from the workbook part's own scan rather than from two more of it. */
36
+ export interface WorkbookRegistrations {
37
+ readonly cacheIdByRelId: ReadonlyMap<string, string>;
38
+ readonly externalIndexByRelId: ReadonlyMap<string, number>;
39
+ }
40
+ export declare function pivotCacheRegistrationsPass(): CollectingPass<ReadonlyMap<string, string>>;
41
+ export declare function externalReferenceRegistrationsPass(): CollectingPass<ReadonlyMap<string, number>>;
42
+ /** The `r:id` each of the two element-wired references carries, or `undefined` where absent. */
43
+ export type WorksheetReferenceRelIds = Readonly<Partial<Record<'drawing' | 'legacyDrawingHF', string>>>;
44
+ /**
45
+ * A pass gathering the `r:id` of the first `<drawing>` and `<legacyDrawingHF>` in a worksheet.
46
+ *
47
+ * These two references live in the worksheet body rather than being distinguishable by relationship
48
+ * Type, since a header/footer VML and a comment VML share the `vmlDrawing` type, so the specific
49
+ * relationship is found by reading the element's own `r:id`.
50
+ *
51
+ * A pass rather than its own scan, because the sheet part is the largest in a package by a wide
52
+ * margin and this ran a full extra scan of it, once or twice per sheet, for two attributes.
53
+ * `readSheet` already builds a multi-pass single parse of it whose own comment records that five
54
+ * separate scans "spent 45% of a large file's read on four scans that matched no element"; this was
55
+ * the sixth scan, added later, that the same argument covers.
56
+ */
57
+ export declare function worksheetReferencePass(): CollectingPass<WorksheetReferenceRelIds>;
58
+ export declare function readSheetTables({ pkg }: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;
59
+ export declare function readSheetPivotTables({ pkg }: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;