@shbernal/ts-xlsx 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +4 -1
  2. package/dist/bytes.d.ts +7 -0
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +60 -2
  5. package/dist/core/address.js +40 -24
  6. package/dist/core/autofilter.d.ts +5 -4
  7. package/dist/core/autofilter.js +34 -35
  8. package/dist/core/axis-handle.d.ts +10 -0
  9. package/dist/core/axis-handle.js +17 -0
  10. package/dist/core/cell.d.ts +25 -0
  11. package/dist/core/cell.js +9 -3
  12. package/dist/core/clone.d.ts +29 -0
  13. package/dist/core/clone.js +24 -0
  14. package/dist/core/color-resolution.js +2 -4
  15. package/dist/core/column.d.ts +5 -1
  16. package/dist/core/column.js +34 -35
  17. package/dist/core/comment-thread.js +2 -1
  18. package/dist/core/conditional-formatting-overlay.d.ts +2 -1
  19. package/dist/core/conditional-formatting-overlay.js +2 -2
  20. package/dist/core/conditional-formatting.d.ts +14 -6
  21. package/dist/core/conditional-formatting.js +45 -43
  22. package/dist/core/containers.d.ts +12 -0
  23. package/dist/core/containers.js +5 -0
  24. package/dist/core/data-validation-overlay.d.ts +2 -1
  25. package/dist/core/data-validation-overlay.js +4 -4
  26. package/dist/core/data-validation.d.ts +9 -3
  27. package/dist/core/data-validation.js +21 -18
  28. package/dist/core/date-format.d.ts +20 -0
  29. package/dist/core/date-format.js +160 -0
  30. package/dist/core/date.d.ts +43 -8
  31. package/dist/core/date.js +18 -2
  32. package/dist/core/formula.d.ts +26 -0
  33. package/dist/core/formula.js +20 -4
  34. package/dist/core/grid-edits.d.ts +2 -3
  35. package/dist/core/grid-edits.js +69 -70
  36. package/dist/core/grid-shift.d.ts +58 -9
  37. package/dist/core/grid-shift.js +26 -4
  38. package/dist/core/image.d.ts +14 -10
  39. package/dist/core/image.js +9 -9
  40. package/dist/core/internal.d.ts +8 -2
  41. package/dist/core/limits.d.ts +23 -0
  42. package/dist/core/limits.js +4 -0
  43. package/dist/core/merge-index.d.ts +31 -0
  44. package/dist/core/merge-index.js +60 -0
  45. package/dist/core/merge.d.ts +5 -14
  46. package/dist/core/merge.js +18 -27
  47. package/dist/core/page-setup.d.ts +72 -2
  48. package/dist/core/page-setup.js +34 -8
  49. package/dist/core/pivot-table.d.ts +1 -1
  50. package/dist/core/pivot-table.js +67 -53
  51. package/dist/core/protection.js +2 -11
  52. package/dist/core/range.js +34 -30
  53. package/dist/core/row-input.js +2 -2
  54. package/dist/core/row.d.ts +5 -1
  55. package/dist/core/row.js +22 -23
  56. package/dist/core/style.d.ts +134 -75
  57. package/dist/core/style.js +112 -53
  58. package/dist/core/table-style.d.ts +1 -1
  59. package/dist/core/table-style.js +4 -4
  60. package/dist/core/table.d.ts +28 -10
  61. package/dist/core/table.js +54 -39
  62. package/dist/core/theme.js +6 -5
  63. package/dist/core/used-extent.d.ts +44 -0
  64. package/dist/core/used-extent.js +102 -0
  65. package/dist/core/value.d.ts +9 -0
  66. package/dist/core/value.js +36 -34
  67. package/dist/core/workbook-media.d.ts +34 -0
  68. package/dist/core/workbook-media.js +44 -0
  69. package/dist/core/workbook-styles.d.ts +67 -1
  70. package/dist/core/workbook-vba.js +3 -2
  71. package/dist/core/workbook.d.ts +23 -2
  72. package/dist/core/workbook.js +24 -40
  73. package/dist/core/worksheet-comments.d.ts +2 -1
  74. package/dist/core/worksheet-comments.js +20 -13
  75. package/dist/core/worksheet-merges.d.ts +44 -0
  76. package/dist/core/worksheet-merges.js +59 -0
  77. package/dist/core/worksheet-model.d.ts +9 -1
  78. package/dist/core/worksheet-model.js +24 -9
  79. package/dist/core/worksheet.d.ts +72 -12
  80. package/dist/core/worksheet.js +68 -70
  81. package/dist/customui/ribbon.js +6 -7
  82. package/dist/entries/core.d.ts +18 -13
  83. package/dist/entries/core.js +12 -6
  84. package/dist/entries/xlsx.d.ts +1 -1
  85. package/dist/errors.d.ts +64 -0
  86. package/dist/errors.js +22 -0
  87. package/dist/hex.d.ts +8 -0
  88. package/dist/hex.js +3 -0
  89. package/dist/io/csv/delimiter.d.ts +9 -0
  90. package/dist/io/csv/delimiter.js +6 -0
  91. package/dist/io/csv/read.js +11 -4
  92. package/dist/io/csv/write.d.ts +9 -2
  93. package/dist/io/csv/write.js +9 -38
  94. package/dist/io/opc/inflate.js +15 -1
  95. package/dist/io/opc/namespaces.d.ts +16 -0
  96. package/dist/io/opc/namespaces.js +4 -0
  97. package/dist/io/opc/part-paths.d.ts +10 -0
  98. package/dist/io/opc/part-paths.js +16 -1
  99. package/dist/io/opc/read-opc.d.ts +69 -10
  100. package/dist/io/opc/read-opc.js +49 -55
  101. package/dist/io/opc/read-options.d.ts +5 -1
  102. package/dist/io/opc/rels.d.ts +1 -6
  103. package/dist/io/opc/rels.js +1 -4
  104. package/dist/io/opc/sniff-format.d.ts +3 -3
  105. package/dist/io/opc/sniff-format.js +2 -2
  106. package/dist/io/style/xf-style.d.ts +48 -2
  107. package/dist/io/style/xf-style.js +14 -0
  108. package/dist/io/xlsb/formula.d.ts +2 -2
  109. package/dist/io/xlsb/formula.js +49 -34
  110. package/dist/io/xlsb/primitives.js +8 -14
  111. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  112. package/dist/io/xlsb/ptg-functions.js +385 -596
  113. package/dist/io/xlsb/read-styles.js +50 -83
  114. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  115. package/dist/io/xlsb/read-worksheet.js +73 -89
  116. package/dist/io/xlsb/read.d.ts +6 -3
  117. package/dist/io/xlsb/read.js +47 -29
  118. package/dist/io/xlsb/record-stream.d.ts +30 -0
  119. package/dist/io/xlsb/record-stream.js +20 -0
  120. package/dist/io/xlsb/record-types.d.ts +1 -5
  121. package/dist/io/xlsb/record-types.js +1 -5
  122. package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
  123. package/dist/io/xlsx/cell-accumulator.js +32 -9
  124. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  125. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  126. package/dist/io/xlsx/cell-value.d.ts +5 -3
  127. package/dist/io/xlsx/cell-value.js +13 -15
  128. package/dist/io/xlsx/color-xml.js +3 -2
  129. package/dist/io/xlsx/column-budget.d.ts +26 -0
  130. package/dist/io/xlsx/column-budget.js +22 -0
  131. package/dist/io/xlsx/comments.d.ts +6 -2
  132. package/dist/io/xlsx/comments.js +36 -31
  133. package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
  134. package/dist/io/xlsx/conditional-formatting.js +51 -46
  135. package/dist/io/xlsx/data-validation.d.ts +9 -7
  136. package/dist/io/xlsx/data-validation.js +38 -35
  137. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  138. package/dist/io/xlsx/edit-vba.js +15 -29
  139. package/dist/io/xlsx/font-xml.d.ts +7 -0
  140. package/dist/io/xlsx/font-xml.js +104 -0
  141. package/dist/io/xlsx/hyperlinks.d.ts +41 -7
  142. package/dist/io/xlsx/hyperlinks.js +40 -28
  143. package/dist/io/xlsx/images.js +24 -25
  144. package/dist/io/xlsx/namespaces.d.ts +16 -0
  145. package/dist/io/xlsx/namespaces.js +7 -0
  146. package/dist/io/xlsx/package-plan.d.ts +23 -3
  147. package/dist/io/xlsx/package-plan.js +66 -25
  148. package/dist/io/xlsx/part-names.d.ts +41 -0
  149. package/dist/io/xlsx/part-names.js +46 -0
  150. package/dist/io/xlsx/pivot.js +8 -7
  151. package/dist/io/xlsx/read-parts.d.ts +59 -0
  152. package/dist/io/xlsx/read-parts.js +252 -0
  153. package/dist/io/xlsx/read-pivot.js +3 -1
  154. package/dist/io/xlsx/read-repair.d.ts +26 -0
  155. package/dist/io/xlsx/read-repair.js +39 -0
  156. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
  157. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
  158. package/dist/io/xlsx/read-rows.d.ts +18 -5
  159. package/dist/io/xlsx/read-rows.js +50 -37
  160. package/dist/io/xlsx/read-shared-strings.js +5 -3
  161. package/dist/io/xlsx/read-styles.d.ts +1 -6
  162. package/dist/io/xlsx/read-styles.js +65 -109
  163. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  164. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +11 -1
  166. package/dist/io/xlsx/read-worksheet.js +51 -193
  167. package/dist/io/xlsx/read.d.ts +5 -13
  168. package/dist/io/xlsx/read.js +111 -436
  169. package/dist/io/xlsx/rich-text.js +1 -1
  170. package/dist/io/xlsx/row-position.d.ts +18 -0
  171. package/dist/io/xlsx/row-position.js +11 -0
  172. package/dist/io/xlsx/row-xml.d.ts +128 -0
  173. package/dist/io/xlsx/row-xml.js +203 -0
  174. package/dist/io/xlsx/shared-formulas.js +2 -2
  175. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  176. package/dist/io/xlsx/sheet-properties.js +166 -37
  177. package/dist/io/xlsx/style-elements.d.ts +43 -0
  178. package/dist/io/xlsx/style-elements.js +156 -0
  179. package/dist/io/xlsx/styles.d.ts +12 -3
  180. package/dist/io/xlsx/styles.js +5 -204
  181. package/dist/io/xlsx/tables.js +11 -14
  182. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  183. package/dist/io/xlsx/theme-xml.js +107 -69
  184. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  185. package/dist/io/xlsx/threaded-comments.js +3 -1
  186. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  187. package/dist/io/xlsx/workbook-xml.js +84 -57
  188. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  189. package/dist/io/xlsx/worksheet-xml.js +59 -242
  190. package/dist/io/xlsx/write-stream.d.ts +71 -16
  191. package/dist/io/xlsx/write-stream.js +79 -41
  192. package/dist/io/xlsx/write.d.ts +6 -2
  193. package/dist/io/xlsx/write.js +217 -153
  194. package/dist/rel-type.d.ts +16 -0
  195. package/dist/rel-type.js +6 -0
  196. package/dist/token-set.d.ts +28 -0
  197. package/dist/token-set.js +7 -0
  198. package/dist/vba/bytes.d.ts +14 -5
  199. package/dist/vba/bytes.js +16 -6
  200. package/dist/vba/cfb-format.d.ts +32 -0
  201. package/dist/vba/cfb-format.js +13 -0
  202. package/dist/vba/cfb-writer.d.ts +9 -0
  203. package/dist/vba/cfb-writer.js +28 -25
  204. package/dist/vba/cfb.d.ts +13 -1
  205. package/dist/vba/cfb.js +73 -57
  206. package/dist/vba/codepage.js +2 -1
  207. package/dist/vba/errors.d.ts +7 -0
  208. package/dist/vba/ms-ovba.js +79 -23
  209. package/dist/vba/project-editor.js +35 -40
  210. package/dist/vba/project.d.ts +11 -1
  211. package/dist/vba/project.js +40 -21
  212. package/dist/vba/vba-encoding.js +5 -6
  213. package/dist/xml/xml-attrs.d.ts +37 -0
  214. package/dist/xml/xml-attrs.js +30 -0
  215. package/dist/xml/xml-chars.d.ts +40 -0
  216. package/dist/xml/xml-chars.js +12 -0
  217. package/dist/xml/xml-namespaces.d.ts +31 -0
  218. package/dist/xml/xml-namespaces.js +50 -0
  219. package/dist/xml/xml-read.d.ts +128 -81
  220. package/dist/xml/xml-read.js +161 -178
  221. package/dist/xml/xml-scan.d.ts +78 -0
  222. package/dist/xml/xml-scan.js +180 -0
  223. package/dist/xml/xml.d.ts +29 -28
  224. package/dist/xml/xml.js +24 -24
  225. package/package.json +13 -9
@@ -1,12 +1,14 @@
1
1
  import { strToU8, zip, zipSync } from 'fflate';
2
- import { AuthoringError } from '../../errors.js';
3
- import { THEME_PART_PATH } from '../opc/part-paths.js';
2
+ import { AuthoringError, InternalError, quoted } from '../../errors.js';
3
+ import { isRelType } from '../../rel-type.js';
4
+ import { relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
4
5
  import { relsPartXml } from '../opc/rels.js';
5
6
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
6
7
  import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
7
- import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
8
+ import { collectHyperlinks, liveCells, planHyperlinks } from './hyperlinks.js';
8
9
  import { drawingRelsXml, drawingXml } from './images.js';
9
- import { planMedia, planPreservedParts, SheetRelIds, } from './package-plan.js';
10
+ import { planMedia, planPreservedParts, RelIdAllocator, } from './package-plan.js';
11
+ 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';
10
12
  import { pivotCacheDefinitionXml, pivotCacheRecordsXml, pivotTableXml } from './pivot.js';
11
13
  import { REL } from './relationships.js';
12
14
  import { SharedStringTable } from './shared-strings.js';
@@ -14,9 +16,8 @@ import { StyleRegistry } from './styles.js';
14
16
  import { tableXml } from './tables.js';
15
17
  import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
16
18
  import { personsXml, threadedCommentsXml } from './threaded-comments.js';
17
- import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
18
- import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
19
- export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
19
+ import { appPropsXml, contentTypesXml, corePropsXml, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
20
+ import { worksheetRelsXml, worksheetXml } from './worksheet-xml.js';
20
21
  export function writeXlsx(workbook, options = {}) {
21
22
  return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
22
23
  }
@@ -47,12 +48,78 @@ export function createStyleRegistry(workbook) {
47
48
  styles.addTableStyle(style);
48
49
  return styles;
49
50
  }
51
+ function planSheet(context) {
52
+ const { sheet, index, media, preserved, numbering, flushed } = context;
53
+ const rels = new RelIdAllocator();
54
+ const tables = sheet.tables.map((table) => ({
55
+ table,
56
+ number: ++numbering.table,
57
+ relId: rels.next(),
58
+ }));
59
+ 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 };
71
+ }
72
+ const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
73
+ const sheetComments = collectComments(liveCells(sheet), threads, flushed?.notes ?? []);
74
+ const comments = sheetComments.length === 0
75
+ ? null
76
+ : {
77
+ number: index + 1,
78
+ comments: sheetComments,
79
+ vmlRelId: rels.next(),
80
+ commentsRelId: rels.next(),
81
+ };
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(liveCells(sheet)), ...(flushed?.hyperlinks ?? [])].sort((a, b) => a.row - b.row || a.col - b.col), rels);
86
+ let background = null;
87
+ if (sheet.backgroundImageId !== undefined) {
88
+ const { number, image } = media.resolve(sheet.backgroundImageId);
89
+ background = { relId: rels.next(), mediaNumber: number, extension: image.extension };
90
+ }
91
+ const preservedRefs = (preserved.perSheet[index] ?? []).map((reference) => ({ ...reference, relId: rels.next() }));
92
+ const pivots = sheet.pivotTables.map((table) => {
93
+ const number = ++numbering.pivot;
94
+ return { number, cacheId: String(number), table, sheetRelId: rels.next(), workbookRelId: '' };
95
+ });
96
+ return {
97
+ tables,
98
+ drawing,
99
+ comments,
100
+ threadedComments,
101
+ printerSettings,
102
+ hyperlinks,
103
+ background,
104
+ preservedRefs,
105
+ pivots,
106
+ };
107
+ }
108
+ function numbersOf(plans, pick) {
109
+ const numbers = [];
110
+ for (const plan of plans) {
111
+ const part = pick(plan);
112
+ if (part !== null)
113
+ numbers.push(part.number);
114
+ }
115
+ return numbers;
116
+ }
50
117
  function resolveSheetReferences(plan) {
51
118
  const refs = plan.preservedRefs;
52
119
  const preservedDrawingRelId = refs.find((ref) => ref.element === 'drawing')?.relId ?? null;
53
120
  const legacyDrawingHFRelId = refs.find((ref) => ref.element === 'legacyDrawingHF')?.relId ?? null;
54
121
  const slicerRelIds = refs
55
- .filter((ref) => ref.relType.endsWith('/slicer'))
122
+ .filter((ref) => isRelType(ref.relType, 'slicer'))
56
123
  .map((ref) => ref.relId);
57
124
  return {
58
125
  drawingRelId: plan.drawing?.relId ?? preservedDrawingRelId,
@@ -63,153 +130,153 @@ function resolveSheetReferences(plan) {
63
130
  slicerRelIds,
64
131
  };
65
132
  }
66
- export function buildPackageParts(workbook, options = {}) {
67
- const sheets = workbook.worksheets;
68
- if (sheets.length === 0) {
69
- throw new AuthoringError('cannot write a workbook with no worksheets: a zero-sheet package is corrupt to Excel');
70
- }
71
- const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
133
+ function assignWorkbookRelIds(context) {
134
+ const { sheetCount, hasSharedStrings, hasPersons, preservedWorkbook, pivots } = context;
135
+ const ids = new RelIdAllocator();
136
+ const sheetRelIds = Array.from({ length: sheetCount }, () => ids.next());
137
+ const stylesRelId = ids.next();
138
+ const themeRelId = ids.next();
139
+ const sharedStringsRelId = hasSharedStrings ? ids.next() : null;
140
+ const personsRelId = hasPersons ? ids.next() : null;
141
+ const preservedWorkbookRels = preservedWorkbook.map((ref) => ({ ...ref, relId: ids.next() }));
142
+ for (const pivot of pivots)
143
+ pivot.workbookRelId = ids.next();
144
+ return {
145
+ sheetRelIds,
146
+ stylesRelId,
147
+ themeRelId,
148
+ sharedStringsRelId,
149
+ personsRelId,
150
+ preservedWorkbookRels,
151
+ };
152
+ }
153
+ function planPackage(workbook, sheets, flushed) {
72
154
  const media = planMedia(workbook, sheets);
73
155
  const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
74
- const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length);
75
- let tableNumber = 0;
76
- let drawingNumber = 0;
77
- let pivotNumber = 0;
78
- const perSheet = sheets.map((sheet, i) => {
79
- const rels = new SheetRelIds();
80
- const tables = sheet.tables.map((table) => ({
81
- table,
82
- number: ++tableNumber,
83
- relId: rels.next(),
84
- }));
85
- let drawing = null;
86
- if (sheet.images.length > 0) {
87
- const images = sheet.images.map((image, j) => {
88
- const registered = workbook.getImage(image.imageId);
89
- return {
90
- anchor: image.anchor,
91
- embedId: `rId${j + 1}`,
92
- mediaNumber: media.numberById.get(image.imageId),
93
- extension: registered.extension,
94
- };
95
- });
96
- drawing = { number: ++drawingNumber, relId: rels.next(), images };
97
- }
98
- const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
99
- const sheetComments = collectComments(sheet, threads);
100
- const comments = sheetComments.length === 0
101
- ? null
102
- : {
103
- number: i + 1,
104
- comments: sheetComments,
105
- vmlRelId: rels.next(),
106
- commentsRelId: rels.next(),
107
- };
108
- const threadedComments = threads.length === 0 ? null : { number: i + 1, threads, relId: rels.next() };
109
- const printerData = sheet.pageSetup.printerSettings;
110
- const printerSettings = printerData === undefined ? null : { number: i + 1, data: printerData, relId: rels.next() };
111
- const hyperlinks = planHyperlinks(collectHyperlinks(sheet), rels);
112
- let background = null;
113
- if (sheet.backgroundImageId !== undefined) {
114
- const registered = workbook.getImage(sheet.backgroundImageId);
115
- if (registered === undefined) {
116
- throw new AuthoringError(`sheet "${sheet.name}" sets background image id ${sheet.backgroundImageId}, which is not registered on the workbook`);
117
- }
118
- background = {
119
- relId: rels.next(),
120
- mediaNumber: media.numberById.get(sheet.backgroundImageId),
121
- extension: registered.extension,
122
- };
123
- }
124
- const preservedRefs = (preserved.perSheet[i] ?? []).map((reference) => ({ ...reference, relId: rels.next() }));
125
- const pivots = sheet.pivotTables.map((table) => {
126
- const number = ++pivotNumber;
127
- return { number, cacheId: String(number), table, sheetRelId: rels.next(), workbookRelId: '' };
156
+ const generatedPivotCount = sheets.reduce((total, sheet) => total + sheet.pivotTables.length, 0);
157
+ const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount);
158
+ const numbering = { table: 0, drawing: 0, pivot: 0 };
159
+ const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering, flushed: flushed?.get(sheet) }));
160
+ return {
161
+ media,
162
+ preserved,
163
+ perSheet,
164
+ allTables: perSheet.flatMap((plan) => plan.tables),
165
+ allPivots: perSheet.flatMap((plan) => plan.pivots),
166
+ };
167
+ }
168
+ function serialiseSheets(context) {
169
+ const { workbook, sheets, plan, styles, sharedStrings, flushed } = context;
170
+ return sheets.map((sheet, i) => {
171
+ const sheetPlan = plan.perSheet[i];
172
+ return worksheetXml({
173
+ sheet,
174
+ tables: sheetPlan.tables,
175
+ styles,
176
+ references: resolveSheetReferences(sheetPlan),
177
+ hyperlinks: sheetPlan.hyperlinks,
178
+ sharedStrings,
179
+ dateEpoch: workbook.dateEpoch,
180
+ active: i === workbook.activeTabIndex,
181
+ flushed: flushed?.get(sheet),
128
182
  });
129
- return {
130
- tables,
131
- drawing,
132
- comments,
133
- threadedComments,
134
- printerSettings,
135
- hyperlinks,
136
- background,
137
- preservedRefs,
138
- pivots,
139
- };
140
- });
141
- const allTables = perSheet.flatMap((plan) => plan.tables);
142
- const allPivots = perSheet.flatMap((plan) => plan.pivots);
143
- const styles = options.styles ?? createStyleRegistry(workbook);
144
- const sheetXml = sheets.map((sheet, i) => {
145
- const plan = perSheet[i];
146
- return worksheetXml(sheet, plan.tables, styles, resolveSheetReferences(plan), plan.hyperlinks, sharedStrings, i === workbook.activeTabIndex, options.flushed?.get(sheet));
147
183
  });
184
+ }
185
+ function emitPackageParts(context) {
186
+ const { workbook, sheets, plan, styles, sharedStrings, sheetXml } = context;
187
+ const { media, preserved, perSheet, allTables, allPivots } = plan;
148
188
  const hasSharedStrings = sharedStrings !== null && !sharedStrings.isEmpty;
149
- const commentNumbers = perSheet
150
- .map((plan) => plan.comments)
151
- .filter((c) => c !== null)
152
- .map((c) => c.number);
153
- const drawingNumbers = perSheet
154
- .map((plan) => plan.drawing)
155
- .filter((d) => d !== null)
156
- .map((d) => d.number);
157
- const printerSettingsNumbers = perSheet
158
- .map((plan) => plan.printerSettings)
159
- .filter((p) => p !== null)
160
- .map((p) => p.number);
161
- const threadedCommentNumbers = perSheet
162
- .map((plan) => plan.threadedComments)
163
- .filter((t) => t !== null)
164
- .map((t) => t.number);
189
+ const commentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.comments);
190
+ const drawingNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.drawing);
191
+ const printerSettingsNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.printerSettings);
192
+ const threadedCommentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.threadedComments);
165
193
  const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
166
- const modeledWorkbookRelCount = sheets.length + FIXED_WORKBOOK_REL_COUNT + (hasSharedStrings ? 1 : 0);
167
- const personsRelId = persons.length === 0 ? null : `rId${modeledWorkbookRelCount + 1}`;
168
- const workbookRelBase = modeledWorkbookRelCount + (personsRelId === null ? 0 : 1);
169
- const preservedWorkbookRels = preserved.workbook.map((ref, i) => ({
170
- ...ref,
171
- relId: `rId${workbookRelBase + 1 + i}`,
172
- }));
173
- const pivotWorkbookRelBase = workbookRelBase + preserved.workbook.length;
174
- allPivots.forEach((pivot, i) => {
175
- pivot.workbookRelId = `rId${pivotWorkbookRelBase + 1 + i}`;
194
+ const workbookRels = assignWorkbookRelIds({
195
+ sheetCount: sheets.length,
196
+ hasSharedStrings,
197
+ hasPersons: persons.length > 0,
198
+ preservedWorkbook: preserved.workbook,
199
+ pivots: allPivots,
176
200
  });
177
- const files = {
178
- '[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
179
- '_rels/.rels': strToU8(rootRelsXml(preserved.root)),
180
- 'docProps/core.xml': strToU8(corePropsXml(workbook.properties)),
181
- 'docProps/app.xml': strToU8(appPropsXml(workbook.properties)),
182
- 'xl/workbook.xml': strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
183
- 'xl/_rels/workbook.xml.rels': strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
184
- 'xl/styles.xml': strToU8(styles.toXml()),
185
- };
201
+ const { preservedWorkbookRels } = workbookRels;
202
+ const files = new PackageFiles();
203
+ files.add('[Content_Types].xml', strToU8(contentTypesXml({
204
+ sheetCount: sheets.length,
205
+ tables: allTables,
206
+ commentNumbers,
207
+ drawingNumbers,
208
+ printerSettingsNumbers,
209
+ mediaExtensions: media.extensions,
210
+ hasSharedStrings,
211
+ preservedParts: preserved.parts,
212
+ pivots: allPivots,
213
+ preservedWorkbookRefs: preservedWorkbookRels,
214
+ threadedCommentNumbers,
215
+ hasPersons: persons.length > 0,
216
+ })));
217
+ files.add('_rels/.rels', strToU8(rootRelsXml(preserved.root)));
218
+ files.add(CORE_PROPS_PART, strToU8(corePropsXml(workbook.properties)));
219
+ files.add(APP_PROPS_PART, strToU8(appPropsXml(workbook.properties)));
220
+ files.add(WORKBOOK_PART, strToU8(workbookXml(workbook, workbookRels, allPivots)));
221
+ files.add(relsPathFor(WORKBOOK_PART), strToU8(workbookRelsXml(workbookRels, allPivots)));
222
+ files.add(STYLES_PART, strToU8(styles.toXml()));
186
223
  if (!preserved.themeEmitted) {
187
224
  const overrides = workbook.themeOverrides;
188
- files[THEME_PART_PATH] = strToU8(overrides === undefined
225
+ files.add(THEME_PART_PATH, strToU8(overrides === undefined
189
226
  ? DEFAULT_THEME_XML
190
- : applyThemeOverrides(DEFAULT_THEME_XML, overrides));
227
+ : applyThemeOverrides(DEFAULT_THEME_XML, overrides)));
191
228
  }
192
229
  if (hasSharedStrings) {
193
- files['xl/sharedStrings.xml'] = strToU8(sharedStrings.toXml());
230
+ files.add(SHARED_STRINGS_PART, strToU8(sharedStrings.toXml()));
194
231
  }
195
232
  if (persons.length > 0)
196
- files['xl/persons/person.xml'] = strToU8(personsXml(persons));
233
+ files.add(PERSONS_PART, strToU8(personsXml(persons)));
197
234
  for (const part of media.parts) {
198
- files[`xl/media/image${part.number}.${part.extension}`] = part.data;
235
+ files.add(mediaPart(part.number, part.extension), part.data);
199
236
  }
200
237
  emitSheetParts(files, perSheet, sheetXml);
201
238
  for (const { table, number } of allTables) {
202
- files[`xl/tables/table${number}.xml`] = strToU8(tableXml(table, number));
239
+ files.add(tablePart(number), strToU8(tableXml(table, number)));
203
240
  }
204
241
  emitPivotParts(files, allPivots);
205
242
  emitPreservedParts(files, preserved.parts);
206
- return files;
243
+ return files.toRecord();
244
+ }
245
+ export function buildPackageParts(workbook, options = {}) {
246
+ const sheets = workbook.worksheets;
247
+ if (sheets.length === 0) {
248
+ throw new AuthoringError('cannot write a workbook with no worksheets: a zero-sheet package is corrupt to Excel');
249
+ }
250
+ const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
251
+ const styles = options.styles ?? createStyleRegistry(workbook);
252
+ const plan = planPackage(workbook, sheets, options.flushed);
253
+ const sheetXml = serialiseSheets({
254
+ workbook,
255
+ sheets,
256
+ plan,
257
+ styles,
258
+ sharedStrings,
259
+ flushed: options.flushed,
260
+ });
261
+ return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, sheetXml });
262
+ }
263
+ class PackageFiles {
264
+ #files = Object.create(null);
265
+ add(path, bytes) {
266
+ if (path in this.#files) {
267
+ throw new InternalError(`two package parts claim the path ${quoted(path)}`);
268
+ }
269
+ this.#files[path] = bytes;
270
+ }
271
+ toRecord() {
272
+ return this.#files;
273
+ }
207
274
  }
208
275
  function emitSheetParts(files, perSheet, sheetXml) {
209
276
  perSheet.forEach((plan, i) => {
210
277
  const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
211
278
  const hasExternalHyperlink = hyperlinks.some((link) => link.relId !== undefined);
212
- files[`xl/worksheets/sheet${i + 1}.xml`] = strToU8(sheetXml[i]);
279
+ files.add(worksheetPart(i + 1), strToU8(sheetXml[i]));
213
280
  if (tables.length > 0 ||
214
281
  drawing !== null ||
215
282
  comments !== null ||
@@ -219,49 +286,46 @@ function emitSheetParts(files, perSheet, sheetXml) {
219
286
  hasExternalHyperlink ||
220
287
  preservedRefs.length > 0 ||
221
288
  pivots.length > 0) {
222
- files[`xl/worksheets/_rels/sheet${i + 1}.xml.rels`] = strToU8(worksheetRelsXml(tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots));
289
+ files.add(relsPathFor(worksheetPart(i + 1)), strToU8(worksheetRelsXml(plan)));
223
290
  }
224
291
  if (printerSettings !== null) {
225
- files[`xl/printerSettings/printerSettings${printerSettings.number}.bin`] =
226
- printerSettings.data;
292
+ files.add(printerSettingsPart(printerSettings.number), printerSettings.data);
227
293
  }
228
294
  if (drawing !== null) {
229
- files[`xl/drawings/drawing${drawing.number}.xml`] = strToU8(drawingXml(drawing.images));
230
- const targets = drawing.images.map((image) => `../media/image${image.mediaNumber}.${image.extension}`);
231
- files[`xl/drawings/_rels/drawing${drawing.number}.xml.rels`] = strToU8(drawingRelsXml(targets));
295
+ const drawingPath = drawingPart(drawing.number);
296
+ files.add(drawingPath, strToU8(drawingXml(drawing.images)));
297
+ const targets = drawing.images.map((image) => relativePartPath(drawingPath, mediaPart(image.mediaNumber, image.extension)));
298
+ files.add(relsPathFor(drawingPath), strToU8(drawingRelsXml(targets)));
232
299
  }
233
300
  if (comments !== null) {
234
- files[`xl/comments${comments.number}.xml`] = strToU8(commentsXml(comments.comments));
235
- files[`xl/drawings/vmlDrawing${comments.number}.vml`] = strToU8(vmlDrawingXml(comments.comments));
301
+ files.add(commentsPart(comments.number), strToU8(commentsXml(comments.comments)));
302
+ files.add(vmlDrawingPart(comments.number), strToU8(vmlDrawingXml(comments.comments)));
236
303
  }
237
304
  if (threadedComments !== null) {
238
- files[`xl/threadedComments/threadedComment${threadedComments.number}.xml`] = strToU8(threadedCommentsXml(threadedComments.threads));
305
+ files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads)));
239
306
  }
240
307
  });
241
308
  }
242
309
  function emitPivotParts(files, allPivots) {
243
310
  for (const pivot of allPivots) {
244
311
  const { number, cacheId, table } = pivot;
245
- files[`xl/pivotTables/pivotTable${number}.xml`] = strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId));
246
- files[`xl/pivotTables/_rels/pivotTable${number}.xml.rels`] = strToU8(relsPartXml([
247
- {
248
- id: 'rId1',
249
- type: REL.pivotCacheDefinition,
250
- target: `../pivotCache/pivotCacheDefinition${number}.xml`,
251
- },
252
- ]));
253
- files[`xl/pivotCache/pivotCacheDefinition${number}.xml`] = strToU8(pivotCacheDefinitionXml(table));
254
- files[`xl/pivotCache/_rels/pivotCacheDefinition${number}.xml.rels`] = strToU8(relsPartXml([
255
- { id: 'rId1', type: REL.pivotCacheRecords, target: `pivotCacheRecords${number}.xml` },
256
- ]));
257
- files[`xl/pivotCache/pivotCacheRecords${number}.xml`] = strToU8(pivotCacheRecordsXml(table));
312
+ const tablePath = pivotTablePart(number);
313
+ const definitionPath = pivotCacheDefinitionPart(number);
314
+ files.add(tablePath, strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId)));
315
+ addSingleRelPart(files, tablePath, REL.pivotCacheDefinition, definitionPath);
316
+ files.add(definitionPath, strToU8(pivotCacheDefinitionXml(table)));
317
+ addSingleRelPart(files, definitionPath, REL.pivotCacheRecords, pivotCacheRecordsPart(number));
318
+ files.add(pivotCacheRecordsPart(number), strToU8(pivotCacheRecordsXml(table)));
258
319
  }
259
320
  }
321
+ function addSingleRelPart(files, from, type, to) {
322
+ files.add(relsPathFor(from), strToU8(relsPartXml([{ id: 'rId1', type, target: relativePartPath(from, to) }])));
323
+ }
260
324
  function emitPreservedParts(files, parts) {
261
325
  for (const part of parts) {
262
- files[part.path] = part.bytes;
326
+ files.add(part.path, part.bytes);
263
327
  if (part.relsPath !== null && part.relsXml !== null) {
264
- files[part.relsPath] = strToU8(part.relsXml);
328
+ files.add(part.relsPath, strToU8(part.relsXml));
265
329
  }
266
330
  }
267
331
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Does a relationship Type name this class of part?
3
+ *
4
+ * A Type is a URI whose last segment names the part class, and the namespace in front of it varies by
5
+ * relationship family and by the schema version a producer wrote against. So the match is on that
6
+ * last segment.
7
+ *
8
+ * **The separator is the load-bearing character.** `/slicer` and `slicer` are not the same test:
9
+ * without the slash, `slicer` matches `slicerCache`, `table` matches `pivotTable`, and a lookup
10
+ * quietly gathers a part class nobody asked for. Every caller used to spell the slash itself, ten
11
+ * times over, with nowhere to say why it was there.
12
+ */
13
+ export declare function isRelType(type: string, name: string): boolean;
14
+ /** {@link isRelType} against several part classes at once, for the predicates that partition
15
+ * relationships into "modeled", "preserved verbatim", and "dropped". */
16
+ export declare function isAnyRelType(type: string, ...names: readonly string[]): boolean;
@@ -0,0 +1,6 @@
1
+ export function isRelType(type, name) {
2
+ return type.endsWith(`/${name}`);
3
+ }
4
+ export function isAnyRelType(type, ...names) {
5
+ return names.some((name) => isRelType(type, name));
6
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Build a narrowing predicate for a closed token union.
3
+ *
4
+ * `members` is keyed by the union, so the compiler refuses a foreign member and an omitted one
5
+ * alike. A bare list of string literals is checked one way only: it cannot name a token the union
6
+ * does not, but it can quietly omit one the union does, and a guard narrower than its union
7
+ * silently drops a token real files legitimately carry.
8
+ *
9
+ * Deriving the union from the list instead (`(typeof TOKENS)[number]`, as `table-style.ts` does)
10
+ * would make divergence impossible rather than merely detected, and is the better shape for a type
11
+ * that is not public. It is not available to a published union: the API reference renders a
12
+ * declaration by slicing its own source text, so a derived alias reaches the docs as that
13
+ * expression instead of as its members.
14
+ */
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;
@@ -0,0 +1,7 @@
1
+ export function tokenSet(members) {
2
+ return tokenSetOf(Object.keys(members));
3
+ }
4
+ export function tokenSetOf(members) {
5
+ const set = new Set(members);
6
+ return (value) => set.has(value);
7
+ }
@@ -1,11 +1,20 @@
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
+ /**
13
+ * Replace `src[start..end)` with `insert`, returning a new array.
14
+ *
15
+ * The three record edits the project editor makes are all this shape: splice a MODULE record block in,
16
+ * cut one out, cut a PROJECTwm entry out. Each was an allocate-and-copy-around written out longhand,
17
+ * and each had to get the same three offsets right; saying it once means the arithmetic is checked
18
+ * once. Passing no `insert` is a pure deletion, and `start === end` a pure insertion.
19
+ */
20
+ 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,20 @@ 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 spliceBytes(src, start, end, insert = EMPTY) {
33
+ const out = new Uint8Array(src.length - (end - start) + insert.length);
34
+ out.set(src.subarray(0, start), 0);
35
+ out.set(insert, start);
36
+ out.set(src.subarray(end), start + insert.length);
37
+ return out;
29
38
  }
39
+ const EMPTY = new Uint8Array(0);
@@ -0,0 +1,32 @@
1
+ /** Sector chain markers ([MS-CFB] 2.2). */
2
+ export declare const FREESECT = 4294967295;
3
+ export declare const ENDOFCHAIN = 4294967294;
4
+ export declare const FATSECT = 4294967293;
5
+ export declare const DIFSECT = 4294967292;
6
+ /**
7
+ * Sector values 0xFFFFFFFA..0xFFFFFFFF are the reserved markers above, not data-sector indices, so
8
+ * any value at or above this ceiling is chain-terminal. A reader walking a hostile chain tests the
9
+ * ceiling rather than the four markers by name: an unassigned reserved value is terminal too.
10
+ */
11
+ export declare const MAX_REGULAR_SECTOR = 4294967290;
12
+ /** Directory-tree links reuse the sector convention: NOSTREAM, and any value at or above
13
+ * {@link MAX_REGULAR_SECTOR}, mean "no such sibling or child". */
14
+ export declare const NOSTREAM = 4294967295;
15
+ /** Object types ([MS-CFB] 2.6.1). */
16
+ export declare const TYPE_EMPTY = 0;
17
+ export declare const TYPE_STORAGE = 1;
18
+ export declare const TYPE_STREAM = 2;
19
+ export declare const TYPE_ROOT = 5;
20
+ /**
21
+ * The stream size at or above which a stream lives in the regular FAT rather than the mini stream
22
+ * ([MS-CFB] 2.2, header offset 56). The spec fixes it at 4096, so the reader checks the header's value
23
+ * against this rather than believing it: a crafted 0 or 0xFFFFFFFF routes every stream through the
24
+ * wrong allocator, and since both destinations are bounds-checked the result is a module's source read
25
+ * back as something nobody wrote rather than a clean rejection.
26
+ */
27
+ export declare const MINI_STREAM_CUTOFF = 4096;
28
+ /** A directory entry is a fixed 128 bytes ([MS-CFB] 2.6.1), whatever the sector size. */
29
+ export declare const DIR_ENTRY_SIZE = 128;
30
+ /** 32 UTF-16 code units including the NUL terminator. This is also why a VBA module name is capped
31
+ * at 31: the module is a stream, and the stream's name is the module's. */
32
+ export declare const MAX_NAME_CHARS = 31;
@@ -0,0 +1,13 @@
1
+ export const FREESECT = 0xffffffff;
2
+ export const ENDOFCHAIN = 0xfffffffe;
3
+ export const FATSECT = 0xfffffffd;
4
+ export const DIFSECT = 0xfffffffc;
5
+ export const MAX_REGULAR_SECTOR = 0xfffffffa;
6
+ export const NOSTREAM = 0xffffffff;
7
+ export const TYPE_EMPTY = 0;
8
+ export const TYPE_STORAGE = 1;
9
+ export const TYPE_STREAM = 2;
10
+ export const TYPE_ROOT = 5;
11
+ export const MINI_STREAM_CUTOFF = 4096;
12
+ export const DIR_ENTRY_SIZE = 128;
13
+ export const MAX_NAME_CHARS = 31;
@@ -8,6 +8,15 @@ export interface CfbStorage {
8
8
  readonly children: readonly CfbNode[];
9
9
  }
10
10
  export type CfbNode = CfbStream | CfbStorage;
11
+ /**
12
+ * Which arm of {@link CfbNode} a node is.
13
+ *
14
+ * Exported beside the union rather than kept private, because the discriminant is `'data' in node`
15
+ * and that is the sort of test callers re-spell inline: `project-editor.ts` had it three times, once
16
+ * negated. The name says what the test means, and the pair says that a node is one or the other.
17
+ */
18
+ export declare function isStream(node: CfbNode): node is CfbStream;
19
+ export declare function isStorage(node: CfbNode): node is CfbStorage;
11
20
  /**
12
21
  * Encode a hierarchy of storages and streams into a v3 compound file. The Root Entry is synthesized
13
22
  * automatically; `root` is its top-level children. Every stream becomes a directory entry reachable both