aicodeman 1.34.0 → 1.40.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 (299) hide show
  1. package/README.md +11 -0
  2. package/README.zh-CN.md +8 -0
  3. package/dist/attachment-registry.d.ts +6 -0
  4. package/dist/attachment-registry.d.ts.map +1 -1
  5. package/dist/attachment-registry.js +9 -3
  6. package/dist/attachment-registry.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +2 -2
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config/cli-registry/patterns.d.ts +6 -0
  11. package/dist/config/cli-registry/patterns.d.ts.map +1 -1
  12. package/dist/config/cli-registry/patterns.js +13 -0
  13. package/dist/config/cli-registry/patterns.js.map +1 -1
  14. package/dist/config/cli-registry/schema.d.ts +13 -0
  15. package/dist/config/cli-registry/schema.d.ts.map +1 -1
  16. package/dist/config/cli-registry/schema.js +60 -2
  17. package/dist/config/cli-registry/schema.js.map +1 -1
  18. package/dist/config/cli-registry/stock.d.ts.map +1 -1
  19. package/dist/config/cli-registry/stock.js +175 -7
  20. package/dist/config/cli-registry/stock.js.map +1 -1
  21. package/dist/config/cli-registry/types.d.ts +93 -6
  22. package/dist/config/cli-registry/types.d.ts.map +1 -1
  23. package/dist/config/dependency-registry.d.ts +7 -0
  24. package/dist/config/dependency-registry.d.ts.map +1 -1
  25. package/dist/config/dependency-registry.js +15 -0
  26. package/dist/config/dependency-registry.js.map +1 -1
  27. package/dist/config/path-probe.d.ts +37 -0
  28. package/dist/config/path-probe.d.ts.map +1 -0
  29. package/dist/config/path-probe.js +43 -0
  30. package/dist/config/path-probe.js.map +1 -0
  31. package/dist/deepseek-route-config.d.ts +110 -0
  32. package/dist/deepseek-route-config.d.ts.map +1 -0
  33. package/dist/deepseek-route-config.js +477 -0
  34. package/dist/deepseek-route-config.js.map +1 -0
  35. package/dist/git-workspace-status.d.ts +246 -0
  36. package/dist/git-workspace-status.d.ts.map +1 -0
  37. package/dist/git-workspace-status.js +626 -0
  38. package/dist/git-workspace-status.js.map +1 -0
  39. package/dist/hooks-config.d.ts.map +1 -1
  40. package/dist/hooks-config.js +83 -13
  41. package/dist/hooks-config.js.map +1 -1
  42. package/dist/model-config-resolvers.d.ts +29 -0
  43. package/dist/model-config-resolvers.d.ts.map +1 -0
  44. package/dist/model-config-resolvers.js +34 -0
  45. package/dist/model-config-resolvers.js.map +1 -0
  46. package/dist/session-display-model.d.ts +110 -0
  47. package/dist/session-display-model.d.ts.map +1 -0
  48. package/dist/session-display-model.js +154 -0
  49. package/dist/session-display-model.js.map +1 -0
  50. package/dist/session.d.ts +145 -8
  51. package/dist/session.d.ts.map +1 -1
  52. package/dist/session.js +280 -35
  53. package/dist/session.js.map +1 -1
  54. package/dist/types/api.d.ts +7 -0
  55. package/dist/types/api.d.ts.map +1 -1
  56. package/dist/types/api.js.map +1 -1
  57. package/dist/types/session.d.ts +28 -1
  58. package/dist/types/session.d.ts.map +1 -1
  59. package/dist/types/session.js.map +1 -1
  60. package/dist/types/tools.d.ts +1 -1
  61. package/dist/types/tools.d.ts.map +1 -1
  62. package/dist/utils/bounded-path-probe.d.ts +99 -0
  63. package/dist/utils/bounded-path-probe.d.ts.map +1 -0
  64. package/dist/utils/bounded-path-probe.js +228 -0
  65. package/dist/utils/bounded-path-probe.js.map +1 -0
  66. package/dist/utils/cli-executable-resolver.d.ts +2 -0
  67. package/dist/utils/cli-executable-resolver.d.ts.map +1 -1
  68. package/dist/utils/cli-executable-resolver.js +2 -1
  69. package/dist/utils/cli-executable-resolver.js.map +1 -1
  70. package/dist/utils/deepseek-cli-resolver.d.ts +8 -0
  71. package/dist/utils/deepseek-cli-resolver.d.ts.map +1 -1
  72. package/dist/utils/deepseek-cli-resolver.js +18 -2
  73. package/dist/utils/deepseek-cli-resolver.js.map +1 -1
  74. package/dist/utils/dependency-checker.d.ts +2 -0
  75. package/dist/utils/dependency-checker.d.ts.map +1 -1
  76. package/dist/utils/dependency-checker.js +37 -9
  77. package/dist/utils/dependency-checker.js.map +1 -1
  78. package/dist/utils/index.d.ts +2 -0
  79. package/dist/utils/index.d.ts.map +1 -1
  80. package/dist/utils/index.js +1 -0
  81. package/dist/utils/index.js.map +1 -1
  82. package/dist/web/case-path.d.ts +58 -0
  83. package/dist/web/case-path.d.ts.map +1 -0
  84. package/dist/web/case-path.js +182 -0
  85. package/dist/web/case-path.js.map +1 -0
  86. package/dist/web/launch-defaults.d.ts +22 -0
  87. package/dist/web/launch-defaults.d.ts.map +1 -0
  88. package/dist/web/launch-defaults.js +46 -0
  89. package/dist/web/launch-defaults.js.map +1 -0
  90. package/dist/web/public/admin-ui.js.gz +0 -0
  91. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  92. package/dist/web/public/app.abadc970.js +43 -0
  93. package/dist/web/public/app.abadc970.js.br +0 -0
  94. package/dist/web/public/app.abadc970.js.gz +0 -0
  95. package/dist/web/public/approvals-ui.js.gz +0 -0
  96. package/dist/web/public/{constants.d95fade9.js → constants.c0f8f9de.js} +1009 -127
  97. package/dist/web/public/constants.c0f8f9de.js.br +0 -0
  98. package/dist/web/public/constants.c0f8f9de.js.gz +0 -0
  99. package/dist/web/public/cron-ui.js.gz +0 -0
  100. package/dist/web/public/entrance-animations.js +1 -0
  101. package/dist/web/public/entrance-animations.js.br +0 -0
  102. package/dist/web/public/entrance-animations.js.gz +0 -0
  103. package/dist/web/public/git-status-ui.js +719 -0
  104. package/dist/web/public/git-status-ui.js.br +0 -0
  105. package/dist/web/public/git-status-ui.js.gz +0 -0
  106. package/dist/web/public/home-sessions.js +12 -9
  107. package/dist/web/public/home-sessions.js.br +0 -0
  108. package/dist/web/public/home-sessions.js.gz +0 -0
  109. package/dist/web/public/host-wake-ui.js.gz +0 -0
  110. package/dist/web/public/i18n.a31747a9.js +2 -0
  111. package/dist/web/public/i18n.a31747a9.js.br +0 -0
  112. package/dist/web/public/i18n.a31747a9.js.gz +0 -0
  113. package/dist/web/public/{image-input.a216cf9e.js → image-input.898a538b.js} +18 -8
  114. package/dist/web/public/image-input.898a538b.js.br +0 -0
  115. package/dist/web/public/image-input.898a538b.js.gz +0 -0
  116. package/dist/web/public/index.html +205 -42
  117. package/dist/web/public/index.html.br +0 -0
  118. package/dist/web/public/index.html.gz +0 -0
  119. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  120. package/dist/web/public/keyboard-accessory.c1e7c7b7.js.gz +0 -0
  121. package/dist/web/public/{mobile-handlers.23a85db1.js → mobile-handlers.0ef9c311.js} +20 -0
  122. package/dist/web/public/mobile-handlers.0ef9c311.js.br +0 -0
  123. package/dist/web/public/mobile-handlers.0ef9c311.js.gz +0 -0
  124. package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
  125. package/dist/web/public/mobile-overview.js.gz +0 -0
  126. package/dist/web/public/mobile.111b187d.css +1 -0
  127. package/dist/web/public/mobile.111b187d.css.br +0 -0
  128. package/dist/web/public/mobile.111b187d.css.gz +0 -0
  129. package/dist/web/public/notification-manager.50bb87d8.js.gz +0 -0
  130. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  131. package/dist/web/public/{panels-ui.6a5898d9.js → panels-ui.4735dfc2.js} +4 -4
  132. package/dist/web/public/panels-ui.4735dfc2.js.br +0 -0
  133. package/dist/web/public/{panels-ui.6a5898d9.js.gz → panels-ui.4735dfc2.js.gz} +0 -0
  134. package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
  135. package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
  136. package/dist/web/public/readmymind-ui.js.gz +0 -0
  137. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  138. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  139. package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
  140. package/dist/web/public/session-lineage.js +229 -86
  141. package/dist/web/public/session-lineage.js.br +0 -0
  142. package/dist/web/public/session-lineage.js.gz +0 -0
  143. package/dist/web/public/session-ui.f731602c.js +100 -0
  144. package/dist/web/public/session-ui.f731602c.js.br +0 -0
  145. package/dist/web/public/session-ui.f731602c.js.gz +0 -0
  146. package/dist/web/public/settings-ui.a97ab16f.js +97 -0
  147. package/dist/web/public/settings-ui.a97ab16f.js.br +0 -0
  148. package/dist/web/public/settings-ui.a97ab16f.js.gz +0 -0
  149. package/dist/web/public/spreadsheet-preview-worker.js +290 -0
  150. package/dist/web/public/spreadsheet-preview-worker.js.br +0 -0
  151. package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
  152. package/dist/web/public/spreadsheet-preview.af82fa2f.js +555 -0
  153. package/dist/web/public/spreadsheet-preview.af82fa2f.js.br +0 -0
  154. package/dist/web/public/spreadsheet-preview.af82fa2f.js.gz +0 -0
  155. package/dist/web/public/spreadsheet-xlsx-core.js +1006 -0
  156. package/dist/web/public/spreadsheet-xlsx-core.js.br +0 -0
  157. package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
  158. package/dist/web/public/styles.8ee12666.css +1 -0
  159. package/dist/web/public/styles.8ee12666.css.br +0 -0
  160. package/dist/web/public/styles.8ee12666.css.gz +0 -0
  161. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  162. package/dist/web/public/sw.js +2 -2
  163. package/dist/web/public/sw.js.br +0 -0
  164. package/dist/web/public/sw.js.gz +0 -0
  165. package/dist/web/public/tab-layout-browser.065fcba4.js +1 -0
  166. package/dist/web/public/tab-layout-browser.065fcba4.js.br +0 -0
  167. package/dist/web/public/tab-layout-browser.065fcba4.js.gz +0 -0
  168. package/dist/web/public/{tab-rail-resize.dcdc5772.js → tab-rail-resize.a5867793.js} +1 -1
  169. package/dist/web/public/tab-rail-resize.a5867793.js.br +0 -0
  170. package/dist/web/public/tab-rail-resize.a5867793.js.gz +0 -0
  171. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js +1 -0
  172. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.br +0 -0
  173. package/dist/web/public/terminal-keycode229-recovery.af09f99a.js.gz +0 -0
  174. package/dist/web/public/terminal-split.js +146 -649
  175. package/dist/web/public/terminal-split.js.br +0 -0
  176. package/dist/web/public/terminal-split.js.gz +0 -0
  177. package/dist/web/public/terminal-tile.js +1611 -0
  178. package/dist/web/public/terminal-tile.js.br +0 -0
  179. package/dist/web/public/terminal-tile.js.gz +0 -0
  180. package/dist/web/public/terminal-ui.5059b30e.js +2 -0
  181. package/dist/web/public/terminal-ui.5059b30e.js.br +0 -0
  182. package/dist/web/public/terminal-ui.5059b30e.js.gz +0 -0
  183. package/dist/web/public/tile-grid.js +2684 -0
  184. package/dist/web/public/tile-grid.js.br +0 -0
  185. package/dist/web/public/tile-grid.js.gz +0 -0
  186. package/dist/web/public/ultracode-panel.js.gz +0 -0
  187. package/dist/web/public/ultracode-windows.js.gz +0 -0
  188. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  189. package/dist/web/public/vendor/exceljs.min.js +44 -0
  190. package/dist/web/public/vendor/exceljs.min.js.br +0 -0
  191. package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
  192. package/dist/web/public/vendor/fflate.min.js +1 -0
  193. package/dist/web/public/vendor/fflate.min.js.br +0 -0
  194. package/dist/web/public/vendor/fflate.min.js.gz +0 -0
  195. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  196. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  197. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  198. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  199. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  200. package/dist/web/public/vendor/xterm-predictive-echo.ae5e3d5d.js.gz +0 -0
  201. package/dist/web/public/vendor/xterm-zerolag-input.bbb5f84c.js.gz +0 -0
  202. package/dist/web/public/vendor/xterm.css.gz +0 -0
  203. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  204. package/dist/web/public/{voice-input.c4b51eb6.js → voice-input.2342c37b.js} +64 -10
  205. package/dist/web/public/voice-input.2342c37b.js.br +0 -0
  206. package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
  207. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  208. package/dist/web/public/webview-tabs.js +9 -1
  209. package/dist/web/public/webview-tabs.js.br +0 -0
  210. package/dist/web/public/webview-tabs.js.gz +0 -0
  211. package/dist/web/routes/case-routes.d.ts.map +1 -1
  212. package/dist/web/routes/case-routes.js +136 -18
  213. package/dist/web/routes/case-routes.js.map +1 -1
  214. package/dist/web/routes/cli-registry-routes.d.ts.map +1 -1
  215. package/dist/web/routes/cli-registry-routes.js +3 -1
  216. package/dist/web/routes/cli-registry-routes.js.map +1 -1
  217. package/dist/web/routes/doctor-routes.d.ts +23 -0
  218. package/dist/web/routes/doctor-routes.d.ts.map +1 -0
  219. package/dist/web/routes/doctor-routes.js +83 -0
  220. package/dist/web/routes/doctor-routes.js.map +1 -0
  221. package/dist/web/routes/file-routes.d.ts +7 -0
  222. package/dist/web/routes/file-routes.d.ts.map +1 -1
  223. package/dist/web/routes/file-routes.js +34 -4
  224. package/dist/web/routes/file-routes.js.map +1 -1
  225. package/dist/web/routes/git-status-routes.d.ts +16 -0
  226. package/dist/web/routes/git-status-routes.d.ts.map +1 -0
  227. package/dist/web/routes/git-status-routes.js +98 -0
  228. package/dist/web/routes/git-status-routes.js.map +1 -0
  229. package/dist/web/routes/index.d.ts +2 -0
  230. package/dist/web/routes/index.d.ts.map +1 -1
  231. package/dist/web/routes/index.js +2 -0
  232. package/dist/web/routes/index.js.map +1 -1
  233. package/dist/web/routes/session-routes.d.ts +18 -0
  234. package/dist/web/routes/session-routes.d.ts.map +1 -1
  235. package/dist/web/routes/session-routes.js +106 -42
  236. package/dist/web/routes/session-routes.js.map +1 -1
  237. package/dist/web/routes/status-telemetry-routes.d.ts +3 -0
  238. package/dist/web/routes/status-telemetry-routes.d.ts.map +1 -1
  239. package/dist/web/routes/status-telemetry-routes.js +12 -0
  240. package/dist/web/routes/status-telemetry-routes.js.map +1 -1
  241. package/dist/web/routes/system-routes.d.ts.map +1 -1
  242. package/dist/web/routes/system-routes.js +13 -0
  243. package/dist/web/routes/system-routes.js.map +1 -1
  244. package/dist/web/schemas.d.ts +28 -0
  245. package/dist/web/schemas.d.ts.map +1 -1
  246. package/dist/web/schemas.js +41 -0
  247. package/dist/web/schemas.js.map +1 -1
  248. package/dist/web/server.d.ts +5 -0
  249. package/dist/web/server.d.ts.map +1 -1
  250. package/dist/web/server.js +35 -12
  251. package/dist/web/server.js.map +1 -1
  252. package/dist/web/session-listener-wiring.d.ts +1 -0
  253. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  254. package/dist/web/session-listener-wiring.js +11 -0
  255. package/dist/web/session-listener-wiring.js.map +1 -1
  256. package/package.json +3 -1
  257. package/scripts/postinstall.js +16 -0
  258. package/skills/codeman/reference/verbs.md +3 -0
  259. package/dist/web/public/app.cf0db8ce.js +0 -43
  260. package/dist/web/public/app.cf0db8ce.js.br +0 -0
  261. package/dist/web/public/app.cf0db8ce.js.gz +0 -0
  262. package/dist/web/public/constants.d95fade9.js.br +0 -0
  263. package/dist/web/public/constants.d95fade9.js.gz +0 -0
  264. package/dist/web/public/i18n.398b8331.js +0 -1
  265. package/dist/web/public/i18n.398b8331.js.br +0 -0
  266. package/dist/web/public/i18n.398b8331.js.gz +0 -0
  267. package/dist/web/public/image-input.a216cf9e.js.br +0 -0
  268. package/dist/web/public/image-input.a216cf9e.js.gz +0 -0
  269. package/dist/web/public/mobile-handlers.23a85db1.js.br +0 -0
  270. package/dist/web/public/mobile-handlers.23a85db1.js.gz +0 -0
  271. package/dist/web/public/mobile.1aa4952d.css +0 -1
  272. package/dist/web/public/mobile.1aa4952d.css.br +0 -0
  273. package/dist/web/public/mobile.1aa4952d.css.gz +0 -0
  274. package/dist/web/public/panels-ui.6a5898d9.js.br +0 -0
  275. package/dist/web/public/session-ui.bf66c13b.js +0 -89
  276. package/dist/web/public/session-ui.bf66c13b.js.br +0 -0
  277. package/dist/web/public/session-ui.bf66c13b.js.gz +0 -0
  278. package/dist/web/public/settings-ui.331d3b1d.js +0 -97
  279. package/dist/web/public/settings-ui.331d3b1d.js.br +0 -0
  280. package/dist/web/public/settings-ui.331d3b1d.js.gz +0 -0
  281. package/dist/web/public/styles.8c485d26.css +0 -1
  282. package/dist/web/public/styles.8c485d26.css.br +0 -0
  283. package/dist/web/public/styles.8c485d26.css.gz +0 -0
  284. package/dist/web/public/tab-layout-browser.4e0ed50c.js +0 -1
  285. package/dist/web/public/tab-layout-browser.4e0ed50c.js.br +0 -0
  286. package/dist/web/public/tab-layout-browser.4e0ed50c.js.gz +0 -0
  287. package/dist/web/public/tab-rail-resize.dcdc5772.js.br +0 -0
  288. package/dist/web/public/tab-rail-resize.dcdc5772.js.gz +0 -0
  289. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js +0 -1
  290. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.br +0 -0
  291. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
  292. package/dist/web/public/terminal-ui.a4535311.js +0 -2
  293. package/dist/web/public/terminal-ui.a4535311.js.br +0 -0
  294. package/dist/web/public/terminal-ui.a4535311.js.gz +0 -0
  295. package/dist/web/public/upload.html +0 -155
  296. package/dist/web/public/upload.html.br +0 -0
  297. package/dist/web/public/upload.html.gz +0 -0
  298. package/dist/web/public/voice-input.c4b51eb6.js.br +0 -0
  299. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
@@ -0,0 +1,1006 @@
1
+ /**
2
+ * @fileoverview Pure XLSX admission, formatting, and sparse-grid helpers.
3
+ *
4
+ * Loaded only inside spreadsheet-preview-worker.js (never by the page) and by
5
+ * test/spreadsheet-xlsx-core.test.ts. `admitXlsx()` walks the ZIP central
6
+ * directory and streams every entry through fflate BEFORE ExcelJS sees the
7
+ * bytes, enforcing {@link LIMITS}; a workbook that trips any cap is refused
8
+ * rather than truncated. It returns the entries it inflated, and the worker
9
+ * hands ExcelJS a STORE-only archive rebuilt from exactly those
10
+ * (`buildAdmittedArchive()`), never the original bytes: admission follows local
11
+ * headers while ExcelJS (JSZip) follows the central directory, so overlapping
12
+ * entries could otherwise show each reader a different file. Entries are
13
+ * counted and rebuilt under the name ExcelJS will see (`excelJsEntryName()`),
14
+ * never the stored spelling, so `/xl/...` or `xl/./...` cannot skip a counter.
15
+ */
16
+
17
+ (function initSpreadsheetXlsxCore(global) {
18
+ 'use strict';
19
+
20
+ const LIMITS = Object.freeze({
21
+ maxEntries: 5000,
22
+ maxInflatedBytes: 64 * 1024 * 1024,
23
+ maxEntryBytes: 32 * 1024 * 1024,
24
+ maxCompressionRatio: 100,
25
+ maxWorksheets: 50,
26
+ maxCells: 250000,
27
+ maxCellsPerSheet: 100000,
28
+ maxRows: 250000,
29
+ maxRowsPerSheet: 100000,
30
+ // ExcelJS's `_mergeCellsInternal` checks each new merge against every
31
+ // earlier one on its sheet, so a sheet costs the SQUARE of its merge count
32
+ // (5,000 on one sheet took 1.6 s). Both caps keep the worst case near 1.3 s.
33
+ maxMergesPerSheet: 2000,
34
+ maxMerges: 10000,
35
+ maxStyles: 5000,
36
+ // ExcelJS stores each sheet at `_worksheets[sheetId]`, so the id is an array
37
+ // length. Excel numbers sheets from 1 and never reuses an id, so real ids
38
+ // stay small; 65535 leaves room for heavy editing at a negligible cost.
39
+ maxSheetId: 65535,
40
+ // Excel's own limit on a number format. ExcelJS runs `isDateFmt` on the code
41
+ // once per numeric cell, and the code is echoed into the notice bar.
42
+ maxNumFmtChars: 255,
43
+ // Display text per cell. A cell is one `nowrap` line ending in an ellipsis,
44
+ // so nothing past the column width shows; uncapped, every tile cell carried
45
+ // its whole string to the page (a 1 MB shared string over a 60 x 20 block
46
+ // froze the page's main thread at 9.6 GB).
47
+ maxCellTextChars: 1000,
48
+ // Start tags across every part ExcelJS parses (all but xl/media). ExcelJS
49
+ // builds an object per element, and outside worksheets nothing else bounded
50
+ // them: one shared string of 8.3M empty `<r/>` runs (a 375 KB file, padded
51
+ // past the ratio cap with empty stored blocks) cost 570 MB of heap. A
52
+ // three-sheet 249k-cell workbook with rich text has about 589k.
53
+ maxElements: 2000000,
54
+ });
55
+ const MAX_ROW = 1048576;
56
+ const MAX_COL = 16384;
57
+
58
+ class XlsxPreviewError extends Error {
59
+ constructor(code, message) {
60
+ super(message);
61
+ this.name = 'XlsxPreviewError';
62
+ this.code = code;
63
+ }
64
+ }
65
+
66
+ function fail(code, message) {
67
+ throw new XlsxPreviewError(code, message);
68
+ }
69
+
70
+ function mergedLimits(overrides) {
71
+ return Object.assign({}, LIMITS, overrides || {});
72
+ }
73
+
74
+ function u16(bytes, offset) {
75
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint16(offset, true);
76
+ }
77
+
78
+ function u32(bytes, offset) {
79
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
80
+ }
81
+
82
+ /**
83
+ * The name ExcelJS will give a ZIP entry. JSZip resolves every name on load
84
+ * (`utils.resolve` in jszip/lib/utils.js, called from lib/load.js): `.` and
85
+ * empty middle segments are dropped and `..` pops the previous segment. ExcelJS
86
+ * then strips ONE leading `/` (lib/xlsx/xlsx.js, the `load` loop). Admission
87
+ * counts, and the admitted archive is rebuilt, under this name, so the
88
+ * stored spelling of a name cannot steer an entry past the counters.
89
+ */
90
+ function excelJsEntryName(name) {
91
+ const parts = String(name).split('/');
92
+ const resolved = [];
93
+ for (let index = 0; index < parts.length; index += 1) {
94
+ const part = parts[index];
95
+ // JSZip keeps an empty first or last segment (a leading or trailing `/`).
96
+ if (part === '.' || (part === '' && index !== 0 && index !== parts.length - 1)) continue;
97
+ if (part === '..') resolved.pop();
98
+ else resolved.push(part);
99
+ }
100
+ const joined = resolved.join('/');
101
+ return joined[0] === '/' ? joined.slice(1) : joined;
102
+ }
103
+
104
+ // ExcelJS's own worksheet test, copied verbatim (lib/xlsx/xlsx.js): it is
105
+ // UNANCHORED, so `xl/xl/worksheets/sheet1.xml` and `.../sheet1.xml.x` are
106
+ // worksheets too. The older anchored test stays as a conservative superset.
107
+ const EXCELJS_WORKSHEET = /xl\/worksheets\/sheet(\d+)[.]xml/;
108
+ function isWorksheetName(name) {
109
+ return EXCELJS_WORKSHEET.test(name) || /^xl\/worksheets\/[^/]+\.xml$/i.test(name);
110
+ }
111
+
112
+ function inspectZipDirectory(bytes, overrides) {
113
+ const limits = mergedLimits(overrides);
114
+ if (bytes.length >= 4 && bytes[0] === 0xd0 && bytes[1] === 0xcf && bytes[2] === 0x11 && bytes[3] === 0xe0) {
115
+ fail('encrypted', 'Encrypted or legacy OLE workbooks cannot be previewed');
116
+ }
117
+ let eocd = -1;
118
+ const floor = Math.max(0, bytes.length - 65557);
119
+ for (let i = bytes.length - 22; i >= floor; i -= 1) {
120
+ if (u32(bytes, i) === 0x06054b50) {
121
+ eocd = i;
122
+ break;
123
+ }
124
+ }
125
+ if (eocd < 0) fail('malformed', 'Malformed XLSX ZIP directory');
126
+ const entryCount = u16(bytes, eocd + 10);
127
+ const directorySize = u32(bytes, eocd + 12);
128
+ const directoryOffset = u32(bytes, eocd + 16);
129
+ if (entryCount === 0xffff || directorySize === 0xffffffff || directoryOffset === 0xffffffff) {
130
+ fail('zip64', 'ZIP64 workbooks are not supported');
131
+ }
132
+ if (entryCount > limits.maxEntries) fail('entry-limit', `Workbook exceeds ${limits.maxEntries} ZIP entries`);
133
+ if (directoryOffset + directorySize > eocd) fail('malformed', 'Malformed XLSX central directory bounds');
134
+ const entries = [];
135
+ const excelJsNames = new Set();
136
+ let cursor = directoryOffset;
137
+ for (let i = 0; i < entryCount; i += 1) {
138
+ if (cursor + 46 > eocd || u32(bytes, cursor) !== 0x02014b50)
139
+ fail('malformed', 'Malformed XLSX central directory');
140
+ const compressedSize = u32(bytes, cursor + 20);
141
+ const declaredSize = u32(bytes, cursor + 24);
142
+ const nameLength = u16(bytes, cursor + 28);
143
+ const extraLength = u16(bytes, cursor + 30);
144
+ const commentLength = u16(bytes, cursor + 32);
145
+ const localHeaderOffset = u32(bytes, cursor + 42);
146
+ if (compressedSize === 0xffffffff || declaredSize === 0xffffffff)
147
+ fail('zip64', 'ZIP64 entries are not supported');
148
+ const end = cursor + 46 + nameLength + extraLength + commentLength;
149
+ if (end > eocd) fail('malformed', 'Malformed XLSX entry bounds');
150
+ const name = new TextDecoder().decode(bytes.subarray(cursor + 46, cursor + 46 + nameLength));
151
+ if (localHeaderOffset + 30 > directoryOffset || u32(bytes, localHeaderOffset) !== 0x04034b50) {
152
+ fail('malformed', 'Malformed XLSX local file header');
153
+ }
154
+ const localNameLength = u16(bytes, localHeaderOffset + 26);
155
+ const localExtraLength = u16(bytes, localHeaderOffset + 28);
156
+ const localNameEnd = localHeaderOffset + 30 + localNameLength;
157
+ if (localNameEnd + localExtraLength > directoryOffset) fail('malformed', 'Malformed XLSX local entry bounds');
158
+ // The compression-ratio cap divides by this declared size, so it must
159
+ // describe bytes that actually exist before the central directory.
160
+ if (localNameEnd + localExtraLength + compressedSize > directoryOffset) {
161
+ fail('malformed', 'XLSX entry declares more compressed bytes than the file holds');
162
+ }
163
+ const localName = new TextDecoder().decode(bytes.subarray(localHeaderOffset + 30, localNameEnd));
164
+ if (localName !== name) fail('malformed', 'XLSX local and central directory names do not match');
165
+ // JSZip keeps only the last of two entries that resolve to one name, and
166
+ // the rebuilt archive can hold only one, so both are refused.
167
+ const excelJsName = excelJsEntryName(name);
168
+ if (excelJsName === '') fail('malformed', 'XLSX entry name resolves to nothing');
169
+ if (excelJsNames.has(excelJsName)) fail('malformed', 'Two XLSX entries resolve to the same name');
170
+ excelJsNames.add(excelJsName);
171
+ entries.push({ name, excelJsName, compressedSize, declaredSize, localHeaderOffset });
172
+ cursor = end;
173
+ }
174
+ return { entries };
175
+ }
176
+
177
+ function featureForName(name) {
178
+ if (name.startsWith('xl/charts/')) return 'charts';
179
+ if (name.startsWith('xl/drawings/')) return 'drawings';
180
+ if (name.startsWith('xl/pivotCache/')) return 'pivotTables';
181
+ if (name.startsWith('xl/externalLinks/')) return 'externalLinks';
182
+ if (/vbaProject\.bin$/i.test(name)) return 'macros';
183
+ return null;
184
+ }
185
+
186
+ // Longest single XML tag the streaming counter will carry across chunks. Real
187
+ // worksheet tags are a few hundred bytes; this only stops a pathological tag
188
+ // from turning the carried tail into quadratic re-scanning.
189
+ const MAX_CARRIED_TAG = 256 * 1024;
190
+
191
+ // XML attribute syntax, read in order from just after the tag name. A quoted
192
+ // value may hold a raw `>` or the other quote character, so a value is always
193
+ // consumed whole; a tag is only understood if this walk reaches its `>`.
194
+ const XML_SPACE = '[ \\t\\r\\n]';
195
+ const ATTRIBUTE = new RegExp(
196
+ `${XML_SPACE}+([^ \\t\\r\\n=/>]+)${XML_SPACE}*=${XML_SPACE}*(?:"([^"]*)"|'([^']*)')`,
197
+ 'y'
198
+ );
199
+ const TAG_END = new RegExp(`${XML_SPACE}*/?>`, 'y');
200
+
201
+ /**
202
+ * Parse the attributes of the tag whose name ends at `from` in `text`.
203
+ * Returns the attributes, or null when they do not parse cleanly up to the
204
+ * tag's closing `/>` or `>` (or a name repeats, which XML forbids).
205
+ */
206
+ function readTagAttributes(text, from) {
207
+ const attributes = new Map();
208
+ let at = from;
209
+ for (;;) {
210
+ ATTRIBUTE.lastIndex = at;
211
+ const match = ATTRIBUTE.exec(text);
212
+ if (!match) break;
213
+ if (attributes.has(match[1])) return null;
214
+ attributes.set(match[1], match[2] ?? match[3]);
215
+ at = ATTRIBUTE.lastIndex;
216
+ }
217
+ TAG_END.lastIndex = at;
218
+ return TAG_END.test(text) ? attributes : null;
219
+ }
220
+
221
+ // ExcelJS expands a merge into one cell object per covered cell at load time,
222
+ // so a merge costs its AREA, not one tag.
223
+ function mergeArea(attributes) {
224
+ const range = parseRange(attributes.get('ref'));
225
+ if (!range) fail('malformed', 'Worksheet has a merged range that does not parse');
226
+ return (Math.abs(range.r2 - range.r1) + 1) * (Math.abs(range.c2 - range.c1) + 1);
227
+ }
228
+
229
+ // ExcelJS builds one column object for every index up to `<col max>`, unclamped.
230
+ function checkColumnSpan(attributes) {
231
+ for (const name of ['min', 'max']) {
232
+ const value = attributes.get(name);
233
+ if (value === undefined) continue;
234
+ const index = Number(value);
235
+ if (!Number.isInteger(index) || index < 1 || index > MAX_COL) {
236
+ fail('malformed', `Worksheet column ${name} is outside 1-${MAX_COL}`);
237
+ }
238
+ }
239
+ }
240
+
241
+ // ExcelJS stores each row at `_rows[r - 1]`, and eachRow and `sheet.model`
242
+ // walk every index up to the largest, so the index a row CLAIMS is a cost.
243
+ function checkRowIndex(attributes) {
244
+ const value = attributes.get('r');
245
+ if (value === undefined) return;
246
+ if (!/^[0-9]+$/.test(value) || Number(value) < 1 || Number(value) > MAX_ROW) {
247
+ fail('malformed', `Worksheet row index is outside 1-${MAX_ROW}`);
248
+ }
249
+ }
250
+
251
+ // ExcelJS stores each sheet at `_worksheets[sheetId]` (an absent id parses to
252
+ // NaN, a plain property, so it is harmless).
253
+ function checkSheetId(attributes, limits) {
254
+ const value = attributes.get('sheetId');
255
+ if (value === undefined) return;
256
+ if (!/^[0-9]+$/.test(value) || Number(value) > limits.maxSheetId) {
257
+ fail('malformed', `Workbook sheetId is not a number up to ${limits.maxSheetId}`);
258
+ }
259
+ }
260
+
261
+ // Attribute values as the XML parser inside ExcelJS (saxes) hands them over:
262
+ // the predefined entities and character references decoded. Checking the raw
263
+ // text would let `&#91;` hide a `[` and would overcount `&quot;`.
264
+ function decodeXmlAttribute(value) {
265
+ return value.replace(/&(?:#x([0-9a-fA-F]+)|#([0-9]+)|(amp|lt|gt|quot|apos));/g, (entity, hex, dec, name) => {
266
+ if (name) return { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" }[name];
267
+ const code = hex ? Number.parseInt(hex, 16) : Number(dec);
268
+ return code <= 0x10ffff ? String.fromCodePoint(code) : entity;
269
+ });
270
+ }
271
+
272
+ // ExcelJS's `isDateFmt` strips `/\[[^\]]*]/g` from the code once per numeric
273
+ // cell, and that pattern rescans to the end of the code for every `[` with no
274
+ // later `]` (a 60,000-character code of `[` cost 2.2 s a cell; even 255
275
+ // characters added up at the cell cap). Once nothing follows the last `]`
276
+ // but text without `[`, each `[` stops at the next `]` and the scan is linear.
277
+ // The messages never echo the code.
278
+ function checkNumberFormat(attributes, limits) {
279
+ const raw = attributes.get('formatCode');
280
+ if (raw === undefined) return;
281
+ const code = decodeXmlAttribute(raw);
282
+ if (code.length > limits.maxNumFmtChars) {
283
+ fail('number-format', `Workbook has a number format longer than ${limits.maxNumFmtChars} characters`);
284
+ }
285
+ if (code.lastIndexOf('[') > code.lastIndexOf(']')) {
286
+ fail('number-format', 'Workbook has a number format with an unclosed bracket');
287
+ }
288
+ }
289
+
290
+ // Exactly the names ExcelJS hands to `_processMediaEntry` and to no parser:
291
+ // its other patterns are unanchored, so `xl/media/xl/drawings/a.xml` is still
292
+ // parsed as a drawing, and only a single `<name>.<ext>` segment is pure bytes.
293
+ const EXCELJS_MEDIA = /^xl\/media\/[a-zA-Z0-9]+[.][a-zA-Z0-9]{3,4}$/;
294
+ const START_TAG = /<[A-Za-z_]/g;
295
+
296
+ function countStartTags(text) {
297
+ let count = 0;
298
+ START_TAG.lastIndex = 0;
299
+ while (START_TAG.exec(text)) count += 1;
300
+ return count;
301
+ }
302
+
303
+ function createXmlCounter(name, counts, limits) {
304
+ let tail = '';
305
+ const decoder = new TextDecoder();
306
+ let sheetCells = 0;
307
+ let sheetMerges = 0;
308
+ let sheetRows = 0;
309
+ const excelJsName = excelJsEntryName(name);
310
+ const worksheet = isWorksheetName(excelJsName);
311
+ const styles = excelJsName === 'xl/styles.xml';
312
+ const workbook = excelJsName === 'xl/workbook.xml';
313
+ const media = EXCELJS_MEDIA.test(excelJsName);
314
+ // Only these parts read tag attributes, so only they carry a whole tag.
315
+ const readsTags = worksheet || styles || workbook;
316
+ const addCells = (cells) => {
317
+ sheetCells += cells;
318
+ counts.cells += cells;
319
+ if (sheetCells > limits.maxCellsPerSheet || counts.cells > limits.maxCells)
320
+ fail('cell-limit', 'Workbook exceeds the cells limit');
321
+ };
322
+ return {
323
+ push(chunk, final) {
324
+ if (media) return;
325
+ const text = tail + decoder.decode(chunk, { stream: !final });
326
+ // `<` can never appear inside an attribute value, so every tag before the
327
+ // last `<` is complete. Carry everything from that `<` into the next scan
328
+ // so a tag cut by a chunk edge is always read in one piece. Any other part
329
+ // only needs its start tags counted, so it carries at most a trailing `<`
330
+ // (a long text node or a binary part is never mistaken for a huge tag).
331
+ let safeEnd = text.length;
332
+ if (!final && readsTags) safeEnd = Math.max(0, text.lastIndexOf('<'));
333
+ else if (!final && text.endsWith('<')) safeEnd = text.length - 1;
334
+ const scan = text.slice(0, safeEnd);
335
+ counts.elements += countStartTags(scan);
336
+ if (counts.elements > limits.maxElements) fail('element-limit', 'Workbook exceeds the XML elements limit');
337
+ if (worksheet) {
338
+ addCells((scan.match(/<c(?:\s|>)/g) || []).length);
339
+ // ExcelJS keeps a Row object for every <row>, with or without cells.
340
+ const rows = (scan.match(/<row(?=[\s/>])/g) || []).length;
341
+ sheetRows += rows;
342
+ counts.rows += rows;
343
+ if (sheetRows > limits.maxRowsPerSheet || counts.rows > limits.maxRows)
344
+ fail('row-limit', 'Workbook exceeds the rows limit');
345
+ for (const match of scan.matchAll(/<(mergeCell|col|row)(?=[\s/>])/g)) {
346
+ const attributes = readTagAttributes(scan, match.index + match[0].length);
347
+ if (!attributes) fail('malformed', `Worksheet has a <${match[1]}> whose attributes do not parse`);
348
+ if (match[1] === 'row') {
349
+ checkRowIndex(attributes);
350
+ continue;
351
+ }
352
+ if (match[1] === 'col') {
353
+ checkColumnSpan(attributes);
354
+ continue;
355
+ }
356
+ sheetMerges += 1;
357
+ counts.merges += 1;
358
+ if (sheetMerges > limits.maxMergesPerSheet || counts.merges > limits.maxMerges)
359
+ fail('merge-limit', 'Workbook exceeds the merged ranges limit');
360
+ addCells(mergeArea(attributes));
361
+ }
362
+ }
363
+ if (workbook) {
364
+ // `<sheets>` is the container; the lookahead keeps it from matching.
365
+ for (const match of scan.matchAll(/<sheet(?=[\s/>])/g)) {
366
+ const attributes = readTagAttributes(scan, match.index + match[0].length);
367
+ if (!attributes) fail('malformed', 'Workbook has a <sheet> whose attributes do not parse');
368
+ checkSheetId(attributes, limits);
369
+ }
370
+ }
371
+ if (styles) {
372
+ // Every <xf> counts, cellStyleXfs included: tracking which list a tag
373
+ // sits in can be desynced by a closing tag inside an XML comment.
374
+ counts.styles += (scan.match(/<xf(?=[\s/>])/g) || []).length;
375
+ if (counts.styles > limits.maxStyles) fail('style-limit', 'Workbook exceeds the cell styles limit');
376
+ // Every <numFmt>, cellXfs' and dxfs' alike; the lookahead skips <numFmts>.
377
+ for (const match of scan.matchAll(/<numFmt(?=[\s/>])/g)) {
378
+ const attributes = readTagAttributes(scan, match.index + match[0].length);
379
+ if (!attributes) fail('malformed', 'Workbook has a <numFmt> whose attributes do not parse');
380
+ checkNumberFormat(attributes, limits);
381
+ }
382
+ }
383
+ tail = text.slice(safeEnd);
384
+ if (tail.length > MAX_CARRIED_TAG) fail('malformed', 'Workbook XML has an oversized tag');
385
+ },
386
+ };
387
+ }
388
+
389
+ function admitXlsx(bytes, zipApi, overrides) {
390
+ const limits = mergedLimits(overrides);
391
+ const directory = inspectZipDirectory(bytes, limits);
392
+ const directoryByName = new Map(directory.entries.map((entry) => [entry.name, entry]));
393
+ const expectedEntries = new Map();
394
+ for (const entry of directory.entries) expectedEntries.set(entry.name, (expectedEntries.get(entry.name) || 0) + 1);
395
+ const streamedEntries = new Map();
396
+ const inflatedEntries = Object.create(null);
397
+ const counts = { worksheets: 0, cells: 0, rows: 0, merges: 0, styles: 0, elements: 0 };
398
+ const features = new Set();
399
+ let totalInflated = 0;
400
+ let seenEntries = 0;
401
+ let thrown;
402
+ const unzip = new zipApi.Unzip((file) => {
403
+ if (!directoryByName.has(file.name)) fail('malformed', 'Local XLSX entry is absent from the central directory');
404
+ // The admitted archive is rebuilt from these entries, which cannot hold two
405
+ // files under one name, so a duplicate name is refused rather than dropped.
406
+ if (streamedEntries.has(file.name)) fail('malformed', 'Duplicate XLSX entry name');
407
+ streamedEntries.set(file.name, (streamedEntries.get(file.name) || 0) + 1);
408
+ seenEntries += 1;
409
+ if (seenEntries > limits.maxEntries) fail('entry-limit', 'Workbook exceeds the ZIP entries limit');
410
+ // Everything below keys on the name ExcelJS will see, never the stored one.
411
+ const name = directoryByName.get(file.name).excelJsName;
412
+ if (isWorksheetName(name)) {
413
+ counts.worksheets += 1;
414
+ if (counts.worksheets > limits.maxWorksheets) fail('worksheet-limit', 'Workbook exceeds the worksheet limit');
415
+ }
416
+ const feature = featureForName(name);
417
+ if (feature) features.add(feature);
418
+ const counter = createXmlCounter(name, counts, limits);
419
+ let entryInflated = 0;
420
+ const chunks = [];
421
+ file.ondata = (error, chunk, final) => {
422
+ if (error) throw error;
423
+ chunks.push(chunk.slice());
424
+ entryInflated += chunk.length;
425
+ totalInflated += chunk.length;
426
+ if (entryInflated > limits.maxEntryBytes) fail('entry-size', 'Inflated ZIP entry exceeds the entry limit');
427
+ if (totalInflated > limits.maxInflatedBytes) fail('inflated-size', 'Workbook exceeds the inflated bytes limit');
428
+ const compressed = directoryByName.get(file.name)?.compressedSize || 1;
429
+ if (entryInflated / Math.max(1, compressed) > limits.maxCompressionRatio) {
430
+ fail('compression-ratio', 'ZIP entry exceeds the compression ratio limit');
431
+ }
432
+ counter.push(chunk, final);
433
+ if (final) {
434
+ const data = new Uint8Array(entryInflated);
435
+ let offset = 0;
436
+ for (const part of chunks) {
437
+ data.set(part, offset);
438
+ offset += part.length;
439
+ }
440
+ chunks.length = 0;
441
+ inflatedEntries[name] = data;
442
+ }
443
+ };
444
+ file.start();
445
+ });
446
+ unzip.register(zipApi.UnzipInflate);
447
+ try {
448
+ const inputChunkBytes = 64 * 1024;
449
+ for (let offset = 0; offset < bytes.length && !thrown; offset += inputChunkBytes) {
450
+ const end = Math.min(bytes.length, offset + inputChunkBytes);
451
+ unzip.push(bytes.subarray(offset, end), end === bytes.length);
452
+ }
453
+ } catch (error) {
454
+ thrown = error;
455
+ }
456
+ if (thrown) throw thrown;
457
+ for (const [name, count] of expectedEntries) {
458
+ if (streamedEntries.get(name) !== count) fail('malformed', 'Central XLSX entry was not streamed for admission');
459
+ }
460
+ for (const name of streamedEntries.keys()) {
461
+ if (!(directoryByName.get(name).excelJsName in inflatedEntries)) {
462
+ fail('malformed', 'XLSX entry did not finish streaming for admission');
463
+ }
464
+ }
465
+ return { counts, features: Array.from(features), inflatedBytes: totalInflated, entries: inflatedEntries };
466
+ }
467
+
468
+ // The ONLY bytes ExcelJS may parse: the entries admission itself inflated and
469
+ // counted, re-packed uncompressed. Its size is ~ the inflated total (already
470
+ // capped at LIMITS.maxInflatedBytes) plus per-entry headers.
471
+ function buildAdmittedArchive(admission, zipApi) {
472
+ return zipApi.zipSync(admission.entries, { level: 0 });
473
+ }
474
+
475
+ function parseCellRef(ref) {
476
+ const match = /^\$?([A-Z]{1,3})\$?([1-9]\d*)$/i.exec(String(ref || ''));
477
+ if (!match) return null;
478
+ let col = 0;
479
+ for (const char of match[1].toUpperCase()) col = col * 26 + char.charCodeAt(0) - 64;
480
+ const row = Number(match[2]);
481
+ return row <= MAX_ROW && col <= MAX_COL ? { row, col } : null;
482
+ }
483
+
484
+ function parseRange(range) {
485
+ const parts = String(range).split(':');
486
+ const start = parseCellRef(parts[0]);
487
+ const end = parseCellRef(parts[1] || parts[0]);
488
+ return start && end ? { r1: start.row, c1: start.col, r2: end.row, c2: end.col } : null;
489
+ }
490
+
491
+ function deriveExtent(cells, merges) {
492
+ let rows = 0;
493
+ let cols = 0;
494
+ for (const ref of cells) {
495
+ const cell = parseCellRef(ref);
496
+ if (cell) {
497
+ rows = Math.max(rows, cell.row);
498
+ cols = Math.max(cols, cell.col);
499
+ }
500
+ }
501
+ for (const merge of merges) {
502
+ const range = parseRange(merge);
503
+ if (range) {
504
+ rows = Math.max(rows, range.r2);
505
+ cols = Math.max(cols, range.c2);
506
+ }
507
+ }
508
+ return { rows, cols };
509
+ }
510
+
511
+ function createSparseAxis(count, defaultSize, overrides) {
512
+ const sorted = Array.from(overrides || [])
513
+ .filter(([index, size]) => index >= 1 && index <= count && Number.isFinite(size))
514
+ .map(([index, size]) => [index, Math.max(0, size)])
515
+ .sort((a, b) => a[0] - b[0]);
516
+ const base = Math.max(0, defaultSize);
517
+ // deltas[i] is the summed size change of overrides[0..i], so an offset is
518
+ // one binary search instead of a walk over every override.
519
+ const deltas = [];
520
+ let delta = 0;
521
+ for (const [, size] of sorted) {
522
+ delta += size - base;
523
+ deltas.push(delta);
524
+ }
525
+ return { count: Math.max(0, count), defaultSize: base, overrides: sorted, deltas };
526
+ }
527
+
528
+ // Number of overrides whose index is below `bounded`.
529
+ function overridesBefore(overrides, bounded) {
530
+ let low = 0;
531
+ let high = overrides.length;
532
+ while (low < high) {
533
+ const mid = (low + high) >> 1;
534
+ if (overrides[mid][0] < bounded) low = mid + 1;
535
+ else high = mid;
536
+ }
537
+ return low;
538
+ }
539
+
540
+ function axisOffset(axis, index) {
541
+ const bounded = Math.max(1, Math.min(axis.count + 1, index));
542
+ const before = overridesBefore(axis.overrides, bounded);
543
+ return (bounded - 1) * axis.defaultSize + (before ? axis.deltas[before - 1] : 0);
544
+ }
545
+
546
+ function axisIndexAt(axis, offset) {
547
+ let low = 1;
548
+ let high = Math.max(1, axis.count);
549
+ const target = Math.max(0, offset);
550
+ while (low < high) {
551
+ const mid = Math.floor((low + high + 1) / 2);
552
+ if (axisOffset(axis, mid) <= target) low = mid;
553
+ else high = mid - 1;
554
+ }
555
+ return low;
556
+ }
557
+
558
+ function computeViewport(axis, offset, viewportSize, overscan) {
559
+ const pad = Math.max(0, overscan || 0);
560
+ const start = Math.max(1, axisIndexAt(axis, offset) - pad);
561
+ const end = Math.min(axis.count, axisIndexAt(axis, offset + Math.max(0, viewportSize)) + pad);
562
+ return [start, end];
563
+ }
564
+
565
+ function intersectingMerges(merges, viewport) {
566
+ return merges.filter((merge) => {
567
+ const range = parseRange(merge);
568
+ return (
569
+ range &&
570
+ range.r1 <= viewport.r2 &&
571
+ range.r2 >= viewport.r1 &&
572
+ range.c1 <= viewport.c2 &&
573
+ range.c2 >= viewport.c1
574
+ );
575
+ });
576
+ }
577
+
578
+ // Rounded to whole milliseconds: `new Date(fraction)` truncates, which turned
579
+ // midnight minus a float error into the previous day.
580
+ function excelDate(serial, date1904) {
581
+ if (date1904) return new Date(Math.round(Date.UTC(1904, 0, 1) + Number(serial) * 86400000));
582
+ const numeric = Number(serial);
583
+ const adjusted = numeric >= 60 ? numeric - 1 : numeric;
584
+ return new Date(Math.round(Date.UTC(1899, 11, 31) + adjusted * 86400000));
585
+ }
586
+
587
+ function isDateValue(value) {
588
+ return Object.prototype.toString.call(value) === '[object Date]' && Number.isFinite(value.getTime());
589
+ }
590
+
591
+ // Inverse of ExcelJS's own `excelToDate()` (utils.js), which builds the Date
592
+ // from the serial in UTC. Recovering the serial keeps the result independent of
593
+ // the viewer's timezone; `String(date)` rendered it in local time, a day early
594
+ // at any negative UTC offset.
595
+ function dateToSerial(date, date1904) {
596
+ return 25569 + date.getTime() / 86400000 - (date1904 ? 1462 : 0);
597
+ }
598
+
599
+ const DATE_FORMAT = /^[ymd\-/ ]+$/i;
600
+ // An optional trailing AM/PM (built-in format 18 is `h:mm AM/PM`).
601
+ const TIME_FORMAT = /^[hms: ]+(?:AM\/PM)?$/i;
602
+ const DATE_TIME_FORMAT = /^[ymdhis\-/: ]+$/i;
603
+
604
+ function isFormulaValue(value) {
605
+ return 'formula' in value || 'sharedFormula' in value;
606
+ }
607
+
608
+ // Cut to LIMITS.maxCellTextChars, ending in an ellipsis, never between the
609
+ // halves of a surrogate pair.
610
+ function capCellText(text) {
611
+ const max = LIMITS.maxCellTextChars;
612
+ if (text.length <= max) return text;
613
+ let end = max - 1;
614
+ const last = text.charCodeAt(end - 1);
615
+ if (last >= 0xd800 && last <= 0xdbff) end -= 1;
616
+ return `${text.slice(0, end)}…`;
617
+ }
618
+
619
+ // Joins rich-text runs only until the cap is passed, so a long run is never
620
+ // copied whole once per cell. It also visits at most maxCellTextChars + 1
621
+ // runs: an empty run (`<r/>`, 4 bytes) adds no text, so a length check alone
622
+ // walked every run, for every cell sharing the string, on every tile. Any
623
+ // maxCellTextChars + 1 non-empty runs already pass the cap.
624
+ function richTextPrefix(runs) {
625
+ let text = '';
626
+ const visit = Math.min(runs.length, LIMITS.maxCellTextChars + 1);
627
+ for (let index = 0; index < visit; index += 1) {
628
+ if (text.length > LIMITS.maxCellTextChars) break;
629
+ const run = runs[index];
630
+ if (typeof run?.text === 'string') text += run.text.slice(0, LIMITS.maxCellTextChars + 1);
631
+ }
632
+ return text;
633
+ }
634
+
635
+ // Excel displays at most 15 significant digits, so `=0.1+0.2` shows 0.3,
636
+ // never the binary float's 0.30000000000000004.
637
+ function generalNumber(value) {
638
+ return Number.isFinite(value) ? String(Number(value.toPrecision(15))) : String(value);
639
+ }
640
+
641
+ const UNSUPPORTED_FORMAT_PREFIX = 'Unsupported number format: ';
642
+
643
+ /**
644
+ * Folds every unsupported number format warning into one counted entry when
645
+ * there is more than one, so a sheet with a code per cell cannot grow the
646
+ * notice bar without bound. A lone warning is kept as is; its code is at
647
+ * most 255 characters (admission refuses longer ones). Order is preserved.
648
+ */
649
+ function foldWarnings(warnings) {
650
+ const formats = warnings.filter((warning) => String(warning).startsWith(UNSUPPORTED_FORMAT_PREFIX));
651
+ if (formats.length < 2) return warnings.slice();
652
+ const folded = [];
653
+ let placed = false;
654
+ for (const warning of warnings) {
655
+ if (!String(warning).startsWith(UNSUPPORTED_FORMAT_PREFIX)) folded.push(warning);
656
+ else if (!placed) {
657
+ folded.push(`${formats.length} unsupported number formats`);
658
+ placed = true;
659
+ }
660
+ }
661
+ return folded;
662
+ }
663
+
664
+ /**
665
+ * A cell's display text and any warning. The text is ALWAYS at most
666
+ * `LIMITS.maxCellTextChars` characters, whatever shape the value has.
667
+ */
668
+ function formatCellValue(value, format, date1904) {
669
+ const formatted = formatCellValueUncapped(value, format, date1904);
670
+ return formatted.text.length > LIMITS.maxCellTextChars
671
+ ? Object.assign({}, formatted, { text: capCellText(formatted.text) })
672
+ : formatted;
673
+ }
674
+
675
+ // Every non-scalar shape ExcelJS loads a cell value as. Anything not handled
676
+ // here would otherwise reach String() and render as "[object Object]".
677
+ function formatCellValueUncapped(value, format, date1904) {
678
+ if (value === null || value === undefined) return { text: '' };
679
+ if (isDateValue(value)) {
680
+ const code = String(format || 'General');
681
+ const known = DATE_FORMAT.test(code) || TIME_FORMAT.test(code) || DATE_TIME_FORMAT.test(code);
682
+ const serial = dateToSerial(value, date1904);
683
+ if (known) return formatCellValueUncapped(serial, code, date1904);
684
+ const fallback = serial % 1 === 0 ? 'yyyy-mm-dd' : 'yyyy-mm-dd hh:mm';
685
+ const formatted = formatCellValueUncapped(serial, fallback, date1904);
686
+ return /^General$/i.test(code)
687
+ ? formatted
688
+ : { text: formatted.text, warning: `${UNSUPPORTED_FORMAT_PREFIX}${code}` };
689
+ }
690
+ if (typeof value === 'object') {
691
+ if (isFormulaValue(value)) {
692
+ if (value.result !== undefined && value.result !== null) {
693
+ return formatCellValueUncapped(value.result, format, date1904);
694
+ }
695
+ const source = typeof value.formula === 'string' ? `=${value.formula}` : '';
696
+ return { text: source, warning: 'Formula has no cached result' };
697
+ }
698
+ if (typeof value.error === 'string') return { text: value.error };
699
+ if (Array.isArray(value.richText)) {
700
+ return { text: richTextPrefix(value.richText) };
701
+ }
702
+ // Hyperlink: the display text, never the target. The text may be rich.
703
+ if ('text' in value) return formatCellValueUncapped(value.text, 'General', date1904);
704
+ return { text: '', warning: 'Unsupported cell value' };
705
+ }
706
+ const code = String(format || 'General');
707
+ if (typeof value !== 'number') return { text: String(value) };
708
+ if (/^General$/i.test(code)) return { text: generalNumber(value) };
709
+ if (DATE_FORMAT.test(code)) {
710
+ const date = excelDate(value, Boolean(date1904));
711
+ const yyyy = date.getUTCFullYear();
712
+ const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
713
+ const dd = String(date.getUTCDate()).padStart(2, '0');
714
+ return { text: `${yyyy}-${mm}-${dd}` };
715
+ }
716
+ if (TIME_FORMAT.test(code)) {
717
+ const seconds = Math.round((value - Math.floor(value)) * 86400) % 86400;
718
+ const hours = Math.floor(seconds / 3600);
719
+ const mm = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
720
+ const ss = String(seconds % 60).padStart(2, '0');
721
+ if (/AM\/PM$/i.test(code)) {
722
+ const hour12 = String(hours % 12 || 12);
723
+ const hh = /hh/i.test(code) ? hour12.padStart(2, '0') : hour12;
724
+ const clock = /s/i.test(code) ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
725
+ return { text: `${clock} ${hours < 12 ? 'AM' : 'PM'}` };
726
+ }
727
+ const hh = String(hours).padStart(2, '0');
728
+ return { text: `${hh}:${mm}:${ss}` };
729
+ }
730
+ if (DATE_TIME_FORMAT.test(code)) {
731
+ const date = excelDate(value, Boolean(date1904));
732
+ const yyyy = date.getUTCFullYear();
733
+ const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
734
+ const dd = String(date.getUTCDate()).padStart(2, '0');
735
+ const hh = String(date.getUTCHours()).padStart(2, '0');
736
+ const minutes = String(date.getUTCMinutes()).padStart(2, '0');
737
+ return { text: `${yyyy}-${mm}-${dd} ${hh}:${minutes}` };
738
+ }
739
+ const percent = code.includes('%');
740
+ // toLocaleString throws above 100 fraction digits; Excel itself caps at 30.
741
+ const decimals = Math.min(code.match(/\.([0#]+)/)?.[1].length || 0, 30);
742
+ const numericPattern = /^[€£¥$]?[#,0]+(?:\.[0#]+)?%?$/;
743
+ if (numericPattern.test(code)) {
744
+ const currency = /^[€£¥$]/.exec(code)?.[0] || '';
745
+ const numeric = percent ? value * 100 : value;
746
+ const useGrouping = code.includes(',');
747
+ return {
748
+ text:
749
+ currency +
750
+ numeric.toLocaleString('en-US', {
751
+ useGrouping,
752
+ minimumFractionDigits: decimals,
753
+ maximumFractionDigits: decimals,
754
+ }) +
755
+ (percent ? '%' : ''),
756
+ };
757
+ }
758
+ return { text: generalNumber(value), warning: `${UNSUPPORTED_FORMAT_PREFIX}${code}` };
759
+ }
760
+
761
+ // Colour resolution --------------------------------------------------------
762
+ //
763
+ // ExcelJS surfaces theme and indexed palette colours WITHOUT an `argb` key
764
+ // (`{theme,tint}` / `{indexed}`), and theme colours are what Excel emits by
765
+ // default. Dropping them left cells rendering the workbook's font colour on
766
+ // the skin's own background, which is how black-on-dark (invisible) text got
767
+ // shipped. Everything below is pure so `test/spreadsheet-xlsx-core.test.ts`
768
+ // can pin it without a DOM.
769
+
770
+ // styles.xml `theme="N"` order. NOTE: theme1.xml's <a:clrScheme> lists
771
+ // dk1, lt1, dk2, lt2 — indices 0/1 and 2/3 are SWAPPED between the two.
772
+ const THEME_SLOT_ORDER = Object.freeze([
773
+ 'lt1',
774
+ 'dk1',
775
+ 'lt2',
776
+ 'dk2',
777
+ 'accent1',
778
+ 'accent2',
779
+ 'accent3',
780
+ 'accent4',
781
+ 'accent5',
782
+ 'accent6',
783
+ 'hlink',
784
+ 'folHlink',
785
+ ]);
786
+
787
+ // Default Office theme, used when theme1.xml is missing or unparseable.
788
+ const DEFAULT_THEME_PALETTE = Object.freeze([
789
+ '#ffffff',
790
+ '#000000',
791
+ '#eeece1',
792
+ '#1f497d',
793
+ '#4f81bd',
794
+ '#c0504d',
795
+ '#9bbb59',
796
+ '#8064a2',
797
+ '#4bacc6',
798
+ '#f79646',
799
+ '#0000ff',
800
+ '#800080',
801
+ ]);
802
+
803
+ // Legacy 64-entry indexed palette. Indices 64/65 are the "auto" foreground and
804
+ // background sentinels and deliberately have no entry here.
805
+ // prettier-ignore
806
+ const INDEXED_PALETTE = Object.freeze([
807
+ '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff',
808
+ '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff',
809
+ '#800000', '#008000', '#000080', '#808000', '#800080', '#008080', '#c0c0c0', '#808080',
810
+ '#9999ff', '#993366', '#ffffcc', '#ccffff', '#660066', '#ff8080', '#0066cc', '#ccccff',
811
+ '#000080', '#ff00ff', '#ffff00', '#00ffff', '#800080', '#800000', '#008080', '#0000ff',
812
+ '#00ccff', '#ccffff', '#ccffcc', '#ffff99', '#99ccff', '#ff99cc', '#cc99ff', '#ffcc99',
813
+ '#3366ff', '#33cccc', '#99cc00', '#ffcc00', '#ff9900', '#ff6600', '#666699', '#969696',
814
+ '#003366', '#339966', '#003300', '#333300', '#993300', '#993366', '#333399', '#333333',
815
+ ]);
816
+
817
+ const MIN_CONTRAST_RATIO = 4.5;
818
+ // A workbook with no fill renders on Excel's implicit white sheet background,
819
+ // never on the viewer skin's `var(--bg-primary)`.
820
+ const IMPLICIT_SHEET_BACKGROUND = '#ffffff';
821
+ const IMPLICIT_SHEET_FOREGROUND = '#000000';
822
+
823
+ function hexToRgb(hex) {
824
+ const match = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(String(hex || ''));
825
+ if (!match) return null;
826
+ return [Number.parseInt(match[1], 16), Number.parseInt(match[2], 16), Number.parseInt(match[3], 16)];
827
+ }
828
+
829
+ function rgbToHex(rgb) {
830
+ let hex = '#';
831
+ for (const channel of rgb) {
832
+ const bounded = Math.max(0, Math.min(255, Math.round(channel)));
833
+ hex += (bounded < 16 ? '0' : '') + bounded.toString(16);
834
+ }
835
+ return hex;
836
+ }
837
+
838
+ function rgbToHsl(rgb) {
839
+ const r = rgb[0] / 255;
840
+ const g = rgb[1] / 255;
841
+ const b = rgb[2] / 255;
842
+ const max = Math.max(r, g, b);
843
+ const min = Math.min(r, g, b);
844
+ const l = (max + min) / 2;
845
+ if (max === min) return { h: 0, s: 0, l };
846
+ const delta = max - min;
847
+ const s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
848
+ let h;
849
+ if (max === r) h = (g - b) / delta + (g < b ? 6 : 0);
850
+ else if (max === g) h = (b - r) / delta + 2;
851
+ else h = (r - g) / delta + 4;
852
+ return { h: h / 6, s, l };
853
+ }
854
+
855
+ function hueToChannel(p, q, hue) {
856
+ let t = hue;
857
+ if (t < 0) t += 1;
858
+ if (t > 1) t -= 1;
859
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
860
+ if (t < 1 / 2) return q;
861
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
862
+ return p;
863
+ }
864
+
865
+ function hslToRgb(hsl) {
866
+ if (hsl.s === 0) {
867
+ const gray = hsl.l * 255;
868
+ return [gray, gray, gray];
869
+ }
870
+ const q = hsl.l < 0.5 ? hsl.l * (1 + hsl.s) : hsl.l + hsl.s - hsl.l * hsl.s;
871
+ const p = 2 * hsl.l - q;
872
+ return [
873
+ hueToChannel(p, q, hsl.h + 1 / 3) * 255,
874
+ hueToChannel(p, q, hsl.h) * 255,
875
+ hueToChannel(p, q, hsl.h - 1 / 3) * 255,
876
+ ];
877
+ }
878
+
879
+ // Excel tint acts on HSL luminance: negative darkens, positive lightens.
880
+ function applyTint(hex, tint) {
881
+ const amount = Number(tint);
882
+ if (!Number.isFinite(amount) || amount === 0) return hex;
883
+ const rgb = hexToRgb(hex);
884
+ if (!rgb) return hex;
885
+ const bounded = Math.max(-1, Math.min(1, amount));
886
+ const hsl = rgbToHsl(rgb);
887
+ const luminance = bounded < 0 ? hsl.l * (1 + bounded) : hsl.l * (1 - bounded) + bounded;
888
+ return rgbToHex(hslToRgb({ h: hsl.h, s: hsl.s, l: Math.max(0, Math.min(1, luminance)) }));
889
+ }
890
+
891
+ function parseSchemeColor(fragment) {
892
+ const srgb = /<(?:[A-Za-z0-9_]+:)?srgbClr\b[^>]*\bval="([0-9A-Fa-f]{6})"/.exec(fragment);
893
+ if (srgb) return `#${srgb[1].toLowerCase()}`;
894
+ const sys = /<(?:[A-Za-z0-9_]+:)?sysClr\b[^>]*\blastClr="([0-9A-Fa-f]{6})"/.exec(fragment);
895
+ if (sys) return `#${sys[1].toLowerCase()}`;
896
+ return undefined;
897
+ }
898
+
899
+ // Real theme1.xml files are under 10 KB. The scheme and slot patterns below
900
+ // rescan to the end of the text for every opening tag that has no close, so a
901
+ // padded theme is quadratic (1 MB of `<a:clrScheme>` took 21.8 s); above this
902
+ // many characters (UTF-16 code units of the decoded XML) the default palette
903
+ // is used instead.
904
+ const MAX_THEME_XML_CHARS = 64 * 1024;
905
+
906
+ function parseThemePalette(xml) {
907
+ const palette = DEFAULT_THEME_PALETTE.slice();
908
+ const text = typeof xml === 'string' ? xml : '';
909
+ if (text.length > MAX_THEME_XML_CHARS) return palette;
910
+ const scheme = /<(?:[A-Za-z0-9_]+:)?clrScheme\b[^>]*>([\s\S]*?)<\/(?:[A-Za-z0-9_]+:)?clrScheme\s*>/.exec(text);
911
+ if (!scheme) return palette;
912
+ // Fresh pattern per call: a shared /g regex would carry lastIndex across calls.
913
+ const slots =
914
+ /<(?:[A-Za-z0-9_]+:)?(lt1|dk1|lt2|dk2|accent[1-6]|hlink|folHlink)\b[^>]*>([\s\S]*?)<\/(?:[A-Za-z0-9_]+:)?\1\s*>/g;
915
+ let match = slots.exec(scheme[1]);
916
+ while (match) {
917
+ const index = THEME_SLOT_ORDER.indexOf(match[1]);
918
+ const resolved = index >= 0 ? parseSchemeColor(match[2]) : undefined;
919
+ if (resolved) palette[index] = resolved;
920
+ match = slots.exec(scheme[1]);
921
+ }
922
+ return palette;
923
+ }
924
+
925
+ function themePaletteOrDefault(palette) {
926
+ return Array.isArray(palette) && palette.length === THEME_SLOT_ORDER.length ? palette : DEFAULT_THEME_PALETTE;
927
+ }
928
+
929
+ // Accepts every colour shape ExcelJS emits: {argb}, {theme,tint}, {indexed}.
930
+ function resolveColor(color, palette) {
931
+ if (!color || typeof color !== 'object') return undefined;
932
+ const argb = color.argb;
933
+ if (typeof argb === 'string' && /^[A-Fa-f0-9]{8}$/.test(argb)) return `#${argb.slice(2).toLowerCase()}`;
934
+ const theme = color.theme;
935
+ if (Number.isInteger(theme) && theme >= 0 && theme < THEME_SLOT_ORDER.length) {
936
+ const base = themePaletteOrDefault(palette)[theme];
937
+ return typeof base === 'string' ? applyTint(base, color.tint) : undefined;
938
+ }
939
+ const indexed = color.indexed;
940
+ if (Number.isInteger(indexed) && indexed >= 0 && indexed < INDEXED_PALETTE.length) return INDEXED_PALETTE[indexed];
941
+ return undefined;
942
+ }
943
+
944
+ function channelLuminance(channel) {
945
+ const value = channel / 255;
946
+ return value <= 0.03928 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
947
+ }
948
+
949
+ function relativeLuminance(hex) {
950
+ const rgb = hexToRgb(hex);
951
+ if (!rgb) return 0;
952
+ return 0.2126 * channelLuminance(rgb[0]) + 0.7152 * channelLuminance(rgb[1]) + 0.0722 * channelLuminance(rgb[2]);
953
+ }
954
+
955
+ function contrastRatio(a, b) {
956
+ const first = relativeLuminance(a);
957
+ const second = relativeLuminance(b);
958
+ return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
959
+ }
960
+
961
+ function ensureContrast(foreground, background, minRatio) {
962
+ const minimum = Number.isFinite(minRatio) ? minRatio : MIN_CONTRAST_RATIO;
963
+ if (contrastRatio(foreground, background) >= minimum) return foreground;
964
+ return contrastRatio('#000000', background) >= contrastRatio('#ffffff', background) ? '#000000' : '#ffffff';
965
+ }
966
+
967
+ // Returns a SELF-CONSISTENT pair, or nothing at all. Emitting only one half is
968
+ // what let workbook text land on the skin's background and vanish.
969
+ function resolveCellColors(fillColor, fontColor, palette) {
970
+ const fill = resolveColor(fillColor, palette);
971
+ const font = resolveColor(fontColor, palette);
972
+ if (!fill && !font) return {};
973
+ const background = fill || IMPLICIT_SHEET_BACKGROUND;
974
+ return { background, foreground: ensureContrast(font || IMPLICIT_SHEET_FOREGROUND, background) };
975
+ }
976
+
977
+ global.CodemanSpreadsheetXlsxCore = Object.freeze({
978
+ LIMITS,
979
+ MAX_ROW,
980
+ MAX_COL,
981
+ XlsxPreviewError,
982
+ inspectZipDirectory,
983
+ createXmlCounter,
984
+ admitXlsx,
985
+ buildAdmittedArchive,
986
+ excelJsEntryName,
987
+ parseCellRef,
988
+ parseRange,
989
+ deriveExtent,
990
+ createSparseAxis,
991
+ axisOffset,
992
+ axisIndexAt,
993
+ computeViewport,
994
+ intersectingMerges,
995
+ formatCellValue,
996
+ foldWarnings,
997
+ DEFAULT_THEME_PALETTE,
998
+ INDEXED_PALETTE,
999
+ MIN_CONTRAST_RATIO,
1000
+ parseThemePalette,
1001
+ resolveColor,
1002
+ contrastRatio,
1003
+ ensureContrast,
1004
+ resolveCellColors,
1005
+ });
1006
+ })(typeof self !== 'undefined' ? self : globalThis);