@cj-tech-master/excelts 9.2.1 → 9.3.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 (383) hide show
  1. package/README.md +25 -2
  2. package/README_zh.md +29 -6
  3. package/dist/browser/index.browser.d.ts +1 -1
  4. package/dist/browser/index.browser.js +4 -0
  5. package/dist/browser/index.d.ts +1 -1
  6. package/dist/browser/index.js +4 -0
  7. package/dist/browser/modules/excel/cell.d.ts +17 -3
  8. package/dist/browser/modules/excel/cell.js +170 -22
  9. package/dist/browser/modules/excel/defined-names.d.ts +96 -1
  10. package/dist/browser/modules/excel/defined-names.js +411 -21
  11. package/dist/browser/modules/excel/image.d.ts +11 -0
  12. package/dist/browser/modules/excel/image.js +24 -1
  13. package/dist/browser/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  14. package/dist/browser/modules/excel/stream/workbook-reader.browser.js +14 -0
  15. package/dist/browser/modules/excel/stream/workbook-reader.d.ts +2 -1
  16. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  17. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +48 -1
  18. package/dist/browser/modules/excel/stream/workbook-writer.d.ts +3 -2
  19. package/dist/browser/modules/excel/stream/worksheet-reader.js +17 -1
  20. package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +39 -6
  21. package/dist/browser/modules/excel/stream/worksheet-writer.js +45 -5
  22. package/dist/browser/modules/excel/table.js +15 -2
  23. package/dist/browser/modules/excel/types.d.ts +133 -2
  24. package/dist/browser/modules/excel/utils/col-cache.d.ts +1 -0
  25. package/dist/browser/modules/excel/utils/col-cache.js +15 -0
  26. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +3 -3
  27. package/dist/browser/modules/excel/utils/drawing-utils.js +4 -0
  28. package/dist/browser/modules/excel/utils/external-link-formula.d.ts +76 -0
  29. package/dist/browser/modules/excel/utils/external-link-formula.js +208 -0
  30. package/dist/browser/modules/excel/utils/iterate-stream.d.ts +9 -3
  31. package/dist/browser/modules/excel/utils/iterate-stream.js +3 -1
  32. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +19 -0
  33. package/dist/browser/modules/excel/utils/ooxml-paths.js +37 -2
  34. package/dist/browser/modules/excel/utils/shared-strings.d.ts +8 -3
  35. package/dist/browser/modules/excel/utils/shared-strings.js +21 -2
  36. package/dist/browser/modules/excel/utils/workbook-protection.d.ts +30 -0
  37. package/dist/browser/modules/excel/utils/workbook-protection.js +30 -0
  38. package/dist/browser/modules/excel/workbook.browser.d.ts +257 -6
  39. package/dist/browser/modules/excel/workbook.browser.js +318 -34
  40. package/dist/browser/modules/excel/workbook.d.ts +1 -1
  41. package/dist/browser/modules/excel/worksheet.d.ts +3 -1
  42. package/dist/browser/modules/excel/worksheet.js +21 -2
  43. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +15 -0
  44. package/dist/browser/modules/excel/xlsx/rel-type.js +16 -1
  45. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  46. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  47. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  48. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  49. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  50. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  51. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  52. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  53. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  54. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  55. package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  56. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  57. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  58. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  59. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  60. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  61. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  62. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  63. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  64. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  65. package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  66. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  67. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  68. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  69. package/dist/browser/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  70. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  71. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +410 -20
  72. package/dist/browser/modules/excel/xlsx/xlsx.d.ts +7 -4
  73. package/dist/browser/modules/excel/xlsx/xlsx.js +4 -5
  74. package/dist/browser/modules/formula/compile/address-utils.d.ts +62 -0
  75. package/dist/browser/modules/formula/compile/address-utils.js +83 -0
  76. package/dist/browser/modules/formula/compile/binder.d.ts +42 -0
  77. package/dist/browser/modules/formula/compile/binder.js +487 -0
  78. package/dist/browser/modules/formula/compile/bound-ast.d.ts +230 -0
  79. package/dist/browser/modules/formula/compile/bound-ast.js +80 -0
  80. package/dist/browser/modules/formula/compile/compiled-formula.d.ts +137 -0
  81. package/dist/browser/modules/formula/compile/compiled-formula.js +383 -0
  82. package/dist/browser/modules/formula/compile/dependency-analysis.d.ts +93 -0
  83. package/dist/browser/modules/formula/compile/dependency-analysis.js +432 -0
  84. package/dist/browser/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  85. package/dist/browser/modules/formula/compile/structured-ref-utils.js +136 -0
  86. package/dist/browser/modules/formula/default-syntax-probe.d.ts +79 -0
  87. package/dist/browser/modules/formula/default-syntax-probe.js +83 -0
  88. package/dist/browser/modules/formula/functions/_date-context.d.ts +4 -0
  89. package/dist/browser/modules/formula/functions/_date-context.js +29 -0
  90. package/dist/browser/modules/formula/functions/_shared.d.ts +121 -0
  91. package/dist/browser/modules/formula/functions/_shared.js +381 -0
  92. package/dist/browser/modules/formula/functions/conditional.d.ts +27 -0
  93. package/dist/browser/modules/formula/functions/conditional.js +343 -0
  94. package/dist/browser/modules/formula/functions/database.d.ts +37 -0
  95. package/dist/browser/modules/formula/functions/database.js +274 -0
  96. package/dist/browser/modules/formula/functions/date.d.ts +61 -0
  97. package/dist/browser/modules/formula/functions/date.js +855 -0
  98. package/dist/browser/modules/formula/functions/dynamic-array.d.ts +23 -0
  99. package/dist/browser/modules/formula/functions/dynamic-array.js +860 -0
  100. package/dist/browser/modules/formula/functions/engineering.d.ts +57 -0
  101. package/dist/browser/modules/formula/functions/engineering.js +1128 -0
  102. package/dist/browser/modules/formula/functions/financial.d.ts +202 -0
  103. package/dist/browser/modules/formula/functions/financial.js +2296 -0
  104. package/dist/browser/modules/formula/functions/lookup.d.ts +18 -0
  105. package/dist/browser/modules/formula/functions/lookup.js +886 -0
  106. package/dist/browser/modules/formula/functions/math.d.ts +114 -0
  107. package/dist/browser/modules/formula/functions/math.js +1406 -0
  108. package/dist/browser/modules/formula/functions/statistical.d.ts +193 -0
  109. package/dist/browser/modules/formula/functions/statistical.js +3390 -0
  110. package/dist/browser/modules/formula/functions/text.d.ts +86 -0
  111. package/dist/browser/modules/formula/functions/text.js +1845 -0
  112. package/dist/browser/modules/formula/host-registry.d.ts +53 -0
  113. package/dist/browser/modules/formula/host-registry.js +69 -0
  114. package/dist/browser/modules/formula/index.d.ts +39 -0
  115. package/dist/browser/modules/formula/index.js +49 -0
  116. package/dist/browser/modules/formula/install.d.ts +62 -0
  117. package/dist/browser/modules/formula/install.js +88 -0
  118. package/dist/browser/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  119. package/dist/browser/modules/formula/integration/apply-writeback-plan.js +210 -0
  120. package/dist/browser/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  121. package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +616 -0
  122. package/dist/browser/modules/formula/integration/calculate-formulas.d.ts +67 -0
  123. package/dist/browser/modules/formula/integration/calculate-formulas.js +68 -0
  124. package/dist/browser/modules/formula/integration/formula-instance.d.ts +64 -0
  125. package/dist/browser/modules/formula/integration/formula-instance.js +79 -0
  126. package/dist/browser/modules/formula/integration/workbook-adapter.d.ts +26 -0
  127. package/dist/browser/modules/formula/integration/workbook-adapter.js +324 -0
  128. package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  129. package/dist/browser/modules/formula/integration/workbook-snapshot.js +77 -0
  130. package/dist/browser/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  131. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +473 -0
  132. package/dist/browser/modules/formula/materialize/spill-engine.d.ts +9 -0
  133. package/dist/browser/modules/formula/materialize/spill-engine.js +38 -0
  134. package/dist/browser/modules/formula/materialize/types.d.ts +179 -0
  135. package/dist/browser/modules/formula/materialize/types.js +29 -0
  136. package/dist/browser/modules/formula/materialize/writeback-plan.d.ts +167 -0
  137. package/dist/browser/modules/formula/materialize/writeback-plan.js +27 -0
  138. package/dist/browser/modules/formula/runtime/evaluator.d.ts +151 -0
  139. package/dist/browser/modules/formula/runtime/evaluator.js +2291 -0
  140. package/dist/browser/modules/formula/runtime/function-registry.d.ts +47 -0
  141. package/dist/browser/modules/formula/runtime/function-registry.js +840 -0
  142. package/dist/browser/modules/formula/runtime/values.d.ts +211 -0
  143. package/dist/browser/modules/formula/runtime/values.js +385 -0
  144. package/dist/browser/modules/formula/syntax/ast.d.ts +129 -0
  145. package/dist/browser/modules/formula/syntax/ast.js +28 -0
  146. package/dist/browser/modules/formula/syntax/parser.d.ts +18 -0
  147. package/dist/browser/modules/formula/syntax/parser.js +439 -0
  148. package/dist/browser/modules/formula/syntax/token-types.d.ts +153 -0
  149. package/dist/browser/modules/formula/syntax/token-types.js +59 -0
  150. package/dist/browser/modules/formula/syntax/tokenizer.d.ts +10 -0
  151. package/dist/browser/modules/formula/syntax/tokenizer.js +1074 -0
  152. package/dist/browser/modules/pdf/excel-bridge.js +9 -0
  153. package/dist/cjs/index.js +4 -0
  154. package/dist/cjs/modules/excel/cell.js +170 -22
  155. package/dist/cjs/modules/excel/defined-names.js +411 -21
  156. package/dist/cjs/modules/excel/image.js +24 -1
  157. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +14 -0
  158. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +48 -1
  159. package/dist/cjs/modules/excel/stream/worksheet-reader.js +17 -1
  160. package/dist/cjs/modules/excel/stream/worksheet-writer.js +45 -5
  161. package/dist/cjs/modules/excel/table.js +15 -2
  162. package/dist/cjs/modules/excel/utils/col-cache.js +15 -0
  163. package/dist/cjs/modules/excel/utils/drawing-utils.js +4 -0
  164. package/dist/cjs/modules/excel/utils/external-link-formula.js +212 -0
  165. package/dist/cjs/modules/excel/utils/iterate-stream.js +3 -1
  166. package/dist/cjs/modules/excel/utils/ooxml-paths.js +42 -2
  167. package/dist/cjs/modules/excel/utils/shared-strings.js +21 -2
  168. package/dist/cjs/modules/excel/utils/workbook-protection.js +33 -0
  169. package/dist/cjs/modules/excel/workbook.browser.js +318 -34
  170. package/dist/cjs/modules/excel/worksheet.js +20 -1
  171. package/dist/cjs/modules/excel/xlsx/rel-type.js +16 -1
  172. package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  173. package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +333 -0
  174. package/dist/cjs/modules/excel/xlsx/xform/book/external-reference-xform.js +27 -0
  175. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  176. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-protection-xform.js +69 -0
  177. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  178. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +18 -0
  179. package/dist/cjs/modules/excel/xlsx/xform/core/metadata-xform.js +161 -0
  180. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +108 -0
  181. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  182. package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  183. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  184. package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  185. package/dist/cjs/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +83 -0
  186. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  187. package/dist/cjs/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  188. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +408 -18
  189. package/dist/cjs/modules/excel/xlsx/xlsx.js +4 -5
  190. package/dist/cjs/modules/formula/compile/address-utils.js +89 -0
  191. package/dist/cjs/modules/formula/compile/binder.js +489 -0
  192. package/dist/cjs/modules/formula/compile/bound-ast.js +68 -0
  193. package/dist/cjs/modules/formula/compile/compiled-formula.js +387 -0
  194. package/dist/cjs/modules/formula/compile/dependency-analysis.js +437 -0
  195. package/dist/cjs/modules/formula/compile/structured-ref-utils.js +141 -0
  196. package/dist/cjs/modules/formula/default-syntax-probe.js +87 -0
  197. package/dist/cjs/modules/formula/functions/_date-context.js +33 -0
  198. package/dist/cjs/modules/formula/functions/_shared.js +396 -0
  199. package/dist/cjs/modules/formula/functions/conditional.js +354 -0
  200. package/dist/cjs/modules/formula/functions/database.js +288 -0
  201. package/dist/cjs/modules/formula/functions/date.js +883 -0
  202. package/dist/cjs/modules/formula/functions/dynamic-array.js +881 -0
  203. package/dist/cjs/modules/formula/functions/engineering.js +1183 -0
  204. package/dist/cjs/modules/formula/functions/financial.js +2348 -0
  205. package/dist/cjs/modules/formula/functions/lookup.js +902 -0
  206. package/dist/cjs/modules/formula/functions/math.js +1487 -0
  207. package/dist/cjs/modules/formula/functions/statistical.js +3488 -0
  208. package/dist/cjs/modules/formula/functions/text.js +1889 -0
  209. package/dist/cjs/modules/formula/host-registry.js +75 -0
  210. package/dist/cjs/modules/formula/index.js +58 -0
  211. package/dist/cjs/modules/formula/install.js +93 -0
  212. package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +213 -0
  213. package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +619 -0
  214. package/dist/cjs/modules/formula/integration/calculate-formulas.js +71 -0
  215. package/dist/cjs/modules/formula/integration/formula-instance.js +82 -0
  216. package/dist/cjs/modules/formula/integration/workbook-adapter.js +327 -0
  217. package/dist/cjs/modules/formula/integration/workbook-snapshot.js +84 -0
  218. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +475 -0
  219. package/dist/cjs/modules/formula/materialize/spill-engine.js +42 -0
  220. package/dist/cjs/modules/formula/materialize/types.js +32 -0
  221. package/dist/cjs/modules/formula/materialize/writeback-plan.js +28 -0
  222. package/dist/cjs/modules/formula/runtime/evaluator.js +2298 -0
  223. package/dist/cjs/modules/formula/runtime/function-registry.js +846 -0
  224. package/dist/cjs/modules/formula/runtime/values.js +385 -0
  225. package/dist/cjs/modules/formula/syntax/ast.js +8 -0
  226. package/dist/cjs/modules/formula/syntax/parser.js +440 -0
  227. package/dist/cjs/modules/formula/syntax/token-types.js +32 -0
  228. package/dist/cjs/modules/formula/syntax/tokenizer.js +1076 -0
  229. package/dist/cjs/modules/pdf/excel-bridge.js +9 -0
  230. package/dist/esm/index.browser.js +4 -0
  231. package/dist/esm/index.js +4 -0
  232. package/dist/esm/modules/excel/cell.js +170 -22
  233. package/dist/esm/modules/excel/defined-names.js +411 -21
  234. package/dist/esm/modules/excel/image.js +24 -1
  235. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +14 -0
  236. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +48 -1
  237. package/dist/esm/modules/excel/stream/worksheet-reader.js +17 -1
  238. package/dist/esm/modules/excel/stream/worksheet-writer.js +45 -5
  239. package/dist/esm/modules/excel/table.js +15 -2
  240. package/dist/esm/modules/excel/utils/col-cache.js +15 -0
  241. package/dist/esm/modules/excel/utils/drawing-utils.js +4 -0
  242. package/dist/esm/modules/excel/utils/external-link-formula.js +208 -0
  243. package/dist/esm/modules/excel/utils/iterate-stream.js +3 -1
  244. package/dist/esm/modules/excel/utils/ooxml-paths.js +37 -2
  245. package/dist/esm/modules/excel/utils/shared-strings.js +21 -2
  246. package/dist/esm/modules/excel/utils/workbook-protection.js +30 -0
  247. package/dist/esm/modules/excel/workbook.browser.js +318 -34
  248. package/dist/esm/modules/excel/worksheet.js +21 -2
  249. package/dist/esm/modules/excel/xlsx/rel-type.js +16 -1
  250. package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  251. package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  252. package/dist/esm/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  253. package/dist/esm/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  254. package/dist/esm/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  255. package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  256. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  257. package/dist/esm/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  258. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  259. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  260. package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  261. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  262. package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  263. package/dist/esm/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  264. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  265. package/dist/esm/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  266. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +410 -20
  267. package/dist/esm/modules/excel/xlsx/xlsx.js +4 -5
  268. package/dist/esm/modules/formula/compile/address-utils.js +83 -0
  269. package/dist/esm/modules/formula/compile/binder.js +487 -0
  270. package/dist/esm/modules/formula/compile/bound-ast.js +80 -0
  271. package/dist/esm/modules/formula/compile/compiled-formula.js +383 -0
  272. package/dist/esm/modules/formula/compile/dependency-analysis.js +432 -0
  273. package/dist/esm/modules/formula/compile/structured-ref-utils.js +136 -0
  274. package/dist/esm/modules/formula/default-syntax-probe.js +83 -0
  275. package/dist/esm/modules/formula/functions/_date-context.js +29 -0
  276. package/dist/esm/modules/formula/functions/_shared.js +381 -0
  277. package/dist/esm/modules/formula/functions/conditional.js +343 -0
  278. package/dist/esm/modules/formula/functions/database.js +274 -0
  279. package/dist/esm/modules/formula/functions/date.js +855 -0
  280. package/dist/esm/modules/formula/functions/dynamic-array.js +860 -0
  281. package/dist/esm/modules/formula/functions/engineering.js +1128 -0
  282. package/dist/esm/modules/formula/functions/financial.js +2296 -0
  283. package/dist/esm/modules/formula/functions/lookup.js +886 -0
  284. package/dist/esm/modules/formula/functions/math.js +1406 -0
  285. package/dist/esm/modules/formula/functions/statistical.js +3390 -0
  286. package/dist/esm/modules/formula/functions/text.js +1845 -0
  287. package/dist/esm/modules/formula/host-registry.js +69 -0
  288. package/dist/esm/modules/formula/index.js +49 -0
  289. package/dist/esm/modules/formula/install.js +88 -0
  290. package/dist/esm/modules/formula/integration/apply-writeback-plan.js +210 -0
  291. package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +616 -0
  292. package/dist/esm/modules/formula/integration/calculate-formulas.js +68 -0
  293. package/dist/esm/modules/formula/integration/formula-instance.js +79 -0
  294. package/dist/esm/modules/formula/integration/workbook-adapter.js +324 -0
  295. package/dist/esm/modules/formula/integration/workbook-snapshot.js +77 -0
  296. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +473 -0
  297. package/dist/esm/modules/formula/materialize/spill-engine.js +38 -0
  298. package/dist/esm/modules/formula/materialize/types.js +29 -0
  299. package/dist/esm/modules/formula/materialize/writeback-plan.js +27 -0
  300. package/dist/esm/modules/formula/runtime/evaluator.js +2291 -0
  301. package/dist/esm/modules/formula/runtime/function-registry.js +840 -0
  302. package/dist/esm/modules/formula/runtime/values.js +385 -0
  303. package/dist/esm/modules/formula/syntax/ast.js +28 -0
  304. package/dist/esm/modules/formula/syntax/parser.js +439 -0
  305. package/dist/esm/modules/formula/syntax/token-types.js +59 -0
  306. package/dist/esm/modules/formula/syntax/tokenizer.js +1074 -0
  307. package/dist/esm/modules/pdf/excel-bridge.js +9 -0
  308. package/dist/iife/excelts.iife.js +2302 -373
  309. package/dist/iife/excelts.iife.js.map +1 -1
  310. package/dist/iife/excelts.iife.min.js +34 -34
  311. package/dist/types/index.browser.d.ts +1 -1
  312. package/dist/types/index.d.ts +1 -1
  313. package/dist/types/modules/excel/cell.d.ts +17 -3
  314. package/dist/types/modules/excel/defined-names.d.ts +96 -1
  315. package/dist/types/modules/excel/image.d.ts +11 -0
  316. package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  317. package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
  318. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  319. package/dist/types/modules/excel/stream/workbook-writer.d.ts +3 -2
  320. package/dist/types/modules/excel/stream/worksheet-writer.d.ts +39 -6
  321. package/dist/types/modules/excel/types.d.ts +133 -2
  322. package/dist/types/modules/excel/utils/col-cache.d.ts +1 -0
  323. package/dist/types/modules/excel/utils/drawing-utils.d.ts +3 -3
  324. package/dist/types/modules/excel/utils/external-link-formula.d.ts +76 -0
  325. package/dist/types/modules/excel/utils/iterate-stream.d.ts +9 -3
  326. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +19 -0
  327. package/dist/types/modules/excel/utils/shared-strings.d.ts +8 -3
  328. package/dist/types/modules/excel/utils/workbook-protection.d.ts +30 -0
  329. package/dist/types/modules/excel/workbook.browser.d.ts +257 -6
  330. package/dist/types/modules/excel/workbook.d.ts +1 -1
  331. package/dist/types/modules/excel/worksheet.d.ts +3 -1
  332. package/dist/types/modules/excel/xlsx/rel-type.d.ts +15 -0
  333. package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  334. package/dist/types/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  335. package/dist/types/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  336. package/dist/types/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  337. package/dist/types/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  338. package/dist/types/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  339. package/dist/types/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  340. package/dist/types/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  341. package/dist/types/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  342. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  343. package/dist/types/modules/excel/xlsx/xlsx.d.ts +7 -4
  344. package/dist/types/modules/formula/compile/address-utils.d.ts +62 -0
  345. package/dist/types/modules/formula/compile/binder.d.ts +42 -0
  346. package/dist/types/modules/formula/compile/bound-ast.d.ts +230 -0
  347. package/dist/types/modules/formula/compile/compiled-formula.d.ts +137 -0
  348. package/dist/types/modules/formula/compile/dependency-analysis.d.ts +93 -0
  349. package/dist/types/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  350. package/dist/types/modules/formula/default-syntax-probe.d.ts +79 -0
  351. package/dist/types/modules/formula/functions/_date-context.d.ts +4 -0
  352. package/dist/types/modules/formula/functions/_shared.d.ts +121 -0
  353. package/dist/types/modules/formula/functions/conditional.d.ts +27 -0
  354. package/dist/types/modules/formula/functions/database.d.ts +37 -0
  355. package/dist/types/modules/formula/functions/date.d.ts +61 -0
  356. package/dist/types/modules/formula/functions/dynamic-array.d.ts +23 -0
  357. package/dist/types/modules/formula/functions/engineering.d.ts +57 -0
  358. package/dist/types/modules/formula/functions/financial.d.ts +202 -0
  359. package/dist/types/modules/formula/functions/lookup.d.ts +18 -0
  360. package/dist/types/modules/formula/functions/math.d.ts +114 -0
  361. package/dist/types/modules/formula/functions/statistical.d.ts +193 -0
  362. package/dist/types/modules/formula/functions/text.d.ts +86 -0
  363. package/dist/types/modules/formula/host-registry.d.ts +53 -0
  364. package/dist/types/modules/formula/index.d.ts +39 -0
  365. package/dist/types/modules/formula/install.d.ts +62 -0
  366. package/dist/types/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  367. package/dist/types/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  368. package/dist/types/modules/formula/integration/calculate-formulas.d.ts +67 -0
  369. package/dist/types/modules/formula/integration/formula-instance.d.ts +64 -0
  370. package/dist/types/modules/formula/integration/workbook-adapter.d.ts +26 -0
  371. package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  372. package/dist/types/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  373. package/dist/types/modules/formula/materialize/spill-engine.d.ts +9 -0
  374. package/dist/types/modules/formula/materialize/types.d.ts +179 -0
  375. package/dist/types/modules/formula/materialize/writeback-plan.d.ts +167 -0
  376. package/dist/types/modules/formula/runtime/evaluator.d.ts +151 -0
  377. package/dist/types/modules/formula/runtime/function-registry.d.ts +47 -0
  378. package/dist/types/modules/formula/runtime/values.d.ts +211 -0
  379. package/dist/types/modules/formula/syntax/ast.d.ts +129 -0
  380. package/dist/types/modules/formula/syntax/parser.d.ts +18 -0
  381. package/dist/types/modules/formula/syntax/token-types.d.ts +153 -0
  382. package/dist/types/modules/formula/syntax/tokenizer.d.ts +10 -0
  383. package/package.json +28 -28
@@ -0,0 +1,1889 @@
1
+ "use strict";
2
+ /**
3
+ * Text Functions — Native RuntimeValue implementations.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fnENCODEURL = exports.fnARRAYTOTEXT = exports.fnVALUETOTEXT = exports.fnREGEXREPLACE = exports.fnREGEXEXTRACT = exports.fnREGEXTEST = exports.fnTEXTSPLIT = exports.fnTEXTAFTER = exports.fnTEXTBEFORE = exports.fnNUMBERVALUE = exports.fnPHONETIC = exports.fnJIS = exports.fnDBCS = exports.fnASC = exports.fnFIXED = exports.fnDOLLAR = exports.fnBAHTTEXT = exports.fnUNICODE = exports.fnUNICHAR = exports.fnT = exports.fnCLEAN = exports.fnCHAR = exports.fnCODE = exports.fnEXACT = exports.fnVALUE = exports.fnTEXT = exports.fnREPT = exports.fnSEARCH = exports.fnFIND = exports.fnREPLACE = exports.fnSUBSTITUTE = exports.fnPROPER = exports.fnUPPER = exports.fnLOWER = exports.fnTRIM = exports.fnLEN = exports.fnMID = exports.fnRIGHT = exports.fnLEFT = exports.fnTEXTJOIN = exports.fnCONCAT = exports.fnCONCATENATE = void 0;
7
+ const utils_base_1 = require("../../../utils/utils.base.js");
8
+ const values_1 = require("../runtime/values");
9
+ const _date_context_1 = require("./_date-context");
10
+ const _shared_1 = require("./_shared");
11
+ // ============================================================================
12
+ // Local utility
13
+ // ============================================================================
14
+ function escapeRegex(s) {
15
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16
+ }
17
+ // ============================================================================
18
+ // CONCATENATE / CONCAT
19
+ // ============================================================================
20
+ const fnCONCATENATE = args => {
21
+ const parts = [];
22
+ for (const a of args) {
23
+ if ((0, values_1.isArray)(a)) {
24
+ for (const row of a.rows) {
25
+ for (const cell of row) {
26
+ const err = (0, _shared_1.checkError)(cell);
27
+ if (err) {
28
+ return err;
29
+ }
30
+ parts.push((0, values_1.toStringRV)(cell));
31
+ }
32
+ }
33
+ }
34
+ else {
35
+ const err = (0, _shared_1.checkError)(a);
36
+ if (err) {
37
+ return err;
38
+ }
39
+ parts.push((0, values_1.toStringRV)(a));
40
+ }
41
+ }
42
+ return (0, values_1.rvString)(parts.join(""));
43
+ };
44
+ exports.fnCONCATENATE = fnCONCATENATE;
45
+ // CONCAT has the same semantics as CONCATENATE
46
+ exports.fnCONCAT = exports.fnCONCATENATE;
47
+ // ============================================================================
48
+ // TEXTJOIN
49
+ // ============================================================================
50
+ const fnTEXTJOIN = args => {
51
+ if (args.length < 3) {
52
+ return values_1.ERRORS.VALUE;
53
+ }
54
+ const e0 = (0, _shared_1.checkError)(args[0]);
55
+ if (e0) {
56
+ return e0;
57
+ }
58
+ const delimiter = (0, values_1.toStringRV)(args[0]);
59
+ const ignoreEmptyRV = (0, values_1.toBooleanRV)(args[1]);
60
+ if ((0, values_1.isError)(ignoreEmptyRV)) {
61
+ return ignoreEmptyRV;
62
+ }
63
+ const ignoreEmpty = ignoreEmptyRV.value;
64
+ const parts = [];
65
+ for (let i = 2; i < args.length; i++) {
66
+ const a = args[i];
67
+ if ((0, values_1.isArray)(a)) {
68
+ for (const row of a.rows) {
69
+ for (const cell of row) {
70
+ const err = (0, _shared_1.checkError)(cell);
71
+ if (err) {
72
+ return err;
73
+ }
74
+ const s = (0, values_1.toStringRV)(cell);
75
+ if (ignoreEmpty && s === "") {
76
+ continue;
77
+ }
78
+ parts.push(s);
79
+ }
80
+ }
81
+ }
82
+ else {
83
+ const err = (0, _shared_1.checkError)(a);
84
+ if (err) {
85
+ return err;
86
+ }
87
+ const s = (0, values_1.toStringRV)(a);
88
+ if (ignoreEmpty && s === "") {
89
+ continue;
90
+ }
91
+ parts.push(s);
92
+ }
93
+ }
94
+ return (0, values_1.rvString)(parts.join(delimiter));
95
+ };
96
+ exports.fnTEXTJOIN = fnTEXTJOIN;
97
+ // ============================================================================
98
+ // LEFT / RIGHT / MID / LEN
99
+ // ============================================================================
100
+ const fnLEFT = args => {
101
+ const err = (0, _shared_1.checkError)(args[0]);
102
+ if (err) {
103
+ return err;
104
+ }
105
+ const text = (0, values_1.toStringRV)(args[0]);
106
+ let n;
107
+ if (args.length > 1) {
108
+ // Use `argToNumber` so array arguments get implicit-intersection to
109
+ // their top-left cell before numeric coercion — otherwise
110
+ // `LEFT("abc", A1:A2)` would land in `toNumberRV`'s array path and
111
+ // incorrectly surface #VALUE! instead of using A1.
112
+ const nRV = (0, _shared_1.argToNumber)(args[1]);
113
+ if ((0, values_1.isError)(nRV)) {
114
+ return nRV;
115
+ }
116
+ n = nRV.value;
117
+ }
118
+ else {
119
+ n = 1;
120
+ }
121
+ // Excel rejects negative lengths outright. Without this guard,
122
+ // `text.slice(0, -1)` would silently trim the last character.
123
+ if (n < 0) {
124
+ return values_1.ERRORS.VALUE;
125
+ }
126
+ return (0, values_1.rvString)(text.slice(0, Math.trunc(n)));
127
+ };
128
+ exports.fnLEFT = fnLEFT;
129
+ const fnRIGHT = args => {
130
+ const err = (0, _shared_1.checkError)(args[0]);
131
+ if (err) {
132
+ return err;
133
+ }
134
+ const text = (0, values_1.toStringRV)(args[0]);
135
+ let n;
136
+ if (args.length > 1) {
137
+ // Implicit intersection via `argToNumber` — see LEFT for rationale.
138
+ const nRV = (0, _shared_1.argToNumber)(args[1]);
139
+ if ((0, values_1.isError)(nRV)) {
140
+ return nRV;
141
+ }
142
+ n = nRV.value;
143
+ }
144
+ else {
145
+ n = 1;
146
+ }
147
+ if (n < 0) {
148
+ return values_1.ERRORS.VALUE;
149
+ }
150
+ const k = Math.trunc(n);
151
+ if (k === 0) {
152
+ return (0, values_1.rvString)("");
153
+ }
154
+ return (0, values_1.rvString)(text.slice(-k));
155
+ };
156
+ exports.fnRIGHT = fnRIGHT;
157
+ const fnMID = args => {
158
+ const err = (0, _shared_1.checkError)(args[0]);
159
+ if (err) {
160
+ return err;
161
+ }
162
+ const text = (0, values_1.toStringRV)(args[0]);
163
+ // Implicit intersection on both numeric arguments so array inputs
164
+ // collapse to their top-left cells before coercion.
165
+ const startNumRV = (0, _shared_1.argToNumber)(args[1]);
166
+ if ((0, values_1.isError)(startNumRV)) {
167
+ return startNumRV;
168
+ }
169
+ const startNum = Math.trunc(startNumRV.value);
170
+ const numCharsRV = (0, _shared_1.argToNumber)(args[2]);
171
+ if ((0, values_1.isError)(numCharsRV)) {
172
+ return numCharsRV;
173
+ }
174
+ const numChars = Math.trunc(numCharsRV.value);
175
+ // MID: start_num must be >= 1, num_chars must be >= 0.
176
+ if (startNum < 1 || numChars < 0) {
177
+ return values_1.ERRORS.VALUE;
178
+ }
179
+ return (0, values_1.rvString)(text.slice(startNum - 1, startNum - 1 + numChars));
180
+ };
181
+ exports.fnMID = fnMID;
182
+ const fnLEN = args => {
183
+ const err = (0, _shared_1.checkError)(args[0]);
184
+ if (err) {
185
+ return err;
186
+ }
187
+ // `toStringRV` doesn't dereference arrays — passing `A1:A2` would hit
188
+ // its `default: ""` branch and silently return 0. Do an implicit
189
+ // intersection via `topLeft` so `LEN(A1:A2)` behaves like Excel's
190
+ // legacy implicit-intersection semantics (pick the first cell).
191
+ return (0, values_1.rvNumber)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0])).length);
192
+ };
193
+ exports.fnLEN = fnLEN;
194
+ // ============================================================================
195
+ // TRIM / LOWER / UPPER / PROPER
196
+ // ============================================================================
197
+ const fnTRIM = args => {
198
+ const err = (0, _shared_1.checkError)(args[0]);
199
+ if (err) {
200
+ return err;
201
+ }
202
+ // Implicit intersection: turn an array argument into its top-left
203
+ // cell before stringifying, to match Excel's legacy behaviour.
204
+ // Excel's TRIM only collapses plain ASCII space (U+0020), NOT tabs,
205
+ // newlines, or non-breaking space (U+00A0).
206
+ return (0, values_1.rvString)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0]))
207
+ .replace(/^ +| +$/g, "")
208
+ .replace(/ +/g, " "));
209
+ };
210
+ exports.fnTRIM = fnTRIM;
211
+ const fnLOWER = args => {
212
+ const err = (0, _shared_1.checkError)(args[0]);
213
+ if (err) {
214
+ return err;
215
+ }
216
+ return (0, values_1.rvString)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0])).toLowerCase());
217
+ };
218
+ exports.fnLOWER = fnLOWER;
219
+ const fnUPPER = args => {
220
+ const err = (0, _shared_1.checkError)(args[0]);
221
+ if (err) {
222
+ return err;
223
+ }
224
+ return (0, values_1.rvString)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0])).toUpperCase());
225
+ };
226
+ exports.fnUPPER = fnUPPER;
227
+ const fnPROPER = args => {
228
+ const err = (0, _shared_1.checkError)(args[0]);
229
+ if (err) {
230
+ return err;
231
+ }
232
+ return (0, values_1.rvString)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0])).replace(/\p{L}+/gu, word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()));
233
+ };
234
+ exports.fnPROPER = fnPROPER;
235
+ // ============================================================================
236
+ // SUBSTITUTE / REPLACE
237
+ // ============================================================================
238
+ const fnSUBSTITUTE = args => {
239
+ const err0 = (0, _shared_1.checkError)(args[0]);
240
+ if (err0) {
241
+ return err0;
242
+ }
243
+ const err1 = (0, _shared_1.checkError)(args[1]);
244
+ if (err1) {
245
+ return err1;
246
+ }
247
+ const err2 = (0, _shared_1.checkError)(args[2]);
248
+ if (err2) {
249
+ return err2;
250
+ }
251
+ const text = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
252
+ const oldText = (0, values_1.toStringRV)((0, values_1.topLeft)(args[1]));
253
+ const newText = (0, values_1.toStringRV)((0, values_1.topLeft)(args[2]));
254
+ // An empty old_text is a no-op in Excel. Without this guard we would
255
+ // `"abc".split("").join(newText)` and insert newText between every
256
+ // character, and the regex path would match empty strings infinitely.
257
+ if (oldText === "") {
258
+ return (0, values_1.rvString)(text);
259
+ }
260
+ if (args.length > 3) {
261
+ const instanceNumRV = (0, values_1.toNumberRV)(args[3]);
262
+ if ((0, values_1.isError)(instanceNumRV)) {
263
+ return instanceNumRV;
264
+ }
265
+ // Excel requires a positive integer; zero, negative, or non-numeric
266
+ // values are #VALUE!. Previously we let the replace pass silently
267
+ // no-op (since `count === 0` never matched), masking caller bugs.
268
+ if (!Number.isFinite(instanceNumRV.value) || instanceNumRV.value < 1) {
269
+ return values_1.ERRORS.VALUE;
270
+ }
271
+ const instanceNum = Math.trunc(instanceNumRV.value);
272
+ let count = 0;
273
+ return (0, values_1.rvString)(text.replace(new RegExp(escapeRegex(oldText), "g"), match => {
274
+ count++;
275
+ return count === instanceNum ? newText : match;
276
+ }));
277
+ }
278
+ return (0, values_1.rvString)(text.split(oldText).join(newText));
279
+ };
280
+ exports.fnSUBSTITUTE = fnSUBSTITUTE;
281
+ const fnREPLACE = args => {
282
+ const err = (0, _shared_1.checkError)(args[0]);
283
+ if (err) {
284
+ return err;
285
+ }
286
+ const text = (0, values_1.toStringRV)(args[0]);
287
+ // Implicit intersection on the numeric arguments — see LEFT.
288
+ const startNumRV = (0, _shared_1.argToNumber)(args[1]);
289
+ if ((0, values_1.isError)(startNumRV)) {
290
+ return startNumRV;
291
+ }
292
+ const startNum = Math.trunc(startNumRV.value);
293
+ const numCharsRV = (0, _shared_1.argToNumber)(args[2]);
294
+ if ((0, values_1.isError)(numCharsRV)) {
295
+ return numCharsRV;
296
+ }
297
+ const numChars = Math.trunc(numCharsRV.value);
298
+ // REPLACE: start_num >= 1, num_chars >= 0. Without this check, negative
299
+ // start_num becomes a slice with negative index and silently trims from
300
+ // the right, which does not match Excel's #VALUE! result.
301
+ if (startNum < 1 || numChars < 0) {
302
+ return values_1.ERRORS.VALUE;
303
+ }
304
+ const e3 = (0, _shared_1.checkError)(args[3]);
305
+ if (e3) {
306
+ return e3;
307
+ }
308
+ const newText = (0, values_1.toStringRV)(args[3]);
309
+ return (0, values_1.rvString)(text.slice(0, startNum - 1) + newText + text.slice(startNum - 1 + numChars));
310
+ };
311
+ exports.fnREPLACE = fnREPLACE;
312
+ // ============================================================================
313
+ // FIND / SEARCH
314
+ // ============================================================================
315
+ const fnFIND = args => {
316
+ const err0 = (0, _shared_1.checkError)(args[0]);
317
+ if (err0) {
318
+ return err0;
319
+ }
320
+ const err1 = (0, _shared_1.checkError)(args[1]);
321
+ if (err1) {
322
+ return err1;
323
+ }
324
+ const findText = (0, values_1.toStringRV)(args[0]);
325
+ const withinText = (0, values_1.toStringRV)(args[1]);
326
+ let startNum;
327
+ if (args.length > 2) {
328
+ // Implicit intersection so an array supplied as start_num collapses
329
+ // to its top-left cell — matches Excel and the other text family.
330
+ const startNumRV = (0, _shared_1.argToNumber)(args[2]);
331
+ if ((0, values_1.isError)(startNumRV)) {
332
+ return startNumRV;
333
+ }
334
+ startNum = Math.trunc(startNumRV.value);
335
+ }
336
+ else {
337
+ startNum = 1;
338
+ }
339
+ // Excel's FIND rejects start_num outside [1, length(withinText)].
340
+ if (startNum < 1 || startNum > withinText.length + 1) {
341
+ return values_1.ERRORS.VALUE;
342
+ }
343
+ const idx = withinText.indexOf(findText, startNum - 1);
344
+ return idx === -1 ? values_1.ERRORS.VALUE : (0, values_1.rvNumber)(idx + 1);
345
+ };
346
+ exports.fnFIND = fnFIND;
347
+ const fnSEARCH = args => {
348
+ const err0 = (0, _shared_1.checkError)(args[0]);
349
+ if (err0) {
350
+ return err0;
351
+ }
352
+ const err1 = (0, _shared_1.checkError)(args[1]);
353
+ if (err1) {
354
+ return err1;
355
+ }
356
+ let findText = (0, values_1.toStringRV)(args[0]);
357
+ const withinText = (0, values_1.toStringRV)(args[1]);
358
+ let startNum;
359
+ if (args.length > 2) {
360
+ const startNumRV = (0, _shared_1.argToNumber)(args[2]);
361
+ if ((0, values_1.isError)(startNumRV)) {
362
+ return startNumRV;
363
+ }
364
+ startNum = Math.trunc(startNumRV.value);
365
+ }
366
+ else {
367
+ startNum = 1;
368
+ }
369
+ if (startNum < 1 || startNum > withinText.length + 1) {
370
+ return values_1.ERRORS.VALUE;
371
+ }
372
+ // Use the shared Excel-wildcard → regex converter so SEARCH, MATCH,
373
+ // XLOOKUP, and SUMIF/COUNTIF agree on escape semantics (`~*`, `~?`, `~~`).
374
+ const pattern = (0, _shared_1.excelWildcardToRegex)(findText);
375
+ try {
376
+ const re = new RegExp(pattern, "i");
377
+ const sub = withinText.slice(startNum - 1);
378
+ const match = re.exec(sub);
379
+ return match ? (0, values_1.rvNumber)(match.index + startNum) : values_1.ERRORS.VALUE;
380
+ }
381
+ catch {
382
+ // If regex is invalid, fall back to simple case-insensitive indexOf.
383
+ findText = findText.toLowerCase();
384
+ const idx = withinText.toLowerCase().indexOf(findText, startNum - 1);
385
+ return idx === -1 ? values_1.ERRORS.VALUE : (0, values_1.rvNumber)(idx + 1);
386
+ }
387
+ };
388
+ exports.fnSEARCH = fnSEARCH;
389
+ // ============================================================================
390
+ // REPT
391
+ // ============================================================================
392
+ const fnREPT = args => {
393
+ const err = (0, _shared_1.checkError)(args[0]);
394
+ if (err) {
395
+ return err;
396
+ }
397
+ const text = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
398
+ const timesRV = (0, values_1.toNumberRV)(args[1]);
399
+ if ((0, values_1.isError)(timesRV)) {
400
+ return timesRV;
401
+ }
402
+ const times = Math.floor(timesRV.value);
403
+ if (times < 0) {
404
+ return values_1.ERRORS.VALUE;
405
+ }
406
+ // Excel caps the result at 32767 characters; we additionally bail out
407
+ // early on huge products so the engine can't be DoS'd into allocating
408
+ // a multi-gigabyte string. (R6-P1-4)
409
+ const total = text.length * times;
410
+ if (total > 32767) {
411
+ return values_1.ERRORS.VALUE;
412
+ }
413
+ return (0, values_1.rvString)(text.repeat(times));
414
+ };
415
+ exports.fnREPT = fnREPT;
416
+ // ============================================================================
417
+ // TEXT (complex number/date formatting)
418
+ // ============================================================================
419
+ const fnTEXT = args => {
420
+ const rawVal = (0, values_1.topLeft)(args[0]);
421
+ if ((0, values_1.isError)(rawVal)) {
422
+ return rawVal;
423
+ }
424
+ const e1 = (0, _shared_1.checkError)(args[1]);
425
+ if (e1) {
426
+ return e1;
427
+ }
428
+ const fmt = (0, values_1.toStringRV)(args[1]);
429
+ // "@" format = return text as-is
430
+ if (fmt === "@") {
431
+ return (0, values_1.rvString)((0, values_1.toStringRV)(rawVal));
432
+ }
433
+ // Conditional sections: positive;negative;zero;text (up to 4 parts).
434
+ // Text input — when the value is a String/Boolean that doesn't parse
435
+ // as a number — is routed to the 4th section if present. Without this
436
+ // early dispatch, the later `toNumberRV(rawVal)` would #VALUE! and
437
+ // `TEXT("hi", "0;-0;0;@")` could never reach the 4th section.
438
+ const sections = splitFormatSections(fmt);
439
+ const isTextInput = rawVal.kind === 2 /* RVKind.String */;
440
+ if (isTextInput && sections.length >= 4) {
441
+ // The 4th section formats the text value — `@` is a placeholder
442
+ // that re-emits the source string (like Excel's `@` metacharacter).
443
+ return (0, values_1.rvString)(sections[3].replace(/@/g, (0, values_1.toStringRV)(rawVal)));
444
+ }
445
+ const valRV = (0, values_1.toNumberRV)(rawVal);
446
+ if ((0, values_1.isError)(valRV)) {
447
+ return valRV;
448
+ }
449
+ const val = valRV.value;
450
+ let activeFmt;
451
+ if (sections.length >= 3) {
452
+ activeFmt = val > 0 ? sections[0] : val < 0 ? sections[1] : sections[2];
453
+ }
454
+ else if (sections.length === 2) {
455
+ activeFmt = val >= 0 ? sections[0] : sections[1];
456
+ }
457
+ else {
458
+ activeFmt = sections[0];
459
+ }
460
+ // For negative section, use absolute value (sign is in the format)
461
+ const useVal = sections.length >= 2 && val < 0 ? Math.abs(val) : val;
462
+ return (0, values_1.rvString)(formatWithCode(useVal, activeFmt, rawVal));
463
+ };
464
+ exports.fnTEXT = fnTEXT;
465
+ /**
466
+ * Split format string on `;` separators, respecting quoted strings.
467
+ */
468
+ function splitFormatSections(fmt) {
469
+ const sections = [];
470
+ let current = "";
471
+ let inQuotes = false;
472
+ for (let i = 0; i < fmt.length; i++) {
473
+ if (fmt[i] === '"') {
474
+ inQuotes = !inQuotes;
475
+ current += fmt[i];
476
+ }
477
+ else if (fmt[i] === ";" && !inQuotes) {
478
+ sections.push(current);
479
+ current = "";
480
+ }
481
+ else {
482
+ current += fmt[i];
483
+ }
484
+ }
485
+ sections.push(current);
486
+ return sections;
487
+ }
488
+ /**
489
+ * Format a number using a single format code section.
490
+ */
491
+ function formatWithCode(val, fmt, rawVal) {
492
+ const upper = fmt.toUpperCase();
493
+ // Date/time formats — detect by presence of date/time tokens
494
+ if (upper.includes("YYYY") ||
495
+ upper.includes("YY") ||
496
+ upper.includes("MMMM") ||
497
+ upper.includes("MMM") ||
498
+ upper.includes("DDDD") ||
499
+ upper.includes("DDD") ||
500
+ upper.includes("DD") ||
501
+ /(?:^|[^H])MM(?!M)/.test(upper) ||
502
+ /(?:^|[^M])M(?!M)/.test(upper.replace(/MMMM|MMM/g, "")) ||
503
+ upper.includes("HH") ||
504
+ /(?:^|[^H])H(?!H)/.test(upper) ||
505
+ upper.includes("SS") ||
506
+ upper.includes("AM/PM") ||
507
+ upper.includes("A/P")) {
508
+ return formatDate(val, fmt);
509
+ }
510
+ // Percentage format
511
+ if (fmt.includes("%")) {
512
+ const stripped = fmt.replace(/[^0#.%,]/g, "");
513
+ const dotIdx = stripped.indexOf(".");
514
+ const afterDot = dotIdx >= 0
515
+ ? stripped
516
+ .slice(dotIdx + 1)
517
+ .replace(/%/g, "")
518
+ .replace(/,/g, "")
519
+ : "";
520
+ const decimals = afterDot.length;
521
+ const pctVal = val * 100;
522
+ let result = pctVal.toFixed(decimals);
523
+ if (fmt.includes(",")) {
524
+ const parts = result.split(".");
525
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
526
+ result = parts.join(".");
527
+ }
528
+ return result + "%";
529
+ }
530
+ // Scientific notation: 0.00E+00
531
+ if (/[0#]\.?[0#]*E[+-][0#]+/i.test(fmt)) {
532
+ return formatScientific(val, fmt);
533
+ }
534
+ // Fraction format: # ?/? or # ??/??
535
+ if (/[?]+\/[?]+/.test(fmt)) {
536
+ return formatFraction(val, fmt);
537
+ }
538
+ // Number format with 0 and #
539
+ if (fmt.includes("0") || fmt.includes("#")) {
540
+ return formatNumber(val, fmt);
541
+ }
542
+ // Literal-only section (no numeric placeholders). Excel emits the
543
+ // format string verbatim, substituting any `@` with the stringified
544
+ // value. This matters for 3- and 4-section formats like
545
+ // `"pos;neg;zero"` — the selected section has no `#`/`0` but should
546
+ // still appear in the output.
547
+ return fmt.replace(/@/g, String(val));
548
+ }
549
+ /**
550
+ * Format number using #, 0, comma patterns.
551
+ */
552
+ /**
553
+ * Format number using #, 0, comma patterns.
554
+ *
555
+ * The implementation tokenises the pattern left-to-right so that *any*
556
+ * literal character (parentheses, `+`/`-`, currency symbols, letters in
557
+ * quoted `"..."` segments, backslash-escaped characters, leading/trailing
558
+ * punctuation) is preserved in its original position. The previous
559
+ * implementation stripped everything that wasn't `0 # . ,` before analysis
560
+ * and tried to re-inject a handful of literals afterwards, which caused
561
+ * formats like `0;(0)` (→ "(5)" for -5) and `#,##0.00;-#,##0.00` to drop
562
+ * their negative-section literal characters entirely.
563
+ */
564
+ function formatNumber(val, fmt) {
565
+ // Strip `[color]` and `[condition]` tags up front (Excel-style
566
+ // decorations that don't affect the numeric layout in our engine).
567
+ const cleanFmt = fmt.replace(/\[[^\]]*\]/g, "");
568
+ const tokens = [];
569
+ for (let i = 0; i < cleanFmt.length; i++) {
570
+ const ch = cleanFmt[i];
571
+ if (ch === '"') {
572
+ // Quoted literal run: consume until the closing quote.
573
+ let j = i + 1;
574
+ let buf = "";
575
+ while (j < cleanFmt.length && cleanFmt[j] !== '"') {
576
+ buf += cleanFmt[j];
577
+ j++;
578
+ }
579
+ tokens.push({ kind: "literal", text: buf });
580
+ i = j; // skip closing quote
581
+ continue;
582
+ }
583
+ if (ch === "\\" && i + 1 < cleanFmt.length) {
584
+ // Backslash escape: next character is a literal.
585
+ tokens.push({ kind: "literal", text: cleanFmt[i + 1] });
586
+ i++;
587
+ continue;
588
+ }
589
+ if (ch === "0" || ch === "#") {
590
+ tokens.push({ kind: "digit", char: ch });
591
+ continue;
592
+ }
593
+ if (ch === ".") {
594
+ tokens.push({ kind: "dot" });
595
+ continue;
596
+ }
597
+ if (ch === ",") {
598
+ tokens.push({ kind: "comma" });
599
+ continue;
600
+ }
601
+ // Any other character is a literal (space, parentheses, currency
602
+ // symbol, sign, hyphen, etc.).
603
+ tokens.push({ kind: "literal", text: ch });
604
+ }
605
+ // Count integer/fraction digit slots and decide whether to group.
606
+ let sawDot = false;
607
+ const intDigitSlots = [];
608
+ const fracDigitSlots = [];
609
+ let hasGrouping = false;
610
+ for (const t of tokens) {
611
+ if (t.kind === "dot") {
612
+ sawDot = true;
613
+ continue;
614
+ }
615
+ if (t.kind === "digit") {
616
+ if (sawDot) {
617
+ fracDigitSlots.push(t.char);
618
+ }
619
+ else {
620
+ intDigitSlots.push(t.char);
621
+ }
622
+ continue;
623
+ }
624
+ if (t.kind === "comma" && !sawDot) {
625
+ // A comma between digit slots means "thousands grouping".
626
+ hasGrouping = true;
627
+ }
628
+ }
629
+ if (intDigitSlots.length === 0 && fracDigitSlots.length === 0) {
630
+ return fmt; // Nothing to format; return the (raw) pattern.
631
+ }
632
+ // Round to the requested fractional precision.
633
+ const rounded = roundHalfAwayFromZeroFmt(val, fracDigitSlots.length);
634
+ const sign = rounded < 0 ? "-" : "";
635
+ const absStr = Math.abs(rounded).toFixed(fracDigitSlots.length);
636
+ const [intPartRaw, fracPart = ""] = absStr.split(".");
637
+ let intPart = intPartRaw;
638
+ // Pad the integer part to satisfy mandatory `0` slots.
639
+ const mandatoryInt = intDigitSlots.filter(s => s === "0").length;
640
+ if (intPart.length < mandatoryInt) {
641
+ intPart = intPart.padStart(mandatoryInt, "0");
642
+ }
643
+ // Apply thousand grouping if the pattern requested it.
644
+ let groupedInt = intPart;
645
+ if (hasGrouping) {
646
+ groupedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
647
+ }
648
+ // Walk the tokens again and materialise output, interleaving the
649
+ // integer run, decimal point, and fractional run with literals.
650
+ let out = "";
651
+ let intCursor = 0; // index into groupedInt (right-to-left on integer digits)
652
+ // We emit integer digits in source order using a trick: scan tokens
653
+ // left-to-right; the first integer digit slot consumes `groupedInt[0]`
654
+ // if integer has more digits than slots all extras come out before the
655
+ // first digit slot (Excel overflow rule).
656
+ const totalIntSlots = intDigitSlots.length;
657
+ // Determine how much "overflow" we need to prepend. Overflow digits
658
+ // appear right before the first digit token we encounter.
659
+ const overflowDigits = Math.max(0, groupedInt.length - totalIntSlots);
660
+ let fracCursor = 0;
661
+ let emittedOverflow = false;
662
+ let firstDigitTokenIdx = -1;
663
+ for (let k = 0; k < tokens.length; k++) {
664
+ if (tokens[k].kind === "digit") {
665
+ // We only care about the first *integer* digit token — one that
666
+ // comes before the dot. If the pattern has no integer slots, we
667
+ // still need to emit any overflow somewhere; pick the first digit
668
+ // token regardless.
669
+ firstDigitTokenIdx = k;
670
+ break;
671
+ }
672
+ if (tokens[k].kind === "dot") {
673
+ break;
674
+ }
675
+ }
676
+ for (let k = 0; k < tokens.length; k++) {
677
+ const t = tokens[k];
678
+ if (t.kind === "literal") {
679
+ out += t.text;
680
+ continue;
681
+ }
682
+ if (t.kind === "dot") {
683
+ if (fracDigitSlots.length > 0 || fracPart.length > 0) {
684
+ out += ".";
685
+ }
686
+ continue;
687
+ }
688
+ if (t.kind === "comma") {
689
+ // Grouping commas are consumed by the integer-build step above,
690
+ // so trailing commas here are literal (unusual but valid).
691
+ continue;
692
+ }
693
+ // digit token
694
+ if (sawDotAt(tokens, k)) {
695
+ // fractional slot
696
+ if (fracCursor < fracPart.length) {
697
+ out += fracPart[fracCursor];
698
+ }
699
+ else if (t.char === "0") {
700
+ out += "0";
701
+ }
702
+ fracCursor++;
703
+ continue;
704
+ }
705
+ // integer slot
706
+ if (k === firstDigitTokenIdx && !emittedOverflow) {
707
+ if (overflowDigits > 0) {
708
+ out += groupedInt.slice(0, overflowDigits);
709
+ }
710
+ emittedOverflow = true;
711
+ }
712
+ const slotIdx = intCursor;
713
+ // Integer slots map right-to-left onto `groupedInt`'s right-to-left
714
+ // ordering. We'll compute the source character for this slot from
715
+ // the right edge.
716
+ const srcIdx = overflowDigits + slotIdx;
717
+ if (srcIdx < groupedInt.length) {
718
+ out += groupedInt[srcIdx];
719
+ }
720
+ else if (t.char === "0") {
721
+ out += "0";
722
+ }
723
+ intCursor++;
724
+ }
725
+ return sign + out;
726
+ }
727
+ /**
728
+ * Did a dot token appear at any index < k in `tokens`? Used to classify
729
+ * each digit slot as integer vs fractional during the emit pass.
730
+ */
731
+ function sawDotAt(tokens, k) {
732
+ for (let i = 0; i < k; i++) {
733
+ if (tokens[i].kind === "dot") {
734
+ return true;
735
+ }
736
+ }
737
+ return false;
738
+ }
739
+ /**
740
+ * Round `val` to `decimals` fractional digits using Excel's half-away-
741
+ * from-zero convention (the same rule `formatNumber`'s caller relies on
742
+ * to keep `-0.5` displaying as `"-1"` instead of `"0"`).
743
+ */
744
+ function roundHalfAwayFromZeroFmt(val, decimals) {
745
+ if (!isFinite(val) || decimals < 0) {
746
+ return val;
747
+ }
748
+ const factor = Math.pow(10, decimals);
749
+ return ((val < 0 ? -1 : 1) * Math.round(Math.abs(val) * factor)) / factor;
750
+ }
751
+ /**
752
+ * Format a number in scientific notation: 0.00E+00
753
+ */
754
+ function formatScientific(val, fmt) {
755
+ const upper = fmt.toUpperCase();
756
+ const eIdx = upper.indexOf("E");
757
+ const beforeE = fmt.slice(0, eIdx);
758
+ const afterE = fmt.slice(eIdx + 1);
759
+ // Count decimal places in mantissa
760
+ const dotIdx = beforeE.indexOf(".");
761
+ const mantissaDecimals = dotIdx >= 0 ? beforeE.slice(dotIdx + 1).replace(/[^0#]/g, "").length : 0;
762
+ // Count exponent digits
763
+ const expSign = afterE[0] === "+" || afterE[0] === "-" ? afterE[0] : "+";
764
+ const expDigits = afterE.replace(/[^0#]/g, "").length;
765
+ const exp = val === 0 ? 0 : Math.floor(Math.log10(Math.abs(val)));
766
+ const mantissa = val / Math.pow(10, exp);
767
+ const expStr = (exp >= 0 && expSign === "+" ? "+" : exp < 0 ? "-" : "") +
768
+ String(Math.abs(exp)).padStart(expDigits, "0");
769
+ return mantissa.toFixed(mantissaDecimals) + "E" + expStr;
770
+ }
771
+ /**
772
+ * Format a number as a fraction: "# ?/?" or "# ??/??"
773
+ */
774
+ function formatFraction(val, fmt) {
775
+ const whole = Math.floor(Math.abs(val));
776
+ const frac = Math.abs(val) - whole;
777
+ const sign = val < 0 ? "-" : "";
778
+ if (frac === 0) {
779
+ if (fmt.includes("#") || fmt.includes("0")) {
780
+ return sign + whole + " ";
781
+ }
782
+ return sign + whole + " 0/1";
783
+ }
784
+ // Determine max denominator from ? count
785
+ const slashIdx = fmt.indexOf("/");
786
+ const denomPattern = fmt.slice(slashIdx + 1).replace(/[^?0#]/g, "");
787
+ const maxDenom = Math.pow(10, denomPattern.length) - 1;
788
+ // Find best fraction approximation
789
+ let bestNum = 0;
790
+ let bestDen = 1;
791
+ let bestError = Math.abs(frac);
792
+ for (let d = 1; d <= maxDenom; d++) {
793
+ const n = Math.round(frac * d);
794
+ const error = Math.abs(frac - n / d);
795
+ if (error < bestError) {
796
+ bestError = error;
797
+ bestNum = n;
798
+ bestDen = d;
799
+ if (error === 0) {
800
+ break;
801
+ }
802
+ }
803
+ }
804
+ const hasWholePart = fmt.indexOf("?") > 0 && fmt[0] !== "?" && fmt[0] !== "/";
805
+ if (hasWholePart) {
806
+ const numStr = String(bestNum).padStart(denomPattern.length, " ");
807
+ const denStr = String(bestDen).padStart(denomPattern.length, " ");
808
+ return sign + (whole > 0 ? whole + " " : "") + numStr + "/" + denStr;
809
+ }
810
+ return sign + (whole * bestDen + bestNum) + "/" + bestDen;
811
+ }
812
+ /**
813
+ * Day name arrays for date formatting.
814
+ */
815
+ const DAY_NAMES_FULL = [
816
+ "Sunday",
817
+ "Monday",
818
+ "Tuesday",
819
+ "Wednesday",
820
+ "Thursday",
821
+ "Friday",
822
+ "Saturday"
823
+ ];
824
+ const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
825
+ const MONTH_NAMES_FULL = [
826
+ "January",
827
+ "February",
828
+ "March",
829
+ "April",
830
+ "May",
831
+ "June",
832
+ "July",
833
+ "August",
834
+ "September",
835
+ "October",
836
+ "November",
837
+ "December"
838
+ ];
839
+ const MONTH_NAMES_SHORT = [
840
+ "Jan",
841
+ "Feb",
842
+ "Mar",
843
+ "Apr",
844
+ "May",
845
+ "Jun",
846
+ "Jul",
847
+ "Aug",
848
+ "Sep",
849
+ "Oct",
850
+ "Nov",
851
+ "Dec"
852
+ ];
853
+ /**
854
+ * Format a numeric Excel serial date with a date/time format code.
855
+ *
856
+ * In the native value system, dates are always numbers (serial values).
857
+ * We always use excelToDate() to convert and then read UTC fields so the
858
+ * output does not drift by a day in timezones west of UTC.
859
+ */
860
+ /**
861
+ * Format a serial-date value using an Excel-style pattern.
862
+ *
863
+ * Unlike a naive multi-pass regex approach, this implementation tokenises
864
+ * the pattern in a single left-to-right sweep and disambiguates the
865
+ * overloaded `m` / `mm` token by looking at its neighbours:
866
+ *
867
+ * - `mm` after `h:` or before `:ss` is rendered as **minutes**
868
+ * - otherwise `mm` is rendered as **month**
869
+ *
870
+ * Without this, a mixed format like `"yyyy-mm-dd hh:mm:ss"` would render
871
+ * the time's minutes as months (both tokens fire the same regex), giving
872
+ * garbage like `"2023-06-15 14:06:45"` for a timestamp at 14:30:45.
873
+ */
874
+ function formatDate(val, fmt) {
875
+ const d = (0, utils_base_1.excelToDate)(val, (0, _date_context_1.isDate1904)());
876
+ const year = d.getUTCFullYear();
877
+ const month0 = d.getUTCMonth();
878
+ const dayN = d.getUTCDate();
879
+ const dow = d.getUTCDay();
880
+ const fracDay = val % 1;
881
+ const totalSeconds = Math.round(Math.abs(fracDay) * 86400);
882
+ const hours = Math.floor(totalSeconds / 3600) % 24;
883
+ const minutes = Math.floor(totalSeconds / 60) % 60;
884
+ const seconds = totalSeconds % 60;
885
+ const hasAmPmToken = /AM\/PM/i.test(fmt) || /A\/P/i.test(fmt);
886
+ const h12 = hours % 12 === 0 ? 12 : hours % 12;
887
+ const ampm = hours < 12 ? "AM" : "PM";
888
+ const ap = hours < 12 ? "A" : "P";
889
+ const runs = [];
890
+ let i = 0;
891
+ while (i < fmt.length) {
892
+ const ch = fmt[i];
893
+ const lo = ch.toLowerCase();
894
+ // Handle two-char literals that must not tokenise as letters
895
+ if (fmt.slice(i, i + 5).toUpperCase() === "AM/PM") {
896
+ runs.push({ kind: "literal", text: ampm });
897
+ i += 5;
898
+ continue;
899
+ }
900
+ if (fmt.slice(i, i + 3).toUpperCase() === "A/P") {
901
+ runs.push({ kind: "literal", text: ap });
902
+ i += 3;
903
+ continue;
904
+ }
905
+ if (lo === "y" || lo === "m" || lo === "d" || lo === "h" || lo === "s") {
906
+ let j = i + 1;
907
+ while (j < fmt.length && fmt[j].toLowerCase() === lo) {
908
+ j++;
909
+ }
910
+ runs.push({ kind: "letters", lower: lo, count: j - i });
911
+ i = j;
912
+ continue;
913
+ }
914
+ // Literal: consume until the next token-letter (to keep literal runs
915
+ // contiguous and reduce array churn).
916
+ let j = i + 1;
917
+ while (j < fmt.length) {
918
+ const nx = fmt[j].toLowerCase();
919
+ if (nx === "y" || nx === "m" || nx === "d" || nx === "h" || nx === "s") {
920
+ break;
921
+ }
922
+ if (fmt.slice(j, j + 5).toUpperCase() === "AM/PM") {
923
+ break;
924
+ }
925
+ if (fmt.slice(j, j + 3).toUpperCase() === "A/P") {
926
+ break;
927
+ }
928
+ j++;
929
+ }
930
+ runs.push({ kind: "literal", text: fmt.slice(i, j) });
931
+ i = j;
932
+ }
933
+ // Phase 2: render each run, using neighbour context to decide whether
934
+ // an `m` / `mm` run means minute (when adjacent to an `h` or `s` run)
935
+ // or month (otherwise).
936
+ let out = "";
937
+ for (let r = 0; r < runs.length; r++) {
938
+ const run = runs[r];
939
+ if (run.kind === "literal") {
940
+ out += run.text;
941
+ continue;
942
+ }
943
+ switch (run.lower) {
944
+ case "y":
945
+ out += run.count >= 4 ? String(year).padStart(4, "0") : String(year).slice(-2);
946
+ break;
947
+ case "d":
948
+ if (run.count >= 4) {
949
+ out += DAY_NAMES_FULL[dow];
950
+ }
951
+ else if (run.count === 3) {
952
+ out += DAY_NAMES_SHORT[dow];
953
+ }
954
+ else if (run.count === 2) {
955
+ out += String(dayN).padStart(2, "0");
956
+ }
957
+ else {
958
+ out += String(dayN);
959
+ }
960
+ break;
961
+ case "h":
962
+ out +=
963
+ run.count >= 2
964
+ ? String(hasAmPmToken ? h12 : hours).padStart(2, "0")
965
+ : String(hasAmPmToken ? h12 : hours);
966
+ break;
967
+ case "s":
968
+ out += run.count >= 2 ? String(seconds).padStart(2, "0") : String(seconds);
969
+ break;
970
+ case "m": {
971
+ // Month-names render independent of context.
972
+ if (run.count === 4) {
973
+ out += MONTH_NAMES_FULL[month0];
974
+ break;
975
+ }
976
+ if (run.count === 3) {
977
+ out += MONTH_NAMES_SHORT[month0];
978
+ break;
979
+ }
980
+ // Otherwise `m` / `mm` is month-or-minute. Follow Excel's rule:
981
+ // treat as minute iff an adjacent letter-run is `h` or `s`.
982
+ const isMinute = isAdjacentTimeContext(runs, r);
983
+ const value = isMinute ? minutes : month0 + 1;
984
+ out += run.count >= 2 ? String(value).padStart(2, "0") : String(value);
985
+ break;
986
+ }
987
+ }
988
+ }
989
+ return out;
990
+ }
991
+ /**
992
+ * Determine whether the `m` / `mm` run at `runs[idx]` should render as
993
+ * minutes (true) or months (false). Excel's rule, simplified: minutes
994
+ * when the immediately preceding or following *letter* run is `h` or
995
+ * `s`, ignoring intervening literal separators like `:`.
996
+ */
997
+ function isAdjacentTimeContext(runs, idx) {
998
+ for (let j = idx - 1; j >= 0; j--) {
999
+ const p = runs[j];
1000
+ if (p.kind === "letters") {
1001
+ if (p.lower === "h" || p.lower === "s") {
1002
+ return true;
1003
+ }
1004
+ break;
1005
+ }
1006
+ }
1007
+ for (let j = idx + 1; j < runs.length; j++) {
1008
+ const p = runs[j];
1009
+ if (p.kind === "letters") {
1010
+ if (p.lower === "h" || p.lower === "s") {
1011
+ return true;
1012
+ }
1013
+ break;
1014
+ }
1015
+ }
1016
+ return false;
1017
+ }
1018
+ // ============================================================================
1019
+ // VALUE / EXACT
1020
+ // ============================================================================
1021
+ const fnVALUE = args => {
1022
+ const err = (0, _shared_1.checkError)(args[0]);
1023
+ if (err) {
1024
+ return err;
1025
+ }
1026
+ // Delegate to the central numeric-string parser. It rejects empty /
1027
+ // whitespace-only / Infinity / NaN / hex forms the way Excel does, which
1028
+ // a naive `Number(s)` would accept silently.
1029
+ return (0, values_1.toNumberRV)((0, values_1.topLeft)(args[0]));
1030
+ };
1031
+ exports.fnVALUE = fnVALUE;
1032
+ const fnEXACT = args => {
1033
+ const err0 = (0, _shared_1.checkError)(args[0]);
1034
+ if (err0) {
1035
+ return err0;
1036
+ }
1037
+ const err1 = (0, _shared_1.checkError)(args[1]);
1038
+ if (err1) {
1039
+ return err1;
1040
+ }
1041
+ return (0, values_1.rvBoolean)((0, values_1.toStringRV)(args[0]) === (0, values_1.toStringRV)(args[1]));
1042
+ };
1043
+ exports.fnEXACT = fnEXACT;
1044
+ // ============================================================================
1045
+ // Additional Text Functions
1046
+ // ============================================================================
1047
+ const fnCODE = args => {
1048
+ const err = (0, _shared_1.checkError)(args[0]);
1049
+ if (err) {
1050
+ return err;
1051
+ }
1052
+ const text = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1053
+ return text.length > 0 ? (0, values_1.rvNumber)(text.charCodeAt(0)) : values_1.ERRORS.VALUE;
1054
+ };
1055
+ exports.fnCODE = fnCODE;
1056
+ const fnCHAR = args => {
1057
+ const err = (0, _shared_1.checkError)(args[0]);
1058
+ if (err) {
1059
+ return err;
1060
+ }
1061
+ const nRV = (0, values_1.toNumberRV)((0, values_1.topLeft)(args[0]));
1062
+ if ((0, values_1.isError)(nRV)) {
1063
+ return nRV;
1064
+ }
1065
+ // Excel's CHAR accepts integers in [1, 255] only; outside the ANSI range
1066
+ // it returns #VALUE!. We also truncate fractional inputs toward zero to
1067
+ // match Excel's coercion semantics.
1068
+ const code = Math.trunc(nRV.value);
1069
+ if (code < 1 || code > 255) {
1070
+ return values_1.ERRORS.VALUE;
1071
+ }
1072
+ return (0, values_1.rvString)(String.fromCharCode(code));
1073
+ };
1074
+ exports.fnCHAR = fnCHAR;
1075
+ const fnCLEAN = args => {
1076
+ const err = (0, _shared_1.checkError)(args[0]);
1077
+ if (err) {
1078
+ return err;
1079
+ }
1080
+ const text = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1081
+ // Remove non-printable ASCII control characters (0x00-0x1F)
1082
+ let result = "";
1083
+ for (let i = 0; i < text.length; i++) {
1084
+ if (text.charCodeAt(i) >= 32) {
1085
+ result += text[i];
1086
+ }
1087
+ }
1088
+ return (0, values_1.rvString)(result);
1089
+ };
1090
+ exports.fnCLEAN = fnCLEAN;
1091
+ const fnT = args => {
1092
+ const v = (0, values_1.topLeft)(args[0]);
1093
+ if ((0, values_1.isError)(v)) {
1094
+ return v;
1095
+ }
1096
+ return v.kind === 2 /* RVKind.String */ ? v : (0, values_1.rvString)("");
1097
+ };
1098
+ exports.fnT = fnT;
1099
+ // ============================================================================
1100
+ // More Text Functions
1101
+ // ============================================================================
1102
+ const fnUNICHAR = args => {
1103
+ const err = (0, _shared_1.checkError)(args[0]);
1104
+ if (err) {
1105
+ return err;
1106
+ }
1107
+ const nRV = (0, values_1.toNumberRV)(args[0]);
1108
+ if ((0, values_1.isError)(nRV)) {
1109
+ return nRV;
1110
+ }
1111
+ const code = Math.floor(nRV.value);
1112
+ if (code < 1) {
1113
+ return values_1.ERRORS.VALUE;
1114
+ }
1115
+ try {
1116
+ return (0, values_1.rvString)(String.fromCodePoint(code));
1117
+ }
1118
+ catch {
1119
+ return values_1.ERRORS.VALUE;
1120
+ }
1121
+ };
1122
+ exports.fnUNICHAR = fnUNICHAR;
1123
+ const fnUNICODE = args => {
1124
+ const err = (0, _shared_1.checkError)(args[0]);
1125
+ if (err) {
1126
+ return err;
1127
+ }
1128
+ const text = (0, values_1.toStringRV)(args[0]);
1129
+ if (text.length === 0) {
1130
+ return values_1.ERRORS.VALUE;
1131
+ }
1132
+ const cp = text.codePointAt(0);
1133
+ return cp !== undefined ? (0, values_1.rvNumber)(cp) : values_1.ERRORS.VALUE;
1134
+ };
1135
+ exports.fnUNICODE = fnUNICODE;
1136
+ const fnBAHTTEXT = args => {
1137
+ const err = (0, _shared_1.checkError)(args[0]);
1138
+ if (err) {
1139
+ return err;
1140
+ }
1141
+ return (0, values_1.rvString)((0, values_1.toStringRV)(args[0]));
1142
+ };
1143
+ exports.fnBAHTTEXT = fnBAHTTEXT;
1144
+ const fnDOLLAR = args => {
1145
+ const numRV = (0, values_1.toNumberRV)(args[0]);
1146
+ if ((0, values_1.isError)(numRV)) {
1147
+ return numRV;
1148
+ }
1149
+ const num = numRV.value;
1150
+ let decimals;
1151
+ if (args.length > 1) {
1152
+ const decRV = (0, values_1.toNumberRV)(args[1]);
1153
+ if ((0, values_1.isError)(decRV)) {
1154
+ return decRV;
1155
+ }
1156
+ decimals = decRV.value;
1157
+ }
1158
+ else {
1159
+ decimals = 2;
1160
+ }
1161
+ const d = Math.floor(decimals);
1162
+ let rounded;
1163
+ if (d < 0) {
1164
+ const factor = Math.pow(10, -d);
1165
+ rounded = Math.round(Math.abs(num) / factor) * factor;
1166
+ }
1167
+ else {
1168
+ rounded = Math.abs(num);
1169
+ }
1170
+ const formatted = rounded.toFixed(Math.max(0, d));
1171
+ const parts = formatted.split(".");
1172
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1173
+ const result = parts.join(".");
1174
+ return (0, values_1.rvString)(num < 0 ? `($${result})` : `$${result}`);
1175
+ };
1176
+ exports.fnDOLLAR = fnDOLLAR;
1177
+ const fnFIXED = args => {
1178
+ const numRV = (0, values_1.toNumberRV)(args[0]);
1179
+ if ((0, values_1.isError)(numRV)) {
1180
+ return numRV;
1181
+ }
1182
+ const num = numRV.value;
1183
+ let decimals;
1184
+ if (args.length > 1) {
1185
+ const decRV = (0, values_1.toNumberRV)(args[1]);
1186
+ if ((0, values_1.isError)(decRV)) {
1187
+ return decRV;
1188
+ }
1189
+ decimals = decRV.value;
1190
+ }
1191
+ else {
1192
+ decimals = 2;
1193
+ }
1194
+ let noCommas;
1195
+ if (args.length > 2) {
1196
+ const ncRV = (0, values_1.toBooleanRV)(args[2]);
1197
+ if ((0, values_1.isError)(ncRV)) {
1198
+ return ncRV;
1199
+ }
1200
+ noCommas = ncRV.value;
1201
+ }
1202
+ else {
1203
+ noCommas = false;
1204
+ }
1205
+ const d = Math.floor(decimals);
1206
+ let rounded;
1207
+ if (d < 0) {
1208
+ const factor = Math.pow(10, -d);
1209
+ rounded = Math.round(num / factor) * factor;
1210
+ }
1211
+ else {
1212
+ rounded = num;
1213
+ }
1214
+ let result = rounded.toFixed(Math.max(0, d));
1215
+ if (!noCommas) {
1216
+ const parts = result.split(".");
1217
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1218
+ result = parts.join(".");
1219
+ }
1220
+ return (0, values_1.rvString)(result);
1221
+ };
1222
+ exports.fnFIXED = fnFIXED;
1223
+ const fnASC = args => {
1224
+ const err = (0, _shared_1.checkError)(args[0]);
1225
+ if (err) {
1226
+ return err;
1227
+ }
1228
+ const text = (0, values_1.toStringRV)(args[0]);
1229
+ return (0, values_1.rvString)(text.replace(/[\uFF01-\uFF5E]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 0xfee0)));
1230
+ };
1231
+ exports.fnASC = fnASC;
1232
+ const fnDBCS = args => {
1233
+ const err = (0, _shared_1.checkError)(args[0]);
1234
+ if (err) {
1235
+ return err;
1236
+ }
1237
+ const text = (0, values_1.toStringRV)(args[0]);
1238
+ return (0, values_1.rvString)(text.replace(/[!-~]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 0xfee0)));
1239
+ };
1240
+ exports.fnDBCS = fnDBCS;
1241
+ const fnJIS = args => (0, exports.fnDBCS)(args);
1242
+ exports.fnJIS = fnJIS;
1243
+ const fnPHONETIC = args => {
1244
+ const err = (0, _shared_1.checkError)(args[0]);
1245
+ if (err) {
1246
+ return err;
1247
+ }
1248
+ // Implicit intersection: array → top-left cell, matching the rest of
1249
+ // the text-function family.
1250
+ return (0, values_1.rvString)((0, values_1.toStringRV)((0, values_1.topLeft)(args[0])));
1251
+ };
1252
+ exports.fnPHONETIC = fnPHONETIC;
1253
+ const fnNUMBERVALUE = args => {
1254
+ const e0 = (0, _shared_1.checkError)(args[0]);
1255
+ if (e0) {
1256
+ return e0;
1257
+ }
1258
+ let text = (0, values_1.toStringRV)(args[0]);
1259
+ let decSep = ".";
1260
+ if (args.length > 1) {
1261
+ const e1 = (0, _shared_1.checkError)(args[1]);
1262
+ if (e1) {
1263
+ return e1;
1264
+ }
1265
+ decSep = (0, values_1.toStringRV)(args[1]);
1266
+ }
1267
+ let grpSep = ",";
1268
+ if (args.length > 2) {
1269
+ const e2 = (0, _shared_1.checkError)(args[2]);
1270
+ if (e2) {
1271
+ return e2;
1272
+ }
1273
+ grpSep = (0, values_1.toStringRV)(args[2]);
1274
+ }
1275
+ text = text.split(grpSep).join("");
1276
+ if (decSep !== ".") {
1277
+ text = text.replace(decSep, ".");
1278
+ }
1279
+ // Handle percentage
1280
+ const isPct = text.endsWith("%");
1281
+ if (isPct) {
1282
+ text = text.slice(0, -1);
1283
+ }
1284
+ // `Number("")` is 0, not NaN — reject empty / whitespace-only inputs
1285
+ // so `NUMBERVALUE("")` does not silently produce 0 (R6-P1-6).
1286
+ if (text.trim() === "") {
1287
+ return values_1.ERRORS.VALUE;
1288
+ }
1289
+ const n = Number(text);
1290
+ if (isNaN(n)) {
1291
+ return values_1.ERRORS.VALUE;
1292
+ }
1293
+ return (0, values_1.rvNumber)(isPct ? n / 100 : n);
1294
+ };
1295
+ exports.fnNUMBERVALUE = fnNUMBERVALUE;
1296
+ // ============================================================================
1297
+ // Excel 365 Text Functions: TEXTBEFORE, TEXTAFTER, TEXTSPLIT
1298
+ // ============================================================================
1299
+ /**
1300
+ * Parse the common [instance_num, match_mode, match_end, if_not_found]
1301
+ * tail used by TEXTBEFORE / TEXTAFTER. Returns the numeric values (with
1302
+ * defaults filled in) or an error if any argument is malformed.
1303
+ *
1304
+ * - match_mode: 0 = case-sensitive (default), 1 = case-insensitive.
1305
+ * - match_end: 0 = don't treat string edge as delimiter (default),
1306
+ * 1 = treat string edge as a virtual delimiter so that
1307
+ * TEXTAFTER with a missing delimiter returns "".
1308
+ */
1309
+ function parseTextBeforeAfterTail(args) {
1310
+ let inst = 1;
1311
+ if (args.length > 2) {
1312
+ const instRV = (0, values_1.toNumberRV)(args[2]);
1313
+ if ((0, values_1.isError)(instRV)) {
1314
+ return instRV;
1315
+ }
1316
+ inst = Math.trunc(instRV.value);
1317
+ }
1318
+ let matchMode = 0;
1319
+ if (args.length > 3) {
1320
+ const mmRV = (0, values_1.toNumberRV)(args[3]);
1321
+ if ((0, values_1.isError)(mmRV)) {
1322
+ return mmRV;
1323
+ }
1324
+ const mm = Math.trunc(mmRV.value);
1325
+ if (mm !== 0 && mm !== 1) {
1326
+ return values_1.ERRORS.VALUE;
1327
+ }
1328
+ matchMode = mm;
1329
+ }
1330
+ let matchEnd = 0;
1331
+ if (args.length > 4) {
1332
+ const meRV = (0, values_1.toNumberRV)(args[4]);
1333
+ if ((0, values_1.isError)(meRV)) {
1334
+ return meRV;
1335
+ }
1336
+ const me = Math.trunc(meRV.value);
1337
+ if (me !== 0 && me !== 1) {
1338
+ return values_1.ERRORS.VALUE;
1339
+ }
1340
+ matchEnd = me;
1341
+ }
1342
+ const ifNotFound = args.length > 5 ? args[5] : null;
1343
+ return { inst, matchMode, matchEnd, ifNotFound };
1344
+ }
1345
+ const fnTEXTBEFORE = args => {
1346
+ const e0 = (0, _shared_1.checkError)(args[0]);
1347
+ if (e0) {
1348
+ return e0;
1349
+ }
1350
+ const e1 = (0, _shared_1.checkError)(args[1]);
1351
+ if (e1) {
1352
+ return e1;
1353
+ }
1354
+ const text = (0, values_1.toStringRV)(args[0]);
1355
+ const delimiter = (0, values_1.toStringRV)(args[1]);
1356
+ const tail = parseTextBeforeAfterTail(args);
1357
+ if ("kind" in tail && tail.kind === 4 /* RVKind.Error */) {
1358
+ return tail;
1359
+ }
1360
+ const { inst, matchMode, matchEnd, ifNotFound } = tail;
1361
+ if (inst === 0) {
1362
+ return values_1.ERRORS.VALUE;
1363
+ }
1364
+ if (delimiter === "") {
1365
+ return (0, values_1.rvString)(inst > 0 ? "" : text);
1366
+ }
1367
+ // For case-insensitive matching we search within the lower-cased
1368
+ // haystack but slice against the original so the returned prefix/
1369
+ // suffix preserves the source text's case.
1370
+ const haystack = matchMode === 1 ? text.toLowerCase() : text;
1371
+ const needle = matchMode === 1 ? delimiter.toLowerCase() : delimiter;
1372
+ const notFound = () => {
1373
+ if (matchEnd === 1 && inst === 1) {
1374
+ // Treat the string end as a virtual delimiter: everything is "before".
1375
+ return (0, values_1.rvString)(text);
1376
+ }
1377
+ return ifNotFound !== null ? ifNotFound : values_1.ERRORS.NA;
1378
+ };
1379
+ if (inst > 0) {
1380
+ let pos = -1;
1381
+ for (let i = 0; i < inst; i++) {
1382
+ pos = haystack.indexOf(needle, pos + 1);
1383
+ if (pos === -1) {
1384
+ return notFound();
1385
+ }
1386
+ }
1387
+ return (0, values_1.rvString)(text.slice(0, pos));
1388
+ }
1389
+ // Negative: search from end
1390
+ let pos = haystack.length;
1391
+ for (let i = 0; i < -inst; i++) {
1392
+ pos = haystack.lastIndexOf(needle, pos - 1);
1393
+ if (pos === -1) {
1394
+ return notFound();
1395
+ }
1396
+ }
1397
+ return (0, values_1.rvString)(text.slice(0, pos));
1398
+ };
1399
+ exports.fnTEXTBEFORE = fnTEXTBEFORE;
1400
+ const fnTEXTAFTER = args => {
1401
+ const e0 = (0, _shared_1.checkError)(args[0]);
1402
+ if (e0) {
1403
+ return e0;
1404
+ }
1405
+ const e1 = (0, _shared_1.checkError)(args[1]);
1406
+ if (e1) {
1407
+ return e1;
1408
+ }
1409
+ const text = (0, values_1.toStringRV)(args[0]);
1410
+ const delimiter = (0, values_1.toStringRV)(args[1]);
1411
+ const tail = parseTextBeforeAfterTail(args);
1412
+ if ("kind" in tail && tail.kind === 4 /* RVKind.Error */) {
1413
+ return tail;
1414
+ }
1415
+ const { inst, matchMode, matchEnd, ifNotFound } = tail;
1416
+ if (inst === 0) {
1417
+ return values_1.ERRORS.VALUE;
1418
+ }
1419
+ if (delimiter === "") {
1420
+ return (0, values_1.rvString)(inst > 0 ? text : "");
1421
+ }
1422
+ const haystack = matchMode === 1 ? text.toLowerCase() : text;
1423
+ const needle = matchMode === 1 ? delimiter.toLowerCase() : delimiter;
1424
+ const notFound = () => {
1425
+ if (matchEnd === 1 && inst === 1) {
1426
+ // String end is a virtual delimiter → everything after it is "".
1427
+ return (0, values_1.rvString)("");
1428
+ }
1429
+ return ifNotFound !== null ? ifNotFound : values_1.ERRORS.NA;
1430
+ };
1431
+ if (inst > 0) {
1432
+ let pos = -1;
1433
+ for (let i = 0; i < inst; i++) {
1434
+ pos = haystack.indexOf(needle, pos + 1);
1435
+ if (pos === -1) {
1436
+ return notFound();
1437
+ }
1438
+ }
1439
+ return (0, values_1.rvString)(text.slice(pos + delimiter.length));
1440
+ }
1441
+ let pos = haystack.length;
1442
+ for (let i = 0; i < -inst; i++) {
1443
+ pos = haystack.lastIndexOf(needle, pos - 1);
1444
+ if (pos === -1) {
1445
+ return notFound();
1446
+ }
1447
+ }
1448
+ return (0, values_1.rvString)(text.slice(pos + delimiter.length));
1449
+ };
1450
+ exports.fnTEXTAFTER = fnTEXTAFTER;
1451
+ const fnTEXTSPLIT = args => {
1452
+ const e0 = (0, _shared_1.checkError)(args[0]);
1453
+ if (e0) {
1454
+ return e0;
1455
+ }
1456
+ const text = (0, values_1.toStringRV)(args[0]);
1457
+ let colDelimiter = "";
1458
+ if (args.length > 1) {
1459
+ const e1 = (0, _shared_1.checkError)(args[1]);
1460
+ if (e1) {
1461
+ return e1;
1462
+ }
1463
+ colDelimiter = (0, values_1.toStringRV)(args[1]);
1464
+ }
1465
+ const rowDelimiter = args.length > 2 && args[2].kind !== 0 /* RVKind.Blank */ ? (0, values_1.toStringRV)(args[2]) : "";
1466
+ // `ignore_empty` (4th arg, default FALSE) — when TRUE, suppress empty
1467
+ // fragments produced by consecutive delimiters.
1468
+ let ignoreEmpty = false;
1469
+ if (args.length > 3 && args[3].kind !== 0 /* RVKind.Blank */) {
1470
+ const ieRV = (0, values_1.toBooleanRV)(args[3]);
1471
+ if ((0, values_1.isError)(ieRV)) {
1472
+ return ieRV;
1473
+ }
1474
+ ignoreEmpty = ieRV.value;
1475
+ }
1476
+ // `match_mode` (5th arg, default 0 = case-sensitive). When 1 the
1477
+ // delimiter match is case-insensitive; we implement that by lowercasing
1478
+ // both the haystack and the delimiter(s) before splitting, which is
1479
+ // consistent with Excel's specification for TEXTSPLIT.
1480
+ let matchMode = 0;
1481
+ if (args.length > 4 && args[4].kind !== 0 /* RVKind.Blank */) {
1482
+ const mmRV = (0, values_1.toNumberRV)(args[4]);
1483
+ if ((0, values_1.isError)(mmRV)) {
1484
+ return mmRV;
1485
+ }
1486
+ matchMode = Math.trunc(mmRV.value);
1487
+ if (matchMode !== 0 && matchMode !== 1) {
1488
+ return values_1.ERRORS.VALUE;
1489
+ }
1490
+ }
1491
+ // `pad_with` (6th arg, default #N/A) — value used to fill shorter rows
1492
+ // when the split produces a ragged 2D shape. Explicit error arguments
1493
+ // (e.g. `TEXTSPLIT(…, #VALUE!)`) propagate into the pad cells verbatim,
1494
+ // matching Excel.
1495
+ const pad = args.length > 5 ? (0, values_1.topLeft)(args[5]) : values_1.ERRORS.NA;
1496
+ const splitString = (s, delim) => {
1497
+ if (!delim) {
1498
+ return [s];
1499
+ }
1500
+ if (matchMode === 1) {
1501
+ // Case-insensitive split — find positions by scanning the lowercased
1502
+ // haystack but slice the original so case is preserved in output.
1503
+ const haystack = s.toLowerCase();
1504
+ const needle = delim.toLowerCase();
1505
+ const parts = [];
1506
+ let last = 0;
1507
+ let i = 0;
1508
+ while (i <= haystack.length - needle.length) {
1509
+ if (haystack.slice(i, i + needle.length) === needle) {
1510
+ parts.push(s.slice(last, i));
1511
+ i += needle.length;
1512
+ last = i;
1513
+ }
1514
+ else {
1515
+ i++;
1516
+ }
1517
+ }
1518
+ parts.push(s.slice(last));
1519
+ return parts;
1520
+ }
1521
+ return s.split(delim);
1522
+ };
1523
+ let rows;
1524
+ if (rowDelimiter) {
1525
+ rows = splitString(text, rowDelimiter);
1526
+ }
1527
+ else {
1528
+ rows = [text];
1529
+ }
1530
+ // Split each row into columns, applying ignore_empty per row after the
1531
+ // split. When ignore_empty is TRUE at the row level we also drop rows
1532
+ // that were themselves empty (i.e. empty string from consecutive row
1533
+ // delimiters).
1534
+ const matrix = [];
1535
+ let maxWidth = 0;
1536
+ for (const row of rows) {
1537
+ if (ignoreEmpty && row === "") {
1538
+ continue;
1539
+ }
1540
+ let parts;
1541
+ if (colDelimiter) {
1542
+ parts = splitString(row, colDelimiter);
1543
+ if (ignoreEmpty) {
1544
+ parts = parts.filter(p => p !== "");
1545
+ }
1546
+ }
1547
+ else {
1548
+ parts = [row];
1549
+ }
1550
+ if (parts.length === 0) {
1551
+ // All fragments were empty and ignore_empty discarded them; keep a
1552
+ // pad row so the result is still a well-formed rectangle.
1553
+ parts = [""];
1554
+ }
1555
+ matrix.push(parts.map(p => (0, values_1.rvString)(p)));
1556
+ if (parts.length > maxWidth) {
1557
+ maxWidth = parts.length;
1558
+ }
1559
+ }
1560
+ if (matrix.length === 0) {
1561
+ // ignore_empty consumed everything → return a single pad cell so the
1562
+ // array is still a valid 1×1 spill (matches Excel).
1563
+ return (0, values_1.rvArray)([[pad]]);
1564
+ }
1565
+ // Pad ragged rows out to the maximum width with `pad_with`.
1566
+ const result = [];
1567
+ for (const row of matrix) {
1568
+ if (row.length < maxWidth) {
1569
+ const padded = row.slice();
1570
+ while (padded.length < maxWidth) {
1571
+ padded.push(pad);
1572
+ }
1573
+ result.push(padded);
1574
+ }
1575
+ else {
1576
+ result.push(row);
1577
+ }
1578
+ }
1579
+ return (0, values_1.rvArray)(result);
1580
+ };
1581
+ exports.fnTEXTSPLIT = fnTEXTSPLIT;
1582
+ // ============================================================================
1583
+ // REGEX family (Excel 365, 2024)
1584
+ // ============================================================================
1585
+ /**
1586
+ * Convert an Excel REGEX pattern to a JavaScript RegExp. Excel's regex
1587
+ * dialect is close to PCRE; JavaScript's RegExp is close enough for the
1588
+ * vast majority of practical patterns, but a few constructs (named
1589
+ * captures, look-behind, some Unicode classes) behave slightly
1590
+ * differently. We pass patterns through as-is and let JavaScript's
1591
+ * parser surface #VALUE! on the rare incompatibility.
1592
+ */
1593
+ function compileExcelRegex(pattern, caseSensitive, global) {
1594
+ try {
1595
+ let flags = "u";
1596
+ if (!caseSensitive) {
1597
+ flags += "i";
1598
+ }
1599
+ if (global) {
1600
+ flags += "g";
1601
+ }
1602
+ return new RegExp(pattern, flags);
1603
+ }
1604
+ catch {
1605
+ return null;
1606
+ }
1607
+ }
1608
+ /**
1609
+ * Resolve the optional `case_sensitivity` argument used by every REGEX
1610
+ * function. `0`/FALSE/omitted → case-insensitive (Excel default),
1611
+ * any other value → case-sensitive. Errors propagate.
1612
+ */
1613
+ function resolveCaseSensitivity(arg) {
1614
+ if (arg === undefined) {
1615
+ return { caseSensitive: false };
1616
+ }
1617
+ // Accept boolean or number; anything else coerced via toBooleanRV.
1618
+ const b = (0, values_1.toBooleanRV)(arg);
1619
+ if ((0, values_1.isError)(b)) {
1620
+ return b;
1621
+ }
1622
+ return { caseSensitive: b.value };
1623
+ }
1624
+ /**
1625
+ * REGEXTEST(text, pattern, [case_sensitivity]) — returns TRUE iff the
1626
+ * regex matches any substring of `text`.
1627
+ */
1628
+ const fnREGEXTEST = args => {
1629
+ const textV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1630
+ const patternV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[1]));
1631
+ const cs = resolveCaseSensitivity(args[2]);
1632
+ if ("kind" in cs) {
1633
+ return cs; // error
1634
+ }
1635
+ const errCheck = (0, _shared_1.checkError)(args[0]) ?? (0, _shared_1.checkError)(args[1]);
1636
+ if (errCheck) {
1637
+ return errCheck;
1638
+ }
1639
+ const re = compileExcelRegex(patternV, cs.caseSensitive, false);
1640
+ if (!re) {
1641
+ return values_1.ERRORS.VALUE;
1642
+ }
1643
+ return (0, values_1.rvBoolean)(re.test(textV));
1644
+ };
1645
+ exports.fnREGEXTEST = fnREGEXTEST;
1646
+ /**
1647
+ * REGEXEXTRACT(text, pattern, [return_mode], [case_sensitivity]) —
1648
+ * return_mode = 0 (default) → first match as a string
1649
+ * return_mode = 1 → all matches as a 1-column array
1650
+ * return_mode = 2 → capture groups of the first match as a 1-row array
1651
+ */
1652
+ const fnREGEXEXTRACT = args => {
1653
+ const textV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1654
+ const patternV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[1]));
1655
+ const errCheck = (0, _shared_1.checkError)(args[0]) ?? (0, _shared_1.checkError)(args[1]);
1656
+ if (errCheck) {
1657
+ return errCheck;
1658
+ }
1659
+ const modeV = args.length > 2 ? (0, values_1.toNumberRV)((0, values_1.topLeft)(args[2])) : (0, values_1.rvNumber)(0);
1660
+ if ((0, values_1.isError)(modeV)) {
1661
+ return modeV;
1662
+ }
1663
+ const mode = Math.trunc(modeV.value);
1664
+ if (mode !== 0 && mode !== 1 && mode !== 2) {
1665
+ return values_1.ERRORS.VALUE;
1666
+ }
1667
+ const cs = resolveCaseSensitivity(args[3]);
1668
+ if ("kind" in cs) {
1669
+ return cs;
1670
+ }
1671
+ const needGlobal = mode === 1;
1672
+ const re = compileExcelRegex(patternV, cs.caseSensitive, needGlobal);
1673
+ if (!re) {
1674
+ return values_1.ERRORS.VALUE;
1675
+ }
1676
+ if (mode === 0) {
1677
+ const m = re.exec(textV);
1678
+ if (!m) {
1679
+ return values_1.ERRORS.NA;
1680
+ }
1681
+ return (0, values_1.rvString)(m[0]);
1682
+ }
1683
+ if (mode === 1) {
1684
+ const matches = [];
1685
+ let m;
1686
+ // eslint-disable-next-line no-cond-assign
1687
+ while ((m = re.exec(textV)) !== null) {
1688
+ matches.push(m[0]);
1689
+ // Guard against zero-length matches causing an infinite loop.
1690
+ if (m.index === re.lastIndex) {
1691
+ re.lastIndex++;
1692
+ }
1693
+ }
1694
+ if (matches.length === 0) {
1695
+ return values_1.ERRORS.NA;
1696
+ }
1697
+ return (0, values_1.rvArray)(matches.map(s => [(0, values_1.rvString)(s)]));
1698
+ }
1699
+ // mode === 2 — capture groups of first match as a row array.
1700
+ const m = re.exec(textV);
1701
+ if (!m) {
1702
+ return values_1.ERRORS.NA;
1703
+ }
1704
+ // Exclude the full-match element (index 0) — only capture groups.
1705
+ if (m.length <= 1) {
1706
+ // No capture groups defined in the pattern — return the full match.
1707
+ return (0, values_1.rvArray)([[(0, values_1.rvString)(m[0])]]);
1708
+ }
1709
+ const row = [];
1710
+ for (let i = 1; i < m.length; i++) {
1711
+ row.push((0, values_1.rvString)(m[i] ?? ""));
1712
+ }
1713
+ return (0, values_1.rvArray)([row]);
1714
+ };
1715
+ exports.fnREGEXEXTRACT = fnREGEXEXTRACT;
1716
+ /**
1717
+ * REGEXREPLACE(text, pattern, replacement, [occurrence], [case_sensitivity]) —
1718
+ * occurrence = 0 (default) → replace all
1719
+ * occurrence = n (positive) → replace only the n-th match
1720
+ * occurrence = n (negative) → replace only the n-th-last match
1721
+ */
1722
+ const fnREGEXREPLACE = args => {
1723
+ const textV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1724
+ const patternV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[1]));
1725
+ const replacementV = (0, values_1.toStringRV)((0, values_1.topLeft)(args[2]));
1726
+ const errCheck = (0, _shared_1.checkError)(args[0]) ?? (0, _shared_1.checkError)(args[1]) ?? (0, _shared_1.checkError)(args[2]);
1727
+ if (errCheck) {
1728
+ return errCheck;
1729
+ }
1730
+ const occurrenceV = args.length > 3 ? (0, values_1.toNumberRV)((0, values_1.topLeft)(args[3])) : (0, values_1.rvNumber)(0);
1731
+ if ((0, values_1.isError)(occurrenceV)) {
1732
+ return occurrenceV;
1733
+ }
1734
+ const occurrence = Math.trunc(occurrenceV.value);
1735
+ const cs = resolveCaseSensitivity(args[4]);
1736
+ if ("kind" in cs) {
1737
+ return cs;
1738
+ }
1739
+ // Always compile with the global flag — we need to enumerate matches
1740
+ // to apply the occurrence filter; `String.replace` without `/g` would
1741
+ // only see the first match and we wouldn't be able to address later
1742
+ // hits for `occurrence > 1`.
1743
+ const re = compileExcelRegex(patternV, cs.caseSensitive, true);
1744
+ if (!re) {
1745
+ return values_1.ERRORS.VALUE;
1746
+ }
1747
+ if (occurrence === 0) {
1748
+ // Replace all.
1749
+ return (0, values_1.rvString)(textV.replace(re, replacementV));
1750
+ }
1751
+ // Collect every match's range so we can address them by index.
1752
+ const ranges = [];
1753
+ let m;
1754
+ // eslint-disable-next-line no-cond-assign
1755
+ while ((m = re.exec(textV)) !== null) {
1756
+ ranges.push({ start: m.index, end: m.index + m[0].length });
1757
+ if (m.index === re.lastIndex) {
1758
+ re.lastIndex++;
1759
+ }
1760
+ }
1761
+ if (ranges.length === 0) {
1762
+ return (0, values_1.rvString)(textV); // no match → unchanged (Excel behavior)
1763
+ }
1764
+ // Negative index counts from the end; -1 is the last match.
1765
+ const idx = occurrence > 0 ? occurrence - 1 : ranges.length + occurrence;
1766
+ if (idx < 0 || idx >= ranges.length) {
1767
+ // Out-of-range occurrence → unchanged (Excel behavior).
1768
+ return (0, values_1.rvString)(textV);
1769
+ }
1770
+ const { start, end } = ranges[idx];
1771
+ return (0, values_1.rvString)(textV.slice(0, start) + replacementV + textV.slice(end));
1772
+ };
1773
+ exports.fnREGEXREPLACE = fnREGEXREPLACE;
1774
+ // ============================================================================
1775
+ // VALUETOTEXT / ARRAYTOTEXT (Excel 365)
1776
+ // ============================================================================
1777
+ /**
1778
+ * Format a single scalar for VALUETOTEXT / ARRAYTOTEXT.
1779
+ *
1780
+ * Format 0 (concise, default):
1781
+ * - Number → plain number string
1782
+ * - String → the string itself (no quotes)
1783
+ * - Boolean → "TRUE" / "FALSE"
1784
+ * - Error → error text (e.g. "#N/A")
1785
+ * - Blank → ""
1786
+ *
1787
+ * Format 1 (strict):
1788
+ * - String → wrapped in double quotes with `""` escapes
1789
+ * - Everything else → same as format 0
1790
+ */
1791
+ function scalarToText(v, strict) {
1792
+ switch (v.kind) {
1793
+ case 1 /* RVKind.Number */:
1794
+ return String(v.value);
1795
+ case 2 /* RVKind.String */:
1796
+ if (strict) {
1797
+ return `"${v.value.replace(/"/g, '""')}"`;
1798
+ }
1799
+ return v.value;
1800
+ case 3 /* RVKind.Boolean */:
1801
+ return v.value ? "TRUE" : "FALSE";
1802
+ case 4 /* RVKind.Error */:
1803
+ return v.code;
1804
+ case 0 /* RVKind.Blank */:
1805
+ return "";
1806
+ }
1807
+ }
1808
+ /**
1809
+ * VALUETOTEXT(value, [format]) — format a scalar or 1×1 array as text.
1810
+ * For multi-cell arrays, this applies implicit intersection at the
1811
+ * evaluator layer — so by the time we see args[0] it is already scalar.
1812
+ */
1813
+ const fnVALUETOTEXT = args => {
1814
+ const formatV = args.length > 1 ? (0, values_1.toNumberRV)((0, values_1.topLeft)(args[1])) : (0, values_1.rvNumber)(0);
1815
+ if ((0, values_1.isError)(formatV)) {
1816
+ return formatV;
1817
+ }
1818
+ const fmt = Math.trunc(formatV.value);
1819
+ if (fmt !== 0 && fmt !== 1) {
1820
+ return values_1.ERRORS.VALUE;
1821
+ }
1822
+ const strict = fmt === 1;
1823
+ return (0, values_1.rvString)(scalarToText((0, values_1.topLeft)(args[0]), strict));
1824
+ };
1825
+ exports.fnVALUETOTEXT = fnVALUETOTEXT;
1826
+ /**
1827
+ * ARRAYTOTEXT(array, [format]) — flatten an array to a delimited text
1828
+ * representation.
1829
+ *
1830
+ * Format 0 (concise, default): row-major join with ", ".
1831
+ * Format 1 (strict): wraps output in `{…}`, rows separated by `;`,
1832
+ * cells by `,`; strings inside quoted.
1833
+ */
1834
+ const fnARRAYTOTEXT = args => {
1835
+ const formatV = args.length > 1 ? (0, values_1.toNumberRV)((0, values_1.topLeft)(args[1])) : (0, values_1.rvNumber)(0);
1836
+ if ((0, values_1.isError)(formatV)) {
1837
+ return formatV;
1838
+ }
1839
+ const fmt = Math.trunc(formatV.value);
1840
+ if (fmt !== 0 && fmt !== 1) {
1841
+ return values_1.ERRORS.VALUE;
1842
+ }
1843
+ const strict = fmt === 1;
1844
+ const arg = args[0];
1845
+ if (arg.kind !== 5 /* RVKind.Array */) {
1846
+ return (0, values_1.rvString)(scalarToText((0, values_1.topLeft)(arg), strict));
1847
+ }
1848
+ if (!strict) {
1849
+ // Concise: flatten row-major, join with ", ".
1850
+ const parts = [];
1851
+ for (const row of arg.rows) {
1852
+ for (const cell of row) {
1853
+ parts.push(scalarToText(cell, false));
1854
+ }
1855
+ }
1856
+ return (0, values_1.rvString)(parts.join(", "));
1857
+ }
1858
+ // Strict: `{row1;row2;...}` with rows as `a,b,c` and strings quoted.
1859
+ const rowStrs = [];
1860
+ for (const row of arg.rows) {
1861
+ const cellStrs = [];
1862
+ for (const cell of row) {
1863
+ cellStrs.push(scalarToText(cell, true));
1864
+ }
1865
+ rowStrs.push(cellStrs.join(","));
1866
+ }
1867
+ return (0, values_1.rvString)(`{${rowStrs.join(";")}}`);
1868
+ };
1869
+ exports.fnARRAYTOTEXT = fnARRAYTOTEXT;
1870
+ // ============================================================================
1871
+ // ENCODEURL
1872
+ // ============================================================================
1873
+ /**
1874
+ * ENCODEURL(text) — percent-encode a string for URL use.
1875
+ *
1876
+ * Excel follows RFC 3986's "unreserved" character rule: A-Z, a-z, 0-9,
1877
+ * and `- _ . ~` are kept verbatim; everything else is encoded as
1878
+ * `%HH` using the UTF-8 byte sequence. This is exactly what JavaScript's
1879
+ * `encodeURIComponent` does, so we delegate to it.
1880
+ */
1881
+ const fnENCODEURL = args => {
1882
+ const err = (0, _shared_1.checkError)(args[0]);
1883
+ if (err) {
1884
+ return err;
1885
+ }
1886
+ const s = (0, values_1.toStringRV)((0, values_1.topLeft)(args[0]));
1887
+ return (0, values_1.rvString)(encodeURIComponent(s));
1888
+ };
1889
+ exports.fnENCODEURL = fnENCODEURL;