@shbernal/ts-xlsx 3.0.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 (277) hide show
  1. package/README.md +34 -24
  2. package/dist/bytes.d.ts +10 -2
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +96 -2
  5. package/dist/core/address.js +87 -33
  6. package/dist/core/array-formula-ranges.d.ts +33 -0
  7. package/dist/core/array-formula-ranges.js +62 -0
  8. package/dist/core/autofilter.d.ts +4 -3
  9. package/dist/core/autofilter.js +31 -30
  10. package/dist/core/axis-handle.d.ts +2 -0
  11. package/dist/core/axis-handle.js +5 -2
  12. package/dist/core/cell.d.ts +4 -3
  13. package/dist/core/cell.js +4 -6
  14. package/dist/core/clone.d.ts +29 -0
  15. package/dist/core/clone.js +24 -0
  16. package/dist/core/color-resolution.js +4 -6
  17. package/dist/core/column.d.ts +1 -0
  18. package/dist/core/column.js +6 -2
  19. package/dist/core/comment-thread.d.ts +7 -3
  20. package/dist/core/comment-thread.js +2 -1
  21. package/dist/core/conditional-formatting-overlay.d.ts +11 -1
  22. package/dist/core/conditional-formatting-overlay.js +30 -3
  23. package/dist/core/conditional-formatting.d.ts +107 -11
  24. package/dist/core/conditional-formatting.js +76 -19
  25. package/dist/core/containers.d.ts +12 -0
  26. package/dist/core/containers.js +5 -0
  27. package/dist/core/data-validation-overlay.d.ts +7 -1
  28. package/dist/core/data-validation-overlay.js +19 -4
  29. package/dist/core/data-validation.d.ts +22 -0
  30. package/dist/core/data-validation.js +30 -4
  31. package/dist/core/date-format.d.ts +20 -0
  32. package/dist/core/date-format.js +170 -0
  33. package/dist/core/date.d.ts +43 -8
  34. package/dist/core/date.js +19 -3
  35. package/dist/core/formula-references.d.ts +26 -0
  36. package/dist/core/formula-references.js +173 -0
  37. package/dist/core/formula-scan.d.ts +2 -0
  38. package/dist/core/formula-scan.js +58 -0
  39. package/dist/core/formula.d.ts +76 -17
  40. package/dist/core/formula.js +87 -80
  41. package/dist/core/function-values.d.ts +4 -0
  42. package/dist/core/function-values.js +58 -0
  43. package/dist/core/future-functions.d.ts +3 -0
  44. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  45. package/dist/core/grid-edits.d.ts +30 -3
  46. package/dist/core/grid-edits.js +248 -81
  47. package/dist/core/grid-shift.d.ts +58 -9
  48. package/dist/core/grid-shift.js +26 -4
  49. package/dist/core/hyperlink.d.ts +39 -0
  50. package/dist/core/hyperlink.js +88 -0
  51. package/dist/core/image.d.ts +49 -14
  52. package/dist/core/image.js +16 -5
  53. package/dist/core/internal.d.ts +10 -2
  54. package/dist/core/limits.d.ts +25 -0
  55. package/dist/core/limits.js +4 -0
  56. package/dist/core/merge.d.ts +18 -4
  57. package/dist/core/merge.js +33 -20
  58. package/dist/core/page-setup.d.ts +26 -11
  59. package/dist/core/pivot-table.d.ts +49 -10
  60. package/dist/core/pivot-table.js +112 -60
  61. package/dist/core/protection.d.ts +66 -4
  62. package/dist/core/protection.js +6 -11
  63. package/dist/core/range.js +34 -30
  64. package/dist/core/row-input.js +2 -2
  65. package/dist/core/row.d.ts +1 -0
  66. package/dist/core/row.js +6 -2
  67. package/dist/core/structured-reference.d.ts +7 -0
  68. package/dist/core/structured-reference.js +7 -0
  69. package/dist/core/style.d.ts +85 -70
  70. package/dist/core/style.js +92 -28
  71. package/dist/core/table-style.d.ts +1 -1
  72. package/dist/core/table-style.js +4 -4
  73. package/dist/core/table.d.ts +64 -16
  74. package/dist/core/table.js +87 -40
  75. package/dist/core/theme.d.ts +2 -0
  76. package/dist/core/theme.js +9 -5
  77. package/dist/core/value.d.ts +60 -28
  78. package/dist/core/value.js +48 -43
  79. package/dist/core/workbook-media.d.ts +34 -0
  80. package/dist/core/workbook-media.js +44 -0
  81. package/dist/core/workbook-styles.d.ts +83 -1
  82. package/dist/core/workbook-styles.js +4 -0
  83. package/dist/core/workbook-theme.js +5 -3
  84. package/dist/core/workbook-vba.js +3 -2
  85. package/dist/core/workbook.d.ts +41 -3
  86. package/dist/core/workbook.js +46 -41
  87. package/dist/core/worksheet-comments.d.ts +2 -1
  88. package/dist/core/worksheet-comments.js +20 -13
  89. package/dist/core/worksheet-merges.d.ts +49 -0
  90. package/dist/core/worksheet-merges.js +65 -0
  91. package/dist/core/worksheet-model.d.ts +10 -2
  92. package/dist/core/worksheet-model.js +28 -9
  93. package/dist/core/worksheet-pictures.d.ts +3 -3
  94. package/dist/core/worksheet-pictures.js +7 -6
  95. package/dist/core/worksheet.d.ts +165 -27
  96. package/dist/core/worksheet.js +206 -89
  97. package/dist/customui/ribbon.js +2 -1
  98. package/dist/entries/core.d.ts +21 -14
  99. package/dist/entries/core.js +14 -7
  100. package/dist/entries/errors.d.ts +1 -0
  101. package/dist/entries/errors.js +1 -0
  102. package/dist/entries/xlsx.d.ts +1 -1
  103. package/dist/errors.d.ts +64 -0
  104. package/dist/errors.js +22 -0
  105. package/dist/hex.d.ts +8 -0
  106. package/dist/hex.js +3 -0
  107. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  108. package/dist/io/cell-metadata/metadata.js +35 -0
  109. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  110. package/dist/io/cell-metadata/rich-values.js +80 -0
  111. package/dist/io/csv/delimiter.d.ts +21 -0
  112. package/dist/io/csv/delimiter.js +18 -0
  113. package/dist/io/csv/errors.d.ts +14 -0
  114. package/dist/io/csv/errors.js +5 -0
  115. package/dist/io/csv/read.d.ts +6 -1
  116. package/dist/io/csv/read.js +51 -22
  117. package/dist/io/csv/write.d.ts +19 -5
  118. package/dist/io/csv/write.js +24 -44
  119. package/dist/io/opc/inflate.js +14 -0
  120. package/dist/io/opc/namespaces.d.ts +16 -0
  121. package/dist/io/opc/namespaces.js +4 -0
  122. package/dist/io/opc/part-paths.d.ts +9 -0
  123. package/dist/io/opc/part-paths.js +5 -5
  124. package/dist/io/opc/read-opc.d.ts +58 -14
  125. package/dist/io/opc/read-opc.js +49 -21
  126. package/dist/io/opc/read-options.d.ts +5 -1
  127. package/dist/io/opc/sniff-format.d.ts +3 -3
  128. package/dist/io/opc/sniff-format.js +2 -2
  129. package/dist/io/opc/strict-relationships.d.ts +2 -0
  130. package/dist/io/opc/strict-relationships.js +12 -0
  131. package/dist/io/read-policy/column-budget.d.ts +26 -0
  132. package/dist/io/read-policy/column-budget.js +18 -0
  133. package/dist/io/read-policy/read-repair.d.ts +74 -0
  134. package/dist/io/read-policy/read-repair.js +119 -0
  135. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  136. package/dist/io/style/cell-style-resolution.js +30 -0
  137. package/dist/io/style/xf-style.d.ts +23 -1
  138. package/dist/io/style/xf-style.js +8 -0
  139. package/dist/io/xlsb/formula.d.ts +2 -2
  140. package/dist/io/xlsb/formula.js +52 -35
  141. package/dist/io/xlsb/primitives.js +8 -14
  142. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  143. package/dist/io/xlsb/ptg-functions.js +495 -596
  144. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  145. package/dist/io/xlsb/read-metadata.js +69 -0
  146. package/dist/io/xlsb/read-styles.js +45 -81
  147. package/dist/io/xlsb/read-worksheet.d.ts +26 -1
  148. package/dist/io/xlsb/read-worksheet.js +148 -106
  149. package/dist/io/xlsb/read.d.ts +6 -3
  150. package/dist/io/xlsb/read.js +70 -31
  151. package/dist/io/xlsb/record-stream.d.ts +30 -0
  152. package/dist/io/xlsb/record-stream.js +20 -0
  153. package/dist/io/xlsb/record-types.d.ts +14 -5
  154. package/dist/io/xlsb/record-types.js +14 -5
  155. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  156. package/dist/io/xlsb/sheet-protection.js +60 -0
  157. package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
  158. package/dist/io/xlsx/cell-accumulator.js +74 -21
  159. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  160. package/dist/io/xlsx/cell-metadata.js +162 -0
  161. package/dist/io/xlsx/cell-value.d.ts +19 -5
  162. package/dist/io/xlsx/cell-value.js +45 -30
  163. package/dist/io/xlsx/color-xml.js +3 -2
  164. package/dist/io/xlsx/column-span.d.ts +11 -0
  165. package/dist/io/xlsx/column-span.js +9 -0
  166. package/dist/io/xlsx/comments.d.ts +9 -30
  167. package/dist/io/xlsx/comments.js +25 -83
  168. package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
  169. package/dist/io/xlsx/conditional-formatting.js +437 -138
  170. package/dist/io/xlsx/data-validation.d.ts +12 -7
  171. package/dist/io/xlsx/data-validation.js +67 -53
  172. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  173. package/dist/io/xlsx/edit-vba.js +52 -52
  174. package/dist/io/xlsx/font-xml.d.ts +7 -0
  175. package/dist/io/xlsx/font-xml.js +104 -0
  176. package/dist/io/xlsx/hyperlinks.d.ts +19 -21
  177. package/dist/io/xlsx/hyperlinks.js +12 -32
  178. package/dist/io/xlsx/images.d.ts +49 -16
  179. package/dist/io/xlsx/images.js +149 -50
  180. package/dist/io/xlsx/namespaces.d.ts +24 -0
  181. package/dist/io/xlsx/namespaces.js +11 -0
  182. package/dist/io/xlsx/package-plan.d.ts +60 -15
  183. package/dist/io/xlsx/package-plan.js +83 -18
  184. package/dist/io/xlsx/part-names.d.ts +6 -8
  185. package/dist/io/xlsx/part-names.js +3 -7
  186. package/dist/io/xlsx/pivot.d.ts +5 -5
  187. package/dist/io/xlsx/pivot.js +26 -24
  188. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  189. package/dist/io/xlsx/preserved-splices.js +74 -0
  190. package/dist/io/xlsx/read-comments.d.ts +29 -0
  191. package/dist/io/xlsx/read-comments.js +75 -0
  192. package/dist/io/xlsx/read-parts.d.ts +59 -0
  193. package/dist/io/xlsx/read-parts.js +259 -0
  194. package/dist/io/xlsx/read-pivot.js +11 -4
  195. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
  196. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
  197. package/dist/io/xlsx/read-rows.d.ts +35 -11
  198. package/dist/io/xlsx/read-rows.js +73 -49
  199. package/dist/io/xlsx/read-shared-strings.js +3 -2
  200. package/dist/io/xlsx/read-styles.d.ts +0 -30
  201. package/dist/io/xlsx/read-styles.js +45 -105
  202. package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
  203. package/dist/io/xlsx/read-workbook-xml.js +175 -0
  204. package/dist/io/xlsx/read-worksheet.d.ts +9 -3
  205. package/dist/io/xlsx/read-worksheet.js +70 -177
  206. package/dist/io/xlsx/read.d.ts +3 -14
  207. package/dist/io/xlsx/read.js +74 -411
  208. package/dist/io/xlsx/relationships.d.ts +3 -0
  209. package/dist/io/xlsx/relationships.js +4 -0
  210. package/dist/io/xlsx/rich-text.js +1 -1
  211. package/dist/io/xlsx/row-position.d.ts +20 -0
  212. package/dist/io/xlsx/row-position.js +11 -0
  213. package/dist/io/xlsx/row-xml.d.ts +139 -0
  214. package/dist/io/xlsx/row-xml.js +222 -0
  215. package/dist/io/xlsx/shared-formulas.js +2 -2
  216. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  217. package/dist/io/xlsx/sheet-properties.js +182 -20
  218. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  219. package/dist/io/xlsx/strict-parts.js +171 -0
  220. package/dist/io/xlsx/style-elements.d.ts +41 -0
  221. package/dist/io/xlsx/style-elements.js +136 -0
  222. package/dist/io/xlsx/styles.d.ts +17 -3
  223. package/dist/io/xlsx/styles.js +13 -209
  224. package/dist/io/xlsx/tables.d.ts +10 -2
  225. package/dist/io/xlsx/tables.js +55 -52
  226. package/dist/io/xlsx/theme-xml.d.ts +32 -4
  227. package/dist/io/xlsx/theme-xml.js +106 -71
  228. package/dist/io/xlsx/threaded-comments.d.ts +14 -11
  229. package/dist/io/xlsx/threaded-comments.js +16 -8
  230. package/dist/io/xlsx/workbook-xml.d.ts +45 -6
  231. package/dist/io/xlsx/workbook-xml.js +62 -54
  232. package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
  233. package/dist/io/xlsx/worksheet-xml.js +69 -282
  234. package/dist/io/xlsx/write-stream.d.ts +90 -19
  235. package/dist/io/xlsx/write-stream.js +80 -25
  236. package/dist/io/xlsx/write.d.ts +12 -6
  237. package/dist/io/xlsx/write.js +226 -123
  238. package/dist/rel-type.d.ts +25 -0
  239. package/dist/rel-type.js +9 -0
  240. package/dist/token-set.d.ts +13 -0
  241. package/dist/token-set.js +4 -1
  242. package/dist/vba/bytes.d.ts +16 -5
  243. package/dist/vba/bytes.js +27 -6
  244. package/dist/vba/cfb-format.d.ts +55 -0
  245. package/dist/vba/cfb-format.js +35 -0
  246. package/dist/vba/cfb-writer.d.ts +8 -0
  247. package/dist/vba/cfb-writer.js +40 -40
  248. package/dist/vba/cfb.d.ts +21 -4
  249. package/dist/vba/cfb.js +124 -90
  250. package/dist/vba/codepage.js +2 -1
  251. package/dist/vba/dir-records.d.ts +6 -0
  252. package/dist/vba/dir-records.js +10 -2
  253. package/dist/vba/errors.d.ts +11 -4
  254. package/dist/vba/index.d.ts +0 -2
  255. package/dist/vba/index.js +0 -2
  256. package/dist/vba/ms-ovba.d.ts +6 -2
  257. package/dist/vba/ms-ovba.js +66 -22
  258. package/dist/vba/project-editor.js +129 -119
  259. package/dist/vba/project.d.ts +15 -1
  260. package/dist/vba/project.js +38 -13
  261. package/dist/vba/vba-encoding.d.ts +4 -10
  262. package/dist/vba/vba-encoding.js +3 -17
  263. package/dist/xml/xml-attrs.d.ts +37 -0
  264. package/dist/xml/xml-attrs.js +30 -0
  265. package/dist/xml/xml-chars.d.ts +40 -0
  266. package/dist/xml/xml-chars.js +12 -0
  267. package/dist/xml/xml-namespaces.d.ts +31 -0
  268. package/dist/xml/xml-namespaces.js +50 -0
  269. package/dist/xml/xml-read.d.ts +101 -11
  270. package/dist/xml/xml-read.js +150 -18
  271. package/dist/xml/xml-scan.d.ts +25 -34
  272. package/dist/xml/xml-scan.js +69 -46
  273. package/dist/xml/xml.d.ts +59 -26
  274. package/dist/xml/xml.js +45 -23
  275. package/package.json +15 -9
  276. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  277. package/dist/core/modern-functions.d.ts +0 -1
@@ -1,13 +1,17 @@
1
1
  import { strToU8, zip, zipSync } from 'fflate';
2
- import { AuthoringError } from '../../errors.js';
3
- import { relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
2
+ import { formulaNamesInScope } from '../../core/formula.js';
3
+ import { pictureProperties } from '../../core/image.js';
4
+ import { AuthoringError, InternalError, quoted } from '../../errors.js';
5
+ import { isRelType } from '../../rel-type.js';
6
+ import { relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
4
7
  import { relsPartXml } from '../opc/rels.js';
5
8
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
6
- import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
7
- import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
8
- import { drawingRelsXml, drawingXml } from './images.js';
9
- import { planMedia, planPreservedParts, SheetRelIds, } from './package-plan.js';
10
- import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, mediaPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, printerSettingsPart, SHARED_STRINGS_PART, STYLES_PART, tablePart, threadedCommentsPart, vmlDrawingPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
9
+ import { RICH_VALUE_STRUCTURES_XML, WorkbookMetadataTable } from './cell-metadata.js';
10
+ import { collectComments, commentsXml, liveCells, vmlDrawingXml } from './comments.js';
11
+ import { planHyperlinks } from './hyperlinks.js';
12
+ import { drawingXml } from './images.js';
13
+ import { planMedia, planPreservedParts, RelationshipLedger, } from './package-plan.js';
14
+ import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, mediaPart, METADATA_PART, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, printerSettingsPart, RICH_VALUE_STRUCTURES_PART, RICH_VALUES_PART, SHARED_STRINGS_PART, STYLES_PART, tablePart, threadedCommentsPart, vmlDrawingPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
11
15
  import { pivotCacheDefinitionXml, pivotCacheRecordsXml, pivotTableXml } from './pivot.js';
12
16
  import { REL } from './relationships.js';
13
17
  import { SharedStringTable } from './shared-strings.js';
@@ -15,9 +19,8 @@ import { StyleRegistry } from './styles.js';
15
19
  import { tableXml } from './tables.js';
16
20
  import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
17
21
  import { personsXml, threadedCommentsXml } from './threaded-comments.js';
18
- import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
19
- import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
20
- export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
22
+ import { appPropsXml, contentTypesXml, corePropsXml, workbookXml, } from './workbook-xml.js';
23
+ import { worksheetXml } from './worksheet-xml.js';
21
24
  export function writeXlsx(workbook, options = {}) {
22
25
  return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
23
26
  }
@@ -48,52 +51,77 @@ export function createStyleRegistry(workbook) {
48
51
  styles.addTableStyle(style);
49
52
  return styles;
50
53
  }
54
+ function keptDrawingPath(sheet) {
55
+ return sheet.preservedReferences.find((reference) => reference.element === 'drawing')?.entryPath;
56
+ }
57
+ function drawingImages(sheet, media, drawingRels) {
58
+ return sheet.images.map((image) => {
59
+ const { number: mediaNumber, image: registered } = media.resolve(image.imageId);
60
+ const embedId = drawingRels.add(REL.image, mediaPart(mediaNumber, registered.extension));
61
+ const properties = pictureProperties(image);
62
+ const target = properties.hyperlink?.target;
63
+ if (target === undefined)
64
+ return { anchor: image.anchor, embedId, properties };
65
+ const hyperlinkId = target.startsWith('#')
66
+ ? drawingRels.addInDocument(REL.hyperlink, target)
67
+ : drawingRels.addExternal(REL.hyperlink, target);
68
+ return { anchor: image.anchor, embedId, properties, hyperlinkId };
69
+ });
70
+ }
51
71
  function planSheet(context) {
52
- const { sheet, index, media, preserved, numbering } = context;
53
- const rels = new SheetRelIds();
54
- const tables = sheet.tables.map((table) => ({
55
- table,
56
- number: ++numbering.table,
57
- relId: rels.next(),
58
- }));
72
+ const { sheet, index, media, preserved, numbering, flushed } = context;
73
+ const rels = new RelationshipLedger(worksheetPart(index + 1));
74
+ const tables = sheet.tables.map((table) => {
75
+ const number = ++numbering.table;
76
+ return { table, number, relId: rels.add(REL.table, tablePart(number)) };
77
+ });
59
78
  let drawing = null;
60
- if (sheet.images.length > 0) {
61
- const images = sheet.images.map((image, j) => {
62
- const { number, image: registered } = media.resolve(image.imageId);
63
- return {
64
- anchor: image.anchor,
65
- embedId: `rId${j + 1}`,
66
- mediaNumber: number,
67
- extension: registered.extension,
68
- };
69
- });
70
- drawing = { number: ++numbering.drawing, relId: rels.next(), images };
79
+ if (sheet.images.length > 0 && keptDrawingPath(sheet) === undefined) {
80
+ const number = ++numbering.drawing;
81
+ const path = drawingPart(number);
82
+ const drawingRels = new RelationshipLedger(path);
83
+ drawing = {
84
+ number,
85
+ relId: rels.add(REL.drawing, path),
86
+ images: drawingImages(sheet, media, drawingRels),
87
+ relationships: drawingRels.relationships,
88
+ };
71
89
  }
72
90
  const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
73
- const sheetComments = collectComments(sheet, threads);
74
- const comments = sheetComments.length === 0
91
+ const sheetComments = collectComments(liveCells(sheet), threads, flushed?.notes ?? []);
92
+ let comments = null;
93
+ if (sheetComments.length > 0) {
94
+ const vmlRelId = rels.add(REL.vmlDrawing, vmlDrawingPart(index + 1));
95
+ rels.add(REL.comments, commentsPart(index + 1));
96
+ comments = { number: index + 1, comments: sheetComments, vmlRelId };
97
+ }
98
+ let threadedComments = null;
99
+ if (threads.length > 0) {
100
+ rels.add(REL.threadedComment, threadedCommentsPart(index + 1));
101
+ threadedComments = { number: index + 1, threads };
102
+ }
103
+ const printerData = sheet.pageSetup.printerSettings;
104
+ const printerSettings = printerData === undefined
75
105
  ? null
76
106
  : {
77
107
  number: index + 1,
78
- comments: sheetComments,
79
- vmlRelId: rels.next(),
80
- commentsRelId: rels.next(),
108
+ data: printerData,
109
+ relId: rels.add(REL.printerSettings, printerSettingsPart(index + 1)),
81
110
  };
82
- const threadedComments = threads.length === 0 ? null : { number: index + 1, threads, relId: rels.next() };
83
- const printerData = sheet.pageSetup.printerSettings;
84
- const printerSettings = printerData === undefined ? null : { number: index + 1, data: printerData, relId: rels.next() };
85
- const hyperlinks = planHyperlinks(collectHyperlinks(sheet), rels);
111
+ const hyperlinks = planHyperlinks(sheet.hyperlinks, rels);
86
112
  let background = null;
87
113
  if (sheet.backgroundImageId !== undefined) {
88
114
  const { number, image } = media.resolve(sheet.backgroundImageId);
89
- background = { relId: rels.next(), mediaNumber: number, extension: image.extension };
115
+ background = { relId: rels.add(REL.image, mediaPart(number, image.extension)) };
90
116
  }
91
- const preservedRefs = (preserved.perSheet[index] ?? []).map((reference) => ({ ...reference, relId: rels.next() }));
117
+ const preservedRefs = (preserved.perSheet[index] ?? []).map((reference) => ({ ...reference, relId: rels.add(reference.relType, reference.entryPath) }));
92
118
  const pivots = sheet.pivotTables.map((table) => {
93
119
  const number = ++numbering.pivot;
94
- return { number, cacheId: String(number), table, sheetRelId: rels.next(), workbookRelId: '' };
120
+ rels.add(REL.pivotTable, pivotTablePart(number));
121
+ return { number, cacheId: String(number), table, workbookRelId: '' };
95
122
  });
96
123
  return {
124
+ relationships: rels.relationships,
97
125
  tables,
98
126
  drawing,
99
127
  comments,
@@ -119,7 +147,7 @@ function resolveSheetReferences(plan) {
119
147
  const preservedDrawingRelId = refs.find((ref) => ref.element === 'drawing')?.relId ?? null;
120
148
  const legacyDrawingHFRelId = refs.find((ref) => ref.element === 'legacyDrawingHF')?.relId ?? null;
121
149
  const slicerRelIds = refs
122
- .filter((ref) => ref.relType.endsWith('/slicer'))
150
+ .filter((ref) => isRelType(ref.relType, 'slicer'))
123
151
  .map((ref) => ref.relId);
124
152
  return {
125
153
  drawingRelId: plan.drawing?.relId ?? preservedDrawingRelId,
@@ -130,27 +158,57 @@ function resolveSheetReferences(plan) {
130
158
  slicerRelIds,
131
159
  };
132
160
  }
133
- function assignWorkbookRelIds(context) {
134
- const { sheetCount, hasSharedStrings, hasPersons, preservedWorkbook, pivots } = context;
135
- const modeledCount = sheetCount + FIXED_WORKBOOK_REL_COUNT + (hasSharedStrings ? 1 : 0);
136
- const personsRelId = hasPersons ? `rId${modeledCount + 1}` : null;
137
- const preservedBase = modeledCount + (personsRelId === null ? 0 : 1);
138
- const preservedWorkbookRels = preservedWorkbook.map((ref, i) => ({
161
+ function planWorkbookRelationships(context) {
162
+ const { sheetCount, hasSharedStrings, hasPersons, hasCellMetadata, hasRichValues, preservedWorkbook, pivots, } = context;
163
+ const rels = new RelationshipLedger(WORKBOOK_PART);
164
+ const sheetRelIds = Array.from({ length: sheetCount }, (_, i) => rels.add(REL.worksheet, worksheetPart(i + 1)));
165
+ rels.add(REL.styles, STYLES_PART);
166
+ rels.add(REL.theme, THEME_PART_PATH);
167
+ if (hasSharedStrings)
168
+ rels.add(REL.sharedStrings, SHARED_STRINGS_PART);
169
+ if (hasPersons)
170
+ rels.add(REL.person, PERSONS_PART);
171
+ if (hasCellMetadata)
172
+ rels.add(REL.sheetMetadata, METADATA_PART);
173
+ if (hasRichValues) {
174
+ rels.add(REL.rdRichValue, RICH_VALUES_PART);
175
+ rels.add(REL.rdRichValueStructure, RICH_VALUE_STRUCTURES_PART);
176
+ }
177
+ const preservedWorkbookRels = preservedWorkbook.map((ref) => ({
139
178
  ...ref,
140
- relId: `rId${preservedBase + 1 + i}`,
179
+ relId: rels.add(ref.relType, ref.entryPath),
141
180
  }));
142
- const pivotBase = preservedBase + preservedWorkbook.length;
143
- pivots.forEach((pivot, i) => {
144
- pivot.workbookRelId = `rId${pivotBase + 1 + i}`;
145
- });
146
- return { personsRelId, preservedWorkbookRels };
181
+ for (const pivot of pivots) {
182
+ pivot.workbookRelId = rels.add(REL.pivotCacheDefinition, pivotCacheDefinitionPart(pivot.number));
183
+ }
184
+ return { sheetRelIds, preservedWorkbookRels, relationships: rels.relationships };
147
185
  }
148
- function planPackage(workbook, sheets) {
186
+ function planRootRelationships(rootRefs) {
187
+ const rels = new RelationshipLedger('');
188
+ rels.add(REL.officeDocument, WORKBOOK_PART);
189
+ rels.add(REL.coreProps, CORE_PROPS_PART);
190
+ rels.add(REL.extProps, APP_PROPS_PART);
191
+ for (const ref of rootRefs)
192
+ rels.add(ref.relType, ref.entryPath);
193
+ return rels.relationships;
194
+ }
195
+ function planPackage(workbook, sheets, flushed) {
149
196
  const media = planMedia(workbook, sheets);
150
- const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
151
- const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length);
197
+ const pictures = new Map();
198
+ let generatedDrawingCount = 0;
199
+ for (const sheet of sheets) {
200
+ if (sheet.images.length === 0)
201
+ continue;
202
+ const kept = keptDrawingPath(sheet);
203
+ if (kept === undefined)
204
+ generatedDrawingCount += 1;
205
+ else
206
+ pictures.set(kept, (ledger) => drawingImages(sheet, media, ledger));
207
+ }
208
+ const generatedPivotCount = sheets.reduce((total, sheet) => total + sheet.pivotTables.length, 0);
209
+ const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount, pictures);
152
210
  const numbering = { table: 0, drawing: 0, pivot: 0 };
153
- const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering }));
211
+ const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering, flushed: flushed?.get(sheet) }));
154
212
  return {
155
213
  media,
156
214
  preserved,
@@ -159,58 +217,99 @@ function planPackage(workbook, sheets) {
159
217
  allPivots: perSheet.flatMap((plan) => plan.pivots),
160
218
  };
161
219
  }
162
- function serialiseSheets(workbook, sheets, plan, styles, sharedStrings, flushed) {
220
+ function serialiseSheets(context) {
221
+ const { workbook, sheets, plan, styles, sharedStrings, cellMetadata, flushed } = context;
163
222
  return sheets.map((sheet, i) => {
164
223
  const sheetPlan = plan.perSheet[i];
165
- return worksheetXml(sheet, sheetPlan.tables, styles, resolveSheetReferences(sheetPlan), sheetPlan.hyperlinks, sharedStrings, i === workbook.activeTabIndex, flushed?.get(sheet));
224
+ return worksheetXml({
225
+ sheet,
226
+ tables: sheetPlan.tables,
227
+ styles,
228
+ references: resolveSheetReferences(sheetPlan),
229
+ hyperlinks: sheetPlan.hyperlinks,
230
+ sharedStrings,
231
+ cellMetadata,
232
+ dateEpoch: workbook.dateEpoch,
233
+ formulaNames: formulaNamesInScope(workbook.definedNames, sheet.name),
234
+ active: i === workbook.activeTabIndex,
235
+ flushed: flushed?.get(sheet),
236
+ });
166
237
  });
167
238
  }
168
239
  function emitPackageParts(context) {
169
- const { workbook, sheets, plan, styles, sharedStrings, sheetXml } = context;
240
+ const { workbook, sheets, plan, styles, sharedStrings, cellMetadata, sheetXml } = context;
170
241
  const { media, preserved, perSheet, allTables, allPivots } = plan;
171
242
  const hasSharedStrings = sharedStrings !== null && !sharedStrings.isEmpty;
243
+ const hasCellMetadata = !cellMetadata.isEmpty;
244
+ const hasRichValues = cellMetadata.hasRichValues;
172
245
  const commentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.comments);
173
246
  const drawingNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.drawing);
174
247
  const printerSettingsNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.printerSettings);
175
248
  const threadedCommentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.threadedComments);
176
249
  const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
177
- const { personsRelId, preservedWorkbookRels } = assignWorkbookRelIds({
250
+ const workbookRels = planWorkbookRelationships({
178
251
  sheetCount: sheets.length,
179
252
  hasSharedStrings,
180
253
  hasPersons: persons.length > 0,
254
+ hasCellMetadata,
255
+ hasRichValues,
181
256
  preservedWorkbook: preserved.workbook,
182
257
  pivots: allPivots,
183
258
  });
184
- const files = {
185
- '[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
186
- '_rels/.rels': strToU8(rootRelsXml(preserved.root)),
187
- [CORE_PROPS_PART]: strToU8(corePropsXml(workbook.properties)),
188
- [APP_PROPS_PART]: strToU8(appPropsXml(workbook.properties)),
189
- [WORKBOOK_PART]: strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
190
- [relsPathFor(WORKBOOK_PART)]: strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
191
- [STYLES_PART]: strToU8(styles.toXml()),
192
- };
259
+ const { preservedWorkbookRels } = workbookRels;
260
+ const files = new PackageFiles();
261
+ files.add('[Content_Types].xml', strToU8(contentTypesXml({
262
+ sheetCount: sheets.length,
263
+ tables: allTables,
264
+ commentNumbers,
265
+ drawingNumbers,
266
+ printerSettingsNumbers,
267
+ mediaExtensions: media.extensions,
268
+ hasSharedStrings,
269
+ preservedParts: preserved.parts,
270
+ pivots: allPivots,
271
+ preservedWorkbookRefs: preservedWorkbookRels,
272
+ threadedCommentNumbers,
273
+ hasPersons: persons.length > 0,
274
+ hasCellMetadata,
275
+ hasRichValues,
276
+ })));
277
+ files.add(relsPathFor(''), strToU8(relsPartXml(planRootRelationships(preserved.root))));
278
+ files.add(CORE_PROPS_PART, strToU8(corePropsXml(workbook.properties)));
279
+ files.add(APP_PROPS_PART, strToU8(appPropsXml(workbook.properties)));
280
+ files.add(WORKBOOK_PART, strToU8(workbookXml(workbook, workbookRels, allPivots)));
281
+ files.add(relsPathFor(WORKBOOK_PART), strToU8(relsPartXml(workbookRels.relationships)));
282
+ files.add(STYLES_PART, strToU8(styles.toXml()));
193
283
  if (!preserved.themeEmitted) {
194
284
  const overrides = workbook.themeOverrides;
195
- files[THEME_PART_PATH] = strToU8(overrides === undefined
285
+ files.add(THEME_PART_PATH, strToU8(overrides === undefined
196
286
  ? DEFAULT_THEME_XML
197
- : applyThemeOverrides(DEFAULT_THEME_XML, overrides));
287
+ : applyThemeOverrides(DEFAULT_THEME_XML, overrides)));
198
288
  }
199
289
  if (hasSharedStrings) {
200
- files[SHARED_STRINGS_PART] = strToU8(sharedStrings.toXml());
290
+ files.add(SHARED_STRINGS_PART, strToU8(sharedStrings.toXml()));
201
291
  }
202
292
  if (persons.length > 0)
203
- files[PERSONS_PART] = strToU8(personsXml(persons));
204
- for (const part of media.parts) {
205
- files[mediaPart(part.number, part.extension)] = part.data;
293
+ files.add(PERSONS_PART, strToU8(personsXml(persons)));
294
+ if (hasCellMetadata)
295
+ files.add(METADATA_PART, strToU8(cellMetadata.toXml()));
296
+ if (hasRichValues) {
297
+ files.add(RICH_VALUES_PART, strToU8(cellMetadata.richValuesXml()));
298
+ files.add(RICH_VALUE_STRUCTURES_PART, strToU8(RICH_VALUE_STRUCTURES_XML));
206
299
  }
207
- emitSheetParts(files, perSheet, sheetXml);
208
- for (const { table, number } of allTables) {
209
- files[tablePart(number)] = strToU8(tableXml(table, number));
300
+ for (const part of media.parts) {
301
+ files.add(mediaPart(part.number, part.extension), part.data);
210
302
  }
303
+ emitSheetParts(files, perSheet, sheetXml, (id) => workbook.getPerson(id));
304
+ perSheet.forEach(({ tables }, index) => {
305
+ const formulaNames = formulaNamesInScope(workbook.definedNames, sheets[index]?.name);
306
+ for (const { table, number } of tables) {
307
+ files.add(tablePart(number), strToU8(tableXml(table, number, formulaNames)));
308
+ }
309
+ });
211
310
  emitPivotParts(files, allPivots);
212
311
  emitPreservedParts(files, preserved.parts);
213
- return files;
312
+ return files.toRecord();
214
313
  }
215
314
  export function buildPackageParts(workbook, options = {}) {
216
315
  const sheets = workbook.worksheets;
@@ -219,41 +318,52 @@ export function buildPackageParts(workbook, options = {}) {
219
318
  }
220
319
  const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
221
320
  const styles = options.styles ?? createStyleRegistry(workbook);
222
- const plan = planPackage(workbook, sheets);
223
- const sheetXml = serialiseSheets(workbook, sheets, plan, styles, sharedStrings, options.flushed);
224
- return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, sheetXml });
321
+ const cellMetadata = options.cellMetadata ?? new WorkbookMetadataTable();
322
+ const plan = planPackage(workbook, sheets, options.flushed);
323
+ const sheetXml = serialiseSheets({
324
+ workbook,
325
+ sheets,
326
+ plan,
327
+ styles,
328
+ sharedStrings,
329
+ cellMetadata,
330
+ flushed: options.flushed,
331
+ });
332
+ return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, cellMetadata, sheetXml });
333
+ }
334
+ class PackageFiles {
335
+ #files = Object.create(null);
336
+ add(path, bytes) {
337
+ if (path in this.#files) {
338
+ throw new InternalError(`two package parts claim the path ${quoted(path)}`);
339
+ }
340
+ this.#files[path] = bytes;
341
+ }
342
+ toRecord() {
343
+ return this.#files;
344
+ }
225
345
  }
226
- function emitSheetParts(files, perSheet, sheetXml) {
346
+ function emitSheetParts(files, perSheet, sheetXml, personById) {
227
347
  perSheet.forEach((plan, i) => {
228
- const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
229
- const hasExternalHyperlink = hyperlinks.some((link) => link.relId !== undefined);
230
- files[worksheetPart(i + 1)] = strToU8(sheetXml[i]);
231
- if (tables.length > 0 ||
232
- drawing !== null ||
233
- comments !== null ||
234
- threadedComments !== null ||
235
- printerSettings !== null ||
236
- background !== null ||
237
- hasExternalHyperlink ||
238
- preservedRefs.length > 0 ||
239
- pivots.length > 0) {
240
- files[relsPathFor(worksheetPart(i + 1))] = strToU8(worksheetRelsXml(tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots));
348
+ const { relationships, drawing, comments, threadedComments, printerSettings } = plan;
349
+ files.add(worksheetPart(i + 1), strToU8(sheetXml[i]));
350
+ if (relationships.length > 0) {
351
+ files.add(relsPathFor(worksheetPart(i + 1)), strToU8(relsPartXml(relationships)));
241
352
  }
242
353
  if (printerSettings !== null) {
243
- files[printerSettingsPart(printerSettings.number)] = printerSettings.data;
354
+ files.add(printerSettingsPart(printerSettings.number), printerSettings.data);
244
355
  }
245
356
  if (drawing !== null) {
246
357
  const drawingPath = drawingPart(drawing.number);
247
- files[drawingPath] = strToU8(drawingXml(drawing.images));
248
- const targets = drawing.images.map((image) => relativePartPath(drawingPath, mediaPart(image.mediaNumber, image.extension)));
249
- files[relsPathFor(drawingPath)] = strToU8(drawingRelsXml(targets));
358
+ files.add(drawingPath, strToU8(drawingXml(drawing.images)));
359
+ files.add(relsPathFor(drawingPath), strToU8(relsPartXml(drawing.relationships)));
250
360
  }
251
361
  if (comments !== null) {
252
- files[commentsPart(comments.number)] = strToU8(commentsXml(comments.comments));
253
- files[vmlDrawingPart(comments.number)] = strToU8(vmlDrawingXml(comments.comments));
362
+ files.add(commentsPart(comments.number), strToU8(commentsXml(comments.comments)));
363
+ files.add(vmlDrawingPart(comments.number), strToU8(vmlDrawingXml(comments.comments)));
254
364
  }
255
365
  if (threadedComments !== null) {
256
- files[threadedCommentsPart(threadedComments.number)] = strToU8(threadedCommentsXml(threadedComments.threads));
366
+ files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads, personById)));
257
367
  }
258
368
  });
259
369
  }
@@ -262,30 +372,23 @@ function emitPivotParts(files, allPivots) {
262
372
  const { number, cacheId, table } = pivot;
263
373
  const tablePath = pivotTablePart(number);
264
374
  const definitionPath = pivotCacheDefinitionPart(number);
265
- files[tablePath] = strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId));
266
- files[relsPathFor(tablePath)] = strToU8(relsPartXml([
267
- {
268
- id: 'rId1',
269
- type: REL.pivotCacheDefinition,
270
- target: relativePartPath(tablePath, definitionPath),
271
- },
272
- ]));
273
- files[definitionPath] = strToU8(pivotCacheDefinitionXml(table));
274
- files[relsPathFor(definitionPath)] = strToU8(relsPartXml([
275
- {
276
- id: 'rId1',
277
- type: REL.pivotCacheRecords,
278
- target: relativePartPath(definitionPath, pivotCacheRecordsPart(number)),
279
- },
280
- ]));
281
- files[pivotCacheRecordsPart(number)] = strToU8(pivotCacheRecordsXml(table));
375
+ const recordsPath = pivotCacheRecordsPart(number);
376
+ const tableRels = new RelationshipLedger(tablePath);
377
+ tableRels.add(REL.pivotCacheDefinition, definitionPath);
378
+ files.add(tablePath, strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId)));
379
+ files.add(relsPathFor(tablePath), strToU8(relsPartXml(tableRels.relationships)));
380
+ const definitionRels = new RelationshipLedger(definitionPath);
381
+ const recordsRelId = definitionRels.add(REL.pivotCacheRecords, recordsPath);
382
+ files.add(definitionPath, strToU8(pivotCacheDefinitionXml(table, recordsRelId)));
383
+ files.add(relsPathFor(definitionPath), strToU8(relsPartXml(definitionRels.relationships)));
384
+ files.add(recordsPath, strToU8(pivotCacheRecordsXml(table)));
282
385
  }
283
386
  }
284
387
  function emitPreservedParts(files, parts) {
285
388
  for (const part of parts) {
286
- files[part.path] = part.bytes;
389
+ files.add(part.path, part.bytes);
287
390
  if (part.relsPath !== null && part.relsXml !== null) {
288
- files[part.relsPath] = strToU8(part.relsXml);
391
+ files.add(part.relsPath, strToU8(part.relsXml));
289
392
  }
290
393
  }
291
394
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The last segment of a relationship Type (`vbaProjectSignatureAgile` in
3
+ * `http://schemas.microsoft.com/office/2014/relationships/vbaProjectSignatureAgile`), or the whole Type
4
+ * when it has no `/`: for a caller that looks the segment up rather than testing for one class.
5
+ *
6
+ * {@link isRelType} is not built on it, because a class name may span segments: the ribbon's is
7
+ * `ui/extensibility`.
8
+ */
9
+ export declare function relTypeSegment(type: string): string;
10
+ /**
11
+ * Does a relationship Type name this class of part?
12
+ *
13
+ * A Type is a URI whose last segment names the part class, and the namespace in front of it varies by
14
+ * relationship family and by the schema version a producer wrote against. So the match is on that
15
+ * last segment.
16
+ *
17
+ * **The separator is the load-bearing character.** `/slicer` and `slicer` are not the same test:
18
+ * without the slash, `slicer` matches `slicerCache`, `table` matches `pivotTable`, and a lookup
19
+ * quietly gathers a part class nobody asked for. Every caller used to spell the slash itself, ten
20
+ * times over, with nowhere to say why it was there.
21
+ */
22
+ export declare function isRelType(type: string, name: string): boolean;
23
+ /** {@link isRelType} against several part classes at once, for the predicates that partition
24
+ * relationships into "modeled", "preserved verbatim", and "dropped". */
25
+ export declare function isAnyRelType(type: string, ...names: readonly string[]): boolean;
@@ -0,0 +1,9 @@
1
+ export function relTypeSegment(type) {
2
+ return type.slice(type.lastIndexOf('/') + 1);
3
+ }
4
+ export function isRelType(type, name) {
5
+ return type.endsWith(`/${name}`);
6
+ }
7
+ export function isAnyRelType(type, ...names) {
8
+ return names.some((name) => isRelType(type, name));
9
+ }
@@ -13,3 +13,16 @@
13
13
  * expression instead of as its members.
14
14
  */
15
15
  export declare function tokenSet<T extends string>(members: Record<T, true>): (value: string) => value is T;
16
+ /**
17
+ * {@link tokenSet} for a union whose members are also needed as an ordered *list*, because a binary
18
+ * serialisation indexes one.
19
+ *
20
+ * The record form's whole guarantee is that a member cannot be omitted, and a list gives that up: it
21
+ * can name only members of the union, but it can name fewer. So a caller here owes an explicit proof
22
+ * of set equality in both directions, which is what `FILL_PATTERNS_IN_SCHEMA_ORDER` carries beside
23
+ * itself. That is a worse guarantee than the record's, taken deliberately: the alternative is the
24
+ * arrangement it replaced, where the union, the guard's table and the binary codec's index list were
25
+ * three hand-maintained copies of one enumeration and only two of them were checked against each
26
+ * other.
27
+ */
28
+ export declare function tokenSetOf<T extends string>(members: readonly T[]): (value: string) => value is T;
package/dist/token-set.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export function tokenSet(members) {
2
- const set = new Set(Object.keys(members));
2
+ return tokenSetOf(Object.keys(members));
3
+ }
4
+ export function tokenSetOf(members) {
5
+ const set = new Set(members);
3
6
  return (value) => set.has(value);
4
7
  }
@@ -1,11 +1,22 @@
1
- export { concat } from '../bytes.ts';
1
+ export { concat, decodeUtf16le } from '../bytes.ts';
2
2
  /** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
3
3
  export declare function readU16(buf: Uint8Array, at: number): number;
4
4
  /** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
5
5
  export declare function readU32(buf: Uint8Array, at: number): number;
6
6
  /**
7
- * Decode UTF-16LE code units: the encoding [MS-CFB] uses for directory-entry names and [MS-OVBA] for
8
- * every "Unicode" name field. A trailing odd byte is dropped: these fields are length-prefixed by the
9
- * producer and a half code unit carries nothing to decode.
7
+ * Write a little-endian `uint16` over bytes already in `buf`. The value is asserted rather than
8
+ * masked: a caller that arrived at `-1` or `0x1_0000` has miscounted something, and silently storing
9
+ * `0xffff` turns that into a `dir` stream declaring 65,535 modules.
10
10
  */
11
- export declare function decodeUtf16le(bytes: Uint8Array): string;
11
+ export declare function writeU16(buf: Uint8Array, at: number, value: number): void;
12
+ /** Write a little-endian `uint32` over bytes already in `buf`, asserted rather than masked as {@link writeU16} is. */
13
+ export declare function writeU32(buf: Uint8Array, at: number, value: number): void;
14
+ /**
15
+ * Replace `src[start..end)` with `insert`, returning a new array.
16
+ *
17
+ * The three record edits the project editor makes are all this shape: splice a MODULE record block in,
18
+ * cut one out, cut a PROJECTwm entry out. Each was an allocate-and-copy-around written out longhand,
19
+ * and each had to get the same three offsets right; saying it once means the arithmetic is checked
20
+ * once. Passing no `insert` is a pure deletion, and `start === end` a pure insertion.
21
+ */
22
+ export declare function spliceBytes(src: Uint8Array, start: number, end: number, insert?: Uint8Array): Uint8Array;
package/dist/vba/bytes.js CHANGED
@@ -1,4 +1,4 @@
1
- export { concat } from '../bytes.js';
1
+ export { concat, decodeUtf16le } from '../bytes.js';
2
2
  import { VbaParseError } from './errors.js';
3
3
  function truncated(at, need, length) {
4
4
  return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
@@ -20,10 +20,31 @@ export function readU32(buf, at) {
20
20
  }
21
21
  return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
22
22
  }
23
- export function decodeUtf16le(bytes) {
24
- let s = '';
25
- for (let i = 0; i + 1 < bytes.length; i += 2) {
26
- s += String.fromCharCode(readU16(bytes, i));
23
+ export function writeU16(buf, at, value) {
24
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
25
+ throw new VbaParseError(`${value} does not fit the uint16 at offset ${at}`);
27
26
  }
28
- return s;
27
+ if (at + 1 >= buf.length)
28
+ throw truncated(at, 2, buf.length);
29
+ buf[at] = value & 0xff;
30
+ buf[at + 1] = (value >> 8) & 0xff;
31
+ }
32
+ export function writeU32(buf, at, value) {
33
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
34
+ throw new VbaParseError(`${value} does not fit the uint32 at offset ${at}`);
35
+ }
36
+ if (at + 3 >= buf.length)
37
+ throw truncated(at, 4, buf.length);
38
+ buf[at] = value & 0xff;
39
+ buf[at + 1] = (value >>> 8) & 0xff;
40
+ buf[at + 2] = (value >>> 16) & 0xff;
41
+ buf[at + 3] = (value >>> 24) & 0xff;
42
+ }
43
+ export function spliceBytes(src, start, end, insert = EMPTY) {
44
+ const out = new Uint8Array(src.length - (end - start) + insert.length);
45
+ out.set(src.subarray(0, start), 0);
46
+ out.set(insert, start);
47
+ out.set(src.subarray(end), start + insert.length);
48
+ return out;
29
49
  }
50
+ const EMPTY = new Uint8Array(0);