@rowsncolumns/toolkit 24.4.1

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 (456) hide show
  1. package/LICENSE.md +100 -0
  2. package/Readme.md +27 -0
  3. package/dist/cjs/autofilter-condition.d.ts +43 -0
  4. package/dist/cjs/autofilter-condition.d.ts.map +1 -0
  5. package/dist/cjs/autofilter-condition.js +155 -0
  6. package/dist/cjs/csv-export.d.ts +49 -0
  7. package/dist/cjs/csv-export.d.ts.map +1 -0
  8. package/dist/cjs/csv-export.js +142 -0
  9. package/dist/cjs/csv-import-worker.d.ts +16 -0
  10. package/dist/cjs/csv-import-worker.d.ts.map +1 -0
  11. package/dist/cjs/csv-import-worker.js +60 -0
  12. package/dist/cjs/csv-import.d.ts +38 -0
  13. package/dist/cjs/csv-import.d.ts.map +1 -0
  14. package/dist/cjs/csv-import.js +166 -0
  15. package/dist/cjs/csv-parser/helpers.d.ts +14 -0
  16. package/dist/cjs/csv-parser/helpers.d.ts.map +1 -0
  17. package/dist/cjs/csv-parser/helpers.js +145 -0
  18. package/dist/cjs/csv-parser/types.d.ts +66 -0
  19. package/dist/cjs/csv-parser/types.d.ts.map +1 -0
  20. package/dist/cjs/csv-parser/types.js +14 -0
  21. package/dist/cjs/csv-parser/worker.d.ts +2 -0
  22. package/dist/cjs/csv-parser/worker.d.ts.map +1 -0
  23. package/dist/cjs/csv-parser/worker.js +200 -0
  24. package/dist/cjs/excel-export.d.ts +11 -0
  25. package/dist/cjs/excel-export.d.ts.map +1 -0
  26. package/dist/cjs/excel-export.js +22 -0
  27. package/dist/cjs/excel-import-session.d.ts +51 -0
  28. package/dist/cjs/excel-import-session.d.ts.map +1 -0
  29. package/dist/cjs/excel-import-session.js +307 -0
  30. package/dist/cjs/excel-import-worker.d.ts +7 -0
  31. package/dist/cjs/excel-import-worker.d.ts.map +1 -0
  32. package/dist/cjs/excel-import-worker.js +119 -0
  33. package/dist/cjs/excel-parser/builtin-numfmts.d.ts +24 -0
  34. package/dist/cjs/excel-parser/builtin-numfmts.d.ts.map +1 -0
  35. package/dist/cjs/excel-parser/builtin-numfmts.js +63 -0
  36. package/dist/cjs/excel-parser/cfb-decryptor.d.ts +53 -0
  37. package/dist/cjs/excel-parser/cfb-decryptor.d.ts.map +1 -0
  38. package/dist/cjs/excel-parser/cfb-decryptor.js +553 -0
  39. package/dist/cjs/excel-parser/cfb-encryptor.d.ts +23 -0
  40. package/dist/cjs/excel-parser/cfb-encryptor.d.ts.map +1 -0
  41. package/dist/cjs/excel-parser/cfb-encryptor.js +200 -0
  42. package/dist/cjs/excel-parser/dv-cell-stamp.d.ts +29 -0
  43. package/dist/cjs/excel-parser/dv-cell-stamp.d.ts.map +1 -0
  44. package/dist/cjs/excel-parser/dv-cell-stamp.js +239 -0
  45. package/dist/cjs/excel-parser/helpers.d.ts +28 -0
  46. package/dist/cjs/excel-parser/helpers.d.ts.map +1 -0
  47. package/dist/cjs/excel-parser/helpers.js +188 -0
  48. package/dist/cjs/excel-parser/interface.d.ts +179 -0
  49. package/dist/cjs/excel-parser/interface.d.ts.map +1 -0
  50. package/dist/cjs/excel-parser/interface.js +1 -0
  51. package/dist/cjs/excel-parser/plugins/base/index.d.ts +31 -0
  52. package/dist/cjs/excel-parser/plugins/base/index.d.ts.map +1 -0
  53. package/dist/cjs/excel-parser/plugins/base/index.js +101 -0
  54. package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.d.ts +16 -0
  55. package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.d.ts.map +1 -0
  56. package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.js +488 -0
  57. package/dist/cjs/excel-parser/plugins/ods/data-validations.d.ts +15 -0
  58. package/dist/cjs/excel-parser/plugins/ods/data-validations.d.ts.map +1 -0
  59. package/dist/cjs/excel-parser/plugins/ods/data-validations.js +353 -0
  60. package/dist/cjs/excel-parser/plugins/ods/formula.d.ts +8 -0
  61. package/dist/cjs/excel-parser/plugins/ods/formula.d.ts.map +1 -0
  62. package/dist/cjs/excel-parser/plugins/ods/formula.js +82 -0
  63. package/dist/cjs/excel-parser/plugins/ods/index.d.ts +122 -0
  64. package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -0
  65. package/dist/cjs/excel-parser/plugins/ods/index.js +3642 -0
  66. package/dist/cjs/excel-parser/plugins/ods/sparklines.d.ts +25 -0
  67. package/dist/cjs/excel-parser/plugins/ods/sparklines.d.ts.map +1 -0
  68. package/dist/cjs/excel-parser/plugins/ods/sparklines.js +221 -0
  69. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +60 -0
  70. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -0
  71. package/dist/cjs/excel-parser/plugins/ods/styles.js +808 -0
  72. package/dist/cjs/excel-parser/plugins/ods/types.d.ts +7 -0
  73. package/dist/cjs/excel-parser/plugins/ods/types.d.ts.map +1 -0
  74. package/dist/cjs/excel-parser/plugins/ods/types.js +1 -0
  75. package/dist/cjs/excel-parser/plugins/xls/index.d.ts +5 -0
  76. package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -0
  77. package/dist/cjs/excel-parser/plugins/xls/index.js +19 -0
  78. package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.d.ts +13 -0
  79. package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.d.ts.map +1 -0
  80. package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.js +49 -0
  81. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +117 -0
  82. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -0
  83. package/dist/cjs/excel-parser/plugins/xlsb/index.js +2972 -0
  84. package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts +18 -0
  85. package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -0
  86. package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.js +527 -0
  87. package/dist/cjs/excel-parser/plugins/xlsm/index.d.ts +5 -0
  88. package/dist/cjs/excel-parser/plugins/xlsm/index.d.ts.map +1 -0
  89. package/dist/cjs/excel-parser/plugins/xlsm/index.js +10 -0
  90. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +60 -0
  91. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -0
  92. package/dist/cjs/excel-parser/plugins/xlsx/charts.js +2178 -0
  93. package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts +18 -0
  94. package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -0
  95. package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.js +620 -0
  96. package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts +4 -0
  97. package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -0
  98. package/dist/cjs/excel-parser/plugins/xlsx/data-validations.js +515 -0
  99. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +39 -0
  100. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -0
  101. package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +941 -0
  102. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +320 -0
  103. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -0
  104. package/dist/cjs/excel-parser/plugins/xlsx/index.js +6056 -0
  105. package/dist/cjs/excel-parser/plugins/xlsx/numfmt.d.ts +2 -0
  106. package/dist/cjs/excel-parser/plugins/xlsx/numfmt.d.ts.map +1 -0
  107. package/dist/cjs/excel-parser/plugins/xlsx/numfmt.js +3 -0
  108. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts +40 -0
  109. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -0
  110. package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +342 -0
  111. package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts +8 -0
  112. package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -0
  113. package/dist/cjs/excel-parser/plugins/xlsx/relationships.js +26 -0
  114. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts +26 -0
  115. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -0
  116. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.js +59 -0
  117. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts +44 -0
  118. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -0
  119. package/dist/cjs/excel-parser/plugins/xlsx/styles.js +1377 -0
  120. package/dist/cjs/excel-parser/plugins/xlsx/theme.d.ts +50 -0
  121. package/dist/cjs/excel-parser/plugins/xlsx/theme.d.ts.map +1 -0
  122. package/dist/cjs/excel-parser/plugins/xlsx/theme.js +150 -0
  123. package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.d.ts +47 -0
  124. package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.d.ts.map +1 -0
  125. package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.js +141 -0
  126. package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.d.ts +44 -0
  127. package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.d.ts.map +1 -0
  128. package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.js +407 -0
  129. package/dist/cjs/excel-parser/registry.d.ts +5 -0
  130. package/dist/cjs/excel-parser/registry.d.ts.map +1 -0
  131. package/dist/cjs/excel-parser/registry.js +21 -0
  132. package/dist/cjs/excel-parser/style-registry.d.ts +51 -0
  133. package/dist/cjs/excel-parser/style-registry.d.ts.map +1 -0
  134. package/dist/cjs/excel-parser/style-registry.js +175 -0
  135. package/dist/cjs/excel-parser/theme-default.d.ts +3 -0
  136. package/dist/cjs/excel-parser/theme-default.d.ts.map +1 -0
  137. package/dist/cjs/excel-parser/theme-default.js +19 -0
  138. package/dist/cjs/excel-parser/types.d.ts +365 -0
  139. package/dist/cjs/excel-parser/types.d.ts.map +1 -0
  140. package/dist/cjs/excel-parser/types.js +59 -0
  141. package/dist/cjs/excel-parser/utils.d.ts +6 -0
  142. package/dist/cjs/excel-parser/utils.d.ts.map +1 -0
  143. package/dist/cjs/excel-parser/utils.js +30 -0
  144. package/dist/cjs/excel-parser/workbook.d.ts +51 -0
  145. package/dist/cjs/excel-parser/workbook.d.ts.map +1 -0
  146. package/dist/cjs/excel-parser/workbook.js +317 -0
  147. package/dist/cjs/excel-parser/worker.d.ts +2 -0
  148. package/dist/cjs/excel-parser/worker.d.ts.map +1 -0
  149. package/dist/cjs/excel-parser/worker.js +1149 -0
  150. package/dist/cjs/excel-parser/zip-parser-streaming.d.ts +13 -0
  151. package/dist/cjs/excel-parser/zip-parser-streaming.d.ts.map +1 -0
  152. package/dist/cjs/excel-parser/zip-parser-streaming.js +200 -0
  153. package/dist/cjs/excel-parser/zip-parser.d.ts +11 -0
  154. package/dist/cjs/excel-parser/zip-parser.d.ts.map +1 -0
  155. package/dist/cjs/excel-parser/zip-parser.js +49 -0
  156. package/dist/cjs/exporter/invalid-data-validation.d.ts +19 -0
  157. package/dist/cjs/exporter/invalid-data-validation.d.ts.map +1 -0
  158. package/dist/cjs/exporter/invalid-data-validation.js +39 -0
  159. package/dist/cjs/exporter/ods/ods-export.d.ts +26 -0
  160. package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -0
  161. package/dist/cjs/exporter/ods/ods-export.js +3714 -0
  162. package/dist/cjs/exporter/ods/sparkline-builder.d.ts +33 -0
  163. package/dist/cjs/exporter/ods/sparkline-builder.d.ts.map +1 -0
  164. package/dist/cjs/exporter/ods/sparkline-builder.js +221 -0
  165. package/dist/cjs/exporter/xlsx/chart-builder.d.ts +3 -0
  166. package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -0
  167. package/dist/cjs/exporter/xlsx/chart-builder.js +1901 -0
  168. package/dist/cjs/exporter/xlsx/chart-templates.d.ts +1781 -0
  169. package/dist/cjs/exporter/xlsx/chart-templates.d.ts.map +1 -0
  170. package/dist/cjs/exporter/xlsx/chart-templates.js +1798 -0
  171. package/dist/cjs/exporter/xlsx/future-functions.d.ts +10 -0
  172. package/dist/cjs/exporter/xlsx/future-functions.d.ts.map +1 -0
  173. package/dist/cjs/exporter/xlsx/future-functions.js +264 -0
  174. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts +58 -0
  175. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -0
  176. package/dist/cjs/exporter/xlsx/pivot-builder.js +277 -0
  177. package/dist/cjs/exporter/xlsx/power-query-builder.d.ts +26 -0
  178. package/dist/cjs/exporter/xlsx/power-query-builder.d.ts.map +1 -0
  179. package/dist/cjs/exporter/xlsx/power-query-builder.js +306 -0
  180. package/dist/cjs/exporter/xlsx/slicer-builder.d.ts +11 -0
  181. package/dist/cjs/exporter/xlsx/slicer-builder.d.ts.map +1 -0
  182. package/dist/cjs/exporter/xlsx/slicer-builder.js +87 -0
  183. package/dist/cjs/exporter/xlsx/sparkline-builder.d.ts +61 -0
  184. package/dist/cjs/exporter/xlsx/sparkline-builder.d.ts.map +1 -0
  185. package/dist/cjs/exporter/xlsx/sparkline-builder.js +489 -0
  186. package/dist/cjs/exporter/xlsx/style-builder.d.ts +93 -0
  187. package/dist/cjs/exporter/xlsx/style-builder.d.ts.map +1 -0
  188. package/dist/cjs/exporter/xlsx/style-builder.js +813 -0
  189. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts +58 -0
  190. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -0
  191. package/dist/cjs/exporter/xlsx/threaded-comments-builder.js +189 -0
  192. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +121 -0
  193. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -0
  194. package/dist/cjs/exporter/xlsx/xlsx-export.js +4596 -0
  195. package/dist/cjs/helpers/theme-xml-generator.d.ts +8 -0
  196. package/dist/cjs/helpers/theme-xml-generator.d.ts.map +1 -0
  197. package/dist/cjs/helpers/theme-xml-generator.js +33 -0
  198. package/dist/cjs/helpers.d.ts +34 -0
  199. package/dist/cjs/helpers.d.ts.map +1 -0
  200. package/dist/cjs/helpers.js +189 -0
  201. package/dist/cjs/index.d.ts +16 -0
  202. package/dist/cjs/index.d.ts.map +1 -0
  203. package/dist/cjs/index.js +15 -0
  204. package/dist/cjs/indexed-colors.d.ts +2 -0
  205. package/dist/cjs/indexed-colors.d.ts.map +1 -0
  206. package/dist/cjs/indexed-colors.js +75 -0
  207. package/dist/cjs/ods-export.d.ts +24 -0
  208. package/dist/cjs/ods-export.d.ts.map +1 -0
  209. package/dist/cjs/ods-export.js +23 -0
  210. package/dist/cjs/parse.d.ts +24 -0
  211. package/dist/cjs/parse.d.ts.map +1 -0
  212. package/dist/cjs/parse.js +68 -0
  213. package/dist/cjs/power-query.d.ts +41 -0
  214. package/dist/cjs/power-query.d.ts.map +1 -0
  215. package/dist/cjs/power-query.js +22 -0
  216. package/dist/cjs/server.d.ts +9 -0
  217. package/dist/cjs/server.d.ts.map +1 -0
  218. package/dist/cjs/server.js +8 -0
  219. package/dist/cjs/theme1.d.ts +3 -0
  220. package/dist/cjs/theme1.d.ts.map +1 -0
  221. package/dist/cjs/theme1.js +2 -0
  222. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  223. package/dist/cjs/types.d.ts +14 -0
  224. package/dist/cjs/types.d.ts.map +1 -0
  225. package/dist/cjs/types.js +14 -0
  226. package/dist/cjs/vitest.config.d.ts +3 -0
  227. package/dist/cjs/vitest.config.d.ts.map +1 -0
  228. package/dist/cjs/vitest.config.js +16 -0
  229. package/dist/esm/autofilter-condition.d.ts +43 -0
  230. package/dist/esm/autofilter-condition.d.ts.map +1 -0
  231. package/dist/esm/autofilter-condition.js +145 -0
  232. package/dist/esm/csv-export.d.ts +49 -0
  233. package/dist/esm/csv-export.d.ts.map +1 -0
  234. package/dist/esm/csv-export.js +126 -0
  235. package/dist/esm/csv-import-worker.d.ts +16 -0
  236. package/dist/esm/csv-import-worker.d.ts.map +1 -0
  237. package/dist/esm/csv-import-worker.js +60 -0
  238. package/dist/esm/csv-import.d.ts +38 -0
  239. package/dist/esm/csv-import.d.ts.map +1 -0
  240. package/dist/esm/csv-import.js +132 -0
  241. package/dist/esm/csv-parser/helpers.d.ts +14 -0
  242. package/dist/esm/csv-parser/helpers.d.ts.map +1 -0
  243. package/dist/esm/csv-parser/helpers.js +0 -0
  244. package/dist/esm/csv-parser/types.d.ts +66 -0
  245. package/dist/esm/csv-parser/types.d.ts.map +1 -0
  246. package/dist/esm/csv-parser/types.js +14 -0
  247. package/dist/esm/csv-parser/worker.d.ts +2 -0
  248. package/dist/esm/csv-parser/worker.d.ts.map +1 -0
  249. package/dist/esm/csv-parser/worker.js +154 -0
  250. package/dist/esm/excel-export.d.ts +11 -0
  251. package/dist/esm/excel-export.d.ts.map +1 -0
  252. package/dist/esm/excel-export.js +13 -0
  253. package/dist/esm/excel-import-session.d.ts +51 -0
  254. package/dist/esm/excel-import-session.d.ts.map +1 -0
  255. package/dist/esm/excel-import-session.js +255 -0
  256. package/dist/esm/excel-import-worker.d.ts +7 -0
  257. package/dist/esm/excel-import-worker.d.ts.map +1 -0
  258. package/dist/esm/excel-import-worker.js +116 -0
  259. package/dist/esm/excel-parser/builtin-numfmts.d.ts +24 -0
  260. package/dist/esm/excel-parser/builtin-numfmts.d.ts.map +1 -0
  261. package/dist/esm/excel-parser/builtin-numfmts.js +59 -0
  262. package/dist/esm/excel-parser/cfb-decryptor.d.ts +53 -0
  263. package/dist/esm/excel-parser/cfb-decryptor.d.ts.map +1 -0
  264. package/dist/esm/excel-parser/cfb-decryptor.js +493 -0
  265. package/dist/esm/excel-parser/cfb-encryptor.d.ts +23 -0
  266. package/dist/esm/excel-parser/cfb-encryptor.d.ts.map +1 -0
  267. package/dist/esm/excel-parser/cfb-encryptor.js +181 -0
  268. package/dist/esm/excel-parser/dv-cell-stamp.d.ts +29 -0
  269. package/dist/esm/excel-parser/dv-cell-stamp.d.ts.map +1 -0
  270. package/dist/esm/excel-parser/dv-cell-stamp.js +155 -0
  271. package/dist/esm/excel-parser/helpers.d.ts +28 -0
  272. package/dist/esm/excel-parser/helpers.d.ts.map +1 -0
  273. package/dist/esm/excel-parser/helpers.js +160 -0
  274. package/dist/esm/excel-parser/interface.d.ts +179 -0
  275. package/dist/esm/excel-parser/interface.d.ts.map +1 -0
  276. package/dist/esm/excel-parser/interface.js +1 -0
  277. package/dist/esm/excel-parser/plugins/base/index.d.ts +31 -0
  278. package/dist/esm/excel-parser/plugins/base/index.d.ts.map +1 -0
  279. package/dist/esm/excel-parser/plugins/base/index.js +64 -0
  280. package/dist/esm/excel-parser/plugins/ods/conditional-formatting.d.ts +16 -0
  281. package/dist/esm/excel-parser/plugins/ods/conditional-formatting.d.ts.map +1 -0
  282. package/dist/esm/excel-parser/plugins/ods/conditional-formatting.js +405 -0
  283. package/dist/esm/excel-parser/plugins/ods/data-validations.d.ts +15 -0
  284. package/dist/esm/excel-parser/plugins/ods/data-validations.d.ts.map +1 -0
  285. package/dist/esm/excel-parser/plugins/ods/data-validations.js +331 -0
  286. package/dist/esm/excel-parser/plugins/ods/formula.d.ts +8 -0
  287. package/dist/esm/excel-parser/plugins/ods/formula.d.ts.map +1 -0
  288. package/dist/esm/excel-parser/plugins/ods/formula.js +82 -0
  289. package/dist/esm/excel-parser/plugins/ods/index.d.ts +122 -0
  290. package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -0
  291. package/dist/esm/excel-parser/plugins/ods/index.js +2896 -0
  292. package/dist/esm/excel-parser/plugins/ods/sparklines.d.ts +25 -0
  293. package/dist/esm/excel-parser/plugins/ods/sparklines.d.ts.map +1 -0
  294. package/dist/esm/excel-parser/plugins/ods/sparklines.js +188 -0
  295. package/dist/esm/excel-parser/plugins/ods/styles.d.ts +60 -0
  296. package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -0
  297. package/dist/esm/excel-parser/plugins/ods/styles.js +732 -0
  298. package/dist/esm/excel-parser/plugins/ods/types.d.ts +7 -0
  299. package/dist/esm/excel-parser/plugins/ods/types.d.ts.map +1 -0
  300. package/dist/esm/excel-parser/plugins/ods/types.js +1 -0
  301. package/dist/esm/excel-parser/plugins/xls/index.d.ts +5 -0
  302. package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -0
  303. package/dist/esm/excel-parser/plugins/xls/index.js +7 -0
  304. package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.d.ts +13 -0
  305. package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.d.ts.map +1 -0
  306. package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.js +48 -0
  307. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +117 -0
  308. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -0
  309. package/dist/esm/excel-parser/plugins/xlsb/index.js +2339 -0
  310. package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts +18 -0
  311. package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -0
  312. package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.js +522 -0
  313. package/dist/esm/excel-parser/plugins/xlsm/index.d.ts +5 -0
  314. package/dist/esm/excel-parser/plugins/xlsm/index.d.ts.map +1 -0
  315. package/dist/esm/excel-parser/plugins/xlsm/index.js +6 -0
  316. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +60 -0
  317. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -0
  318. package/dist/esm/excel-parser/plugins/xlsx/charts.js +2036 -0
  319. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts +18 -0
  320. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -0
  321. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.js +621 -0
  322. package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts +4 -0
  323. package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -0
  324. package/dist/esm/excel-parser/plugins/xlsx/data-validations.js +493 -0
  325. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +39 -0
  326. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -0
  327. package/dist/esm/excel-parser/plugins/xlsx/drawing.js +905 -0
  328. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +320 -0
  329. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -0
  330. package/dist/esm/excel-parser/plugins/xlsx/index.js +4654 -0
  331. package/dist/esm/excel-parser/plugins/xlsx/numfmt.d.ts +2 -0
  332. package/dist/esm/excel-parser/plugins/xlsx/numfmt.d.ts.map +1 -0
  333. package/dist/esm/excel-parser/plugins/xlsx/numfmt.js +3 -0
  334. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts +40 -0
  335. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -0
  336. package/dist/esm/excel-parser/plugins/xlsx/pivots.js +254 -0
  337. package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts +8 -0
  338. package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -0
  339. package/dist/esm/excel-parser/plugins/xlsx/relationships.js +13 -0
  340. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts +26 -0
  341. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -0
  342. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.js +64 -0
  343. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +44 -0
  344. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -0
  345. package/dist/esm/excel-parser/plugins/xlsx/styles.js +1365 -0
  346. package/dist/esm/excel-parser/plugins/xlsx/theme.d.ts +50 -0
  347. package/dist/esm/excel-parser/plugins/xlsx/theme.d.ts.map +1 -0
  348. package/dist/esm/excel-parser/plugins/xlsx/theme.js +127 -0
  349. package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.d.ts +47 -0
  350. package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.d.ts.map +1 -0
  351. package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.js +103 -0
  352. package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.d.ts +44 -0
  353. package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.d.ts.map +1 -0
  354. package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.js +399 -0
  355. package/dist/esm/excel-parser/registry.d.ts +5 -0
  356. package/dist/esm/excel-parser/registry.d.ts.map +1 -0
  357. package/dist/esm/excel-parser/registry.js +20 -0
  358. package/dist/esm/excel-parser/style-registry.d.ts +51 -0
  359. package/dist/esm/excel-parser/style-registry.d.ts.map +1 -0
  360. package/dist/esm/excel-parser/style-registry.js +163 -0
  361. package/dist/esm/excel-parser/theme-default.d.ts +3 -0
  362. package/dist/esm/excel-parser/theme-default.d.ts.map +1 -0
  363. package/dist/esm/excel-parser/theme-default.js +19 -0
  364. package/dist/esm/excel-parser/types.d.ts +365 -0
  365. package/dist/esm/excel-parser/types.d.ts.map +1 -0
  366. package/dist/esm/excel-parser/types.js +59 -0
  367. package/dist/esm/excel-parser/utils.d.ts +6 -0
  368. package/dist/esm/excel-parser/utils.d.ts.map +1 -0
  369. package/dist/esm/excel-parser/utils.js +25 -0
  370. package/dist/esm/excel-parser/workbook.d.ts +51 -0
  371. package/dist/esm/excel-parser/workbook.d.ts.map +1 -0
  372. package/dist/esm/excel-parser/workbook.js +192 -0
  373. package/dist/esm/excel-parser/worker.d.ts +2 -0
  374. package/dist/esm/excel-parser/worker.d.ts.map +1 -0
  375. package/dist/esm/excel-parser/worker.js +862 -0
  376. package/dist/esm/excel-parser/zip-parser-streaming.d.ts +13 -0
  377. package/dist/esm/excel-parser/zip-parser-streaming.d.ts.map +1 -0
  378. package/dist/esm/excel-parser/zip-parser-streaming.js +119 -0
  379. package/dist/esm/excel-parser/zip-parser.d.ts +11 -0
  380. package/dist/esm/excel-parser/zip-parser.d.ts.map +1 -0
  381. package/dist/esm/excel-parser/zip-parser.js +43 -0
  382. package/dist/esm/exporter/invalid-data-validation.d.ts +19 -0
  383. package/dist/esm/exporter/invalid-data-validation.d.ts.map +1 -0
  384. package/dist/esm/exporter/invalid-data-validation.js +37 -0
  385. package/dist/esm/exporter/ods/ods-export.d.ts +26 -0
  386. package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -0
  387. package/dist/esm/exporter/ods/ods-export.js +3238 -0
  388. package/dist/esm/exporter/ods/sparkline-builder.d.ts +33 -0
  389. package/dist/esm/exporter/ods/sparkline-builder.d.ts.map +1 -0
  390. package/dist/esm/exporter/ods/sparkline-builder.js +165 -0
  391. package/dist/esm/exporter/xlsx/chart-builder.d.ts +3 -0
  392. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -0
  393. package/dist/esm/exporter/xlsx/chart-builder.js +1805 -0
  394. package/dist/esm/exporter/xlsx/chart-templates.d.ts +1781 -0
  395. package/dist/esm/exporter/xlsx/chart-templates.d.ts.map +1 -0
  396. package/dist/esm/exporter/xlsx/chart-templates.js +1798 -0
  397. package/dist/esm/exporter/xlsx/future-functions.d.ts +10 -0
  398. package/dist/esm/exporter/xlsx/future-functions.d.ts.map +1 -0
  399. package/dist/esm/exporter/xlsx/future-functions.js +264 -0
  400. package/dist/esm/exporter/xlsx/pivot-builder.d.ts +58 -0
  401. package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -0
  402. package/dist/esm/exporter/xlsx/pivot-builder.js +230 -0
  403. package/dist/esm/exporter/xlsx/power-query-builder.d.ts +26 -0
  404. package/dist/esm/exporter/xlsx/power-query-builder.d.ts.map +1 -0
  405. package/dist/esm/exporter/xlsx/power-query-builder.js +257 -0
  406. package/dist/esm/exporter/xlsx/slicer-builder.d.ts +11 -0
  407. package/dist/esm/exporter/xlsx/slicer-builder.d.ts.map +1 -0
  408. package/dist/esm/exporter/xlsx/slicer-builder.js +84 -0
  409. package/dist/esm/exporter/xlsx/sparkline-builder.d.ts +61 -0
  410. package/dist/esm/exporter/xlsx/sparkline-builder.d.ts.map +1 -0
  411. package/dist/esm/exporter/xlsx/sparkline-builder.js +437 -0
  412. package/dist/esm/exporter/xlsx/style-builder.d.ts +93 -0
  413. package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -0
  414. package/dist/esm/exporter/xlsx/style-builder.js +795 -0
  415. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts +58 -0
  416. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -0
  417. package/dist/esm/exporter/xlsx/threaded-comments-builder.js +175 -0
  418. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +121 -0
  419. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -0
  420. package/dist/esm/exporter/xlsx/xlsx-export.js +4062 -0
  421. package/dist/esm/helpers/theme-xml-generator.d.ts +8 -0
  422. package/dist/esm/helpers/theme-xml-generator.d.ts.map +1 -0
  423. package/dist/esm/helpers/theme-xml-generator.js +33 -0
  424. package/dist/esm/helpers.d.ts +34 -0
  425. package/dist/esm/helpers.d.ts.map +1 -0
  426. package/dist/esm/helpers.js +182 -0
  427. package/dist/esm/index.d.ts +16 -0
  428. package/dist/esm/index.d.ts.map +1 -0
  429. package/dist/esm/index.js +15 -0
  430. package/dist/esm/indexed-colors.d.ts +2 -0
  431. package/dist/esm/indexed-colors.d.ts.map +1 -0
  432. package/dist/esm/indexed-colors.js +75 -0
  433. package/dist/esm/ods-export.d.ts +24 -0
  434. package/dist/esm/ods-export.d.ts.map +1 -0
  435. package/dist/esm/ods-export.js +12 -0
  436. package/dist/esm/parse.d.ts +24 -0
  437. package/dist/esm/parse.d.ts.map +1 -0
  438. package/dist/esm/parse.js +55 -0
  439. package/dist/esm/power-query.d.ts +41 -0
  440. package/dist/esm/power-query.d.ts.map +1 -0
  441. package/dist/esm/power-query.js +19 -0
  442. package/dist/esm/server.d.ts +9 -0
  443. package/dist/esm/server.d.ts.map +1 -0
  444. package/dist/esm/server.js +8 -0
  445. package/dist/esm/theme1.d.ts +3 -0
  446. package/dist/esm/theme1.d.ts.map +1 -0
  447. package/dist/esm/theme1.js +2 -0
  448. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  449. package/dist/esm/types.d.ts +14 -0
  450. package/dist/esm/types.d.ts.map +1 -0
  451. package/dist/esm/types.js +14 -0
  452. package/dist/esm/vitest.config.d.ts +3 -0
  453. package/dist/esm/vitest.config.d.ts.map +1 -0
  454. package/dist/esm/vitest.config.js +16 -0
  455. package/package.json +62 -0
  456. package/xlsx.d.ts +52 -0
@@ -0,0 +1,2036 @@
1
+ import { generateSelectionsFromFormula } from "@rowsncolumns/utils";
2
+ import { uuid } from "@rowsncolumns/utils";
3
+ import { get } from "lodash-es";
4
+ /**
5
+ * Pick the data sheetId for a parsed selection. Honours the explicit
6
+ * sheet prefix in the chart's formula reference (cross-sheet charts);
7
+ * falls back to the host sheet when the formula has no prefix.
8
+ */
9
+ function pickSheetId(sheetName, hostSheetId, resolveSheetId) {
10
+ if (sheetName && resolveSheetId) {
11
+ const resolved = resolveSheetId(sheetName);
12
+ if (resolved !== undefined)
13
+ return resolved;
14
+ }
15
+ return hostSheetId;
16
+ }
17
+ // Import from index.ts in the same directory (circular dependency but resolving symbols)
18
+ // Note: We need to use type import for Workbook if needed, or functions
19
+ // In this case, we need getChartReferenceFromAnchor which was exported from workbook.ts originally
20
+ // but now is in index.ts of the plugin. However, charts.ts doesn't seem to import it based on the file content I read.
21
+ // The search_replace failed because the import wasn't there.
22
+ // Let me double check the file content again.
23
+ // The file content I read has NO imports from "../charts" or "../workbook".
24
+ // It seems I was trying to fix a linter error that I *assumed* existed or was trying to match code that wasn't there.
25
+ // The file content shows standard imports and internal functions.
26
+ function getWithFallback(source, paths) {
27
+ for (const path of paths) {
28
+ const value = get(source, path);
29
+ if (value !== undefined) {
30
+ return value;
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ function toInt(value) {
36
+ const parsed = parseInt(typeof value === "number" ? String(value) : value ?? "0", 10);
37
+ return Number.isNaN(parsed) ? 0 : parsed;
38
+ }
39
+ export async function processChart(chartTarget, drawingPath, anchor, sheetId, zipParser, xmlParser, sheetDimensions, resolveSheetId) {
40
+ try {
41
+ // Resolve chart file path
42
+ let chartPath = chartTarget;
43
+ const drawingDir = drawingPath.substring(0, drawingPath.lastIndexOf("/"));
44
+ if (chartPath.startsWith("../")) {
45
+ chartPath = `xl/${chartPath.replace("../", "")}`;
46
+ }
47
+ else if (!chartPath.startsWith("/") && !chartPath.startsWith("xl/")) {
48
+ chartPath = `${drawingDir}/${chartPath}`;
49
+ }
50
+ // Read the chart file
51
+ const chartContent = await zipParser.getText(chartPath);
52
+ if (!chartContent) {
53
+ return null;
54
+ }
55
+ const chartXml = xmlParser.parse(chartContent);
56
+ const chart = getWithFallback(chartXml, [
57
+ "c:chartSpace.c:chart",
58
+ "chartSpace.chart",
59
+ "chartSpace.c:chart",
60
+ "c:chartSpace.chart",
61
+ ]);
62
+ if (!chart) {
63
+ console.log("Chart data not found in file:", chartPath);
64
+ console.log("Available keys:", Object.keys(chartXml));
65
+ return null;
66
+ }
67
+ // `<c:roundedCorners val="1"/>` is a sibling of `<c:chart>` at the
68
+ // chartSpace root, not inside `<c:chart>` itself. Default per
69
+ // OOXML §21.2.2.183 is `false`. Read both namespace-prefixed and
70
+ // bare forms — fast-xml-parser preserves the namespace when the
71
+ // schema declares it.
72
+ const chartSpaceRoot = getWithFallback(chartXml, [
73
+ "c:chartSpace",
74
+ "chartSpace",
75
+ ]);
76
+ const roundedCornersRaw = getWithFallback(chartSpaceRoot, [
77
+ "c:roundedCorners",
78
+ "roundedCorners",
79
+ ]);
80
+ const roundedCornersVal = roundedCornersRaw?.a$?.val;
81
+ const roundedCorners = roundedCornersVal === "1" ||
82
+ roundedCornersVal === "true" ||
83
+ roundedCornersVal === true
84
+ ? true
85
+ : undefined;
86
+ const chartId = uuid();
87
+ // Extract chart title (text + formatting)
88
+ const title = extractChartTitle(chart);
89
+ const titleTextFormat = extractChartTitleTextFormat(chart);
90
+ // Determine chart type
91
+ const chartType = determineChartType(chart);
92
+ // Extract data
93
+ const plotArea = getWithFallback(chart, ["c:plotArea", "plotArea"]);
94
+ const series = extractChartSeries(plotArea, chartType, sheetId, resolveSheetId);
95
+ const domains = extractChartDomains(plotArea, chartType, sheetId, resolveSheetId);
96
+ const dataRange = extractChartDataRange(plotArea, chartType, sheetId, resolveSheetId);
97
+ // Scatter/bubble only — bubble sizes (3rd dimension) and per-point
98
+ // labels. Bubble charts also have a `bubble3D` flag that scatter
99
+ // doesn't carry.
100
+ const bubbleSizes = chartType === "bubble"
101
+ ? extractChartBubbleSizes(plotArea, sheetId, resolveSheetId)
102
+ : undefined;
103
+ // Scatter only — `<c:scatterStyle val="lineMarker">` etc. controls
104
+ // whether points are connected and whether markers are drawn.
105
+ // Excel's default when omitted is "lineMarker".
106
+ const scatterStyle = chartType === "scatter" ? extractScatterStyle(plotArea) : undefined;
107
+ // Extract hidden dimension strategy from plotVisOnly
108
+ const hiddenDimensionStrategy = extractHiddenDimensionStrategy(chart);
109
+ // Extract position from anchor
110
+ const position = extractChartPosition(anchor, sheetId, sheetDimensions);
111
+ if (!position) {
112
+ console.log("Could not extract chart position");
113
+ return null;
114
+ }
115
+ const legend = extractLegendOptions(chart);
116
+ const dataLabels = extractDataLabelsOptions(plotArea, chartType);
117
+ const { valueAxisOptions, secondaryValueAxisOptions, categoryAxisOptions, } = extractAxisOptions(plotArea);
118
+ const doughnutHoleSize = chartType === "doughnut" ? extractDoughnutHoleSize(plotArea) : undefined;
119
+ // Pie-of-pie / bar-of-pie — <c:ofPieChart> degrades to chartType
120
+ // "pie" with sourceChartType "OF_PIE" plus the split fields the
121
+ // renderer uses to draw the composite layout.
122
+ const ofPieOptions = chartType === "pie" ? extractOfPieOptions(plotArea) : undefined;
123
+ const radarStyle = chartType === "radar" ? extractRadarStyle(plotArea) : undefined;
124
+ const binCount = chartType === "histogram"
125
+ ? extractC15HistogramBinCount(plotArea)
126
+ : undefined;
127
+ // Bar/column-only — <c:grouping>, <c:overlap>, <c:gapWidth>.
128
+ // Excel encodes percentStacked / stacked / clustered here, and the
129
+ // overlap/gapWidth shape the bar layout. Without these, the
130
+ // Event Budget percent-stacked chart renders as clustered.
131
+ const barLayout = chartType === "bar" || chartType === "column"
132
+ ? extractBarLayoutOptions(plotArea)
133
+ : undefined;
134
+ return {
135
+ chartId,
136
+ spec: {
137
+ title,
138
+ ...(titleTextFormat ? { titleTextFormat } : {}),
139
+ chartType,
140
+ domains,
141
+ series,
142
+ dataRange,
143
+ hiddenDimensionStrategy,
144
+ // Scatter/bubble must conform to BubbleChartSpec which requires
145
+ // `bubbleLabels`. We don't currently extract per-point labels
146
+ // from OOXML (rare in real files); set an empty array so the
147
+ // renderer's `flattenChartDataValues(spec.bubbleLabels, …)`
148
+ // call gets a well-formed input.
149
+ ...(chartType === "scatter" || chartType === "bubble"
150
+ ? { bubbleLabels: [] }
151
+ : {}),
152
+ ...(bubbleSizes && bubbleSizes.length ? { bubbleSizes } : {}),
153
+ ...(scatterStyle
154
+ ? { scatterStyle: scatterStyle }
155
+ : {}),
156
+ ...(legend ? { legend } : {}),
157
+ ...(dataLabels ? { dataLabels } : {}),
158
+ ...(valueAxisOptions ? { valueAxisOptions } : {}),
159
+ ...(secondaryValueAxisOptions
160
+ ? { secondaryValueAxisOptions }
161
+ : {}),
162
+ ...(categoryAxisOptions ? { categoryAxisOptions } : {}),
163
+ ...(typeof doughnutHoleSize === "number"
164
+ ? { doughnutHoleSize }
165
+ : {}),
166
+ ...(ofPieOptions
167
+ ? { sourceChartType: "OF_PIE", ...ofPieOptions }
168
+ : {}),
169
+ ...(radarStyle ? { radarStyle } : {}),
170
+ ...(typeof binCount === "number" ? { binCount } : {}),
171
+ ...(barLayout?.stackedType
172
+ ? { stackedType: barLayout.stackedType }
173
+ : {}),
174
+ ...(typeof barLayout?.overlap === "number"
175
+ ? { overlap: barLayout.overlap }
176
+ : {}),
177
+ ...(typeof barLayout?.gapWidth === "number"
178
+ ? { gapWidth: barLayout.gapWidth }
179
+ : {}),
180
+ ...(roundedCorners ? { roundedCorners } : {}),
181
+ },
182
+ position,
183
+ id: chartId,
184
+ type: "chart",
185
+ };
186
+ }
187
+ catch (err) {
188
+ console.log("Error processing chart:", err);
189
+ return null;
190
+ }
191
+ }
192
+ // Helper to get series elements with namespace fallbacks
193
+ function getSeriesElements(plotArea, chartType) {
194
+ switch (chartType) {
195
+ case "bar":
196
+ case "column":
197
+ // Phase 5.5 — c15 extension chart types emit a c:barChart body
198
+ // for older-Excel compatibility, so their series live in the same
199
+ // place as a bar/column chart. Phase 5.5b — histogram additionally
200
+ // wraps the barChart in mc:AlternateContent > mc:Fallback, so we
201
+ // also probe that path.
202
+ case "histogram":
203
+ case "boxplot":
204
+ case "waterfall":
205
+ case "funnel":
206
+ return getWithFallback(plotArea, [
207
+ "c:barChart.c:ser",
208
+ "barChart.ser",
209
+ "c:bar3DChart.c:ser",
210
+ "bar3DChart.ser",
211
+ "mc:AlternateContent.mc:Fallback.c:barChart.c:ser",
212
+ "AlternateContent.Fallback.barChart.ser",
213
+ ]);
214
+ case "line":
215
+ return getWithFallback(plotArea, [
216
+ "c:lineChart.c:ser",
217
+ "lineChart.ser",
218
+ "c:line3DChart.c:ser",
219
+ "line3DChart.ser",
220
+ ]);
221
+ case "pie":
222
+ return getWithFallback(plotArea, [
223
+ "c:pieChart.c:ser",
224
+ "pieChart.ser",
225
+ "c:pie3DChart.c:ser",
226
+ "pie3DChart.ser",
227
+ // pie-of-pie / bar-of-pie — determineChartType maps
228
+ // <c:ofPieChart> to "pie", so its series live here too.
229
+ "c:ofPieChart.c:ser",
230
+ "ofPieChart.ser",
231
+ ]);
232
+ case "doughnut":
233
+ return getWithFallback(plotArea, [
234
+ "c:doughnutChart.c:ser",
235
+ "doughnutChart.ser",
236
+ ]);
237
+ case "area":
238
+ return getWithFallback(plotArea, [
239
+ "c:areaChart.c:ser",
240
+ "areaChart.ser",
241
+ "c:area3DChart.c:ser",
242
+ "area3DChart.ser",
243
+ ]);
244
+ case "radar":
245
+ return getWithFallback(plotArea, ["c:radarChart.c:ser", "radarChart.ser"]);
246
+ case "stock":
247
+ return getWithFallback(plotArea, ["c:stockChart.c:ser", "stockChart.ser"]);
248
+ case "scatter":
249
+ return getWithFallback(plotArea, [
250
+ "c:scatterChart.c:ser",
251
+ "scatterChart.ser",
252
+ ]);
253
+ case "bubble":
254
+ return getWithFallback(plotArea, [
255
+ "c:bubbleChart.c:ser",
256
+ "bubbleChart.ser",
257
+ ]);
258
+ default:
259
+ return getWithFallback(plotArea, ["c:barChart.c:ser", "barChart.ser"]);
260
+ }
261
+ }
262
+ // Path tuples used by the per-series ref extractors below. We accept
263
+ // both namespaced and stripped forms because fast-xml-parser is
264
+ // configured both ways in different code paths.
265
+ const X_VAL_REF_PATHS = [
266
+ "c:xVal.c:numRef.c:f",
267
+ "c:xVal.c:numRef.c:f.t$",
268
+ "xVal.numRef.f",
269
+ "xVal.numRef.f.t$",
270
+ // scatter X axis can also be string (categorical X)
271
+ "c:xVal.c:strRef.c:f",
272
+ "c:xVal.c:strRef.c:f.t$",
273
+ "xVal.strRef.f",
274
+ "xVal.strRef.f.t$",
275
+ ];
276
+ const Y_VAL_REF_PATHS = [
277
+ "c:yVal.c:numRef.c:f",
278
+ "c:yVal.c:numRef.c:f.t$",
279
+ "yVal.numRef.f",
280
+ "yVal.numRef.f.t$",
281
+ ];
282
+ const BUBBLE_SIZE_REF_PATHS = [
283
+ "c:bubbleSize.c:numRef.c:f",
284
+ "c:bubbleSize.c:numRef.c:f.t$",
285
+ "bubbleSize.numRef.f",
286
+ "bubbleSize.numRef.f.t$",
287
+ ];
288
+ const CAT_REF_PATHS = [
289
+ "c:cat.c:strRef.c:f",
290
+ "c:cat.c:numRef.c:f",
291
+ "c:cat.c:strRef.c:f.t$",
292
+ "c:cat.c:numRef.c:f.t$",
293
+ "cat.strRef.f",
294
+ "cat.numRef.f",
295
+ "cat.strRef.f.t$",
296
+ "cat.numRef.f.t$",
297
+ ];
298
+ const VAL_REF_PATHS = [
299
+ "c:val.c:numRef.c:f",
300
+ "c:val.c:numRef.c:f.t$",
301
+ "val.numRef.f",
302
+ "val.numRef.f.t$",
303
+ ];
304
+ const TX_REF_PATHS = [
305
+ "c:tx.c:strRef.c:f",
306
+ "c:tx.c:strRef.c:f.t$",
307
+ "tx.strRef.f",
308
+ "tx.strRef.f.t$",
309
+ ];
310
+ const isXYChart = (chartType) => chartType === "scatter" || chartType === "bubble";
311
+ // Extract the overall data range for the chart
312
+ export function extractChartDataRange(plotArea, chartType, sheetId, resolveSheetId) {
313
+ try {
314
+ const seriesElements = getSeriesElements(plotArea, chartType);
315
+ if (!seriesElements)
316
+ return null;
317
+ const seriesArray = Array.isArray(seriesElements)
318
+ ? seriesElements
319
+ : [seriesElements];
320
+ // Collect all ranges from the chart, tagged with their parsed
321
+ // sheet name so cross-sheet refs can be honoured.
322
+ const allRanges = [];
323
+ for (const ser of seriesArray) {
324
+ if (isXYChart(chartType)) {
325
+ const xRef = getWithFallback(ser, X_VAL_REF_PATHS);
326
+ const yRef = getWithFallback(ser, Y_VAL_REF_PATHS);
327
+ const sizeRef = chartType === "bubble"
328
+ ? getWithFallback(ser, BUBBLE_SIZE_REF_PATHS)
329
+ : undefined;
330
+ const nameRef = getWithFallback(ser, TX_REF_PATHS);
331
+ if (xRef)
332
+ allRanges.push(xRef);
333
+ if (yRef)
334
+ allRanges.push(yRef);
335
+ if (sizeRef)
336
+ allRanges.push(sizeRef);
337
+ if (nameRef)
338
+ allRanges.push(nameRef);
339
+ }
340
+ else {
341
+ const catRef = getWithFallback(ser, CAT_REF_PATHS);
342
+ const valRef = getWithFallback(ser, VAL_REF_PATHS);
343
+ const nameRef = getWithFallback(ser, TX_REF_PATHS);
344
+ if (catRef)
345
+ allRanges.push(catRef);
346
+ if (valRef)
347
+ allRanges.push(valRef);
348
+ if (nameRef)
349
+ allRanges.push(nameRef);
350
+ }
351
+ }
352
+ if (allRanges.length === 0)
353
+ return null;
354
+ // Bounding range is computed per-sheet — different ranges may live
355
+ // on different sheets (a chart referencing both the host sheet and
356
+ // a data sheet, for example). Per Excel, the chart's "data range"
357
+ // is logically the union of inputs; we pick the first sheet's
358
+ // bounding box as the reportable dataRange and pin the rest via
359
+ // the per-series sources.
360
+ const boundingRange = calculateBoundingRange(allRanges);
361
+ if (boundingRange) {
362
+ const selections = generateSelectionsFromFormula(`=${boundingRange}`);
363
+ if (selections.length > 0) {
364
+ const sel = selections[0];
365
+ const resolved = pickSheetId(sel.sheetName, sheetId, resolveSheetId);
366
+ return {
367
+ ...sel.range,
368
+ sheetId: resolved,
369
+ };
370
+ }
371
+ }
372
+ return null;
373
+ }
374
+ catch (err) {
375
+ console.log("Error extracting chart data range:", err);
376
+ return null;
377
+ }
378
+ }
379
+ // Calculate the bounding range that encompasses all the individual ranges
380
+ function calculateBoundingRange(ranges) {
381
+ try {
382
+ let minRow = Infinity;
383
+ let maxRow = -Infinity;
384
+ let minCol = Infinity;
385
+ let maxCol = -Infinity;
386
+ let sheetName = "";
387
+ for (const range of ranges) {
388
+ // Parse each range (e.g., "Sheet1!$A$2:$A$20" or "Sheet1!$C$1")
389
+ const selections = generateSelectionsFromFormula(`=${range}`);
390
+ if (selections.length > 0) {
391
+ const sel = selections[0];
392
+ // Extract sheet name from the first range
393
+ if (!sheetName && range.includes("!")) {
394
+ sheetName = range.split("!")[0];
395
+ }
396
+ // Update bounding box
397
+ minRow = Math.min(minRow, sel.range.startRowIndex);
398
+ maxRow = Math.max(maxRow, sel.range.endRowIndex);
399
+ minCol = Math.min(minCol, sel.range.startColumnIndex);
400
+ maxCol = Math.max(maxCol, sel.range.endColumnIndex);
401
+ }
402
+ }
403
+ if (minRow === Infinity || minCol === Infinity) {
404
+ return null;
405
+ }
406
+ // Convert back to Excel range format
407
+ const startCell = columnIndexToLetter(minCol) + minRow;
408
+ const endCell = columnIndexToLetter(maxCol) + maxRow;
409
+ const rangeStr = startCell === endCell ? startCell : `${startCell}:${endCell}`;
410
+ return sheetName ? `${sheetName}!${rangeStr}` : rangeStr;
411
+ }
412
+ catch (err) {
413
+ console.log("Error calculating bounding range:", err);
414
+ return null;
415
+ }
416
+ }
417
+ // Helper function to convert column index to letter (1 -> A, 2 -> B, etc.)
418
+ function columnIndexToLetter(index) {
419
+ let result = "";
420
+ let num = index;
421
+ while (num > 0) {
422
+ num--;
423
+ result = String.fromCharCode(65 + (num % 26)) + result;
424
+ num = Math.floor(num / 26);
425
+ }
426
+ return result;
427
+ }
428
+ /**
429
+ * Extract hidden dimension strategy from plotVisOnly attribute
430
+ * In Excel: plotVisOnly="1" means skip hidden rows/columns, "0" means show all
431
+ */
432
+ export function extractHiddenDimensionStrategy(chart) {
433
+ const plotVisOnly = getWithFallback(chart, [
434
+ "c:plotVisOnly.a$.val",
435
+ "c:plotVisOnly.a$.t$",
436
+ "plotVisOnly.a$.val",
437
+ "plotVisOnly.a$.t$",
438
+ ]);
439
+ if (plotVisOnly === undefined) {
440
+ return undefined;
441
+ }
442
+ // "1" or "true" = skip hidden cells, "0" or "false" = show all
443
+ const skipHidden = plotVisOnly === "1" || plotVisOnly === "true" || plotVisOnly === true;
444
+ return skipHidden ? "SKIP_HIDDEN_ROWS_AND_COLUMNS" : "SHOW_ALL";
445
+ }
446
+ /**
447
+ * Read the chart title's text formatting. Excel chart titles can be
448
+ * styled two ways:
449
+ * - via `<c:title><c:txPr>` (paragraph default, affects future
450
+ * content); writers use this when no inline rich text is present.
451
+ * - via `<c:title><c:tx><c:rich>` (the title text itself, with
452
+ * per-paragraph `<a:pPr><a:defRPr>` carrying the styling).
453
+ * We probe both, with the rich-text path winning when present (it's
454
+ * the more explicit signal). Returns `undefined` when neither carries
455
+ * formatting so the renderer falls back to its theme defaults.
456
+ */
457
+ function extractChartTitleTextFormat(chart) {
458
+ const title = get(chart, "c:title") ?? get(chart, "title");
459
+ if (!title)
460
+ return undefined;
461
+ const richTxPr = get(title, "c:tx.c:rich") ?? get(title, "tx.rich");
462
+ const fromRich = extractTextProperties(richTxPr);
463
+ if (fromRich)
464
+ return fromRich;
465
+ const txPr = get(title, "c:txPr") ?? get(title, "txPr");
466
+ return extractTextProperties(txPr);
467
+ }
468
+ export function extractChartTitle(chart) {
469
+ // Try different possible paths for chart title
470
+ const title =
471
+ // Rich text format (your current structure)
472
+ get(chart, "c:title.c:tx.c:rich.a:p.a:r.a:t") ||
473
+ // Alternative rich text paths
474
+ get(chart, "c:title.c:tx.c:rich.a:p.a:r.a:t.t$") ||
475
+ // String reference format
476
+ get(chart, "c:title.c:tx.c:strRef.c:strCache.c:pt.c:v") ||
477
+ get(chart, "c:title.c:tx.c:strRef.c:strCache.c:pt.c:v.t$") ||
478
+ // Direct value format
479
+ get(chart, "c:title.c:tx.c:v") ||
480
+ get(chart, "c:title.c:tx.c:v.t$") ||
481
+ // Simple text format
482
+ get(chart, "c:title.c:tx") ||
483
+ // Fallback: look for any text in title
484
+ extractTextFromTitle(get(chart, "c:title")) ||
485
+ "";
486
+ return typeof title === "string" ? title : "";
487
+ }
488
+ // Helper function to recursively search for text in title structure
489
+ function extractTextFromTitle(titleObj) {
490
+ if (!titleObj)
491
+ return "";
492
+ // If it's already a string, return it
493
+ if (typeof titleObj === "string")
494
+ return titleObj;
495
+ // Look for common text properties
496
+ if (titleObj["a:t"])
497
+ return titleObj["a:t"];
498
+ if (titleObj.t$)
499
+ return titleObj.t$;
500
+ if (titleObj.text)
501
+ return titleObj.text;
502
+ if (titleObj.value)
503
+ return titleObj.value;
504
+ // Recursively search through nested objects
505
+ for (const key in titleObj) {
506
+ if (typeof titleObj[key] === "object") {
507
+ const result = extractTextFromTitle(titleObj[key]);
508
+ if (result)
509
+ return result;
510
+ }
511
+ }
512
+ return "";
513
+ }
514
+ export function determineChartType(chart) {
515
+ const plotArea = getWithFallback(chart, ["c:plotArea", "plotArea"]);
516
+ // c15 extension marker (Phase 5.5): if the plotArea's c:extLst tags
517
+ // the chart as histogram / boxplot / waterfall / funnel, that overrides
518
+ // the bar-chart fallback the body uses for older-Excel compatibility.
519
+ const c15Type = detectC15ChartType(plotArea);
520
+ if (c15Type)
521
+ return c15Type;
522
+ const barChart = getWithFallback(plotArea, [
523
+ "c:barChart",
524
+ "barChart",
525
+ "c:bar3DChart",
526
+ "bar3DChart",
527
+ ]);
528
+ if (barChart) {
529
+ const direction = get(barChart, "c:barDir.a$.val") ||
530
+ get(barChart, "c:barDir.a$.t$") ||
531
+ get(barChart, "barDir.a$.val") ||
532
+ get(barChart, "barDir.a$.t$") ||
533
+ get(barChart, "c:barDir.a$.v") ||
534
+ get(barChart, "barDir.a$.v");
535
+ return direction === "bar" ? "bar" : "column";
536
+ }
537
+ if (getWithFallback(plotArea, [
538
+ "c:lineChart",
539
+ "lineChart",
540
+ "c:line3DChart",
541
+ "line3DChart",
542
+ ]))
543
+ return "line";
544
+ // OOXML uses a dedicated <c:doughnutChart> element. Check it before
545
+ // <c:pieChart>/<c:ofPieChart> so the more-specific shape wins.
546
+ if (getWithFallback(plotArea, ["c:doughnutChart", "doughnutChart"]))
547
+ return "doughnut";
548
+ // pie-of-pie / bar-of-pie — the renderer draws the composite from a
549
+ // plain "pie" chartType plus sourceChartType "OF_PIE" and the of-pie
550
+ // split fields (extracted in extractOfPieOptions).
551
+ if (getWithFallback(plotArea, ["c:ofPieChart", "ofPieChart"]))
552
+ return "pie";
553
+ if (getWithFallback(plotArea, [
554
+ "c:pieChart",
555
+ "pieChart",
556
+ "c:pie3DChart",
557
+ "pie3DChart",
558
+ ]))
559
+ return "pie";
560
+ if (getWithFallback(plotArea, [
561
+ "c:areaChart",
562
+ "areaChart",
563
+ "c:area3DChart",
564
+ "area3DChart",
565
+ ]))
566
+ return "area";
567
+ if (getWithFallback(plotArea, ["c:radarChart", "radarChart"]))
568
+ return "radar";
569
+ if (getWithFallback(plotArea, ["c:stockChart", "stockChart"]))
570
+ return "stock";
571
+ // Scatter and bubble — both use c:xVal/c:yVal series shape (not
572
+ // c:cat/c:val like other charts). Bubble additionally has c:bubbleSize.
573
+ if (getWithFallback(plotArea, ["c:scatterChart", "scatterChart"]))
574
+ return "scatter";
575
+ if (getWithFallback(plotArea, ["c:bubbleChart", "bubbleChart"]))
576
+ return "bubble";
577
+ return "column"; // default
578
+ }
579
+ /**
580
+ * Phase 5.5 — detect the c15 extension marker our exporter writes on
581
+ * the plotArea. Returns the originating chart type (histogram /
582
+ * boxplot / waterfall / funnel) or null if no marker is present.
583
+ *
584
+ * The marker shape is:
585
+ * c:plotArea > c:extLst > c:ext (uri = ...) > c15:<chartName>Chart
586
+ *
587
+ * We accept either array or single-object shape for `c:ext` since
588
+ * fast-xml-parser normalizes lone children to objects.
589
+ */
590
+ function detectC15ChartType(plotArea) {
591
+ if (!plotArea)
592
+ return null;
593
+ // Empty c15 elements (`<c15:histogramChart/>`) parse to "" — truthy
594
+ // checks skip them, so we test for key presence instead.
595
+ const hasKey = (obj, key) => obj != null && Object.prototype.hasOwnProperty.call(obj, key);
596
+ // Phase 5.5b: c15 chart wrapped in mc:AlternateContent > mc:Choice.
597
+ // This is the Excel-native shape — modern Excel renders the chart
598
+ // from here, older Excel uses mc:Fallback (a c:barChart).
599
+ const altContent = getWithFallback(plotArea, [
600
+ "mc:AlternateContent",
601
+ "AlternateContent",
602
+ ]);
603
+ if (altContent) {
604
+ const choice = getWithFallback(altContent, ["mc:Choice", "Choice"]);
605
+ if (choice) {
606
+ const choices = Array.isArray(choice) ? choice : [choice];
607
+ for (const c of choices) {
608
+ if (hasKey(c, "c15:histogramChart") || hasKey(c, "histogramChart"))
609
+ return "histogram";
610
+ if (hasKey(c, "c15:boxWhiskerChart") || hasKey(c, "boxWhiskerChart"))
611
+ return "boxplot";
612
+ if (hasKey(c, "c15:waterfallChart") || hasKey(c, "waterfallChart"))
613
+ return "waterfall";
614
+ if (hasKey(c, "c15:funnelChart") || hasKey(c, "funnelChart"))
615
+ return "funnel";
616
+ }
617
+ }
618
+ }
619
+ // Phase 5.5 (legacy): c15 extension marker inside c:extLst > c:ext.
620
+ // Round-trips through our pipeline; opens as bar chart in Excel.
621
+ // Kept so files written by older versions of this code still parse.
622
+ const extLst = getWithFallback(plotArea, ["c:extLst", "extLst"]);
623
+ if (!extLst)
624
+ return null;
625
+ const extNode = getWithFallback(extLst, ["c:ext", "ext"]);
626
+ if (!extNode)
627
+ return null;
628
+ const exts = Array.isArray(extNode) ? extNode : [extNode];
629
+ for (const ext of exts) {
630
+ if (!ext)
631
+ continue;
632
+ if (hasKey(ext, "c15:histogramChart") || hasKey(ext, "histogramChart"))
633
+ return "histogram";
634
+ if (hasKey(ext, "c15:boxWhiskerChart") || hasKey(ext, "boxWhiskerChart"))
635
+ return "boxplot";
636
+ if (hasKey(ext, "c15:waterfallChart") || hasKey(ext, "waterfallChart"))
637
+ return "waterfall";
638
+ if (hasKey(ext, "c15:funnelChart") || hasKey(ext, "funnelChart"))
639
+ return "funnel";
640
+ }
641
+ return null;
642
+ }
643
+ /**
644
+ * Phase 5.5 — pull binCount out of the c15:histogramChart extension
645
+ * marker. Returns undefined if absent.
646
+ */
647
+ export function extractC15HistogramBinCount(plotArea) {
648
+ if (!plotArea)
649
+ return undefined;
650
+ const readBinCount = (hist) => {
651
+ if (!hist)
652
+ return undefined;
653
+ const raw = get(hist, "c15:binCount.a$.val") ??
654
+ get(hist, "binCount.a$.val") ??
655
+ get(hist, "c15:binCount.a$.v") ??
656
+ get(hist, "binCount.a$.v");
657
+ if (raw === undefined || raw === null)
658
+ return undefined;
659
+ const num = Number(raw);
660
+ return Number.isFinite(num) ? num : undefined;
661
+ };
662
+ // Phase 5.5b: histogram inside mc:AlternateContent > mc:Choice.
663
+ const altContent = getWithFallback(plotArea, [
664
+ "mc:AlternateContent",
665
+ "AlternateContent",
666
+ ]);
667
+ if (altContent) {
668
+ const choice = getWithFallback(altContent, ["mc:Choice", "Choice"]);
669
+ const choices = Array.isArray(choice)
670
+ ? choice
671
+ : choice
672
+ ? [choice]
673
+ : [];
674
+ for (const c of choices) {
675
+ const hist = c?.["c15:histogramChart"] ?? c?.["histogramChart"] ?? null;
676
+ const v = readBinCount(hist);
677
+ if (v !== undefined)
678
+ return v;
679
+ }
680
+ }
681
+ // Phase 5.5 (legacy): histogram inside c:extLst > c:ext marker.
682
+ const extLst = getWithFallback(plotArea, ["c:extLst", "extLst"]);
683
+ if (!extLst)
684
+ return undefined;
685
+ const extNode = getWithFallback(extLst, ["c:ext", "ext"]);
686
+ if (!extNode)
687
+ return undefined;
688
+ const exts = Array.isArray(extNode) ? extNode : [extNode];
689
+ for (const ext of exts) {
690
+ const hist = ext?.["c15:histogramChart"] ?? ext?.["histogramChart"] ?? null;
691
+ const v = readBinCount(hist);
692
+ if (v !== undefined)
693
+ return v;
694
+ }
695
+ return undefined;
696
+ }
697
+ // Chart type containers to search for series. Combo charts can mix
698
+ // multiple containers (e.g. column + line on one chart), so we iterate
699
+ // through all of them. XY charts (scatter/bubble) have a different
700
+ // per-series shape (xVal/yVal/bubbleSize) and require their own series
701
+ // extractor — they're listed here so combo charts that mix XY with
702
+ // non-XY series (rare but valid in OOXML) still pick up both.
703
+ const CHART_TYPE_CONTAINERS = [
704
+ {
705
+ paths: ["c:barChart", "barChart", "c:bar3DChart", "bar3DChart"],
706
+ chartType: "column",
707
+ isBar: true,
708
+ },
709
+ {
710
+ paths: ["c:lineChart", "lineChart", "c:line3DChart", "line3DChart"],
711
+ chartType: "line",
712
+ },
713
+ {
714
+ paths: ["c:areaChart", "areaChart", "c:area3DChart", "area3DChart"],
715
+ chartType: "area",
716
+ },
717
+ {
718
+ paths: ["c:scatterChart", "scatterChart"],
719
+ chartType: "scatter",
720
+ isXY: true,
721
+ },
722
+ {
723
+ paths: ["c:bubbleChart", "bubbleChart"],
724
+ chartType: "bubble",
725
+ isXY: true,
726
+ },
727
+ ];
728
+ /**
729
+ * Resolve a chart-formula range string (e.g.
730
+ * `'Data for the Chart'!$A$7:$A$26`) into the sources array our chart
731
+ * model expects (one entry per parsed selection, with the data sheet's
732
+ * sheetId — not the chart's host sheet).
733
+ */
734
+ function resolveSourcesFromRef(ref, hostSheetId, resolveSheetId) {
735
+ const selections = generateSelectionsFromFormula(`=${ref}`);
736
+ return selections.map((sel) => ({
737
+ ...sel.range,
738
+ sheetId: pickSheetId(sel.sheetName, hostSheetId, resolveSheetId),
739
+ }));
740
+ }
741
+ /**
742
+ * Extract inline values from a `<c:strLit>` or `<c:numLit>` element —
743
+ * these appear when chart data is embedded directly in the chart XML
744
+ * rather than referencing the sheet (Event Budget.xlsx uses this for
745
+ * its `["Estimated", "Actual"]` category labels).
746
+ *
747
+ * Returns the values ordered by their `idx` attribute (Excel allows
748
+ * sparse indices). Returns `undefined` when no inline data is present.
749
+ */
750
+ function extractInlineValues(node) {
751
+ if (!node)
752
+ return undefined;
753
+ const strLit = get(node, "c:strLit") ?? get(node, "strLit");
754
+ const numLit = get(node, "c:numLit") ?? get(node, "numLit");
755
+ const lit = strLit ?? numLit;
756
+ if (!lit)
757
+ return undefined;
758
+ const pts = get(lit, "c:pt") ?? get(lit, "pt");
759
+ if (!pts)
760
+ return [];
761
+ const ptArray = Array.isArray(pts) ? pts : [pts];
762
+ // Order by the `idx` attribute. Most files emit them in order but
763
+ // spec permits any order; we honour that to avoid mis-aligning the
764
+ // first column when authors hand-craft chart XML.
765
+ const indexed = [];
766
+ for (const pt of ptArray) {
767
+ const idxAttr = get(pt, "a$.idx");
768
+ const idx = idxAttr !== undefined ? Number(idxAttr) : indexed.length;
769
+ const rawV = get(pt, "c:v") ?? get(pt, "v");
770
+ let v;
771
+ if (typeof rawV === "number" || typeof rawV === "boolean") {
772
+ v = rawV;
773
+ }
774
+ else if (numLit && typeof rawV === "string" && rawV.trim() !== "") {
775
+ const n = Number(rawV);
776
+ v = Number.isFinite(n) ? n : rawV;
777
+ }
778
+ else {
779
+ v = typeof rawV === "string" ? rawV : String(rawV ?? "");
780
+ }
781
+ indexed.push({ idx: Number.isFinite(idx) ? idx : indexed.length, v });
782
+ }
783
+ indexed.sort((a, b) => a.idx - b.idx);
784
+ return indexed.map((entry) => entry.v);
785
+ }
786
+ export function extractChartSeries(plotArea, chartType, sheetId, resolveSheetId) {
787
+ const series = [];
788
+ let seriesIndex = 0;
789
+ // Iterate through all chart type containers to support combo charts
790
+ for (const container of CHART_TYPE_CONTAINERS) {
791
+ const chartElement = getWithFallback(plotArea, container.paths);
792
+ if (!chartElement)
793
+ continue;
794
+ // Handle case where chart element is an array (e.g., multiple c:lineChart elements)
795
+ const chartElements = Array.isArray(chartElement)
796
+ ? chartElement
797
+ : [chartElement];
798
+ for (const singleChartElement of chartElements) {
799
+ // Determine the actual chart type for this container
800
+ let seriesChartType = container.chartType;
801
+ if (container.isBar) {
802
+ // Check barDir to distinguish bar vs column
803
+ const direction = get(singleChartElement, "c:barDir.a$.val") ||
804
+ get(singleChartElement, "c:barDir.a$.t$") ||
805
+ get(singleChartElement, "barDir.a$.val") ||
806
+ get(singleChartElement, "barDir.a$.t$");
807
+ seriesChartType = direction === "bar" ? "bar" : "column";
808
+ }
809
+ // Get series elements from this container
810
+ const seriesElements = getWithFallback(singleChartElement, [
811
+ "c:ser",
812
+ "ser",
813
+ ]);
814
+ if (!seriesElements)
815
+ continue;
816
+ const seriesArray = Array.isArray(seriesElements)
817
+ ? seriesElements
818
+ : [seriesElements];
819
+ for (let i = 0; i < seriesArray.length; i++) {
820
+ const ser = seriesArray[i];
821
+ // Scatter/bubble — "series" carries Y values (yVal). Non-XY —
822
+ // it carries c:val.
823
+ const valRef = container.isXY
824
+ ? getWithFallback(ser, Y_VAL_REF_PATHS)
825
+ : getWithFallback(ser, VAL_REF_PATHS);
826
+ const seriesTitle = extractSeriesTitleFromSeries(ser);
827
+ const colorInfo = extractSeriesColorInfo(ser);
828
+ // Resolve the series' value source — preferring the cell-range
829
+ // reference and falling back to inline `<c:numLit>`/`<c:strLit>`
830
+ // when there's no range (rare for series values, common for
831
+ // tiny embedded charts).
832
+ let sources = [];
833
+ let inlineValues;
834
+ if (valRef) {
835
+ sources = resolveSourcesFromRef(valRef, sheetId, resolveSheetId);
836
+ }
837
+ if (!sources.length) {
838
+ const valNode = container.isXY
839
+ ? get(ser, "c:yVal") ?? get(ser, "yVal")
840
+ : get(ser, "c:val") ?? get(ser, "val");
841
+ inlineValues = extractInlineValues(valNode);
842
+ }
843
+ if (!sources.length && (!inlineValues || !inlineValues.length)) {
844
+ seriesIndex++;
845
+ continue;
846
+ }
847
+ const trendlines = extractTrendlines(ser);
848
+ const errorBars = extractErrorBars(ser, sheetId, resolveSheetId);
849
+ const marker = extractSeriesMarker(ser);
850
+ const dataPointColors = extractSeriesDataPointColors(ser);
851
+ const nameRef = getWithFallback(ser, TX_REF_PATHS);
852
+ // Per-series category reference — charts whose series plot against
853
+ // different category ranges can't be represented by the single
854
+ // chart-level domain alone; the exporter prefers this when present.
855
+ const serCatRef = container.isXY
856
+ ? undefined
857
+ : getWithFallback(ser, CAT_REF_PATHS);
858
+ const categorySources = serCatRef
859
+ ? resolveSourcesFromRef(serCatRef, sheetId, resolveSheetId)
860
+ : [];
861
+ series.push({
862
+ dataLabel: seriesTitle || `Series ${seriesIndex + 1}`,
863
+ ...(nameRef ? { dataLabelRef: nameRef } : {}),
864
+ ...(categorySources.length ? { categorySources } : {}),
865
+ color: colorInfo.color,
866
+ ...(colorInfo.noFill ? { noFill: true } : {}),
867
+ negativeColor: colorInfo.negativeColor,
868
+ invertIfNegative: colorInfo.invertIfNegative,
869
+ ...(colorInfo.gradient ? { gradient: colorInfo.gradient } : {}),
870
+ // `seriesChartType` is only meaningful for combo charts where
871
+ // individual series can override the chart's overall type
872
+ // (bar+line, column+line, etc.). XY charts (scatter/bubble)
873
+ // aren't part of combo mixing — leave it unset there so the
874
+ // renderer falls back to the chart-level chartType.
875
+ ...(container.isXY ? {} : { seriesChartType: seriesChartType }),
876
+ sources,
877
+ ...(inlineValues && inlineValues.length ? { inlineValues } : {}),
878
+ ...(trendlines && trendlines.length ? { trendlines } : {}),
879
+ ...(errorBars ? { errorBars } : {}),
880
+ ...(marker ? { marker } : {}),
881
+ ...(dataPointColors && dataPointColors.length
882
+ ? { dataPointColors }
883
+ : {}),
884
+ });
885
+ seriesIndex++;
886
+ }
887
+ }
888
+ }
889
+ // Fallback: pie/doughnut/radar/stock/ofPie/etc. aren't in
890
+ // CHART_TYPE_CONTAINERS; they're single-container, type-specific,
891
+ // and don't combo with other types. Pick up via the type-specific
892
+ // path-set helper.
893
+ if (series.length === 0) {
894
+ const seriesElements = getSeriesElements(plotArea, chartType);
895
+ if (seriesElements) {
896
+ const seriesArray = Array.isArray(seriesElements)
897
+ ? seriesElements
898
+ : [seriesElements];
899
+ seriesArray.forEach((ser, index) => {
900
+ const valRef = isXYChart(chartType)
901
+ ? getWithFallback(ser, Y_VAL_REF_PATHS)
902
+ : getWithFallback(ser, VAL_REF_PATHS);
903
+ const seriesTitle = extractSeriesTitleFromSeries(ser);
904
+ const colorInfo = extractSeriesColorInfo(ser);
905
+ let sources = [];
906
+ let inlineValues;
907
+ if (valRef) {
908
+ sources = resolveSourcesFromRef(valRef, sheetId, resolveSheetId);
909
+ }
910
+ if (!sources.length) {
911
+ const valNode = isXYChart(chartType)
912
+ ? get(ser, "c:yVal") ?? get(ser, "yVal")
913
+ : get(ser, "c:val") ?? get(ser, "val");
914
+ inlineValues = extractInlineValues(valNode);
915
+ }
916
+ if (!sources.length && (!inlineValues || !inlineValues.length)) {
917
+ return;
918
+ }
919
+ const marker = extractSeriesMarker(ser);
920
+ const dataPointColors = extractSeriesDataPointColors(ser);
921
+ const nameRef = getWithFallback(ser, TX_REF_PATHS);
922
+ const serCatRef = isXYChart(chartType)
923
+ ? undefined
924
+ : getWithFallback(ser, CAT_REF_PATHS);
925
+ const categorySources = serCatRef
926
+ ? resolveSourcesFromRef(serCatRef, sheetId, resolveSheetId)
927
+ : [];
928
+ series.push({
929
+ dataLabel: seriesTitle || `Series ${index + 1}`,
930
+ ...(nameRef ? { dataLabelRef: nameRef } : {}),
931
+ ...(categorySources.length ? { categorySources } : {}),
932
+ color: colorInfo.color,
933
+ ...(colorInfo.noFill ? { noFill: true } : {}),
934
+ negativeColor: colorInfo.negativeColor,
935
+ invertIfNegative: colorInfo.invertIfNegative,
936
+ ...(colorInfo.gradient ? { gradient: colorInfo.gradient } : {}),
937
+ sources,
938
+ ...(inlineValues && inlineValues.length ? { inlineValues } : {}),
939
+ ...(marker ? { marker } : {}),
940
+ ...(dataPointColors && dataPointColors.length
941
+ ? { dataPointColors }
942
+ : {}),
943
+ });
944
+ });
945
+ }
946
+ }
947
+ return series;
948
+ }
949
+ /**
950
+ * Extract per-data-point color overrides — `<c:dPt>` array inside
951
+ * `<c:ser>`. Each entry pins a specific category index to its own
952
+ * fill color (Gantt charts use this to colour individual tasks; pie
953
+ * charts use it for per-slice colors). Returns `undefined` when there
954
+ * are no overrides.
955
+ */
956
+ function extractSeriesDataPointColors(ser) {
957
+ const raw = get(ser, "c:dPt") ?? get(ser, "dPt");
958
+ if (!raw)
959
+ return undefined;
960
+ const list = Array.isArray(raw) ? raw : [raw];
961
+ const result = [];
962
+ for (const dPt of list) {
963
+ const idxRaw = get(dPt, "c:idx.a$.val") ?? get(dPt, "idx.a$.val");
964
+ const idx = idxRaw !== undefined ? Number(idxRaw) : NaN;
965
+ if (!Number.isFinite(idx))
966
+ continue;
967
+ const solidFill = get(dPt, "c:spPr.a:solidFill") ?? get(dPt, "spPr.a:solidFill");
968
+ const color = resolveDrawingColor(solidFill);
969
+ if (!color)
970
+ continue;
971
+ result.push({ idx, color });
972
+ }
973
+ return result.length ? result : undefined;
974
+ }
975
+ const MARKER_SYMBOL_SET = new Set([
976
+ "none",
977
+ "circle",
978
+ "square",
979
+ "diamond",
980
+ "triangle",
981
+ "x",
982
+ "plus",
983
+ "star",
984
+ "dash",
985
+ "dot",
986
+ "auto",
987
+ "picture",
988
+ ]);
989
+ function extractSeriesMarker(ser) {
990
+ const raw = get(ser, "c:marker") ?? get(ser, "marker");
991
+ if (!raw)
992
+ return undefined;
993
+ const symbolRaw = get(raw, "c:symbol.a$.val") ??
994
+ get(raw, "symbol.a$.val") ??
995
+ get(raw, "c:symbol.a$.t$") ??
996
+ get(raw, "symbol.a$.t$");
997
+ const symbol = typeof symbolRaw === "string" &&
998
+ MARKER_SYMBOL_SET.has(symbolRaw)
999
+ ? symbolRaw
1000
+ : undefined;
1001
+ const sizeAttr = get(raw, "c:size.a$.val") ??
1002
+ get(raw, "size.a$.val") ??
1003
+ get(raw, "c:size.a$.t$") ??
1004
+ get(raw, "size.a$.t$");
1005
+ const size = sizeAttr !== undefined && sizeAttr !== null && sizeAttr !== ""
1006
+ ? Number(sizeAttr)
1007
+ : undefined;
1008
+ // Marker fill — used when explicit (not derived from series color).
1009
+ const fillRgb = get(raw, "c:spPr.a:solidFill.a:srgbClr.a$.val") ??
1010
+ get(raw, "spPr.solidFill.srgbClr.a$.val");
1011
+ const color = fillRgb ? `#${fillRgb}` : undefined;
1012
+ const result = {};
1013
+ if (symbol)
1014
+ result.symbol = symbol;
1015
+ if (typeof size === "number" && Number.isFinite(size))
1016
+ result.size = size;
1017
+ if (color)
1018
+ result.color = color;
1019
+ return Object.keys(result).length ? result : undefined;
1020
+ }
1021
+ // ─── Text property extraction (color, font) ──────────────────────────
1022
+ // Used by `readAxis`, `extractLegendOptions`, `extractDataLabelsOptions`,
1023
+ // `extractChartTitle` to pull `<c:txPr>` / `<a:rPr>` styling so axis
1024
+ // labels, data labels, legend entries and chart titles render with
1025
+ // their authored colors instead of falling back to the renderer's
1026
+ // theme default (the BEP.xlsx import showed all labels as
1027
+ // near-black).
1028
+ const OFFICE_THEME_FALLBACK = {
1029
+ // Standard Office theme color tokens. We don't try to resolve from
1030
+ // the workbook's actual `xl/theme/themeN.xml` here — the chart is
1031
+ // typically authored against the same theme palette and these
1032
+ // defaults match the in-app theme defaults closely enough that the
1033
+ // visual difference is below noise level.
1034
+ tx1: "#000000",
1035
+ tx2: "#44546A",
1036
+ bg1: "#FFFFFF",
1037
+ bg2: "#E7E6E6",
1038
+ dk1: "#000000",
1039
+ dk2: "#44546A",
1040
+ lt1: "#FFFFFF",
1041
+ lt2: "#E7E6E6",
1042
+ accent1: "#4472C4",
1043
+ accent2: "#ED7D31",
1044
+ accent3: "#A5A5A5",
1045
+ accent4: "#FFC000",
1046
+ accent5: "#5B9BD5",
1047
+ accent6: "#70AD47",
1048
+ hlink: "#0563C1",
1049
+ folHlink: "#954F72",
1050
+ };
1051
+ /**
1052
+ * Apply OOXML lumMod / lumOff / shade / tint modifiers to a base RGB
1053
+ * color. All are stored as 1000ths of a percent (75000 = 75%).
1054
+ *
1055
+ * - `lumMod` scales each channel (`c * mod`)
1056
+ * - `lumOff` adds toward white (`+ 255 * off`)
1057
+ * - `shade` darkens — interpolates toward black (`c * shade`)
1058
+ * - `tint` lightens — interpolates toward white (`c + (255 - c) * tint`)
1059
+ *
1060
+ * Order: shade/tint apply first (they're independent transforms), then
1061
+ * lumMod/lumOff. Matches Excel's behaviour closely for common patterns:
1062
+ * - `tx1 + lumMod=75% + lumOff=25%` → `#404040` dark gray (BEP)
1063
+ * - `accent1 + shade=76%` → darker accent1 (Gantt)
1064
+ */
1065
+ const applyLumModOff = (hex, lumMod, lumOff, shade, tint) => {
1066
+ const m = /^#?([0-9a-fA-F]{6})$/.exec(hex);
1067
+ if (!m)
1068
+ return hex;
1069
+ const v = parseInt(m[1], 16);
1070
+ let r = (v >> 16) & 0xff;
1071
+ let g = (v >> 8) & 0xff;
1072
+ let b = v & 0xff;
1073
+ // shade darkens (multiply by factor < 1); tint lightens (interp
1074
+ // toward white). Apply before lumMod/lumOff since those mix in a
1075
+ // different luminance space and Excel evaluates shade/tint first.
1076
+ if (typeof shade === "number" && shade > 0 && shade < 1) {
1077
+ r = r * shade;
1078
+ g = g * shade;
1079
+ b = b * shade;
1080
+ }
1081
+ if (typeof tint === "number" && tint > 0 && tint < 1) {
1082
+ r = r + (255 - r) * tint;
1083
+ g = g + (255 - g) * tint;
1084
+ b = b + (255 - b) * tint;
1085
+ }
1086
+ const mod = typeof lumMod === "number" ? lumMod : 1;
1087
+ const off = typeof lumOff === "number" ? lumOff : 0;
1088
+ const apply = (c) => Math.max(0, Math.min(255, Math.round(c * mod + 255 * off)));
1089
+ const toHex = (c) => c.toString(16).padStart(2, "0").toUpperCase();
1090
+ return `#${toHex(apply(r))}${toHex(apply(g))}${toHex(apply(b))}`;
1091
+ };
1092
+ /**
1093
+ * Resolve a DrawingML `<a:solidFill>` (or any element that wraps an
1094
+ * `<a:srgbClr>` / `<a:schemeClr>`) to a hex color string. Handles
1095
+ * `lumMod`/`lumOff` modifiers used to derive grays from theme tokens.
1096
+ */
1097
+ function resolveDrawingColor(node) {
1098
+ if (!node)
1099
+ return undefined;
1100
+ // Direct RGB.
1101
+ const srgb = get(node, "a:srgbClr.a$.val") ?? get(node, "srgbClr.a$.val");
1102
+ if (srgb) {
1103
+ const lumMod = readPctNumber(get(node, "a:srgbClr.a:lumMod.a$.val") ??
1104
+ get(node, "srgbClr.lumMod.a$.val"));
1105
+ const lumOff = readPctNumber(get(node, "a:srgbClr.a:lumOff.a$.val") ??
1106
+ get(node, "srgbClr.lumOff.a$.val"));
1107
+ const shade = readPctNumber(get(node, "a:srgbClr.a:shade.a$.val") ??
1108
+ get(node, "srgbClr.shade.a$.val"));
1109
+ const tint = readPctNumber(get(node, "a:srgbClr.a:tint.a$.val") ??
1110
+ get(node, "srgbClr.tint.a$.val"));
1111
+ return applyLumModOff(`#${srgb}`, lumMod, lumOff, shade, tint);
1112
+ }
1113
+ // Theme reference.
1114
+ const schemeName = get(node, "a:schemeClr.a$.val") ?? get(node, "schemeClr.a$.val");
1115
+ if (typeof schemeName === "string") {
1116
+ const base = OFFICE_THEME_FALLBACK[schemeName] ?? "#000000";
1117
+ const lumMod = readPctNumber(get(node, "a:schemeClr.a:lumMod.a$.val") ??
1118
+ get(node, "schemeClr.lumMod.a$.val"));
1119
+ const lumOff = readPctNumber(get(node, "a:schemeClr.a:lumOff.a$.val") ??
1120
+ get(node, "schemeClr.lumOff.a$.val"));
1121
+ const shade = readPctNumber(get(node, "a:schemeClr.a:shade.a$.val") ??
1122
+ get(node, "schemeClr.shade.a$.val"));
1123
+ const tint = readPctNumber(get(node, "a:schemeClr.a:tint.a$.val") ??
1124
+ get(node, "schemeClr.tint.a$.val"));
1125
+ return applyLumModOff(base, lumMod, lumOff, shade, tint);
1126
+ }
1127
+ return undefined;
1128
+ }
1129
+ /** Parse an OOXML percentage stored as 1000ths-of-a-percent → 0..1. */
1130
+ function readPctNumber(val) {
1131
+ if (val === undefined || val === null || val === "")
1132
+ return undefined;
1133
+ const n = Number(val);
1134
+ return Number.isFinite(n) ? n / 100000 : undefined;
1135
+ }
1136
+ /**
1137
+ * Extract a `ChartTextFormat` from an OOXML text-properties node. The
1138
+ * input is typically `<c:txPr>` or `<c:title><c:tx><c:rich>` — both
1139
+ * use the same `<a:p><a:pPr><a:defRPr>…</a:defRPr></a:pPr></a:p>`
1140
+ * shape to express paragraph-default character properties.
1141
+ *
1142
+ * Returns `undefined` when the node carries no formatting (so the
1143
+ * caller doesn't emit an empty `textFormat` field).
1144
+ */
1145
+ function extractTextProperties(txPr) {
1146
+ if (!txPr)
1147
+ return undefined;
1148
+ // The font/color lives on `<a:p><a:pPr><a:defRPr>`. Some writers put
1149
+ // it directly on `<a:r><a:rPr>` for rich titles — accept both.
1150
+ const defRPr = get(txPr, "a:p.a:pPr.a:defRPr") ??
1151
+ get(txPr, "p.pPr.defRPr") ??
1152
+ get(txPr, "a:p.a:r.a:rPr") ??
1153
+ get(txPr, "p.r.rPr");
1154
+ if (!defRPr)
1155
+ return undefined;
1156
+ const result = {};
1157
+ // Font size — `sz` is in 100ths of a point (`sz="900"` → 9pt).
1158
+ const szAttr = get(defRPr, "a$.sz") ?? get(defRPr, "a$.t$");
1159
+ if (szAttr !== undefined && szAttr !== null && szAttr !== "") {
1160
+ const sz = Number(szAttr);
1161
+ if (Number.isFinite(sz) && sz > 0)
1162
+ result.fontSize = sz / 100;
1163
+ }
1164
+ // Bold / italic / underline / strikethrough.
1165
+ const bAttr = get(defRPr, "a$.b");
1166
+ if (bAttr === "1" || bAttr === 1 || bAttr === true)
1167
+ result.bold = true;
1168
+ const iAttr = get(defRPr, "a$.i");
1169
+ if (iAttr === "1" || iAttr === 1 || iAttr === true)
1170
+ result.italic = true;
1171
+ const uAttr = get(defRPr, "a$.u");
1172
+ if (typeof uAttr === "string" && uAttr !== "none")
1173
+ result.underline = true;
1174
+ const strikeAttr = get(defRPr, "a$.strike");
1175
+ if (typeof strikeAttr === "string" && strikeAttr !== "noStrike") {
1176
+ result.strikethrough = true;
1177
+ }
1178
+ // Font family — `<a:latin typeface="…">`.
1179
+ const latin = get(defRPr, "a:latin.a$.typeface") ?? get(defRPr, "latin.a$.typeface");
1180
+ if (typeof latin === "string" && latin)
1181
+ result.fontFamily = latin;
1182
+ // Color — `<a:solidFill>` containing `<a:srgbClr>` or `<a:schemeClr>`.
1183
+ const solidFill = get(defRPr, "a:solidFill") ?? get(defRPr, "solidFill");
1184
+ const color = resolveDrawingColor(solidFill);
1185
+ if (color)
1186
+ result.color = color;
1187
+ return Object.keys(result).length ? result : undefined;
1188
+ }
1189
+ /**
1190
+ * Read bar/column `<c:grouping>`, `<c:overlap>`, `<c:gapWidth>` —
1191
+ * controls how multi-series bars are arranged (clustered / stacked /
1192
+ * percent-stacked). Looks inside `<c:barChart>` / `<c:bar3DChart>`
1193
+ * containers; returns `undefined` if no settings are present so the
1194
+ * renderer keeps its defaults.
1195
+ */
1196
+ function extractBarLayoutOptions(plotArea) {
1197
+ const bar = get(plotArea, "c:barChart") ??
1198
+ get(plotArea, "barChart") ??
1199
+ get(plotArea, "c:bar3DChart") ??
1200
+ get(plotArea, "bar3DChart");
1201
+ if (!bar)
1202
+ return undefined;
1203
+ // `<c:grouping val="clustered|stacked|percentStacked|standard"/>`.
1204
+ // `standard` is "side-by-side, no stacking" — same as `clustered`
1205
+ // semantically; map both to UNSTACKED.
1206
+ const groupingVal = get(bar, "c:grouping.a$.val") ??
1207
+ get(bar, "grouping.a$.val") ??
1208
+ get(bar, "c:grouping.a$.t$") ??
1209
+ get(bar, "grouping.a$.t$");
1210
+ let stackedType;
1211
+ switch (groupingVal) {
1212
+ case "stacked":
1213
+ stackedType = "STACKED";
1214
+ break;
1215
+ case "percentStacked":
1216
+ stackedType = "PERCENT_STACKED";
1217
+ break;
1218
+ case "clustered":
1219
+ case "standard":
1220
+ stackedType = "UNSTACKED";
1221
+ break;
1222
+ }
1223
+ const overlapVal = readNumber(get(bar, "c:overlap.a$.val") ?? get(bar, "overlap.a$.val"));
1224
+ const gapWidthVal = readNumber(get(bar, "c:gapWidth.a$.val") ?? get(bar, "gapWidth.a$.val"));
1225
+ const result = {};
1226
+ if (stackedType)
1227
+ result.stackedType = stackedType;
1228
+ if (typeof overlapVal === "number")
1229
+ result.overlap = overlapVal;
1230
+ if (typeof gapWidthVal === "number")
1231
+ result.gapWidth = gapWidthVal;
1232
+ return Object.keys(result).length ? result : undefined;
1233
+ }
1234
+ /**
1235
+ * Extract chart-level `<c:scatterStyle val=…>`. Excel default is
1236
+ * "lineMarker" (straight lines + markers). Returns undefined for
1237
+ * non-scatter charts.
1238
+ */
1239
+ function extractScatterStyle(plotArea) {
1240
+ const val = get(plotArea, "c:scatterChart.c:scatterStyle.a$.val") ??
1241
+ get(plotArea, "scatterChart.scatterStyle.a$.val") ??
1242
+ get(plotArea, "c:scatterChart.c:scatterStyle.a$.t$") ??
1243
+ get(plotArea, "scatterChart.scatterStyle.a$.t$");
1244
+ return typeof val === "string" && val ? val : undefined;
1245
+ }
1246
+ function extractSeriesTitleFromSeries(ser) {
1247
+ // Prefer cached textual value if present
1248
+ const cacheEntries = get(ser, "c:tx.c:strRef.c:strCache.c:pt");
1249
+ if (cacheEntries) {
1250
+ const pts = Array.isArray(cacheEntries) ? cacheEntries : [cacheEntries];
1251
+ for (const pt of pts) {
1252
+ const value = coerceTitleText(get(pt, "c:v.t$") ||
1253
+ get(pt, "c:v") ||
1254
+ extractRichText(get(pt, "c:rich")));
1255
+ if (value) {
1256
+ return value;
1257
+ }
1258
+ }
1259
+ }
1260
+ // Fall back to literal
1261
+ const literal = coerceTitleText(get(ser, "c:tx.c:v.t$") ||
1262
+ get(ser, "c:tx.c:v") ||
1263
+ extractRichText(get(ser, "c:tx.c:rich")));
1264
+ if (literal) {
1265
+ return literal;
1266
+ }
1267
+ return undefined;
1268
+ }
1269
+ /**
1270
+ * fast-xml-parser converts numeric `<c:v>` values (e.g. a series header
1271
+ * like 2024) into numbers; normalize primitives to strings and drop
1272
+ * anything else so titles are always strings downstream.
1273
+ */
1274
+ function coerceTitleText(value) {
1275
+ if (typeof value === "string")
1276
+ return value;
1277
+ if (typeof value === "number" || typeof value === "boolean") {
1278
+ return String(value);
1279
+ }
1280
+ return undefined;
1281
+ }
1282
+ function extractRichText(node) {
1283
+ if (!node) {
1284
+ return undefined;
1285
+ }
1286
+ const paragraphs = getWithFallback(node, ["a:p", "p"]);
1287
+ const paragraphArray = Array.isArray(paragraphs)
1288
+ ? paragraphs
1289
+ : paragraphs
1290
+ ? [paragraphs]
1291
+ : [];
1292
+ const fragments = [];
1293
+ for (const paragraph of paragraphArray) {
1294
+ const runs = getWithFallback(paragraph, ["a:r", "r"]);
1295
+ const runArray = Array.isArray(runs) ? runs : runs ? [runs] : [paragraph];
1296
+ for (const run of runArray) {
1297
+ const text = getWithFallback(run, ["a:t.t$", "a:t"]) ||
1298
+ getWithFallback(run, ["t$", "t"]);
1299
+ if (typeof text === "string") {
1300
+ fragments.push(text);
1301
+ }
1302
+ }
1303
+ }
1304
+ if (fragments.length > 0) {
1305
+ return fragments.join("");
1306
+ }
1307
+ return undefined;
1308
+ }
1309
+ function normalizeHexColor(value) {
1310
+ if (!value)
1311
+ return undefined;
1312
+ const trimmed = value.toString().trim().replace(/^#/, "");
1313
+ if (!trimmed)
1314
+ return undefined;
1315
+ const sanitized = trimmed.replace(/[^0-9a-fA-F]/g, "").toUpperCase();
1316
+ if (sanitized.length === 6) {
1317
+ return `#${sanitized}`;
1318
+ }
1319
+ if (sanitized.length === 8) {
1320
+ return `#${sanitized.slice(-6)}`;
1321
+ }
1322
+ return undefined;
1323
+ }
1324
+ function extractSeriesColorInfo(ser) {
1325
+ const result = {};
1326
+ // <a:noFill/> on the series shape properties — transparent series.
1327
+ // OOXML can express this as the singleton element with no children;
1328
+ // the absence of a:solidFill / a:gradFill is also a signal but
1329
+ // brittler. fast-xml-parser emits `noFill: ""` or `noFill: {}` for
1330
+ // empty elements — both falsy by-value but truthy by-presence.
1331
+ const noFillFill = get(ser, "c:spPr.a:noFill") ?? get(ser, "spPr.a:noFill");
1332
+ if (noFillFill !== undefined) {
1333
+ result.noFill = true;
1334
+ }
1335
+ // Try the full <a:solidFill> shape first — handles both srgbClr
1336
+ // and schemeClr (with lumMod/lumOff/shade/tint/alpha modifiers).
1337
+ const solidFillNode = get(ser, "c:spPr.a:solidFill") ?? get(ser, "spPr.a:solidFill");
1338
+ const resolvedFill = resolveDrawingColor(solidFillNode);
1339
+ // Extract main color - check both fill color (for bar/column) and line color (for line/area charts)
1340
+ const fillColorValue = getWithFallback(ser, [
1341
+ "c:spPr.a:solidFill.a:srgbClr.a$.val",
1342
+ "c:spPr.a:solidFill.a:srgbClr.a$.t$",
1343
+ "c:spPr.a:solidFill.a:srgbClr.a$.rgb",
1344
+ "c:spPr.solidFill.srgbClr.a$.val",
1345
+ "spPr.a:solidFill.a:srgbClr.a$.val",
1346
+ "spPr.solidFill.srgbClr.a$.val",
1347
+ ]);
1348
+ // Line color for line/scatter/area charts
1349
+ const lineColorValue = getWithFallback(ser, [
1350
+ "c:spPr.a:ln.a:solidFill.a:srgbClr.a$.val",
1351
+ "c:spPr.a:ln.a:solidFill.a:srgbClr.a$.t$",
1352
+ "c:spPr.a:ln.a:solidFill.a:srgbClr.a$.rgb",
1353
+ "c:spPr.a:ln.solidFill.srgbClr.a$.val",
1354
+ "spPr.a:ln.a:solidFill.a:srgbClr.a$.val",
1355
+ "spPr.ln.solidFill.srgbClr.a$.val",
1356
+ ]);
1357
+ // Use fill color if available, otherwise use line color.
1358
+ // resolvedFill handles schemeClr; the legacy normalize call handles
1359
+ // the explicit srgbClr path (kept as a fallback). When noFill is set
1360
+ // we leave `color` undefined — the renderer treats the series as
1361
+ // transparent.
1362
+ if (!result.noFill) {
1363
+ result.color =
1364
+ resolvedFill ??
1365
+ normalizeHexColor(fillColorValue) ??
1366
+ normalizeHexColor(lineColorValue);
1367
+ }
1368
+ // Gradient fill — DrawingML `<a:gradFill>` inside `<c:spPr>`. Stops
1369
+ // come in EMU per-mille (0..100000); positions get normalized back
1370
+ // to 0..1 for our shape. Linear angle is in 60000ths of a degree;
1371
+ // path gradients carry insets via `<a:path><a:fillToRect/>`.
1372
+ const gradFill = getWithFallback(ser, [
1373
+ "c:spPr.a:gradFill",
1374
+ "spPr.a:gradFill",
1375
+ "c:spPr.gradFill",
1376
+ ]);
1377
+ if (gradFill) {
1378
+ const rawStops = getWithFallback(gradFill, [
1379
+ "a:gsLst.a:gs",
1380
+ "gsLst.gs",
1381
+ ]);
1382
+ const stopList = rawStops
1383
+ ? Array.isArray(rawStops)
1384
+ ? rawStops
1385
+ : [rawStops]
1386
+ : [];
1387
+ const stops = [];
1388
+ for (const stop of stopList) {
1389
+ const posRaw = stop?.a$?.pos;
1390
+ const position = posRaw != null
1391
+ ? Math.max(0, Math.min(1, parseFloat(posRaw) / 100000))
1392
+ : NaN;
1393
+ if (!Number.isFinite(position))
1394
+ continue;
1395
+ const hex = getWithFallback(stop, [
1396
+ "a:srgbClr.a$.val",
1397
+ "srgbClr.a$.val",
1398
+ ]);
1399
+ const color = normalizeHexColor(hex);
1400
+ if (!color)
1401
+ continue;
1402
+ stops.push({ position, color });
1403
+ }
1404
+ if (stops.length > 0) {
1405
+ stops.sort((a, b) => a.position - b.position);
1406
+ const lin = getWithFallback(gradFill, ["a:lin.a$", "lin.a$"]);
1407
+ const path = getWithFallback(gradFill, ["a:path", "path"]);
1408
+ if (path) {
1409
+ const insets = getWithFallback(path, [
1410
+ "a:fillToRect.a$",
1411
+ "fillToRect.a$",
1412
+ ]);
1413
+ const emuToFraction = (v) => {
1414
+ if (v == null)
1415
+ return undefined;
1416
+ const n = parseFloat(v);
1417
+ return Number.isFinite(n) ? n / 100000 : undefined;
1418
+ };
1419
+ result.gradient = {
1420
+ type: "path",
1421
+ left: emuToFraction(insets?.l),
1422
+ right: emuToFraction(insets?.r),
1423
+ top: emuToFraction(insets?.t),
1424
+ bottom: emuToFraction(insets?.b),
1425
+ stops,
1426
+ };
1427
+ }
1428
+ else {
1429
+ const ang = lin?.ang != null ? parseFloat(lin.ang) : 0;
1430
+ result.gradient = {
1431
+ type: "linear",
1432
+ degree: Number.isFinite(ang) ? ang / 60000 : 0,
1433
+ stops,
1434
+ };
1435
+ }
1436
+ }
1437
+ }
1438
+ // Extract invertIfNegative flag
1439
+ const invertIfNegativeVal = getWithFallback(ser, [
1440
+ "c:invertIfNegative.a$.val",
1441
+ "c:invertIfNegative.t$",
1442
+ "c:invertIfNegative",
1443
+ "invertIfNegative.a$.val",
1444
+ "invertIfNegative",
1445
+ ]);
1446
+ // Value can be "1", "true", true, or 1
1447
+ if (invertIfNegativeVal !== undefined) {
1448
+ result.invertIfNegative =
1449
+ invertIfNegativeVal === "1" ||
1450
+ invertIfNegativeVal === true ||
1451
+ invertIfNegativeVal === 1 ||
1452
+ invertIfNegativeVal === "true";
1453
+ }
1454
+ // Extract negative fill color from extLst (Excel 2010+ format)
1455
+ // The negative color is stored in c:extLst.c:ext with specific URI
1456
+ const extLst = getWithFallback(ser, [
1457
+ "c:extLst.c:ext",
1458
+ "c:extLst",
1459
+ "extLst.ext",
1460
+ "extLst",
1461
+ ]);
1462
+ if (extLst) {
1463
+ const extArray = Array.isArray(extLst) ? extLst : [extLst];
1464
+ for (const ext of extArray) {
1465
+ // Look for invertSolidFillFmt extension (negative fill format)
1466
+ const negFillColor = getWithFallback(ext, [
1467
+ "c14:invertSolidFillFmt.c14:spPr.a:solidFill.a:srgbClr.a$.val",
1468
+ "c14:invertSolidFillFmt.c14:spPr.a:solidFill.a:srgbClr.a$.rgb",
1469
+ "invertSolidFillFmt.spPr.solidFill.srgbClr.a$.val",
1470
+ // Also check for c15 namespace (Excel 2013+)
1471
+ "c15:invertSolidFillFmt.c15:spPr.a:solidFill.a:srgbClr.a$.val",
1472
+ ]);
1473
+ if (negFillColor) {
1474
+ result.negativeColor = normalizeHexColor(negFillColor);
1475
+ // If we have a negative color, invertIfNegative should be true
1476
+ if (result.negativeColor) {
1477
+ result.invertIfNegative = true;
1478
+ }
1479
+ break;
1480
+ }
1481
+ }
1482
+ }
1483
+ return result;
1484
+ }
1485
+ export function extractChartDomains(plotArea, chartType, sheetId, resolveSheetId) {
1486
+ const domains = [];
1487
+ // Search all chart type containers for domains. For XY charts the
1488
+ // "domain" is the X values (c:xVal); for everything else it's the
1489
+ // category axis (c:cat). Per-container loop covers combo charts.
1490
+ for (const container of CHART_TYPE_CONTAINERS) {
1491
+ const chartElement = getWithFallback(plotArea, container.paths);
1492
+ if (!chartElement)
1493
+ continue;
1494
+ const chartElements = Array.isArray(chartElement)
1495
+ ? chartElement
1496
+ : [chartElement];
1497
+ for (const singleChartElement of chartElements) {
1498
+ const seriesElements = getWithFallback(singleChartElement, [
1499
+ "c:ser",
1500
+ "ser",
1501
+ ]);
1502
+ if (!seriesElements)
1503
+ continue;
1504
+ const seriesArray = Array.isArray(seriesElements)
1505
+ ? seriesElements
1506
+ : [seriesElements];
1507
+ for (const ser of seriesArray) {
1508
+ const domainRef = container.isXY
1509
+ ? getWithFallback(ser, X_VAL_REF_PATHS)
1510
+ : getWithFallback(ser, CAT_REF_PATHS);
1511
+ if (domainRef) {
1512
+ const sources = resolveSourcesFromRef(domainRef, sheetId, resolveSheetId);
1513
+ if (sources.length) {
1514
+ domains.push({ sources });
1515
+ // XY charts may carry per-series domains that differ — keep
1516
+ // the first one we encounter for the dataRange anchor, but
1517
+ // continue collecting so the renderer has a per-series X
1518
+ // when it needs one. For non-XY, first-found is enough.
1519
+ if (!container.isXY)
1520
+ return domains;
1521
+ }
1522
+ }
1523
+ else if (!container.isXY) {
1524
+ // No cell ref — check for an inline `<c:strLit>`/`<c:numLit>`
1525
+ // category list (e.g. `["Estimated", "Actual"]` embedded
1526
+ // directly in the chart XML). Same for the series val side,
1527
+ // but domains is by far the most common case.
1528
+ const catNode = get(ser, "c:cat") ?? get(ser, "cat");
1529
+ const inlineValues = extractInlineValues(catNode);
1530
+ if (inlineValues && inlineValues.length) {
1531
+ domains.push({ sources: [], inlineValues });
1532
+ return domains;
1533
+ }
1534
+ }
1535
+ }
1536
+ if (domains.length && container.isXY)
1537
+ return domains;
1538
+ }
1539
+ }
1540
+ // Fallback for chart types not in CHART_TYPE_CONTAINERS
1541
+ // (pie/doughnut/radar/stock/ofPie/scatter/bubble outside combos).
1542
+ const seriesElements = getSeriesElements(plotArea, chartType);
1543
+ if (seriesElements) {
1544
+ const seriesArray = Array.isArray(seriesElements)
1545
+ ? seriesElements
1546
+ : [seriesElements];
1547
+ for (const ser of seriesArray) {
1548
+ const domainRef = isXYChart(chartType)
1549
+ ? getWithFallback(ser, X_VAL_REF_PATHS)
1550
+ : getWithFallback(ser, CAT_REF_PATHS);
1551
+ if (domainRef) {
1552
+ const sources = resolveSourcesFromRef(domainRef, sheetId, resolveSheetId);
1553
+ if (sources.length) {
1554
+ domains.push({ sources });
1555
+ if (!isXYChart(chartType))
1556
+ break;
1557
+ }
1558
+ }
1559
+ else if (!isXYChart(chartType)) {
1560
+ const catNode = get(ser, "c:cat") ?? get(ser, "cat");
1561
+ const inlineValues = extractInlineValues(catNode);
1562
+ if (inlineValues && inlineValues.length) {
1563
+ domains.push({ sources: [], inlineValues });
1564
+ break;
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+ return domains;
1570
+ }
1571
+ /**
1572
+ * Bubble charts have a third dimension — bubble size — referenced
1573
+ * via `<c:bubbleSize><c:numRef><c:f>` on each series. Returns one
1574
+ * `ChartData` entry per series (matches the shape used by `domains` /
1575
+ * `series`).
1576
+ */
1577
+ export function extractChartBubbleSizes(plotArea, sheetId, resolveSheetId) {
1578
+ const sizes = [];
1579
+ const seriesElements = getSeriesElements(plotArea, "bubble");
1580
+ if (!seriesElements)
1581
+ return sizes;
1582
+ const seriesArray = Array.isArray(seriesElements)
1583
+ ? seriesElements
1584
+ : [seriesElements];
1585
+ for (const ser of seriesArray) {
1586
+ const sizeRef = getWithFallback(ser, BUBBLE_SIZE_REF_PATHS);
1587
+ if (!sizeRef)
1588
+ continue;
1589
+ const sources = resolveSourcesFromRef(sizeRef, sheetId, resolveSheetId);
1590
+ if (sources.length)
1591
+ sizes.push({ sources });
1592
+ }
1593
+ return sizes;
1594
+ }
1595
+ export function extractChartPosition(anchor, sheetId, sheetDimensions) {
1596
+ try {
1597
+ // Handle oneCellAnchor
1598
+ const oneCellFrom = getWithFallback(anchor, ["xdr:from", "from"]) ?? undefined;
1599
+ const hasTwoCellTarget = getWithFallback(anchor, ["xdr:to", "to"]) !== undefined;
1600
+ if (oneCellFrom && !hasTwoCellTarget) {
1601
+ const extNode = getWithFallback(anchor, ["xdr:ext", "ext"]);
1602
+ const extAttributes = (extNode && (extNode.a$ ?? extNode)) || undefined;
1603
+ const col = toInt(getWithFallback(oneCellFrom, ["xdr:col", "col"]));
1604
+ const row = toInt(getWithFallback(oneCellFrom, ["xdr:row", "row"]));
1605
+ const colOff = toInt(getWithFallback(oneCellFrom, ["xdr:colOff", "colOff"]));
1606
+ const rowOff = toInt(getWithFallback(oneCellFrom, ["xdr:rowOff", "rowOff"]));
1607
+ // Convert EMU to pixels (1 EMU = 1/9525 pixels)
1608
+ const width = extAttributes && extAttributes.cx
1609
+ ? Math.round(parseInt(String(extAttributes.cx), 10) / 9525)
1610
+ : 400;
1611
+ const height = extAttributes && extAttributes.cy
1612
+ ? Math.round(parseInt(String(extAttributes.cy), 10) / 9525)
1613
+ : 300;
1614
+ return {
1615
+ sheetId,
1616
+ overlayPosition: {
1617
+ anchorCell: {
1618
+ rowIndex: row + 1,
1619
+ columnIndex: col + 1,
1620
+ },
1621
+ offsetXPixels: Math.round(colOff / 9525),
1622
+ offsetYPixels: Math.round(rowOff / 9525),
1623
+ widthPixels: width,
1624
+ heightPixels: height,
1625
+ },
1626
+ };
1627
+ }
1628
+ // Handle twoCellAnchor
1629
+ const fromCell = getWithFallback(anchor, ["xdr:from", "from"]) ?? undefined;
1630
+ const toCell = getWithFallback(anchor, ["xdr:to", "to"]) ?? undefined;
1631
+ if (fromCell && toCell) {
1632
+ const col = toInt(getWithFallback(fromCell, ["xdr:col", "col"]));
1633
+ const row = toInt(getWithFallback(fromCell, ["xdr:row", "row"]));
1634
+ const colOff = toInt(getWithFallback(fromCell, ["xdr:colOff", "colOff"]));
1635
+ const rowOff = toInt(getWithFallback(fromCell, ["xdr:rowOff", "rowOff"]));
1636
+ const toCol = toInt(getWithFallback(toCell, ["xdr:col", "col"]));
1637
+ const toRow = toInt(getWithFallback(toCell, ["xdr:row", "row"]));
1638
+ const toColOff = toInt(getWithFallback(toCell, ["xdr:colOff", "colOff"]));
1639
+ const toRowOff = toInt(getWithFallback(toCell, ["xdr:rowOff", "rowOff"]));
1640
+ // Default cell dimensions in pixels
1641
+ const DEFAULT_COL_WIDTH_PX = sheetDimensions?.defaultColumnWidth ?? 64;
1642
+ const DEFAULT_ROW_HEIGHT_PX = sheetDimensions?.defaultRowHeight ?? 20;
1643
+ // Helper to get column width in pixels
1644
+ const getColWidth = (colIndex) => {
1645
+ return (sheetDimensions?.columnWidths?.get(colIndex + 1) ??
1646
+ DEFAULT_COL_WIDTH_PX);
1647
+ };
1648
+ // Helper to get row height in pixels
1649
+ const getRowHeight = (rowIndex) => {
1650
+ return (sheetDimensions?.rowHeights?.get(rowIndex + 1) ??
1651
+ DEFAULT_ROW_HEIGHT_PX);
1652
+ };
1653
+ // Calculate width by summing actual column widths
1654
+ let widthPx = 0;
1655
+ for (let c = col; c < toCol; c++) {
1656
+ widthPx += getColWidth(c);
1657
+ }
1658
+ // Subtract offset into first cell, add offset into last cell
1659
+ // Offsets are in EMUs, convert to pixels
1660
+ widthPx -= Math.round(colOff / 9525);
1661
+ widthPx += Math.round(toColOff / 9525);
1662
+ // Calculate height by summing actual row heights
1663
+ let heightPx = 0;
1664
+ for (let r = row; r < toRow; r++) {
1665
+ heightPx += getRowHeight(r);
1666
+ }
1667
+ // Subtract offset into first cell, add offset into last cell
1668
+ heightPx -= Math.round(rowOff / 9525);
1669
+ heightPx += Math.round(toRowOff / 9525);
1670
+ const width = Math.max(widthPx, 100);
1671
+ const height = Math.max(heightPx, 100);
1672
+ return {
1673
+ sheetId,
1674
+ overlayPosition: {
1675
+ anchorCell: {
1676
+ rowIndex: row + 1,
1677
+ columnIndex: col + 1,
1678
+ },
1679
+ offsetXPixels: Math.round(colOff / 9525),
1680
+ offsetYPixels: Math.round(rowOff / 9525),
1681
+ widthPixels: width,
1682
+ heightPixels: height,
1683
+ },
1684
+ };
1685
+ }
1686
+ return null;
1687
+ }
1688
+ catch (err) {
1689
+ console.log("Error extracting chart position:", err);
1690
+ return null;
1691
+ }
1692
+ }
1693
+ // -------------------------- Phase 5 helpers --------------------------
1694
+ // Read OOXML legend (<c:legend>), data labels (<c:dLbls>), and axis
1695
+ // formatting (<c:valAx>, <c:catAx>) elements into the engine's ChartSpec
1696
+ // shape. All helpers return `undefined` when nothing meaningful is set,
1697
+ // so callers can spread the result without polluting the spec.
1698
+ const readBoolean = (val) => {
1699
+ if (val === undefined || val === null)
1700
+ return undefined;
1701
+ return val === "1" || val === "true" || val === true;
1702
+ };
1703
+ const readNumber = (val) => {
1704
+ if (val === undefined || val === null)
1705
+ return undefined;
1706
+ const n = typeof val === "number" ? val : parseFloat(String(val));
1707
+ return Number.isFinite(n) ? n : undefined;
1708
+ };
1709
+ const mapLegendPosition = (val) => {
1710
+ switch (val) {
1711
+ case "t":
1712
+ return "top";
1713
+ case "b":
1714
+ return "bottom";
1715
+ case "l":
1716
+ return "left";
1717
+ case "r":
1718
+ return "right";
1719
+ case "tr":
1720
+ return "topRight";
1721
+ default:
1722
+ return undefined;
1723
+ }
1724
+ };
1725
+ function extractLegendOptions(chart) {
1726
+ // OOXML omits <c:legend> when the chart hides the legend, but
1727
+ // includes it (possibly empty) when the legend is shown. Some
1728
+ // writers add a child <c:delete val="1"/> instead — handle that too.
1729
+ const legend = getWithFallback(chart, ["c:legend", "legend"]);
1730
+ if (legend === undefined) {
1731
+ // Excel's default is "show". We only emit a `legend` field when the
1732
+ // source explicitly suppressed it, otherwise renderers fall back.
1733
+ return undefined;
1734
+ }
1735
+ const deleted = readBoolean(get(legend, "c:delete.a$.val") ?? get(legend, "delete.a$.val"));
1736
+ if (deleted) {
1737
+ return { position: "none" };
1738
+ }
1739
+ const posVal = get(legend, "c:legendPos.a$.val") ?? get(legend, "legendPos.a$.val");
1740
+ const position = mapLegendPosition(posVal);
1741
+ const textFormat = extractTextProperties(get(legend, "c:txPr") ?? get(legend, "txPr"));
1742
+ if (!position && !textFormat)
1743
+ return undefined;
1744
+ return {
1745
+ ...(position ? { position } : {}),
1746
+ ...(textFormat ? { textFormat } : {}),
1747
+ };
1748
+ }
1749
+ const mapDataLabelPosition = (val) => {
1750
+ switch (val) {
1751
+ case "ctr":
1752
+ return "center";
1753
+ case "inEnd":
1754
+ return "insideEnd";
1755
+ case "inBase":
1756
+ return "inside";
1757
+ case "outEnd":
1758
+ return "outsideEnd";
1759
+ case "bestFit":
1760
+ return "bestFit";
1761
+ default:
1762
+ return undefined;
1763
+ }
1764
+ };
1765
+ function extractDataLabelsOptions(plotArea, chartType) {
1766
+ // Look for <c:dLbls> inside the chartType-specific container. We pick
1767
+ // the first one we find — chart-level labels apply to all series.
1768
+ const containerKeys = {
1769
+ bar: ["c:barChart", "barChart"],
1770
+ column: ["c:barChart", "barChart"],
1771
+ line: ["c:lineChart", "lineChart"],
1772
+ pie: ["c:pieChart", "pieChart", "c:ofPieChart", "ofPieChart"],
1773
+ doughnut: ["c:doughnutChart", "doughnutChart"],
1774
+ area: ["c:areaChart", "areaChart"],
1775
+ };
1776
+ const keys = containerKeys[chartType] ?? [];
1777
+ let container;
1778
+ for (const key of keys) {
1779
+ container = container ?? get(plotArea, key);
1780
+ }
1781
+ const dLbls = get(container, "c:dLbls") ??
1782
+ get(container, "dLbls") ??
1783
+ get(plotArea, "c:dLbls") ??
1784
+ get(plotArea, "dLbls");
1785
+ if (!dLbls)
1786
+ return undefined;
1787
+ const result = {};
1788
+ const pos = get(dLbls, "c:dLblPos.a$.val") ?? get(dLbls, "dLblPos.a$.val");
1789
+ const position = mapDataLabelPosition(pos);
1790
+ if (position)
1791
+ result.position = position;
1792
+ const showVal = readBoolean(get(dLbls, "c:showVal.a$.val") ?? get(dLbls, "showVal.a$.val"));
1793
+ if (showVal) {
1794
+ result.show = true;
1795
+ result.showValue = true;
1796
+ }
1797
+ const showCat = readBoolean(get(dLbls, "c:showCatName.a$.val") ?? get(dLbls, "showCatName.a$.val"));
1798
+ if (showCat) {
1799
+ result.show = true;
1800
+ result.showCategoryName = true;
1801
+ }
1802
+ const showPct = readBoolean(get(dLbls, "c:showPercent.a$.val") ?? get(dLbls, "showPercent.a$.val"));
1803
+ if (showPct) {
1804
+ result.show = true;
1805
+ result.showPercent = true;
1806
+ }
1807
+ const showSer = readBoolean(get(dLbls, "c:showSerName.a$.val") ?? get(dLbls, "showSerName.a$.val"));
1808
+ if (showSer) {
1809
+ result.show = true;
1810
+ result.showSeriesName = true;
1811
+ }
1812
+ // If only `position` was set with no `show*` flags, treat as "show".
1813
+ if (position && result.show === undefined)
1814
+ result.show = true;
1815
+ // <c:txPr> on the dLbls block — affects every label in the chart.
1816
+ const textFormat = extractTextProperties(get(dLbls, "c:txPr") ?? get(dLbls, "txPr"));
1817
+ if (textFormat)
1818
+ result.textFormat = textFormat;
1819
+ // <c:numFmt formatCode="…"/> — Excel uses this to render labels as
1820
+ // currency / percent / etc. (`"$"#,##0`, `0.00%`). Without parsing
1821
+ // this, the renderer falls back to raw numbers (e.g. `1831` instead
1822
+ // of `$1,831`).
1823
+ const numFmt = get(dLbls, "c:numFmt.a$.formatCode") ??
1824
+ get(dLbls, "numFmt.a$.formatCode") ??
1825
+ get(dLbls, "c:numFmt.a$.t$") ??
1826
+ get(dLbls, "numFmt.a$.t$");
1827
+ if (typeof numFmt === "string" && numFmt) {
1828
+ result.numberFormatPattern = numFmt;
1829
+ }
1830
+ return Object.keys(result).length ? result : undefined;
1831
+ }
1832
+ function readAxis(axis) {
1833
+ if (!axis)
1834
+ return undefined;
1835
+ const result = {};
1836
+ // <c:scaling><c:logBase val="…"/> + <c:orientation val="minMax|maxMin"/>
1837
+ const logBase = readNumber(get(axis, "c:scaling.c:logBase.a$.val") ??
1838
+ get(axis, "scaling.logBase.a$.val"));
1839
+ if (typeof logBase === "number" && logBase > 0)
1840
+ result.logarithmic = true;
1841
+ const orientation = get(axis, "c:scaling.c:orientation.a$.val") ??
1842
+ get(axis, "scaling.orientation.a$.val");
1843
+ if (orientation === "maxMin")
1844
+ result.reverse = true;
1845
+ const min = readNumber(get(axis, "c:scaling.c:min.a$.val") ?? get(axis, "scaling.min.a$.val"));
1846
+ if (typeof min === "number")
1847
+ result.min = min;
1848
+ const max = readNumber(get(axis, "c:scaling.c:max.a$.val") ?? get(axis, "scaling.max.a$.val"));
1849
+ if (typeof max === "number")
1850
+ result.max = max;
1851
+ // <c:delete val="1"/> hides the axis entirely.
1852
+ const deleted = readBoolean(get(axis, "c:delete.a$.val") ?? get(axis, "delete.a$.val"));
1853
+ if (deleted)
1854
+ result.visible = false;
1855
+ // <c:majorGridlines/> presence == show. Empty element is sufficient.
1856
+ const majorGridlines = get(axis, "c:majorGridlines") ?? get(axis, "majorGridlines");
1857
+ if (majorGridlines !== undefined)
1858
+ result.showMajorGridlines = true;
1859
+ // <c:txPr> — axis tick-label font/color. Without this we'd inherit
1860
+ // the renderer's theme default (near-black) and lose the authored
1861
+ // color (e.g. BEP.xlsx X axis = #14325A, Y axis = #F0621C).
1862
+ const textFormat = extractTextProperties(get(axis, "c:txPr") ?? get(axis, "txPr"));
1863
+ if (textFormat)
1864
+ result.textFormat = textFormat;
1865
+ return Object.keys(result).length ? result : undefined;
1866
+ }
1867
+ export function extractAxisOptions(plotArea) {
1868
+ const valAx = get(plotArea, "c:valAx") ?? get(plotArea, "valAx");
1869
+ const catAx = get(plotArea, "c:catAx") ?? get(plotArea, "catAx");
1870
+ // Some chart types use <c:dateAx>; treat as category for our purposes.
1871
+ const dateAx = get(plotArea, "c:dateAx") ?? get(plotArea, "dateAx");
1872
+ const valAxes = Array.isArray(valAx) ? valAx : valAx ? [valAx] : [];
1873
+ return {
1874
+ valueAxisOptions: readAxis(valAxes[0]),
1875
+ secondaryValueAxisOptions: valAxes.length > 1 ? readAxis(valAxes[1]) : undefined,
1876
+ categoryAxisOptions: readAxis((Array.isArray(catAx) ? catAx[0] : catAx) ??
1877
+ (Array.isArray(dateAx) ? dateAx[0] : dateAx)),
1878
+ };
1879
+ }
1880
+ /**
1881
+ * Pie-of-pie / bar-of-pie — extract the of-pie layout fields from
1882
+ * `<c:ofPieChart>`. Returns undefined for plain pies. `splitType`
1883
+ * maps the OOXML tokens (`pos`/`val`/`percent`) to the spec vocabulary;
1884
+ * `auto` and `cust` (custom point assignment, carried in `<c:custSplit>`
1885
+ * which the spec cannot represent) are omitted so the renderer applies
1886
+ * Excel's default rule (position, last 3 values).
1887
+ */
1888
+ export function extractOfPieOptions(plotArea) {
1889
+ const ofPie = getWithFallback(plotArea, ["c:ofPieChart", "ofPieChart"]);
1890
+ if (!ofPie)
1891
+ return undefined;
1892
+ const typeRaw = get(ofPie, "c:ofPieType.a$.val") ?? get(ofPie, "ofPieType.a$.val");
1893
+ const ofPieType = typeRaw === "bar" ? "bar" : "pie";
1894
+ const splitRaw = get(ofPie, "c:splitType.a$.val") ?? get(ofPie, "splitType.a$.val");
1895
+ const splitType = splitRaw === "pos"
1896
+ ? "position"
1897
+ : splitRaw === "val"
1898
+ ? "value"
1899
+ : splitRaw === "percent"
1900
+ ? "percent"
1901
+ : undefined;
1902
+ const splitPos = readNumber(get(ofPie, "c:splitPos.a$.val") ?? get(ofPie, "splitPos.a$.val"));
1903
+ const secondPieSize = readNumber(get(ofPie, "c:secondPieSize.a$.val") ??
1904
+ get(ofPie, "secondPieSize.a$.val"));
1905
+ return {
1906
+ ofPieType,
1907
+ ...(splitType ? { splitType } : {}),
1908
+ ...(typeof splitPos === "number" ? { splitPos } : {}),
1909
+ ...(typeof secondPieSize === "number" ? { secondPieSize } : {}),
1910
+ };
1911
+ }
1912
+ function extractDoughnutHoleSize(plotArea) {
1913
+ // <c:doughnutChart><c:holeSize val="50"/> — value is the inner radius
1914
+ // as a percentage of the chart radius (0-90). We normalize to a 0-1
1915
+ // fraction so renderers can use it without knowing the spec.
1916
+ const holeSize = readNumber(get(plotArea, "c:doughnutChart.c:holeSize.a$.val") ??
1917
+ get(plotArea, "doughnutChart.holeSize.a$.val"));
1918
+ if (typeof holeSize !== "number")
1919
+ return undefined;
1920
+ return Math.min(0.9, Math.max(0, holeSize / 100));
1921
+ }
1922
+ // -------------------------- Phase 5.2 helpers --------------------------
1923
+ const mapTrendlineType = (val) => {
1924
+ switch (val) {
1925
+ case "exp":
1926
+ return "exp";
1927
+ case "log":
1928
+ return "log";
1929
+ case "poly":
1930
+ return "poly";
1931
+ case "power":
1932
+ return "power";
1933
+ case "movingAvg":
1934
+ return "movingAvg";
1935
+ case "linear":
1936
+ default:
1937
+ return "linear";
1938
+ }
1939
+ };
1940
+ function extractTrendlines(ser) {
1941
+ // <c:trendline> can appear multiple times under a single series; the
1942
+ // parser may surface either an array or a single object.
1943
+ const raw = get(ser, "c:trendline") ?? get(ser, "trendline");
1944
+ if (!raw)
1945
+ return undefined;
1946
+ const list = Array.isArray(raw) ? raw : [raw];
1947
+ const results = [];
1948
+ for (const t of list) {
1949
+ const type = mapTrendlineType(get(t, "c:trendlineType.a$.val") ?? get(t, "trendlineType.a$.val"));
1950
+ const order = readNumber(get(t, "c:order.a$.val") ?? get(t, "order.a$.val"));
1951
+ const period = readNumber(get(t, "c:period.a$.val") ?? get(t, "period.a$.val"));
1952
+ const name = get(t, "c:name.t$") ?? get(t, "name.t$") ?? get(t, "c:name") ?? get(t, "name");
1953
+ const showEquation = readBoolean(get(t, "c:dispEq.a$.val") ?? get(t, "dispEq.a$.val"));
1954
+ const showRSquared = readBoolean(get(t, "c:dispRSqr.a$.val") ?? get(t, "dispRSqr.a$.val"));
1955
+ const forecastForward = readNumber(get(t, "c:forward.a$.val") ?? get(t, "forward.a$.val"));
1956
+ const forecastBackward = readNumber(get(t, "c:backward.a$.val") ?? get(t, "backward.a$.val"));
1957
+ const entry = { type };
1958
+ if (typeof order === "number")
1959
+ entry.order = order;
1960
+ if (typeof period === "number")
1961
+ entry.period = period;
1962
+ if (typeof name === "string" && name)
1963
+ entry.name = name;
1964
+ if (showEquation)
1965
+ entry.showEquation = true;
1966
+ if (showRSquared)
1967
+ entry.showRSquared = true;
1968
+ if (typeof forecastForward === "number")
1969
+ entry.forecastForward = forecastForward;
1970
+ if (typeof forecastBackward === "number")
1971
+ entry.forecastBackward = forecastBackward;
1972
+ results.push(entry);
1973
+ }
1974
+ return results;
1975
+ }
1976
+ function extractErrorBars(ser, hostSheetId, resolveSheetId) {
1977
+ const raw = get(ser, "c:errBars") ?? get(ser, "errBars");
1978
+ if (!raw)
1979
+ return undefined;
1980
+ const direction = get(raw, "c:errDir.a$.val") ?? get(raw, "errDir.a$.val");
1981
+ const barType = get(raw, "c:errBarType.a$.val") ?? get(raw, "errBarType.a$.val");
1982
+ const valTypeVal = get(raw, "c:errValType.a$.val") ?? get(raw, "errValType.a$.val");
1983
+ const value = readNumber(get(raw, "c:val.a$.val") ?? get(raw, "val.a$.val"));
1984
+ const noEndCap = readBoolean(get(raw, "c:noEndCap.a$.val") ?? get(raw, "noEndCap.a$.val"));
1985
+ const result = {};
1986
+ if (direction === "x" || direction === "y")
1987
+ result.direction = direction;
1988
+ if (barType === "both" || barType === "plus" || barType === "minus") {
1989
+ result.barType = barType;
1990
+ }
1991
+ if (valTypeVal === "fixedVal" ||
1992
+ valTypeVal === "percentage" ||
1993
+ valTypeVal === "stdDev" ||
1994
+ valTypeVal === "stdErr" ||
1995
+ valTypeVal === "cust") {
1996
+ result.valueType = valTypeVal === "cust" ? "custom" : valTypeVal;
1997
+ }
1998
+ if (typeof value === "number")
1999
+ result.value = value;
2000
+ if (noEndCap)
2001
+ result.noEndCap = true;
2002
+ // Custom error bars carry per-point magnitudes in worksheet ranges —
2003
+ // `<c:plus>/<c:minus>` each hold a `<c:numRef><c:f>` reference.
2004
+ if (result.valueType === "custom") {
2005
+ const plusRef = getWithFallback(raw, [
2006
+ "c:plus.c:numRef.c:f",
2007
+ "c:plus.c:numRef.c:f.t$",
2008
+ "plus.numRef.f",
2009
+ "plus.numRef.f.t$",
2010
+ ]);
2011
+ const minusRef = getWithFallback(raw, [
2012
+ "c:minus.c:numRef.c:f",
2013
+ "c:minus.c:numRef.c:f.t$",
2014
+ "minus.numRef.f",
2015
+ "minus.numRef.f.t$",
2016
+ ]);
2017
+ if (plusRef) {
2018
+ const plusSources = resolveSourcesFromRef(plusRef, hostSheetId, resolveSheetId);
2019
+ if (plusSources.length)
2020
+ result.plusRange = plusSources[0];
2021
+ }
2022
+ if (minusRef) {
2023
+ const minusSources = resolveSourcesFromRef(minusRef, hostSheetId, resolveSheetId);
2024
+ if (minusSources.length)
2025
+ result.minusRange = minusSources[0];
2026
+ }
2027
+ }
2028
+ return Object.keys(result).length ? result : undefined;
2029
+ }
2030
+ function extractRadarStyle(plotArea) {
2031
+ const val = get(plotArea, "c:radarChart.c:radarStyle.a$.val") ??
2032
+ get(plotArea, "radarChart.radarStyle.a$.val");
2033
+ if (val === "standard" || val === "marker" || val === "filled")
2034
+ return val;
2035
+ return undefined;
2036
+ }