@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
@@ -3,20 +3,19 @@ import { Workbook } from '../../core/workbook.js';
3
3
  import { parseXmlPasses } from '../../xml/xml-read.js';
4
4
  import { UnsupportedFormatError } from '../opc/errors.js';
5
5
  import { contentTypeResolver, openSpreadsheetPackage, readPartRelationships, } from '../opc/read-opc.js';
6
+ import { admitArrayRanges, admitting, repairedSheetNames } from '../read-policy/read-repair.js';
6
7
  import { readXlsbPackage } from '../xlsb/read.js';
7
- import { applyNotes } from './comments.js';
8
- import { conditionalFormattingPass } from './conditional-formatting.js';
8
+ import { readCellMetadata } from './cell-metadata.js';
9
+ import { applyConditionalFormattings, conditionalFormattingPass } from './conditional-formatting.js';
9
10
  import { applyDataValidations, dataValidationPass, extendedDataValidationPass, } from './data-validation.js';
10
11
  import { applyHyperlinks, sheetHyperlinkPass } from './hyperlinks.js';
11
12
  import { SHARED_STRINGS_PART, STYLES_PART } from './part-names.js';
13
+ import { applyNotes } from './read-comments.js';
12
14
  import { readRootPreservedReferences, readSheetBackground, readSheetComments, readSheetCommentThreads, readSheetImages, readSheetPivotTables, readSheetPreservedReferences, readSheetPrinterSettings, readSheetTables, readWorkbookPersons, externalReferenceRegistrationsPass, pivotCacheRegistrationsPass, readWorkbookPreservedReferences, readWorkbookTheme, worksheetReferencePass, } from './read-parts.js';
13
- import { admitting, repairSheetName } from './read-repair.js';
14
15
  import { parseSharedStrings } from './read-shared-strings.js';
15
16
  import { parseStyleTable } from './read-styles.js';
16
17
  import { applyAppProperties, applyCoreProperties, definedNamesPass, workbookPropertiesPass, workbookProtectionPass, workbookSheetsPass, workbookViewPass, } from './read-workbook-xml.js';
17
18
  import { worksheetPass } from './read-worksheet.js';
18
- export { parseStyleTable } from './read-styles.js';
19
- export { workbookPropertiesPass, workbookSheetsPass, workbookViewPass, } from './read-workbook-xml.js';
20
19
  export function readXlsx(data, options = {}) {
21
20
  const { files, pkg, documentPath, workbookXml } = openSpreadsheetPackage(data, options.maxUncompressedBytes);
22
21
  const { partText, partBytes } = pkg;
@@ -37,6 +36,7 @@ export function readXlsx(data, options = {}) {
37
36
  contentTypeOf,
38
37
  sharedStrings,
39
38
  xfStyles,
39
+ cellMetadata: readCellMetadata(workbookRels),
40
40
  imageIdByMediaPath: new Map(),
41
41
  };
42
42
  workbook[INTERNAL].restoreDifferentialStyles([...preserved.dxfs]);
@@ -69,14 +69,13 @@ export function readXlsx(data, options = {}) {
69
69
  ]);
70
70
  workbook.protection = protection.result();
71
71
  readWorkbookPersons(context, workbookRels);
72
+ const sheetContext = { ...context, definedNames: definedNames.spellings() };
72
73
  const sheetOrder = [];
73
- const takenSheetNames = new Set();
74
- for (const { name, relId, state } of sheets.result()) {
74
+ for (const { name, relId, state } of repairedSheetNames(sheets.result())) {
75
75
  const target = workbookRels.byId(relId)?.target;
76
- const sheet = workbook.addWorksheet(repairSheetName(name, takenSheetNames), state === undefined ? undefined : { state });
77
- takenSheetNames.add(sheet.name.toLowerCase());
76
+ const sheet = workbook.addWorksheet(name, state === undefined ? undefined : { state });
78
77
  sheetOrder.push(sheet.name);
79
- readSheet(sheet, target === undefined ? undefined : workbookRels.pathOf(target), context);
78
+ readSheet(sheet, target === undefined ? undefined : workbookRels.pathOf(target), sheetContext);
80
79
  }
81
80
  readWorkbookPreservedReferences(context, workbookRels, {
82
81
  cacheIdByRelId: pivotCaches.result(),
@@ -91,23 +90,24 @@ export function readXlsx(data, options = {}) {
91
90
  return workbook;
92
91
  }
93
92
  function readSheet(sheet, path, context) {
94
- const { pkg, workbook, sharedStrings, xfStyles } = context;
93
+ const { pkg, workbook, sharedStrings, xfStyles, definedNames, cellMetadata } = context;
95
94
  const { partText } = pkg;
96
95
  const sheetXml = path === undefined ? undefined : partText(path);
97
96
  const hyperlinks = sheetHyperlinkPass();
98
- const validations = dataValidationPass();
99
- const extendedValidations = extendedDataValidationPass();
100
- const formattings = conditionalFormattingPass();
97
+ const validations = dataValidationPass(definedNames);
98
+ const extendedValidations = extendedDataValidationPass(definedNames);
99
+ const formattings = conditionalFormattingPass(definedNames, (fragment) => workbook[INTERNAL].adoptDifferentialStyle(fragment));
101
100
  const references = worksheetReferencePass();
102
101
  if (sheetXml !== undefined) {
103
102
  parseXmlPasses(sheetXml, [
104
- worksheetPass(sheet, sharedStrings, xfStyles, workbook.dateEpoch),
103
+ worksheetPass(sheet, sharedStrings, xfStyles, workbook.dateEpoch, definedNames, cellMetadata),
105
104
  hyperlinks,
106
105
  validations,
107
106
  extendedValidations,
108
107
  formattings,
109
108
  references,
110
109
  ]);
110
+ admitArrayRanges(sheet);
111
111
  }
112
112
  if (path === undefined)
113
113
  return;
@@ -115,11 +115,7 @@ function readSheet(sheet, path, context) {
115
115
  if (sheetXml !== undefined) {
116
116
  applyHyperlinks(sheet, hyperlinks.result(), (id) => sheetRels.byId(id)?.target);
117
117
  applyDataValidations(sheet, [...validations.result(), ...extendedValidations.result()]);
118
- for (const cf of formattings.result()) {
119
- admitting(() => {
120
- sheet.addConditionalFormatting(cf);
121
- });
122
- }
118
+ applyConditionalFormattings(sheet, formattings.result());
123
119
  }
124
120
  const threads = readSheetCommentThreads(context, sheetRels);
125
121
  if (threads.length > 0)
@@ -132,7 +128,7 @@ function readSheet(sheet, path, context) {
132
128
  if (sheetXml !== undefined) {
133
129
  readSheetPreservedReferences(context, sheetRels, references.result(), sheet);
134
130
  }
135
- readSheetTables(context, sheetRels, sheet);
131
+ readSheetTables(context, sheetRels, sheet, definedNames);
136
132
  readSheetPivotTables(context, sheetRels, sheet);
137
133
  const printerSettings = readSheetPrinterSettings(sheetRels);
138
134
  if (printerSettings !== undefined)
@@ -28,6 +28,9 @@ export declare const REL: {
28
28
  readonly pivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable";
29
29
  readonly pivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition";
30
30
  readonly pivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords";
31
+ readonly sheetMetadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata";
32
+ readonly rdRichValue: "http://schemas.microsoft.com/office/2017/06/relationships/rdRichValue";
33
+ readonly rdRichValueStructure: "http://schemas.microsoft.com/office/2017/06/relationships/rdRichValueStructure";
31
34
  readonly threadedComment: "http://schemas.microsoft.com/office/2017/10/relationships/threadedComment";
32
35
  readonly person: "http://schemas.microsoft.com/office/2017/10/relationships/person";
33
36
  };
@@ -13,6 +13,7 @@ export const NS = {
13
13
  xsi: 'http://www.w3.org/2001/XMLSchema-instance',
14
14
  };
15
15
  const MS_OFFICE_2017_RELS_NS = 'http://schemas.microsoft.com/office/2017/10/relationships';
16
+ const RICH_DATA_RELS_NS = 'http://schemas.microsoft.com/office/2017/06/relationships';
16
17
  export const REL = {
17
18
  worksheet: `${NS.docRels}/worksheet`,
18
19
  styles: `${NS.docRels}/styles`,
@@ -31,6 +32,9 @@ export const REL = {
31
32
  pivotTable: `${NS.docRels}/pivotTable`,
32
33
  pivotCacheDefinition: `${NS.docRels}/pivotCacheDefinition`,
33
34
  pivotCacheRecords: `${NS.docRels}/pivotCacheRecords`,
35
+ sheetMetadata: `${NS.docRels}/sheetMetadata`,
36
+ rdRichValue: `${RICH_DATA_RELS_NS}/rdRichValue`,
37
+ rdRichValueStructure: `${RICH_DATA_RELS_NS}/rdRichValueStructure`,
34
38
  threadedComment: `${MS_OFFICE_2017_RELS_NS}/threadedComment`,
35
39
  person: `${MS_OFFICE_2017_RELS_NS}/person`,
36
40
  };
@@ -6,7 +6,9 @@ export interface RowPosition {
6
6
  /**
7
7
  * Whether that number names a row the grid has. A row past the ceiling is dropped whole by both
8
8
  * readers rather than clamped: an `r` names one row, so there is nothing to fold it onto, and
9
- * clamping would silently move its formatting to the last row of the sheet.
9
+ * clamping would silently move its formatting to the last row of the sheet. Its cells go with it,
10
+ * even one whose own `r` is in the grid, because `CellAccumulator` places no cell outside an
11
+ * in-grid row.
10
12
  */
11
13
  readonly inGrid: boolean;
12
14
  }
@@ -1,8 +1,9 @@
1
+ import type { FormulaPlacement } from '../../core/array-formula-ranges.ts';
1
2
  import { type Cell } from '../../core/cell.ts';
2
3
  import { type DateEpoch } from '../../core/date.ts';
3
4
  import type { ColumnProperties, RowProperties, Worksheet } from '../../core/worksheet.ts';
5
+ import type { WorkbookMetadataTable } from './cell-metadata.ts';
4
6
  import type { CommentCell } from './comments.ts';
5
- import type { CollectedHyperlink } from './hyperlinks.ts';
6
7
  import type { SharedFormulaRole } from './shared-formulas.ts';
7
8
  import type { SharedStringTable } from './shared-strings.ts';
8
9
  import type { StyleRegistry } from './styles.ts';
@@ -63,14 +64,18 @@ export interface FlushedSheet {
63
64
  readonly hidden: boolean;
64
65
  }>;
65
66
  /**
66
- * The hyperlinks and notes the flushed rows carried, on the same terms and for the same reason.
67
- * Both are serialised outside the `<row>`: a hyperlink into the sheet's `<hyperlinks>` element plus
68
- * an external relationship, a note into the comments and VML parts. The buffered pass gathers them
69
- * by walking the sheet's rows at commit time, which finds nothing on a row whose cells have already
70
- * been evicted, so a streamed row's link kept its visible label and silently lost its destination.
67
+ * The notes the flushed rows carried, on the same terms and for the same reason. A note is
68
+ * serialised outside the `<row>`, into the comments and VML parts, and the buffered pass gathers
69
+ * notes by walking the sheet's rows at commit time, which finds nothing on a row whose cells have
70
+ * already been evicted. A hyperlink needs no such record: it lives beside the grid, on the sheet.
71
71
  */
72
- readonly hyperlinks: readonly CollectedHyperlink[];
73
72
  readonly notes: readonly CommentCell[];
73
+ /**
74
+ * The formulas the flushed rows carried, on the same terms again: whether an array formula's range
75
+ * holds another formula, or shares a cell with another's, is a question about other rows, and the
76
+ * writer asks it of the whole sheet at commit.
77
+ */
78
+ readonly formulas: readonly FormulaPlacement[];
74
79
  }
75
80
  /**
76
81
  * A column's style facets are defaults its cells inherit unless they override them; the writer
@@ -91,6 +96,12 @@ export interface RowRenderContext {
91
96
  readonly collapsedSummaries: ReadonlySet<number>;
92
97
  /** The workbook's date system, which is what a `Date` cell's serial counts from. */
93
98
  readonly dateEpoch: DateEpoch;
99
+ /** The defined names a bare name in this sheet's formulas resolves to, as `formulaNamesInScope`
100
+ * gives them: a function passed as a value is written bare where one of them captures its name. */
101
+ readonly formulaNames: ReadonlySet<string>;
102
+ /** The workbook's metadata, which a dynamic-array formula's cell and a rich-value error's cell point
103
+ * into. Shared by every row of every sheet, since the parts it becomes are the workbook's. */
104
+ readonly cellMetadata: WorkbookMetadataTable;
94
105
  }
95
106
  /**
96
107
  * Serialise one row to its `<row>` element, or '' when the row has neither data nor its own
@@ -1,11 +1,12 @@
1
+ import { encodeRange, tryDecodeAnchoredRange } from '../../core/address.js';
1
2
  import { cellHasOwnStyle } from '../../core/cell.js';
2
3
  import { DEFAULT_DATE_NUMFMT, dateToSerial } from '../../core/date.js';
3
4
  import { mangleFormula } from '../../core/formula.js';
4
5
  import { NAMED_STYLE_ID } from '../../core/internal.js';
5
6
  import { CELL_STYLE_FACETS } from '../../core/style.js';
6
- import { detectValueType, isDataTableFormulaValue, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
7
- import { AuthoringError, InternalError } from '../../errors.js';
8
- import { boolAttr, escapeAttr, escapeSpreadsheetText, escapeText, numberText, textAttr, textElement, } from '../../xml/xml.js';
7
+ import { detectValueType, isArrayFormulaValue, isDataTableFormulaValue, isErrorCode, isErrorValue, isFormulaValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
8
+ import { AuthoringError, InternalError, quoted } from '../../errors.js';
9
+ import { boolAttr, checkedToken, escapeAttr, escapeSpreadsheetText, escapeText, numberText, nonDefaultNumAttr, textAttr, textElement, } from '../../xml/xml.js';
9
10
  import { richTextRunsXml } from './rich-text.js';
10
11
  export class Extent {
11
12
  top = Infinity;
@@ -53,7 +54,7 @@ export function renderRow(entry, ctx) {
53
54
  const cellsXml = rendered
54
55
  .map((cell) => {
55
56
  const style = ctx.styles.styleId(composeCellStyle(cell, rowFill, ctx.columnDefaults.get(cell.col)));
56
- return cellXml(cell, style, ctx.sharedRoles.get(cell.address), ctx.sharedStrings, ctx.dateEpoch);
57
+ return cellXml(cell, style, ctx.sharedRoles.get(cell.address), ctx);
57
58
  })
58
59
  .join('');
59
60
  let minCol = Infinity;
@@ -104,18 +105,19 @@ function rowAttrs(properties, styles, collapsedSummary) {
104
105
  function dateDefaultNumFmt(value) {
105
106
  const date = value instanceof Date
106
107
  ? value
107
- : (isFormulaValue(value) || isSharedFormulaValue(value)) && value.result instanceof Date
108
+ : (isFormulaValue(value) || isSharedFormulaValue(value) || isArrayFormulaValue(value)) &&
109
+ value.result instanceof Date
108
110
  ? value.result
109
111
  : undefined;
110
112
  return date !== undefined && !Number.isNaN(date.getTime()) ? DEFAULT_DATE_NUMFMT : undefined;
111
113
  }
112
- function cellElement(ref, s, type, body) {
113
- const t = type === '' ? '' : ` t="${type}"`;
114
- return body === '' ? `<c r="${ref}"${s}${t}/>` : `<c r="${ref}"${s}${t}>${body}</c>`;
114
+ function cellElement(ref, s, type, body, cm = '', vm = '') {
115
+ const attrs = `${s}${type === '' ? '' : ` t="${type}"`}${cm}${vm}`;
116
+ return body === '' ? `<c r="${ref}"${attrs}/>` : `<c r="${ref}"${attrs}>${body}</c>`;
115
117
  }
116
118
  const UNWRITABLE = { type: '', v: null };
117
119
  const vElement = (v) => (v === null ? '' : `<v>${v}</v>`);
118
- function valueBody(value, epoch) {
120
+ function valueBody(value, ctx) {
119
121
  if (typeof value === 'number') {
120
122
  return Number.isFinite(value) ? { type: '', v: numberText(value) } : UNWRITABLE;
121
123
  }
@@ -124,22 +126,28 @@ function valueBody(value, epoch) {
124
126
  if (value instanceof Date) {
125
127
  return Number.isNaN(value.getTime())
126
128
  ? UNWRITABLE
127
- : { type: '', v: numberText(dateToSerial(value, epoch)) };
129
+ : { type: '', v: numberText(dateToSerial(value, ctx.dateEpoch)) };
130
+ }
131
+ if (isErrorValue(value)) {
132
+ checkedToken(value.error, isErrorCode, 'cell error value');
133
+ const vm = ctx.cellMetadata.markRichValueError(value.error);
134
+ return vm === undefined
135
+ ? { type: 'e', v: value.error }
136
+ : { type: 'e', v: '#VALUE!', vm: ` vm="${vm}"` };
128
137
  }
129
- if (isErrorValue(value))
130
- return { type: 'e', v: value.error };
131
138
  return undefined;
132
139
  }
133
- function cellXml(cell, style, shared, sharedStrings, epoch) {
140
+ function cellXml(cell, style, shared, ctx) {
141
+ const { sharedStrings } = ctx;
134
142
  const ref = cell.address;
135
143
  const value = cell.value;
136
144
  const s = style !== 0 ? ` s="${style}"` : '';
137
- const formula = cellFormulaXml(ref, s, value, shared, epoch);
145
+ const formula = cellFormulaXml(cell, s, shared, ctx);
138
146
  if (formula !== undefined)
139
147
  return formula;
140
- const body = valueBody(value, epoch);
148
+ const body = valueBody(value, ctx);
141
149
  if (body !== undefined)
142
- return cellElement(ref, s, body.type, vElement(body.v));
150
+ return cellElement(ref, s, body.type, vElement(body.v), '', body.vm);
143
151
  if (typeof value === 'string' || isRichTextValue(value)) {
144
152
  if (sharedStrings !== null) {
145
153
  return cellElement(ref, s, 's', vElement(`${sharedStrings.intern(value)}`));
@@ -147,46 +155,57 @@ function cellXml(cell, style, shared, sharedStrings, epoch) {
147
155
  const inline = typeof value === 'string' ? textElement(value) : richTextRunsXml(value.richText);
148
156
  return cellElement(ref, s, 'inlineStr', `<is>${inline}</is>`);
149
157
  }
150
- if (isHyperlinkValue(value)) {
151
- const label = typeof value.text === 'string'
152
- ? textElement(value.text)
153
- : richTextRunsXml(value.text.richText);
154
- return cellElement(ref, s, 'inlineStr', `<is>${label}</is>`);
155
- }
156
158
  if (value === null)
157
159
  return cellElement(ref, s, '', '');
158
160
  throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm: every CellValue kind is handled above`);
159
161
  }
160
- function cellFormulaXml(ref, s, value, shared, epoch) {
162
+ function cellFormulaXml(cell, s, shared, ctx) {
163
+ const { formulaNames } = ctx;
164
+ const { address: ref, value } = cell;
161
165
  if (shared !== undefined) {
162
166
  if (shared.ref !== undefined && isFormulaValue(value)) {
163
- const f = `<f t="shared" ref="${shared.ref}" si="${shared.si}">${escapeText(mangleFormula(value.formula))}</f>`;
164
- return formulaBodyXml(ref, s, f, value.result, epoch);
167
+ const f = `<f t="shared" ref="${shared.ref}" si="${shared.si}">${escapeText(mangleFormula(value.formula, formulaNames))}</f>`;
168
+ return formulaBodyXml(ref, s, f, value.result, ctx);
165
169
  }
166
170
  const result = isSharedFormulaValue(value) ? value.result : undefined;
167
- return formulaBodyXml(ref, s, `<f t="shared" si="${shared.si}"/>`, result, epoch);
171
+ return formulaBodyXml(ref, s, `<f t="shared" si="${shared.si}"/>`, result, ctx);
168
172
  }
169
173
  if (isDataTableFormulaValue(value)) {
170
174
  const attrs = `ref="${escapeAttr(value.ref)}"` +
171
175
  boolAttr('dt2D', value.dataTable2D) +
172
176
  boolAttr('dtr', value.dataTableRow) +
177
+ (value.r1Deleted === true ? ' del1="1"' : '') +
178
+ (value.r2Deleted === true ? ' del2="1"' : '') +
173
179
  textAttr('r1', value.r1) +
174
180
  textAttr('r2', value.r2);
175
- return formulaBodyXml(ref, s, `<f t="dataTable" ${attrs}/>`, value.result, epoch);
181
+ return formulaBodyXml(ref, s, `<f t="dataTable" ${attrs}/>`, value.result, ctx);
182
+ }
183
+ if (isArrayFormulaValue(value)) {
184
+ const cm = value.dynamic === true ? ` cm="${ctx.cellMetadata.markDynamicArray()}"` : '';
185
+ const f = `<f t="array" ref="${arrayFormulaRange(cell, value.ref)}">${escapeText(mangleFormula(value.formula, formulaNames))}</f>`;
186
+ return formulaBodyXml(ref, s, f, value.result, ctx, cm);
176
187
  }
177
188
  if (isFormulaValue(value)) {
178
- return formulaBodyXml(ref, s, `<f>${escapeText(mangleFormula(value.formula))}</f>`, value.result, epoch);
189
+ return formulaBodyXml(ref, s, `<f>${escapeText(mangleFormula(value.formula, formulaNames))}</f>`, value.result, ctx);
179
190
  }
180
191
  return undefined;
181
192
  }
182
- function formulaBodyXml(ref, s, f, result, epoch) {
193
+ function arrayFormulaRange(cell, ref) {
194
+ const range = tryDecodeAnchoredRange(ref, cell.col, cell.row);
195
+ if (range === undefined) {
196
+ throw new AuthoringError(`the array formula in ${cell.address} fills ${quoted(ref)}, which is not a range starting at ${cell.address}`);
197
+ }
198
+ return encodeRange(range);
199
+ }
200
+ function formulaBodyXml(ref, s, f, result, ctx, cm = '') {
183
201
  if (result === undefined)
184
- return cellElement(ref, s, '', f);
185
- const body = valueBody(result, epoch);
186
- if (body !== undefined)
187
- return cellElement(ref, s, body.type, f + vElement(body.v));
202
+ return cellElement(ref, s, '', f, cm);
203
+ const body = valueBody(result, ctx);
204
+ if (body !== undefined) {
205
+ return cellElement(ref, s, body.type, f + vElement(body.v), cm, body.vm);
206
+ }
188
207
  if (typeof result === 'string') {
189
- return cellElement(ref, s, 'str', f + vElement(escapeSpreadsheetText(result)));
208
+ return cellElement(ref, s, 'str', f + vElement(escapeSpreadsheetText(result)), cm);
190
209
  }
191
210
  throw new InternalError('writing a non-primitive formula result has no arm: every FormulaResult kind is handled above');
192
211
  }
@@ -199,5 +218,5 @@ export function outlineAttr(name, level) {
199
218
  if (level === undefined)
200
219
  return '';
201
220
  assertWritableLevel(name, level);
202
- return level > 0 ? ` ${name}="${numberText(level)}"` : '';
221
+ return nonDefaultNumAttr(name, level, 0);
203
222
  }
@@ -1,19 +1,22 @@
1
- import { encodeAddress } from '../../core/address.js';
1
+ import { encodeAddress, MAX_COLUMN, MAX_ROW } from '../../core/address.js';
2
2
  import { isCustomFilterOperator, } from '../../core/autofilter.js';
3
3
  import { HEADER_FOOTER_ELEMENTS, MARGIN_SIDES, PAGE_SETUP_FACETS, PRINT_OPTION_FLAGS, } from '../../core/page-setup.js';
4
- import { SHEET_PROTECTION_FLAGS, } from '../../core/protection.js';
4
+ import { isLegacyPasswordHash, SHEET_PROTECTION_FLAGS, } from '../../core/protection.js';
5
+ import { assertWritableNumber, AuthoringError } from '../../errors.js';
5
6
  import { numFinite, numInteger } from '../../xml/xml-attrs.js';
6
- import { boolPresent, boolStrict, boolTristate } from '../../xml/xml-scan.js';
7
+ import { boolStrict, boolTristate } from '../../xml/xml-scan.js';
7
8
  import { boolAttr, checkedToken, escapeAttr, escapeSpreadsheetText, numAttr, numberText, } from '../../xml/xml.js';
8
9
  import { colorAttrs, parseColor } from './color-xml.js';
9
10
  export function sheetViewsXml(view, active) {
10
11
  const selected = active ? ' tabSelected="1"' : '';
11
12
  const gridLines = view.showGridLines === false ? ' showGridLines="0"' : '';
12
- const xSplit = view.xSplit ?? 0;
13
- const ySplit = view.ySplit ?? 0;
14
- if (view.state !== 'frozen' || (xSplit === 0 && ySplit === 0)) {
15
- return `<sheetViews><sheetView${gridLines}${selected} workbookViewId="0"/></sheetViews>`;
16
- }
13
+ const normal = `<sheetViews><sheetView${gridLines}${selected} workbookViewId="0"/></sheetViews>`;
14
+ if (view.state !== 'frozen')
15
+ return normal;
16
+ const xSplit = frozenSplit('xSplit', view.xSplit, MAX_COLUMN);
17
+ const ySplit = frozenSplit('ySplit', view.ySplit, MAX_ROW);
18
+ if (xSplit === 0 && ySplit === 0)
19
+ return normal;
17
20
  const topLeftCell = view.topLeftCell ?? encodeAddress(xSplit + 1, ySplit + 1);
18
21
  const activePane = xSplit > 0 && ySplit > 0 ? 'bottomRight' : xSplit > 0 ? 'topRight' : 'bottomLeft';
19
22
  const pane = '<pane' +
@@ -23,6 +26,15 @@ export function sheetViewsXml(view, active) {
23
26
  const selection = `<selection pane="${activePane}" activeCell="${escapeAttr(topLeftCell)}" sqref="${escapeAttr(topLeftCell)}"/>`;
24
27
  return `<sheetViews><sheetView${gridLines}${selected} workbookViewId="0">${pane}${selection}</sheetView></sheetViews>`;
25
28
  }
29
+ function frozenSplit(name, value, lines) {
30
+ if (value === undefined)
31
+ return 0;
32
+ assertWritableNumber(value);
33
+ if (!Number.isInteger(value) || value < 0 || value >= lines) {
34
+ throw new AuthoringError(`cannot write ${name}="${value}": a frozen split is a whole count from 0 to ${lines - 1}`);
35
+ }
36
+ return value;
37
+ }
26
38
  export function sheetPrXml(sheet) {
27
39
  const attrs = sheet.codeName === undefined ? '' : ` codeName="${escapeAttr(sheet.codeName)}"`;
28
40
  const children = (sheet.tabColor !== undefined ? `<tabColor ${colorAttrs(sheet.tabColor)}/>` : '') +
@@ -42,8 +54,11 @@ function outlinePrXml(outline) {
42
54
  export function sheetProtectionXml(protection) {
43
55
  if (protection === undefined)
44
56
  return '';
45
- const { flags, credential } = protection;
57
+ const { flags, credential, legacyPasswordHash } = protection;
46
58
  let attrs = '';
59
+ if (legacyPasswordHash !== undefined) {
60
+ attrs += ` password="${checkedToken(legacyPasswordHash, isLegacyPasswordHash, 'legacy sheet password hash')}"`;
61
+ }
47
62
  if (credential !== undefined) {
48
63
  attrs +=
49
64
  ` algorithmName="${escapeAttr(credential.algorithmName)}"` +
@@ -143,15 +158,15 @@ export function pageSetupXml(pageSetup, printerSettingsRelId) {
143
158
  export function pageBreaksXml(breaks, element) {
144
159
  if (breaks.length === 0)
145
160
  return '';
161
+ const manual = breaks.filter((brk) => brk.man !== false).length;
146
162
  const brks = breaks
147
- .map((brk) => {
148
- return `<brk${numAttr('id', brk.id)}${numAttr('max', brk.max)} man="1"/>`;
149
- })
163
+ .map((brk) => `<brk${numAttr('id', brk.id)}${numAttr('min', brk.min)}${numAttr('max', brk.max)}` +
164
+ `${brk.man === false ? '' : ' man="1"'}/>`)
150
165
  .join('');
151
- return `<${element} count="${breaks.length}" manualBreakCount="${breaks.length}">${brks}</${element}>`;
166
+ return `<${element} count="${breaks.length}" manualBreakCount="${manual}">${brks}</${element}>`;
152
167
  }
153
168
  export function parseSheetProtection(attrs) {
154
- if (boolTristate(attrs.sheet) === false)
169
+ if (!boolStrict(attrs.sheet))
155
170
  return undefined;
156
171
  const flags = {};
157
172
  for (const { key } of SHEET_PROTECTION_FLAGS) {
@@ -171,9 +186,14 @@ export function parseSheetProtection(attrs) {
171
186
  saltValue,
172
187
  spinCount: spin,
173
188
  };
174
- return { flags, credential };
189
+ return { flags, credential, ...legacyHash(attrs.password) };
175
190
  }
176
- return { flags };
191
+ return { flags, ...legacyHash(attrs.password) };
192
+ }
193
+ function legacyHash(password) {
194
+ return password !== undefined && isLegacyPasswordHash(password)
195
+ ? { legacyPasswordHash: password }
196
+ : {};
177
197
  }
178
198
  export class PageBreakAccumulator {
179
199
  #target = null;
@@ -189,12 +209,16 @@ export class PageBreakAccumulator {
189
209
  const id = numInteger(attrs.id, 1);
190
210
  if (id === undefined)
191
211
  return;
192
- const brk = { id };
212
+ const brk = {
213
+ id,
214
+ man: boolStrict(attrs.man),
215
+ };
216
+ const min = numInteger(attrs.min, 0);
217
+ if (min !== undefined)
218
+ brk.min = min;
193
219
  const max = numInteger(attrs.max, 0);
194
220
  if (max !== undefined)
195
221
  brk.max = max;
196
- if (boolStrict(attrs.man))
197
- brk.man = true;
198
222
  this.#target.push(brk);
199
223
  }
200
224
  }
@@ -208,13 +232,14 @@ export function applySheetProperties(local, attrs, sheet) {
208
232
  sheet.tabColor = parseColor(attrs);
209
233
  break;
210
234
  case 'outlinePr':
211
- if (attrs.summaryBelow !== undefined)
212
- sheet.outline.summaryBelow = boolPresent(attrs.summaryBelow);
213
- if (attrs.summaryRight !== undefined)
214
- sheet.outline.summaryRight = boolPresent(attrs.summaryRight);
235
+ for (const flag of ['summaryBelow', 'summaryRight']) {
236
+ const value = boolTristate(attrs[flag]);
237
+ if (value !== undefined)
238
+ sheet.outline[flag] = value;
239
+ }
215
240
  break;
216
241
  case 'sheetView':
217
- if (attrs.showGridLines !== undefined && !boolPresent(attrs.showGridLines)) {
242
+ if (boolTristate(attrs.showGridLines) === false) {
218
243
  sheet.view.showGridLines = false;
219
244
  }
220
245
  break;
@@ -223,17 +248,18 @@ export function applySheetProperties(local, attrs, sheet) {
223
248
  sheet.view.state = 'frozen';
224
249
  const xSplit = numInteger(attrs.xSplit, 0);
225
250
  const ySplit = numInteger(attrs.ySplit, 0);
226
- if (xSplit !== undefined)
251
+ if (xSplit !== undefined && xSplit < MAX_COLUMN)
227
252
  sheet.view.xSplit = xSplit;
228
- if (ySplit !== undefined)
253
+ if (ySplit !== undefined && ySplit < MAX_ROW)
229
254
  sheet.view.ySplit = ySplit;
230
255
  if (attrs.topLeftCell !== undefined)
231
256
  sheet.view.topLeftCell = attrs.topLeftCell;
232
257
  }
233
258
  break;
234
259
  case 'pageSetUpPr':
235
- if (attrs.fitToPage !== undefined)
236
- sheet.pageSetup.fitToPage = boolPresent(attrs.fitToPage);
260
+ if (boolTristate(attrs.fitToPage) !== undefined) {
261
+ sheet.pageSetup.fitToPage = boolStrict(attrs.fitToPage);
262
+ }
237
263
  break;
238
264
  case 'printOptions':
239
265
  applyPrintOptions(sheet.printOptions, attrs);
@@ -263,20 +289,20 @@ function applyMargins(margins, attrs) {
263
289
  function applyPageSetup(pageSetup, attrs) {
264
290
  for (const facet of PAGE_SETUP_FACETS) {
265
291
  const raw = attrs[facet.key];
266
- switch (facet.kind) {
267
- case 'count': {
268
- const value = numInteger(raw, 0);
269
- if (value !== undefined)
270
- pageSetup[facet.key] = value;
271
- break;
272
- }
273
- case 'token':
274
- if (raw !== undefined && facet.isValid(raw))
275
- assignPageSetupToken(pageSetup, facet.key, raw);
276
- break;
292
+ if (facet.kind === 'count') {
293
+ const value = numInteger(raw, 0);
294
+ if (value !== undefined)
295
+ pageSetup[facet.key] = value;
296
+ }
297
+ else if (raw === undefined) {
298
+ continue;
299
+ }
300
+ else if (facet.key === 'pageOrder') {
301
+ if (facet.isValid(raw))
302
+ pageSetup.pageOrder = raw;
303
+ }
304
+ else if (facet.isValid(raw)) {
305
+ pageSetup.orientation = raw;
277
306
  }
278
307
  }
279
308
  }
280
- function assignPageSetupToken(pageSetup, key, raw) {
281
- pageSetup[key] = raw;
282
- }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A package part's bytes in Transitional: an XML part declaring a Strict namespace has its
3
+ * declarations and its percentages translated, and any other part is handed back as the same bytes.
4
+ * Only the opening tags that change are re-rendered, so everything else in the part rides through byte
5
+ * for byte. A part the scanner cannot read is left as it came, as it would have been carried.
6
+ */
7
+ export declare function transitionalPart(bytes: Uint8Array, contentType: string): Uint8Array;