@svgrid/enterprise 3.0.2 → 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 (364) hide show
  1. package/LICENSE +94 -24
  2. package/README.md +36 -4
  3. package/dist/SvAdvancedFilter.svelte +1 -1
  4. package/dist/SvAlertsManager.svelte +2 -1
  5. package/dist/SvBoard.svelte +2 -1
  6. package/dist/SvExportMenu.svelte +4 -1
  7. package/dist/SvExportMenu.svelte.d.ts +0 -13
  8. package/dist/SvExpressionEditor.svelte +3 -3
  9. package/dist/SvFormulaBar.svelte +94 -20
  10. package/dist/SvFormulaBar.svelte.d.ts +7 -0
  11. package/dist/SvGridAlerts.svelte +49 -9
  12. package/dist/SvGridBoard.svelte +13 -8
  13. package/dist/SvGridBulkEditDrawer.svelte +26 -2
  14. package/dist/SvGridEditPanel.svelte +3 -0
  15. package/dist/SvGridScheduler.svelte +69 -40
  16. package/dist/SvGridSelectionBar.svelte +10 -3
  17. package/dist/SvPivotDesigner.svelte +198 -40
  18. package/dist/SvPivotDesigner.svelte.d.ts +27 -20
  19. package/dist/SvSheet.svelte +4043 -243
  20. package/dist/SvSheet.svelte.d.ts +76 -3
  21. package/dist/SvSheetChartSetup.svelte +151 -0
  22. package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
  23. package/dist/SvSheetComment.svelte +216 -31
  24. package/dist/SvSheetComment.svelte.d.ts +11 -11
  25. package/dist/SvSheetConditionalFormat.svelte +28 -31
  26. package/dist/SvSheetDataValidation.svelte +66 -42
  27. package/dist/SvSheetEditRanges.svelte +217 -0
  28. package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
  29. package/dist/SvSheetErrors.svelte +125 -0
  30. package/dist/SvSheetErrors.svelte.d.ts +20 -0
  31. package/dist/SvSheetEvaluate.svelte +133 -0
  32. package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
  33. package/dist/SvSheetFilterMenu.svelte +137 -34
  34. package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
  35. package/dist/SvSheetFindReplace.svelte +19 -19
  36. package/dist/SvSheetFormatCells.svelte +125 -81
  37. package/dist/SvSheetGoalSeek.svelte +17 -16
  38. package/dist/SvSheetInsertFunction.svelte +15 -13
  39. package/dist/SvSheetIteration.svelte +81 -0
  40. package/dist/SvSheetIteration.svelte.d.ts +10 -0
  41. package/dist/SvSheetLink.svelte +99 -0
  42. package/dist/SvSheetLink.svelte.d.ts +17 -0
  43. package/dist/SvSheetListPicker.svelte +4 -2
  44. package/dist/SvSheetListPicker.svelte.d.ts +0 -6
  45. package/dist/SvSheetManageRules.svelte +20 -18
  46. package/dist/SvSheetNameManager.svelte +17 -15
  47. package/dist/SvSheetPageSetup.svelte +159 -0
  48. package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
  49. package/dist/SvSheetPasteSpecial.svelte +22 -20
  50. package/dist/SvSheetPivot.svelte +220 -0
  51. package/dist/SvSheetPivot.svelte.d.ts +21 -0
  52. package/dist/SvSheetProtectSheet.svelte +81 -0
  53. package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
  54. package/dist/SvSheetRemoveDuplicates.svelte +13 -10
  55. package/dist/SvSheetRibbon.svelte +104 -51
  56. package/dist/SvSheetSizeDialog.svelte +4 -2
  57. package/dist/SvSheetSort.svelte +218 -0
  58. package/dist/SvSheetSort.svelte.d.ts +28 -0
  59. package/dist/SvSheetSparklines.svelte +135 -0
  60. package/dist/SvSheetSparklines.svelte.d.ts +14 -0
  61. package/dist/SvSheetTable.svelte +160 -0
  62. package/dist/SvSheetTable.svelte.d.ts +24 -0
  63. package/dist/SvSheetTabs.svelte +161 -31
  64. package/dist/SvSheetTabs.svelte.d.ts +22 -8
  65. package/dist/SvSheetTextToColumns.svelte +20 -20
  66. package/dist/SvSheetValidationAlert.svelte +8 -6
  67. package/dist/advanced-filter-enable.js +1 -1
  68. package/dist/alerts/alert-engine.d.ts +6 -0
  69. package/dist/alerts/alert-engine.js +21 -4
  70. package/dist/alerts/alert-formats.js +2 -0
  71. package/dist/alerts/alert-types.d.ts +1 -1
  72. package/dist/alerts.d.ts +1 -1
  73. package/dist/alerts.js +2 -2
  74. package/dist/board.js +2 -2
  75. package/dist/bulk-edit.d.ts +3 -1
  76. package/dist/bulk-edit.js +1 -1
  77. package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
  78. package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
  79. package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
  80. package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
  81. package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
  82. package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
  83. package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
  84. package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
  85. package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
  86. package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
  87. package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
  88. package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
  89. package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
  90. package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
  91. package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
  92. package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
  93. package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
  94. package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
  95. package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
  96. package/dist/designer/assets/{index-DwU7CAMm.js → index-BSjv6Qec.js} +317 -315
  97. package/dist/designer/assets/index-_JVFxt02.css +1 -0
  98. package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
  99. package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
  100. package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
  101. package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
  102. package/dist/designer/index.html +17 -16
  103. package/dist/export-pdf.d.ts +75 -0
  104. package/dist/export-pdf.js +216 -15
  105. package/dist/export.js +10 -2
  106. package/dist/gantt/SvGridGantt.svelte +2890 -0
  107. package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
  108. package/dist/gantt/gantt-config.d.ts +73 -0
  109. package/dist/gantt/gantt-config.js +1 -0
  110. package/dist/gantt/gantt-critical-path.d.ts +67 -0
  111. package/dist/gantt/gantt-critical-path.js +207 -0
  112. package/dist/gantt/gantt-model.d.ts +257 -0
  113. package/dist/gantt/gantt-model.js +651 -0
  114. package/dist/gantt/gantt-resources.d.ts +59 -0
  115. package/dist/gantt/gantt-resources.js +71 -0
  116. package/dist/gantt/gantt.d.ts +7 -0
  117. package/dist/gantt/gantt.js +36 -0
  118. package/dist/gantt/index.d.ts +15 -0
  119. package/dist/gantt/index.js +22 -0
  120. package/dist/gantt/timeline-arrows.d.ts +102 -0
  121. package/dist/gantt/timeline-arrows.js +181 -0
  122. package/dist/import.d.ts +2 -2
  123. package/dist/import.js +1 -1
  124. package/dist/index.d.ts +34 -7
  125. package/dist/index.js +44 -7
  126. package/dist/install.js +8 -1
  127. package/dist/license-core.d.ts +17 -1
  128. package/dist/license-core.js +16 -5
  129. package/dist/license.d.ts +18 -2
  130. package/dist/license.js +71 -6
  131. package/dist/node/studio.js +140 -8
  132. package/dist/pivot-enable.js +2 -2
  133. package/dist/scheduler-dependencies.d.ts +35 -0
  134. package/dist/scheduler-dependencies.js +36 -1
  135. package/dist/scheduler.js +2 -2
  136. package/dist/selection-bar.js +2 -2
  137. package/dist/server/SvGroupCell.svelte +213 -0
  138. package/dist/server/SvGroupCell.svelte.d.ts +62 -0
  139. package/dist/server/SvRowGroupPanel.svelte +322 -0
  140. package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
  141. package/dist/server/enable.d.ts +17 -0
  142. package/dist/server/enable.js +49 -0
  143. package/dist/server/index.d.ts +22 -0
  144. package/dist/server/index.js +24 -0
  145. package/dist/server/messages.d.ts +45 -0
  146. package/dist/server/messages.js +35 -0
  147. package/dist/server/server-group-model.d.ts +104 -0
  148. package/dist/server/server-group-model.js +272 -0
  149. package/dist/server/server-pivot.d.ts +46 -0
  150. package/dist/server/server-pivot.js +57 -0
  151. package/dist/server/server-row-model.d.ts +547 -0
  152. package/dist/server/server-row-model.js +1551 -0
  153. package/dist/server/server-selection.d.ts +99 -0
  154. package/dist/server/server-selection.js +215 -0
  155. package/dist/server/svgrid-adapter.d.ts +155 -0
  156. package/dist/server/svgrid-adapter.js +202 -0
  157. package/dist/sheet/ast.d.ts +45 -1
  158. package/dist/sheet/ast.js +14 -0
  159. package/dist/sheet/auto-filter.d.ts +58 -4
  160. package/dist/sheet/auto-filter.js +159 -11
  161. package/dist/sheet/autocomplete.d.ts +9 -0
  162. package/dist/sheet/autocomplete.js +90 -4
  163. package/dist/sheet/cell-images.d.ts +39 -0
  164. package/dist/sheet/cell-images.js +90 -0
  165. package/dist/sheet/cell-types.d.ts +58 -0
  166. package/dist/sheet/cell-types.js +96 -0
  167. package/dist/sheet/coerce.d.ts +40 -0
  168. package/dist/sheet/coerce.js +170 -3
  169. package/dist/sheet/comments.d.ts +43 -9
  170. package/dist/sheet/comments.js +85 -27
  171. package/dist/sheet/conditional-formats.d.ts +29 -6
  172. package/dist/sheet/conditional-formats.js +37 -12
  173. package/dist/sheet/csv.d.ts +32 -0
  174. package/dist/sheet/csv.js +158 -0
  175. package/dist/sheet/culture.d.ts +87 -0
  176. package/dist/sheet/culture.js +247 -0
  177. package/dist/sheet/delta.d.ts +102 -0
  178. package/dist/sheet/delta.js +203 -0
  179. package/dist/sheet/deps.d.ts +45 -1
  180. package/dist/sheet/deps.js +77 -2
  181. package/dist/sheet/document.d.ts +106 -4
  182. package/dist/sheet/document.js +204 -3
  183. package/dist/sheet/engine.d.ts +67 -0
  184. package/dist/sheet/engine.js +19 -0
  185. package/dist/sheet/entry.d.ts +8 -4
  186. package/dist/sheet/entry.js +171 -21
  187. package/dist/sheet/error-check.d.ts +49 -0
  188. package/dist/sheet/error-check.js +83 -0
  189. package/dist/sheet/evaluate-steps.d.ts +48 -0
  190. package/dist/sheet/evaluate-steps.js +196 -0
  191. package/dist/sheet/evaluate.d.ts +67 -0
  192. package/dist/sheet/evaluate.js +725 -13
  193. package/dist/sheet/filter-files.d.ts +37 -0
  194. package/dist/sheet/filter-files.js +465 -0
  195. package/dist/sheet/format-store.d.ts +15 -0
  196. package/dist/sheet/format-store.js +11 -1
  197. package/dist/sheet/from-rows.d.ts +14 -0
  198. package/dist/sheet/from-rows.js +50 -0
  199. package/dist/sheet/function-catalog.d.ts +1 -1
  200. package/dist/sheet/function-catalog.js +296 -5
  201. package/dist/sheet/functions.js +363 -91
  202. package/dist/sheet/index.d.ts +35 -8
  203. package/dist/sheet/index.js +35 -8
  204. package/dist/sheet/links.d.ts +57 -0
  205. package/dist/sheet/links.js +174 -0
  206. package/dist/sheet/messages.d.ts +778 -0
  207. package/dist/sheet/messages.js +806 -0
  208. package/dist/sheet/number-format.d.ts +52 -5
  209. package/dist/sheet/number-format.js +465 -68
  210. package/dist/sheet/objects.d.ts +106 -0
  211. package/dist/sheet/objects.js +149 -0
  212. package/dist/sheet/ods-document.d.ts +44 -0
  213. package/dist/sheet/ods-document.js +1275 -0
  214. package/dist/sheet/open-file.d.ts +26 -0
  215. package/dist/sheet/open-file.js +56 -0
  216. package/dist/sheet/outline.d.ts +82 -0
  217. package/dist/sheet/outline.js +228 -0
  218. package/dist/sheet/packs/array.d.ts +16 -0
  219. package/dist/sheet/packs/array.js +172 -0
  220. package/dist/sheet/packs/database.d.ts +42 -0
  221. package/dist/sheet/packs/database.js +142 -0
  222. package/dist/sheet/packs/engineering.d.ts +2 -0
  223. package/dist/sheet/packs/engineering.js +296 -0
  224. package/dist/sheet/packs/financial.d.ts +5 -0
  225. package/dist/sheet/packs/financial.js +220 -0
  226. package/dist/sheet/packs/math-stats.d.ts +2 -0
  227. package/dist/sheet/packs/math-stats.js +372 -0
  228. package/dist/sheet/packs/special.d.ts +44 -0
  229. package/dist/sheet/packs/special.js +187 -0
  230. package/dist/sheet/packs/stats-dist.d.ts +2 -0
  231. package/dist/sheet/packs/stats-dist.js +785 -0
  232. package/dist/sheet/packs/text-date.d.ts +2 -0
  233. package/dist/sheet/packs/text-date.js +332 -0
  234. package/dist/sheet/packs/trig.d.ts +2 -0
  235. package/dist/sheet/packs/trig.js +73 -0
  236. package/dist/sheet/page-setup.d.ts +73 -0
  237. package/dist/sheet/page-setup.js +73 -0
  238. package/dist/sheet/parse.js +42 -10
  239. package/dist/sheet/paste-special.d.ts +22 -0
  240. package/dist/sheet/paste-special.js +14 -5
  241. package/dist/sheet/pivot-range.d.ts +160 -0
  242. package/dist/sheet/pivot-range.js +333 -0
  243. package/dist/sheet/presence.d.ts +73 -0
  244. package/dist/sheet/presence.js +79 -0
  245. package/dist/sheet/print.d.ts +104 -0
  246. package/dist/sheet/print.js +169 -0
  247. package/dist/sheet/protection.d.ts +55 -1
  248. package/dist/sheet/protection.js +55 -2
  249. package/dist/sheet/rects.d.ts +1 -1
  250. package/dist/sheet/refs.d.ts +48 -0
  251. package/dist/sheet/refs.js +155 -8
  252. package/dist/sheet/ribbon-icons.d.ts +1 -1
  253. package/dist/sheet/ribbon-icons.js +186 -0
  254. package/dist/sheet/ribbon.d.ts +1 -1
  255. package/dist/sheet/ribbon.js +324 -23
  256. package/dist/sheet/sheet-kinds.d.ts +74 -0
  257. package/dist/sheet/sheet-kinds.js +91 -0
  258. package/dist/sheet/shortcuts.d.ts +13 -3
  259. package/dist/sheet/shortcuts.js +63 -12
  260. package/dist/sheet/sort.d.ts +51 -0
  261. package/dist/sheet/sort.js +107 -0
  262. package/dist/sheet/sparklines.d.ts +111 -0
  263. package/dist/sheet/sparklines.js +204 -0
  264. package/dist/sheet/table-styles.d.ts +74 -0
  265. package/dist/sheet/table-styles.js +152 -0
  266. package/dist/sheet/tables.d.ts +19 -0
  267. package/dist/sheet/tables.js +31 -0
  268. package/dist/sheet/tokenize.d.ts +18 -1
  269. package/dist/sheet/tokenize.js +60 -9
  270. package/dist/sheet/validation.d.ts +16 -0
  271. package/dist/sheet/validation.js +37 -0
  272. package/dist/sheet/workbook.d.ts +148 -3
  273. package/dist/sheet/workbook.js +742 -31
  274. package/dist/sheet/xls-cfb.d.ts +32 -0
  275. package/dist/sheet/xls-cfb.js +290 -0
  276. package/dist/sheet/xls-document.d.ts +10 -0
  277. package/dist/sheet/xls-document.js +1243 -0
  278. package/dist/sheet/xls-formula.d.ts +40 -0
  279. package/dist/sheet/xls-formula.js +569 -0
  280. package/dist/sheet/xlsx-document.d.ts +108 -0
  281. package/dist/sheet/xlsx-document.js +1849 -0
  282. package/dist/sheet/xlsx-drawing.d.ts +99 -0
  283. package/dist/sheet/xlsx-drawing.js +420 -0
  284. package/dist/sheet-enable.js +6 -7
  285. package/dist/sheet-text.d.ts +7 -0
  286. package/dist/sheet-text.js +29 -0
  287. package/dist/sources/auth-supabase.js +2 -2
  288. package/dist/sources/realtime-supabase.js +2 -2
  289. package/dist/sources/rest.d.ts +6 -2
  290. package/dist/sources/rest.js +42 -7
  291. package/dist/sources/supabase.d.ts +10 -1
  292. package/dist/sources/supabase.js +73 -40
  293. package/dist/studio/emit-project.js +14 -1
  294. package/dist/studio/index.js +1 -1
  295. package/dist/studio/project.d.ts +11 -2
  296. package/dist/studio/project.js +5 -2
  297. package/dist/studio/ui-components.generated.js +107 -2
  298. package/dist/sveltekit/in-memory.d.ts +2 -2
  299. package/dist/sveltekit/in-memory.js +125 -4
  300. package/dist/sveltekit/index.d.ts +1 -1
  301. package/dist/sveltekit/index.js +1 -1
  302. package/dist/sveltekit/query-plan.d.ts +50 -4
  303. package/dist/sveltekit/query-plan.js +25 -3
  304. package/dist/sveltekit/sql-source.d.ts +6 -0
  305. package/dist/sveltekit/sql-source.js +34 -6
  306. package/dist/sveltekit/sql.d.ts +31 -0
  307. package/dist/sveltekit/sql.js +135 -25
  308. package/dist/sveltekit/transport.d.ts +10 -2
  309. package/dist/sveltekit/transport.js +30 -6
  310. package/dist/upgrade-prompt.d.ts +1 -1
  311. package/dist/version.d.ts +1 -1
  312. package/dist/version.js +1 -1
  313. package/dist/watermark.js +35 -2
  314. package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
  315. package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
  316. package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
  317. package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
  318. package/dist/wc/SvField-B_kurx_P.js +149 -0
  319. package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
  320. package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
  321. package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
  322. package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
  323. package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
  324. package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
  325. package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
  326. package/dist/wc/SvModal-DrfOYYyX.js +189 -0
  327. package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
  328. package/dist/wc/chart-6OXlybVT.js +4341 -0
  329. package/dist/wc/chart-export-CeSADGfi.js +135 -0
  330. package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
  331. package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
  332. package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
  333. package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
  334. package/dist/wc/client-DQIzjDBG.js +3064 -0
  335. package/dist/wc/column-resize-DUPgynvc.js +101 -0
  336. package/dist/wc/date-format-DS-z_h9v.js +1130 -0
  337. package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
  338. package/dist/wc/editor-contract-peBDxir_.js +22 -0
  339. package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
  340. package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
  341. package/dist/wc/list-option-DrRyivWp.js +82 -0
  342. package/dist/wc/popover-WsGlvtQv.js +69 -0
  343. package/dist/wc/positioning-BiKLfu1E.js +112 -0
  344. package/dist/wc/react.d.ts +41 -0
  345. package/dist/wc/react.js +90 -0
  346. package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
  347. package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
  348. package/dist/wc/row-resize-limG_N1z.js +111 -0
  349. package/dist/wc/sv-sheet-element.d.ts +97 -0
  350. package/dist/wc/sv-sheet-element.js +44213 -0
  351. package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
  352. package/dist/wc/validate-BqDthkNr.js +88 -0
  353. package/dist/wc/vue.d.ts +31 -0
  354. package/dist/wc/vue.js +75 -0
  355. package/package.json +30 -5
  356. package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
  357. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
  358. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
  359. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
  360. package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
  361. package/dist/designer/assets/index-CMFgAoVV.css +0 -1
  362. package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
  363. package/dist/designer/assets/src-EdI3Xqds.js +0 -58
  364. package/dist/designer/assets/validate-h-aslRV9.js +0 -1
@@ -0,0 +1,1243 @@
1
+ import { colToLetters, lettersToCol, quoteSheet } from './address.js';
2
+ import { formatKeyAt } from './format-store.js';
3
+ import { isCompoundFile, readCompoundFile, writeCompoundFile } from './xls-cfb.js';
4
+ import { formulaFromRpn, isModernFunctionName, rpnFromFormula, xlsErrorCode, xlsErrorText } from './xls-formula.js';
5
+ import { asText, BUILTIN_NUMFMT, isDateFormat, isDayFormat, isoToSerial, ptToPx, pxToPt, serialToIso, widthToPx, } from './xlsx-document.js';
6
+ import { isError } from './ast.js';
7
+ import { TEXT_PREFIX } from './workbook.js';
8
+ import { filteredRows } from './filter-files.js';
9
+ // ---------------------------------------------------------------------------
10
+ // The records this module knows by name.
11
+ const BOF = 0x0809;
12
+ const EOF_RECORD = 0x000a;
13
+ const BOUNDSHEET = 0x0085;
14
+ const SST = 0x00fc;
15
+ const CONTINUE = 0x003c;
16
+ const FORMAT = 0x041e;
17
+ const XF = 0x00e0;
18
+ const FONT = 0x0031;
19
+ const PALETTE = 0x0092;
20
+ const EXTERNSHEET = 0x0017;
21
+ const SUPBOOK = 0x01ae;
22
+ const NAME = 0x0018;
23
+ const ROW = 0x0208;
24
+ const COLINFO = 0x007d;
25
+ const DIMENSIONS = 0x0200;
26
+ const LABELSST = 0x00fd;
27
+ const LABEL = 0x0204;
28
+ const NUMBER = 0x0203;
29
+ const RK = 0x027e;
30
+ const MULRK = 0x00bd;
31
+ const BLANK = 0x0201;
32
+ const MULBLANK = 0x00be;
33
+ const BOOLERR = 0x0205;
34
+ const FORMULA = 0x0006;
35
+ const STRING_RECORD = 0x0207;
36
+ const SHRFMLA = 0x04bc;
37
+ const ARRAY_RECORD = 0x0221;
38
+ const MERGEDCELLS = 0x00e5;
39
+ const WINDOW1 = 0x003d;
40
+ const WINDOW2 = 0x023e;
41
+ const PANE = 0x0041;
42
+ const PROTECT = 0x0012;
43
+ const CODEPAGE = 0x0042;
44
+ const DATEMODE = 0x0022;
45
+ const STYLE = 0x0293;
46
+ const DEFCOLWIDTH = 0x0055;
47
+ const DEFAULTROWHEIGHT = 0x0225;
48
+ const COUNTRY = 0x008c;
49
+ const SELECTION = 0x001d;
50
+ /** BIFF8's own palette, for the colours a file names by index alone. */
51
+ const PALETTE_DEFAULT = [
52
+ '000000', 'ffffff', 'ff0000', '00ff00', '0000ff', 'ffff00', 'ff00ff', '00ffff',
53
+ '800000', '008000', '000080', '808000', '800080', '008080', 'c0c0c0', '808080',
54
+ '9999ff', '993366', 'ffffcc', 'ccffff', '660066', 'ff8080', '0066cc', 'ccccff',
55
+ '000080', 'ff00ff', 'ffff00', '00ffff', '800080', '800000', '008080', '0000ff',
56
+ '00ccff', 'ccffff', 'ccffcc', 'ffff99', '99ccff', 'ff99cc', 'cc99ff', 'ffcc99',
57
+ '3366ff', '33cccc', '99cc00', 'ffcc00', 'ff9900', 'ff6600', '666699', '969696',
58
+ '003366', '339966', '003300', '333300', '993300', '993366', '333399', '333333',
59
+ ];
60
+ /** The first palette entry a colour index names; 0-7 repeat the first eight. */
61
+ const PALETTE_BASE = 8;
62
+ /** Every record in a stream, in order. */
63
+ function records(stream) {
64
+ const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
65
+ const out = [];
66
+ let at = 0;
67
+ while (at + 4 <= stream.length) {
68
+ const type = view.getUint16(at, true);
69
+ const size = view.getUint16(at + 2, true);
70
+ if (at + 4 + size > stream.length)
71
+ break;
72
+ out.push({ type, data: stream.subarray(at + 4, at + 4 + size) });
73
+ at += 4 + size;
74
+ }
75
+ return out;
76
+ }
77
+ const u8 = (r, at) => r[at] ?? 0;
78
+ const u16 = (r, at) => (r[at] ?? 0) | ((r[at + 1] ?? 0) << 8);
79
+ const u32 = (r, at) => (u16(r, at) | (u16(r, at + 2) << 16)) >>> 0;
80
+ const f64 = (r, at) => new DataView(r.buffer, r.byteOffset, r.byteLength).getFloat64(at, true);
81
+ /**
82
+ * An RK number: a double with its low thirty-four bits thrown away, or a
83
+ * thirty-bit integer, either of them possibly divided by a hundred. Excel
84
+ * uses it because most numbers in a sheet fit, and four bytes beat eight.
85
+ */
86
+ function rkValue(rk) {
87
+ const hundredths = (rk & 0x01) !== 0;
88
+ const whole = (rk & 0x02) !== 0;
89
+ let value;
90
+ if (whole) {
91
+ value = rk >> 2;
92
+ }
93
+ else {
94
+ const buffer = new DataView(new ArrayBuffer(8));
95
+ buffer.setUint32(4, (rk & 0xfffffffc) >>> 0, true);
96
+ value = buffer.getFloat64(0, true);
97
+ }
98
+ return hundredths ? value / 100 : value;
99
+ }
100
+ /** The four bytes an RK holds, or null for a number that does not fit one. */
101
+ function rkBytes(value) {
102
+ for (const hundredths of [false, true]) {
103
+ const scaled = hundredths ? value * 100 : value;
104
+ if (Number.isInteger(scaled) && scaled >= -(2 ** 29) && scaled < 2 ** 29) {
105
+ return ((scaled << 2) | (hundredths ? 0x01 : 0) | 0x02) >>> 0;
106
+ }
107
+ const buffer = new DataView(new ArrayBuffer(8));
108
+ buffer.setFloat64(0, scaled, true);
109
+ if (buffer.getUint32(0, true) === 0 && (buffer.getUint32(4, true) & 0x03) === 0) {
110
+ return (buffer.getUint32(4, true) | (hundredths ? 0x01 : 0)) >>> 0;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ /** A string with its own length in front: `cch`, a flag byte, the characters. */
116
+ function unicodeString(data, at, lengthBytes = 2) {
117
+ const count = lengthBytes === 1 ? u8(data, at) : u16(data, at);
118
+ let cursor = at + lengthBytes;
119
+ const flags = u8(data, cursor);
120
+ cursor += 1;
121
+ const wide = (flags & 0x01) !== 0;
122
+ const rich = (flags & 0x08) !== 0;
123
+ const far = (flags & 0x04) !== 0;
124
+ const runs = rich ? u16(data, cursor) : 0;
125
+ if (rich)
126
+ cursor += 2;
127
+ const extra = far ? u32(data, cursor) : 0;
128
+ if (far)
129
+ cursor += 4;
130
+ let text = '';
131
+ for (let i = 0; i < count; i += 1) {
132
+ if (wide) {
133
+ text += String.fromCharCode(u16(data, cursor));
134
+ cursor += 2;
135
+ }
136
+ else {
137
+ text += String.fromCharCode(u8(data, cursor));
138
+ cursor += 1;
139
+ }
140
+ }
141
+ return { text, next: cursor + runs * 4 + extra };
142
+ }
143
+ /**
144
+ * The shared string table, which is the one record that does not fit in a
145
+ * record: it runs on through CONTINUEs, and a string may be cut in half by
146
+ * one, with a flag byte at the join saying how the rest is encoded.
147
+ */
148
+ function sharedStrings(blocks) {
149
+ const out = [];
150
+ if (!blocks.length)
151
+ return out;
152
+ let block = 0;
153
+ let at = 8; // cstTotal and cstUnique
154
+ const nextBlock = () => {
155
+ block += 1;
156
+ at = 0;
157
+ return block < blocks.length;
158
+ };
159
+ const byteAt = () => {
160
+ while (block < blocks.length && at >= blocks[block].length)
161
+ if (!nextBlock())
162
+ return 0;
163
+ const value = u8(blocks[block], at);
164
+ at += 1;
165
+ return value;
166
+ };
167
+ const wordAt = () => byteAt() | (byteAt() << 8);
168
+ const total = blocks[0].length >= 8 ? u32(blocks[0], 4) : 0;
169
+ for (let i = 0; i < total; i += 1) {
170
+ while (block < blocks.length && at >= blocks[block].length)
171
+ if (!nextBlock())
172
+ return out;
173
+ if (block >= blocks.length)
174
+ return out;
175
+ const count = wordAt();
176
+ let flags = byteAt();
177
+ let wide = (flags & 0x01) !== 0;
178
+ const rich = (flags & 0x08) !== 0;
179
+ const far = (flags & 0x04) !== 0;
180
+ const runs = rich ? wordAt() : 0;
181
+ const extra = far ? (wordAt() | (wordAt() << 16)) : 0;
182
+ let text = '';
183
+ for (let c = 0; c < count; c += 1) {
184
+ // A CONTINUE in the middle of a string starts with a flag byte of its
185
+ // own: the rest can be encoded the other way round.
186
+ if (at >= blocks[block].length) {
187
+ if (!nextBlock())
188
+ return out;
189
+ flags = byteAt();
190
+ wide = (flags & 0x01) !== 0;
191
+ }
192
+ text += wide ? String.fromCharCode(wordAt()) : String.fromCharCode(byteAt());
193
+ }
194
+ for (let skip = 0; skip < runs * 4 + extra; skip += 1)
195
+ byteAt();
196
+ out.push(text);
197
+ }
198
+ return out;
199
+ }
200
+ /** A workbook's fonts, formats and cell styles, as this document spells them. */
201
+ function readStyles(globals) {
202
+ const palette = [...PALETTE_DEFAULT];
203
+ for (const record of globals) {
204
+ if (record.type !== PALETTE)
205
+ continue;
206
+ const count = u16(record.data, 0);
207
+ for (let i = 0; i < count && i < palette.length; i += 1) {
208
+ const at = 2 + i * 4;
209
+ palette[i] = [u8(record.data, at), u8(record.data, at + 1), u8(record.data, at + 2)]
210
+ .map((v) => v.toString(16).padStart(2, '0')).join('');
211
+ }
212
+ }
213
+ const colour = (index) => {
214
+ if (index === 0x7fff || index === 64 || index === 65)
215
+ return undefined;
216
+ const rgb = palette[Math.max(0, index - PALETTE_BASE)];
217
+ return rgb ? `#${rgb}` : undefined;
218
+ };
219
+ const fonts = [];
220
+ const formats = new Map();
221
+ const xfs = [];
222
+ for (const record of globals) {
223
+ if (record.type === FONT) {
224
+ const grbit = u16(record.data, 2);
225
+ const font = {};
226
+ const height = u16(record.data, 0);
227
+ if (height)
228
+ font.fontSize = ptToPx(height / 20);
229
+ if (u16(record.data, 6) >= 700)
230
+ font.bold = true;
231
+ if (grbit & 0x02)
232
+ font.italic = true;
233
+ if (grbit & 0x08)
234
+ font.strike = true;
235
+ if (u8(record.data, 10) !== 0)
236
+ font.underline = true;
237
+ const shade = colour(u16(record.data, 4));
238
+ if (shade)
239
+ font.color = shade;
240
+ const name = unicodeString(record.data, 14, 1).text;
241
+ if (name)
242
+ font.fontFamily = name;
243
+ fonts.push(font);
244
+ }
245
+ else if (record.type === FORMAT) {
246
+ formats.set(u16(record.data, 0), unicodeString(record.data, 2).text);
247
+ }
248
+ else if (record.type === XF) {
249
+ const fontIndex = u16(record.data, 0);
250
+ // There is no fifth font: Excel leaves index 4 out of the file.
251
+ const font = fonts[fontIndex >= 5 ? fontIndex - 1 : fontIndex] ?? {};
252
+ const numFmt = formats.get(u16(record.data, 2)) ?? BUILTIN_NUMFMT[u16(record.data, 2)];
253
+ const format = { ...font };
254
+ if (numFmt && !/^General$/i.test(numFmt))
255
+ format.numFmt = numFmt;
256
+ if ((u16(record.data, 4) & 0x01) === 0)
257
+ format.locked = false;
258
+ const align = u8(record.data, 6) & 0x07;
259
+ if (align === 1)
260
+ format.align = 'left';
261
+ else if (align === 2 || align === 6)
262
+ format.align = 'center';
263
+ else if (align === 3)
264
+ format.align = 'right';
265
+ if (u8(record.data, 6) & 0x08)
266
+ format.wrap = true;
267
+ const indent = u8(record.data, 8) & 0x0f;
268
+ if (indent)
269
+ format.indent = indent;
270
+ const fill = u32(record.data, 14);
271
+ const pattern = (fill >>> 26) & 0x3f;
272
+ const fore = u16(record.data, 18) & 0x7f;
273
+ const back = (u16(record.data, 18) >> 7) & 0x7f;
274
+ // A solid fill paints in the pattern's foreground colour; anything
275
+ // else is a hatch this document has no word for, so its background
276
+ // stands in for it.
277
+ const paint = pattern === 1 ? colour(fore) : pattern > 1 ? colour(back) : undefined;
278
+ if (paint)
279
+ format.fill = paint;
280
+ const borders = u32(record.data, 10);
281
+ const sides = {};
282
+ // BIFF numbers its line styles: 1 thin, 2 medium, 5 thick, 6 double,
283
+ // 4 and 7 dotted, and the rest dashes of one length or another.
284
+ const edge = (line, colourIndex) => {
285
+ if (!line)
286
+ return undefined;
287
+ const spec = { width: line === 5 ? 3 : line === 2 || line === 8 || line === 10 || line === 12 ? 2 : 1 };
288
+ if (line === 6)
289
+ spec.style = 'double';
290
+ else if (line === 4 || line === 7)
291
+ spec.style = 'dotted';
292
+ else if (line >= 3 && line !== 5)
293
+ spec.style = 'dashed';
294
+ const shade = colour(colourIndex);
295
+ if (shade)
296
+ spec.color = shade;
297
+ return spec;
298
+ };
299
+ const left = edge(borders & 0x0f, (borders >>> 16) & 0x7f);
300
+ const right = edge((borders >>> 4) & 0x0f, (borders >>> 23) & 0x7f);
301
+ const top = edge((borders >>> 8) & 0x0f, u16(record.data, 14) & 0x7f);
302
+ const bottom = edge((borders >>> 12) & 0x0f, (u16(record.data, 14) >> 7) & 0x7f);
303
+ if (left)
304
+ sides.left = left;
305
+ if (right)
306
+ sides.right = right;
307
+ if (top)
308
+ sides.top = top;
309
+ if (bottom)
310
+ sides.bottom = bottom;
311
+ if (Object.keys(sides).length)
312
+ format.border = sides;
313
+ xfs.push({ format, numFmt });
314
+ }
315
+ }
316
+ return { xfs };
317
+ }
318
+ /** The document an .xls holds, as a saved state. */
319
+ export function sheetStateFromXls(bytes) {
320
+ const streams = readCompoundFile(bytes);
321
+ const stream = streams.get('Workbook') ?? streams.get('Book');
322
+ if (!stream)
323
+ throw new Error('@svgrid/enterprise: not an .xls file: it holds no workbook stream');
324
+ const all = records(stream);
325
+ // The globals run to the first EOF; each sheet is its own run of records
326
+ // after them, found through the offsets the BOUNDSHEETs carry.
327
+ const globalEnd = all.findIndex((r) => r.type === EOF_RECORD);
328
+ const globals = all.slice(0, globalEnd < 0 ? all.length : globalEnd);
329
+ const styles = readStyles(globals);
330
+ const sheetNames = [];
331
+ const sheetHidden = [];
332
+ for (const record of globals) {
333
+ if (record.type !== BOUNDSHEET)
334
+ continue;
335
+ sheetNames.push(unicodeString(record.data, 6, 1).text);
336
+ sheetHidden.push((u8(record.data, 4) & 0x03) !== 0);
337
+ }
338
+ const sstBlocks = [];
339
+ for (let i = 0; i < globals.length; i += 1) {
340
+ if (globals[i].type !== SST)
341
+ continue;
342
+ sstBlocks.push(globals[i].data);
343
+ for (let j = i + 1; j < globals.length && globals[j].type === CONTINUE; j += 1)
344
+ sstBlocks.push(globals[j].data);
345
+ break;
346
+ }
347
+ const shared = sharedStrings(sstBlocks);
348
+ // Which sheet a 3d reference names: an index into EXTERNSHEET, which
349
+ // points at a SUPBOOK, which is this workbook or another one.
350
+ const internal = [];
351
+ for (const record of globals) {
352
+ if (record.type !== SUPBOOK)
353
+ continue;
354
+ internal.push(record.data.length === 4 && u16(record.data, 2) === 0x0401);
355
+ }
356
+ const xti = [];
357
+ for (const record of globals) {
358
+ if (record.type !== EXTERNSHEET)
359
+ continue;
360
+ const count = u16(record.data, 0);
361
+ for (let i = 0; i < count; i += 1) {
362
+ const at = 2 + i * 6;
363
+ xti.push({ book: u16(record.data, at), first: u16(record.data, at + 2), last: u16(record.data, at + 4) });
364
+ }
365
+ }
366
+ const sheetAt = (index) => {
367
+ const entry = xti[index];
368
+ if (!entry || internal[entry.book] === false)
369
+ return null;
370
+ const first = sheetNames[entry.first];
371
+ if (first === undefined)
372
+ return null;
373
+ const last = sheetNames[entry.last];
374
+ return entry.first === entry.last || last === undefined
375
+ ? quoteSheet(first)
376
+ : `${quoteSheet(first)}:${quoteSheet(last)}`;
377
+ };
378
+ // Defined names, which formulas reach by index. The name itself sits at a
379
+ // fixed offset inside the record, after four lengths of text nobody uses
380
+ // any more (a custom menu, a description, a help topic, a status bar).
381
+ const nameTexts = [];
382
+ const nameRpn = [];
383
+ const names = {};
384
+ for (const record of globals) {
385
+ if (record.type !== NAME)
386
+ continue;
387
+ const builtin = (u16(record.data, 0) & 0x20) !== 0;
388
+ const count = u8(record.data, 3);
389
+ const wide = (u8(record.data, 14) & 0x01) !== 0;
390
+ let text = '';
391
+ for (let i = 0; i < count; i += 1) {
392
+ text += wide ? String.fromCharCode(u16(record.data, 15 + i * 2)) : String.fromCharCode(u8(record.data, 15 + i));
393
+ }
394
+ const start = 15 + count * (wide ? 2 : 1);
395
+ nameTexts.push(builtin ? '' : text);
396
+ nameRpn.push(record.data.subarray(start, start + u16(record.data, 4)));
397
+ }
398
+ const nameAt = (index) => nameTexts[index - 1] || null;
399
+ nameTexts.forEach((text, index) => {
400
+ // A name that only stands in for a function Excel 97 never had is not a
401
+ // name the workbook wants: the formulas calling it say so themselves.
402
+ if (!text || isModernFunctionName(text))
403
+ return;
404
+ const formula = formulaFromRpn(nameRpn[index], { sheetAt, nameAt, row: 0, col: 0 });
405
+ if (formula)
406
+ names[text] = `=${formula}`;
407
+ });
408
+ const sheets = [];
409
+ const entries = {};
410
+ // Every sheet's records: the runs of records between the BOFs that follow
411
+ // the globals, in the order the BOUNDSHEETs named them.
412
+ const starts = [];
413
+ for (let i = (globalEnd < 0 ? all.length : globalEnd + 1); i < all.length; i += 1) {
414
+ if (all[i].type === BOF)
415
+ starts.push(i);
416
+ }
417
+ sheetNames.forEach((name, index) => {
418
+ const from = starts[index];
419
+ const to = starts[index + 1] ?? all.length;
420
+ const body = from === undefined ? [] : all.slice(from, to);
421
+ const cells = [];
422
+ const entry = {
423
+ formats: {}, columnWidths: {}, rowHeights: [], hidden: { rows: [], cols: [] },
424
+ freeze: { rows: 0, cols: 0 }, comments: {}, protected: false, sheetHidden: sheetHidden[index] ?? false,
425
+ merges: [], validation: [], conditionalFormats: [], autoFilter: null,
426
+ };
427
+ const put = (row, col, text) => {
428
+ if (text === '')
429
+ return;
430
+ while (cells.length <= row)
431
+ cells.push([]);
432
+ const line = cells[row];
433
+ while (line.length <= col)
434
+ line.push('');
435
+ line[col] = text;
436
+ };
437
+ const style = (ixfe) => styles.xfs[ixfe];
438
+ const look = (row, col, ixfe) => {
439
+ const found = style(ixfe);
440
+ if (!found || !Object.keys(found.format).length)
441
+ return;
442
+ entry.formats[formatKeyAt(row, col)] = { ...found.format };
443
+ };
444
+ /** A number as the workbook stores it: an ISO date when the cell is one. */
445
+ const numberText = (value, ixfe) => isDayFormat(style(ixfe)?.numFmt) && Number.isInteger(value) && value > 0 ? serialToIso(value) : String(value);
446
+ // A shared or array formula is stored once, at its anchor, and every
447
+ // other cell points back at it.
448
+ const anchored = new Map();
449
+ for (const record of body) {
450
+ if (record.type === SHRFMLA) {
451
+ const cce = u16(record.data, 8);
452
+ anchored.set(`${u16(record.data, 0)},${u8(record.data, 4)}`, { rpn: record.data.subarray(10, 10 + cce), array: false });
453
+ }
454
+ else if (record.type === ARRAY_RECORD) {
455
+ const cce = u16(record.data, 12);
456
+ anchored.set(`${u16(record.data, 0)},${u8(record.data, 4)}`, { rpn: record.data.subarray(14, 14 + cce), array: true });
457
+ }
458
+ }
459
+ /** The cells an array formula spills into, which hold no text of their own. */
460
+ const spills = [];
461
+ for (const record of body) {
462
+ if (record.type !== ARRAY_RECORD)
463
+ continue;
464
+ spills.push([u16(record.data, 0), u8(record.data, 4), u16(record.data, 2), u8(record.data, 5)]);
465
+ }
466
+ const spilled = (row, col) => spills.some(([r1, c1, r2, c2]) => row >= r1 && row <= r2 && col >= c1 && col <= c2 && !(row === r1 && col === c1));
467
+ let pending = null;
468
+ for (const record of body) {
469
+ const data = record.data;
470
+ switch (record.type) {
471
+ case ROW: {
472
+ const row = u16(data, 0);
473
+ const twips = u16(data, 6) & 0x7fff;
474
+ const grbit = u32(data, 12);
475
+ if (grbit & 0x20)
476
+ entry.hidden.rows.push(row);
477
+ if (grbit & 0x40)
478
+ entry.rowHeights.push([row, ptToPx(twips / 20)]);
479
+ break;
480
+ }
481
+ case COLINFO: {
482
+ const first = u16(data, 0);
483
+ const last = Math.min(u16(data, 2), 255);
484
+ const width = widthToPx(u16(data, 4) / 256);
485
+ const hidden = (u16(data, 8) & 0x01) !== 0;
486
+ // A run over the whole sheet is the default width said once, not a
487
+ // decision about every column.
488
+ const wide = last - first > 64;
489
+ for (let col = first; col <= last; col += 1) {
490
+ if (!wide)
491
+ entry.columnWidths[colToLetters(col)] = width;
492
+ if (hidden)
493
+ entry.hidden.cols.push(col);
494
+ }
495
+ break;
496
+ }
497
+ case LABELSST: {
498
+ const row = u16(data, 0);
499
+ const col = u16(data, 2);
500
+ look(row, col, u16(data, 4));
501
+ put(row, col, asText(shared[u32(data, 6)] ?? ''));
502
+ break;
503
+ }
504
+ case LABEL: {
505
+ const row = u16(data, 0);
506
+ const col = u16(data, 2);
507
+ look(row, col, u16(data, 4));
508
+ put(row, col, asText(unicodeString(data, 6).text));
509
+ break;
510
+ }
511
+ case NUMBER: {
512
+ const row = u16(data, 0);
513
+ const col = u16(data, 2);
514
+ const ixfe = u16(data, 4);
515
+ look(row, col, ixfe);
516
+ put(row, col, numberText(f64(data, 6), ixfe));
517
+ break;
518
+ }
519
+ case RK: {
520
+ const row = u16(data, 0);
521
+ const col = u16(data, 2);
522
+ const ixfe = u16(data, 4);
523
+ look(row, col, ixfe);
524
+ put(row, col, numberText(rkValue(u32(data, 6) | 0), ixfe));
525
+ break;
526
+ }
527
+ case MULRK: {
528
+ const row = u16(data, 0);
529
+ const first = u16(data, 2);
530
+ const count = Math.floor((data.length - 6) / 6);
531
+ for (let i = 0; i < count; i += 1) {
532
+ const ixfe = u16(data, 4 + i * 6);
533
+ const col = first + i;
534
+ look(row, col, ixfe);
535
+ put(row, col, numberText(rkValue(u32(data, 6 + i * 6) | 0), ixfe));
536
+ }
537
+ break;
538
+ }
539
+ case BLANK:
540
+ look(u16(data, 0), u16(data, 2), u16(data, 4));
541
+ break;
542
+ case MULBLANK: {
543
+ const row = u16(data, 0);
544
+ const first = u16(data, 2);
545
+ const count = Math.floor((data.length - 6) / 2);
546
+ for (let i = 0; i < count; i += 1)
547
+ look(row, first + i, u16(data, 4 + i * 2));
548
+ break;
549
+ }
550
+ case BOOLERR: {
551
+ const row = u16(data, 0);
552
+ const col = u16(data, 2);
553
+ look(row, col, u16(data, 4));
554
+ put(row, col, u8(data, 7) ? xlsErrorText(u8(data, 6)) : u8(data, 6) ? 'TRUE' : 'FALSE');
555
+ break;
556
+ }
557
+ case FORMULA: {
558
+ const row = u16(data, 0);
559
+ const col = u16(data, 2);
560
+ const ixfe = u16(data, 4);
561
+ look(row, col, ixfe);
562
+ if (spilled(row, col)) {
563
+ pending = null;
564
+ break;
565
+ }
566
+ const cce = u16(data, 20);
567
+ let rpn = data.subarray(22, 22 + cce);
568
+ // A cell that points at a shared or array formula: the tokens are
569
+ // the anchor's, read as if they were written here.
570
+ if (rpn.length >= 5 && rpn[0] === 0x01) {
571
+ const found = anchored.get(`${u16(rpn, 1)},${u16(rpn, 3)}`);
572
+ rpn = found ? found.rpn : new Uint8Array(0);
573
+ }
574
+ const ctx = { sheetAt, nameAt, row, col };
575
+ const text = rpn.length ? formulaFromRpn(rpn, ctx) : null;
576
+ if (text) {
577
+ put(row, col, `=${text}`);
578
+ pending = null;
579
+ break;
580
+ }
581
+ // A formula this reader cannot spell keeps the value the file
582
+ // cached for it, which is what the sheet showed.
583
+ if (u16(data, 12) === 0xffff) {
584
+ const kind = u8(data, 6);
585
+ if (kind === 0) {
586
+ pending = { row, col };
587
+ break;
588
+ }
589
+ if (kind === 1)
590
+ put(row, col, u8(data, 8) ? 'TRUE' : 'FALSE');
591
+ else if (kind === 2)
592
+ put(row, col, xlsErrorText(u8(data, 8)));
593
+ pending = null;
594
+ break;
595
+ }
596
+ put(row, col, numberText(f64(data, 6), ixfe));
597
+ pending = null;
598
+ break;
599
+ }
600
+ case STRING_RECORD:
601
+ if (pending)
602
+ put(pending.row, pending.col, asText(unicodeString(data, 0).text));
603
+ pending = null;
604
+ break;
605
+ case MERGEDCELLS: {
606
+ const count = u16(data, 0);
607
+ for (let i = 0; i < count; i += 1) {
608
+ const at = 2 + i * 8;
609
+ entry.merges.push([u16(data, at), u16(data, at + 4), u16(data, at + 2), u16(data, at + 6)]);
610
+ }
611
+ break;
612
+ }
613
+ case WINDOW2:
614
+ if ((u16(data, 0) & 0x08) === 0)
615
+ entry.freeze = { rows: 0, cols: 0 };
616
+ break;
617
+ case PANE:
618
+ entry.freeze = { rows: u16(data, 2), cols: u16(data, 0) };
619
+ break;
620
+ case PROTECT:
621
+ if (u16(data, 0))
622
+ entry.protected = true;
623
+ break;
624
+ default:
625
+ break;
626
+ }
627
+ }
628
+ // A pane is written whether or not it is frozen; only WINDOW2 says which.
629
+ const window2 = body.find((r) => r.type === WINDOW2);
630
+ if (window2 && (u16(window2.data, 0) & 0x08) === 0)
631
+ entry.freeze = { rows: 0, cols: 0 };
632
+ sheets.push({ name, cells });
633
+ entries[name] = entry;
634
+ });
635
+ if (!sheets.length) {
636
+ sheets.push({ name: 'Sheet1', cells: [] });
637
+ entries.Sheet1 = {
638
+ formats: {}, columnWidths: {}, rowHeights: [], hidden: { rows: [], cols: [] },
639
+ freeze: { rows: 0, cols: 0 }, comments: {}, protected: false,
640
+ merges: [], validation: [], conditionalFormats: [], autoFilter: null,
641
+ };
642
+ }
643
+ return { version: 1, workbook: { sheets, active: sheets[0].name, names }, sheets: entries };
644
+ }
645
+ /** Whether these bytes are an .xls at all, which is how the opener tells one. */
646
+ export const isXlsFile = (bytes) => isCompoundFile(bytes);
647
+ /** The document an .xls file holds, as a saved state. */
648
+ export async function documentFromXls(file) {
649
+ const bytes = file instanceof Uint8Array
650
+ ? file
651
+ : new Uint8Array(file instanceof ArrayBuffer ? file : await file.arrayBuffer());
652
+ return sheetStateFromXls(bytes);
653
+ }
654
+ // ---------------------------------------------------------------------------
655
+ // Writing.
656
+ /** The most a record's payload can hold; past this it runs on in a CONTINUE. */
657
+ const MAX_PAYLOAD = 8224;
658
+ class Stream {
659
+ constructor() {
660
+ this.bytes = [];
661
+ }
662
+ byte(v) { this.bytes.push(v & 0xff); }
663
+ word(v) { this.bytes.push(v & 0xff, (v >> 8) & 0xff); }
664
+ long(v) { this.word(v & 0xffff); this.word((v >>> 16) & 0xffff); }
665
+ double(v) {
666
+ const view = new DataView(new ArrayBuffer(8));
667
+ view.setFloat64(0, v, true);
668
+ for (let i = 0; i < 8; i += 1)
669
+ this.bytes.push(view.getUint8(i));
670
+ }
671
+ put(values) { for (let i = 0; i < values.length; i += 1)
672
+ this.bytes.push(values[i] & 0xff); }
673
+ /** A record: its type, its length, its payload, and a CONTINUE for the rest. */
674
+ record(type, payload) {
675
+ let at = 0;
676
+ do {
677
+ const take = Math.min(MAX_PAYLOAD, payload.length - at);
678
+ this.word(at === 0 ? type : CONTINUE);
679
+ this.word(take);
680
+ for (let i = 0; i < take; i += 1)
681
+ this.bytes.push(payload[at + i] & 0xff);
682
+ at += take;
683
+ } while (at < payload.length);
684
+ }
685
+ get length() { return this.bytes.length; }
686
+ }
687
+ /** A short string as a record holds one: a length byte, a flag, the characters. */
688
+ function shortStringBytes(text) {
689
+ const wide = [...text].some((c) => c.charCodeAt(0) > 0xff);
690
+ const out = [Math.min(text.length, 255), wide ? 1 : 0];
691
+ for (const ch of text.slice(0, 255)) {
692
+ const code = ch.charCodeAt(0);
693
+ if (wide)
694
+ out.push(code & 0xff, (code >> 8) & 0xff);
695
+ else
696
+ out.push(code & 0xff);
697
+ }
698
+ return out;
699
+ }
700
+ /** The same, with a two-byte length: what a STRING record holds. */
701
+ function longStringBytes(text) {
702
+ const clipped = text.slice(0, 32767);
703
+ const wide = [...clipped].some((c) => c.charCodeAt(0) > 0xff);
704
+ const out = [clipped.length & 0xff, (clipped.length >> 8) & 0xff, wide ? 1 : 0];
705
+ for (const ch of clipped) {
706
+ const code = ch.charCodeAt(0);
707
+ if (wide)
708
+ out.push(code & 0xff, (code >> 8) & 0xff);
709
+ else
710
+ out.push(code & 0xff);
711
+ }
712
+ return out;
713
+ }
714
+ /**
715
+ * The shared string table, written the way it is read: one run of bytes per
716
+ * record, and a string that does not fit what is left of one carries on in
717
+ * the next with its flag byte repeated.
718
+ */
719
+ function sstRecords(strings, out) {
720
+ if (!strings.length)
721
+ return;
722
+ const blocks = [];
723
+ let block = [];
724
+ const room = () => MAX_PAYLOAD - block.length;
725
+ const flush = () => { blocks.push(block); block = []; };
726
+ block.push(strings.length & 0xff, (strings.length >> 8) & 0xff, 0, 0);
727
+ block.push(strings.length & 0xff, (strings.length >> 8) & 0xff, 0, 0);
728
+ for (const text of strings) {
729
+ const clipped = text.slice(0, 32767);
730
+ const wide = [...clipped].some((c) => c.charCodeAt(0) > 0xff);
731
+ const width = wide ? 2 : 1;
732
+ // The length and the flag byte are never split.
733
+ if (room() < 4)
734
+ flush();
735
+ block.push(clipped.length & 0xff, (clipped.length >> 8) & 0xff, wide ? 1 : 0);
736
+ for (const ch of clipped) {
737
+ if (room() < width) {
738
+ flush();
739
+ block.push(wide ? 1 : 0);
740
+ }
741
+ const code = ch.charCodeAt(0);
742
+ if (wide)
743
+ block.push(code & 0xff, (code >> 8) & 0xff);
744
+ else
745
+ block.push(code & 0xff);
746
+ }
747
+ }
748
+ flush();
749
+ blocks.forEach((data, index) => { out.record(index === 0 ? SST : CONTINUE, data); });
750
+ }
751
+ /** The colours a workbook uses, as the indexed palette BIFF8 wants. */
752
+ function palette() {
753
+ const colours = [...PALETTE_DEFAULT];
754
+ /** Where a new colour may go: the first eight are the standard ones, which
755
+ * a file is expected to keep. */
756
+ let next = 8;
757
+ const index = (colour) => {
758
+ if (!colour)
759
+ return null;
760
+ const m = /^#?([0-9a-f]{6})$/i.exec(colour.trim());
761
+ const rgb = m
762
+ ? m[1].toLowerCase()
763
+ : /^#([0-9a-f]{3})$/i.test(colour.trim())
764
+ ? colour.trim().slice(1).split('').map((c) => c + c).join('').toLowerCase()
765
+ : null;
766
+ if (!rgb)
767
+ return null;
768
+ const found = colours.indexOf(rgb);
769
+ if (found >= 0)
770
+ return found + PALETTE_BASE;
771
+ if (next >= colours.length)
772
+ return null;
773
+ colours[next] = rgb;
774
+ next += 1;
775
+ return next - 1 + PALETTE_BASE;
776
+ };
777
+ const record = () => {
778
+ const out = [colours.length & 0xff, (colours.length >> 8) & 0xff];
779
+ for (const rgb of colours) {
780
+ out.push(parseInt(rgb.slice(0, 2), 16), parseInt(rgb.slice(2, 4), 16), parseInt(rgb.slice(4, 6), 16), 0);
781
+ }
782
+ return out;
783
+ };
784
+ return { index, record };
785
+ }
786
+ /** Excel's line style for a border, by how thick and how dashed it is. */
787
+ function lineStyle(spec) {
788
+ const width = spec.width ?? 2;
789
+ if (spec.style === 'double')
790
+ return 6;
791
+ if (spec.style === 'dotted')
792
+ return 4;
793
+ if (spec.style === 'dashed')
794
+ return width >= 2 ? 8 : 3;
795
+ return width >= 3 ? 5 : width >= 2 ? 2 : 1;
796
+ }
797
+ /** The fonts, number formats and cell styles of a workbook, deduplicated. */
798
+ function styleTable() {
799
+ const colours = palette();
800
+ const fonts = [{}];
801
+ const fontKeys = new Map([[JSON.stringify({}), 0]]);
802
+ const formats = new Map();
803
+ const xfKeys = new Map();
804
+ const xfs = [];
805
+ const fontOf = (entry) => {
806
+ const font = {};
807
+ for (const key of ['bold', 'italic', 'underline', 'strike', 'color', 'fontFamily', 'fontSize']) {
808
+ const value = entry[key];
809
+ if (value !== undefined)
810
+ Object.assign(font, { [key]: value });
811
+ }
812
+ const key = JSON.stringify(font);
813
+ let index = fontKeys.get(key);
814
+ if (index === undefined) {
815
+ index = fonts.length;
816
+ fonts.push(font);
817
+ fontKeys.set(key, index);
818
+ }
819
+ return index;
820
+ };
821
+ const formatOf = (code) => {
822
+ if (!code || /^General$/i.test(code))
823
+ return 0;
824
+ const builtin = Object.entries(BUILTIN_NUMFMT).find(([, c]) => c === code);
825
+ if (builtin)
826
+ return Number(builtin[0]);
827
+ let id = formats.get(code);
828
+ if (id === undefined) {
829
+ id = 164 + formats.size;
830
+ formats.set(code, id);
831
+ }
832
+ return id;
833
+ };
834
+ /** The XF index for a look, counting the fifteen style records in front. */
835
+ const xfOf = (entry) => {
836
+ if (!entry || !Object.keys(entry).length)
837
+ return 15;
838
+ const key = JSON.stringify(entry);
839
+ let index = xfKeys.get(key);
840
+ if (index === undefined) {
841
+ index = xfs.length;
842
+ xfs.push(entry);
843
+ xfKeys.set(key, index);
844
+ fontOf(entry);
845
+ formatOf(entry.numFmt);
846
+ colours.index(entry.color);
847
+ colours.index(entry.fill);
848
+ for (const side of ['top', 'right', 'bottom', 'left'])
849
+ colours.index(entry.border?.[side]?.color);
850
+ }
851
+ return index + 16;
852
+ };
853
+ const write = (out) => {
854
+ // A font record for each look, with the gap at index four Excel leaves.
855
+ fonts.forEach((font, index) => {
856
+ if (index === 4)
857
+ writeFont(out, font, colours);
858
+ writeFont(out, font, colours);
859
+ });
860
+ for (const [code, id] of formats)
861
+ out.record(FORMAT, [id & 0xff, (id >> 8) & 0xff, ...longStringBytes(code)]);
862
+ // Fifteen style records, then the plain cell style, then the looks.
863
+ for (let i = 0; i < 15; i += 1)
864
+ out.record(XF, xfBytes({}, 0, 0, true, colours));
865
+ out.record(XF, xfBytes({}, 0, 0, false, colours));
866
+ for (const entry of xfs) {
867
+ const fontIndex = fontOf(entry);
868
+ out.record(XF, xfBytes(entry, fontIndex >= 4 ? fontIndex + 1 : fontIndex, formatOf(entry.numFmt), false, colours));
869
+ }
870
+ out.record(STYLE, [0x00, 0x80, 0x00, 0xff]);
871
+ out.record(PALETTE, colours.record());
872
+ };
873
+ return { xfOf, write };
874
+ }
875
+ function writeFont(out, font, colours) {
876
+ const body = [];
877
+ const twips = Math.round((font.fontSize ? pxToPt(font.fontSize) : 11) * 20);
878
+ body.push(twips & 0xff, (twips >> 8) & 0xff);
879
+ const grbit = (font.italic ? 0x02 : 0) | (font.strike ? 0x08 : 0);
880
+ body.push(grbit, 0);
881
+ const colour = colours.index(font.color) ?? 0x7fff;
882
+ body.push(colour & 0xff, (colour >> 8) & 0xff);
883
+ const weight = font.bold ? 700 : 400;
884
+ body.push(weight & 0xff, (weight >> 8) & 0xff);
885
+ body.push(0, 0); // not super- or subscript
886
+ body.push(font.underline ? 1 : 0, 0, 0, 0);
887
+ body.push(...shortStringBytes(font.fontFamily ?? 'Calibri'));
888
+ out.record(FONT, body);
889
+ }
890
+ /** One XF record: the font, the format, and everything about the cell's look. */
891
+ function xfBytes(entry, fontIndex, formatId, style, colours) {
892
+ const out = [];
893
+ out.push(fontIndex & 0xff, (fontIndex >> 8) & 0xff);
894
+ out.push(formatId & 0xff, (formatId >> 8) & 0xff);
895
+ // Locked unless the cell says otherwise; a style record is its own parent.
896
+ const grbit = (entry.locked === false ? 0 : 0x01) | (style ? 0x04 | 0xfff0 : 0);
897
+ out.push(grbit & 0xff, (grbit >> 8) & 0xff);
898
+ const align = entry.align === 'left' ? 1 : entry.align === 'center' ? 2 : entry.align === 'right' ? 3 : 0;
899
+ out.push(align | (entry.wrap ? 0x08 : 0) | 0x20, 0);
900
+ out.push((entry.indent ?? 0) & 0x0f, 0);
901
+ const line = (spec) => spec ? lineStyle(spec) : 0;
902
+ const shade = (spec) => (spec && colours.index(spec.color)) || 0x40;
903
+ const left = entry.border?.left;
904
+ const right = entry.border?.right;
905
+ const top = entry.border?.top;
906
+ const bottom = entry.border?.bottom;
907
+ const borders = (line(left) | (line(right) << 4) | (line(top) << 8) | (line(bottom) << 12)
908
+ | (shade(left) << 16) | (shade(right) << 23)) >>> 0;
909
+ out.push(borders & 0xff, (borders >>> 8) & 0xff, (borders >>> 16) & 0xff, (borders >>> 24) & 0xff);
910
+ const fill = colours.index(entry.fill);
911
+ const second = ((shade(top) | (shade(bottom) << 7)) | ((fill === null ? 0 : 1) << 26)) >>> 0;
912
+ out.push(second & 0xff, (second >>> 8) & 0xff, (second >>> 16) & 0xff, (second >>> 24) & 0xff);
913
+ const pattern = ((fill ?? 0x40) & 0x7f) | ((0x41 & 0x7f) << 7);
914
+ out.push(pattern & 0xff, (pattern >> 8) & 0xff);
915
+ return out;
916
+ }
917
+ /** The last row and column Excel 97-2003 has; anything past them is dropped. */
918
+ const XLS_ROWS = 65536;
919
+ const XLS_COLS = 256;
920
+ /** The bytes of an .xls holding this document. */
921
+ export function documentToXls(doc) {
922
+ const wb = doc.workbook;
923
+ const styles = styleTable();
924
+ const strings = [];
925
+ const stringIds = new Map();
926
+ const sstOf = (text) => {
927
+ let id = stringIds.get(text);
928
+ if (id === undefined) {
929
+ id = strings.length;
930
+ strings.push(text);
931
+ stringIds.set(text, id);
932
+ }
933
+ return id;
934
+ };
935
+ const sheetNames = wb.sheets.slice(0, 255);
936
+ const sheetRef = (name) => {
937
+ const found = sheetNames.findIndex((s) => s.toLowerCase() === name.toLowerCase());
938
+ return found < 0 ? 0 : found;
939
+ };
940
+ const defined = wb.names.list();
941
+ const nameRef = (name) => {
942
+ const found = defined.findIndex((d) => d.name.toLowerCase() === name.toLowerCase());
943
+ return found < 0 ? null : found + 1;
944
+ };
945
+ const formulaCtx = { sheetRef, nameRef };
946
+ // Every sheet's cells, worked out before anything is written: the strings
947
+ // and the looks they use have to be in the globals, which come first.
948
+ const planned = sheetNames.map((name) => {
949
+ const state = doc.get(name);
950
+ const formats = state.formats.serialize();
951
+ const looks = new Map();
952
+ for (const [key, entry] of Object.entries(formats)) {
953
+ const at = splitKey(key);
954
+ if (at)
955
+ looks.set(`${at.row},${at.col}`, entry);
956
+ }
957
+ let maxRow = Math.min(wb.rowCount(name), XLS_ROWS) - 1;
958
+ let maxCol = Math.min(wb.colCount(name), XLS_COLS) - 1;
959
+ for (const key of looks.keys()) {
960
+ const [r, c] = key.split(',').map(Number);
961
+ if (r < XLS_ROWS)
962
+ maxRow = Math.max(maxRow, r);
963
+ if (c < XLS_COLS)
964
+ maxCol = Math.max(maxCol, c);
965
+ }
966
+ for (const r of state.heights.keys())
967
+ if (r < XLS_ROWS)
968
+ maxRow = Math.max(maxRow, r);
969
+ for (const r of state.hidden.rows)
970
+ if (r < XLS_ROWS)
971
+ maxRow = Math.max(maxRow, r);
972
+ const cells = [];
973
+ for (let row = 0; row <= maxRow; row += 1) {
974
+ for (let col = 0; col <= maxCol; col += 1) {
975
+ const raw = wb.getRaw(name, row, col);
976
+ const look = looks.get(`${row},${col}`);
977
+ if (raw === '' && !look)
978
+ continue;
979
+ const text = raw.trim();
980
+ const value = text === '' ? '' : wb.getValue(name, row, col);
981
+ // A date is stored here as its ISO text, and Excel has no such cell:
982
+ // it goes out as the number of days Excel counts, and the cell is
983
+ // given a date format when it carries none, so it still reads as a
984
+ // date rather than as forty-six thousand.
985
+ const serial = text.startsWith('=') || raw.startsWith(TEXT_PREFIX) ? null : isoToSerial(text);
986
+ const withDate = serial !== null && !isDateFormat(look?.numFmt)
987
+ ? { ...(look ?? {}), numFmt: BUILTIN_NUMFMT[14] }
988
+ : look;
989
+ const ixfe = styles.xfOf(withDate);
990
+ if (text === '') {
991
+ cells.push({ kind: 'blank', row, col, ixfe });
992
+ continue;
993
+ }
994
+ if (text.startsWith('=')) {
995
+ const rpn = rpnFromFormula(text, formulaCtx);
996
+ if (rpn) {
997
+ cells.push({ kind: 'formula', row, col, ixfe, rpn, value });
998
+ continue;
999
+ }
1000
+ // A formula BIFF8 has no words for keeps the value it worked out.
1001
+ }
1002
+ if (raw.startsWith(TEXT_PREFIX)) {
1003
+ cells.push({ kind: 'string', row, col, ixfe, sst: sstOf(raw.slice(1)) });
1004
+ continue;
1005
+ }
1006
+ if (serial !== null) {
1007
+ cells.push({ kind: 'number', row, col, ixfe, value: serial });
1008
+ continue;
1009
+ }
1010
+ if (isError(value)) {
1011
+ cells.push({ kind: 'error', row, col, ixfe, code: xlsErrorCode(value.error) });
1012
+ continue;
1013
+ }
1014
+ if (typeof value === 'number') {
1015
+ cells.push({ kind: 'number', row, col, ixfe, value });
1016
+ continue;
1017
+ }
1018
+ if (typeof value === 'boolean') {
1019
+ cells.push({ kind: 'bool', row, col, ixfe, value });
1020
+ continue;
1021
+ }
1022
+ cells.push({ kind: 'string', row, col, ixfe, sst: sstOf(String(value === '' ? raw : value)) });
1023
+ }
1024
+ }
1025
+ // The rows the AutoFilter folds away go out hidden: BIFF has AUTOFILTER
1026
+ // records this does not write, so they come back hidden by hand, which
1027
+ // shows the same rows.
1028
+ const filtered = filteredRows(doc, name);
1029
+ return { name, state, cells, maxRow, maxCol, filtered };
1030
+ });
1031
+ // The globals: what every sheet shares.
1032
+ const globals = new Stream();
1033
+ globals.record(BOF, [0x00, 0x06, 0x05, 0x00, 0xd3, 0x10, 0xcc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00]);
1034
+ globals.record(CODEPAGE, [0xb0, 0x04]);
1035
+ globals.record(DATEMODE, [0x00, 0x00]);
1036
+ globals.record(COUNTRY, [0x01, 0x00, 0x01, 0x00]);
1037
+ styles.write(globals);
1038
+ sstRecords(strings, globals);
1039
+ // One window over the workbook, saying which sheet opens.
1040
+ const active = Math.max(0, sheetNames.indexOf(wb.active));
1041
+ globals.record(WINDOW1, [
1042
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x38, 0x00, 0x25, 0x38, 0x00,
1043
+ active & 0xff, (active >> 8) & 0xff, active & 0xff, (active >> 8) & 0xff,
1044
+ 0x01, 0x00, 0x58, 0x02,
1045
+ ]);
1046
+ // A workbook of its own, so a formula on one sheet can name another.
1047
+ globals.record(SUPBOOK, [sheetNames.length & 0xff, (sheetNames.length >> 8) & 0xff, 0x01, 0x04]);
1048
+ const externsheet = [sheetNames.length & 0xff, (sheetNames.length >> 8) & 0xff];
1049
+ sheetNames.forEach((_, index) => { externsheet.push(0, 0, index & 0xff, (index >> 8) & 0xff, index & 0xff, (index >> 8) & 0xff); });
1050
+ globals.record(EXTERNSHEET, externsheet);
1051
+ for (const entry of defined) {
1052
+ const rpn = rpnFromFormula(String(entry.refersTo), formulaCtx);
1053
+ if (!rpn)
1054
+ continue;
1055
+ const name = shortStringBytes(entry.name);
1056
+ globals.record(NAME, [
1057
+ 0x00, 0x00, 0x00, name[0], rpn.length & 0xff, (rpn.length >> 8) & 0xff,
1058
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1059
+ ...name.slice(1), ...rpn,
1060
+ ]);
1061
+ }
1062
+ // Where each sheet's records start is written into its BOUNDSHEET, which
1063
+ // is in front of them: the place is noted now and filled in at the end.
1064
+ const offsets = [];
1065
+ planned.forEach(({ name, state }) => {
1066
+ globals.word(BOUNDSHEET);
1067
+ const body = [0, 0, 0, 0, state.sheetHidden ? 0x01 : 0x00, 0x00, ...shortStringBytes(name)];
1068
+ globals.word(body.length);
1069
+ offsets.push(globals.length);
1070
+ globals.put(body);
1071
+ });
1072
+ globals.record(EOF_RECORD, []);
1073
+ const sheets = planned.map(sheetStream);
1074
+ let at = globals.length;
1075
+ offsets.forEach((position, index) => {
1076
+ const value = at;
1077
+ globals.bytes[position] = value & 0xff;
1078
+ globals.bytes[position + 1] = (value >>> 8) & 0xff;
1079
+ globals.bytes[position + 2] = (value >>> 16) & 0xff;
1080
+ globals.bytes[position + 3] = (value >>> 24) & 0xff;
1081
+ at += sheets[index].length;
1082
+ });
1083
+ const workbook = new Uint8Array(globals.length + sheets.reduce((sum, s) => sum + s.length, 0));
1084
+ workbook.set(Uint8Array.from(globals.bytes), 0);
1085
+ let written = globals.length;
1086
+ for (const sheet of sheets) {
1087
+ workbook.set(Uint8Array.from(sheet.bytes), written);
1088
+ written += sheet.length;
1089
+ }
1090
+ return writeCompoundFile([{ name: 'Workbook', data: workbook }]);
1091
+ }
1092
+ /** A format store key back into a row and a column. */
1093
+ function splitKey(key) {
1094
+ const gap = key.indexOf(' ');
1095
+ if (gap < 0)
1096
+ return null;
1097
+ const row = Number(decodeURIComponent(key.slice(0, gap)).slice(1));
1098
+ const col = lettersToCol(decodeURIComponent(key.slice(gap + 1)));
1099
+ return Number.isInteger(row) && row >= 0 && col >= 0 ? { row, col } : null;
1100
+ }
1101
+ /** One sheet's own run of records. */
1102
+ function sheetStream(sheet) {
1103
+ const { state, cells, maxRow, maxCol, filtered } = sheet;
1104
+ const out = new Stream();
1105
+ out.record(BOF, [0x00, 0x06, 0x10, 0x00, 0xd3, 0x10, 0xcc, 0x07, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00]);
1106
+ out.record(DEFAULTROWHEIGHT, [0x00, 0x00, 0xff, 0x00]);
1107
+ out.record(DEFCOLWIDTH, [0x08, 0x00]);
1108
+ const widths = new Set();
1109
+ for (const letter of Object.keys(state.widths)) {
1110
+ const c = lettersToCol(letter);
1111
+ if (c >= 0 && c < XLS_COLS)
1112
+ widths.add(c);
1113
+ }
1114
+ for (const c of state.hidden.cols)
1115
+ if (c < XLS_COLS)
1116
+ widths.add(c);
1117
+ for (const col of [...widths].sort((a, b) => a - b)) {
1118
+ const px = state.widths[colToLetters(col)];
1119
+ const units = Math.round((px === undefined ? 64 : px) / 7 * 256);
1120
+ const grbit = state.hidden.cols.has(col) ? 0x01 : 0x00;
1121
+ out.record(COLINFO, [
1122
+ col & 0xff, (col >> 8) & 0xff, col & 0xff, (col >> 8) & 0xff,
1123
+ units & 0xff, (units >> 8) & 0xff, 15, 0, grbit, 0, 0, 0,
1124
+ ]);
1125
+ }
1126
+ out.record(DIMENSIONS, [
1127
+ 0, 0, 0, 0,
1128
+ (maxRow + 1) & 0xff, ((maxRow + 1) >> 8) & 0xff, ((maxRow + 1) >>> 16) & 0xff, ((maxRow + 1) >>> 24) & 0xff,
1129
+ 0, 0, (maxCol + 1) & 0xff, ((maxCol + 1) >> 8) & 0xff, 0, 0,
1130
+ ]);
1131
+ // The rows that hold something, then the cells: a reader wants the row
1132
+ // records in front of the cells they describe.
1133
+ const rowsWithCells = new Set(cells.map((c) => c.row));
1134
+ for (const row of state.heights.keys())
1135
+ if (row < XLS_ROWS)
1136
+ rowsWithCells.add(row);
1137
+ for (const row of state.hidden.rows)
1138
+ if (row < XLS_ROWS)
1139
+ rowsWithCells.add(row);
1140
+ for (const row of filtered)
1141
+ if (row < XLS_ROWS)
1142
+ rowsWithCells.add(row);
1143
+ for (const row of [...rowsWithCells].sort((a, b) => a - b)) {
1144
+ const px = state.heights.get(row);
1145
+ const twips = px === undefined ? 255 : Math.round(pxToPt(px) * 20);
1146
+ const grbit = 0x0100 | (state.hidden.rows.has(row) || filtered.has(row) ? 0x20 : 0) | (px === undefined ? 0 : 0x40);
1147
+ out.record(ROW, [
1148
+ row & 0xff, (row >> 8) & 0xff, 0, 0, (maxCol + 1) & 0xff, ((maxCol + 1) >> 8) & 0xff,
1149
+ twips & 0xff, (twips >> 8) & 0xff, 0, 0, 0, 0,
1150
+ grbit & 0xff, (grbit >> 8) & 0xff, 15, 0,
1151
+ ]);
1152
+ }
1153
+ for (const cell of cells) {
1154
+ const head = [cell.row & 0xff, (cell.row >> 8) & 0xff, cell.col & 0xff, (cell.col >> 8) & 0xff, cell.ixfe & 0xff, (cell.ixfe >> 8) & 0xff];
1155
+ switch (cell.kind) {
1156
+ case 'blank':
1157
+ out.record(BLANK, head);
1158
+ break;
1159
+ case 'string':
1160
+ out.record(LABELSST, [...head, cell.sst & 0xff, (cell.sst >>> 8) & 0xff, (cell.sst >>> 16) & 0xff, (cell.sst >>> 24) & 0xff]);
1161
+ break;
1162
+ case 'bool':
1163
+ out.record(BOOLERR, [...head, cell.value ? 1 : 0, 0]);
1164
+ break;
1165
+ case 'error':
1166
+ out.record(BOOLERR, [...head, cell.code, 1]);
1167
+ break;
1168
+ case 'number': {
1169
+ const rk = rkBytes(cell.value);
1170
+ if (rk === null) {
1171
+ const body = new Stream();
1172
+ body.put(head);
1173
+ body.double(cell.value);
1174
+ out.record(NUMBER, body.bytes);
1175
+ }
1176
+ else {
1177
+ out.record(RK, [...head, rk & 0xff, (rk >>> 8) & 0xff, (rk >>> 16) & 0xff, (rk >>> 24) & 0xff]);
1178
+ }
1179
+ break;
1180
+ }
1181
+ case 'formula': {
1182
+ const body = new Stream();
1183
+ body.put(head);
1184
+ // The value the sheet showed, so a reader has it before it
1185
+ // recalculates - and the shape that says which kind of value it is.
1186
+ let follows = null;
1187
+ if (isError(cell.value))
1188
+ body.put([0x02, 0x00, xlsErrorCode(cell.value.error), 0x00, 0x00, 0x00, 0xff, 0xff]);
1189
+ else if (typeof cell.value === 'boolean')
1190
+ body.put([0x01, 0x00, cell.value ? 1 : 0, 0x00, 0x00, 0x00, 0xff, 0xff]);
1191
+ else if (typeof cell.value === 'number')
1192
+ body.double(cell.value);
1193
+ else if (cell.value === '')
1194
+ body.put([0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff]);
1195
+ else {
1196
+ body.put([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff]);
1197
+ follows = String(cell.value);
1198
+ }
1199
+ body.word(0x0002); // work it out again on the way in
1200
+ body.long(0);
1201
+ body.word(cell.rpn.length);
1202
+ body.put(cell.rpn);
1203
+ out.record(FORMULA, body.bytes);
1204
+ if (follows !== null)
1205
+ out.record(STRING_RECORD, longStringBytes(follows));
1206
+ break;
1207
+ }
1208
+ }
1209
+ }
1210
+ if (state.merges.length) {
1211
+ const merges = state.merges.filter(([r1, c1, r2, c2]) => r2 < XLS_ROWS && c2 < XLS_COLS && r1 >= 0 && c1 >= 0);
1212
+ for (let at = 0; at < merges.length; at += 1024) {
1213
+ const block = merges.slice(at, at + 1024);
1214
+ const body = [block.length & 0xff, (block.length >> 8) & 0xff];
1215
+ for (const [r1, c1, r2, c2] of block) {
1216
+ body.push(r1 & 0xff, (r1 >> 8) & 0xff, r2 & 0xff, (r2 >> 8) & 0xff, c1 & 0xff, (c1 >> 8) & 0xff, c2 & 0xff, (c2 >> 8) & 0xff);
1217
+ }
1218
+ out.record(MERGEDCELLS, body);
1219
+ }
1220
+ }
1221
+ if (state.protected)
1222
+ out.record(PROTECT, [0x01, 0x00]);
1223
+ const frozen = state.freeze.rows > 0 || state.freeze.cols > 0;
1224
+ // Gridlines and headings on, the sheet selected, and frozen when it is:
1225
+ // a frozen pane is both "there is a split" and "the split does not move".
1226
+ const grbit = 0x06b6 | (frozen ? 0x0108 : 0);
1227
+ out.record(WINDOW2, [
1228
+ grbit & 0xff, (grbit >> 8) & 0xff, 0, 0, 0, 0, 0x40, 0x00, 0x00, 0x00,
1229
+ 0, 0, 0, 0, 0, 0, 0, 0,
1230
+ ]);
1231
+ if (frozen) {
1232
+ out.record(PANE, [
1233
+ state.freeze.cols & 0xff, (state.freeze.cols >> 8) & 0xff,
1234
+ state.freeze.rows & 0xff, (state.freeze.rows >> 8) & 0xff,
1235
+ state.freeze.rows & 0xff, (state.freeze.rows >> 8) & 0xff,
1236
+ state.freeze.cols & 0xff, (state.freeze.cols >> 8) & 0xff,
1237
+ 0x02,
1238
+ ]);
1239
+ }
1240
+ out.record(SELECTION, [0x03, 0, 0, 0, 0, 0, 0x01, 0x00, 0, 0, 0, 0, 0, 0, 0]);
1241
+ out.record(EOF_RECORD, []);
1242
+ return out;
1243
+ }