@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -0,0 +1,6 @@
1
+ import { type SheetSplice } from '../../core/formula-references.ts';
2
+ /**
3
+ * A preserved part's bytes with the references it spells moved through `splices`, in order, or the
4
+ * same bytes when the part is of a kind that spells none or nothing in it moved.
5
+ */
6
+ export declare function splicePreservedPart(bytes: Uint8Array, contentType: string, splices: readonly SheetSplice[]): Uint8Array;
@@ -0,0 +1,74 @@
1
+ import { spliceFormula } from '../../core/formula-references.js';
2
+ import { splicePivotSource } from '../../core/pivot-table.js';
3
+ import { XmlParseError } from '../../xml/errors.js';
4
+ import { elementRange, elementRanges, openElements } from '../../xml/xml-read.js';
5
+ import { decodeEntities } from '../../xml/xml-scan.js';
6
+ import { escapeText, startTag } from '../../xml/xml.js';
7
+ import { relAttr } from '../opc/namespaces.js';
8
+ const chartReferences = (xml, splices) => {
9
+ const edits = [];
10
+ for (const range of elementRanges(xml, 'f')) {
11
+ const content = xml.slice(range.contentStart, range.contentEnd);
12
+ if (content.includes('<'))
13
+ continue;
14
+ const formula = decodeEntities(content);
15
+ const moved = splices.reduce((text, edit) => spliceFormula(text, undefined, edit), formula);
16
+ if (moved !== formula) {
17
+ edits.push({ start: range.contentStart, end: range.contentEnd, text: escapeText(moved) });
18
+ }
19
+ }
20
+ return edits;
21
+ };
22
+ const pivotCacheSource = (xml, splices) => {
23
+ for (const source of openElements(xml, 'worksheetSource')) {
24
+ if (relAttr(source.scope, source.attrs, 'id') !== undefined)
25
+ return [];
26
+ break;
27
+ }
28
+ const range = elementRange(xml, ['cacheSource', 'worksheetSource']);
29
+ const { sheet, ref } = range?.attrs ?? {};
30
+ if (range === undefined || sheet === undefined || ref === undefined)
31
+ return [];
32
+ const moved = splices.reduce((text, edit) => splicePivotSource(sheet, text, edit), ref);
33
+ if (moved === ref)
34
+ return [];
35
+ return [
36
+ {
37
+ start: range.start,
38
+ end: range.end,
39
+ text: startTag(range.name, { ...range.attrs, ref: moved }, true),
40
+ },
41
+ ];
42
+ };
43
+ const REWRITERS = new Map([
44
+ ['application/vnd.openxmlformats-officedocument.drawingml.chart+xml', chartReferences],
45
+ ['application/vnd.ms-office.chartex+xml', chartReferences],
46
+ [
47
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml',
48
+ pivotCacheSource,
49
+ ],
50
+ ].map(([type, rewrite]) => [type.toLowerCase(), rewrite]));
51
+ export function splicePreservedPart(bytes, contentType, splices) {
52
+ if (splices.length === 0)
53
+ return bytes;
54
+ const rewrite = REWRITERS.get(contentType.toLowerCase());
55
+ if (rewrite === undefined)
56
+ return bytes;
57
+ const source = new TextDecoder().decode(bytes);
58
+ let edits;
59
+ try {
60
+ edits = rewrite(source, splices);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof XmlParseError)
64
+ return bytes;
65
+ throw error;
66
+ }
67
+ if (edits.length === 0)
68
+ return bytes;
69
+ let xml = source;
70
+ for (const edit of edits.toSorted((a, b) => b.start - a.start)) {
71
+ xml = xml.slice(0, edit.start) + edit.text + xml.slice(edit.end);
72
+ }
73
+ return new TextEncoder().encode(xml);
74
+ }
@@ -0,0 +1,29 @@
1
+ import type { Worksheet } from '../../core/worksheet.ts';
2
+ /** One `<comment>` read back from a comments part. */
3
+ export interface ParsedComment {
4
+ readonly text: string;
5
+ /**
6
+ * The thread head id this comment is the legacy fallback for, read off its synthetic `tc={headId}`
7
+ * author; absent for a user's own note.
8
+ */
9
+ readonly threadId?: string;
10
+ }
11
+ /**
12
+ * Parse a `comments{n}.xml` part into a map of A1 reference → comment. Text runs within one comment are
13
+ * concatenated; an author-name run is Excel's own convention and is not stripped, so a note reads back
14
+ * as exactly the text that was written.
15
+ */
16
+ export declare function parseComments(xml: string): Map<string, ParsedComment>;
17
+ /**
18
+ * Apply a parsed comments part onto a sheet's cells as notes, addressing each by its A1 reference.
19
+ *
20
+ * A thread's legacy fallback is not a note and does not become one: its text is boilerplate wrapping a
21
+ * copy of the conversation, so surfacing it as `cell.note` hands the caller garbage, and on write it
22
+ * would be re-emitted as a plain note, destroying the `tc=`/`xr:uid` binding and leaving Excel unable to
23
+ * see the thread at all.
24
+ *
25
+ * Suppressed only for a conversation the reader actually holds: a file whose thread part is missing or
26
+ * damaged has nothing else left, so there the boilerplate is kept rather than the content lost. Call
27
+ * after the sheet's threads are restored, since that is what this reads to decide.
28
+ */
29
+ export declare function applyNotes(sheet: Worksheet, comments: ReadonlyMap<string, ParsedComment>): void;
@@ -0,0 +1,75 @@
1
+ import { tryDecodeCellRef } from '../../core/address.js';
2
+ import { numInteger } from '../../xml/xml-attrs.js';
3
+ import { parseXml, TextCapture } from '../../xml/xml-read.js';
4
+ import { localName } from '../../xml/xml-scan.js';
5
+ import { RunAccumulator } from './read-rich-runs.js';
6
+ const COMMENT_EMPTY_CLOSES = new Set(['author', 'text']);
7
+ const THREAD_AUTHOR_PREFIX = 'tc=';
8
+ export function parseComments(xml) {
9
+ const comments = new Map();
10
+ const authors = [];
11
+ let currentRef;
12
+ let currentAuthorId;
13
+ const author = new TextCapture('author');
14
+ const bodyRuns = new RunAccumulator({ container: 'text', readRuns: false });
15
+ let body = '';
16
+ parseXml(xml, {
17
+ onOpen(name, attrs, selfClosing) {
18
+ const local = localName(name);
19
+ if (local === 'comment') {
20
+ currentRef = attrs.ref;
21
+ currentAuthorId = attrs.authorId;
22
+ body = '';
23
+ }
24
+ if (!bodyRuns.open(local, attrs, selfClosing))
25
+ author.open(local, selfClosing);
26
+ },
27
+ onText(text) {
28
+ bodyRuns.text(text);
29
+ author.text(text);
30
+ },
31
+ onClose(name) {
32
+ const local = localName(name);
33
+ const bodyClose = bodyRuns.close(local);
34
+ if (bodyClose === 'container') {
35
+ body = bodyRuns.plainText;
36
+ return;
37
+ }
38
+ if (bodyClose === 'claimed')
39
+ return;
40
+ const text = author.close(local);
41
+ if (local === 'author') {
42
+ authors.push(text ?? '');
43
+ }
44
+ else if (local === 'comment' && currentRef !== undefined) {
45
+ const threadId = threadIdOf(authors[numInteger(currentAuthorId, 0) ?? -1]);
46
+ comments.set(currentRef, {
47
+ text: body,
48
+ ...(threadId !== undefined ? { threadId } : {}),
49
+ });
50
+ currentRef = undefined;
51
+ currentAuthorId = undefined;
52
+ }
53
+ },
54
+ }, { closeEmptyElements: COMMENT_EMPTY_CLOSES });
55
+ return comments;
56
+ }
57
+ function threadIdOf(author) {
58
+ if (author === undefined || !author.startsWith(THREAD_AUTHOR_PREFIX))
59
+ return undefined;
60
+ const id = author.slice(THREAD_AUTHOR_PREFIX.length);
61
+ return id === '' ? undefined : id;
62
+ }
63
+ export function applyNotes(sheet, comments) {
64
+ const headIds = new Set(sheet.commentThreads.flatMap((thread) => {
65
+ const head = thread.comments[0];
66
+ return head === undefined ? [] : [head.id];
67
+ }));
68
+ for (const [ref, comment] of comments) {
69
+ if (comment.threadId !== undefined && headIds.has(comment.threadId))
70
+ continue;
71
+ if (tryDecodeCellRef(ref) === undefined)
72
+ continue;
73
+ sheet.getCell(ref).note = comment.text;
74
+ }
75
+ }
@@ -3,7 +3,7 @@ import { Workbook } from '../../core/workbook.ts';
3
3
  import type { Worksheet } from '../../core/worksheet.ts';
4
4
  import type { CollectingPass } from '../../xml/xml-read.ts';
5
5
  import { type PackageAccessors, type PartRelationships } from '../opc/read-opc.ts';
6
- import { type ParsedComment } from './comments.ts';
6
+ import { type ParsedComment } from './read-comments.ts';
7
7
  /**
8
8
  * What every part reader in one package read shares: the inflated package, the model being built, the
9
9
  * content-type resolver a preserved part's closure is captured through, and the interning map that
@@ -55,5 +55,5 @@ export type WorksheetReferenceRelIds = Readonly<Partial<Record<'drawing' | 'lega
55
55
  * the sixth scan, added later, that the same argument covers.
56
56
  */
57
57
  export declare function worksheetReferencePass(): CollectingPass<WorksheetReferenceRelIds>;
58
- export declare function readSheetTables({ pkg }: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;
58
+ export declare function readSheetTables({ pkg }: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet, definedNames: ReadonlySet<string>): void;
59
59
  export declare function readSheetPivotTables({ pkg }: PackageReadContext, sheetRels: PartRelationships, sheet: Worksheet): void;
@@ -1,18 +1,25 @@
1
- import { decodeRange } from '../../core/address.js';
2
1
  import { INTERNAL } from '../../core/internal.js';
2
+ import { mergesOverlappingTables } from '../../core/merge.js';
3
3
  import { Workbook } from '../../core/workbook.js';
4
4
  import { isAnyRelType } from '../../rel-type.js';
5
5
  import { localName } from '../../xml/xml-scan.js';
6
6
  import { relAttr } from '../opc/namespaces.js';
7
7
  import { extensionOf, resolveRelativePart } from '../opc/part-paths.js';
8
8
  import { capturePartClosure, parseRelationshipRecords, readPartRelationships, } from '../opc/read-opc.js';
9
- import { parseComments } from './comments.js';
10
- import { drawingHasUnmodeledContent, parseDrawing } from './images.js';
9
+ import { admitting } from '../read-policy/read-repair.js';
10
+ import { parseDrawing } from './images.js';
11
+ import { parseComments } from './read-comments.js';
11
12
  import { parsePivotTable } from './read-pivot.js';
12
- import { admitting } from './read-repair.js';
13
+ import { transitionalPart } from './strict-parts.js';
13
14
  import { parseTable } from './tables.js';
14
- import { parseThemeColorScheme, parseThemeFontScheme } from './theme-xml.js';
15
+ import { parseThemeSchemes } from './theme-xml.js';
15
16
  import { buildCommentThreads, parsePersons, parseThreadedComments } from './threaded-comments.js';
17
+ function capturePreservedClosure({ pkg, contentTypeOf }, entryPath) {
18
+ return capturePartClosure(entryPath, pkg.partText, pkg.partBytes, contentTypeOf)?.map((part) => {
19
+ const bytes = transitionalPart(part.bytes, part.contentType);
20
+ return bytes === part.bytes ? part : { ...part, bytes };
21
+ });
22
+ }
16
23
  export function readSheetComments(sheetRels) {
17
24
  const commentsXml = sheetRels.relatedText('comments');
18
25
  return commentsXml === undefined ? undefined : parseComments(commentsXml);
@@ -23,15 +30,15 @@ export function readWorkbookPersons({ workbook }, workbookRels) {
23
30
  workbook[INTERNAL].restorePersons(parsePersons(xml));
24
31
  }
25
32
  export function readWorkbookTheme(context, workbookRels) {
26
- const { pkg, contentTypeOf, workbook } = context;
33
+ const { pkg, workbook } = context;
27
34
  const entryPath = workbookRels.targetPath('theme');
28
35
  if (entryPath === undefined)
29
36
  return;
30
- const parts = capturePartClosure(entryPath, pkg.partText, pkg.partBytes, contentTypeOf);
37
+ const parts = capturePreservedClosure(context, entryPath);
31
38
  if (parts === undefined)
32
39
  return;
33
40
  const xml = pkg.partText(entryPath) ?? '';
34
- workbook[INTERNAL].restoreThemePart({ entryPath, parts }, { colors: parseThemeColorScheme(xml), fonts: parseThemeFontScheme(xml) });
41
+ workbook[INTERNAL].restoreThemePart({ entryPath, parts }, parseThemeSchemes(xml));
35
42
  }
36
43
  export function readSheetCommentThreads({ workbook }, sheetRels) {
37
44
  const xml = sheetRels.relatedText('threadedComment');
@@ -62,10 +69,11 @@ export function readSheetImages(context, sheetRels, sheet) {
62
69
  const drawingXml = partText(drawingPath);
63
70
  if (drawingXml === undefined)
64
71
  return;
65
- if (drawingHasUnmodeledContent(drawingXml))
72
+ const { anchors, fullyModeled } = parseDrawing(drawingXml);
73
+ if (!fullyModeled)
66
74
  return;
67
75
  const drawingRels = readPartRelationships(drawingPath, partText);
68
- for (const anchor of parseDrawing(drawingXml)) {
76
+ for (const anchor of anchors) {
69
77
  const embedded = drawingRels.byId(anchor.embed);
70
78
  if (embedded === undefined)
71
79
  continue;
@@ -74,15 +82,29 @@ export function readSheetImages(context, sheetRels, sheet) {
74
82
  if (id === undefined)
75
83
  continue;
76
84
  const rot = anchor.rotation !== undefined ? { rotation: anchor.rotation } : {};
85
+ const properties = pictureOf(anchor, drawingRels);
77
86
  if (anchor.to !== undefined) {
78
87
  const mode = anchor.editAs !== undefined ? { editAs: anchor.editAs } : {};
79
- sheet.addImageAnchor(id, { from: anchor.from, to: anchor.to, ...mode, ...rot });
88
+ sheet.addImageAnchor(id, { from: anchor.from, to: anchor.to, ...mode, ...rot }, properties);
80
89
  }
81
90
  else if (anchor.ext !== undefined) {
82
- sheet.addImageAnchor(id, { from: anchor.from, ext: anchor.ext, ...rot });
91
+ sheet.addImageAnchor(id, { from: anchor.from, ext: anchor.ext, ...rot }, properties);
83
92
  }
84
93
  }
85
94
  }
95
+ function pictureOf(anchor, drawingRels) {
96
+ const { description, title, crop, hyperlinkId, tooltip } = anchor;
97
+ const link = hyperlinkId === undefined ? undefined : drawingRels.byId(hyperlinkId);
98
+ const target = link === undefined || link.target === '' ? undefined : link.target;
99
+ return {
100
+ ...(description === undefined ? {} : { description }),
101
+ ...(title === undefined ? {} : { title }),
102
+ ...(crop === undefined ? {} : { crop }),
103
+ ...(target === undefined
104
+ ? {}
105
+ : { hyperlink: tooltip === undefined ? { target } : { target, tooltip } }),
106
+ };
107
+ }
86
108
  export function readSheetBackground(context, sheetRels, sheet) {
87
109
  const mediaPath = sheetRels.targetPath('image');
88
110
  if (mediaPath === undefined)
@@ -92,11 +114,9 @@ export function readSheetBackground(context, sheetRels, sheet) {
92
114
  sheet.addBackgroundImage(id);
93
115
  }
94
116
  export function readSheetPreservedReferences(context, sheetRels, referenceRelIds, sheet) {
95
- const { contentTypeOf } = context;
96
- const { partText, partBytes } = context.pkg;
97
117
  const capture = (element, relType, target) => {
98
118
  const entryPath = sheetRels.pathOf(target);
99
- const parts = capturePartClosure(entryPath, partText, partBytes, contentTypeOf);
119
+ const parts = capturePreservedClosure(context, entryPath);
100
120
  if (parts !== undefined)
101
121
  sheet[INTERNAL].addPreservedReference({ element, relType, entryPath, parts });
102
122
  };
@@ -118,14 +138,13 @@ function isPreservedSheetRelType(type) {
118
138
  return isAnyRelType(type, 'pivotTable', 'slicer');
119
139
  }
120
140
  export function readWorkbookPreservedReferences(context, workbookRels, registrations) {
121
- const { contentTypeOf, workbook } = context;
122
- const { partText, partBytes } = context.pkg;
141
+ const { workbook } = context;
123
142
  const { cacheIdByRelId, externalIndexByRelId } = registrations;
124
143
  for (const record of workbookRels.records) {
125
144
  if (record.external || !isPreservedWorkbookRelType(record.type))
126
145
  continue;
127
146
  const entryPath = workbookRels.pathOf(record.target);
128
- const parts = capturePartClosure(entryPath, partText, partBytes, contentTypeOf);
147
+ const parts = capturePreservedClosure(context, entryPath);
129
148
  if (parts === undefined)
130
149
  continue;
131
150
  const cacheId = cacheIdByRelId.get(record.id);
@@ -140,16 +159,15 @@ export function readWorkbookPreservedReferences(context, workbookRels, registrat
140
159
  }
141
160
  }
142
161
  export function readRootPreservedReferences(context) {
143
- const { contentTypeOf, workbook } = context;
144
- const { partText, partBytes } = context.pkg;
145
- const relsXml = partText('_rels/.rels');
162
+ const { workbook } = context;
163
+ const relsXml = context.pkg.partText('_rels/.rels');
146
164
  if (relsXml === undefined)
147
165
  return;
148
166
  for (const record of parseRelationshipRecords(relsXml)) {
149
167
  if (record.external || isRegeneratedRootRelType(record.type))
150
168
  continue;
151
169
  const entryPath = resolveRelativePart('', record.target);
152
- const parts = capturePartClosure(entryPath, partText, partBytes, contentTypeOf);
170
+ const parts = capturePreservedClosure(context, entryPath);
153
171
  if (parts === undefined)
154
172
  continue;
155
173
  workbook[INTERNAL].addPreservedRootReference({ relType: record.type, entryPath, parts });
@@ -210,12 +228,12 @@ export function worksheetReferencePass() {
210
228
  result: () => relIds,
211
229
  };
212
230
  }
213
- export function readSheetTables({ pkg }, sheetRels, sheet) {
231
+ export function readSheetTables({ pkg }, sheetRels, sheet, definedNames) {
214
232
  for (const tablePath of sheetRels.targetPaths('table')) {
215
233
  const tableXml = pkg.partText(tablePath);
216
234
  if (tableXml === undefined)
217
235
  continue;
218
- const options = parseTable(tableXml);
236
+ const options = parseTable(tableXml, definedNames);
219
237
  if (options !== undefined) {
220
238
  admitting(() => {
221
239
  sheet.addTable(options);
@@ -235,18 +253,7 @@ export function readSheetPivotTables({ pkg }, sheetRels, sheet) {
235
253
  }
236
254
  }
237
255
  function dropMergesInsideTables(sheet) {
238
- const regions = sheet.tables.map((table) => table.region);
239
- if (regions.length === 0)
240
- return;
241
- for (const range of [...sheet.merges]) {
242
- const { top, left, bottom, right } = decodeRange(range);
243
- if (top === undefined || left === undefined || bottom === undefined || right === undefined)
244
- continue;
245
- const overlaps = regions.some((region) => left <= region.right &&
246
- right >= region.left &&
247
- top <= region.bottom &&
248
- bottom >= region.top);
249
- if (overlaps)
250
- sheet.unmergeCells(range);
256
+ for (const { merge } of mergesOverlappingTables(sheet.merges, sheet.tables)) {
257
+ sheet.unmergeCells(merge);
251
258
  }
252
259
  }
@@ -2,6 +2,7 @@ import { isDeclarablePivotSourceKind, pivotMetricFromSubtotal, } from '../../cor
2
2
  import { numInteger } from '../../xml/xml-attrs.js';
3
3
  import { parseXml } from '../../xml/xml-read.js';
4
4
  import { localName } from '../../xml/xml-scan.js';
5
+ import { relAttr } from '../opc/namespaces.js';
5
6
  export function parsePivotTable(tableXml, cacheXml) {
6
7
  const { fields, source } = parsePivotCacheDefinition(cacheXml);
7
8
  const def = parsePivotTableDefinition(tableXml);
@@ -20,9 +21,9 @@ export function parsePivotTable(tableXml, cacheXml) {
20
21
  }
21
22
  function parsePivotCacheDefinition(cacheXml) {
22
23
  const fields = [];
23
- let source = { kind: 'worksheet', sheet: '', ref: '' };
24
+ let source = { kind: 'worksheet', sheet: '', ref: '', inAnotherWorkbook: false };
24
25
  parseXml(cacheXml, {
25
- onOpen(name, attrs) {
26
+ onOpen(name, attrs, _selfClosing, scope) {
26
27
  const local = localName(name);
27
28
  if (local === 'cacheField' && attrs.name !== undefined) {
28
29
  fields.push({ name: attrs.name });
@@ -31,7 +32,12 @@ function parsePivotCacheDefinition(cacheXml) {
31
32
  source = { ...source, kind: sourceKind(attrs.type) };
32
33
  }
33
34
  else if (local === 'worksheetSource') {
34
- source = { ...source, sheet: attrs.sheet ?? '', ref: attrs.ref ?? '' };
35
+ source = {
36
+ ...source,
37
+ sheet: attrs.sheet ?? '',
38
+ ref: attrs.ref ?? '',
39
+ inAnotherWorkbook: relAttr(scope, attrs, 'id') !== undefined,
40
+ };
35
41
  }
36
42
  },
37
43
  });
@@ -13,19 +13,22 @@ export declare class RunAccumulator {
13
13
  #private;
14
14
  /**
15
15
  * @param options.container the element that opens one string: `'si'` for the shared-string pool,
16
- * `'is'` for an inline string in a worksheet body.
17
- * @param options.readRuns whether `<r>`/`<rPr>` open a rich-text run. The buffered readers read
18
- * them; the row stream deliberately does not, and with runs off every `<t>` falls through to
19
- * {@link plainText}, which is exactly the documented flattening.
16
+ * `'is'` for an inline string in a worksheet body, `'text'` for a note's body.
17
+ * @param options.readRuns whether `<r>`/`<rPr>` open a rich-text run. A cell's string reads them in
18
+ * both worksheet readers, pooled or inline; a note's body does not, and with runs off every `<t>`
19
+ * falls through to {@link plainText}, which is the flattening a plain-text note wants.
20
20
  */
21
21
  constructor(options: {
22
- readonly container: 'si' | 'is';
22
+ readonly container: 'si' | 'is' | 'text';
23
23
  readonly readRuns: boolean;
24
24
  });
25
25
  /** The runs gathered so far. */
26
26
  get runs(): RichTextRun[];
27
27
  /** Whether the container held at least one `<r>`: what decides rich text from a plain string. */
28
28
  get isRich(): boolean;
29
+ /** Whether a container element opened at all since the last {@link beginContainer}. An `<is/>` is an
30
+ * empty string and a `<c t="inlineStr">` with no `<is>` is no string, and only this tells them apart. */
31
+ get opened(): boolean;
29
32
  /** The container's own text: every `<t>` no run claimed, concatenated and already unescaped. */
30
33
  get plainText(): string;
31
34
  /**
@@ -13,7 +13,9 @@ export class RunAccumulator {
13
13
  #plain = '';
14
14
  #inRun = false;
15
15
  #isRich = false;
16
+ #opened = false;
16
17
  #inContainer = false;
18
+ #phoneticDepth = 0;
17
19
  constructor(options) {
18
20
  this.#container = options.container;
19
21
  this.#readRuns = options.readRuns;
@@ -24,6 +26,9 @@ export class RunAccumulator {
24
26
  get isRich() {
25
27
  return this.#isRich;
26
28
  }
29
+ get opened() {
30
+ return this.#opened;
31
+ }
27
32
  get plainText() {
28
33
  return this.#plain;
29
34
  }
@@ -35,7 +40,9 @@ export class RunAccumulator {
35
40
  this.#plain = '';
36
41
  this.#inRun = false;
37
42
  this.#isRich = false;
43
+ this.#opened = false;
38
44
  this.#inContainer = false;
45
+ this.#phoneticDepth = 0;
39
46
  this.#capture.reset();
40
47
  }
41
48
  open(local, attrs, selfClosing) {
@@ -43,6 +50,7 @@ export class RunAccumulator {
43
50
  case this.#container:
44
51
  this.beginContainer();
45
52
  this.#inContainer = !selfClosing;
53
+ this.#opened = true;
46
54
  return true;
47
55
  case 'r':
48
56
  if (!this.#readRuns || !this.#inContainer)
@@ -59,8 +67,13 @@ export class RunAccumulator {
59
67
  this.#font = {};
60
68
  this.#inProperties = true;
61
69
  return true;
70
+ case 'rPh':
71
+ if (!selfClosing)
72
+ this.#phoneticDepth++;
73
+ return true;
62
74
  case 't':
63
- this.#capture.open(local, selfClosing);
75
+ if (this.#phoneticDepth === 0)
76
+ this.#capture.open(local, selfClosing);
64
77
  return true;
65
78
  default:
66
79
  if (!this.#inProperties || this.#font === null)
@@ -85,6 +98,10 @@ export class RunAccumulator {
85
98
  }
86
99
  return 'claimed';
87
100
  }
101
+ case 'rPh':
102
+ if (this.#phoneticDepth > 0)
103
+ this.#phoneticDepth--;
104
+ return 'claimed';
88
105
  case 'rPr':
89
106
  if (!this.#readRuns || !this.#inProperties)
90
107
  return 'other';
@@ -1,5 +1,6 @@
1
1
  import type { CellValue } from '../../core/value.ts';
2
- import { type ReadPackageOptions, type XfStyle } from './read.ts';
2
+ import type { ReadPackageOptions } from '../opc/read-options.ts';
3
+ import type { XfStyle } from '../style/xf-style.ts';
3
4
  export interface ReadSheetRowsOptions extends ReadPackageOptions {
4
5
  /**
5
6
  * Which worksheet to stream: its name, or its 1-based position in the workbook. Defaults to the
@@ -8,9 +9,10 @@ export interface ReadSheetRowsOptions extends ReadPackageOptions {
8
9
  readonly sheet?: string | number;
9
10
  }
10
11
  /**
11
- * The resolved style facets of a streamed cell: its own `<c s>` cell format, flattened exactly as
12
- * the buffered reader resolves it. Present only when the cell carries a format; a consumer can copy
13
- * these straight onto a writer cell to preserve its look through a streaming read→write.
12
+ * The resolved style facets of a streamed cell: its own `<c s>` format, failing that its row's (when
13
+ * the row is marked `customFormat`), failing that its column's, flattened exactly as the buffered
14
+ * reader resolves it. Present only when the cell, its row or its column declares a format; a consumer
15
+ * can copy these straight onto a writer cell to preserve its look through a streaming read→write.
14
16
  *
15
17
  * @unpublished A named commitment this surface is not ready to make. `entries/xlsx.ts` records the
16
18
  * decision: the streaming reader's granular output shapes stay inferred structural types while the
@@ -30,7 +32,8 @@ export interface StreamedCell {
30
32
  readonly address: string;
31
33
  /** The decoded value, identical to what `readXlsx` would produce for the same cell. */
32
34
  readonly value: CellValue;
33
- /** The cell's resolved style facets, or absent when the cell carries no format of its own. */
35
+ /** The cell's resolved style facets, including a format it inherits from its row or column; absent
36
+ * when none of the three declares one. */
34
37
  readonly style?: StreamedCellStyle;
35
38
  }
36
39
  /** One worksheet row, as yielded by {@link readSheetRows} / {@link StreamedSheet.rows}. */
@@ -56,13 +59,21 @@ export interface StreamedRow {
56
59
  * surface settles, so a consumer destructures them and never has to spell them.
57
60
  */
58
61
  export interface StreamedSheet {
59
- /** The worksheet's declared name, joined from the workbook part. Never a positional placeholder. */
62
+ /**
63
+ * The worksheet's name, joined from the workbook part and repaired exactly as `readXlsx` repairs it
64
+ * (a duplicate becomes `S (2)`, a forbidden character is removed), so the two readers name a sheet
65
+ * the same way. Never a positional placeholder.
66
+ */
60
67
  readonly name: string;
61
68
  /** Stream this sheet's rows, one at a time, in sheet order. */
62
69
  rows(): Generator<StreamedRow, void, undefined>;
63
70
  /** 1-based indices of columns the sheet declares hidden, ascending. */
64
71
  readonly hiddenColumns: readonly number[];
65
- /** The sheet's merged ranges, as canonical A1 range strings, in declaration order. */
72
+ /**
73
+ * The sheet's merged ranges, as canonical A1 range strings, in declaration order: the ones
74
+ * `readXlsx` admits, so an unreadable range, or one overlapping a range declared before it, is
75
+ * dropped.
76
+ */
66
77
  readonly merges: readonly string[];
67
78
  }
68
79
  /**