@cj-tech-master/excelts 9.4.2 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (618) hide show
  1. package/dist/browser/index.browser.d.ts +8 -5
  2. package/dist/browser/index.browser.js +19 -1
  3. package/dist/browser/index.d.ts +4 -2
  4. package/dist/browser/index.js +9 -1
  5. package/dist/browser/modules/excel/chart/cache-populator.d.ts +49 -0
  6. package/dist/browser/modules/excel/chart/cache-populator.js +1171 -0
  7. package/dist/browser/modules/excel/chart/chart-api.d.ts +92 -0
  8. package/dist/browser/modules/excel/chart/chart-api.js +364 -0
  9. package/dist/browser/modules/excel/chart/chart-builder.d.ts +48 -0
  10. package/dist/browser/modules/excel/chart/chart-builder.js +2432 -0
  11. package/dist/browser/modules/excel/chart/chart-ex-builder.d.ts +36 -0
  12. package/dist/browser/modules/excel/chart/chart-ex-builder.js +903 -0
  13. package/dist/browser/modules/excel/chart/chart-ex-parser.d.ts +8 -0
  14. package/dist/browser/modules/excel/chart/chart-ex-parser.js +1205 -0
  15. package/dist/browser/modules/excel/chart/chart-ex-renderer.d.ts +187 -0
  16. package/dist/browser/modules/excel/chart/chart-ex-renderer.js +5352 -0
  17. package/dist/browser/modules/excel/chart/chart-ex-types.d.ts +531 -0
  18. package/dist/browser/modules/excel/chart/chart-ex-types.js +11 -0
  19. package/dist/browser/modules/excel/chart/chart-images.d.ts +78 -0
  20. package/dist/browser/modules/excel/chart/chart-images.js +363 -0
  21. package/dist/browser/modules/excel/chart/chart-presets.d.ts +392 -0
  22. package/dist/browser/modules/excel/chart/chart-presets.js +179 -0
  23. package/dist/browser/modules/excel/chart/chart-renderer.d.ts +550 -0
  24. package/dist/browser/modules/excel/chart/chart-renderer.js +6440 -0
  25. package/dist/browser/modules/excel/chart/chart-sidecar.d.ts +21 -0
  26. package/dist/browser/modules/excel/chart/chart-sidecar.js +427 -0
  27. package/dist/browser/modules/excel/chart/chart-utils.d.ts +306 -0
  28. package/dist/browser/modules/excel/chart/chart-utils.js +821 -0
  29. package/dist/browser/modules/excel/chart/chart.d.ts +504 -0
  30. package/dist/browser/modules/excel/chart/chart.js +1320 -0
  31. package/dist/browser/modules/excel/chart/glyph-rasterizer.d.ts +62 -0
  32. package/dist/browser/modules/excel/chart/glyph-rasterizer.js +658 -0
  33. package/dist/browser/modules/excel/chart/index.d.ts +54 -0
  34. package/dist/browser/modules/excel/chart/index.js +46 -0
  35. package/dist/browser/modules/excel/chart/install.d.ts +44 -0
  36. package/dist/browser/modules/excel/chart/install.js +91 -0
  37. package/dist/browser/modules/excel/chart/shape-properties.d.ts +156 -0
  38. package/dist/browser/modules/excel/chart/shape-properties.js +1557 -0
  39. package/dist/browser/modules/excel/chart/stroke-font.d.ts +36 -0
  40. package/dist/browser/modules/excel/chart/stroke-font.js +1556 -0
  41. package/dist/browser/modules/excel/chart/topojson.d.ts +98 -0
  42. package/dist/browser/modules/excel/chart/topojson.js +236 -0
  43. package/dist/browser/modules/excel/chart/types.d.ts +2559 -0
  44. package/dist/browser/modules/excel/chart/types.js +8 -0
  45. package/dist/browser/modules/excel/chart-host-registry.d.ts +157 -0
  46. package/dist/browser/modules/excel/chart-host-registry.js +90 -0
  47. package/dist/browser/modules/excel/chartsheet.d.ts +102 -0
  48. package/dist/browser/modules/excel/chartsheet.js +196 -0
  49. package/dist/browser/modules/excel/defined-names.d.ts +35 -0
  50. package/dist/browser/modules/excel/defined-names.js +44 -4
  51. package/dist/browser/modules/excel/errors.d.ts +6 -0
  52. package/dist/browser/modules/excel/errors.js +9 -0
  53. package/dist/browser/modules/excel/form-control.d.ts +6 -0
  54. package/dist/browser/modules/excel/form-control.js +17 -0
  55. package/dist/browser/modules/excel/image.js +12 -2
  56. package/dist/browser/modules/excel/pivot-chart.d.ts +7 -0
  57. package/dist/browser/modules/excel/pivot-chart.js +53 -0
  58. package/dist/browser/modules/excel/pivot-table.d.ts +55 -0
  59. package/dist/browser/modules/excel/pivot-table.js +35 -0
  60. package/dist/browser/modules/excel/range.js +5 -1
  61. package/dist/browser/modules/excel/sparkline/index.d.ts +7 -0
  62. package/dist/browser/modules/excel/sparkline/index.js +7 -0
  63. package/dist/browser/modules/excel/sparkline/sparkline.d.ts +206 -0
  64. package/dist/browser/modules/excel/sparkline/sparkline.js +750 -0
  65. package/dist/browser/modules/excel/stream/worksheet-writer.js +3 -2
  66. package/dist/browser/modules/excel/table.js +42 -6
  67. package/dist/browser/modules/excel/types.d.ts +72 -0
  68. package/dist/browser/modules/excel/utils/address.d.ts +18 -0
  69. package/dist/browser/modules/excel/utils/address.js +28 -0
  70. package/dist/browser/modules/excel/utils/drawing-utils.js +11 -6
  71. package/dist/browser/modules/excel/utils/guid.d.ts +15 -0
  72. package/dist/browser/modules/excel/utils/guid.js +35 -0
  73. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +74 -0
  74. package/dist/browser/modules/excel/utils/ooxml-paths.js +206 -9
  75. package/dist/browser/modules/excel/utils/ooxml-validator/check-chart-sidecar.d.ts +35 -0
  76. package/dist/browser/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +101 -0
  77. package/dist/browser/modules/excel/utils/ooxml-validator/check-chart.d.ts +32 -0
  78. package/dist/browser/modules/excel/utils/ooxml-validator/check-chart.js +2125 -0
  79. package/dist/browser/modules/excel/utils/ooxml-validator/check-chartsheet.d.ts +9 -0
  80. package/dist/browser/modules/excel/utils/ooxml-validator/check-chartsheet.js +26 -0
  81. package/dist/browser/modules/excel/utils/ooxml-validator/check-content-types.d.ts +16 -0
  82. package/dist/browser/modules/excel/utils/ooxml-validator/check-content-types.js +181 -0
  83. package/dist/browser/modules/excel/utils/ooxml-validator/check-drawing.d.ts +34 -0
  84. package/dist/browser/modules/excel/utils/ooxml-validator/check-drawing.js +267 -0
  85. package/dist/browser/modules/excel/utils/ooxml-validator/check-pivot.d.ts +14 -0
  86. package/dist/browser/modules/excel/utils/ooxml-validator/check-pivot.js +104 -0
  87. package/dist/browser/modules/excel/utils/ooxml-validator/check-relationships.d.ts +18 -0
  88. package/dist/browser/modules/excel/utils/ooxml-validator/check-relationships.js +184 -0
  89. package/dist/browser/modules/excel/utils/ooxml-validator/check-structure.d.ts +21 -0
  90. package/dist/browser/modules/excel/utils/ooxml-validator/check-structure.js +56 -0
  91. package/dist/browser/modules/excel/utils/ooxml-validator/check-styles.d.ts +15 -0
  92. package/dist/browser/modules/excel/utils/ooxml-validator/check-styles.js +89 -0
  93. package/dist/browser/modules/excel/utils/ooxml-validator/check-table.d.ts +31 -0
  94. package/dist/browser/modules/excel/utils/ooxml-validator/check-table.js +177 -0
  95. package/dist/browser/modules/excel/utils/ooxml-validator/check-workbook.d.ts +19 -0
  96. package/dist/browser/modules/excel/utils/ooxml-validator/check-workbook.js +163 -0
  97. package/dist/browser/modules/excel/utils/ooxml-validator/check-worksheet.d.ts +25 -0
  98. package/dist/browser/modules/excel/utils/ooxml-validator/check-worksheet.js +569 -0
  99. package/dist/browser/modules/excel/utils/ooxml-validator/context.d.ts +85 -0
  100. package/dist/browser/modules/excel/utils/ooxml-validator/context.js +191 -0
  101. package/dist/browser/modules/excel/utils/ooxml-validator/index.d.ts +31 -0
  102. package/dist/browser/modules/excel/utils/ooxml-validator/index.js +102 -0
  103. package/dist/browser/modules/excel/utils/ooxml-validator/path-utils.d.ts +67 -0
  104. package/dist/browser/modules/excel/utils/ooxml-validator/path-utils.js +156 -0
  105. package/dist/browser/modules/excel/utils/ooxml-validator/reporter.d.ts +41 -0
  106. package/dist/browser/modules/excel/utils/ooxml-validator/reporter.js +61 -0
  107. package/dist/browser/modules/excel/utils/ooxml-validator/types.d.ts +109 -0
  108. package/dist/browser/modules/excel/utils/ooxml-validator/types.js +12 -0
  109. package/dist/browser/modules/excel/utils/ooxml-validator/xml-utils.d.ts +38 -0
  110. package/dist/browser/modules/excel/utils/ooxml-validator/xml-utils.js +100 -0
  111. package/dist/browser/modules/excel/workbook.browser.d.ts +248 -30
  112. package/dist/browser/modules/excel/workbook.browser.js +966 -31
  113. package/dist/browser/modules/excel/workbook.d.ts +43 -0
  114. package/dist/browser/modules/excel/workbook.js +48 -0
  115. package/dist/browser/modules/excel/worksheet.d.ts +157 -3
  116. package/dist/browser/modules/excel/worksheet.js +394 -35
  117. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +40 -0
  118. package/dist/browser/modules/excel/xlsx/rel-type.js +41 -1
  119. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +1 -0
  120. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
  121. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
  122. package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
  123. package/dist/browser/modules/excel/xlsx/xform/chart/chart-space-xform.d.ts +353 -0
  124. package/dist/browser/modules/excel/xlsx/xform/chart/chart-space-xform.js +6000 -0
  125. package/dist/browser/modules/excel/xlsx/xform/comment/threaded-comments-xform.d.ts +60 -0
  126. package/dist/browser/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +213 -0
  127. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +150 -11
  128. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
  129. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  130. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +30 -0
  131. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
  132. package/dist/browser/modules/excel/xlsx/xform/drawing/graphic-frame-xform.d.ts +54 -0
  133. package/dist/browser/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +225 -0
  134. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +3 -1
  135. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
  136. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +46 -0
  137. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
  138. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +13 -2
  139. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
  140. package/dist/browser/modules/excel/xlsx/xform/sheet/chartsheet-xform.d.ts +185 -0
  141. package/dist/browser/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +441 -0
  142. package/dist/browser/modules/excel/xlsx/xform/sheet/ext-lst-xform.d.ts +1 -0
  143. package/dist/browser/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
  144. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +196 -20
  145. package/dist/browser/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
  146. package/dist/browser/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
  147. package/dist/browser/modules/excel/xlsx/xform/xsd-values.d.ts +63 -0
  148. package/dist/browser/modules/excel/xlsx/xform/xsd-values.js +101 -0
  149. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +115 -21
  150. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +4422 -78
  151. package/dist/browser/modules/pdf/builder/document-builder.d.ts +74 -0
  152. package/dist/browser/modules/pdf/builder/document-builder.js +507 -2
  153. package/dist/browser/modules/pdf/builder/pdf-editor.js +48 -3
  154. package/dist/browser/modules/pdf/excel-bridge.d.ts +69 -0
  155. package/dist/browser/modules/pdf/excel-bridge.js +683 -12
  156. package/dist/browser/modules/pdf/font/font-manager.d.ts +25 -0
  157. package/dist/browser/modules/pdf/font/font-manager.js +39 -0
  158. package/dist/browser/modules/pdf/index.d.ts +5 -2
  159. package/dist/browser/modules/pdf/index.js +3 -1
  160. package/dist/browser/modules/pdf/render/chart-surface.d.ts +33 -0
  161. package/dist/browser/modules/pdf/render/chart-surface.js +200 -0
  162. package/dist/browser/modules/pdf/render/layout-engine.d.ts +22 -1
  163. package/dist/browser/modules/pdf/render/layout-engine.js +436 -56
  164. package/dist/browser/modules/pdf/render/page-renderer.js +169 -28
  165. package/dist/browser/modules/pdf/render/pdf-exporter.js +117 -7
  166. package/dist/browser/modules/pdf/types.d.ts +227 -23
  167. package/dist/browser/modules/pdf/types.js +4 -0
  168. package/dist/browser/modules/pdf/word-bridge.d.ts +47 -0
  169. package/dist/browser/modules/pdf/word-bridge.js +304 -0
  170. package/dist/browser/modules/word/constants.d.ts +179 -0
  171. package/dist/browser/modules/word/constants.js +231 -0
  172. package/dist/browser/modules/word/content-types.d.ts +27 -0
  173. package/dist/browser/modules/word/content-types.js +53 -0
  174. package/dist/browser/modules/word/digital-signatures.d.ts +87 -0
  175. package/dist/browser/modules/word/digital-signatures.js +134 -0
  176. package/dist/browser/modules/word/document.d.ts +728 -0
  177. package/dist/browser/modules/word/document.js +1795 -0
  178. package/dist/browser/modules/word/docx-packager.d.ts +14 -0
  179. package/dist/browser/modules/word/docx-packager.js +822 -0
  180. package/dist/browser/modules/word/docx-reader.d.ts +11 -0
  181. package/dist/browser/modules/word/docx-reader.js +4929 -0
  182. package/dist/browser/modules/word/encryption.d.ts +102 -0
  183. package/dist/browser/modules/word/encryption.js +274 -0
  184. package/dist/browser/modules/word/errors.d.ts +49 -0
  185. package/dist/browser/modules/word/errors.js +68 -0
  186. package/dist/browser/modules/word/font-obfuscation.d.ts +31 -0
  187. package/dist/browser/modules/word/font-obfuscation.js +83 -0
  188. package/dist/browser/modules/word/html-renderer.d.ts +38 -0
  189. package/dist/browser/modules/word/html-renderer.js +782 -0
  190. package/dist/browser/modules/word/index.base.d.ts +19 -0
  191. package/dist/browser/modules/word/index.base.js +51 -0
  192. package/dist/browser/modules/word/index.browser.d.ts +4 -0
  193. package/dist/browser/modules/word/index.browser.js +4 -0
  194. package/dist/browser/modules/word/index.d.ts +4 -0
  195. package/dist/browser/modules/word/index.js +4 -0
  196. package/dist/browser/modules/word/internal-utils.d.ts +23 -0
  197. package/dist/browser/modules/word/internal-utils.js +54 -0
  198. package/dist/browser/modules/word/relationships.d.ts +31 -0
  199. package/dist/browser/modules/word/relationships.js +56 -0
  200. package/dist/browser/modules/word/types.d.ts +2325 -0
  201. package/dist/browser/modules/word/types.js +10 -0
  202. package/dist/browser/modules/word/units.d.ts +49 -0
  203. package/dist/browser/modules/word/units.js +111 -0
  204. package/dist/browser/modules/word/writers/chart-writer.d.ts +10 -0
  205. package/dist/browser/modules/word/writers/chart-writer.js +385 -0
  206. package/dist/browser/modules/word/writers/checkbox-writer.d.ts +9 -0
  207. package/dist/browser/modules/word/writers/checkbox-writer.js +42 -0
  208. package/dist/browser/modules/word/writers/comment-writer.d.ts +15 -0
  209. package/dist/browser/modules/word/writers/comment-writer.js +70 -0
  210. package/dist/browser/modules/word/writers/document-writer.d.ts +16 -0
  211. package/dist/browser/modules/word/writers/document-writer.js +461 -0
  212. package/dist/browser/modules/word/writers/footnote-writer.d.ts +11 -0
  213. package/dist/browser/modules/word/writers/footnote-writer.js +72 -0
  214. package/dist/browser/modules/word/writers/header-footer-writer.d.ts +13 -0
  215. package/dist/browser/modules/word/writers/header-footer-writer.js +129 -0
  216. package/dist/browser/modules/word/writers/image-writer.d.ts +10 -0
  217. package/dist/browser/modules/word/writers/image-writer.js +185 -0
  218. package/dist/browser/modules/word/writers/math-writer.d.ts +9 -0
  219. package/dist/browser/modules/word/writers/math-writer.js +428 -0
  220. package/dist/browser/modules/word/writers/numbering-writer.d.ts +10 -0
  221. package/dist/browser/modules/word/writers/numbering-writer.js +125 -0
  222. package/dist/browser/modules/word/writers/paragraph-writer.d.ts +13 -0
  223. package/dist/browser/modules/word/writers/paragraph-writer.js +516 -0
  224. package/dist/browser/modules/word/writers/parts-writer.d.ts +26 -0
  225. package/dist/browser/modules/word/writers/parts-writer.js +660 -0
  226. package/dist/browser/modules/word/writers/run-writer.d.ts +15 -0
  227. package/dist/browser/modules/word/writers/run-writer.js +649 -0
  228. package/dist/browser/modules/word/writers/section-writer.d.ts +10 -0
  229. package/dist/browser/modules/word/writers/section-writer.js +238 -0
  230. package/dist/browser/modules/word/writers/styles-writer.d.ts +10 -0
  231. package/dist/browser/modules/word/writers/styles-writer.js +242 -0
  232. package/dist/browser/modules/word/writers/table-writer.d.ts +10 -0
  233. package/dist/browser/modules/word/writers/table-writer.js +503 -0
  234. package/dist/browser/modules/word/writers/textbox-writer.d.ts +9 -0
  235. package/dist/browser/modules/word/writers/textbox-writer.js +53 -0
  236. package/dist/browser/modules/word/writers/toc-writer.d.ts +9 -0
  237. package/dist/browser/modules/word/writers/toc-writer.js +79 -0
  238. package/dist/browser/modules/xml/encode.d.ts +56 -7
  239. package/dist/browser/modules/xml/encode.js +157 -11
  240. package/dist/cjs/index.js +13 -2
  241. package/dist/cjs/modules/excel/chart/cache-populator.js +1178 -0
  242. package/dist/cjs/modules/excel/chart/chart-api.js +371 -0
  243. package/dist/cjs/modules/excel/chart/chart-builder.js +2440 -0
  244. package/dist/cjs/modules/excel/chart/chart-ex-builder.js +907 -0
  245. package/dist/cjs/modules/excel/chart/chart-ex-parser.js +1208 -0
  246. package/dist/cjs/modules/excel/chart/chart-ex-renderer.js +5364 -0
  247. package/dist/cjs/modules/excel/chart/chart-ex-types.js +12 -0
  248. package/dist/cjs/modules/excel/chart/chart-images.js +366 -0
  249. package/dist/cjs/modules/excel/chart/chart-presets.js +184 -0
  250. package/dist/cjs/modules/excel/chart/chart-renderer.js +6450 -0
  251. package/dist/cjs/modules/excel/chart/chart-sidecar.js +433 -0
  252. package/dist/cjs/modules/excel/chart/chart-utils.js +845 -0
  253. package/dist/cjs/modules/excel/chart/chart.js +1324 -0
  254. package/dist/cjs/modules/excel/chart/glyph-rasterizer.js +664 -0
  255. package/dist/cjs/modules/excel/chart/index.js +101 -0
  256. package/dist/cjs/modules/excel/chart/install.js +95 -0
  257. package/dist/cjs/modules/excel/chart/shape-properties.js +1577 -0
  258. package/dist/cjs/modules/excel/chart/stroke-font.js +1559 -0
  259. package/dist/cjs/modules/excel/chart/topojson.js +239 -0
  260. package/dist/cjs/modules/excel/chart/types.js +9 -0
  261. package/dist/cjs/modules/excel/chart-host-registry.js +96 -0
  262. package/dist/cjs/modules/excel/chartsheet.js +199 -0
  263. package/dist/cjs/modules/excel/defined-names.js +44 -4
  264. package/dist/cjs/modules/excel/errors.js +11 -1
  265. package/dist/cjs/modules/excel/form-control.js +17 -0
  266. package/dist/cjs/modules/excel/image.js +12 -2
  267. package/dist/cjs/modules/excel/pivot-chart.js +56 -0
  268. package/dist/cjs/modules/excel/pivot-table.js +35 -0
  269. package/dist/cjs/modules/excel/range.js +5 -1
  270. package/dist/cjs/modules/excel/sparkline/index.js +23 -0
  271. package/dist/cjs/modules/excel/sparkline/sparkline.js +756 -0
  272. package/dist/cjs/modules/excel/stream/worksheet-writer.js +3 -2
  273. package/dist/cjs/modules/excel/table.js +42 -6
  274. package/dist/cjs/modules/excel/utils/address.js +29 -0
  275. package/dist/cjs/modules/excel/utils/drawing-utils.js +11 -6
  276. package/dist/cjs/modules/excel/utils/guid.js +38 -0
  277. package/dist/cjs/modules/excel/utils/ooxml-paths.js +246 -9
  278. package/dist/cjs/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +103 -0
  279. package/dist/cjs/modules/excel/utils/ooxml-validator/check-chart.js +2128 -0
  280. package/dist/cjs/modules/excel/utils/ooxml-validator/check-chartsheet.js +29 -0
  281. package/dist/cjs/modules/excel/utils/ooxml-validator/check-content-types.js +184 -0
  282. package/dist/cjs/modules/excel/utils/ooxml-validator/check-drawing.js +270 -0
  283. package/dist/cjs/modules/excel/utils/ooxml-validator/check-pivot.js +107 -0
  284. package/dist/cjs/modules/excel/utils/ooxml-validator/check-relationships.js +188 -0
  285. package/dist/cjs/modules/excel/utils/ooxml-validator/check-structure.js +60 -0
  286. package/dist/cjs/modules/excel/utils/ooxml-validator/check-styles.js +92 -0
  287. package/dist/cjs/modules/excel/utils/ooxml-validator/check-table.js +180 -0
  288. package/dist/cjs/modules/excel/utils/ooxml-validator/check-workbook.js +166 -0
  289. package/dist/cjs/modules/excel/utils/ooxml-validator/check-worksheet.js +572 -0
  290. package/dist/cjs/modules/excel/utils/ooxml-validator/context.js +196 -0
  291. package/dist/cjs/modules/excel/utils/ooxml-validator/index.js +105 -0
  292. package/dist/cjs/modules/excel/utils/ooxml-validator/path-utils.js +168 -0
  293. package/dist/cjs/modules/excel/utils/ooxml-validator/reporter.js +66 -0
  294. package/dist/cjs/modules/excel/utils/ooxml-validator/types.js +13 -0
  295. package/dist/cjs/modules/excel/utils/ooxml-validator/xml-utils.js +110 -0
  296. package/dist/cjs/modules/excel/workbook.browser.js +973 -38
  297. package/dist/cjs/modules/excel/workbook.js +48 -0
  298. package/dist/cjs/modules/excel/worksheet.js +393 -34
  299. package/dist/cjs/modules/excel/xlsx/rel-type.js +41 -1
  300. package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
  301. package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
  302. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
  303. package/dist/cjs/modules/excel/xlsx/xform/chart/chart-space-xform.js +6003 -0
  304. package/dist/cjs/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +219 -0
  305. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +149 -10
  306. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
  307. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  308. package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
  309. package/dist/cjs/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +228 -0
  310. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
  311. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
  312. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
  313. package/dist/cjs/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +444 -0
  314. package/dist/cjs/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
  315. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +195 -19
  316. package/dist/cjs/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
  317. package/dist/cjs/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
  318. package/dist/cjs/modules/excel/xlsx/xform/xsd-values.js +106 -0
  319. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +4420 -76
  320. package/dist/cjs/modules/pdf/builder/document-builder.js +506 -1
  321. package/dist/cjs/modules/pdf/builder/pdf-editor.js +48 -3
  322. package/dist/cjs/modules/pdf/excel-bridge.js +684 -12
  323. package/dist/cjs/modules/pdf/font/font-manager.js +39 -0
  324. package/dist/cjs/modules/pdf/index.js +5 -1
  325. package/dist/cjs/modules/pdf/render/chart-surface.js +203 -0
  326. package/dist/cjs/modules/pdf/render/layout-engine.js +437 -56
  327. package/dist/cjs/modules/pdf/render/page-renderer.js +169 -28
  328. package/dist/cjs/modules/pdf/render/pdf-exporter.js +115 -5
  329. package/dist/cjs/modules/pdf/types.js +5 -0
  330. package/dist/cjs/modules/pdf/word-bridge.js +307 -0
  331. package/dist/cjs/modules/word/constants.js +234 -0
  332. package/dist/cjs/modules/word/content-types.js +57 -0
  333. package/dist/cjs/modules/word/digital-signatures.js +140 -0
  334. package/dist/cjs/modules/word/document.js +1909 -0
  335. package/dist/cjs/modules/word/docx-packager.js +825 -0
  336. package/dist/cjs/modules/word/docx-reader.js +4932 -0
  337. package/dist/cjs/modules/word/encryption.js +282 -0
  338. package/dist/cjs/modules/word/errors.js +88 -0
  339. package/dist/cjs/modules/word/font-obfuscation.js +88 -0
  340. package/dist/cjs/modules/word/html-renderer.js +785 -0
  341. package/dist/cjs/modules/word/index.base.js +199 -0
  342. package/dist/cjs/modules/word/index.browser.js +20 -0
  343. package/dist/cjs/modules/word/index.js +20 -0
  344. package/dist/cjs/modules/word/internal-utils.js +59 -0
  345. package/dist/cjs/modules/word/relationships.js +60 -0
  346. package/dist/cjs/modules/word/types.js +11 -0
  347. package/dist/cjs/modules/word/units.js +135 -0
  348. package/dist/cjs/modules/word/writers/chart-writer.js +388 -0
  349. package/dist/cjs/modules/word/writers/checkbox-writer.js +45 -0
  350. package/dist/cjs/modules/word/writers/comment-writer.js +74 -0
  351. package/dist/cjs/modules/word/writers/document-writer.js +465 -0
  352. package/dist/cjs/modules/word/writers/footnote-writer.js +76 -0
  353. package/dist/cjs/modules/word/writers/header-footer-writer.js +134 -0
  354. package/dist/cjs/modules/word/writers/image-writer.js +188 -0
  355. package/dist/cjs/modules/word/writers/math-writer.js +431 -0
  356. package/dist/cjs/modules/word/writers/numbering-writer.js +128 -0
  357. package/dist/cjs/modules/word/writers/paragraph-writer.js +521 -0
  358. package/dist/cjs/modules/word/writers/parts-writer.js +671 -0
  359. package/dist/cjs/modules/word/writers/run-writer.js +655 -0
  360. package/dist/cjs/modules/word/writers/section-writer.js +241 -0
  361. package/dist/cjs/modules/word/writers/styles-writer.js +245 -0
  362. package/dist/cjs/modules/word/writers/table-writer.js +506 -0
  363. package/dist/cjs/modules/word/writers/textbox-writer.js +56 -0
  364. package/dist/cjs/modules/word/writers/toc-writer.js +82 -0
  365. package/dist/cjs/modules/xml/encode.js +158 -11
  366. package/dist/esm/index.browser.js +20 -2
  367. package/dist/esm/index.js +9 -1
  368. package/dist/esm/modules/excel/chart/cache-populator.js +1171 -0
  369. package/dist/esm/modules/excel/chart/chart-api.js +364 -0
  370. package/dist/esm/modules/excel/chart/chart-builder.js +2432 -0
  371. package/dist/esm/modules/excel/chart/chart-ex-builder.js +903 -0
  372. package/dist/esm/modules/excel/chart/chart-ex-parser.js +1205 -0
  373. package/dist/esm/modules/excel/chart/chart-ex-renderer.js +5352 -0
  374. package/dist/esm/modules/excel/chart/chart-ex-types.js +11 -0
  375. package/dist/esm/modules/excel/chart/chart-images.js +363 -0
  376. package/dist/esm/modules/excel/chart/chart-presets.js +179 -0
  377. package/dist/esm/modules/excel/chart/chart-renderer.js +6440 -0
  378. package/dist/esm/modules/excel/chart/chart-sidecar.js +427 -0
  379. package/dist/esm/modules/excel/chart/chart-utils.js +821 -0
  380. package/dist/esm/modules/excel/chart/chart.js +1320 -0
  381. package/dist/esm/modules/excel/chart/glyph-rasterizer.js +658 -0
  382. package/dist/esm/modules/excel/chart/index.js +46 -0
  383. package/dist/esm/modules/excel/chart/install.js +91 -0
  384. package/dist/esm/modules/excel/chart/shape-properties.js +1557 -0
  385. package/dist/esm/modules/excel/chart/stroke-font.js +1556 -0
  386. package/dist/esm/modules/excel/chart/topojson.js +236 -0
  387. package/dist/esm/modules/excel/chart/types.js +8 -0
  388. package/dist/esm/modules/excel/chart-host-registry.js +90 -0
  389. package/dist/esm/modules/excel/chartsheet.js +196 -0
  390. package/dist/esm/modules/excel/defined-names.js +44 -4
  391. package/dist/esm/modules/excel/errors.js +9 -0
  392. package/dist/esm/modules/excel/form-control.js +17 -0
  393. package/dist/esm/modules/excel/image.js +12 -2
  394. package/dist/esm/modules/excel/pivot-chart.js +53 -0
  395. package/dist/esm/modules/excel/pivot-table.js +35 -0
  396. package/dist/esm/modules/excel/range.js +5 -1
  397. package/dist/esm/modules/excel/sparkline/index.js +7 -0
  398. package/dist/esm/modules/excel/sparkline/sparkline.js +750 -0
  399. package/dist/esm/modules/excel/stream/worksheet-writer.js +3 -2
  400. package/dist/esm/modules/excel/table.js +42 -6
  401. package/dist/esm/modules/excel/utils/address.js +28 -0
  402. package/dist/esm/modules/excel/utils/drawing-utils.js +11 -6
  403. package/dist/esm/modules/excel/utils/guid.js +35 -0
  404. package/dist/esm/modules/excel/utils/ooxml-paths.js +206 -9
  405. package/dist/esm/modules/excel/utils/ooxml-validator/check-chart-sidecar.js +101 -0
  406. package/dist/esm/modules/excel/utils/ooxml-validator/check-chart.js +2125 -0
  407. package/dist/esm/modules/excel/utils/ooxml-validator/check-chartsheet.js +26 -0
  408. package/dist/esm/modules/excel/utils/ooxml-validator/check-content-types.js +181 -0
  409. package/dist/esm/modules/excel/utils/ooxml-validator/check-drawing.js +267 -0
  410. package/dist/esm/modules/excel/utils/ooxml-validator/check-pivot.js +104 -0
  411. package/dist/esm/modules/excel/utils/ooxml-validator/check-relationships.js +184 -0
  412. package/dist/esm/modules/excel/utils/ooxml-validator/check-structure.js +56 -0
  413. package/dist/esm/modules/excel/utils/ooxml-validator/check-styles.js +89 -0
  414. package/dist/esm/modules/excel/utils/ooxml-validator/check-table.js +177 -0
  415. package/dist/esm/modules/excel/utils/ooxml-validator/check-workbook.js +163 -0
  416. package/dist/esm/modules/excel/utils/ooxml-validator/check-worksheet.js +569 -0
  417. package/dist/esm/modules/excel/utils/ooxml-validator/context.js +191 -0
  418. package/dist/esm/modules/excel/utils/ooxml-validator/index.js +102 -0
  419. package/dist/esm/modules/excel/utils/ooxml-validator/path-utils.js +156 -0
  420. package/dist/esm/modules/excel/utils/ooxml-validator/reporter.js +61 -0
  421. package/dist/esm/modules/excel/utils/ooxml-validator/types.js +12 -0
  422. package/dist/esm/modules/excel/utils/ooxml-validator/xml-utils.js +100 -0
  423. package/dist/esm/modules/excel/workbook.browser.js +969 -34
  424. package/dist/esm/modules/excel/workbook.js +48 -0
  425. package/dist/esm/modules/excel/worksheet.js +394 -35
  426. package/dist/esm/modules/excel/xlsx/rel-type.js +41 -1
  427. package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +11 -2
  428. package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +12 -10
  429. package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +96 -22
  430. package/dist/esm/modules/excel/xlsx/xform/chart/chart-space-xform.js +6000 -0
  431. package/dist/esm/modules/excel/xlsx/xform/comment/threaded-comments-xform.js +213 -0
  432. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +150 -11
  433. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +20 -1
  434. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  435. package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +109 -5
  436. package/dist/esm/modules/excel/xlsx/xform/drawing/graphic-frame-xform.js +225 -0
  437. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +18 -3
  438. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +294 -12
  439. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +32 -6
  440. package/dist/esm/modules/excel/xlsx/xform/sheet/chartsheet-xform.js +441 -0
  441. package/dist/esm/modules/excel/xlsx/xform/sheet/ext-lst-xform.js +51 -2
  442. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +196 -20
  443. package/dist/esm/modules/excel/xlsx/xform/table/auto-filter-xform.js +16 -1
  444. package/dist/esm/modules/excel/xlsx/xform/table/table-column-xform.js +17 -2
  445. package/dist/esm/modules/excel/xlsx/xform/xsd-values.js +101 -0
  446. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +4422 -78
  447. package/dist/esm/modules/pdf/builder/document-builder.js +507 -2
  448. package/dist/esm/modules/pdf/builder/pdf-editor.js +48 -3
  449. package/dist/esm/modules/pdf/excel-bridge.js +683 -12
  450. package/dist/esm/modules/pdf/font/font-manager.js +39 -0
  451. package/dist/esm/modules/pdf/index.js +3 -1
  452. package/dist/esm/modules/pdf/render/chart-surface.js +200 -0
  453. package/dist/esm/modules/pdf/render/layout-engine.js +436 -56
  454. package/dist/esm/modules/pdf/render/page-renderer.js +169 -28
  455. package/dist/esm/modules/pdf/render/pdf-exporter.js +117 -7
  456. package/dist/esm/modules/pdf/types.js +4 -0
  457. package/dist/esm/modules/pdf/word-bridge.js +304 -0
  458. package/dist/esm/modules/word/constants.js +231 -0
  459. package/dist/esm/modules/word/content-types.js +53 -0
  460. package/dist/esm/modules/word/digital-signatures.js +134 -0
  461. package/dist/esm/modules/word/document.js +1795 -0
  462. package/dist/esm/modules/word/docx-packager.js +822 -0
  463. package/dist/esm/modules/word/docx-reader.js +4929 -0
  464. package/dist/esm/modules/word/encryption.js +274 -0
  465. package/dist/esm/modules/word/errors.js +68 -0
  466. package/dist/esm/modules/word/font-obfuscation.js +83 -0
  467. package/dist/esm/modules/word/html-renderer.js +782 -0
  468. package/dist/esm/modules/word/index.base.js +51 -0
  469. package/dist/esm/modules/word/index.browser.js +4 -0
  470. package/dist/esm/modules/word/index.js +4 -0
  471. package/dist/esm/modules/word/internal-utils.js +54 -0
  472. package/dist/esm/modules/word/relationships.js +56 -0
  473. package/dist/esm/modules/word/types.js +10 -0
  474. package/dist/esm/modules/word/units.js +111 -0
  475. package/dist/esm/modules/word/writers/chart-writer.js +385 -0
  476. package/dist/esm/modules/word/writers/checkbox-writer.js +42 -0
  477. package/dist/esm/modules/word/writers/comment-writer.js +70 -0
  478. package/dist/esm/modules/word/writers/document-writer.js +461 -0
  479. package/dist/esm/modules/word/writers/footnote-writer.js +72 -0
  480. package/dist/esm/modules/word/writers/header-footer-writer.js +129 -0
  481. package/dist/esm/modules/word/writers/image-writer.js +185 -0
  482. package/dist/esm/modules/word/writers/math-writer.js +428 -0
  483. package/dist/esm/modules/word/writers/numbering-writer.js +125 -0
  484. package/dist/esm/modules/word/writers/paragraph-writer.js +516 -0
  485. package/dist/esm/modules/word/writers/parts-writer.js +660 -0
  486. package/dist/esm/modules/word/writers/run-writer.js +649 -0
  487. package/dist/esm/modules/word/writers/section-writer.js +238 -0
  488. package/dist/esm/modules/word/writers/styles-writer.js +242 -0
  489. package/dist/esm/modules/word/writers/table-writer.js +503 -0
  490. package/dist/esm/modules/word/writers/textbox-writer.js +53 -0
  491. package/dist/esm/modules/word/writers/toc-writer.js +79 -0
  492. package/dist/esm/modules/xml/encode.js +157 -11
  493. package/dist/iife/excelts.iife.js +11789 -687
  494. package/dist/iife/excelts.iife.js.map +1 -1
  495. package/dist/iife/excelts.iife.min.js +52 -44
  496. package/dist/types/index.browser.d.ts +8 -5
  497. package/dist/types/index.d.ts +4 -2
  498. package/dist/types/modules/excel/chart/cache-populator.d.ts +49 -0
  499. package/dist/types/modules/excel/chart/chart-api.d.ts +92 -0
  500. package/dist/types/modules/excel/chart/chart-builder.d.ts +48 -0
  501. package/dist/types/modules/excel/chart/chart-ex-builder.d.ts +36 -0
  502. package/dist/types/modules/excel/chart/chart-ex-parser.d.ts +8 -0
  503. package/dist/types/modules/excel/chart/chart-ex-renderer.d.ts +187 -0
  504. package/dist/types/modules/excel/chart/chart-ex-types.d.ts +531 -0
  505. package/dist/types/modules/excel/chart/chart-images.d.ts +78 -0
  506. package/dist/types/modules/excel/chart/chart-presets.d.ts +392 -0
  507. package/dist/types/modules/excel/chart/chart-renderer.d.ts +550 -0
  508. package/dist/types/modules/excel/chart/chart-sidecar.d.ts +21 -0
  509. package/dist/types/modules/excel/chart/chart-utils.d.ts +306 -0
  510. package/dist/types/modules/excel/chart/chart.d.ts +504 -0
  511. package/dist/types/modules/excel/chart/glyph-rasterizer.d.ts +62 -0
  512. package/dist/types/modules/excel/chart/index.d.ts +54 -0
  513. package/dist/types/modules/excel/chart/install.d.ts +44 -0
  514. package/dist/types/modules/excel/chart/shape-properties.d.ts +156 -0
  515. package/dist/types/modules/excel/chart/stroke-font.d.ts +36 -0
  516. package/dist/types/modules/excel/chart/topojson.d.ts +98 -0
  517. package/dist/types/modules/excel/chart/types.d.ts +2559 -0
  518. package/dist/types/modules/excel/chart-host-registry.d.ts +157 -0
  519. package/dist/types/modules/excel/chartsheet.d.ts +102 -0
  520. package/dist/types/modules/excel/defined-names.d.ts +35 -0
  521. package/dist/types/modules/excel/errors.d.ts +6 -0
  522. package/dist/types/modules/excel/form-control.d.ts +6 -0
  523. package/dist/types/modules/excel/pivot-chart.d.ts +7 -0
  524. package/dist/types/modules/excel/pivot-table.d.ts +55 -0
  525. package/dist/types/modules/excel/sparkline/index.d.ts +7 -0
  526. package/dist/types/modules/excel/sparkline/sparkline.d.ts +206 -0
  527. package/dist/types/modules/excel/types.d.ts +72 -0
  528. package/dist/types/modules/excel/utils/address.d.ts +18 -0
  529. package/dist/types/modules/excel/utils/guid.d.ts +15 -0
  530. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +74 -0
  531. package/dist/types/modules/excel/utils/ooxml-validator/check-chart-sidecar.d.ts +35 -0
  532. package/dist/types/modules/excel/utils/ooxml-validator/check-chart.d.ts +32 -0
  533. package/dist/types/modules/excel/utils/ooxml-validator/check-chartsheet.d.ts +9 -0
  534. package/dist/types/modules/excel/utils/ooxml-validator/check-content-types.d.ts +16 -0
  535. package/dist/types/modules/excel/utils/ooxml-validator/check-drawing.d.ts +34 -0
  536. package/dist/types/modules/excel/utils/ooxml-validator/check-pivot.d.ts +14 -0
  537. package/dist/types/modules/excel/utils/ooxml-validator/check-relationships.d.ts +18 -0
  538. package/dist/types/modules/excel/utils/ooxml-validator/check-structure.d.ts +21 -0
  539. package/dist/types/modules/excel/utils/ooxml-validator/check-styles.d.ts +15 -0
  540. package/dist/types/modules/excel/utils/ooxml-validator/check-table.d.ts +31 -0
  541. package/dist/types/modules/excel/utils/ooxml-validator/check-workbook.d.ts +19 -0
  542. package/dist/types/modules/excel/utils/ooxml-validator/check-worksheet.d.ts +25 -0
  543. package/dist/types/modules/excel/utils/ooxml-validator/context.d.ts +85 -0
  544. package/dist/types/modules/excel/utils/ooxml-validator/index.d.ts +31 -0
  545. package/dist/types/modules/excel/utils/ooxml-validator/path-utils.d.ts +67 -0
  546. package/dist/types/modules/excel/utils/ooxml-validator/reporter.d.ts +41 -0
  547. package/dist/types/modules/excel/utils/ooxml-validator/types.d.ts +109 -0
  548. package/dist/types/modules/excel/utils/ooxml-validator/xml-utils.d.ts +38 -0
  549. package/dist/types/modules/excel/workbook.browser.d.ts +248 -30
  550. package/dist/types/modules/excel/workbook.d.ts +43 -0
  551. package/dist/types/modules/excel/worksheet.d.ts +157 -3
  552. package/dist/types/modules/excel/xlsx/rel-type.d.ts +40 -0
  553. package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +1 -0
  554. package/dist/types/modules/excel/xlsx/xform/chart/chart-space-xform.d.ts +353 -0
  555. package/dist/types/modules/excel/xlsx/xform/comment/threaded-comments-xform.d.ts +60 -0
  556. package/dist/types/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +30 -0
  557. package/dist/types/modules/excel/xlsx/xform/drawing/graphic-frame-xform.d.ts +54 -0
  558. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +3 -1
  559. package/dist/types/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +46 -0
  560. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +13 -2
  561. package/dist/types/modules/excel/xlsx/xform/sheet/chartsheet-xform.d.ts +185 -0
  562. package/dist/types/modules/excel/xlsx/xform/sheet/ext-lst-xform.d.ts +1 -0
  563. package/dist/types/modules/excel/xlsx/xform/xsd-values.d.ts +63 -0
  564. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +115 -21
  565. package/dist/types/modules/pdf/builder/document-builder.d.ts +74 -0
  566. package/dist/types/modules/pdf/excel-bridge.d.ts +69 -0
  567. package/dist/types/modules/pdf/font/font-manager.d.ts +25 -0
  568. package/dist/types/modules/pdf/index.d.ts +5 -2
  569. package/dist/types/modules/pdf/render/chart-surface.d.ts +33 -0
  570. package/dist/types/modules/pdf/render/layout-engine.d.ts +22 -1
  571. package/dist/types/modules/pdf/types.d.ts +227 -23
  572. package/dist/types/modules/pdf/word-bridge.d.ts +47 -0
  573. package/dist/types/modules/word/constants.d.ts +179 -0
  574. package/dist/types/modules/word/content-types.d.ts +27 -0
  575. package/dist/types/modules/word/digital-signatures.d.ts +87 -0
  576. package/dist/types/modules/word/document.d.ts +728 -0
  577. package/dist/types/modules/word/docx-packager.d.ts +14 -0
  578. package/dist/types/modules/word/docx-reader.d.ts +11 -0
  579. package/dist/types/modules/word/encryption.d.ts +102 -0
  580. package/dist/types/modules/word/errors.d.ts +49 -0
  581. package/dist/types/modules/word/font-obfuscation.d.ts +31 -0
  582. package/dist/types/modules/word/html-renderer.d.ts +38 -0
  583. package/dist/types/modules/word/index.base.d.ts +19 -0
  584. package/dist/types/modules/word/index.browser.d.ts +4 -0
  585. package/dist/types/modules/word/index.d.ts +4 -0
  586. package/dist/types/modules/word/internal-utils.d.ts +23 -0
  587. package/dist/types/modules/word/relationships.d.ts +31 -0
  588. package/dist/types/modules/word/types.d.ts +2325 -0
  589. package/dist/types/modules/word/units.d.ts +49 -0
  590. package/dist/types/modules/word/writers/chart-writer.d.ts +10 -0
  591. package/dist/types/modules/word/writers/checkbox-writer.d.ts +9 -0
  592. package/dist/types/modules/word/writers/comment-writer.d.ts +15 -0
  593. package/dist/types/modules/word/writers/document-writer.d.ts +16 -0
  594. package/dist/types/modules/word/writers/footnote-writer.d.ts +11 -0
  595. package/dist/types/modules/word/writers/header-footer-writer.d.ts +13 -0
  596. package/dist/types/modules/word/writers/image-writer.d.ts +10 -0
  597. package/dist/types/modules/word/writers/math-writer.d.ts +9 -0
  598. package/dist/types/modules/word/writers/numbering-writer.d.ts +10 -0
  599. package/dist/types/modules/word/writers/paragraph-writer.d.ts +13 -0
  600. package/dist/types/modules/word/writers/parts-writer.d.ts +26 -0
  601. package/dist/types/modules/word/writers/run-writer.d.ts +15 -0
  602. package/dist/types/modules/word/writers/section-writer.d.ts +10 -0
  603. package/dist/types/modules/word/writers/styles-writer.d.ts +10 -0
  604. package/dist/types/modules/word/writers/table-writer.d.ts +10 -0
  605. package/dist/types/modules/word/writers/textbox-writer.d.ts +9 -0
  606. package/dist/types/modules/word/writers/toc-writer.d.ts +9 -0
  607. package/dist/types/modules/xml/encode.d.ts +56 -7
  608. package/package.json +29 -11
  609. package/dist/browser/modules/excel/utils/ooxml-validator.d.ts +0 -48
  610. package/dist/browser/modules/excel/utils/ooxml-validator.js +0 -493
  611. package/dist/browser/modules/excel/utils/passthrough-manager.d.ts +0 -77
  612. package/dist/browser/modules/excel/utils/passthrough-manager.js +0 -129
  613. package/dist/cjs/modules/excel/utils/ooxml-validator.js +0 -499
  614. package/dist/cjs/modules/excel/utils/passthrough-manager.js +0 -133
  615. package/dist/esm/modules/excel/utils/ooxml-validator.js +0 -493
  616. package/dist/esm/modules/excel/utils/passthrough-manager.js +0 -129
  617. package/dist/types/modules/excel/utils/ooxml-validator.d.ts +0 -48
  618. package/dist/types/modules/excel/utils/passthrough-manager.d.ts +0 -77
@@ -13,16 +13,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.XLSX = void 0;
14
14
  const zip_parser_1 = require("../../archive/unzip/zip-parser.js");
15
15
  const stream_1 = require("../../archive/zip/stream.js");
16
+ // Chart serialisation / deserialisation goes through the chart-host-registry
17
+ // slot so the chart module is only pulled into consumer bundles when they
18
+ // explicitly `import "@cj-tech-master/excelts/chart"`. Type imports are
19
+ // erased at runtime; runtime entry points route through `getChartSupport()`.
20
+ const chart_host_registry_1 = require("../chart-host-registry.js");
16
21
  const errors_1 = require("../errors.js");
17
22
  const drawing_utils_1 = require("../utils/drawing-utils.js");
18
23
  const external_link_formula_1 = require("../utils/external-link-formula.js");
19
24
  const ooxml_paths_1 = require("../utils/ooxml-paths.js");
20
- const passthrough_manager_1 = require("../utils/passthrough-manager.js");
25
+ const ooxml_validator_1 = require("../utils/ooxml-validator/index.js");
21
26
  const stream_buf_1 = require("../utils/stream-buf.js");
22
27
  const rel_type_1 = require("./rel-type.js");
23
28
  const external_link_xform_1 = require("./xform/book/external-link-xform.js");
24
29
  const workbook_xform_1 = require("./xform/book/workbook-xform.js");
25
30
  const comments_xform_1 = require("./xform/comment/comments-xform.js");
31
+ const threaded_comments_xform_1 = require("./xform/comment/threaded-comments-xform.js");
26
32
  const app_xform_1 = require("./xform/core/app-xform.js");
27
33
  const content_types_xform_1 = require("./xform/core/content-types-xform.js");
28
34
  const core_xform_1 = require("./xform/core/core-xform.js");
@@ -35,6 +41,7 @@ const vml_drawing_xform_1 = require("./xform/drawing/vml-drawing-xform.js");
35
41
  const pivot_cache_definition_xform_1 = require("./xform/pivot-table/pivot-cache-definition-xform.js");
36
42
  const pivot_cache_records_xform_1 = require("./xform/pivot-table/pivot-cache-records-xform.js");
37
43
  const pivot_table_xform_1 = require("./xform/pivot-table/pivot-table-xform.js");
44
+ const chartsheet_xform_1 = require("./xform/sheet/chartsheet-xform.js");
38
45
  const worksheet_xform_1 = require("./xform/sheet/worksheet-xform.js");
39
46
  const shared_strings_xform_1 = require("./xform/strings/shared-strings-xform.js");
40
47
  const styles_xform_1 = require("./xform/style/styles-xform.js");
@@ -43,7 +50,10 @@ const theme1_1 = require("./xml/theme1.js");
43
50
  const _stream_1 = require("../../stream/index.js");
44
51
  const binary_1 = require("../../../utils/binary.js");
45
52
  const utils_1 = require("../../../utils/utils.js");
53
+ const uuid_1 = require("../../../utils/uuid.js");
54
+ const encode_1 = require("../../xml/encode.js");
46
55
  const stream_writer_1 = require("../../xml/stream-writer.js");
56
+ const writer_1 = require("../../xml/writer.js");
47
57
  class StreamingZipWriterAdapter {
48
58
  constructor(options) {
49
59
  this.events = new Map();
@@ -273,6 +283,3491 @@ function upsertSheet(link, sheetName) {
273
283
  link.sheetNames.push(sheetName);
274
284
  }
275
285
  }
286
+ function snapshotChartModel(model) {
287
+ try {
288
+ return JSON.stringify(model);
289
+ }
290
+ catch {
291
+ return undefined;
292
+ }
293
+ }
294
+ /**
295
+ * Extract leading XML comments that appear immediately before a target
296
+ * element's open tag in an OOXML chart part. We use this to preserve
297
+ * vendor / annotation comments (e.g. style provenance markers) when the
298
+ * chart writer falls back to a structured rebuild — `BaseXform.parseStreamDirect`
299
+ * does not surface `comment` events, so the structured model has no
300
+ * memory of them.
301
+ *
302
+ * Returns the substring of comment nodes (whitespace stripped, joined
303
+ * by no separator). Empty string when no comment precedes the open tag.
304
+ */
305
+ /**
306
+ * Build the chartsheet-drawing XML that wraps a single classic or
307
+ * ChartEx chart occupying the entire chartsheet canvas.
308
+ *
309
+ * Chartsheets have no cell grid — `sheetData` is empty and there are
310
+ * no `<cols>` / `<row>` sizing entries for Excel to lay an anchor
311
+ * against. A cell-based `<xdr:twoCellAnchor from="A1" to="R31"/>`
312
+ * (what the generic `DrawingXform` emits) therefore resolves to a
313
+ * 0×0 bounding box on a chartsheet, and Excel renders a blank
314
+ * white canvas with no chart inside. Using `<xdr:absoluteAnchor>`
315
+ * with concrete EMU coordinates is how Excel itself writes
316
+ * chartsheet drawings — the anchor's `pos`/`ext` pair gives the
317
+ * engine something real to lay the graphic against, while the
318
+ * inner `<xdr:graphicFrame>/<xdr:xfrm>` repeats the extent so the
319
+ * graphic is sized to fill the anchor.
320
+ *
321
+ * ChartEx drawings additionally need an `<mc:AlternateContent>`
322
+ * wrapper around the `<xdr:graphicFrame>` — the `cx` namespace is
323
+ * a Microsoft extension that legacy-Excel loaders don't understand,
324
+ * so the Fallback branch emits a placeholder shape (the same
325
+ * "This chart isn't available in your version of Excel" message
326
+ * Office uses).
327
+ */
328
+ function renderChartsheetDrawingXml(options) {
329
+ const { chartRId, chartName, isChartEx, extCx, extCy } = options;
330
+ const escName = (0, encode_1.xmlEncodeAttr)(chartName);
331
+ const escRId = (0, encode_1.xmlEncodeAttr)(chartRId);
332
+ const cNvPrExtLst = isChartEx
333
+ ? `<a:extLst><a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}"><a16:creationId xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main" id="{${(0, uuid_1.uuidV4)().toUpperCase()}}"/></a:ext></a:extLst>`
334
+ : "";
335
+ const graphicFrame = `<xdr:graphicFrame macro="">` +
336
+ `<xdr:nvGraphicFramePr>` +
337
+ (cNvPrExtLst
338
+ ? `<xdr:cNvPr id="1" name="${escName}">${cNvPrExtLst}</xdr:cNvPr>`
339
+ : `<xdr:cNvPr id="1" name="${escName}"/>`) +
340
+ `<xdr:cNvGraphicFramePr/>` +
341
+ `</xdr:nvGraphicFramePr>` +
342
+ `<xdr:xfrm>` +
343
+ `<a:off x="0" y="0"/>` +
344
+ `<a:ext cx="${extCx}" cy="${extCy}"/>` +
345
+ `</xdr:xfrm>` +
346
+ `<a:graphic>` +
347
+ (isChartEx
348
+ ? `<a:graphicData uri="http://schemas.microsoft.com/office/drawing/2014/chartex">` +
349
+ `<cx:chart xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${escRId}"/>` +
350
+ `</a:graphicData>`
351
+ : `<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart">` +
352
+ `<c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${escRId}"/>` +
353
+ `</a:graphicData>`) +
354
+ `</a:graphic>` +
355
+ `</xdr:graphicFrame>`;
356
+ const fallbackShape = `<xdr:sp macro="" textlink="">` +
357
+ `<xdr:nvSpPr>` +
358
+ `<xdr:cNvPr id="0" name=""/>` +
359
+ `<xdr:cNvSpPr><a:spLocks noTextEdit="1"/></xdr:cNvSpPr>` +
360
+ `</xdr:nvSpPr>` +
361
+ `<xdr:spPr>` +
362
+ `<a:xfrm>` +
363
+ `<a:off x="0" y="0"/>` +
364
+ `<a:ext cx="${extCx}" cy="${extCy}"/>` +
365
+ `</a:xfrm>` +
366
+ `<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>` +
367
+ `<a:solidFill><a:prstClr val="white"/></a:solidFill>` +
368
+ `<a:ln w="1"><a:solidFill><a:prstClr val="black"/></a:solidFill></a:ln>` +
369
+ `</xdr:spPr>` +
370
+ `<xdr:txBody>` +
371
+ `<a:bodyPr vertOverflow="clip" horzOverflow="clip"/>` +
372
+ `<a:lstStyle/>` +
373
+ `<a:p><a:r><a:rPr lang="en-US" sz="1100"/>` +
374
+ `<a:t>This chart isn&apos;t available in your version of Excel.\n\n` +
375
+ `Editing this shape or saving this workbook into a different file format will permanently break the chart.</a:t>` +
376
+ `</a:r></a:p>` +
377
+ `</xdr:txBody>` +
378
+ `</xdr:sp>`;
379
+ const anchorBody = isChartEx
380
+ ? `<mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">` +
381
+ `<mc:Choice xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" Requires="cx1">` +
382
+ graphicFrame +
383
+ `</mc:Choice>` +
384
+ `<mc:Fallback>` +
385
+ fallbackShape +
386
+ `</mc:Fallback>` +
387
+ `</mc:AlternateContent>`
388
+ : graphicFrame;
389
+ return (`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n` +
390
+ `<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">` +
391
+ `<xdr:absoluteAnchor>` +
392
+ `<xdr:pos x="0" y="0"/>` +
393
+ `<xdr:ext cx="${extCx}" cy="${extCy}"/>` +
394
+ anchorBody +
395
+ `<xdr:clientData/>` +
396
+ `</xdr:absoluteAnchor>` +
397
+ `</xdr:wsDr>`);
398
+ }
399
+ function extractLeadingComments(originalXml, openTagRegex) {
400
+ const m = openTagRegex.exec(originalXml);
401
+ if (!m) {
402
+ return "";
403
+ }
404
+ const before = originalXml.slice(0, m.index);
405
+ // Walk backwards collecting consecutive `<!--…-->` blocks (with
406
+ // optional whitespace between them and the open tag).
407
+ const comments = [];
408
+ let cursor = before.length;
409
+ while (cursor > 0) {
410
+ // Skip trailing whitespace
411
+ let head = cursor;
412
+ while (head > 0 && /\s/.test(before.charAt(head - 1))) {
413
+ head--;
414
+ }
415
+ // Look for `-->` ending right at `head`
416
+ if (head < 3 || before.slice(head - 3, head) !== "-->") {
417
+ break;
418
+ }
419
+ // Find the matching `<!--` start
420
+ const start = before.lastIndexOf("<!--", head - 3);
421
+ if (start < 0) {
422
+ break;
423
+ }
424
+ comments.unshift(before.slice(start, head));
425
+ cursor = start;
426
+ }
427
+ return comments.join("");
428
+ }
429
+ /**
430
+ * Render a chart part (classic) to bytes via `XmlWriter`, then splice
431
+ * preserved leading comments from the original raw XML in front of the
432
+ * `<c:chart>` open tag. If the original has no leading comments or no
433
+ * `rawData` is available, returns the unmodified rendered bytes.
434
+ */
435
+ function renderChartWithLeadingComments(entry, xform) {
436
+ const writer = new writer_1.XmlWriter();
437
+ xform.render(writer, entry.model);
438
+ let xml = writer.toString();
439
+ if (entry.rawData) {
440
+ const originalXml = new TextDecoder().decode(entry.rawData);
441
+ const comments = extractLeadingComments(originalXml, /<c:chart(?:\s|>)/);
442
+ if (comments) {
443
+ xml = xml.replace(/<c:chart(\s|>)/, `${comments}<c:chart$1`);
444
+ }
445
+ }
446
+ return new TextEncoder().encode(xml);
447
+ }
448
+ /**
449
+ * Splice preserved leading comments from a ChartEx raw XML buffer into
450
+ * a freshly-rendered structural rebuild output.
451
+ */
452
+ function spliceChartExLeadingComments(renderedXml, originalRawXml) {
453
+ if (!originalRawXml) {
454
+ return renderedXml;
455
+ }
456
+ const comments = extractLeadingComments(originalRawXml, /<cx:chart(?:\s|>)/);
457
+ if (!comments) {
458
+ return renderedXml;
459
+ }
460
+ return renderedXml.replace(/<cx:chart(\s|>)/, `${comments}<cx:chart$1`);
461
+ }
462
+ function shouldPassthroughChartEntry(entry) {
463
+ if (!entry.rawData || entry.dirty) {
464
+ return false;
465
+ }
466
+ if (entry.modelSnapshot === undefined) {
467
+ return true;
468
+ }
469
+ return snapshotChartModel(entry.model) === entry.modelSnapshot;
470
+ }
471
+ function shouldPassthroughChartExEntry(entry) {
472
+ if (!entry.rawData || entry.dirty) {
473
+ return false;
474
+ }
475
+ if (entry.modelSnapshot === undefined) {
476
+ return true;
477
+ }
478
+ return snapshotChartModel(entry.model) === entry.modelSnapshot;
479
+ }
480
+ function stripChartExRawXml(model) {
481
+ return { ...model, rawXml: undefined };
482
+ }
483
+ function isStrictTemplateMode(options) {
484
+ return options?.templateMode === "strict" || options?.strictTemplateMode === true;
485
+ }
486
+ /**
487
+ * Decide whether `writeBuffer` should run the OOXML self-check after
488
+ * producing bytes. Resolves the `validate` option against the current
489
+ * environment:
490
+ *
491
+ * - Explicit `true` / `false` → honoured as-is.
492
+ * - `undefined` (default) → `true` in non-production Node.js
493
+ * when NOT running under vitest. We
494
+ * detect vitest via `process.env.VITEST`
495
+ * to avoid adding multi-second
496
+ * validation overhead to fixture
497
+ * `beforeAll` hooks that produce
498
+ * hundreds of workbooks (the chartEx
499
+ * preset corpus alone builds ~100
500
+ * fixtures per run — at ~450 ms each
501
+ * that is a 45 s penalty on every full
502
+ * suite execution). Vitest tests that
503
+ * need validation call
504
+ * `expectValidXlsx()` explicitly.
505
+ * `false` in production and in the
506
+ * browser where `process` is absent.
507
+ *
508
+ * Kept as a module-level helper so the resolution rule is testable in
509
+ * isolation and so subclasses can override by passing an explicit
510
+ * `validate` flag rather than re-implementing the default logic.
511
+ */
512
+ function shouldAutoValidate(explicit) {
513
+ if (explicit !== undefined) {
514
+ return explicit;
515
+ }
516
+ // In the browser `process` is undefined; skip the overhead there.
517
+ if (typeof process === "undefined" || !process.env) {
518
+ return false;
519
+ }
520
+ if (process.env.NODE_ENV === "production") {
521
+ return false;
522
+ }
523
+ // Vitest sets VITEST=true automatically in its worker processes.
524
+ // Skip the auto-check there; tests opt-in via `expectValidXlsx`.
525
+ if (process.env.VITEST === "true") {
526
+ return false;
527
+ }
528
+ return true;
529
+ }
530
+ /**
531
+ * Run `validateXlsxBuffer` on writer output and emit a consolidated
532
+ * `console.warn` for every detected problem. Never throws: a validator
533
+ * exception is degraded to a warning so writers that intentionally
534
+ * produce non-conformant xlsx (e.g. for negative-path tests) keep
535
+ * working. The message includes the actionable opt-out so downstream
536
+ * consumers know how to silence it without grepping docs.
537
+ */
538
+ async function runWriteBufferSelfCheck(bytes) {
539
+ try {
540
+ const report = await (0, ooxml_validator_1.validateXlsxBuffer)(bytes, { maxProblems: 20 });
541
+ if (report.ok) {
542
+ return;
543
+ }
544
+ const summary = report.problems
545
+ .map((p, i) => ` ${i + 1}. [${p.kind}] ${p.file ?? "<package>"}: ${p.message}`)
546
+ .join("\n");
547
+ // eslint-disable-next-line no-console
548
+ console.warn(`[excelts] writeBuffer() produced xlsx with ${report.problems.length} OOXML issue(s):\n` +
549
+ `${summary}\n` +
550
+ `Pass \`{ validate: false }\` to silence this self-check, or set NODE_ENV=production.`);
551
+ }
552
+ catch (err) {
553
+ // eslint-disable-next-line no-console
554
+ console.warn(`[excelts] writeBuffer() self-check threw unexpectedly and was skipped: ${String(err)}`);
555
+ }
556
+ }
557
+ function hasChartEntryChanged(entry) {
558
+ if (!entry.rawData) {
559
+ return false;
560
+ }
561
+ if (entry.dirty) {
562
+ return true;
563
+ }
564
+ if (entry.modelSnapshot === undefined) {
565
+ return false;
566
+ }
567
+ return snapshotChartModel(entry.model) !== entry.modelSnapshot;
568
+ }
569
+ function hasChartExEntryChanged(entry) {
570
+ if (!entry.rawData) {
571
+ return false;
572
+ }
573
+ if (entry.dirty) {
574
+ return true;
575
+ }
576
+ if (entry.modelSnapshot === undefined) {
577
+ return false;
578
+ }
579
+ return snapshotChartModel(entry.model) !== entry.modelSnapshot;
580
+ }
581
+ function shouldRequireChartRawPatch(entry, strictTemplateMode) {
582
+ return !!entry.requireRawPatch || (strictTemplateMode && hasChartEntryChanged(entry));
583
+ }
584
+ function shouldRequireChartExRawPatch(entry, strictTemplateMode) {
585
+ return !!entry.requireRawPatch || (strictTemplateMode && hasChartExEntryChanged(entry));
586
+ }
587
+ /**
588
+ * Assemble the error message thrown when a loaded chartEx part cannot be
589
+ * raw-patched but the caller required it (either `requireRawPatch` on the
590
+ * entry or `strictTemplateMode` at the writer). Surfaces any unknown XML
591
+ * elements the parser noticed so the author can decide whether to relax
592
+ * the requirement or adjust the mutation shape.
593
+ */
594
+ function buildChartExStrictFailureMessage(entryName, model) {
595
+ const base = `ChartEx ${entryName} requires raw XML patching ` +
596
+ `(requireRawPatch/strict template mode), but the mutation cannot be safely applied as a raw XML patch.`;
597
+ const unknown = model?.unknownElements;
598
+ return appendUnknownElementsSummary(base, unknown);
599
+ }
600
+ /**
601
+ * Classic-chart counterpart of {@link buildChartExStrictFailureMessage}.
602
+ * Pulls `unknownElements` off the {@link ChartModel} so the same
603
+ * "you are about to silently drop these vendor extensions" warning is
604
+ * surfaced when strict template mode refuses a re-render.
605
+ */
606
+ function buildChartStrictFailureMessage(entryName, model) {
607
+ const base = `Chart ${entryName} requires raw XML patching ` +
608
+ `(requireRawPatch/strict template mode), but the mutation cannot be safely applied as a raw XML patch.`;
609
+ const unknown = model?.unknownElements;
610
+ return appendUnknownElementsSummary(base, unknown);
611
+ }
612
+ function appendUnknownElementsSummary(base, unknown) {
613
+ if (!unknown || unknown.length === 0) {
614
+ return base;
615
+ }
616
+ // De-duplicate by path; real files often repeat the same extension element
617
+ // across multiple series/axes and noise doesn't help diagnosis.
618
+ const uniquePaths = Array.from(new Set(unknown.map(entry => entry.path))).slice(0, 8);
619
+ const extra = unknown.length > uniquePaths.length
620
+ ? ` (showing ${uniquePaths.length} of ${unknown.length})`
621
+ : "";
622
+ return (`${base} The loaded part contains unstructured XML at: ${uniquePaths.join(", ")}${extra}. ` +
623
+ `Rebuilding the part would discard these extensions; adjust the mutation to a ` +
624
+ `patch-friendly shape or relax strictTemplateMode.`);
625
+ }
626
+ function tryPatchChartExRawXml(entry, forceRawPatch = false) {
627
+ if (!entry.rawData ||
628
+ (!entry.preferRawPatch && !forceRawPatch) ||
629
+ !hasChartExEntryChanged(entry)) {
630
+ return undefined;
631
+ }
632
+ const patchPlan = getChartExRawPatchPlan(entry);
633
+ if (patchPlan === undefined) {
634
+ return undefined;
635
+ }
636
+ const raw = new TextDecoder().decode(entry.rawData);
637
+ const chartRange = findXmlBlock(raw, "cx:chartSpace");
638
+ if (!chartRange) {
639
+ return undefined;
640
+ }
641
+ const chartBlock = raw.slice(chartRange.start, chartRange.end);
642
+ const patchedChartBlock = patchRawChartExChartBlock(chartBlock, entry.model, patchPlan);
643
+ if (patchedChartBlock === undefined) {
644
+ return undefined;
645
+ }
646
+ const patched = raw.slice(0, chartRange.start) + patchedChartBlock + raw.slice(chartRange.end);
647
+ return patched !== raw ? new TextEncoder().encode(patched) : undefined;
648
+ }
649
+ function getChartExRawPatchPlan(entry) {
650
+ if (entry.modelSnapshot === undefined) {
651
+ return {
652
+ title: true,
653
+ data: true,
654
+ legend: true,
655
+ autoTitleDeleted: true,
656
+ chartSpaceSpPr: true,
657
+ plotAreaSpPr: true,
658
+ plotSurface: true,
659
+ series: true,
660
+ axes: true
661
+ };
662
+ }
663
+ let previous;
664
+ try {
665
+ previous = JSON.parse(entry.modelSnapshot);
666
+ }
667
+ catch {
668
+ return undefined;
669
+ }
670
+ const current = entry.model;
671
+ if (!sameJson(stripPatchableChartExFields(previous), stripPatchableChartExFields(current))) {
672
+ return undefined;
673
+ }
674
+ const prevChart = previous.chartSpace?.chart;
675
+ const curChart = current.chartSpace?.chart;
676
+ const series = buildChartExSeriesRawPatchPlan(previous, current);
677
+ const axes = buildChartExAxisRawPatchPlan(prevChart?.plotArea?.axis ?? [], curChart?.plotArea?.axis ?? []);
678
+ const plan = {
679
+ data: !sameJson(previous.chartSpace?.chartData, current.chartSpace?.chartData),
680
+ title: !sameJson(prevChart?.title, curChart?.title),
681
+ legend: !sameJson(prevChart?.legend, curChart?.legend),
682
+ autoTitleDeleted: !sameJson(prevChart?.autoTitleDeleted, curChart?.autoTitleDeleted),
683
+ // Chart-frame styling lives on `CT_ChartSpace/spPr` in Chart2014,
684
+ // not on `CT_Chart`. Diff the correct slot; the `ChartExChart.spPr`
685
+ // field has been removed from the type.
686
+ chartSpaceSpPr: !sameJson(previous.chartSpace?.spPr, current.chartSpace?.spPr),
687
+ plotAreaSpPr: !sameJson(prevChart?.plotArea?.spPr, curChart?.plotArea?.spPr),
688
+ plotSurface: !sameJson(prevChart?.plotArea?.plotAreaRegion?.plotSurface, curChart?.plotArea?.plotAreaRegion?.plotSurface),
689
+ series,
690
+ axes
691
+ };
692
+ return plan.data ||
693
+ plan.title ||
694
+ plan.legend ||
695
+ plan.autoTitleDeleted ||
696
+ plan.chartSpaceSpPr ||
697
+ plan.plotAreaSpPr ||
698
+ plan.plotSurface ||
699
+ hasRawPatchListChanges(plan.series) ||
700
+ hasRawPatchListChanges(plan.axes)
701
+ ? plan
702
+ : undefined;
703
+ }
704
+ function buildChartExSeriesRawPatchPlan(previous, current) {
705
+ const previousSeries = extractChartExSeries(previous);
706
+ const currentSeries = extractChartExSeries(current);
707
+ return currentSeries.map((series, index) => {
708
+ const prev = previousSeries[index] ?? {};
709
+ return {
710
+ hidden: !sameJson(prev.hidden, series.hidden),
711
+ ownerIdx: !sameJson(prev.ownerIdx, series.ownerIdx),
712
+ tx: !sameJson(prev.tx, series.tx),
713
+ dataRefs: !sameJson(prev.dataRefs, series.dataRefs),
714
+ layoutPr: !sameJson(prev.layoutPr, series.layoutPr),
715
+ axisId: !sameJson(prev.axisId, series.axisId),
716
+ dataLabels: !sameJson(prev.dataLabels, series.dataLabels),
717
+ spPr: !sameJson(prev.spPr, series.spPr),
718
+ dataPoints: !sameJson(prev.dataPt, series.dataPt)
719
+ };
720
+ });
721
+ }
722
+ function buildChartExAxisRawPatchPlan(previousAxes, currentAxes) {
723
+ const previousById = new Map(previousAxes.map(axis => [axis.axisId, axis]));
724
+ return currentAxes.map(axis => {
725
+ const prev = previousById.get(axis.axisId) ?? {};
726
+ return {
727
+ hidden: !sameJson(prev.hidden, axis.hidden),
728
+ majorTickMark: !sameJson(prev.majorTickMark, axis.majorTickMark),
729
+ minorTickMark: !sameJson(prev.minorTickMark, axis.minorTickMark),
730
+ numFmt: !sameJson(prev.numFmt, axis.numFmt),
731
+ title: !sameJson(prev.title, axis.title),
732
+ valScaling: !sameJson(prev.valScaling, axis.valScaling),
733
+ catScaling: !sameJson(prev.catScaling, axis.catScaling),
734
+ spPr: !sameJson(prev.spPr, axis.spPr),
735
+ txPr: !sameJson(prev.txPr, axis.txPr)
736
+ };
737
+ });
738
+ }
739
+ function stripPatchableChartExFields(model) {
740
+ const clone = JSON.parse(JSON.stringify(model));
741
+ clone.rawXml = undefined;
742
+ // Vendor / extension metadata the parser recorded but the raw patcher
743
+ // does not rewrite. Letting them differ in the diff keeps
744
+ // `getChartExRawPatchPlan` from giving up on fast-path patches for
745
+ // loaded templates that carry c14/c15/c16 extensions. The patcher
746
+ // never touches these bytes, so the raw XML already preserves them
747
+ // verbatim.
748
+ clone.unknownElements = undefined;
749
+ if (clone.chartSpace) {
750
+ clone.chartSpace.chartData = undefined;
751
+ clone.chartSpace.clrMapOvr = undefined;
752
+ clone.chartSpace.extLst = undefined;
753
+ }
754
+ if (clone.chartSpace?.chart) {
755
+ clone.chartSpace.chart.title = undefined;
756
+ clone.chartSpace.chart.legend = undefined;
757
+ clone.chartSpace.chart.autoTitleDeleted = undefined;
758
+ // NOTE: `chart.spPr` was previously cleared here, but the field
759
+ // has been removed from `ChartExChart` (see the migration in
760
+ // chart-ex-parser); the writer now emits chart-frame styling from
761
+ // `chartSpace.spPr` only.
762
+ if (clone.chartSpace.chart.plotArea) {
763
+ clone.chartSpace.chart.plotArea.spPr = undefined;
764
+ clone.chartSpace.chart.plotArea.axis = undefined;
765
+ if (clone.chartSpace.chart.plotArea.plotAreaRegion) {
766
+ clone.chartSpace.chart.plotArea.plotAreaRegion.layout = undefined;
767
+ clone.chartSpace.chart.plotArea.plotAreaRegion.plotSurface = undefined;
768
+ clone.chartSpace.chart.plotArea.plotAreaRegion.series = (clone.chartSpace.chart.plotArea.plotAreaRegion.series ?? []).map(stripPatchableChartExSeriesFields);
769
+ }
770
+ if (clone.chartSpace.chart.plotArea.series) {
771
+ clone.chartSpace.chart.plotArea.series = clone.chartSpace.chart.plotArea.series.map(stripPatchableChartExSeriesFields);
772
+ }
773
+ }
774
+ }
775
+ return clone;
776
+ }
777
+ function stripPatchableChartExSeriesFields(series) {
778
+ return {
779
+ ...series,
780
+ hidden: undefined,
781
+ ownerIdx: undefined,
782
+ tx: undefined,
783
+ spPr: undefined,
784
+ dataRefs: undefined,
785
+ layoutPr: undefined,
786
+ axisId: undefined,
787
+ dataLabels: undefined,
788
+ dataPt: undefined
789
+ };
790
+ }
791
+ function extractChartExSeries(model) {
792
+ const plotArea = model.chartSpace?.chart?.plotArea;
793
+ return plotArea?.plotAreaRegion?.series ?? plotArea?.series ?? [];
794
+ }
795
+ function patchRawChartExChartBlock(block, model, patchPlan) {
796
+ let patched = block;
797
+ const chart = model.chartSpace?.chart;
798
+ if (!chart) {
799
+ return undefined;
800
+ }
801
+ if (patchPlan.data) {
802
+ const dataRange = findXmlBlock(patched, "cx:chartData");
803
+ if (!dataRange) {
804
+ return undefined;
805
+ }
806
+ const dataXml = buildRawChartExDataXml(model.chartSpace?.chartData);
807
+ patched = patched.slice(0, dataRange.start) + dataXml + patched.slice(dataRange.end);
808
+ }
809
+ if (patchPlan.title) {
810
+ const titleText = chart.title?.text?.paragraphs?.[0]?.runs?.[0]?.text;
811
+ patched =
812
+ titleText !== undefined
813
+ ? replaceOrInsertBeforeGeneric(patched, "cx:title", buildRawChartExTitleXml(titleText), ["cx:autoTitleDeleted", "cx:plotArea", "cx:legend", "cx:spPr"], "cx:chart")
814
+ : removeXmlBlock(patched, "cx:title");
815
+ }
816
+ if (patchPlan.autoTitleDeleted) {
817
+ patched =
818
+ chart.autoTitleDeleted !== undefined
819
+ ? replaceOrInsertBeforeGeneric(patched, "cx:autoTitleDeleted", `<cx:autoTitleDeleted val="${chart.autoTitleDeleted ? "1" : "0"}"/>`, ["cx:plotArea", "cx:legend", "cx:spPr"], "cx:chart")
820
+ : removeXmlBlock(patched, "cx:autoTitleDeleted");
821
+ }
822
+ if (patchPlan.legend) {
823
+ patched =
824
+ chart.legend !== undefined
825
+ ? replaceOrInsertBeforeGeneric(patched, "cx:legend", buildRawChartExLegendXml(chart.legend), ["cx:spPr"], "cx:chart")
826
+ : removeXmlBlock(patched, "cx:legend");
827
+ }
828
+ if (patchPlan.chartSpaceSpPr) {
829
+ // Target `<cx:chartSpace>` (the root element) rather than
830
+ // `<cx:chart>`. Chart-frame styling belongs on the chartSpace
831
+ // parent per Chart2014; previous versions of this patcher
832
+ // incorrectly wrote it inside `<cx:chart>`, producing output
833
+ // strict validators reject. The siblings list is CT_ChartSpace's
834
+ // child order after `cx:chart`: `cx:spPr, cx:txPr, cx:externalData,
835
+ // cx:printSettings, cx:extLst`.
836
+ patched = patchGenericChild(patched, "cx:spPr", buildRawShapePropertiesXml(model.chartSpace?.spPr, "cx"), ["cx:txPr", "cx:externalData", "cx:printSettings", "cx:extLst"], "cx:chartSpace");
837
+ }
838
+ if (patchPlan.plotAreaSpPr || patchPlan.plotSurface) {
839
+ const plotRange = findXmlBlock(patched, "cx:plotArea");
840
+ if (!plotRange) {
841
+ return undefined;
842
+ }
843
+ let plotBlock = patched.slice(plotRange.start, plotRange.end);
844
+ const plotArea = chart.plotArea;
845
+ if (patchPlan.plotAreaSpPr) {
846
+ // `CT_PlotArea` sequence: `plotAreaRegion?` → `axis*` → `spPr?` →
847
+ // `extLst?`. `spPr` is the next-to-last child, so its only
848
+ // follower is `extLst`.
849
+ plotBlock = patchGenericChild(plotBlock, "cx:spPr", buildRawShapePropertiesXml(plotArea?.spPr, "cx"), ["cx:extLst"], "cx:plotArea");
850
+ }
851
+ if (patchPlan.plotSurface) {
852
+ // `CT_PlotAreaRegion` (Chart2014): `plotSurface?` → `series*` →
853
+ // `extLst?`. The `spPr` is a child of `<cx:plotSurface>`, NOT a
854
+ // direct child of `<cx:plotAreaRegion>`. Previously the raw
855
+ // patcher wrote a bare `<cx:spPr>` under `<cx:plotAreaRegion>`
856
+ // (schema violation) and also had a separate
857
+ // `plotAreaRegionLayout` patch that emitted `<cx:layout>`
858
+ // there (also invalid — layout only lives on `<cx:plotArea>` /
859
+ // `<cx:title>` via the manualLayout extension).
860
+ //
861
+ // The correct form is:
862
+ // <cx:plotAreaRegion>
863
+ // <cx:plotSurface>
864
+ // <cx:spPr>…</cx:spPr>
865
+ // </cx:plotSurface>
866
+ // <cx:series/>
867
+ // …
868
+ // </cx:plotAreaRegion>
869
+ const regionRange = findXmlBlock(plotBlock, "cx:plotAreaRegion");
870
+ if (!regionRange) {
871
+ return undefined;
872
+ }
873
+ let regionBlock = plotBlock.slice(regionRange.start, regionRange.end);
874
+ const region = plotArea?.plotAreaRegion;
875
+ const surfaceSpPrXml = buildRawShapePropertiesXml(region?.plotSurface, "cx");
876
+ const plotSurfaceXml = surfaceSpPrXml
877
+ ? `<cx:plotSurface>${surfaceSpPrXml}</cx:plotSurface>`
878
+ : undefined;
879
+ regionBlock = patchGenericChild(regionBlock, "cx:plotSurface", plotSurfaceXml, ["cx:series", "cx:extLst"], "cx:plotAreaRegion");
880
+ plotBlock =
881
+ plotBlock.slice(0, regionRange.start) + regionBlock + plotBlock.slice(regionRange.end);
882
+ }
883
+ patched = patched.slice(0, plotRange.start) + plotBlock + patched.slice(plotRange.end);
884
+ }
885
+ if (hasRawPatchListChanges(patchPlan.series)) {
886
+ const next = patchRawChartExSeries(patched, chart, patchPlan);
887
+ if (next === undefined) {
888
+ return undefined;
889
+ }
890
+ patched = next;
891
+ }
892
+ if (hasRawPatchListChanges(patchPlan.axes)) {
893
+ const next = patchRawChartExAxes(patched, chart, patchPlan.axes);
894
+ if (next === undefined) {
895
+ return undefined;
896
+ }
897
+ patched = next;
898
+ }
899
+ return patched;
900
+ }
901
+ function tryPatchChartRawXml(entry, forceRawPatch = false) {
902
+ if (!entry.rawData || (!entry.preferRawPatch && !forceRawPatch) || !hasChartEntryChanged(entry)) {
903
+ return undefined;
904
+ }
905
+ const patchPlan = getChartRawPatchPlan(entry);
906
+ if (patchPlan === undefined) {
907
+ return undefined;
908
+ }
909
+ const raw = new TextDecoder().decode(entry.rawData);
910
+ let patched = raw;
911
+ if (patchPlan.title) {
912
+ const titleText = entry.model.chart?.title?.text?.paragraphs?.[0]?.runs?.[0]?.text;
913
+ const hasTitle = /<c:title>[\s\S]*?<\/c:title>/.test(patched);
914
+ if (titleText !== undefined && hasTitle) {
915
+ patched = patched.replace(/<c:title>[\s\S]*?<\/c:title>/, buildRawChartTitleXml(titleText));
916
+ }
917
+ else if (titleText === undefined && hasTitle) {
918
+ patched = patched.replace(/<c:title>[\s\S]*?<\/c:title>/, "");
919
+ }
920
+ }
921
+ if (patchPlan.legend) {
922
+ const legend = entry.model.chart?.legend;
923
+ if (legend === undefined) {
924
+ patched = patched.replace(/<c:legend>[\s\S]*?<\/c:legend>/, "");
925
+ }
926
+ else if (/<c:legend>[\s\S]*?<\/c:legend>/.test(patched)) {
927
+ patched = patched.replace(/<c:legend>[\s\S]*?<\/c:legend>/, buildRawChartLegendXml(legend.legendPos ?? "b"));
928
+ }
929
+ }
930
+ if (hasRawPatchListChanges(patchPlan.series)) {
931
+ const next = patchRawSeries(patched, entry.model, patchPlan.series);
932
+ if (next === undefined) {
933
+ return undefined;
934
+ }
935
+ patched = next;
936
+ }
937
+ if (patchPlan.groupDataLabels) {
938
+ const next = patchRawChartGroupDataLabels(patched, entry.model);
939
+ if (next === undefined) {
940
+ return undefined;
941
+ }
942
+ patched = next;
943
+ }
944
+ if (patchPlan.groupSimpleFields) {
945
+ const next = patchRawChartGroupSimpleFields(patched, entry.model);
946
+ if (next === undefined) {
947
+ return undefined;
948
+ }
949
+ patched = next;
950
+ }
951
+ if (patchPlan.plotAreaLayout) {
952
+ const next = patchRawPlotAreaLayout(patched, entry.model);
953
+ if (next === undefined) {
954
+ return undefined;
955
+ }
956
+ patched = next;
957
+ }
958
+ if (hasRawPatchListChanges(patchPlan.axes)) {
959
+ const next = patchRawAxes(patched, entry.model, patchPlan.axes);
960
+ if (next === undefined) {
961
+ return undefined;
962
+ }
963
+ patched = next;
964
+ }
965
+ return patched !== raw ? new TextEncoder().encode(patched) : undefined;
966
+ }
967
+ function getChartRawPatchPlan(entry) {
968
+ if (entry.modelSnapshot === undefined) {
969
+ return {
970
+ title: true,
971
+ legend: true,
972
+ series: true,
973
+ axes: true,
974
+ groupDataLabels: true,
975
+ groupSimpleFields: true,
976
+ plotAreaLayout: true
977
+ };
978
+ }
979
+ let previous;
980
+ try {
981
+ previous = JSON.parse(entry.modelSnapshot);
982
+ }
983
+ catch {
984
+ return undefined;
985
+ }
986
+ const current = entry.model;
987
+ const prevChart = previous.chart;
988
+ const curChart = current.chart;
989
+ const plan = {
990
+ title: false,
991
+ legend: false,
992
+ series: buildChartSeriesRawPatchPlan(previous, current),
993
+ axes: buildChartAxisRawPatchPlan(prevChart?.plotArea?.axes ?? [], curChart?.plotArea?.axes ?? []),
994
+ groupDataLabels: false,
995
+ groupSimpleFields: false,
996
+ plotAreaLayout: false
997
+ };
998
+ const curWithoutPatchable = stripPatchableChartFields(current);
999
+ const prevWithoutPatchable = stripPatchableChartFields(previous);
1000
+ if (!sameJson(curWithoutPatchable, prevWithoutPatchable)) {
1001
+ return undefined;
1002
+ }
1003
+ plan.title = plan.title || !sameJson(prevChart?.title, curChart?.title);
1004
+ plan.legend = plan.legend || !sameJson(prevChart?.legend, curChart?.legend);
1005
+ plan.groupDataLabels = !sameJson(extractPatchableGroupDataLabels(previous), extractPatchableGroupDataLabels(current));
1006
+ plan.groupSimpleFields = !sameJson(extractSimpleGroupFields(previous), extractSimpleGroupFields(current));
1007
+ plan.plotAreaLayout = !sameJson(prevChart?.plotArea?.layout, curChart?.plotArea?.layout);
1008
+ return plan.title ||
1009
+ plan.legend ||
1010
+ hasRawPatchListChanges(plan.series) ||
1011
+ hasRawPatchListChanges(plan.axes) ||
1012
+ plan.groupDataLabels ||
1013
+ plan.groupSimpleFields ||
1014
+ plan.plotAreaLayout
1015
+ ? plan
1016
+ : undefined;
1017
+ }
1018
+ function stripPatchableChartFields(model) {
1019
+ const clone = JSON.parse(JSON.stringify(model));
1020
+ // Top-level fields that tryPatchChartRawXml does not rewrite. Allowing
1021
+ // them to differ between `previous` and `current` means a caller can
1022
+ // load a template that carries c14/c15/c16 extension XML, edit a
1023
+ // title or legend, and still take the fast raw-patch path — without
1024
+ // this the extLst JSON shape shifts (e.g. empty string `""` vs
1025
+ // `undefined` after a round-trip) and the plan gets rejected.
1026
+ //
1027
+ // We deliberately do NOT strip `pivotOptions`: it is structurally
1028
+ // parsed, and the raw patcher has no branch to replay a mutation
1029
+ // into the XML. Keeping it out of the whitelist forces a rebuild so
1030
+ // the user's change is honoured.
1031
+ clone.extLst = undefined;
1032
+ clone.unknownElements = undefined;
1033
+ clone.extraNamespaces = undefined;
1034
+ clone.alternateContentStyle = undefined;
1035
+ clone.clrMapOvr = undefined;
1036
+ clone.protection = undefined;
1037
+ if (clone.chart) {
1038
+ clone.chart.title = undefined;
1039
+ clone.chart.legend = undefined;
1040
+ clone.chart.extLst = undefined;
1041
+ if (clone.chart.plotArea) {
1042
+ clone.chart.plotArea.axes = (clone.chart.plotArea.axes ?? []).map(stripPatchableAxisFields);
1043
+ clone.chart.plotArea.layout = undefined;
1044
+ clone.chart.plotArea.extLst = undefined;
1045
+ for (const group of clone.chart.plotArea.chartTypes ?? []) {
1046
+ group.dataLabels = undefined;
1047
+ group.extLst = undefined;
1048
+ // Simple leaf fields the `patchRawChartGroupSimpleFields`
1049
+ // branch rewrites in place (see `SIMPLE_GROUP_FIELD_TAGS`).
1050
+ // Stripping them from the baseline diff is what makes the
1051
+ // "edit `overlap` then write" path take the fast raw-patch
1052
+ // route instead of a full structural rebuild. Every field
1053
+ // listed here must have a matching entry in
1054
+ // `SIMPLE_GROUP_FIELD_TAGS` — the two are kept symmetric.
1055
+ for (const field of SIMPLE_GROUP_FIELD_NAMES) {
1056
+ group[field] = undefined;
1057
+ }
1058
+ group.series = (group.series ?? []).map((series) => ({
1059
+ ...series,
1060
+ tx: undefined,
1061
+ spPr: undefined,
1062
+ marker: undefined,
1063
+ dataPoints: undefined,
1064
+ trendlines: undefined,
1065
+ errorBars: undefined,
1066
+ cat: undefined,
1067
+ val: undefined,
1068
+ xVal: undefined,
1069
+ yVal: undefined,
1070
+ bubbleSize: undefined,
1071
+ dataLabels: undefined,
1072
+ extLst: undefined
1073
+ }));
1074
+ }
1075
+ }
1076
+ }
1077
+ return clone;
1078
+ }
1079
+ function stripPatchableAxisFields(axis) {
1080
+ return {
1081
+ ...axis,
1082
+ scaling: undefined,
1083
+ delete: undefined,
1084
+ majorGridlines: undefined,
1085
+ minorGridlines: undefined,
1086
+ title: undefined,
1087
+ numFmt: undefined,
1088
+ majorTickMark: undefined,
1089
+ minorTickMark: undefined,
1090
+ tickLblPos: undefined,
1091
+ spPr: undefined,
1092
+ txPr: undefined,
1093
+ crosses: undefined,
1094
+ crossesAt: undefined,
1095
+ auto: undefined,
1096
+ lblAlgn: undefined,
1097
+ lblOffset: undefined,
1098
+ tickLblSkip: undefined,
1099
+ tickMarkSkip: undefined,
1100
+ noMultiLvlLbl: undefined,
1101
+ crossBetween: undefined,
1102
+ majorUnit: undefined,
1103
+ minorUnit: undefined,
1104
+ baseTimeUnit: undefined,
1105
+ majorTimeUnit: undefined,
1106
+ minorTimeUnit: undefined,
1107
+ // `c:extLst` on an axis is always raw XML passthrough in the
1108
+ // structural parser; freezing it out of the diff lets template
1109
+ // edits (scaling / gridlines / title) take the fast raw-patch
1110
+ // path when the template happens to carry c15:axisTitleExtLst or
1111
+ // similar vendor ext markers.
1112
+ extLst: undefined
1113
+ };
1114
+ }
1115
+ function extractPatchableGroupDataLabels(model) {
1116
+ return (model.chart?.plotArea?.chartTypes ?? []).map((group) => group.dataLabels);
1117
+ }
1118
+ function buildChartSeriesRawPatchPlan(previous, current) {
1119
+ const previousSeries = flattenChartSeries(previous);
1120
+ return flattenChartSeries(current).map((series, index) => {
1121
+ const prev = previousSeries[index] ?? {};
1122
+ return {
1123
+ tx: !sameJson(prev.tx, series.tx),
1124
+ spPr: !sameJson(prev.spPr, series.spPr),
1125
+ marker: !sameJson(prev.marker, series.marker),
1126
+ dataPoints: !sameJson(prev.dataPoints, series.dataPoints),
1127
+ trendlines: !sameJson(prev.trendlines, series.trendlines),
1128
+ errorBars: !sameJson(prev.errorBars, series.errorBars),
1129
+ cat: !sameJson(prev.cat, series.cat),
1130
+ val: !sameJson(prev.val, series.val),
1131
+ xVal: !sameJson(prev.xVal, series.xVal),
1132
+ yVal: !sameJson(prev.yVal, series.yVal),
1133
+ bubbleSize: !sameJson(prev.bubbleSize, series.bubbleSize),
1134
+ dataLabels: !sameJson(prev.dataLabels, series.dataLabels)
1135
+ };
1136
+ });
1137
+ }
1138
+ function buildChartAxisRawPatchPlan(previousAxes, currentAxes) {
1139
+ const previousById = new Map(previousAxes.map(axis => [axis.axId, axis]));
1140
+ return currentAxes.map(axis => {
1141
+ const prev = previousById.get(axis.axId) ?? {};
1142
+ return {
1143
+ scaling: !sameJson(prev.scaling, axis.scaling),
1144
+ delete: !sameJson(prev.delete, axis.delete),
1145
+ title: !sameJson(prev.title, axis.title),
1146
+ numFmt: !sameJson(prev.numFmt, axis.numFmt),
1147
+ majorGridlines: !sameJson(prev.majorGridlines, axis.majorGridlines),
1148
+ minorGridlines: !sameJson(prev.minorGridlines, axis.minorGridlines),
1149
+ majorTickMark: !sameJson(prev.majorTickMark, axis.majorTickMark),
1150
+ minorTickMark: !sameJson(prev.minorTickMark, axis.minorTickMark),
1151
+ tickLblPos: !sameJson(prev.tickLblPos, axis.tickLblPos),
1152
+ spPr: !sameJson(prev.spPr, axis.spPr),
1153
+ txPr: !sameJson(prev.txPr, axis.txPr),
1154
+ crosses: !sameJson(prev.crosses, axis.crosses),
1155
+ crossesAt: !sameJson(prev.crossesAt, axis.crossesAt),
1156
+ auto: !sameJson(prev.auto, axis.auto),
1157
+ lblAlgn: !sameJson(prev.lblAlgn, axis.lblAlgn),
1158
+ lblOffset: !sameJson(prev.lblOffset, axis.lblOffset),
1159
+ tickLblSkip: !sameJson(prev.tickLblSkip, axis.tickLblSkip),
1160
+ tickMarkSkip: !sameJson(prev.tickMarkSkip, axis.tickMarkSkip),
1161
+ noMultiLvlLbl: !sameJson(prev.noMultiLvlLbl, axis.noMultiLvlLbl),
1162
+ crossBetween: !sameJson(prev.crossBetween, axis.crossBetween),
1163
+ majorUnit: !sameJson(prev.majorUnit, axis.majorUnit),
1164
+ minorUnit: !sameJson(prev.minorUnit, axis.minorUnit),
1165
+ baseTimeUnit: !sameJson(prev.baseTimeUnit, axis.baseTimeUnit),
1166
+ majorTimeUnit: !sameJson(prev.majorTimeUnit, axis.majorTimeUnit),
1167
+ minorTimeUnit: !sameJson(prev.minorTimeUnit, axis.minorTimeUnit)
1168
+ };
1169
+ });
1170
+ }
1171
+ function flattenChartSeries(model) {
1172
+ return (model.chart?.plotArea?.chartTypes ?? []).flatMap((group) => (group.series ?? []).map((series) => ({
1173
+ tx: series.tx,
1174
+ spPr: series.spPr,
1175
+ marker: series.marker,
1176
+ dataPoints: series.dataPoints,
1177
+ trendlines: series.trendlines,
1178
+ errorBars: series.errorBars,
1179
+ cat: series.cat,
1180
+ val: series.val,
1181
+ xVal: series.xVal,
1182
+ yVal: series.yVal,
1183
+ bubbleSize: series.bubbleSize,
1184
+ dataLabels: series.dataLabels
1185
+ })));
1186
+ }
1187
+ function sameJson(left, right) {
1188
+ return JSON.stringify(left) === JSON.stringify(right);
1189
+ }
1190
+ function hasRawPatchListChanges(plan) {
1191
+ return (plan === true || (Array.isArray(plan) && plan.some(item => Object.values(item).some(Boolean))));
1192
+ }
1193
+ function getRawPatchListItem(plan, index) {
1194
+ return plan === true ? true : Array.isArray(plan) ? (plan[index] ?? false) : false;
1195
+ }
1196
+ function rawPatchFlag(plan, key) {
1197
+ if (plan === true) {
1198
+ return true;
1199
+ }
1200
+ if (plan === false) {
1201
+ return false;
1202
+ }
1203
+ return Boolean(plan[key]);
1204
+ }
1205
+ function buildRawChartTitleXml(text) {
1206
+ // Full text escape (strips C0 control characters beyond `\t\n\r`,
1207
+ // encodes the five reserved entities) so injected titles can't break
1208
+ // out of the `<a:t>` element. Matches the `escapeXml` helper used
1209
+ // elsewhere in this module.
1210
+ const escaped = escapeXml(text);
1211
+ return `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${escaped}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title>`;
1212
+ }
1213
+ function buildRawChartLegendXml(pos) {
1214
+ // Escape the attribute value — `pos` is typed as `LegendPosition`
1215
+ // (a 5-member enum) but the raw-patch path can't enforce that
1216
+ // statically, so a malicious or buggy caller could inject XML via
1217
+ // the attribute. Narrow to the enum set so truly unexpected values
1218
+ // fall back to the schema default `"b"` instead of being echoed
1219
+ // through verbatim.
1220
+ const safe = pos === "b" || pos === "l" || pos === "r" || pos === "t" || pos === "tr" ? pos : "b";
1221
+ return `<c:legend><c:legendPos val="${safe}"/><c:overlay val="0"/></c:legend>`;
1222
+ }
1223
+ function buildRawChartExTitleXml(text) {
1224
+ return `<cx:title><cx:tx><cx:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${escapeXml(text)}</a:t></a:r></a:p></cx:rich></cx:tx><cx:overlay val="0"/></cx:title>`;
1225
+ }
1226
+ function buildRawChartExLegendXml(legend) {
1227
+ // Delegate to the structured ChartEx writer so the raw-patch path
1228
+ // produces a byte-identical serialisation. Previously this function
1229
+ // hand-rolled a self-closing `<cx:legend pos="…" overlay="…"/>`,
1230
+ // silently dropping `align`, `legendEntry*`, `spPr`, `txPr`, and
1231
+ // `extLst` on every styled-legend round-trip. Sharing the writer
1232
+ // guarantees parity with the non-raw path.
1233
+ // Indentation differs from the structured writer's formatted output —
1234
+ // the raw patcher inserts into an inline stream, so strip the
1235
+ // leading indent that `renderChartExLegendXml` prefixes each line
1236
+ // with. The result is semantically identical; just flattened.
1237
+ return (0, chart_host_registry_1.getChartSupport)()
1238
+ .renderChartExLegendXml(legend)
1239
+ .split("\n")
1240
+ .map(line => line.replace(/^\s*/, ""))
1241
+ .join("");
1242
+ }
1243
+ function buildRawChartExDataXml(chartData) {
1244
+ const parts = ["<cx:chartData>"];
1245
+ // NOTE: `cx:externalData` is a child of `cx:chartSpace` per
1246
+ // Chart2014's `CT_ChartSpace`, NOT of `cx:chartData`. The raw
1247
+ // patcher used to emit it here — that produced a document Office
1248
+ // rejected in strict mode. The structured writer and raw-patch
1249
+ // paths now both emit externalData at the chartSpace level; any
1250
+ // legacy `chartData.externalData` is migrated to
1251
+ // `chartSpace.externalData` at parse time so the deprecated slot
1252
+ // can be ignored here without data loss.
1253
+ for (const entry of chartData?.data ?? []) {
1254
+ parts.push(`<cx:data id="${entry.id}">`);
1255
+ if (entry.strDim) {
1256
+ parts.push(buildRawChartExStringDimensionXml(entry.strDim));
1257
+ }
1258
+ if (entry.numDim) {
1259
+ parts.push(buildRawChartExNumericDimensionXml(entry.numDim));
1260
+ }
1261
+ parts.push("</cx:data>");
1262
+ }
1263
+ parts.push("</cx:chartData>");
1264
+ return parts.join("");
1265
+ }
1266
+ function buildRawChartExStringDimensionXml(dim) {
1267
+ const parts = [`<cx:strDim type="${escapeAttr(dim.type)}">`];
1268
+ if (dim.formula) {
1269
+ parts.push(`<cx:f>${escapeXml(dim.formula)}</cx:f>`);
1270
+ }
1271
+ for (const level of dim.levels ?? []) {
1272
+ const ptCount = level.ptCount ?? level.points?.length ?? 0;
1273
+ if (!level.points?.length) {
1274
+ parts.push(`<cx:lvl ptCount="${ptCount}"/>`);
1275
+ }
1276
+ else {
1277
+ parts.push(`<cx:lvl ptCount="${ptCount}">`);
1278
+ for (const point of level.points) {
1279
+ parts.push(`<cx:pt idx="${point.index}">${escapeXml(String(point.value))}</cx:pt>`);
1280
+ }
1281
+ parts.push("</cx:lvl>");
1282
+ }
1283
+ }
1284
+ parts.push("</cx:strDim>");
1285
+ return parts.join("");
1286
+ }
1287
+ function buildRawChartExNumericDimensionXml(dim) {
1288
+ const parts = [`<cx:numDim type="${escapeAttr(dim.type)}">`];
1289
+ if (dim.formula) {
1290
+ parts.push(`<cx:f>${escapeXml(dim.formula)}</cx:f>`);
1291
+ }
1292
+ for (const level of dim.levels ?? []) {
1293
+ const ptCount = level.ptCount ?? level.points?.length ?? 0;
1294
+ const fmt = level.formatCode ? ` formatCode="${escapeAttr(level.formatCode)}"` : "";
1295
+ if (!level.points?.length) {
1296
+ parts.push(`<cx:lvl ptCount="${ptCount}"${fmt}/>`);
1297
+ }
1298
+ else {
1299
+ parts.push(`<cx:lvl ptCount="${ptCount}"${fmt}>`);
1300
+ for (const point of level.points) {
1301
+ parts.push(`<cx:pt idx="${point.index}">${escapeXml(String(point.value))}</cx:pt>`);
1302
+ }
1303
+ parts.push("</cx:lvl>");
1304
+ }
1305
+ }
1306
+ parts.push("</cx:numDim>");
1307
+ return parts.join("");
1308
+ }
1309
+ function patchRawSeries(raw, model, patchPlan) {
1310
+ // Track the owning chart-type group for each series so doughnut
1311
+ // series can suppress `c:dLblPos` when writing `<c:dLbls>` — Excel
1312
+ // rejects that element on doughnut charts (see
1313
+ // `_renderDoughnutChart` in `chart-space-xform.ts`).
1314
+ const seriesEntries = [];
1315
+ for (const group of model.chart?.plotArea?.chartTypes ?? []) {
1316
+ for (const series of group.series ?? []) {
1317
+ seriesEntries.push({ series, chartType: group.type });
1318
+ }
1319
+ }
1320
+ let index = 0;
1321
+ return replaceXmlBlocks(raw, "c:ser", block => {
1322
+ const entry = seriesEntries[index++];
1323
+ const seriesPlan = getRawPatchListItem(patchPlan, index - 1);
1324
+ return entry && seriesPlan
1325
+ ? patchRawSeriesBlock(block, entry.series, seriesPlan, entry.chartType)
1326
+ : block;
1327
+ });
1328
+ }
1329
+ function patchRawSeriesBlock(block, series, patchPlan, chartType) {
1330
+ let patched = block;
1331
+ if (rawPatchFlag(patchPlan, "tx") && series.tx) {
1332
+ const txXml = buildRawSeriesTxXml(series.tx);
1333
+ patched = replaceOrInsertBefore(patched, "c:tx", txXml, [
1334
+ "c:spPr",
1335
+ "c:cat",
1336
+ "c:xVal",
1337
+ "c:val",
1338
+ "c:yVal",
1339
+ "c:bubbleSize"
1340
+ ]);
1341
+ }
1342
+ if (rawPatchFlag(patchPlan, "spPr")) {
1343
+ patched = patchGenericChild(patched, "c:spPr", buildRawShapePropertiesXml(series.spPr, "c"), [
1344
+ "c:marker",
1345
+ "c:invertIfNegative",
1346
+ "c:pictureOptions",
1347
+ "c:dPt",
1348
+ "c:dLbls",
1349
+ "c:trendline",
1350
+ "c:errBars",
1351
+ "c:cat",
1352
+ "c:xVal",
1353
+ "c:val",
1354
+ "c:yVal",
1355
+ "c:bubbleSize",
1356
+ "c:smooth",
1357
+ "c:shape",
1358
+ "c:extLst"
1359
+ ], "c:ser");
1360
+ }
1361
+ if (rawPatchFlag(patchPlan, "marker")) {
1362
+ patched = patchGenericChild(patched, "c:marker", buildRawMarkerXml(series.marker), [
1363
+ "c:dPt",
1364
+ "c:dLbls",
1365
+ "c:trendline",
1366
+ "c:errBars",
1367
+ "c:cat",
1368
+ "c:xVal",
1369
+ "c:val",
1370
+ "c:yVal",
1371
+ "c:bubbleSize",
1372
+ "c:smooth",
1373
+ "c:extLst"
1374
+ ], "c:ser");
1375
+ }
1376
+ for (const [tag, source] of [
1377
+ ["c:cat", series.cat],
1378
+ ["c:val", series.val],
1379
+ ["c:xVal", series.xVal],
1380
+ ["c:yVal", series.yVal],
1381
+ ["c:bubbleSize", series.bubbleSize]
1382
+ ]) {
1383
+ if (rawPatchFlag(patchPlan, chartSeriesPatchKeyForDataTag(tag))) {
1384
+ if (!source) {
1385
+ patched = removeXmlBlock(patched, tag);
1386
+ continue;
1387
+ }
1388
+ const dataXml = buildRawDataSourceXml(tag, source);
1389
+ if (!dataXml) {
1390
+ return undefined;
1391
+ }
1392
+ patched = replaceOrInsertBefore(patched, tag, dataXml, ["c:smooth", "c:shape", "c:extLst"]);
1393
+ }
1394
+ }
1395
+ if (rawPatchFlag(patchPlan, "dataPoints")) {
1396
+ const dataPointsXml = buildRawDataPointsXml(series.dataPoints);
1397
+ patched = patchRepeatingChildren(patched, "c:dPt", dataPointsXml, [
1398
+ "c:dLbls",
1399
+ "c:trendline",
1400
+ "c:errBars",
1401
+ "c:cat",
1402
+ "c:xVal",
1403
+ "c:val",
1404
+ "c:yVal",
1405
+ "c:bubbleSize",
1406
+ "c:smooth",
1407
+ "c:shape",
1408
+ "c:extLst"
1409
+ ], "c:ser");
1410
+ }
1411
+ if (rawPatchFlag(patchPlan, "trendlines")) {
1412
+ const trendlinesXml = buildRawTrendlinesXml(series.trendlines);
1413
+ patched = patchRepeatingChildren(patched, "c:trendline", trendlinesXml, [
1414
+ "c:errBars",
1415
+ "c:cat",
1416
+ "c:xVal",
1417
+ "c:val",
1418
+ "c:yVal",
1419
+ "c:bubbleSize",
1420
+ "c:smooth",
1421
+ "c:shape",
1422
+ "c:extLst"
1423
+ ], "c:ser");
1424
+ }
1425
+ if (rawPatchFlag(patchPlan, "errorBars")) {
1426
+ const errorBarsXml = buildRawErrorBarsXml(series.errorBars);
1427
+ patched = patchRepeatingChildren(patched, "c:errBars", errorBarsXml, ["c:cat", "c:xVal", "c:val", "c:yVal", "c:bubbleSize", "c:smooth", "c:shape", "c:extLst"], "c:ser");
1428
+ }
1429
+ if (rawPatchFlag(patchPlan, "dataLabels")) {
1430
+ if (series.dataLabels) {
1431
+ patched = replaceOrInsertBefore(patched, "c:dLbls", buildRawDataLabelsXml(series.dataLabels, { suppressDLblPos: chartType === "doughnut" }), [
1432
+ "c:trendline",
1433
+ "c:errBars",
1434
+ "c:cat",
1435
+ "c:xVal",
1436
+ "c:val",
1437
+ "c:yVal",
1438
+ "c:bubbleSize",
1439
+ "c:smooth",
1440
+ "c:shape",
1441
+ "c:extLst"
1442
+ ]);
1443
+ }
1444
+ else {
1445
+ patched = patched.replace(/<c:dLbls>[\s\S]*?<\/c:dLbls>/, "");
1446
+ }
1447
+ }
1448
+ return patched;
1449
+ }
1450
+ function chartSeriesPatchKeyForDataTag(tag) {
1451
+ switch (tag) {
1452
+ case "c:cat":
1453
+ return "cat";
1454
+ case "c:val":
1455
+ return "val";
1456
+ case "c:xVal":
1457
+ return "xVal";
1458
+ case "c:yVal":
1459
+ return "yVal";
1460
+ default:
1461
+ return "bubbleSize";
1462
+ }
1463
+ }
1464
+ /**
1465
+ * Ordered child tag list for each `ChartTypeGroup` block in a classic
1466
+ * chartN.xml, used by `replaceOrRemoveSimpleGroupField` to place a new
1467
+ * leaf in the correct schema position. The ordering mirrors the
1468
+ * `_renderXxxChart` functions in `chart-space-xform.ts` and is the
1469
+ * "schema order" ECMA-376 requires — inserting `c:gapWidth` before
1470
+ * `c:barDir` would produce XML Excel refuses to open.
1471
+ *
1472
+ * Tags not listed (e.g. `c:dLbls`, `c:ser`, `c:extLst`) are inserted
1473
+ * by existing dedicated patchers. Anything in this map is a single
1474
+ * `<c:… val="…"/>` leaf with no child elements.
1475
+ */
1476
+ const CLASSIC_GROUP_CHILD_ORDER = [
1477
+ "c:barDir",
1478
+ "c:grouping",
1479
+ "c:varyColors",
1480
+ "c:ofPieType",
1481
+ "c:radarStyle",
1482
+ "c:scatterStyle",
1483
+ "c:wireframe",
1484
+ "c:ser",
1485
+ "c:dLbls",
1486
+ "c:marker",
1487
+ "c:smooth",
1488
+ "c:dropLines",
1489
+ "c:hiLowLines",
1490
+ "c:upDownBars",
1491
+ "c:bubbleScale",
1492
+ "c:showNegBubbles",
1493
+ "c:sizeRepresents",
1494
+ "c:gapWidth",
1495
+ "c:overlap",
1496
+ "c:serLines",
1497
+ "c:shape",
1498
+ "c:firstSliceAng",
1499
+ "c:holeSize",
1500
+ "c:gapDepth",
1501
+ "c:splitType",
1502
+ "c:splitPos",
1503
+ "c:custSplit",
1504
+ "c:secondPieSize",
1505
+ "c:axId",
1506
+ "c:extLst"
1507
+ ];
1508
+ /**
1509
+ * The subset of {@link CLASSIC_GROUP_CHILD_ORDER} that
1510
+ * `patchRawChartGroupSimpleFields` can rewrite in place. The field
1511
+ * name on the left is the `ChartTypeGroup` model key; the right-hand
1512
+ * value is the OOXML element name (sans `val=` attribute, which this
1513
+ * patcher always uses). Boolean model fields are serialised as
1514
+ * `"1"` / `"0"` to match the ECMA-376 convention.
1515
+ *
1516
+ * Kept in sync with `SIMPLE_GROUP_FIELD_NAMES` (used by
1517
+ * `stripPatchableChartFields`) — every entry in this map must also be
1518
+ * stripped from the baseline diff, otherwise a plain
1519
+ * "previous === current after strip" check would see the mutated
1520
+ * leaf and refuse the raw-patch plan.
1521
+ */
1522
+ const SIMPLE_GROUP_FIELD_TAGS = {
1523
+ barDir: "c:barDir",
1524
+ grouping: "c:grouping",
1525
+ varyColors: "c:varyColors",
1526
+ gapWidth: "c:gapWidth",
1527
+ overlap: "c:overlap",
1528
+ firstSliceAng: "c:firstSliceAng",
1529
+ holeSize: "c:holeSize",
1530
+ gapDepth: "c:gapDepth",
1531
+ scatterStyle: "c:scatterStyle",
1532
+ radarStyle: "c:radarStyle",
1533
+ ofPieType: "c:ofPieType",
1534
+ splitType: "c:splitType",
1535
+ splitPos: "c:splitPos",
1536
+ secondPieSize: "c:secondPieSize",
1537
+ bubbleScale: "c:bubbleScale",
1538
+ showNegBubbles: "c:showNegBubbles",
1539
+ sizeRepresents: "c:sizeRepresents",
1540
+ shape: "c:shape",
1541
+ smooth: "c:smooth",
1542
+ wireframe: "c:wireframe"
1543
+ };
1544
+ const SIMPLE_GROUP_FIELD_NAMES = Object.keys(SIMPLE_GROUP_FIELD_TAGS);
1545
+ /**
1546
+ * Extract the simple-field projection of every chart-type group in a
1547
+ * `ChartModel` for diffing. Produces a stable array of plain objects
1548
+ * (one per group) keyed by field name so
1549
+ * `sameJson(extractSimpleGroupFields(prev), extractSimpleGroupFields(curr))`
1550
+ * answers "did any group simple field change?".
1551
+ */
1552
+ function extractSimpleGroupFields(model) {
1553
+ const groups = model.chart?.plotArea?.chartTypes ?? [];
1554
+ return groups.map((group) => {
1555
+ const out = { type: group.type };
1556
+ for (const key of SIMPLE_GROUP_FIELD_NAMES) {
1557
+ if (group[key] !== undefined) {
1558
+ out[key] = group[key];
1559
+ }
1560
+ }
1561
+ return out;
1562
+ });
1563
+ }
1564
+ /**
1565
+ * Raw-XML patcher for the simple leaf fields of every chart-type
1566
+ * group: `gapWidth`, `overlap`, `varyColors`, `firstSliceAng`,
1567
+ * `holeSize`, `gapDepth`, `radarStyle`, `scatterStyle`, `ofPieType`,
1568
+ * `smooth`, and the other `val="…"` leaves listed in
1569
+ * {@link SIMPLE_GROUP_FIELD_TAGS}. Called by `tryPatchChartRawXml`
1570
+ * when `plan.groupSimpleFields` is true so these common user edits
1571
+ * (tightening bar overlap, rotating a pie, lowering bubble scale)
1572
+ * keep the fast raw-patch path instead of rebuilding the chart XML
1573
+ * structurally and losing any vendor extensions along the way.
1574
+ *
1575
+ * Returns `undefined` when a group block cannot be located or its
1576
+ * type lacks a known tag — signalling to the caller that it should
1577
+ * fall back to a structural rebuild.
1578
+ */
1579
+ function patchRawChartGroupSimpleFields(raw, model) {
1580
+ let patched = raw;
1581
+ for (const group of model.chart?.plotArea?.chartTypes ?? []) {
1582
+ const tag = chartGroupTagName(group);
1583
+ if (!tag) {
1584
+ return undefined;
1585
+ }
1586
+ const range = findXmlBlock(patched, tag);
1587
+ if (!range) {
1588
+ return undefined;
1589
+ }
1590
+ const block = patched.slice(range.start, range.end);
1591
+ // Series blocks can themselves contain elements with the same
1592
+ // tag names (e.g. a custom series dLbls might ship with a stale
1593
+ // `c:smooth`); mask them out while we rewrite the group-level
1594
+ // leaves so our regex replacements don't accidentally target a
1595
+ // series-internal element.
1596
+ const { xml: withoutSeries, seriesBlocks } = preserveSeriesBlocks(block, xml => xml);
1597
+ let current = withoutSeries;
1598
+ for (const fieldName of SIMPLE_GROUP_FIELD_NAMES) {
1599
+ const xmlTag = SIMPLE_GROUP_FIELD_TAGS[fieldName];
1600
+ const value = group[fieldName];
1601
+ current = replaceOrRemoveSimpleGroupField(current, xmlTag, value);
1602
+ }
1603
+ const restored = restoreSeriesBlocks(current, seriesBlocks);
1604
+ patched = patched.slice(0, range.start) + restored + patched.slice(range.end);
1605
+ }
1606
+ return patched;
1607
+ }
1608
+ /**
1609
+ * Replace, insert, or remove a `<c:xxx val="…"/>` leaf inside a
1610
+ * chart-type group block while keeping the block's child order
1611
+ * schema-valid (see {@link CLASSIC_GROUP_CHILD_ORDER}).
1612
+ *
1613
+ * - `value === undefined` → element is removed if present, left
1614
+ * untouched otherwise.
1615
+ * - `value !== undefined` → element is rewritten in place when it
1616
+ * already exists, or inserted before the first schema-later
1617
+ * sibling when it does not.
1618
+ *
1619
+ * Booleans are serialised as `"1"` / `"0"`; numbers use their string
1620
+ * representation; strings pass through with attribute escaping. The
1621
+ * schema only expects these three primitive shapes for simple leaves.
1622
+ */
1623
+ function replaceOrRemoveSimpleGroupField(block, tag, value) {
1624
+ const leafRegex = new RegExp(`<${escapeRegExp(tag)}(?:\\s+[^/>]*)?/>`, "g");
1625
+ if (value === undefined) {
1626
+ // Strip the existing leaf if present, no-op otherwise.
1627
+ return block.replace(leafRegex, "");
1628
+ }
1629
+ const serialised = serialiseSimpleGroupFieldValue(value);
1630
+ const replacement = `<${tag} val="${serialised}"/>`;
1631
+ if (leafRegex.test(block)) {
1632
+ return block.replace(leafRegex, replacement);
1633
+ }
1634
+ // Insert in schema order: find the first sibling that comes after
1635
+ // our tag in CLASSIC_GROUP_CHILD_ORDER and that exists in the
1636
+ // current block.
1637
+ const tagIndex = CLASSIC_GROUP_CHILD_ORDER.indexOf(tag);
1638
+ if (tagIndex < 0) {
1639
+ return block; // unknown tag — do not risk corrupting the XML
1640
+ }
1641
+ const laterSiblings = CLASSIC_GROUP_CHILD_ORDER.slice(tagIndex + 1);
1642
+ for (const sibling of laterSiblings) {
1643
+ const siblingIdx = block.indexOf(`<${sibling}`);
1644
+ if (siblingIdx >= 0) {
1645
+ return block.slice(0, siblingIdx) + replacement + block.slice(siblingIdx);
1646
+ }
1647
+ }
1648
+ // No later sibling found — insert before the closing `</…Chart>`.
1649
+ const closeMatch = /<\/c:\w+Chart>\s*$/.exec(block);
1650
+ if (closeMatch) {
1651
+ const insertAt = closeMatch.index;
1652
+ return block.slice(0, insertAt) + replacement + block.slice(insertAt);
1653
+ }
1654
+ return block;
1655
+ }
1656
+ function escapeRegExp(value) {
1657
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1658
+ }
1659
+ function serialiseSimpleGroupFieldValue(value) {
1660
+ if (typeof value === "boolean") {
1661
+ return value ? "1" : "0";
1662
+ }
1663
+ if (typeof value === "number") {
1664
+ // Guard against `NaN` / `Infinity` leaking into the attribute —
1665
+ // `String(NaN) === "NaN"` produces XML Excel rejects. Callers
1666
+ // that pass an invalid numeric should get an empty string
1667
+ // instead; the caller removes the leaf on absence, so an empty
1668
+ // serialise is equivalent to "don't emit this field".
1669
+ return Number.isFinite(value) ? String(value) : "";
1670
+ }
1671
+ // Strings go through the canonical attribute encoder. Previously
1672
+ // this helper hand-rolled a minimal `& " <` escape chain, which
1673
+ // let newlines / tabs / illegal XML chars / lone surrogates
1674
+ // through verbatim — the raw patcher then produced attribute
1675
+ // values that (a) normalized to a single space on parse (XML 1.0
1676
+ // §3.3.3), losing newlines, or (b) contained chars no parser
1677
+ // accepts. `xmlEncodeAttr` strips the illegal ones and encodes
1678
+ // CR/LF/Tab as numeric character references so round-trip preserves
1679
+ // whitespace.
1680
+ return (0, encode_1.xmlEncodeAttr)(String(value));
1681
+ }
1682
+ function patchRawChartGroupDataLabels(raw, model) {
1683
+ let patched = raw;
1684
+ for (const group of model.chart?.plotArea?.chartTypes ?? []) {
1685
+ const tag = chartGroupTagName(group);
1686
+ if (!tag) {
1687
+ return undefined;
1688
+ }
1689
+ const range = findXmlBlock(patched, tag);
1690
+ if (!range) {
1691
+ return undefined;
1692
+ }
1693
+ const block = patched.slice(range.start, range.end);
1694
+ const replacement = patchRawChartGroupDataLabelsBlock(block, group);
1695
+ patched = patched.slice(0, range.start) + replacement + patched.slice(range.end);
1696
+ }
1697
+ return patched;
1698
+ }
1699
+ function patchRawPlotAreaLayout(raw, model) {
1700
+ const plotArea = model.chart?.plotArea;
1701
+ const range = findXmlBlock(raw, "c:plotArea");
1702
+ if (!range || !plotArea) {
1703
+ return undefined;
1704
+ }
1705
+ const block = raw.slice(range.start, range.end);
1706
+ const layoutXml = plotArea.layout ? buildRawLayoutXml(plotArea.layout) : "";
1707
+ const patched = layoutXml
1708
+ ? replaceOrInsertBeforeGeneric(block, "c:layout", layoutXml, [
1709
+ "c:areaChart",
1710
+ "c:area3DChart",
1711
+ "c:barChart",
1712
+ "c:bar3DChart",
1713
+ "c:lineChart",
1714
+ "c:line3DChart",
1715
+ "c:pieChart",
1716
+ "c:pie3DChart",
1717
+ "c:doughnutChart",
1718
+ "c:scatterChart",
1719
+ "c:bubbleChart",
1720
+ "c:radarChart",
1721
+ "c:stockChart",
1722
+ "c:surfaceChart",
1723
+ "c:surface3DChart",
1724
+ "c:ofPieChart",
1725
+ "c:catAx",
1726
+ "c:valAx",
1727
+ "c:serAx",
1728
+ "c:dateAx",
1729
+ "c:spPr"
1730
+ ], "c:plotArea")
1731
+ : removeXmlBlock(block, "c:layout");
1732
+ return raw.slice(0, range.start) + patched + raw.slice(range.end);
1733
+ }
1734
+ function buildRawLayoutXml(layout, namespace = "c") {
1735
+ if (!layout?.manualLayout) {
1736
+ return `<${namespace}:layout/>`;
1737
+ }
1738
+ const ml = layout.manualLayout;
1739
+ const parts = [`<${namespace}:layout><${namespace}:manualLayout>`];
1740
+ for (const [name, value] of [
1741
+ ["layoutTarget", ml.layoutTarget],
1742
+ ["xMode", ml.xMode],
1743
+ ["yMode", ml.yMode],
1744
+ ["wMode", ml.wMode],
1745
+ ["hMode", ml.hMode],
1746
+ ["x", ml.x],
1747
+ ["y", ml.y],
1748
+ ["w", ml.w],
1749
+ ["h", ml.h]
1750
+ ]) {
1751
+ if (value !== undefined) {
1752
+ parts.push(`<${namespace}:${name} val="${escapeAttr(String(value))}"/>`);
1753
+ }
1754
+ }
1755
+ parts.push(`</${namespace}:manualLayout></${namespace}:layout>`);
1756
+ return parts.join("");
1757
+ }
1758
+ function patchRawChartGroupDataLabelsBlock(block, group) {
1759
+ const withoutSeriesBlocks = preserveSeriesBlocks(block, xml => xml);
1760
+ if (group.dataLabels) {
1761
+ return restoreSeriesBlocks(replaceOrInsertBefore(withoutSeriesBlocks.xml, "c:dLbls", buildRawDataLabelsXml(group.dataLabels, {
1762
+ suppressDLblPos: group.type === "doughnut"
1763
+ }), [
1764
+ "c:gapWidth",
1765
+ "c:overlap",
1766
+ "c:serLines",
1767
+ "c:axId",
1768
+ "c:firstSliceAng",
1769
+ "c:holeSize",
1770
+ "c:extLst"
1771
+ ]), withoutSeriesBlocks.seriesBlocks);
1772
+ }
1773
+ const stripped = withoutSeriesBlocks.xml.replace(/<c:dLbls>[\s\S]*?<\/c:dLbls>/, "");
1774
+ return restoreSeriesBlocks(stripped, withoutSeriesBlocks.seriesBlocks);
1775
+ }
1776
+ function chartGroupTagName(group) {
1777
+ const tagByType = {
1778
+ bar: "c:barChart",
1779
+ bar3D: "c:bar3DChart",
1780
+ line: "c:lineChart",
1781
+ line3D: "c:line3DChart",
1782
+ pie: "c:pieChart",
1783
+ pie3D: "c:pie3DChart",
1784
+ doughnut: "c:doughnutChart",
1785
+ area: "c:areaChart",
1786
+ area3D: "c:area3DChart",
1787
+ scatter: "c:scatterChart",
1788
+ bubble: "c:bubbleChart",
1789
+ radar: "c:radarChart",
1790
+ stock: "c:stockChart",
1791
+ surface: "c:surfaceChart",
1792
+ surface3D: "c:surface3DChart",
1793
+ ofPie: "c:ofPieChart"
1794
+ };
1795
+ return tagByType[group.type];
1796
+ }
1797
+ function preserveSeriesBlocks(block, transform) {
1798
+ const seriesBlocks = [];
1799
+ let cursor = 0;
1800
+ let xml = "";
1801
+ while (cursor < block.length) {
1802
+ const range = findXmlBlock(block, "c:ser", cursor);
1803
+ if (!range) {
1804
+ xml += block.slice(cursor);
1805
+ break;
1806
+ }
1807
+ xml += block.slice(cursor, range.start);
1808
+ const placeholder = `__EXCELTS_SER_${seriesBlocks.length}__`;
1809
+ seriesBlocks.push(transform(block.slice(range.start, range.end)));
1810
+ xml += placeholder;
1811
+ cursor = range.end;
1812
+ }
1813
+ return { xml, seriesBlocks };
1814
+ }
1815
+ function restoreSeriesBlocks(block, seriesBlocks) {
1816
+ return seriesBlocks.reduce((xml, seriesBlock, i) => xml.replace(`__EXCELTS_SER_${i}__`, seriesBlock), block);
1817
+ }
1818
+ function buildRawDataLabelsXml(dataLabels, opts) {
1819
+ const parts = ["<c:dLbls>"];
1820
+ if (Array.isArray(dataLabels.entries)) {
1821
+ for (const entry of dataLabels.entries) {
1822
+ parts.push(buildRawDataLabelEntryXml(entry, opts));
1823
+ }
1824
+ }
1825
+ // ECMA-376 `CT_DLbls` (§21.2.2.49) child order (confirmed against
1826
+ // Microsoft OpenXML `DataLabels.ChildElementInfo`):
1827
+ // dLbl*, delete | (numFmt, spPr, txPr, dLblPos, showLegendKey,
1828
+ // showVal, showCatName, showSerName, showPercent,
1829
+ // showBubbleSize, separator, showLeaderLines, leaderLines),
1830
+ // extLst?.
1831
+ // The earlier raw-builder placed every `show*` flag BEFORE
1832
+ // `dLblPos` / `spPr` / `txPr` and `separator` AFTER
1833
+ // `showLeaderLines` — two schema violations that Excel silently
1834
+ // tolerates but LibreOffice strict mode refuses.
1835
+ if (dataLabels.numFmt?.formatCode) {
1836
+ const sourceLinked = dataLabels.numFmt.sourceLinked === undefined
1837
+ ? "1"
1838
+ : dataLabels.numFmt.sourceLinked
1839
+ ? "1"
1840
+ : "0";
1841
+ parts.push(`<c:numFmt formatCode="${escapeAttr(dataLabels.numFmt.formatCode)}" sourceLinked="${sourceLinked}"/>`);
1842
+ }
1843
+ if (dataLabels.spPr) {
1844
+ parts.push(buildRawShapePropertiesXml(dataLabels.spPr, "c") ?? "");
1845
+ }
1846
+ if (dataLabels.txPr) {
1847
+ parts.push(buildRawTextPropertiesXml(dataLabels.txPr, "c") ?? "");
1848
+ }
1849
+ // Doughnut charts must not emit `c:dLblPos` — Excel rejects the
1850
+ // element on open. See `_renderDoughnutChart` in
1851
+ // `chart-space-xform.ts` for the full rationale and bisect.
1852
+ if (dataLabels.position !== undefined && !opts?.suppressDLblPos) {
1853
+ parts.push(`<c:dLblPos val="${escapeAttr(String(dataLabels.position))}"/>`);
1854
+ }
1855
+ const flags = [
1856
+ ["showLegendKey", dataLabels.showLegendKey],
1857
+ ["showVal", dataLabels.showVal],
1858
+ ["showCatName", dataLabels.showCatName],
1859
+ ["showSerName", dataLabels.showSerName],
1860
+ ["showPercent", dataLabels.showPercent],
1861
+ ["showBubbleSize", dataLabels.showBubbleSize]
1862
+ ];
1863
+ for (const [name, value] of flags) {
1864
+ if (value !== undefined) {
1865
+ parts.push(`<c:${name} val="${value ? "1" : "0"}"/>`);
1866
+ }
1867
+ }
1868
+ if (dataLabels.separator !== undefined) {
1869
+ parts.push(`<c:separator>${escapeXml(String(dataLabels.separator))}</c:separator>`);
1870
+ }
1871
+ if (dataLabels.showLeaderLines !== undefined) {
1872
+ parts.push(`<c:showLeaderLines val="${dataLabels.showLeaderLines ? "1" : "0"}"/>`);
1873
+ }
1874
+ if (dataLabels.extLst) {
1875
+ parts.push(dataLabels.extLst);
1876
+ }
1877
+ parts.push("</c:dLbls>");
1878
+ return parts.join("");
1879
+ }
1880
+ function buildRawDataLabelEntryXml(entry, opts) {
1881
+ // ECMA-376 `CT_DLbl` (§21.2.2.47) is a `choice(delete | …)` — the
1882
+ // two branches are mutually exclusive. Emitting `delete` alongside
1883
+ // any of the display-flag children (layout / tx / numFmt /
1884
+ // dLblPos / show* / separator) violates the schema; Excel's
1885
+ // tolerance varies by build (some strip the label wholesale).
1886
+ const parts = ["<c:dLbl>", `<c:idx val="${entry.index ?? 0}"/>`];
1887
+ if (entry.delete) {
1888
+ parts.push(`<c:delete val="1"/>`);
1889
+ if (entry.extLst) {
1890
+ parts.push(entry.extLst);
1891
+ }
1892
+ parts.push("</c:dLbl>");
1893
+ return parts.join("");
1894
+ }
1895
+ if (entry.layout) {
1896
+ parts.push(buildRawLayoutXml(entry.layout));
1897
+ }
1898
+ if (entry.rawTx) {
1899
+ parts.push(entry.rawTx);
1900
+ }
1901
+ else if (entry.text?.paragraphs?.[0]?.runs?.[0]?.text !== undefined) {
1902
+ parts.push(`<c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${escapeXml(String(entry.text.paragraphs[0].runs[0].text))}</a:t></a:r></a:p></c:rich></c:tx>`);
1903
+ }
1904
+ if (entry.numFmt?.formatCode) {
1905
+ parts.push(`<c:numFmt formatCode="${escapeAttr(entry.numFmt.formatCode)}" sourceLinked="${entry.numFmt.sourceLinked ? "1" : "0"}"/>`);
1906
+ }
1907
+ if (entry.spPr) {
1908
+ parts.push(buildRawShapePropertiesXml(entry.spPr, "c") ?? "");
1909
+ }
1910
+ if (entry.txPr) {
1911
+ parts.push(buildRawTextPropertiesXml(entry.txPr, "c") ?? "");
1912
+ }
1913
+ if (entry.position !== undefined && !opts?.suppressDLblPos) {
1914
+ parts.push(`<c:dLblPos val="${escapeAttr(String(entry.position))}"/>`);
1915
+ }
1916
+ for (const [name, value] of [
1917
+ ["showLegendKey", entry.showLegendKey],
1918
+ ["showVal", entry.showVal],
1919
+ ["showCatName", entry.showCatName],
1920
+ ["showSerName", entry.showSerName],
1921
+ ["showPercent", entry.showPercent],
1922
+ ["showBubbleSize", entry.showBubbleSize]
1923
+ ]) {
1924
+ if (value !== undefined) {
1925
+ parts.push(`<c:${name} val="${value ? "1" : "0"}"/>`);
1926
+ }
1927
+ }
1928
+ if (entry.separator !== undefined) {
1929
+ parts.push(`<c:separator>${escapeXml(String(entry.separator))}</c:separator>`);
1930
+ }
1931
+ if (entry.extLst) {
1932
+ parts.push(entry.extLst);
1933
+ }
1934
+ parts.push("</c:dLbl>");
1935
+ return parts.join("");
1936
+ }
1937
+ function patchRawAxes(raw, model, patchPlan) {
1938
+ let patched = raw;
1939
+ for (const [index, axis] of (model.chart?.plotArea?.axes ?? []).entries()) {
1940
+ const axisPlan = getRawPatchListItem(patchPlan, index);
1941
+ if (!axisPlan) {
1942
+ continue;
1943
+ }
1944
+ const tag = axis.axisType === "cat"
1945
+ ? "c:catAx"
1946
+ : axis.axisType === "val"
1947
+ ? "c:valAx"
1948
+ : axis.axisType === "date"
1949
+ ? "c:dateAx"
1950
+ : "c:serAx";
1951
+ const block = findAxisBlock(patched, tag, axis.axId);
1952
+ if (!block) {
1953
+ return undefined;
1954
+ }
1955
+ const axisXml = patchRawAxisBlock(block.xml, axis, axisPlan);
1956
+ if (!axisXml) {
1957
+ return undefined;
1958
+ }
1959
+ patched = patched.slice(0, block.start) + axisXml + patched.slice(block.end);
1960
+ }
1961
+ return patched;
1962
+ }
1963
+ function patchRawAxisBlock(block, axis, patchPlan) {
1964
+ let patched = block;
1965
+ const axisTag = axisTagName(axis);
1966
+ if (rawPatchFlag(patchPlan, "scaling")) {
1967
+ patched = patchGenericChild(patched, "c:scaling", buildRawScalingXml(axis.scaling), ["c:delete", "c:axPos"], axisTag);
1968
+ }
1969
+ if (rawPatchFlag(patchPlan, "delete")) {
1970
+ patched = patchBooleanLeaf(patched, "c:delete", axis.delete, ["c:axPos"], axisTag);
1971
+ }
1972
+ if (rawPatchFlag(patchPlan, "title")) {
1973
+ if (axis.title) {
1974
+ const titleText = axis.title.text?.paragraphs?.[0]?.runs?.[0]?.text;
1975
+ if (titleText !== undefined) {
1976
+ patched = replaceOrInsertBefore(patched, "c:title", buildRawChartTitleXml(titleText), [
1977
+ "c:numFmt",
1978
+ "c:majorGridlines",
1979
+ "c:minorGridlines",
1980
+ "c:majorUnit",
1981
+ "c:minorUnit",
1982
+ "c:majorTickMark",
1983
+ "c:minorTickMark",
1984
+ "c:tickLblPos"
1985
+ ]);
1986
+ }
1987
+ }
1988
+ else {
1989
+ patched = patched.replace(/<c:title>[\s\S]*?<\/c:title>/, "");
1990
+ }
1991
+ }
1992
+ if (rawPatchFlag(patchPlan, "numFmt")) {
1993
+ patched = patchGenericChild(patched, "c:numFmt", buildRawNumFmtXml(axis.numFmt), [
1994
+ "c:majorGridlines",
1995
+ "c:minorGridlines",
1996
+ "c:majorUnit",
1997
+ "c:minorUnit",
1998
+ "c:majorTickMark",
1999
+ "c:minorTickMark",
2000
+ "c:tickLblPos",
2001
+ "c:spPr",
2002
+ "c:txPr",
2003
+ "c:crossAx"
2004
+ ], axisTag);
2005
+ }
2006
+ if (rawPatchFlag(patchPlan, "majorGridlines")) {
2007
+ patched = patchGridlines(patched, "c:majorGridlines", axis.majorGridlines, [
2008
+ "c:minorGridlines",
2009
+ "c:title",
2010
+ "c:numFmt",
2011
+ "c:majorUnit",
2012
+ "c:minorUnit",
2013
+ "c:majorTickMark",
2014
+ "c:minorTickMark",
2015
+ "c:tickLblPos",
2016
+ "c:spPr",
2017
+ "c:txPr",
2018
+ "c:crossAx"
2019
+ ], axisTag);
2020
+ }
2021
+ if (rawPatchFlag(patchPlan, "minorGridlines")) {
2022
+ patched = patchGridlines(patched, "c:minorGridlines", axis.minorGridlines, [
2023
+ "c:title",
2024
+ "c:numFmt",
2025
+ "c:majorUnit",
2026
+ "c:minorUnit",
2027
+ "c:majorTickMark",
2028
+ "c:minorTickMark",
2029
+ "c:tickLblPos",
2030
+ "c:spPr",
2031
+ "c:txPr",
2032
+ "c:crossAx"
2033
+ ], axisTag);
2034
+ }
2035
+ if (rawPatchFlag(patchPlan, "majorTickMark")) {
2036
+ patched = patchValueLeaf(patched, "c:majorTickMark", axis.majorTickMark, ["c:minorTickMark", "c:tickLblPos", "c:spPr", "c:txPr", "c:crossAx"], axisTag);
2037
+ }
2038
+ if (rawPatchFlag(patchPlan, "minorTickMark")) {
2039
+ patched = patchValueLeaf(patched, "c:minorTickMark", axis.minorTickMark, ["c:tickLblPos", "c:spPr", "c:txPr", "c:crossAx"], axisTag);
2040
+ }
2041
+ if (rawPatchFlag(patchPlan, "tickLblPos")) {
2042
+ patched = patchValueLeaf(patched, "c:tickLblPos", axis.tickLblPos, ["c:spPr", "c:txPr", "c:crossAx"], axisTag);
2043
+ }
2044
+ if (rawPatchFlag(patchPlan, "spPr")) {
2045
+ patched = patchGenericChild(patched, "c:spPr", buildRawShapePropertiesXml(axis.spPr, "c"), ["c:txPr", "c:crossAx"], axisTag);
2046
+ }
2047
+ if (rawPatchFlag(patchPlan, "txPr")) {
2048
+ patched = patchGenericChild(patched, "c:txPr", buildRawTextPropertiesXml(axis.txPr, "c"), ["c:crossAx"], axisTag);
2049
+ }
2050
+ if (rawPatchFlag(patchPlan, "crosses")) {
2051
+ patched = patchValueLeaf(patched, "c:crosses", axis.crosses, [
2052
+ "c:crossesAt",
2053
+ "c:auto",
2054
+ "c:lblAlgn",
2055
+ "c:lblOffset",
2056
+ "c:tickLblSkip",
2057
+ "c:tickMarkSkip",
2058
+ "c:noMultiLvlLbl",
2059
+ "c:crossBetween",
2060
+ "c:majorUnit",
2061
+ "c:minorUnit",
2062
+ "c:baseTimeUnit",
2063
+ "c:majorTimeUnit",
2064
+ "c:minorTimeUnit",
2065
+ "c:dispUnits",
2066
+ "c:extLst"
2067
+ ], axisTag);
2068
+ }
2069
+ if (rawPatchFlag(patchPlan, "crossesAt")) {
2070
+ patched = patchValueLeaf(patched, "c:crossesAt", axis.crossesAt, [
2071
+ "c:auto",
2072
+ "c:lblAlgn",
2073
+ "c:lblOffset",
2074
+ "c:tickLblSkip",
2075
+ "c:tickMarkSkip",
2076
+ "c:noMultiLvlLbl",
2077
+ "c:crossBetween",
2078
+ "c:majorUnit",
2079
+ "c:minorUnit",
2080
+ "c:baseTimeUnit",
2081
+ "c:majorTimeUnit",
2082
+ "c:minorTimeUnit",
2083
+ "c:dispUnits",
2084
+ "c:extLst"
2085
+ ], axisTag);
2086
+ }
2087
+ patched = patchAxisTypeSpecificLeaves(patched, axis, patchPlan);
2088
+ return patched;
2089
+ }
2090
+ function axisTagName(axis) {
2091
+ return axis.axisType === "cat"
2092
+ ? "c:catAx"
2093
+ : axis.axisType === "val"
2094
+ ? "c:valAx"
2095
+ : axis.axisType === "date"
2096
+ ? "c:dateAx"
2097
+ : "c:serAx";
2098
+ }
2099
+ function patchAxisTypeSpecificLeaves(block, axis, patchPlan) {
2100
+ const axisTag = axisTagName(axis);
2101
+ let patched = block;
2102
+ if (rawPatchFlag(patchPlan, "auto")) {
2103
+ patched = patchBooleanLeaf(patched, "c:auto", axis.auto, [
2104
+ "c:lblAlgn",
2105
+ "c:lblOffset",
2106
+ "c:tickLblSkip",
2107
+ "c:tickMarkSkip",
2108
+ "c:noMultiLvlLbl",
2109
+ "c:extLst"
2110
+ ], axisTag);
2111
+ }
2112
+ if (rawPatchFlag(patchPlan, "lblAlgn")) {
2113
+ patched = patchValueLeaf(patched, "c:lblAlgn", axis.lblAlgn, ["c:lblOffset", "c:tickLblSkip", "c:tickMarkSkip", "c:noMultiLvlLbl", "c:extLst"], axisTag);
2114
+ }
2115
+ if (rawPatchFlag(patchPlan, "lblOffset")) {
2116
+ patched = patchValueLeaf(patched, "c:lblOffset", axis.lblOffset, ["c:tickLblSkip", "c:tickMarkSkip", "c:noMultiLvlLbl", "c:extLst"], axisTag);
2117
+ }
2118
+ if (rawPatchFlag(patchPlan, "tickLblSkip")) {
2119
+ patched = patchValueLeaf(patched, "c:tickLblSkip", axis.tickLblSkip, ["c:tickMarkSkip", "c:noMultiLvlLbl", "c:extLst"], axisTag);
2120
+ }
2121
+ if (rawPatchFlag(patchPlan, "tickMarkSkip")) {
2122
+ patched = patchValueLeaf(patched, "c:tickMarkSkip", axis.tickMarkSkip, ["c:noMultiLvlLbl", "c:extLst"], axisTag);
2123
+ }
2124
+ if (rawPatchFlag(patchPlan, "noMultiLvlLbl")) {
2125
+ patched = patchBooleanLeaf(patched, "c:noMultiLvlLbl", axis.noMultiLvlLbl, ["c:extLst"], axisTag);
2126
+ }
2127
+ if (rawPatchFlag(patchPlan, "crossBetween")) {
2128
+ patched = patchValueLeaf(patched, "c:crossBetween", axis.crossBetween, ["c:majorUnit", "c:minorUnit", "c:dispUnits", "c:extLst"], axisTag);
2129
+ }
2130
+ if (rawPatchFlag(patchPlan, "majorUnit")) {
2131
+ patched = patchValueLeaf(patched, "c:majorUnit", axis.majorUnit, ["c:minorUnit", "c:dispUnits", "c:extLst"], axisTag);
2132
+ }
2133
+ if (rawPatchFlag(patchPlan, "minorUnit")) {
2134
+ patched = patchValueLeaf(patched, "c:minorUnit", axis.minorUnit, ["c:dispUnits", "c:extLst"], axisTag);
2135
+ }
2136
+ if (rawPatchFlag(patchPlan, "baseTimeUnit")) {
2137
+ patched = patchValueLeaf(patched, "c:baseTimeUnit", axis.baseTimeUnit, ["c:majorUnit", "c:majorTimeUnit", "c:minorUnit", "c:minorTimeUnit", "c:extLst"], axisTag);
2138
+ }
2139
+ if (rawPatchFlag(patchPlan, "majorTimeUnit")) {
2140
+ patched = patchValueLeaf(patched, "c:majorTimeUnit", axis.majorTimeUnit, ["c:minorUnit", "c:minorTimeUnit", "c:extLst"], axisTag);
2141
+ }
2142
+ if (rawPatchFlag(patchPlan, "minorTimeUnit")) {
2143
+ patched = patchValueLeaf(patched, "c:minorTimeUnit", axis.minorTimeUnit, ["c:extLst"], axisTag);
2144
+ }
2145
+ return patched;
2146
+ }
2147
+ function buildRawScalingXml(scaling) {
2148
+ if (!scaling) {
2149
+ return "";
2150
+ }
2151
+ const parts = ["<c:scaling>"];
2152
+ // ECMA-376 `CT_Scaling` sequence is `logBase?, orientation?,
2153
+ // max?, min?, extLst?`. Emitting children in any other order
2154
+ // triggers a "Repaired Records" dialog when Excel opens the
2155
+ // file and causes LibreOffice strict-mode to reject it outright.
2156
+ if (scaling.logBase !== undefined && Number.isFinite(scaling.logBase) && scaling.logBase > 0) {
2157
+ // `CT_LogBase` requires the value be `>= 2` per ECMA-376
2158
+ // §21.2.3.21; `> 0` is the looser guard we use at parse time.
2159
+ // Leave range clamping to the builder.
2160
+ parts.push(`<c:logBase val="${scaling.logBase}"/>`);
2161
+ }
2162
+ if (scaling.orientation !== undefined) {
2163
+ parts.push(`<c:orientation val="${escapeAttr(scaling.orientation)}"/>`);
2164
+ }
2165
+ // Numeric scaling attributes MUST be finite on the wire; the OOXML
2166
+ // grammar requires `xsd:double` / `xsd:unsignedInt`, and writing
2167
+ // `val="NaN"` or `val="Infinity"` produces a file Excel refuses to
2168
+ // open. `String(NaN) === "NaN"`, so the prior direct interpolation
2169
+ // silently passed garbage through. Guard each slot and skip
2170
+ // non-finite values — the schema treats absence as "auto", which
2171
+ // is closer to the author's intent than an invalid literal.
2172
+ if (scaling.max !== undefined && Number.isFinite(scaling.max)) {
2173
+ parts.push(`<c:max val="${scaling.max}"/>`);
2174
+ }
2175
+ if (scaling.min !== undefined && Number.isFinite(scaling.min)) {
2176
+ parts.push(`<c:min val="${scaling.min}"/>`);
2177
+ }
2178
+ parts.push("</c:scaling>");
2179
+ return parts.join("");
2180
+ }
2181
+ function buildRawNumFmtXml(numFmt) {
2182
+ if (!numFmt?.formatCode) {
2183
+ return "";
2184
+ }
2185
+ const sourceLinked = numFmt.sourceLinked === undefined ? "1" : numFmt.sourceLinked ? "1" : "0";
2186
+ return `<c:numFmt formatCode="${escapeAttr(numFmt.formatCode)}" sourceLinked="${sourceLinked}"/>`;
2187
+ }
2188
+ function patchGridlines(block, tag, spPr, beforeTags, parentTag) {
2189
+ const xml = spPr ? `<${tag}>${buildRawShapePropertiesXml(spPr, "c") ?? ""}</${tag}>` : "";
2190
+ return patchGenericChild(block, tag, xml, beforeTags, parentTag);
2191
+ }
2192
+ function patchValueLeaf(block, tag, value, beforeTags, parentTag) {
2193
+ const xml = value === undefined ? "" : `<${tag} val="${escapeAttr(String(value))}"/>`;
2194
+ return patchGenericChild(block, tag, xml, beforeTags, parentTag);
2195
+ }
2196
+ function patchBooleanLeaf(block, tag, value, beforeTags, parentTag) {
2197
+ const xml = value === undefined ? "" : `<${tag} val="${value ? "1" : "0"}"/>`;
2198
+ return patchGenericChild(block, tag, xml, beforeTags, parentTag);
2199
+ }
2200
+ function buildRawSeriesTxXml(tx) {
2201
+ if (tx.strRef?.formula) {
2202
+ return `<c:tx>${buildRawStrRefXml(tx.strRef)}</c:tx>`;
2203
+ }
2204
+ return `<c:tx><c:v>${escapeXml(String(tx.value ?? ""))}</c:v></c:tx>`;
2205
+ }
2206
+ function buildRawMarkerXml(marker) {
2207
+ if (!marker) {
2208
+ return "";
2209
+ }
2210
+ const parts = ["<c:marker>"];
2211
+ if (marker.symbol) {
2212
+ parts.push(`<c:symbol val="${escapeAttr(String(marker.symbol))}"/>`);
2213
+ }
2214
+ if (marker.size !== undefined) {
2215
+ parts.push(`<c:size val="${marker.size}"/>`);
2216
+ }
2217
+ if (marker.spPr) {
2218
+ parts.push(buildRawShapePropertiesXml(marker.spPr, "c") ?? "");
2219
+ }
2220
+ if (marker.extLst) {
2221
+ parts.push(marker.extLst);
2222
+ }
2223
+ parts.push("</c:marker>");
2224
+ return parts.join("");
2225
+ }
2226
+ function buildRawDataPointsXml(dataPoints) {
2227
+ if (!Array.isArray(dataPoints) || dataPoints.length === 0) {
2228
+ return "";
2229
+ }
2230
+ return dataPoints.map(buildRawDataPointXml).join("");
2231
+ }
2232
+ function buildRawDataPointXml(point) {
2233
+ const parts = ["<c:dPt>", `<c:idx val="${point.index ?? 0}"/>`];
2234
+ if (point.invertIfNegative !== undefined) {
2235
+ parts.push(`<c:invertIfNegative val="${point.invertIfNegative ? "1" : "0"}"/>`);
2236
+ }
2237
+ if (point.marker) {
2238
+ parts.push(buildRawMarkerXml(point.marker));
2239
+ }
2240
+ if (point.bubble3D !== undefined) {
2241
+ parts.push(`<c:bubble3D val="${point.bubble3D ? "1" : "0"}"/>`);
2242
+ }
2243
+ if (point.explosion !== undefined) {
2244
+ parts.push(`<c:explosion val="${point.explosion}"/>`);
2245
+ }
2246
+ if (point.spPr) {
2247
+ parts.push(buildRawShapePropertiesXml(point.spPr, "c") ?? "");
2248
+ }
2249
+ if (point.extLst) {
2250
+ parts.push(point.extLst);
2251
+ }
2252
+ parts.push("</c:dPt>");
2253
+ return parts.join("");
2254
+ }
2255
+ function buildRawTrendlinesXml(trendlines) {
2256
+ if (!Array.isArray(trendlines) || trendlines.length === 0) {
2257
+ return "";
2258
+ }
2259
+ return trendlines.map(buildRawTrendlineXml).join("");
2260
+ }
2261
+ function buildRawTrendlineXml(trendline) {
2262
+ const parts = ["<c:trendline>"];
2263
+ if (trendline.name) {
2264
+ parts.push(`<c:name>${escapeXml(String(trendline.name))}</c:name>`);
2265
+ }
2266
+ if (trendline.spPr) {
2267
+ parts.push(buildRawShapePropertiesXml(trendline.spPr, "c") ?? "");
2268
+ }
2269
+ parts.push(`<c:trendlineType val="${escapeAttr(String(trendline.type ?? "linear"))}"/>`);
2270
+ for (const tag of ["order", "period", "forward", "backward", "intercept"]) {
2271
+ if (trendline[tag] !== undefined) {
2272
+ parts.push(`<c:${tag} val="${trendline[tag]}"/>`);
2273
+ }
2274
+ }
2275
+ if (trendline.displayRSqr !== undefined) {
2276
+ parts.push(`<c:dispRSqr val="${trendline.displayRSqr ? "1" : "0"}"/>`);
2277
+ }
2278
+ if (trendline.displayEq !== undefined) {
2279
+ parts.push(`<c:dispEq val="${trendline.displayEq ? "1" : "0"}"/>`);
2280
+ }
2281
+ if (trendline.trendlineLbl) {
2282
+ parts.push(buildRawTrendlineLabelXml(trendline.trendlineLbl));
2283
+ }
2284
+ if (trendline.extLst) {
2285
+ parts.push(trendline.extLst);
2286
+ }
2287
+ parts.push("</c:trendline>");
2288
+ return parts.join("");
2289
+ }
2290
+ function buildRawTrendlineLabelXml(label) {
2291
+ const parts = ["<c:trendlineLbl>"];
2292
+ if (label.layout) {
2293
+ parts.push(buildRawLayoutXml(label.layout));
2294
+ }
2295
+ if (label.rawTx) {
2296
+ parts.push(label.rawTx);
2297
+ }
2298
+ else if (label.text?.paragraphs?.[0]?.runs?.[0]?.text !== undefined) {
2299
+ parts.push(`<c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${escapeXml(String(label.text.paragraphs[0].runs[0].text))}</a:t></a:r></a:p></c:rich></c:tx>`);
2300
+ }
2301
+ if (label.numFmt?.formatCode) {
2302
+ parts.push(`<c:numFmt formatCode="${escapeAttr(label.numFmt.formatCode)}" sourceLinked="${label.numFmt.sourceLinked ? "1" : "0"}"/>`);
2303
+ }
2304
+ if (label.spPr) {
2305
+ parts.push(buildRawShapePropertiesXml(label.spPr, "c") ?? "");
2306
+ }
2307
+ if (label.txPr) {
2308
+ parts.push(buildRawTextPropertiesXml(label.txPr, "c") ?? "");
2309
+ }
2310
+ if (label.extLst) {
2311
+ parts.push(label.extLst);
2312
+ }
2313
+ parts.push("</c:trendlineLbl>");
2314
+ return parts.join("");
2315
+ }
2316
+ function buildRawErrorBarsXml(errorBars) {
2317
+ const bars = Array.isArray(errorBars) ? errorBars : errorBars ? [errorBars] : [];
2318
+ return bars.map(buildRawErrorBarXml).join("");
2319
+ }
2320
+ function buildRawErrorBarXml(errorBar) {
2321
+ const parts = ["<c:errBars>"];
2322
+ if (errorBar.errDir) {
2323
+ parts.push(`<c:errDir val="${escapeAttr(String(errorBar.errDir))}"/>`);
2324
+ }
2325
+ parts.push(`<c:errBarType val="${escapeAttr(String(errorBar.barDir ?? "both"))}"/>`);
2326
+ parts.push(`<c:errValType val="${escapeAttr(String(errorBar.errValType ?? "fixedVal"))}"/>`);
2327
+ if (errorBar.noEndCap !== undefined) {
2328
+ parts.push(`<c:noEndCap val="${errorBar.noEndCap ? "1" : "0"}"/>`);
2329
+ }
2330
+ if (errorBar.val !== undefined) {
2331
+ parts.push(`<c:val val="${errorBar.val}"/>`);
2332
+ }
2333
+ if (errorBar.plus) {
2334
+ parts.push(buildRawDataSourceXml("c:plus", errorBar.plus) ?? "");
2335
+ }
2336
+ if (errorBar.minus) {
2337
+ parts.push(buildRawDataSourceXml("c:minus", errorBar.minus) ?? "");
2338
+ }
2339
+ if (errorBar.spPr) {
2340
+ parts.push(buildRawShapePropertiesXml(errorBar.spPr, "c") ?? "");
2341
+ }
2342
+ if (errorBar.extLst) {
2343
+ parts.push(errorBar.extLst);
2344
+ }
2345
+ parts.push("</c:errBars>");
2346
+ return parts.join("");
2347
+ }
2348
+ function buildRawDataSourceXml(tag, source) {
2349
+ if (source.strRef) {
2350
+ return `<${tag}>${buildRawStrRefXml(source.strRef)}</${tag}>`;
2351
+ }
2352
+ if (source.numRef) {
2353
+ return `<${tag}>${buildRawNumRefXml(source.numRef)}</${tag}>`;
2354
+ }
2355
+ return undefined;
2356
+ }
2357
+ function buildRawNumRefXml(ref) {
2358
+ return `<c:numRef><c:f>${escapeXml(ref.formula)}</c:f>${buildRawNumCacheXml(ref.cache)}</c:numRef>`;
2359
+ }
2360
+ function buildRawStrRefXml(ref) {
2361
+ return `<c:strRef><c:f>${escapeXml(ref.formula)}</c:f>${buildRawStrCacheXml(ref.cache)}</c:strRef>`;
2362
+ }
2363
+ function buildRawNumCacheXml(cache) {
2364
+ if (!cache) {
2365
+ return "";
2366
+ }
2367
+ const parts = ["<c:numCache>"];
2368
+ if (cache.formatCode) {
2369
+ parts.push(`<c:formatCode>${escapeXml(cache.formatCode)}</c:formatCode>`);
2370
+ }
2371
+ if (cache.pointCount !== undefined) {
2372
+ parts.push(`<c:ptCount val="${cache.pointCount}"/>`);
2373
+ }
2374
+ for (const point of cache.points ?? []) {
2375
+ if (point.value !== null && point.value !== undefined) {
2376
+ parts.push(`<c:pt idx="${point.index}"><c:v>${escapeXml(String(point.value))}</c:v></c:pt>`);
2377
+ }
2378
+ }
2379
+ parts.push("</c:numCache>");
2380
+ return parts.join("");
2381
+ }
2382
+ function buildRawStrCacheXml(cache) {
2383
+ if (!cache) {
2384
+ return "";
2385
+ }
2386
+ const parts = ["<c:strCache>"];
2387
+ if (cache.pointCount !== undefined) {
2388
+ parts.push(`<c:ptCount val="${cache.pointCount}"/>`);
2389
+ }
2390
+ for (const point of cache.points ?? []) {
2391
+ parts.push(`<c:pt idx="${point.index}"><c:v>${escapeXml(String(point.value))}</c:v></c:pt>`);
2392
+ }
2393
+ parts.push("</c:strCache>");
2394
+ return parts.join("");
2395
+ }
2396
+ function buildRawShapePropertiesXml(spPr, namespace) {
2397
+ if (!spPr) {
2398
+ return "";
2399
+ }
2400
+ if (spPr._rawXml) {
2401
+ return normalizeRawNamespace(spPr._rawXml, "spPr", namespace);
2402
+ }
2403
+ const writer = new writer_1.XmlWriter();
2404
+ const chartNamespace = namespace;
2405
+ writer.openNode(`${chartNamespace}:spPr`);
2406
+ if (spPr.fill?.noFill) {
2407
+ writer.leafNode("a:noFill");
2408
+ }
2409
+ else if (spPr.fill?.solid) {
2410
+ writer.openNode("a:solidFill");
2411
+ writeRawColor(writer, spPr.fill.solid);
2412
+ writer.closeNode();
2413
+ }
2414
+ else if (spPr.fill?.gradient) {
2415
+ writeRawGradientFill(writer, spPr.fill.gradient);
2416
+ }
2417
+ else if (spPr.fill?.pattern) {
2418
+ const pattern = spPr.fill.pattern;
2419
+ writer.openNode("a:pattFill", { prst: pattern.preset });
2420
+ if (pattern.foreground) {
2421
+ writer.openNode("a:fgClr");
2422
+ writeRawColor(writer, pattern.foreground);
2423
+ writer.closeNode();
2424
+ }
2425
+ if (pattern.background) {
2426
+ writer.openNode("a:bgClr");
2427
+ writeRawColor(writer, pattern.background);
2428
+ writer.closeNode();
2429
+ }
2430
+ writer.closeNode();
2431
+ }
2432
+ if (spPr.line) {
2433
+ const attrs = {};
2434
+ if (spPr.line.width) {
2435
+ attrs.w = String(spPr.line.width);
2436
+ }
2437
+ if (spPr.line.cap) {
2438
+ attrs.cap = spPr.line.cap;
2439
+ }
2440
+ if (spPr.line.compound) {
2441
+ attrs.cmpd = spPr.line.compound;
2442
+ }
2443
+ writer.openNode("a:ln", attrs);
2444
+ if (spPr.line.noFill) {
2445
+ writer.leafNode("a:noFill");
2446
+ }
2447
+ else if (spPr.line.color) {
2448
+ writer.openNode("a:solidFill");
2449
+ writeRawColor(writer, spPr.line.color);
2450
+ writer.closeNode();
2451
+ }
2452
+ if (spPr.line.dash) {
2453
+ writer.leafNode("a:prstDash", { val: spPr.line.dash });
2454
+ }
2455
+ if (spPr.line.join === "round") {
2456
+ writer.leafNode("a:round");
2457
+ }
2458
+ else if (spPr.line.join === "bevel") {
2459
+ writer.leafNode("a:bevel");
2460
+ }
2461
+ else if (spPr.line.join === "miter") {
2462
+ writer.leafNode("a:miter");
2463
+ }
2464
+ writer.closeNode();
2465
+ }
2466
+ if (spPr.effectList) {
2467
+ writeRawEffectList(writer, spPr.effectList);
2468
+ }
2469
+ if (spPr.scene3d) {
2470
+ writeRawScene3D(writer, spPr.scene3d);
2471
+ }
2472
+ if (spPr.sp3d) {
2473
+ writeRawSp3D(writer, spPr.sp3d);
2474
+ }
2475
+ writer.closeNode();
2476
+ return writer.toString();
2477
+ }
2478
+ function buildRawTextPropertiesXml(txPr, namespace) {
2479
+ if (!txPr) {
2480
+ return "";
2481
+ }
2482
+ if (typeof txPr === "string") {
2483
+ return normalizeRawNamespace(txPr, "txPr", namespace);
2484
+ }
2485
+ if (txPr._rawXml) {
2486
+ return normalizeRawNamespace(txPr._rawXml, "txPr", namespace);
2487
+ }
2488
+ const writer = new writer_1.XmlWriter();
2489
+ writer.openNode(`${namespace}:txPr`);
2490
+ writer.leafNode("a:bodyPr", txPr.rotation !== undefined ? { rot: String(txPr.rotation) } : undefined);
2491
+ writer.leafNode("a:lstStyle");
2492
+ writer.openNode("a:p");
2493
+ writer.openNode("a:pPr");
2494
+ writeRawRunProperties(writer, txPr, "a:defRPr");
2495
+ writer.closeNode();
2496
+ writer.leafNode("a:endParaRPr");
2497
+ writer.closeNode();
2498
+ writer.closeNode();
2499
+ return writer.toString();
2500
+ }
2501
+ function normalizeRawNamespace(rawXml, localName, namespace) {
2502
+ return rawXml
2503
+ .replace(new RegExp(`^<(?:c|cx):${localName}`), `<${namespace}:${localName}`)
2504
+ .replace(new RegExp(`</(?:c|cx):${localName}>$`), `</${namespace}:${localName}>`);
2505
+ }
2506
+ function writeRawRunProperties(writer, props, tag) {
2507
+ const attrs = {};
2508
+ if (props.size !== undefined) {
2509
+ attrs.sz = String(props.size);
2510
+ }
2511
+ if (props.bold !== undefined) {
2512
+ attrs.b = props.bold ? "1" : "0";
2513
+ }
2514
+ if (props.italic !== undefined) {
2515
+ attrs.i = props.italic ? "1" : "0";
2516
+ }
2517
+ if (props.underline !== undefined) {
2518
+ attrs.u =
2519
+ typeof props.underline === "boolean" ? (props.underline ? "sng" : "none") : props.underline;
2520
+ }
2521
+ if (props.strike) {
2522
+ attrs.strike = props.strike;
2523
+ }
2524
+ if (props.rotation !== undefined) {
2525
+ attrs.rot = String(props.rotation);
2526
+ }
2527
+ if (props.baseline !== undefined) {
2528
+ attrs.baseline = String(props.baseline);
2529
+ }
2530
+ if (props.kern !== undefined) {
2531
+ attrs.kern = String(props.kern);
2532
+ }
2533
+ if (props.spacing !== undefined) {
2534
+ attrs.spc = String(props.spacing);
2535
+ }
2536
+ if (props.cap) {
2537
+ attrs.cap = props.cap;
2538
+ }
2539
+ if (props.lang) {
2540
+ attrs.lang = props.lang;
2541
+ }
2542
+ const hasChildren = !!(props.color ||
2543
+ props.fontFamily ||
2544
+ props.eastAsianFamily ||
2545
+ props.complexScriptFamily);
2546
+ if (!hasChildren) {
2547
+ writer.leafNode(tag, attrs);
2548
+ return;
2549
+ }
2550
+ writer.openNode(tag, attrs);
2551
+ if (props.color) {
2552
+ writer.openNode("a:solidFill");
2553
+ writeRawColor(writer, props.color);
2554
+ writer.closeNode();
2555
+ }
2556
+ if (props.fontFamily) {
2557
+ writer.leafNode("a:latin", { typeface: props.fontFamily });
2558
+ }
2559
+ if (props.eastAsianFamily) {
2560
+ writer.leafNode("a:ea", { typeface: props.eastAsianFamily });
2561
+ }
2562
+ if (props.complexScriptFamily) {
2563
+ writer.leafNode("a:cs", { typeface: props.complexScriptFamily });
2564
+ }
2565
+ writer.closeNode();
2566
+ }
2567
+ function writeRawColor(writer, color) {
2568
+ const modifiers = buildRawColorModifiersXml(color);
2569
+ const writeColorNode = (tag, val) => {
2570
+ if (!modifiers) {
2571
+ writer.leafNode(tag, { val });
2572
+ return;
2573
+ }
2574
+ writer.openNode(tag, { val });
2575
+ writer.writeRaw(modifiers);
2576
+ writer.closeNode();
2577
+ };
2578
+ if (color.srgb) {
2579
+ writeColorNode("a:srgbClr", color.srgb);
2580
+ }
2581
+ else if (color.theme !== undefined) {
2582
+ const themeNames = [
2583
+ "dk1",
2584
+ "lt1",
2585
+ "dk2",
2586
+ "lt2",
2587
+ "accent1",
2588
+ "accent2",
2589
+ "accent3",
2590
+ "accent4",
2591
+ "accent5",
2592
+ "accent6",
2593
+ "hlink",
2594
+ "folHlink"
2595
+ ];
2596
+ writeColorNode("a:schemeClr", themeNames[color.theme] ?? "dk1");
2597
+ }
2598
+ else if (color.schemeName) {
2599
+ // Unknown scheme colour tokens (e.g. `phClr`, vendor extensions)
2600
+ // round-trip as `<a:schemeClr>` — keeping the element identity
2601
+ // intact. Previously these fell through to `<a:sysClr>` via the
2602
+ // parser, silently changing the DrawingML colour kind.
2603
+ writeColorNode("a:schemeClr", color.schemeName);
2604
+ }
2605
+ else if (color.sysClr) {
2606
+ writeColorNode("a:sysClr", color.sysClr);
2607
+ }
2608
+ else if (color.prstClr) {
2609
+ writeColorNode("a:prstClr", color.prstClr);
2610
+ }
2611
+ }
2612
+ function writeRawGradientFill(writer, gradient) {
2613
+ if (!Array.isArray(gradient.stops) || gradient.stops.length < 2) {
2614
+ return;
2615
+ }
2616
+ writer.openNode("a:gradFill");
2617
+ writer.openNode("a:gsLst");
2618
+ for (const stop of gradient.stops) {
2619
+ // OOXML `<a:gs pos>` is hundredths of a percent (0–100000). See
2620
+ // the matching fixes in `chart-space-xform.ts` and
2621
+ // `chart-ex-renderer.ts`; the previous `×1000` multiplier was
2622
+ // 100× too small and produced gradients in Excel at wildly
2623
+ // wrong positions.
2624
+ const encoded = Math.max(0, Math.min(100000, Math.round(stop.position * 100000)));
2625
+ writer.openNode("a:gs", { pos: String(encoded) });
2626
+ writeRawColor(writer, stop.color);
2627
+ writer.closeNode();
2628
+ }
2629
+ writer.closeNode();
2630
+ if (gradient.type === "circle" || gradient.type === "rect" || gradient.type === "shape") {
2631
+ // Preserve parsed `fillToRect` focal rectangle when present;
2632
+ // default to Excel's centred form (all components at 50%).
2633
+ // `CT_FillToRectangle` sides are `ST_Percentage`, which permits
2634
+ // negative values (focal point outside the shape). Don't clamp
2635
+ // to `[0, 100000]` — negative focal points were being lost on
2636
+ // round-trip before this fix.
2637
+ const rect = gradient.fillToRect;
2638
+ const pct = (v, def) => {
2639
+ if (v === undefined) {
2640
+ return def;
2641
+ }
2642
+ return Math.round(v * 100000);
2643
+ };
2644
+ writer.openNode("a:path", { path: gradient.type });
2645
+ writer.leafNode("a:fillToRect", {
2646
+ l: String(pct(rect?.left, 50000)),
2647
+ t: String(pct(rect?.top, 50000)),
2648
+ r: String(pct(rect?.right, 50000)),
2649
+ b: String(pct(rect?.bottom, 50000))
2650
+ });
2651
+ writer.closeNode();
2652
+ }
2653
+ else {
2654
+ // Emit `scaled` only when the author explicitly set it; mirrors
2655
+ // the structured ChartEx renderer (chart-ex-renderer.ts line
2656
+ // 4782) so both paths produce the same bytes. Previously this
2657
+ // raw writer unconditionally stamped `scaled="1"`, which
2658
+ // overwrote a parsed `scaled="0"` on round-trip — a visible
2659
+ // drift for gradients with the shape-independent orientation
2660
+ // mode. The OOXML default is `false` per `CT_LinearShadeProperties`,
2661
+ // so omitting it when absent is lossless.
2662
+ const linAttrs = {
2663
+ ang: String(Math.round((gradient.angle ?? 0) * 60000))
2664
+ };
2665
+ if (gradient.scaled !== undefined) {
2666
+ linAttrs.scaled = gradient.scaled ? "1" : "0";
2667
+ }
2668
+ writer.leafNode("a:lin", linAttrs);
2669
+ }
2670
+ writer.closeNode();
2671
+ }
2672
+ function writeRawEffectList(writer, effects) {
2673
+ writer.openNode("a:effectLst");
2674
+ if (effects.blur) {
2675
+ const attrs = {};
2676
+ if (effects.blur.radius !== undefined) {
2677
+ attrs.rad = String(effects.blur.radius);
2678
+ }
2679
+ if (effects.blur.grow !== undefined) {
2680
+ attrs.grow = effects.blur.grow ? "1" : "0";
2681
+ }
2682
+ writer.leafNode("a:blur", attrs);
2683
+ }
2684
+ if (effects.outerShadow) {
2685
+ writeRawShadow(writer, "a:outerShdw", effects.outerShadow);
2686
+ }
2687
+ if (effects.innerShadow) {
2688
+ writeRawShadow(writer, "a:innerShdw", effects.innerShadow);
2689
+ }
2690
+ if (effects.presetShadow) {
2691
+ const ps = effects.presetShadow;
2692
+ const attrs = { prst: ps.preset };
2693
+ if (ps.distance !== undefined) {
2694
+ attrs.dist = String(ps.distance);
2695
+ }
2696
+ if (ps.direction !== undefined) {
2697
+ attrs.dir = String(ps.direction);
2698
+ }
2699
+ writer.openNode("a:prstShdw", attrs);
2700
+ if (ps.color) {
2701
+ writeRawColor(writer, ps.color);
2702
+ }
2703
+ writer.closeNode();
2704
+ }
2705
+ if (effects.glow) {
2706
+ writer.openNode("a:glow", { rad: String(effects.glow.radius) });
2707
+ writeRawColor(writer, effects.glow.color);
2708
+ writer.closeNode();
2709
+ }
2710
+ if (effects.softEdge) {
2711
+ writer.leafNode("a:softEdge", { rad: String(effects.softEdge.radius) });
2712
+ }
2713
+ if (effects.reflection) {
2714
+ const reflection = effects.reflection;
2715
+ const attrs = {};
2716
+ for (const [key, value] of [
2717
+ ["blurRad", reflection.blurRadius],
2718
+ ["stA", reflection.startOpacity],
2719
+ ["stPos", reflection.startPosition],
2720
+ ["endA", reflection.endOpacity],
2721
+ ["endPos", reflection.endPosition],
2722
+ ["dist", reflection.distance],
2723
+ ["dir", reflection.direction],
2724
+ ["fadeDir", reflection.fadeDirection],
2725
+ ["sx", reflection.scaleHorizontal],
2726
+ ["sy", reflection.scaleVertical],
2727
+ ["kx", reflection.skewHorizontal],
2728
+ ["ky", reflection.skewVertical],
2729
+ ["algn", reflection.alignment],
2730
+ ["rotWithShape", reflection.rotateWithShape]
2731
+ ]) {
2732
+ if (value !== undefined) {
2733
+ attrs[key] = typeof value === "boolean" ? (value ? "1" : "0") : String(value);
2734
+ }
2735
+ }
2736
+ writer.leafNode("a:reflection", attrs);
2737
+ }
2738
+ writer.closeNode();
2739
+ }
2740
+ function writeRawShadow(writer, tag, shadow) {
2741
+ const attrs = {};
2742
+ for (const [key, value] of [
2743
+ ["blurRad", shadow.blurRadius],
2744
+ ["dist", shadow.distance],
2745
+ ["dir", shadow.direction],
2746
+ ["algn", shadow.alignment],
2747
+ ["rotWithShape", shadow.rotateWithShape],
2748
+ ["sx", shadow.scaleHorizontal],
2749
+ ["sy", shadow.scaleVertical],
2750
+ ["kx", shadow.skewHorizontal],
2751
+ ["ky", shadow.skewVertical]
2752
+ ]) {
2753
+ if (value !== undefined) {
2754
+ attrs[key] = typeof value === "boolean" ? (value ? "1" : "0") : String(value);
2755
+ }
2756
+ }
2757
+ writer.openNode(tag, attrs);
2758
+ writeRawColor(writer, shadow.color);
2759
+ writer.closeNode();
2760
+ }
2761
+ function writeRawScene3D(writer, scene) {
2762
+ writer.openNode("a:scene3d");
2763
+ if (scene.camera) {
2764
+ const camera = scene.camera;
2765
+ const attrs = { prst: camera.preset };
2766
+ if (camera.fov !== undefined) {
2767
+ attrs.fov = String(camera.fov);
2768
+ }
2769
+ if (camera.zoom !== undefined) {
2770
+ attrs.zoom = String(camera.zoom);
2771
+ }
2772
+ if (camera.rotation) {
2773
+ writer.openNode("a:camera", attrs);
2774
+ writer.leafNode("a:rot", {
2775
+ lat: String(camera.rotation.lat),
2776
+ lon: String(camera.rotation.lon),
2777
+ rev: String(camera.rotation.rev)
2778
+ });
2779
+ writer.closeNode();
2780
+ }
2781
+ else {
2782
+ writer.leafNode("a:camera", attrs);
2783
+ }
2784
+ }
2785
+ if (scene.lightRig) {
2786
+ const lightRig = scene.lightRig;
2787
+ const attrs = { rig: lightRig.rig, dir: lightRig.direction };
2788
+ if (lightRig.rotation) {
2789
+ writer.openNode("a:lightRig", attrs);
2790
+ writer.leafNode("a:rot", {
2791
+ lat: String(lightRig.rotation.lat),
2792
+ lon: String(lightRig.rotation.lon),
2793
+ rev: String(lightRig.rotation.rev)
2794
+ });
2795
+ writer.closeNode();
2796
+ }
2797
+ else {
2798
+ writer.leafNode("a:lightRig", attrs);
2799
+ }
2800
+ }
2801
+ writer.closeNode();
2802
+ }
2803
+ function writeRawSp3D(writer, sp3d) {
2804
+ const attrs = {};
2805
+ if (sp3d.z !== undefined) {
2806
+ attrs.z = String(sp3d.z);
2807
+ }
2808
+ if (sp3d.extrusionHeight !== undefined) {
2809
+ attrs.extrusionH = String(sp3d.extrusionHeight);
2810
+ }
2811
+ if (sp3d.contourWidth !== undefined) {
2812
+ attrs.contourW = String(sp3d.contourWidth);
2813
+ }
2814
+ if (sp3d.material) {
2815
+ attrs.prstMaterial = sp3d.material;
2816
+ }
2817
+ const hasChildren = !!(sp3d.bevelTop ||
2818
+ sp3d.bevelBottom ||
2819
+ sp3d.extrusionColor ||
2820
+ sp3d.contourColor);
2821
+ if (!hasChildren) {
2822
+ writer.leafNode("a:sp3d", attrs);
2823
+ return;
2824
+ }
2825
+ writer.openNode("a:sp3d", attrs);
2826
+ if (sp3d.bevelTop) {
2827
+ writeRawBevel(writer, "a:bevelT", sp3d.bevelTop);
2828
+ }
2829
+ if (sp3d.bevelBottom) {
2830
+ writeRawBevel(writer, "a:bevelB", sp3d.bevelBottom);
2831
+ }
2832
+ if (sp3d.extrusionColor) {
2833
+ writer.openNode("a:extrusionClr");
2834
+ writeRawColor(writer, sp3d.extrusionColor);
2835
+ writer.closeNode();
2836
+ }
2837
+ if (sp3d.contourColor) {
2838
+ writer.openNode("a:contourClr");
2839
+ writeRawColor(writer, sp3d.contourColor);
2840
+ writer.closeNode();
2841
+ }
2842
+ writer.closeNode();
2843
+ }
2844
+ function writeRawBevel(writer, tag, bevel) {
2845
+ const attrs = {};
2846
+ if (bevel.width !== undefined) {
2847
+ attrs.w = String(bevel.width);
2848
+ }
2849
+ if (bevel.height !== undefined) {
2850
+ attrs.h = String(bevel.height);
2851
+ }
2852
+ if (bevel.preset) {
2853
+ attrs.prst = bevel.preset;
2854
+ }
2855
+ writer.leafNode(tag, attrs);
2856
+ }
2857
+ function buildRawColorModifiersXml(color) {
2858
+ // Each modifier must serialise as `<a:* val="N"/>` where `N` is a
2859
+ // valid `xsd:int`. Previously the raw patcher interpolated model
2860
+ // values directly, so `NaN` / `Infinity` / unrounded floats leaked
2861
+ // into the attribute and Excel's strict reader rejected the file
2862
+ // with "invalid attribute value for xs:int". The structured renderer
2863
+ // (`renderColorModifiers` in chart-ex-renderer.ts) guards with
2864
+ // `Number.isFinite` + `Math.round` — mirror that here so both write
2865
+ // paths produce identical bytes, then share the helper.
2866
+ const parts = [];
2867
+ const emitInt = (tag, value) => {
2868
+ if (value === undefined || !Number.isFinite(value)) {
2869
+ return;
2870
+ }
2871
+ parts.push(`<a:${tag} val="${Math.round(value)}"/>`);
2872
+ };
2873
+ emitInt("alpha", color.alpha);
2874
+ // `tint` on the public `ChartColor` is a 0..1 fraction; convert to
2875
+ // the DrawingML 0..100000 per-thousand integer here. DrawingML also
2876
+ // permits NEGATIVE tint (shade toward black) per
2877
+ // `CT_PositiveFixedPercentage` — the structured path preserves the
2878
+ // sign, so we do too.
2879
+ if (color.tint !== undefined && Number.isFinite(color.tint)) {
2880
+ parts.push(`<a:tint val="${Math.round(color.tint * 100000)}"/>`);
2881
+ }
2882
+ emitInt("shade", color.shade);
2883
+ emitInt("satMod", color.satMod);
2884
+ emitInt("lumMod", color.lumMod);
2885
+ emitInt("lumOff", color.lumOff);
2886
+ return parts.join("");
2887
+ }
2888
+ function patchRawChartExSeries(raw, chart, patchPlan) {
2889
+ const seriesModels = extractChartExSeries({ chartSpace: { chart } });
2890
+ let index = 0;
2891
+ return replaceXmlBlocks(raw, "cx:series", block => {
2892
+ const series = seriesModels[index++];
2893
+ const seriesPlan = getRawPatchListItem(patchPlan.series, index - 1);
2894
+ return series && seriesPlan ? patchRawChartExSeriesBlock(block, series, seriesPlan) : block;
2895
+ });
2896
+ }
2897
+ function patchRawChartExSeriesBlock(block, series, patchPlan) {
2898
+ // Child sequence per Chart2014 `CT_Series`:
2899
+ //
2900
+ // tx? → spPr? → txPr? → valueColors? → valueColorPositions? →
2901
+ // dataPt* → dataLabels? → dataId* → layoutPr? → axisId* → extLst?
2902
+ //
2903
+ // The sibling arrays below describe the elements that must come
2904
+ // AFTER the element being inserted so `replaceOrInsertBeforeGeneric`
2905
+ // can splice into the right position. Previous versions used
2906
+ // sibling lists that put `dataId` before `dataLabels` / `dataPt` —
2907
+ // reversing the schema order and producing files strict validators
2908
+ // reject. Use the real schema order so raw-patch output matches
2909
+ // what `renderSeries` produces for the same model.
2910
+ const afterTx = [
2911
+ "cx:spPr",
2912
+ "cx:txPr",
2913
+ "cx:valueColors",
2914
+ "cx:valueColorPositions",
2915
+ "cx:dataPt",
2916
+ "cx:dataLabels",
2917
+ "cx:dataId",
2918
+ "cx:layoutPr",
2919
+ "cx:axisId",
2920
+ "cx:extLst"
2921
+ ];
2922
+ const afterSpPr = [
2923
+ "cx:txPr",
2924
+ "cx:valueColors",
2925
+ "cx:valueColorPositions",
2926
+ "cx:dataPt",
2927
+ "cx:dataLabels",
2928
+ "cx:dataId",
2929
+ "cx:layoutPr",
2930
+ "cx:axisId",
2931
+ "cx:extLst"
2932
+ ];
2933
+ const afterDataPt = ["cx:dataLabels", "cx:dataId", "cx:layoutPr", "cx:axisId", "cx:extLst"];
2934
+ const afterDataLabels = ["cx:dataId", "cx:layoutPr", "cx:axisId", "cx:extLst"];
2935
+ const afterDataId = ["cx:layoutPr", "cx:axisId", "cx:extLst"];
2936
+ const afterLayoutPr = ["cx:axisId", "cx:extLst"];
2937
+ const afterAxisId = ["cx:extLst"];
2938
+ let patched = block;
2939
+ if (rawPatchFlag(patchPlan, "hidden")) {
2940
+ patched = patchOpeningTagBooleanAttribute(patched, "cx:series", "hidden", series.hidden);
2941
+ }
2942
+ if (rawPatchFlag(patchPlan, "ownerIdx")) {
2943
+ patched = patchOpeningTagIntegerAttribute(patched, "cx:series", "ownerIdx", series.ownerIdx);
2944
+ }
2945
+ if (rawPatchFlag(patchPlan, "tx")) {
2946
+ patched = patchGenericChild(patched, "cx:tx", buildRawChartExSeriesTxXml(series.tx), afterTx, "cx:series");
2947
+ }
2948
+ if (rawPatchFlag(patchPlan, "spPr")) {
2949
+ patched = patchGenericChild(patched, "cx:spPr", buildRawShapePropertiesXml(series.spPr, "cx"), afterSpPr, "cx:series");
2950
+ }
2951
+ if (rawPatchFlag(patchPlan, "dataPoints")) {
2952
+ const dataPointXml = (series.dataPt ?? [])
2953
+ .map((point) => {
2954
+ const spPrXml = buildRawShapePropertiesXml(point.spPr, "cx") ?? "";
2955
+ return `<cx:dataPt idx="${point.idx}">${spPrXml}</cx:dataPt>`;
2956
+ })
2957
+ .join("");
2958
+ patched = patchRepeatingChildren(patched, "cx:dataPt", dataPointXml, afterDataPt, "cx:series");
2959
+ }
2960
+ if (rawPatchFlag(patchPlan, "dataLabels")) {
2961
+ patched = patchGenericChild(patched, "cx:dataLabels", buildRawChartExDataLabelsXml(series.dataLabels), afterDataLabels, "cx:series");
2962
+ }
2963
+ if (rawPatchFlag(patchPlan, "dataRefs")) {
2964
+ const dataRefsXml = (series.dataRefs ?? [])
2965
+ .map((ref) => ref.dataId !== undefined
2966
+ ? `<cx:dataId val="${ref.dataId}"/>`
2967
+ : ref.axisId !== undefined
2968
+ ? `<cx:axisId val="${ref.axisId}"/>`
2969
+ : "")
2970
+ .join("");
2971
+ patched = patchRepeatingChildren(patched, "cx:dataId", dataRefsXml, afterDataId, "cx:series");
2972
+ }
2973
+ if (rawPatchFlag(patchPlan, "layoutPr")) {
2974
+ patched = patchGenericChild(patched, "cx:layoutPr", buildRawChartExLayoutPropertiesXml(series.layoutId, series.layoutPr), afterLayoutPr, "cx:series");
2975
+ }
2976
+ if (rawPatchFlag(patchPlan, "axisId")) {
2977
+ const axisIdsXml = (series.axisId ?? [])
2978
+ .map((id) => `<cx:axisId val="${id}"/>`)
2979
+ .join("");
2980
+ patched = patchRepeatingChildren(patched, "cx:axisId", axisIdsXml, afterAxisId, "cx:series");
2981
+ }
2982
+ return patched;
2983
+ }
2984
+ function buildRawChartExSeriesTxXml(tx) {
2985
+ if (!tx) {
2986
+ return "";
2987
+ }
2988
+ if (tx.rich) {
2989
+ // Round-trip parity with the structured writer — ChartEx series
2990
+ // names authored as rich text (per-run formatting, bold / colour
2991
+ // / font-family overrides) used to be silently dropped by the raw
2992
+ // patcher: only `tx.value` and `tx.strRef` were handled, so a
2993
+ // mutation that preserved `tx.rich` on the model would re-emit
2994
+ // `<cx:tx/>` without a `<cx:rich>` child, collapsing the label to
2995
+ // an unstyled placeholder. Emit a minimal `<cx:tx><cx:rich>…`
2996
+ // subtree carrying the paragraph / run structure. The rPr helper
2997
+ // is a pragmatic subset (size / bold / italic / color) — features
2998
+ // beyond that flight through the structured path, which is the
2999
+ // default when `preferRawPatch` isn't opt-in.
3000
+ return `<cx:tx>${buildRawChartExRichTextXml(tx.rich)}</cx:tx>`;
3001
+ }
3002
+ if (tx.value !== undefined) {
3003
+ return `<cx:tx><cx:txData><cx:v>${escapeXml(String(tx.value))}</cx:v></cx:txData></cx:tx>`;
3004
+ }
3005
+ if (tx.strRef !== undefined) {
3006
+ // `tx.strRef` is declared as `string | { formula: string; cached?: string }`
3007
+ // on `ChartExSeries.tx`. The previous writer coerced via
3008
+ // `String(tx.strRef)`, which produced the literal `"[object Object]"`
3009
+ // for the structured form — silently corrupting the formula on every
3010
+ // series that carried a `{ formula, cached }` pair through the raw
3011
+ // patch path.
3012
+ let formula;
3013
+ let cached;
3014
+ if (typeof tx.strRef === "string") {
3015
+ formula = tx.strRef;
3016
+ }
3017
+ else if (tx.strRef &&
3018
+ typeof tx.strRef === "object" &&
3019
+ typeof tx.strRef.formula === "string") {
3020
+ formula = tx.strRef.formula;
3021
+ cached = typeof tx.strRef.cached === "string" ? tx.strRef.cached : undefined;
3022
+ }
3023
+ else {
3024
+ // Degenerate shape (unknown form) — drop the element rather than
3025
+ // emit `<cx:f>[object Object]</cx:f>` and corrupt the formula.
3026
+ return "";
3027
+ }
3028
+ const cachedEl = cached !== undefined ? `<cx:v>${escapeXml(cached)}</cx:v>` : "";
3029
+ return `<cx:tx><cx:txData><cx:f>${escapeXml(formula)}</cx:f>${cachedEl}</cx:txData></cx:tx>`;
3030
+ }
3031
+ return "";
3032
+ }
3033
+ /**
3034
+ * Minimal `<cx:rich>` emitter used by the ChartEx raw patcher when a
3035
+ * series `tx` carries a `rich` paragraph tree. Mirrors the structured
3036
+ * renderer's output shape (`renderRichText` in `chart-ex-renderer`)
3037
+ * for the attributes the raw patch path needs — size / bold / italic
3038
+ * and the text colour — so round-trip parity is preserved for the
3039
+ * common "bold label" case. Features outside this subset (mixed font
3040
+ * families, east-Asian runs, paragraph properties) flow through the
3041
+ * structured writer, which the mutation helper invokes by default;
3042
+ * `preferRawPatch` callers who need the full set should stay on
3043
+ * structural rebuilds.
3044
+ */
3045
+ function buildRawChartExRichTextXml(rich) {
3046
+ if (!rich || !Array.isArray(rich.paragraphs)) {
3047
+ return "";
3048
+ }
3049
+ const parts = ["<cx:rich>", "<a:bodyPr/>", "<a:lstStyle/>"];
3050
+ for (const p of rich.paragraphs) {
3051
+ parts.push("<a:p>");
3052
+ for (const run of p.runs ?? []) {
3053
+ const rPr = buildRawChartExRunPropertiesXml(run.properties);
3054
+ // Preserve significant whitespace — matches the structured
3055
+ // writer's `xml:space="preserve"` rule (see `needsXmlSpacePreserve`).
3056
+ const text = typeof run.text === "string" ? run.text : "";
3057
+ const needsPreserve = /^\s|\s$|[\t\n\r]/.test(text);
3058
+ const tAttrs = needsPreserve ? ' xml:space="preserve"' : "";
3059
+ parts.push(`<a:r>${rPr}<a:t${tAttrs}>${escapeXml(text)}</a:t></a:r>`);
3060
+ }
3061
+ parts.push('<a:endParaRPr lang="en-US"/>');
3062
+ parts.push("</a:p>");
3063
+ }
3064
+ parts.push("</cx:rich>");
3065
+ return parts.join("");
3066
+ }
3067
+ function buildRawChartExRunPropertiesXml(props) {
3068
+ if (!props || typeof props !== "object") {
3069
+ return "";
3070
+ }
3071
+ const attrs = [];
3072
+ if (typeof props.size === "number" && Number.isFinite(props.size)) {
3073
+ attrs.push(`sz="${props.size}"`);
3074
+ }
3075
+ if (props.bold !== undefined) {
3076
+ attrs.push(`b="${props.bold ? 1 : 0}"`);
3077
+ }
3078
+ if (props.italic !== undefined) {
3079
+ attrs.push(`i="${props.italic ? 1 : 0}"`);
3080
+ }
3081
+ // Inline colour child only — the full `<a:solidFill>` emitter is
3082
+ // intentionally out of scope for the raw patcher (structural
3083
+ // rebuild handles anything beyond srgbClr / theme).
3084
+ const color = props.color;
3085
+ let colorChild = "";
3086
+ if (color && typeof color === "object") {
3087
+ if (typeof color.srgb === "string") {
3088
+ colorChild = `<a:solidFill><a:srgbClr val="${escapeAttr(color.srgb)}"/></a:solidFill>`;
3089
+ }
3090
+ else if (typeof color.theme === "number") {
3091
+ // `color.theme` is a 0-based index into the workbook's theme
3092
+ // palette — 0..3 are bg/lt1/dk2/lt2, 4..9 are accent1..accent6,
3093
+ // 10..11 are hlink / folHlink. The previous implementation
3094
+ // emitted `accent${color.theme}`, which produced nonsense
3095
+ // (`accent4` for `theme=4` instead of `accent1`; `accent0` for
3096
+ // `theme=0` which is not even a valid DrawingML scheme slot).
3097
+ // Route through the canonical helper shared with the
3098
+ // structural emitters so the mapping stays in one place.
3099
+ colorChild = `<a:solidFill><a:schemeClr val="${escapeAttr((0, chart_host_registry_1.getChartSupport)().themeIndexToName(color.theme))}"/></a:solidFill>`;
3100
+ }
3101
+ }
3102
+ if (attrs.length === 0 && !colorChild) {
3103
+ return "";
3104
+ }
3105
+ const attrStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
3106
+ return colorChild ? `<a:rPr${attrStr}>${colorChild}</a:rPr>` : `<a:rPr${attrStr}/>`;
3107
+ }
3108
+ function buildRawChartExLayoutPropertiesXml(layoutId, layoutPr) {
3109
+ if (!layoutPr) {
3110
+ return "";
3111
+ }
3112
+ if (layoutPr._rawXml && !hasStructuredChartExLayoutProperties(layoutPr)) {
3113
+ return layoutPr._rawXml;
3114
+ }
3115
+ const parts = ["<cx:layoutPr>"];
3116
+ if (layoutPr.parentLabelLayout && (layoutId === "sunburst" || layoutId === "treemap")) {
3117
+ parts.push(`<cx:parentLabelLayout val="${escapeAttr(layoutPr.parentLabelLayout)}"/>`);
3118
+ }
3119
+ if (layoutPr.subtotals && layoutId === "waterfall") {
3120
+ parts.push("<cx:subtotals>");
3121
+ for (const subtotal of layoutPr.subtotals) {
3122
+ parts.push(`<cx:subtotal idx="${subtotal.idx}"/>`);
3123
+ }
3124
+ parts.push("</cx:subtotals>");
3125
+ }
3126
+ if (layoutId === "waterfall" && layoutPr.connectorLines !== undefined) {
3127
+ parts.push(`<cx:connectorLines val="${layoutPr.connectorLines ? "1" : "0"}"/>`);
3128
+ }
3129
+ if (layoutPr.binning) {
3130
+ const binning = layoutPr.binning;
3131
+ const attrs = [
3132
+ binning.intervalClosed === "l" || binning.intervalClosed === "r"
3133
+ ? `intervalClosed="${escapeAttr(binning.intervalClosed)}"`
3134
+ : undefined,
3135
+ binning.underflow !== undefined && Number.isFinite(binning.underflow)
3136
+ ? `underflow="${binning.underflow}"`
3137
+ : undefined,
3138
+ binning.overflow !== undefined && Number.isFinite(binning.overflow)
3139
+ ? `overflow="${binning.overflow}"`
3140
+ : undefined
3141
+ ].filter((attr) => !!attr);
3142
+ parts.push(`<cx:binning${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>`);
3143
+ // CT_Binning schema order: choice(auto|categories|manual)
3144
+ // followed by optional binSize and binCount. Previously the raw
3145
+ // patcher emitted `<cx:auto/>` then `<cx:binSize/>` then
3146
+ // `<cx:binCount/>` then `<cx:categories/>` / `<cx:manual/>` — but
3147
+ // `categories`/`manual` are mutually exclusive with `auto`, and
3148
+ // emitting them after binSize/binCount puts them out of the
3149
+ // schema sequence. The parser's priority chain at
3150
+ // `chart-ex-parser.ts:parseLayoutProperties` resolves
3151
+ // auto > categories > manual, so the stray trailing elements
3152
+ // never round-tripped back anyway. Mirror the structured
3153
+ // renderer's order: one discriminator first, then the numeric
3154
+ // children.
3155
+ if (binning.binType === "auto") {
3156
+ parts.push("<cx:auto/>");
3157
+ }
3158
+ else if (binning.binType === "categories") {
3159
+ parts.push("<cx:categories/>");
3160
+ }
3161
+ else if (binning.binType === "manual") {
3162
+ parts.push("<cx:manual/>");
3163
+ }
3164
+ if (binning.binSize !== undefined && Number.isFinite(binning.binSize)) {
3165
+ parts.push(`<cx:binSize val="${binning.binSize}"/>`);
3166
+ }
3167
+ if (binning.binCount !== undefined && Number.isFinite(binning.binCount)) {
3168
+ parts.push(`<cx:binCount val="${binning.binCount}"/>`);
3169
+ }
3170
+ parts.push("</cx:binning>");
3171
+ }
3172
+ // `paretoLine` is only a valid child when the enclosing layout is a
3173
+ // pareto (clusteredColumn with pareto overlay, or the standalone
3174
+ // `paretoLine` layoutId). Emit the explicit boolean — including
3175
+ // `false` — so round-trip of user-suppressed pareto overlays
3176
+ // matches the structured writer. Previously `if (layoutPr.paretoLine)`
3177
+ // silently dropped the `false` case, re-enabling the line on save.
3178
+ if (layoutPr.paretoLine !== undefined &&
3179
+ (layoutId === "clusteredColumn" || layoutId === "paretoLine")) {
3180
+ parts.push(`<cx:paretoLine val="${layoutPr.paretoLine ? "1" : "0"}"/>`);
3181
+ }
3182
+ if (layoutId === "boxWhisker") {
3183
+ for (const [name, value] of [
3184
+ ["quartileMethod", layoutPr.quartileMethod],
3185
+ ["showMeanLine", layoutPr.showMeanLine],
3186
+ ["showMeanMarker", layoutPr.showMeanMarker],
3187
+ ["showInnerPoints", layoutPr.showInnerPoints],
3188
+ ["showOutlierPoints", layoutPr.showOutlierPoints]
3189
+ ]) {
3190
+ if (value !== undefined) {
3191
+ parts.push(`<cx:${name} val="${typeof value === "boolean" ? (value ? "1" : "0") : escapeAttr(String(value))}"/>`);
3192
+ }
3193
+ }
3194
+ }
3195
+ if (layoutId === "regionMap") {
3196
+ for (const [name, value] of [
3197
+ ["projection", layoutPr.projection],
3198
+ ["regionLabels", layoutPr.regionLabels],
3199
+ ["geoMappingLevel", layoutPr.geoMappingLevel]
3200
+ ]) {
3201
+ if (value !== undefined) {
3202
+ parts.push(`<cx:${name} val="${escapeAttr(String(value))}"/>`);
3203
+ }
3204
+ }
3205
+ }
3206
+ if (layoutPr.extLst) {
3207
+ parts.push(layoutPr.extLst);
3208
+ }
3209
+ parts.push("</cx:layoutPr>");
3210
+ return parts.join("");
3211
+ }
3212
+ function hasStructuredChartExLayoutProperties(layoutPr) {
3213
+ // `increaseSpPr` / `decreaseSpPr` / `totalSpPr` are **preview-only**
3214
+ // fields consumed by the SVG/PDF renderer to colour waterfall bars;
3215
+ // Chart2014 has no schema slot for them (per-point styling lives on
3216
+ // `<cx:dataPt>` instead). Do NOT treat setting one as a "structured
3217
+ // mutation" — doing so would force the raw patcher onto the
3218
+ // structured rebuild path and discard `_rawXml`, silently dropping
3219
+ // every other property the raw bytes carried. The structured
3220
+ // renderer (`hasStructuredLayoutProperties` in chart-ex-renderer.ts)
3221
+ // uses the same exclusion list; keeping the two helpers in sync
3222
+ // prevents asymmetric behaviour between raw-patch and rebuild.
3223
+ return [
3224
+ layoutPr.parentLabelLayout,
3225
+ layoutPr.subtotals,
3226
+ layoutPr.connectorLines,
3227
+ layoutPr.binning,
3228
+ layoutPr.paretoLine,
3229
+ layoutPr.quartileMethod,
3230
+ layoutPr.showMeanLine,
3231
+ layoutPr.showMeanMarker,
3232
+ layoutPr.showInnerPoints,
3233
+ layoutPr.showOutlierPoints,
3234
+ layoutPr.projection,
3235
+ layoutPr.regionLabels,
3236
+ layoutPr.geoMappingLevel
3237
+ ].some(value => value !== undefined);
3238
+ }
3239
+ function buildRawChartExDataLabelsXml(dataLabels) {
3240
+ if (!dataLabels) {
3241
+ return "";
3242
+ }
3243
+ const parts = ["<cx:dataLabels>"];
3244
+ if (dataLabels.visibility) {
3245
+ const attrs = [
3246
+ dataLabels.visibility.seriesName !== undefined
3247
+ ? `seriesName="${dataLabels.visibility.seriesName ? "1" : "0"}"`
3248
+ : undefined,
3249
+ dataLabels.visibility.categoryName !== undefined
3250
+ ? `categoryName="${dataLabels.visibility.categoryName ? "1" : "0"}"`
3251
+ : undefined,
3252
+ dataLabels.visibility.value !== undefined
3253
+ ? `value="${dataLabels.visibility.value ? "1" : "0"}"`
3254
+ : undefined,
3255
+ dataLabels.visibility.numFmt !== undefined
3256
+ ? `numFmt="${dataLabels.visibility.numFmt ? "1" : "0"}"`
3257
+ : undefined
3258
+ ].filter((attr) => !!attr);
3259
+ parts.push(`<cx:visibility ${attrs.join(" ")}/>`);
3260
+ }
3261
+ if (dataLabels.position) {
3262
+ parts.push(`<cx:dataLabel pos="${escapeAttr(dataLabels.position)}"/>`);
3263
+ }
3264
+ if (dataLabels.separator) {
3265
+ parts.push(`<cx:separator>${escapeXml(String(dataLabels.separator))}</cx:separator>`);
3266
+ }
3267
+ if (dataLabels.numFmt) {
3268
+ parts.push(`<cx:numFmt formatCode="${escapeAttr(String(dataLabels.numFmt))}"/>`);
3269
+ }
3270
+ if (dataLabels.spPr) {
3271
+ parts.push(buildRawShapePropertiesXml(dataLabels.spPr, "cx") ?? "");
3272
+ }
3273
+ if (dataLabels.txPr) {
3274
+ parts.push(buildRawTextPropertiesXml(dataLabels.txPr, "cx") ?? "");
3275
+ }
3276
+ parts.push("</cx:dataLabels>");
3277
+ return parts.join("");
3278
+ }
3279
+ function patchRawChartExAxes(raw, chart, patchPlan) {
3280
+ let patched = raw;
3281
+ for (const [index, axis] of (chart.plotArea?.axis ?? []).entries()) {
3282
+ const axisPlan = getRawPatchListItem(patchPlan, index);
3283
+ if (!axisPlan) {
3284
+ continue;
3285
+ }
3286
+ const range = findChartExAxisBlock(patched, axis.axisId);
3287
+ if (!range) {
3288
+ return undefined;
3289
+ }
3290
+ const axisXml = patchRawChartExAxisBlock(range.xml, axis, axisPlan);
3291
+ patched = patched.slice(0, range.start) + axisXml + patched.slice(range.end);
3292
+ }
3293
+ return patched;
3294
+ }
3295
+ function patchRawChartExAxisBlock(block, axis, patchPlan) {
3296
+ // `CT_Axis` child sequence (Chart2014):
3297
+ //
3298
+ // (catScaling | valScaling) → title → units →
3299
+ // majorTickMarks → minorTickMarks →
3300
+ // majorGridlines → minorGridlines →
3301
+ // numFmt → txPr → spPr → extLst
3302
+ //
3303
+ // (The structured renderer emits `txPr` before `spPr` to match
3304
+ // Excel's real output; some schema mirrors put spPr first, but
3305
+ // Excel itself serialises txPr first and readers accept both. The
3306
+ // raw patcher mirrors the structured renderer so both paths land
3307
+ // byte-identical XML for the same model.)
3308
+ //
3309
+ // Sibling lists describe every element that must come AFTER the
3310
+ // element being inserted. Older versions of the patcher used
3311
+ // sibling arrays that put `majorTickMarks` before
3312
+ // `title`/`valScaling`/`catScaling`, inverting the schema — strict
3313
+ // validators rejected the output and Excel's own reader silently
3314
+ // dropped whichever element landed out of position.
3315
+ const afterScaling = [
3316
+ "cx:title",
3317
+ "cx:units",
3318
+ "cx:majorTickMarks",
3319
+ "cx:majorTickMark",
3320
+ "cx:minorTickMarks",
3321
+ "cx:minorTickMark",
3322
+ "cx:majorGridlines",
3323
+ "cx:minorGridlines",
3324
+ "cx:numFmt",
3325
+ "cx:txPr",
3326
+ "cx:spPr",
3327
+ "cx:extLst"
3328
+ ];
3329
+ const afterTitle = [
3330
+ "cx:units",
3331
+ "cx:majorTickMarks",
3332
+ "cx:majorTickMark",
3333
+ "cx:minorTickMarks",
3334
+ "cx:minorTickMark",
3335
+ "cx:majorGridlines",
3336
+ "cx:minorGridlines",
3337
+ "cx:numFmt",
3338
+ "cx:txPr",
3339
+ "cx:spPr",
3340
+ "cx:extLst"
3341
+ ];
3342
+ const afterMajorTicks = [
3343
+ "cx:minorTickMarks",
3344
+ "cx:minorTickMark",
3345
+ "cx:majorGridlines",
3346
+ "cx:minorGridlines",
3347
+ "cx:numFmt",
3348
+ "cx:txPr",
3349
+ "cx:spPr",
3350
+ "cx:extLst"
3351
+ ];
3352
+ const afterMinorTicks = [
3353
+ "cx:majorGridlines",
3354
+ "cx:minorGridlines",
3355
+ "cx:numFmt",
3356
+ "cx:txPr",
3357
+ "cx:spPr",
3358
+ "cx:extLst"
3359
+ ];
3360
+ const afterNumFmt = ["cx:txPr", "cx:spPr", "cx:extLst"];
3361
+ const afterTxPr = ["cx:spPr", "cx:extLst"];
3362
+ const afterSpPr = ["cx:extLst"];
3363
+ let patched = block;
3364
+ if (rawPatchFlag(patchPlan, "hidden")) {
3365
+ // `CT_Axis/@hidden` is an **attribute** on the opening `<cx:axis>`
3366
+ // tag per ECMA-376 Chart2014, not a child element. Previously
3367
+ // this raw-patch path emitted `<cx:hidden val="1"/>` as a child,
3368
+ // which strict validators reject. Replay the mutation as an
3369
+ // attribute tweak on the opening tag. When `axis.hidden` is
3370
+ // `undefined` the attribute is removed entirely; explicit `false`
3371
+ // lands `hidden="0"` so files that carried an affirmative
3372
+ // visibility marker round-trip byte-identically.
3373
+ patched = patchXmlAttribute(patched, "cx:axis", "hidden", axis.hidden);
3374
+ // Clean up any stale child `<cx:hidden/>` bytes left over from
3375
+ // legacy output that predated the attribute rewrite — the parser
3376
+ // accepts both forms (see `chart-ex-parser.ts:parseAxis`), so
3377
+ // round-tripping an older file must eliminate the legacy form.
3378
+ patched = removeXmlBlock(patched, "cx:hidden");
3379
+ }
3380
+ if (rawPatchFlag(patchPlan, "valScaling")) {
3381
+ patched = patchGenericChild(patched, "cx:valScaling", buildRawChartExScalingXml("valScaling", axis.valScaling), afterScaling, "cx:axis");
3382
+ }
3383
+ if (rawPatchFlag(patchPlan, "catScaling")) {
3384
+ patched = patchGenericChild(patched, "cx:catScaling", buildRawChartExScalingXml("catScaling", axis.catScaling), afterScaling, "cx:axis");
3385
+ }
3386
+ if (rawPatchFlag(patchPlan, "title")) {
3387
+ if (axis.title) {
3388
+ const text = axis.title.text?.paragraphs?.[0]?.runs?.[0]?.text;
3389
+ if (text !== undefined) {
3390
+ patched = patchGenericChild(patched, "cx:title", buildRawChartExTitleXml(text), afterTitle, "cx:axis");
3391
+ }
3392
+ }
3393
+ else {
3394
+ patched = removeXmlBlock(patched, "cx:title");
3395
+ }
3396
+ }
3397
+ if (rawPatchFlag(patchPlan, "majorTickMark")) {
3398
+ // `cx:majorTickMark` in the Chart2014 schema is the **plural**
3399
+ // `majorTickMarks`. Earlier versions of this library emitted the
3400
+ // classic-chart singular form; the raw patcher now always lands
3401
+ // the plural, and strips any stale singular leftover so repeated
3402
+ // patches don't duplicate the element.
3403
+ patched = removeXmlBlock(patched, "cx:majorTickMark");
3404
+ patched = patchValueLeaf(patched, "cx:majorTickMarks", axis.majorTickMark, afterMajorTicks, "cx:axis");
3405
+ }
3406
+ if (rawPatchFlag(patchPlan, "minorTickMark")) {
3407
+ // Plural form — see `majorTickMark` note above.
3408
+ patched = removeXmlBlock(patched, "cx:minorTickMark");
3409
+ patched = patchValueLeaf(patched, "cx:minorTickMarks", axis.minorTickMark, afterMinorTicks, "cx:axis");
3410
+ }
3411
+ if (rawPatchFlag(patchPlan, "numFmt")) {
3412
+ patched = patchGenericChild(patched, "cx:numFmt", buildRawChartExNumFmtXml(axis.numFmt), afterNumFmt, "cx:axis");
3413
+ }
3414
+ if (rawPatchFlag(patchPlan, "txPr")) {
3415
+ patched = patchGenericChild(patched, "cx:txPr", buildRawTextPropertiesXml(axis.txPr, "cx"), afterTxPr, "cx:axis");
3416
+ }
3417
+ if (rawPatchFlag(patchPlan, "spPr")) {
3418
+ patched = patchGenericChild(patched, "cx:spPr", buildRawShapePropertiesXml(axis.spPr, "cx"), afterSpPr, "cx:axis");
3419
+ }
3420
+ return patched;
3421
+ }
3422
+ function buildRawChartExNumFmtXml(numFmt) {
3423
+ if (!numFmt?.formatCode) {
3424
+ return "";
3425
+ }
3426
+ const attrs = [`formatCode="${escapeAttr(numFmt.formatCode)}"`];
3427
+ if (numFmt.sourceLinked !== undefined) {
3428
+ attrs.push(`sourceLinked="${numFmt.sourceLinked ? "1" : "0"}"`);
3429
+ }
3430
+ return `<cx:numFmt ${attrs.join(" ")}/>`;
3431
+ }
3432
+ function buildRawChartExScalingXml(tag, scaling) {
3433
+ if (!scaling) {
3434
+ return "";
3435
+ }
3436
+ const attrs = Object.entries(scaling)
3437
+ .filter(([, value]) => value !== undefined)
3438
+ .map(([key, value]) => `${key}="${escapeAttr(String(value))}"`);
3439
+ return `<cx:${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}/>`;
3440
+ }
3441
+ function findChartExAxisBlock(raw, axisId) {
3442
+ let cursor = 0;
3443
+ while (cursor < raw.length) {
3444
+ const range = findXmlBlock(raw, "cx:axis", cursor);
3445
+ if (!range) {
3446
+ return undefined;
3447
+ }
3448
+ const xml = raw.slice(range.start, range.end);
3449
+ if (new RegExp(`<cx:axis\\s+[^>]*id=["']${axisId}["']`).test(xml)) {
3450
+ return { ...range, xml };
3451
+ }
3452
+ cursor = range.end;
3453
+ }
3454
+ return undefined;
3455
+ }
3456
+ function replaceOrInsertBefore(block, tag, replacement, beforeTags) {
3457
+ const range = findXmlBlock(block, tag);
3458
+ if (range) {
3459
+ return block.slice(0, range.start) + replacement + block.slice(range.end);
3460
+ }
3461
+ const insertAt = beforeTags
3462
+ .map(t => block.indexOf(`<${t}`))
3463
+ .filter(i => i >= 0)
3464
+ .sort((a, b) => a - b)[0];
3465
+ if (insertAt !== undefined) {
3466
+ return block.slice(0, insertAt) + replacement + block.slice(insertAt);
3467
+ }
3468
+ const close = block.lastIndexOf("</c:ser>") >= 0
3469
+ ? block.lastIndexOf("</c:ser>")
3470
+ : block.lastIndexOf("</c:catAx>");
3471
+ return close >= 0 ? block.slice(0, close) + replacement + block.slice(close) : block;
3472
+ }
3473
+ function replaceOrInsertBeforeGeneric(block, tag, replacement, beforeTags, parentTag) {
3474
+ const range = findXmlBlock(block, tag);
3475
+ if (range) {
3476
+ return block.slice(0, range.start) + replacement + block.slice(range.end);
3477
+ }
3478
+ const insertAt = beforeTags
3479
+ .map(t => block.indexOf(`<${t}`))
3480
+ .filter(i => i >= 0)
3481
+ .sort((a, b) => a - b)[0];
3482
+ if (insertAt !== undefined) {
3483
+ return block.slice(0, insertAt) + replacement + block.slice(insertAt);
3484
+ }
3485
+ const close = block.lastIndexOf(`</${parentTag}>`);
3486
+ return close >= 0 ? block.slice(0, close) + replacement + block.slice(close) : block;
3487
+ }
3488
+ function patchGenericChild(block, tag, replacement, beforeTags, parentTag) {
3489
+ if (replacement === undefined || replacement === "") {
3490
+ return removeXmlBlock(block, tag);
3491
+ }
3492
+ return replaceOrInsertBeforeGeneric(block, tag, replacement, beforeTags, parentTag);
3493
+ }
3494
+ function patchOpeningTagBooleanAttribute(block, tag, attr, value) {
3495
+ const openEnd = block.indexOf(">");
3496
+ if (openEnd < 0 || !block.startsWith(`<${tag}`)) {
3497
+ return block;
3498
+ }
3499
+ const head = block
3500
+ .slice(0, openEnd + 1)
3501
+ .replace(new RegExp(`\\s${attr}=("[^"]*"|'[^']*')`, "g"), "");
3502
+ if (value === undefined) {
3503
+ return head + block.slice(openEnd + 1);
3504
+ }
3505
+ // Emit an explicit `val="0"` / `val="1"` for both boolean states so
3506
+ // the raw patch path matches the structured renderer (which emits
3507
+ // `hidden="0"` on `<cx:series>` when the author set `hidden:
3508
+ // false`). Previously the `false` case dropped the attribute
3509
+ // entirely — technically equivalent to the schema default, but
3510
+ // asymmetric with the structured writer: files round-tripping
3511
+ // through raw-patch lost an explicitly-false marker that the
3512
+ // structural path preserved.
3513
+ const selfClosing = head.endsWith("/>");
3514
+ const insertion = ` ${attr}="${value ? "1" : "0"}"`;
3515
+ const rewritten = selfClosing
3516
+ ? head.replace(/\/>$/, `${insertion}/>`)
3517
+ : head.replace(/>$/, `${insertion}>`);
3518
+ return rewritten + block.slice(openEnd + 1);
3519
+ }
3520
+ /**
3521
+ * Replace (or remove) a numeric attribute on the opening tag of `block`.
3522
+ * Used to patch ChartEx series attributes such as `ownerIdx` that live on
3523
+ * `<cx:series …>` rather than as structured children. Matches the element
3524
+ * only when the block begins with `<{tag}` so nested tags with the same
3525
+ * attribute name are left alone. Preserves the `/` on self-closing tags.
3526
+ */
3527
+ function patchOpeningTagIntegerAttribute(block, tag, attr, value) {
3528
+ const openEnd = block.indexOf(">");
3529
+ if (openEnd < 0 || !block.startsWith(`<${tag}`)) {
3530
+ return block;
3531
+ }
3532
+ const head = block.slice(0, openEnd + 1);
3533
+ const selfClosing = head.endsWith("/>");
3534
+ const strippedHead = head.replace(new RegExp(`\\s${attr}=("[^"]*"|'[^']*')`, "g"), "");
3535
+ if (value === undefined || !Number.isFinite(value)) {
3536
+ return strippedHead + block.slice(openEnd + 1);
3537
+ }
3538
+ const insertion = ` ${attr}="${value}"`;
3539
+ const rewritten = selfClosing
3540
+ ? strippedHead.replace(/\/>$/, `${insertion}/>`)
3541
+ : strippedHead.replace(/>$/, `${insertion}>`);
3542
+ return rewritten + block.slice(openEnd + 1);
3543
+ }
3544
+ function patchRepeatingChildren(block, tag, replacement, beforeTags, parentTag) {
3545
+ const stripped = removeXmlBlocks(block, tag);
3546
+ if (!replacement) {
3547
+ return stripped;
3548
+ }
3549
+ return replaceOrInsertBeforeGeneric(stripped, tag, replacement, beforeTags, parentTag);
3550
+ }
3551
+ function removeXmlBlock(block, tag) {
3552
+ const range = findXmlBlock(block, tag);
3553
+ return range ? block.slice(0, range.start) + block.slice(range.end) : block;
3554
+ }
3555
+ /**
3556
+ * Patch a single attribute on the opening tag of `elementTag` inside
3557
+ * the supplied `block`. Intended for raw-XML patching where the
3558
+ * attribute, not a child element, carries the field — e.g.
3559
+ * `CT_Axis/@hidden` in the Chart2014 schema.
3560
+ *
3561
+ * - `value === undefined` removes the attribute (if present).
3562
+ * - `value === true | false` lands `attr="1"` / `attr="0"` — the
3563
+ * OOXML `xsd:boolean` lexical form.
3564
+ * - `value: string` lands literally (escaped).
3565
+ *
3566
+ * The function only mutates the **first** matching opening tag; it
3567
+ * does not recurse into nested elements of the same name (axes in a
3568
+ * combo-chart plotArea are iterated by the caller, each block already
3569
+ * narrowed to a single `<cx:axis …>` opening). Returns the block
3570
+ * unchanged when `elementTag` can't be found — callers rely on the
3571
+ * identity comparison `patched !== block` to detect successful writes.
3572
+ */
3573
+ function patchXmlAttribute(block, elementTag, attrName, value) {
3574
+ // Match the opening tag, allowing leading whitespace in attributes
3575
+ // and both self-closing and regular element forms. Escape the full
3576
+ // element name for regex safety (covers all special regex characters).
3577
+ const escapedTag = elementTag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3578
+ const tagRe = new RegExp(`<${escapedTag}\\b([^>]*)(/?)>`);
3579
+ const match = tagRe.exec(block);
3580
+ if (!match) {
3581
+ return block;
3582
+ }
3583
+ const [fullMatch, attrSegment, selfClose] = match;
3584
+ const attrRe = new RegExp(`\\s${attrName}="[^"]*"`);
3585
+ const stripped = attrSegment.replace(attrRe, "");
3586
+ const serialised = value === undefined
3587
+ ? ""
3588
+ : typeof value === "boolean"
3589
+ ? ` ${attrName}="${value ? "1" : "0"}"`
3590
+ : ` ${attrName}="${escapeAttr(value)}"`;
3591
+ const rebuilt = `<${elementTag}${stripped}${serialised}${selfClose}>`;
3592
+ if (rebuilt === fullMatch) {
3593
+ return block;
3594
+ }
3595
+ return block.slice(0, match.index) + rebuilt + block.slice(match.index + fullMatch.length);
3596
+ }
3597
+ function removeXmlBlocks(block, tag) {
3598
+ let patched = block;
3599
+ let range = findXmlBlock(patched, tag);
3600
+ while (range) {
3601
+ patched = patched.slice(0, range.start) + patched.slice(range.end);
3602
+ range = findXmlBlock(patched, tag, range.start);
3603
+ }
3604
+ return patched;
3605
+ }
3606
+ function replaceXmlBlocks(raw, tag, replace) {
3607
+ let cursor = 0;
3608
+ let output = "";
3609
+ while (cursor < raw.length) {
3610
+ const range = findXmlBlock(raw, tag, cursor);
3611
+ if (!range) {
3612
+ output += raw.slice(cursor);
3613
+ return output;
3614
+ }
3615
+ output += raw.slice(cursor, range.start);
3616
+ const replacement = replace(raw.slice(range.start, range.end));
3617
+ if (replacement === undefined) {
3618
+ return undefined;
3619
+ }
3620
+ output += replacement;
3621
+ cursor = range.end;
3622
+ }
3623
+ return output;
3624
+ }
3625
+ function findAxisBlock(raw, tag, axId) {
3626
+ let cursor = 0;
3627
+ while (cursor < raw.length) {
3628
+ const range = findXmlBlock(raw, tag, cursor);
3629
+ if (!range) {
3630
+ return undefined;
3631
+ }
3632
+ const xml = raw.slice(range.start, range.end);
3633
+ if (new RegExp(`<c:axId\\s+val=["']${axId}["']\\s*/>`).test(xml)) {
3634
+ return { ...range, xml };
3635
+ }
3636
+ cursor = range.end;
3637
+ }
3638
+ return undefined;
3639
+ }
3640
+ /**
3641
+ * Locate the `<tag …>…</tag>` (or self-closing `<tag …/>`) block in
3642
+ * `raw` starting at or after `offset`. Returns `{ start, end }` on hit,
3643
+ * `undefined` on miss.
3644
+ *
3645
+ * Two correctness concerns this implementation guards against:
3646
+ * 1. **Prefix collision** — `indexOf("<tag")` matches `<tag2>` or
3647
+ * `<tagX>` as well as the literal `<tag>` / `<tag `/`<tag/`. We
3648
+ * require the character immediately after the tag name to be one
3649
+ * of `>`, `/`, or whitespace so `<c:chart>` can't match
3650
+ * `<c:chartSpace>` and `<c:ax>` can't match `<c:axId>`.
3651
+ * 2. **Nested same-name elements** — `<c:extLst><c:ext>…<c:extLst>
3652
+ * …</c:extLst></c:ext></c:extLst>` used to match the inner
3653
+ * close. Walk open/close tokens with a depth counter to find the
3654
+ * matching end.
3655
+ *
3656
+ * Limitations: the scanner treats XML tokens lexically and will fail
3657
+ * on same-name occurrences inside CDATA or XML comments. ChartEx XML
3658
+ * does not use either, so this remains a safe shortcut.
3659
+ */
3660
+ function findXmlBlock(raw, tag, offset = 0) {
3661
+ const openToken = `<${tag}`;
3662
+ const closeToken = `</${tag}>`;
3663
+ let pos = offset;
3664
+ let start = -1;
3665
+ // First, find the first legitimate open tag — one whose next char is
3666
+ // `>`, `/`, or whitespace. Prefix collisions (`<tag2>`) are silently
3667
+ // skipped.
3668
+ while (pos < raw.length) {
3669
+ const candidate = raw.indexOf(openToken, pos);
3670
+ if (candidate < 0) {
3671
+ return undefined;
3672
+ }
3673
+ const nextChar = raw[candidate + openToken.length];
3674
+ if (nextChar === ">" || nextChar === "/" || /\s/.test(nextChar ?? "")) {
3675
+ start = candidate;
3676
+ break;
3677
+ }
3678
+ pos = candidate + openToken.length;
3679
+ }
3680
+ if (start < 0) {
3681
+ return undefined;
3682
+ }
3683
+ // Find the end of the open tag. `>` inside a quoted attribute would
3684
+ // confuse this, but Chart XML attributes never contain `>`.
3685
+ const openEnd = raw.indexOf(">", start);
3686
+ if (openEnd < 0) {
3687
+ return undefined;
3688
+ }
3689
+ if (raw[openEnd - 1] === "/") {
3690
+ return { start, end: openEnd + 1 };
3691
+ }
3692
+ // Walk forward balancing opens and closes for this same tag name.
3693
+ // A nested `<tag>` inside the element bumps the depth; `</tag>`
3694
+ // decrements. When depth hits zero we've found the matching close.
3695
+ let depth = 1;
3696
+ let scan = openEnd + 1;
3697
+ while (scan < raw.length && depth > 0) {
3698
+ const nextOpen = (() => {
3699
+ let p = scan;
3700
+ while (p < raw.length) {
3701
+ const c = raw.indexOf(openToken, p);
3702
+ if (c < 0) {
3703
+ return -1;
3704
+ }
3705
+ const next = raw[c + openToken.length];
3706
+ if (next === ">" || next === "/" || /\s/.test(next ?? "")) {
3707
+ return c;
3708
+ }
3709
+ p = c + openToken.length;
3710
+ }
3711
+ return -1;
3712
+ })();
3713
+ const nextClose = raw.indexOf(closeToken, scan);
3714
+ if (nextClose < 0) {
3715
+ return undefined;
3716
+ }
3717
+ if (nextOpen >= 0 && nextOpen < nextClose) {
3718
+ // Another open of the same tag — but only count it if it's a
3719
+ // real element (not self-closing, which shouldn't change depth).
3720
+ const oeNext = raw.indexOf(">", nextOpen);
3721
+ if (oeNext < 0) {
3722
+ return undefined;
3723
+ }
3724
+ if (raw[oeNext - 1] !== "/") {
3725
+ depth++;
3726
+ }
3727
+ scan = oeNext + 1;
3728
+ }
3729
+ else {
3730
+ depth--;
3731
+ if (depth === 0) {
3732
+ return { start, end: nextClose + closeToken.length };
3733
+ }
3734
+ scan = nextClose + closeToken.length;
3735
+ }
3736
+ }
3737
+ return undefined;
3738
+ }
3739
+ function escapeXml(value) {
3740
+ // Route through the canonical XML encoder so every raw-patch / XML
3741
+ // builder call site benefits from the same strict sanitisation:
3742
+ //
3743
+ // - strips XML 1.0-forbidden control characters (`#x0`-`#x1F`
3744
+ // except `\t \n \r`, `#x7F` DEL, `#xFFFE`, `#xFFFF`);
3745
+ // - strips lone surrogate halves (previously `U+D800`-`U+DFFF`
3746
+ // outside a valid pair could leak into attribute / text
3747
+ // content and corrupt the output encoding);
3748
+ // - escapes all five XML structural entities (`< > & " '`).
3749
+ //
3750
+ // The previous local implementation only handled `& < >` plus a
3751
+ // partial control-char strip. That was enough for the reserved-
3752
+ // trio case but left `"` untouched in attribute values (callers
3753
+ // compensated with a manual `.replace(/"/g, "&quot;")`), and
3754
+ // lone surrogates survived — producing bytes no XML parser can
3755
+ // reopen.
3756
+ //
3757
+ // Element-text call sites used to emit `"` / `'` verbatim; the
3758
+ // new encoder produces `&quot;` / `&apos;`. Both are valid XML
3759
+ // and round-trip identically through any parser, but byte-level
3760
+ // diffs against the old output will show the extra entities.
3761
+ return (0, encode_1.xmlEncode)(value);
3762
+ }
3763
+ function escapeAttr(value) {
3764
+ // Attribute values need the extra step of escaping `\t \n \r` as
3765
+ // numeric character references; without it, XML 1.0 §3.3.3
3766
+ // attribute-value normalisation replaces them with a single
3767
+ // literal space at parse time, silently losing any embedded
3768
+ // newline / tab in (e.g.) a chart title.
3769
+ return (0, encode_1.xmlEncodeAttr)(value);
3770
+ }
276
3771
  /**
277
3772
  * XLSX class - handles Excel file operations
278
3773
  * Works in both Node.js and Browser environments
@@ -329,6 +3824,8 @@ class XLSX {
329
3824
  async writeToZip(zip, options) {
330
3825
  const { model } = this.workbook;
331
3826
  this.prepareModel(model, options);
3827
+ this.prepareChartsheets(model);
3828
+ this.prepareChartExSidecars(model);
332
3829
  await this.addContentTypes(zip, model);
333
3830
  await this.addOfficeRels(zip, model);
334
3831
  await this.addWorkbookRels(zip, model);
@@ -339,10 +3836,13 @@ class XLSX {
339
3836
  await this.addWorksheets(zip, model);
340
3837
  await this.addSharedStrings(zip, model);
341
3838
  await this.addDrawings(zip, model);
3839
+ await this.addChartsheets(zip, model);
3840
+ const strictTemplateMode = isStrictTemplateMode(options);
3841
+ await this.addCharts(zip, model, strictTemplateMode);
3842
+ await this.addChartExEntries(zip, model, strictTemplateMode);
342
3843
  await this.addTables(zip, model);
343
3844
  await this.addPivotTables(zip, model);
344
3845
  await this.addExternalLinks(zip, model);
345
- this.addPassthrough(zip, model);
346
3846
  await this.addThemes(zip, model);
347
3847
  await this.addStyles(zip, model);
348
3848
  await this.addFeaturePropertyBag(zip, model);
@@ -350,6 +3850,44 @@ class XLSX {
350
3850
  await this.addMedia(zip, model);
351
3851
  await this.addApp(zip, model);
352
3852
  await this.addCore(zip, model);
3853
+ await this.addPersons(zip, model);
3854
+ await this.addSlicerAndTimelineParts(zip, model);
3855
+ }
3856
+ /**
3857
+ * Emit the raw slicer/timeline parts captured on load. Pure
3858
+ * byte-copy — excelts does not modify these parts. The partner
3859
+ * Content-Types and rels are covered separately (content types in
3860
+ * `addContentTypes`, sheet/workbook rels by the corresponding
3861
+ * xforms consuming the existing `xl/_rels/*.rels` captured on
3862
+ * load).
3863
+ */
3864
+ async addSlicerAndTimelineParts(zip, model) {
3865
+ for (const source of [
3866
+ model.slicerParts,
3867
+ model.slicerCacheParts,
3868
+ model.timelineParts,
3869
+ model.timelineCacheParts
3870
+ ]) {
3871
+ if (!source) {
3872
+ continue;
3873
+ }
3874
+ for (const [path, bytes] of Object.entries(source)) {
3875
+ zip.append(bytes, { name: path });
3876
+ }
3877
+ }
3878
+ }
3879
+ /**
3880
+ * Write the workbook-level `xl/persons/person.xml` part when the
3881
+ * model carries Office 365 threaded-comment authors. No-op when the
3882
+ * persons list is empty so legacy files without threaded comments
3883
+ * stay byte-identical.
3884
+ */
3885
+ async addPersons(zip, model) {
3886
+ const persons = model.persons;
3887
+ if (!persons || persons.length === 0) {
3888
+ return;
3889
+ }
3890
+ zip.append((0, threaded_comments_xform_1.renderPersonList)(persons), { name: "xl/persons/person.xml" });
353
3891
  }
354
3892
  // ===========================================================================
355
3893
  // Stream/Buffer operations - shared by all platforms
@@ -470,8 +4008,6 @@ class XLSX {
470
4008
  mediaIndex: {},
471
4009
  drawings: {},
472
4010
  drawingRels: {},
473
- // Raw drawing XML data for passthrough (when drawing contains chart references)
474
- rawDrawings: {},
475
4011
  comments: {},
476
4012
  tables: {},
477
4013
  vmlDrawings: {},
@@ -479,8 +4015,22 @@ class XLSX {
479
4015
  pivotTableRels: {},
480
4016
  pivotCacheDefinitions: {},
481
4017
  pivotCacheRecords: {},
482
- // Passthrough storage for unknown/unsupported files (charts, etc.)
483
- passthrough: {},
4018
+ // Parsed chart entries keyed by chart number
4019
+ chartEntries: {},
4020
+ // Parsed chart rels keyed by chart number
4021
+ chartRels: {},
4022
+ // Raw chart style bytes keyed by style number
4023
+ chartStyles: {},
4024
+ // Raw chart colors bytes keyed by colors number
4025
+ chartColors: {},
4026
+ chartExStyles: {},
4027
+ chartExColors: {},
4028
+ // Raw chartEx entries (Office 2016+ extended charts) keyed by chartEx number
4029
+ chartExEntries: {},
4030
+ // Parsed chartEx rels keyed by chartEx number
4031
+ chartExRels: {},
4032
+ // Structured chartEx entries (built via addChartEx) keyed by chartEx number
4033
+ chartExStructuredEntries: {},
484
4034
  // External workbook links — parsed from xl/externalLinks/externalLinkN.xml
485
4035
  // during _processDefaultEntry, then reconciled into a dense
486
4036
  // ExternalLinkModel[] by reconcile() using workbookRels + <externalReferences>.
@@ -488,7 +4038,10 @@ class XLSX {
488
4038
  // Raw rels from each externalLinkN.rels file, keyed by index.
489
4039
  // Contains the actual Target path (e.g. "测试.xlsx", "file:///...")
490
4040
  // and TargetMode ("External" / "Internal").
491
- externalLinkRelsByIndex: {}
4041
+ externalLinkRelsByIndex: {},
4042
+ // Chartsheets keyed by sheet number
4043
+ chartsheets: {},
4044
+ chartsheetRels: {}
492
4045
  };
493
4046
  }
494
4047
  /**
@@ -514,20 +4067,6 @@ class XLSX {
514
4067
  });
515
4068
  return (0, binary_1.concatUint8Arrays)(chunks);
516
4069
  }
517
- /**
518
- * Check if a drawing has chart references in its relationships
519
- */
520
- drawingHasChartReference(drawing) {
521
- return (drawing.rels && drawing.rels.some((rel) => rel.Target && rel.Target.includes("/charts/")));
522
- }
523
- /**
524
- * Check if a drawing rels list references charts.
525
- * Used to decide whether we need to keep raw drawing XML for passthrough.
526
- */
527
- drawingRelsHasChartReference(drawingRels) {
528
- return (Array.isArray(drawingRels) &&
529
- drawingRels.some(rel => typeof rel?.Target === "string" && rel.Target.includes("/charts/")));
530
- }
531
4070
  /**
532
4071
  * Process a known OOXML entry (workbook, styles, shared strings, etc.)
533
4072
  * Returns true if handled, false if should be passed to _processDefaultEntry
@@ -538,6 +4077,11 @@ class XLSX {
538
4077
  await this._processWorksheetEntry(stream, model, sheetNo, options, entryName);
539
4078
  return true;
540
4079
  }
4080
+ const chartsheetNo = (0, ooxml_paths_1.getChartsheetNoFromPath)(entryName);
4081
+ if (chartsheetNo !== undefined) {
4082
+ await this._processChartsheetEntry(stream, model, chartsheetNo);
4083
+ return true;
4084
+ }
541
4085
  switch (entryName) {
542
4086
  case ooxml_paths_1.OOXML_PATHS.rootRels:
543
4087
  model.globalRels = await this.parseRels(stream);
@@ -590,8 +4134,56 @@ class XLSX {
590
4134
  }
591
4135
  return true;
592
4136
  }
593
- default:
4137
+ case "xl/persons/person.xml": {
4138
+ // Office 365 threaded-comment person directory. Parsed here so
4139
+ // reconcile can attach the list to the workbook. Silently
4140
+ // ignored when malformed — threaded comments degrade to
4141
+ // "unknown author" rather than breaking the whole load.
4142
+ const data = await this.collectStreamData(stream);
4143
+ const raw = new TextDecoder().decode(data);
4144
+ model.persons = (0, threaded_comments_xform_1.parsePersonList)(raw);
4145
+ return true;
4146
+ }
4147
+ default: {
4148
+ // Catch threaded-comment per-sheet parts (the path contains a
4149
+ // variable sheet index so they can't be matched in the switch).
4150
+ const threadedMatch = /^xl\/threadedComments\/threadedComment(\d+)\.xml$/.exec(entryName);
4151
+ if (threadedMatch) {
4152
+ const sheetIndex = parseInt(threadedMatch[1], 10);
4153
+ const data = await this.collectStreamData(stream);
4154
+ const raw = new TextDecoder().decode(data);
4155
+ model.threadedCommentsByIndex ?? (model.threadedCommentsByIndex = {});
4156
+ model.threadedCommentsByIndex[sheetIndex] = (0, threaded_comments_xform_1.parseThreadedComments)(raw);
4157
+ return true;
4158
+ }
4159
+ // Raw-passthrough capture for slicers and timelines — two
4160
+ // coordinated Office dashboard features excelts does not
4161
+ // structurally model but must not destroy on round-trip.
4162
+ // Each family has two part types (the control itself + its
4163
+ // cache); both are captured into maps on the workbook model
4164
+ // so the writer can emit them verbatim later.
4165
+ if (/^xl\/slicers\/slicer\d+\.xml$/.test(entryName)) {
4166
+ model.slicerParts ?? (model.slicerParts = {});
4167
+ model.slicerParts[entryName] = await this.collectStreamData(stream);
4168
+ return true;
4169
+ }
4170
+ if (/^xl\/slicerCaches\/slicerCache\d+\.xml$/.test(entryName)) {
4171
+ model.slicerCacheParts ?? (model.slicerCacheParts = {});
4172
+ model.slicerCacheParts[entryName] = await this.collectStreamData(stream);
4173
+ return true;
4174
+ }
4175
+ if (/^xl\/timelines\/timeline\d+\.xml$/.test(entryName)) {
4176
+ model.timelineParts ?? (model.timelineParts = {});
4177
+ model.timelineParts[entryName] = await this.collectStreamData(stream);
4178
+ return true;
4179
+ }
4180
+ if (/^xl\/timelineCaches\/timelineCache\d+\.xml$/.test(entryName)) {
4181
+ model.timelineCacheParts ?? (model.timelineCacheParts = {});
4182
+ model.timelineCacheParts[entryName] = await this.collectStreamData(stream);
4183
+ return true;
4184
+ }
594
4185
  return false;
4186
+ }
595
4187
  }
596
4188
  }
597
4189
  async loadFromZipEntries(entries, options) {
@@ -650,7 +4242,14 @@ class XLSX {
650
4242
  zip.pipe(stream);
651
4243
  await this.writeToZip(zip, options);
652
4244
  await this._finalize(zip);
653
- return stream.read() || new Uint8Array(0);
4245
+ const bytes = stream.read() || new Uint8Array(0);
4246
+ // Optional OOXML self-check. Enabled by default in non-production
4247
+ // Node.js environments; disabled in the browser and in production.
4248
+ // See `XlsxWriteOptions.validate` for the resolution rules.
4249
+ if (shouldAutoValidate(options.validate)) {
4250
+ await runWriteBufferSelfCheck(bytes);
4251
+ }
4252
+ return bytes;
654
4253
  }
655
4254
  // ===========================================================================
656
4255
  // Media handling - base implementation (buffer/base64 only)
@@ -731,15 +4330,35 @@ class XLSX {
731
4330
  drawingXform.reconcile(drawing, drawingOptions);
732
4331
  }
733
4332
  });
734
- // Trim raw drawings for non-chart drawings to avoid bloating the serialized workbook model.
735
- if (model.rawDrawings && model.drawingRels) {
736
- for (const name of Object.keys(model.rawDrawings)) {
737
- const drawingRel = model.drawingRels[name];
738
- if (drawingRel && !this.drawingRelsHasChartReference(drawingRel)) {
739
- delete model.rawDrawings[name];
4333
+ // Reconcile chart references in drawing anchors
4334
+ Object.keys(model.drawings).forEach(name => {
4335
+ const drawing = model.drawings[name];
4336
+ const drawingRel = model.drawingRels[name];
4337
+ if (!drawingRel) {
4338
+ return;
4339
+ }
4340
+ const relMap = {};
4341
+ for (const rel of drawingRel) {
4342
+ relMap[rel.Id] = rel;
4343
+ }
4344
+ for (const anchor of drawing.anchors ?? []) {
4345
+ if (anchor.graphicFrame?.rId) {
4346
+ const rel = relMap[anchor.graphicFrame.rId];
4347
+ if (rel?.Target) {
4348
+ // Extract chart number from target like "../charts/chart1.xml"
4349
+ const match = /chart(\d+)\.xml/.exec(rel.Target);
4350
+ if (match) {
4351
+ anchor.chartNumber = parseInt(match[1], 10);
4352
+ }
4353
+ // Extract chartEx number from target like "../charts/chartEx1.xml"
4354
+ const matchEx = /chartEx(\d+)\.xml/.exec(rel.Target);
4355
+ if (matchEx) {
4356
+ anchor.chartExNumber = parseInt(matchEx[1], 10);
4357
+ }
4358
+ }
740
4359
  }
741
4360
  }
742
- }
4361
+ });
743
4362
  // reconcile tables with the default styles
744
4363
  const tableOptions = {
745
4364
  styles: model.styles
@@ -767,7 +4386,59 @@ class XLSX {
767
4386
  model.worksheets.forEach((worksheet) => {
768
4387
  worksheet.relationships = model.worksheetRels[worksheet.sheetNo];
769
4388
  worksheetXform.reconcile(worksheet, sheetOptions);
4389
+ // Attach any threaded comments that arrived in a separate
4390
+ // `xl/threadedComments/threadedComment{N}.xml` part. The sheet
4391
+ // index in that path maps to `worksheet.sheetNo`, not
4392
+ // `worksheet.id` — Excel uses the package-relative file number,
4393
+ // same as classic `xl/comments{N}.xml`.
4394
+ const threaded = model.threadedCommentsByIndex?.[worksheet.sheetNo];
4395
+ if (threaded) {
4396
+ worksheet.threadedComments = threaded;
4397
+ }
770
4398
  });
4399
+ // Reconcile chartsheets — link their drawing references and
4400
+ // preserve every relationship so the writer can round-trip
4401
+ // every r:id referenced by raw-captured children (legacyDrawing,
4402
+ // picture, legacyDrawingHF, drawingHF, etc.). Previously only
4403
+ // the drawing rel was hooked up and everything else was
4404
+ // silently discarded on save, leaving any raw-captured child
4405
+ // with a dangling r:id pointing at a now-missing part.
4406
+ const chartsheetsList = model.chartsheetsList || [];
4407
+ for (const cs of chartsheetsList) {
4408
+ const csRels = model.chartsheetRels[cs.sheetNo];
4409
+ if (csRels) {
4410
+ // Keep the full rels list attached to the model so
4411
+ // `addChartsheets` can re-emit it. Copy so downstream
4412
+ // mutations don't leak back into `model.chartsheetRels`.
4413
+ cs.relationships = [...csRels];
4414
+ }
4415
+ if (cs.drawing && csRels) {
4416
+ const drawingRel = csRels.find((r) => r.Id === cs.drawing.rId);
4417
+ if (drawingRel) {
4418
+ const match = drawingRel.Target.match(/\/drawings\/([a-zA-Z0-9]+)[.][a-zA-Z]{3,4}$/);
4419
+ if (match) {
4420
+ cs.drawingName = match[1];
4421
+ // Resolve drawing → chart number from drawing rels
4422
+ const drawingRelArr = model.drawingRels[cs.drawingName];
4423
+ if (drawingRelArr) {
4424
+ for (const dr of drawingRelArr) {
4425
+ const chartMatch = /chart(\d+)\.xml/.exec(dr.Target);
4426
+ if (chartMatch) {
4427
+ cs.chartNumber = parseInt(chartMatch[1], 10);
4428
+ break;
4429
+ }
4430
+ const chartExMatch = /chartEx(\d+)\.xml/.exec(dr.Target);
4431
+ if (chartExMatch) {
4432
+ cs.chartExNumber = parseInt(chartExMatch[1], 10);
4433
+ break;
4434
+ }
4435
+ }
4436
+ }
4437
+ }
4438
+ }
4439
+ }
4440
+ }
4441
+ model.chartsheets = chartsheetsList;
771
4442
  // Reconcile external workbook links before workbookRels / externalReferences
772
4443
  // are dropped. Joins 3 sources:
773
4444
  // 1. model.externalReferences — ordered list of { rId } from workbook.xml
@@ -775,6 +4446,17 @@ class XLSX {
775
4446
  // 3. model.externalLinksByIndex — parsed externalLinkN.xml parts
776
4447
  // 4. model.externalLinkRelsByIndex — parsed externalLinkN.xml.rels parts
777
4448
  this._reconcileExternalLinks(model);
4449
+ // Preserve parsed chart data through to the workbook model.
4450
+ // chartEntries, chartRels, chartStyles, chartColors are kept as-is.
4451
+ // Reconcile chart user-shapes drawing parts onto their owning
4452
+ // ChartEntry. Each chart rels file may reference an overlay drawing
4453
+ // via `RelType.ChartUserShapes`; we copy those bytes from
4454
+ // `model.drawingRaw` (populated by `_processDrawingEntry`) onto the
4455
+ // chart entry so writers can emit them back, and so the Chart API
4456
+ // can expose them via `Chart.userShapesXml`. Regular worksheet
4457
+ // drawings are untouched — this reconcile only moves bytes for
4458
+ // chart-overlay parts.
4459
+ this._reconcileChartUserShapes(model);
778
4460
  // delete unnecessary parts
779
4461
  delete model.worksheetHash;
780
4462
  delete model.worksheetRels;
@@ -788,6 +4470,7 @@ class XLSX {
788
4470
  delete model.mediaIndex;
789
4471
  delete model.drawings;
790
4472
  delete model.drawingRels;
4473
+ delete model.drawingRaw;
791
4474
  delete model.vmlDrawings;
792
4475
  delete model.pivotTableRels;
793
4476
  delete model.metadata;
@@ -795,6 +4478,56 @@ class XLSX {
795
4478
  delete model.externalReferences;
796
4479
  delete model.externalLinksByIndex;
797
4480
  delete model.externalLinkRelsByIndex;
4481
+ delete model.chartsheetRels;
4482
+ delete model.chartsheetsList;
4483
+ }
4484
+ /**
4485
+ * Copy the raw bytes of each chart's user-shapes drawing part onto
4486
+ * the owning `ChartEntry.userShapesXml` so the writer can emit them
4487
+ * back verbatim (and so {@link Chart.userShapesXml} can surface them
4488
+ * to user code). Runs after all ZIP entries have been processed
4489
+ * because chart rels and drawing bytes stream in independent order.
4490
+ *
4491
+ * Skips charts that have no `ChartUserShapes` rel. The bytes stay
4492
+ * keyed by drawing name (e.g. `drawing3`) inside `model.drawingRaw`
4493
+ * since a workbook may have many user-shape drawings across
4494
+ * different charts; we look up each chart's target through its
4495
+ * rels file.
4496
+ */
4497
+ _reconcileChartUserShapes(model) {
4498
+ var _a;
4499
+ const chartRelsMap = model.chartRels;
4500
+ const drawingRaw = model.drawingRaw;
4501
+ const chartEntries = model.chartEntries;
4502
+ if (!chartRelsMap || !drawingRaw || !chartEntries) {
4503
+ return;
4504
+ }
4505
+ for (const [chartNum, rels] of Object.entries(chartRelsMap)) {
4506
+ if (!Array.isArray(rels)) {
4507
+ continue;
4508
+ }
4509
+ const entry = chartEntries[chartNum];
4510
+ if (!entry) {
4511
+ continue;
4512
+ }
4513
+ const userShapesRel = rels.find(rel => rel && typeof rel === "object" && rel.Type === rel_type_1.RelType.ChartUserShapes);
4514
+ if (!userShapesRel?.Target) {
4515
+ continue;
4516
+ }
4517
+ // Target like `../drawings/drawing3.xml` or `../drawings/chartUserShape2.xml`.
4518
+ const match = /drawings\/([^/]+)\.xml$/i.exec(String(userShapesRel.Target));
4519
+ if (!match) {
4520
+ continue;
4521
+ }
4522
+ const drawingName = match[1];
4523
+ const bytes = drawingRaw[drawingName];
4524
+ if (bytes) {
4525
+ entry.userShapesXml = bytes;
4526
+ // Make sure the chart model carries the r:id so subsequent reads
4527
+ // via Chart.userShapesXml can round-trip without extra setup.
4528
+ (_a = entry.model).userShapesRelId ?? (_a.userShapesRelId = userShapesRel.Id);
4529
+ }
4530
+ }
798
4531
  }
799
4532
  /**
800
4533
  * Join the three on-disk sources that together describe external workbook
@@ -996,6 +4729,7 @@ class XLSX {
996
4729
  const defaultMetric = this._determineMetric(pt.dataFields);
997
4730
  const completePivotTable = {
998
4731
  ...pt,
4732
+ name: pt.name ?? `PivotTable${tableNumber}`,
999
4733
  tableNumber,
1000
4734
  cacheId: String(pt.cacheId),
1001
4735
  cacheDefinition: cacheData?.definition,
@@ -1071,6 +4805,14 @@ class XLSX {
1071
4805
  model.worksheetHash[path] = worksheet;
1072
4806
  model.worksheets.push(worksheet);
1073
4807
  }
4808
+ async _processChartsheetEntry(stream, model, sheetNo) {
4809
+ const xform = new chartsheet_xform_1.ChartsheetXform();
4810
+ const chartsheet = await xform.parseStream(stream);
4811
+ if (chartsheet) {
4812
+ chartsheet.sheetNo = sheetNo;
4813
+ model.chartsheets[sheetNo] = chartsheet;
4814
+ }
4815
+ }
1074
4816
  async _processCommentEntry(stream, model, zipPath) {
1075
4817
  const xform = new comments_xform_1.CommentsXform();
1076
4818
  const comments = await xform.parseStream(stream);
@@ -1142,8 +4884,30 @@ class XLSX {
1142
4884
  const xmlString = this.bufferToString(data);
1143
4885
  const drawing = await xform.parseStream(this.createTextStream(xmlString));
1144
4886
  model.drawings[name] = drawing;
1145
- // Store raw data; reconcile() may later drop it if charts are not referenced.
1146
- model.rawDrawings[name] = data;
4887
+ // Also stash the original bytes chart user-shape drawings use a
4888
+ // distinct schema (`c:relSizeAnchor` / `c:userShapes` instead of
4889
+ // `xdr:twoCellAnchor`) and are post-reconciled onto their owning
4890
+ // ChartEntry so the bytes can be written back verbatim. Regular
4891
+ // worksheet drawings don't read this map.
4892
+ if (!model.drawingRaw) {
4893
+ model.drawingRaw = {};
4894
+ }
4895
+ model.drawingRaw[name] = data;
4896
+ }
4897
+ /**
4898
+ * Stash raw bytes of a chart-overlay drawing part. `c:userShapes`
4899
+ * parts live under `xl/drawings/chartUserShape{N}.xml` in files we
4900
+ * write ourselves and can use arbitrary names in foreign files (the
4901
+ * rel target is the only authoritative reference). The bytes are
4902
+ * keyed by the stem so `_reconcileChartUserShapes` can match them
4903
+ * against each chart's `ChartUserShapes` rel Target.
4904
+ */
4905
+ async _processChartUserShapesEntry(_stream, model, name, rawData) {
4906
+ const data = rawData ?? (await this.collectStreamData(_stream));
4907
+ if (!model.drawingRaw) {
4908
+ model.drawingRaw = {};
4909
+ }
4910
+ model.drawingRaw[name] = data;
1147
4911
  }
1148
4912
  async _processDrawingRelsEntry(entry, model, name) {
1149
4913
  const xform = new relationships_xform_1.RelationshipsXform();
@@ -1244,6 +5008,34 @@ class XLSX {
1244
5008
  const relationships = await this.parseRels(stream);
1245
5009
  model.externalLinkRelsByIndex[index] = relationships ?? [];
1246
5010
  }
5011
+ async _processChartEntry(stream, model, chartNumber, rawData) {
5012
+ const data = rawData ?? (await this.collectStreamData(stream));
5013
+ // Parse into model for high-level API access
5014
+ const xform = (0, chart_host_registry_1.getChartSupport)().createChartSpaceXform();
5015
+ const xmlString = this.bufferToString(data);
5016
+ const chart = await xform.parseStream(this.createTextStream(xmlString));
5017
+ if (chart) {
5018
+ model.chartEntries[chartNumber] = {
5019
+ chartNumber,
5020
+ model: chart,
5021
+ rawData: data,
5022
+ modelSnapshot: snapshotChartModel(chart)
5023
+ };
5024
+ }
5025
+ }
5026
+ async _processChartRelsEntry(stream, model, chartNumber) {
5027
+ const xform = new relationships_xform_1.RelationshipsXform();
5028
+ const relationships = await xform.parseStream(stream);
5029
+ model.chartRels[chartNumber] = relationships;
5030
+ }
5031
+ async _processChartStyleEntry(stream, model, styleNumber) {
5032
+ const data = await this.collectStreamData(stream);
5033
+ model.chartStyles[styleNumber] = data;
5034
+ }
5035
+ async _processChartColorsEntry(stream, model, colorsNumber) {
5036
+ const data = await this.collectStreamData(stream);
5037
+ model.chartColors[colorsNumber] = data;
5038
+ }
1247
5039
  // ===========================================================================
1248
5040
  // loadFromFiles - shared logic for loading from pre-extracted ZIP data
1249
5041
  // ===========================================================================
@@ -1264,7 +5056,6 @@ class XLSX {
1264
5056
  : this.createTextStream(this.bufferToString(entry.data));
1265
5057
  const handled = await this._processKnownEntry(stream, model, entryName, options);
1266
5058
  if (!handled) {
1267
- // Pass raw entry data for drawings to enable passthrough
1268
5059
  await this._processDefaultEntry(stream, model, entryName, entry.data);
1269
5060
  }
1270
5061
  }
@@ -1283,6 +5074,12 @@ class XLSX {
1283
5074
  await this._processWorksheetRelsEntry(stream, model, sheetNo);
1284
5075
  return true;
1285
5076
  }
5077
+ const chartsheetRelsNo = (0, ooxml_paths_1.getChartsheetNoFromRelsPath)(entryName);
5078
+ if (chartsheetRelsNo !== undefined) {
5079
+ const rels = await this.parseRels(stream);
5080
+ model.chartsheetRels[chartsheetRelsNo] = rels;
5081
+ return true;
5082
+ }
1286
5083
  const mediaFilename = (0, ooxml_paths_1.getMediaFilenameFromPath)(entryName);
1287
5084
  if (mediaFilename) {
1288
5085
  await this._processMediaEntry(stream, model, mediaFilename);
@@ -1291,7 +5088,11 @@ class XLSX {
1291
5088
  const drawingName = (0, ooxml_paths_1.getDrawingNameFromPath)(entryName);
1292
5089
  if (drawingName) {
1293
5090
  await this._processDrawingEntry(stream, model, drawingName, rawData);
1294
- // rawData is now stored inside _processDrawingEntry
5091
+ return true;
5092
+ }
5093
+ const chartUserShapesName = (0, ooxml_paths_1.getChartUserShapesNameFromPath)(entryName);
5094
+ if (chartUserShapesName) {
5095
+ await this._processChartUserShapesEntry(stream, model, chartUserShapesName, rawData);
1295
5096
  return true;
1296
5097
  }
1297
5098
  const drawingRelsName = (0, ooxml_paths_1.getDrawingNameFromRelsPath)(entryName);
@@ -1368,27 +5169,108 @@ class XLSX {
1368
5169
  await this._processExternalLinkRelsEntry(stream, model, externalLinkRelsIndex);
1369
5170
  return true;
1370
5171
  }
1371
- // Store passthrough files (charts, etc.) for preservation
1372
- if (passthrough_manager_1.PassthroughManager.isPassthroughPath(entryName)) {
1373
- // If raw data is available (loadFromFiles path), use it directly
5172
+ // Chart files parse natively before the passthrough catch-all
5173
+ const chartNumber = (0, ooxml_paths_1.getChartNumberFromPath)(entryName);
5174
+ if (chartNumber !== undefined) {
5175
+ await this._processChartEntry(stream, model, chartNumber, rawData);
5176
+ return true;
5177
+ }
5178
+ const chartRelsNumber = (0, ooxml_paths_1.getChartNumberFromRelsPath)(entryName);
5179
+ if (chartRelsNumber !== undefined) {
5180
+ await this._processChartRelsEntry(stream, model, chartRelsNumber);
5181
+ return true;
5182
+ }
5183
+ const chartStyleNumber = (0, ooxml_paths_1.getChartStyleNumberFromPath)(entryName);
5184
+ if (chartStyleNumber !== undefined) {
1374
5185
  if (rawData) {
1375
- model.passthrough[entryName] = rawData;
5186
+ model.chartStyles[chartStyleNumber] = rawData;
1376
5187
  }
1377
5188
  else {
1378
- await this._processPassthroughEntry(stream, model, entryName);
5189
+ await this._processChartStyleEntry(stream, model, chartStyleNumber);
5190
+ }
5191
+ return true;
5192
+ }
5193
+ const chartColorsNumber = (0, ooxml_paths_1.getChartColorsNumberFromPath)(entryName);
5194
+ if (chartColorsNumber !== undefined) {
5195
+ if (rawData) {
5196
+ model.chartColors[chartColorsNumber] = rawData;
5197
+ }
5198
+ else {
5199
+ await this._processChartColorsEntry(stream, model, chartColorsNumber);
5200
+ }
5201
+ return true;
5202
+ }
5203
+ const chartExStyleNumber = (0, ooxml_paths_1.getChartExStyleNumberFromPath)(entryName);
5204
+ if (chartExStyleNumber !== undefined) {
5205
+ model.chartExStyles[chartExStyleNumber] = rawData ?? (await this.collectStreamData(stream));
5206
+ return true;
5207
+ }
5208
+ const chartExColorsNumber = (0, ooxml_paths_1.getChartExColorsNumberFromPath)(entryName);
5209
+ if (chartExColorsNumber !== undefined) {
5210
+ model.chartExColors[chartExColorsNumber] = rawData ?? (await this.collectStreamData(stream));
5211
+ return true;
5212
+ }
5213
+ // ChartEx files (Office 2016+ extended charts) — raw bytes plus best-effort structured model
5214
+ const chartExNumber = (0, ooxml_paths_1.getChartExNumberFromPath)(entryName);
5215
+ if (chartExNumber !== undefined) {
5216
+ const data = rawData ?? (await this.collectStreamData(stream));
5217
+ const rawXml = this.bufferToString(data);
5218
+ model.chartExEntries[chartExNumber] = data;
5219
+ try {
5220
+ const parsed = (0, chart_host_registry_1.getChartSupport)().parseChartEx(rawXml);
5221
+ model.chartExStructuredEntries[chartExNumber] = {
5222
+ chartExNumber,
5223
+ model: parsed,
5224
+ rawData: data,
5225
+ modelSnapshot: snapshotChartModel(parsed)
5226
+ };
5227
+ }
5228
+ catch {
5229
+ // Keep legacy-safe passthrough if a third-party chartEx part is not parseable.
1379
5230
  }
1380
5231
  return true;
1381
5232
  }
5233
+ const chartExRelsNumber = (0, ooxml_paths_1.getChartExNumberFromRelsPath)(entryName);
5234
+ if (chartExRelsNumber !== undefined) {
5235
+ const relsXform = new relationships_xform_1.RelationshipsXform();
5236
+ const relationships = await relsXform.parseStream(stream);
5237
+ model.chartExRels[chartExRelsNumber] = relationships;
5238
+ return true;
5239
+ }
5240
+ // Raw-passthrough catch-all for Office 2010+ slicer/timeline
5241
+ // dashboard controls and their associated rels. excelts does not
5242
+ // model these structurally yet; capturing the bytes here prevents
5243
+ // silent data loss on round-trip when a dashboard workbook comes
5244
+ // through. Same idea covers the two-level rels files produced by
5245
+ // Excel (the `_rels` subfolder sits next to each part).
5246
+ if (/^xl\/slicers\/slicer\d+\.xml$/.test(entryName) ||
5247
+ /^xl\/slicerCaches\/slicerCache\d+\.xml$/.test(entryName) ||
5248
+ /^xl\/timelines\/timeline\d+\.xml$/.test(entryName) ||
5249
+ /^xl\/timelineCaches\/timelineCache\d+\.xml$/.test(entryName) ||
5250
+ /^xl\/slicers\/_rels\/slicer\d+\.xml\.rels$/.test(entryName) ||
5251
+ /^xl\/slicerCaches\/_rels\/slicerCache\d+\.xml\.rels$/.test(entryName) ||
5252
+ /^xl\/timelines\/_rels\/timeline\d+\.xml\.rels$/.test(entryName) ||
5253
+ /^xl\/timelineCaches\/_rels\/timelineCache\d+\.xml\.rels$/.test(entryName)) {
5254
+ const targetMap = entryName.startsWith("xl/slicers/") && !entryName.includes("/_rels/")
5255
+ ? (model.slicerParts ?? (model.slicerParts = {}))
5256
+ : entryName.startsWith("xl/slicerCaches/") && !entryName.includes("/_rels/")
5257
+ ? (model.slicerCacheParts ?? (model.slicerCacheParts = {}))
5258
+ : entryName.startsWith("xl/timelines/") && !entryName.includes("/_rels/")
5259
+ ? (model.timelineParts ?? (model.timelineParts = {}))
5260
+ : entryName.startsWith("xl/timelineCaches/") && !entryName.includes("/_rels/")
5261
+ ? (model.timelineCacheParts ?? (model.timelineCacheParts = {}))
5262
+ : entryName.startsWith("xl/slicers/_rels/")
5263
+ ? (model.slicerParts ?? (model.slicerParts = {}))
5264
+ : entryName.startsWith("xl/slicerCaches/_rels/")
5265
+ ? (model.slicerCacheParts ?? (model.slicerCacheParts = {}))
5266
+ : entryName.startsWith("xl/timelines/_rels/")
5267
+ ? (model.timelineParts ?? (model.timelineParts = {}))
5268
+ : (model.timelineCacheParts ?? (model.timelineCacheParts = {}));
5269
+ targetMap[entryName] = rawData ?? (await this.collectStreamData(stream));
5270
+ return true;
5271
+ }
1382
5272
  return false;
1383
5273
  }
1384
- /**
1385
- * Store a passthrough file for preservation during read/write cycles.
1386
- * These files are not parsed but stored as raw bytes to be written back unchanged.
1387
- */
1388
- async _processPassthroughEntry(stream, model, entryName) {
1389
- const data = await this.collectStreamData(stream);
1390
- model.passthrough[entryName] = data;
1391
- }
1392
5274
  // ===========================================================================
1393
5275
  // Write methods - shared by all platforms
1394
5276
  // ===========================================================================
@@ -1457,6 +5339,16 @@ class XLSX {
1457
5339
  Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookMetadata
1458
5340
  });
1459
5341
  }
5342
+ // Office 365 threaded comments need a workbook-level person
5343
+ // directory. The rel Target is `persons/person.xml` (relative to
5344
+ // the xl/ workbook home, matching how Excel writes it).
5345
+ if (model.hasPersons) {
5346
+ relationships.push({
5347
+ Id: `rId${count++}`,
5348
+ Type: XLSX.RelType.Person,
5349
+ Target: "persons/person.xml"
5350
+ });
5351
+ }
1460
5352
  // R9-B6: Deduplicate pivot cache relationships by cacheId. When multiple pivot
1461
5353
  // tables share the same cache, only one workbook relationship should be created.
1462
5354
  // Also assigns rId to each pivot table (R9-B7: typed on PivotTable interface).
@@ -1486,6 +5378,15 @@ class XLSX {
1486
5378
  Target: (0, ooxml_paths_1.worksheetRelTarget)(worksheet.fileIndex)
1487
5379
  });
1488
5380
  });
5381
+ // Add chartsheet relationships
5382
+ (model.chartsheets || []).forEach((cs) => {
5383
+ cs.rId = `rId${count++}`;
5384
+ relationships.push({
5385
+ Id: cs.rId,
5386
+ Type: rel_type_1.RelType.Chartsheet,
5387
+ Target: `chartsheets/sheet${cs.sheetNo}.xml`
5388
+ });
5389
+ });
1489
5390
  // External workbook link rels are written AFTER worksheets on purpose:
1490
5391
  // Excel tolerates either order, but stable ordering (worksheets then
1491
5392
  // externalLinks) keeps the emitted workbook.xml.rels diff-friendly for
@@ -1557,6 +5458,16 @@ class XLSX {
1557
5458
  if (worksheet.comments.length > 0) {
1558
5459
  await this._renderToZip(zip, (0, ooxml_paths_1.commentsPath)(fileIndex), commentsXform, worksheet);
1559
5460
  }
5461
+ // Office 365 threaded comments sit in their own part tree
5462
+ // alongside classic VML comments. Written straight from the
5463
+ // structured model without going through an xform instance —
5464
+ // the payload is small and the shape maps 1:1 onto the output.
5465
+ if (worksheet.threadedComments && worksheet.threadedComments.length > 0) {
5466
+ const xml = (0, threaded_comments_xform_1.renderThreadedComments)(worksheet.threadedComments);
5467
+ zip.append(xml, {
5468
+ name: `xl/threadedComments/threadedComment${fileIndex}.xml`
5469
+ });
5470
+ }
1560
5471
  // Generate unified VML drawing (contains both notes and form controls)
1561
5472
  const hasComments = worksheet.comments.length > 0;
1562
5473
  const hasFormControls = worksheet.formControls && worksheet.formControls.length > 0;
@@ -1603,31 +5514,367 @@ class XLSX {
1603
5514
  }
1604
5515
  }
1605
5516
  }
5517
+ async addChartsheets(zip, model) {
5518
+ const chartsheetXform = new chartsheet_xform_1.ChartsheetXform();
5519
+ const relsXform = new relationships_xform_1.RelationshipsXform();
5520
+ const vmlDrawingXform = new vml_drawing_xform_1.VmlDrawingXform();
5521
+ // Track VML drawing zip paths we re-emit for chartsheets so we
5522
+ // don't accidentally write the same VML part twice when a single
5523
+ // VML file is referenced by multiple chartsheets. Writing a ZIP
5524
+ // entry twice produces a package with duplicate central-directory
5525
+ // entries — most consumers tolerate it (reading the last), but
5526
+ // validators flag it and `unzip -l` shows the duplication.
5527
+ const emittedVmlPaths = new Set();
5528
+ for (const cs of model.chartsheets || []) {
5529
+ await this._renderToZip(zip, (0, ooxml_paths_1.chartsheetPath)(cs.sheetNo), chartsheetXform, cs);
5530
+ // Chartsheet rels. A chartsheet may carry rels beyond the
5531
+ // drawing reference — `legacyDrawing`, `legacyDrawingHF`,
5532
+ // `drawingHF`, `picture`, etc. — and those rels are referenced
5533
+ // by `r:id` attributes inside the raw-captured `rawChildren`
5534
+ // blocks. If we only emit the drawing rel (the previous
5535
+ // implementation), every other r:id goes dangling at save,
5536
+ // corrupting the package.
5537
+ //
5538
+ // Strategy:
5539
+ // 1. Start with the preserved `relationships` list from load
5540
+ // (missing for newly-created chartsheets).
5541
+ // 2. Overlay / insert the current drawing rel — the drawing
5542
+ // target may have been rewritten (e.g. chartsheet renamed
5543
+ // or its drawing renumbered) so we replace any prior
5544
+ // entry with the same Id.
5545
+ const baseRels = Array.isArray(cs.relationships) ? [...cs.relationships] : [];
5546
+ if (cs.drawing) {
5547
+ const drawingRel = {
5548
+ Id: cs.drawing.rId,
5549
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
5550
+ Target: `../drawings/${cs.drawingName}.xml`
5551
+ };
5552
+ const existingIdx = baseRels.findIndex((r) => r?.Id === cs.drawing.rId);
5553
+ if (existingIdx >= 0) {
5554
+ baseRels[existingIdx] = drawingRel;
5555
+ }
5556
+ else {
5557
+ baseRels.push(drawingRel);
5558
+ }
5559
+ }
5560
+ if (baseRels.length > 0) {
5561
+ await this._renderToZip(zip, (0, ooxml_paths_1.chartsheetRelsPath)(cs.sheetNo), relsXform, baseRels);
5562
+ }
5563
+ // Re-emit any VML drawing parts this chartsheet's rels reference.
5564
+ // The worksheet loop only emits VML for worksheets that own
5565
+ // comments / form controls / header images; a chartsheet that
5566
+ // carries its own `<legacyDrawing r:id="…"/>` would preserve its
5567
+ // rel target on write but leave the VML body missing from the
5568
+ // package — a dangling relationship. Walk the chartsheet's rels,
5569
+ // resolve each VML target against the chartsheet path, and emit
5570
+ // the parsed body captured at load time.
5571
+ if (model.vmlDrawings) {
5572
+ const baseDir = `xl/chartsheets/`;
5573
+ for (const rel of baseRels) {
5574
+ if (rel?.Type !== rel_type_1.RelType.VmlDrawing || !rel.Target) {
5575
+ continue;
5576
+ }
5577
+ const vmlPath = (0, ooxml_paths_1.resolveRelTarget)(baseDir, rel.Target);
5578
+ if (emittedVmlPaths.has(vmlPath)) {
5579
+ continue;
5580
+ }
5581
+ const vmlModel = model.vmlDrawings[vmlPath];
5582
+ if (!vmlModel) {
5583
+ continue;
5584
+ }
5585
+ emittedVmlPaths.add(vmlPath);
5586
+ await this._renderToZip(zip, vmlPath, vmlDrawingXform, vmlModel);
5587
+ // `prepareChartsheets` already flipped `model.hasChartsheetVml`
5588
+ // before content-types was written, so no further signalling
5589
+ // is needed here.
5590
+ }
5591
+ }
5592
+ }
5593
+ }
1606
5594
  async addDrawings(zip, model) {
1607
5595
  const drawingXform = new drawing_xform_1.DrawingXform();
1608
5596
  const relsXform = new relationships_xform_1.RelationshipsXform();
1609
- const rawDrawings = model.rawDrawings || {};
1610
5597
  for (const worksheet of model.worksheets) {
1611
5598
  const { drawing } = worksheet;
1612
5599
  if (drawing) {
1613
- // Check if drawing rels contain chart references using helper
1614
- const hasChartReference = this.drawingHasChartReference(drawing);
1615
- if (hasChartReference && rawDrawings[drawing.name]) {
1616
- // Use raw data for drawings with chart references (passthrough)
1617
- zip.append(rawDrawings[drawing.name], { name: (0, ooxml_paths_1.drawingPath)(drawing.name) });
5600
+ const filteredAnchors = (0, drawing_utils_1.filterDrawingAnchors)(drawing.anchors ?? []);
5601
+ const drawingForWrite = drawing.anchors
5602
+ ? { ...drawing, anchors: filteredAnchors }
5603
+ : drawing;
5604
+ drawingXform.prepare(drawingForWrite);
5605
+ await this._renderToZip(zip, (0, ooxml_paths_1.drawingPath)(drawing.name), drawingXform, drawingForWrite);
5606
+ await this._renderToZip(zip, (0, ooxml_paths_1.drawingRelsPath)(drawing.name), relsXform, drawing.rels);
5607
+ }
5608
+ }
5609
+ // Chartsheet drawings — each chartsheet references a drawing
5610
+ // containing a single chart that fills the entire sheet. Unlike
5611
+ // worksheet-embedded charts (where a `<xdr:twoCellAnchor>` with
5612
+ // `<xdr:from>/<xdr:to>` cell references pins the chart to a
5613
+ // rectangle of cells, whose dimensions Excel computes from the
5614
+ // sheet's column widths and row heights), a chartsheet has no
5615
+ // cell grid — its `sheetData` is empty. A cell-based anchor on
5616
+ // a chartsheet therefore resolves to a 0×0 rectangle and Excel
5617
+ // renders an empty white canvas instead of the chart.
5618
+ //
5619
+ // Excel's own output for chartsheet drawings uses
5620
+ // `<xdr:absoluteAnchor>` with concrete EMU `pos`/`ext` values
5621
+ // (≈ 10.84″ × 6.67″ — standard A4 landscape minus default
5622
+ // margins), AND repeats the same `<a:ext>` on the inner
5623
+ // `<xdr:graphicFrame>/<xdr:xfrm>` so both the anchor-level and
5624
+ // frame-level sizes are non-zero. Omitting either produces the
5625
+ // blank-canvas rendering bug users see. We emit the same byte
5626
+ // layout here verbatim rather than route through `DrawingXform`,
5627
+ // which is tuned for the worksheet twoCellAnchor case.
5628
+ const CHARTSHEET_EMU_CX = 9906000; // ≈ 10.84 inches
5629
+ const CHARTSHEET_EMU_CY = 6096000; // ≈ 6.67 inches
5630
+ for (const cs of model.chartsheets || []) {
5631
+ if (cs.drawingName && (cs.chartNumber || cs.chartExNumber)) {
5632
+ const chartRId = "rId1";
5633
+ const isChartEx = !cs.chartNumber && !!cs.chartExNumber;
5634
+ const chartName = isChartEx ? `Chart ${cs.chartExNumber}` : `Chart ${cs.chartNumber}`;
5635
+ const drawingXml = renderChartsheetDrawingXml({
5636
+ chartRId,
5637
+ chartName,
5638
+ isChartEx,
5639
+ extCx: CHARTSHEET_EMU_CX,
5640
+ extCy: CHARTSHEET_EMU_CY
5641
+ });
5642
+ const drawingRels = [
5643
+ {
5644
+ Id: chartRId,
5645
+ Type: isChartEx ? rel_type_1.RelType.ChartEx : rel_type_1.RelType.Chart,
5646
+ Target: isChartEx
5647
+ ? (0, ooxml_paths_1.chartExRelTargetFromDrawing)(cs.chartExNumber)
5648
+ : (0, ooxml_paths_1.chartRelTargetFromDrawing)(cs.chartNumber)
5649
+ }
5650
+ ];
5651
+ zip.append(drawingXml, { name: (0, ooxml_paths_1.drawingPath)(cs.drawingName) });
5652
+ await this._renderToZip(zip, (0, ooxml_paths_1.drawingRelsPath)(cs.drawingName), relsXform, drawingRels);
5653
+ }
5654
+ }
5655
+ }
5656
+ async addCharts(zip, model, strictTemplateMode = false) {
5657
+ const relsXform = new relationships_xform_1.RelationshipsXform();
5658
+ for (const [n, chartEntry] of Object.entries(model.chartEntries || {})) {
5659
+ if (shouldPassthroughChartEntry(chartEntry)) {
5660
+ zip.append(chartEntry.rawData, { name: (0, ooxml_paths_1.chartPath)(n) });
5661
+ }
5662
+ else {
5663
+ const requireRawPatch = shouldRequireChartRawPatch(chartEntry, strictTemplateMode);
5664
+ const patched = tryPatchChartRawXml(chartEntry, requireRawPatch);
5665
+ if (patched) {
5666
+ zip.append(patched, { name: (0, ooxml_paths_1.chartPath)(n) });
1618
5667
  }
1619
5668
  else {
1620
- // Use regenerated XML for normal drawings (images, shapes)
1621
- const filteredAnchors = (0, drawing_utils_1.filterDrawingAnchors)(drawing.anchors ?? []);
1622
- const drawingForWrite = drawing.anchors
1623
- ? { ...drawing, anchors: filteredAnchors }
1624
- : drawing;
1625
- drawingXform.prepare(drawingForWrite);
1626
- await this._renderToZip(zip, (0, ooxml_paths_1.drawingPath)(drawing.name), drawingXform, drawingForWrite);
5669
+ if (requireRawPatch) {
5670
+ throw new errors_1.ChartOptionsError(buildChartStrictFailureMessage(n, chartEntry.model));
5671
+ }
5672
+ // Render via buffered path so we can splice preserved leading
5673
+ // XML comments (e.g. vendor provenance markers) from the
5674
+ // original raw bytes back in front of `<c:chart>`. The SAX-
5675
+ // backed xform parser drops `comment` events so the
5676
+ // structured model has no memory of them.
5677
+ const buffered = renderChartWithLeadingComments(chartEntry, (0, chart_host_registry_1.getChartSupport)().createChartSpaceXform());
5678
+ zip.append(buffered, { name: (0, ooxml_paths_1.chartPath)(n) });
1627
5679
  }
1628
- await this._renderToZip(zip, (0, ooxml_paths_1.drawingRelsPath)(drawing.name), relsXform, drawing.rels);
1629
5680
  }
5681
+ // Write chart style (raw bytes)
5682
+ if (model.chartStyles?.[n]) {
5683
+ zip.append(model.chartStyles[n], { name: (0, ooxml_paths_1.chartStylePath)(n) });
5684
+ }
5685
+ // Write chart colors (raw bytes)
5686
+ if (model.chartColors?.[n]) {
5687
+ zip.append(model.chartColors[n], { name: (0, ooxml_paths_1.chartColorsPath)(n) });
5688
+ }
5689
+ // Build chart rels
5690
+ const rels = [];
5691
+ // Collect original rels first (excluding style/colors which we regenerate)
5692
+ // We keep their original Ids to avoid breaking r:id references inside chart XML
5693
+ const originalRels = model.chartRels?.[n];
5694
+ const usedIds = new Set();
5695
+ if (Array.isArray(originalRels)) {
5696
+ for (const rel of originalRels) {
5697
+ if (rel.Type !== rel_type_1.RelType.ChartStyle && rel.Type !== rel_type_1.RelType.ChartColors) {
5698
+ rels.push(rel);
5699
+ usedIds.add(rel.Id);
5700
+ }
5701
+ }
5702
+ }
5703
+ // Fold in rels allocated during chart registration — notably the
5704
+ // image relationships added by `resolvePendingChartImages` for
5705
+ // `pictureFill.image`. The chart XML already embeds the `r:id`
5706
+ // assigned during registration, so we must preserve those ids
5707
+ // verbatim (don't rewrite) and only skip duplicates that were
5708
+ // already round-tripped through `originalRels`.
5709
+ const entryRels = chartEntry.rels;
5710
+ if (Array.isArray(entryRels)) {
5711
+ for (const rel of entryRels) {
5712
+ if (!rel?.Id || usedIds.has(rel.Id)) {
5713
+ continue;
5714
+ }
5715
+ rels.push(rel);
5716
+ usedIds.add(rel.Id);
5717
+ }
5718
+ }
5719
+ // Allocate new rIds for style/colors that don't conflict with existing ones
5720
+ let rIdCount = 1;
5721
+ const nextRId = () => {
5722
+ let id = `rId${rIdCount++}`;
5723
+ while (usedIds.has(id)) {
5724
+ id = `rId${rIdCount++}`;
5725
+ }
5726
+ usedIds.add(id);
5727
+ return id;
5728
+ };
5729
+ // Add style rel if style exists
5730
+ if (model.chartStyles?.[n]) {
5731
+ rels.push({
5732
+ Id: nextRId(),
5733
+ Type: rel_type_1.RelType.ChartStyle,
5734
+ Target: (0, ooxml_paths_1.chartStyleRelTarget)(n)
5735
+ });
5736
+ }
5737
+ // Add colors rel if colors exist
5738
+ if (model.chartColors?.[n]) {
5739
+ rels.push({
5740
+ Id: nextRId(),
5741
+ Type: rel_type_1.RelType.ChartColors,
5742
+ Target: (0, ooxml_paths_1.chartColorsRelTarget)(n)
5743
+ });
5744
+ }
5745
+ // Write c:userShapes overlay drawing part — preserves annotation
5746
+ // shapes attached to the chart. Bytes can come from a loaded file
5747
+ // (captured onto `chartEntry.userShapesXml` by
5748
+ // `_reconcileChartUserShapes`) or from a programmatic call to
5749
+ // `Chart.setUserShapesXml`. We always emit the bytes at a canonical
5750
+ // path (`xl/drawings/chartUserShape{n}.xml`) and rewrite the rel
5751
+ // Target accordingly so the chart XML's existing `r:id` still
5752
+ // resolves.
5753
+ if (chartEntry.userShapesXml) {
5754
+ zip.append(chartEntry.userShapesXml, { name: (0, ooxml_paths_1.chartUserShapesPath)(n) });
5755
+ const targetPath = (0, ooxml_paths_1.chartUserShapesRelTarget)(n);
5756
+ const existingRel = rels.find(r => r?.Type === rel_type_1.RelType.ChartUserShapes);
5757
+ if (existingRel) {
5758
+ existingRel.Target = targetPath;
5759
+ }
5760
+ else {
5761
+ // No existing rel — allocate one, preferring the r:id the model
5762
+ // already embeds in `<c:userShapes r:id="…"/>` so the chart XML
5763
+ // doesn't need a rewrite.
5764
+ const relId = chartEntry.model.userShapesRelId ?? nextRId();
5765
+ usedIds.add(relId);
5766
+ rels.push({ Id: relId, Type: rel_type_1.RelType.ChartUserShapes, Target: targetPath });
5767
+ }
5768
+ }
5769
+ // Write chart rels if any
5770
+ if (rels.length > 0) {
5771
+ await this._renderToZip(zip, (0, ooxml_paths_1.chartRelsPath)(n), relsXform, rels);
5772
+ }
5773
+ }
5774
+ }
5775
+ async addChartExEntries(zip, model, strictTemplateMode = false) {
5776
+ const relsXform = new relationships_xform_1.RelationshipsXform();
5777
+ const rawEntries = model.chartExEntries || {};
5778
+ const structured = (model.chartExStructuredEntries ?? {});
5779
+ const written = new Set();
5780
+ // 1. Loaded chartEx entries — byte-preserve while clean, render structured XML once edited.
5781
+ for (const [n, rawBytes] of Object.entries(rawEntries)) {
5782
+ const structuredEntry = structured[n];
5783
+ if (structuredEntry && !shouldPassthroughChartExEntry(structuredEntry)) {
5784
+ const requireRawPatch = shouldRequireChartExRawPatch(structuredEntry, strictTemplateMode);
5785
+ const patched = tryPatchChartExRawXml(structuredEntry, requireRawPatch);
5786
+ if (patched) {
5787
+ zip.append(patched, { name: (0, ooxml_paths_1.chartExPath)(n) });
5788
+ }
5789
+ else {
5790
+ if (requireRawPatch) {
5791
+ throw new errors_1.ChartOptionsError(buildChartExStrictFailureMessage(n, structuredEntry.model));
5792
+ }
5793
+ const renderedXml = (0, chart_host_registry_1.getChartSupport)().renderChartEx(stripChartExRawXml(structuredEntry.model));
5794
+ // Splice preserved leading XML comments from original raw
5795
+ // bytes back in front of `<cx:chart>`. The chartEx parser
5796
+ // calls `parseXml(...)` without `{ comments: true }` so the
5797
+ // structured model has no memory of them.
5798
+ const originalRawXml = rawBytes
5799
+ ? new TextDecoder().decode(rawBytes)
5800
+ : structuredEntry.model.rawXml;
5801
+ const finalXml = spliceChartExLeadingComments(renderedXml, originalRawXml);
5802
+ zip.append(finalXml, {
5803
+ name: (0, ooxml_paths_1.chartExPath)(n)
5804
+ });
5805
+ }
5806
+ }
5807
+ else {
5808
+ zip.append(rawBytes, { name: (0, ooxml_paths_1.chartExPath)(n) });
5809
+ }
5810
+ written.add(n);
5811
+ // Write chartEx rels if present
5812
+ const rels = model.chartExRels?.[n];
5813
+ const chartExRels = this._buildChartExRels(n, rels, model);
5814
+ if (chartExRels.length > 0) {
5815
+ await this._renderToZip(zip, (0, ooxml_paths_1.chartExRelsPath)(n), relsXform, chartExRels);
5816
+ }
5817
+ this._appendChartExSidecars(zip, model, n, structuredEntry);
5818
+ }
5819
+ // 2. Structured chartEx entries — built programmatically via addChartEx()
5820
+ for (const [n, entry] of Object.entries(structured)) {
5821
+ if (written.has(n)) {
5822
+ continue;
5823
+ }
5824
+ // Data-ref → `_xlchart.vN.M` defined-name rewrite has already
5825
+ // run in `prepareChartExSidecars` so the model's formulas now
5826
+ // point at hidden names and the cached `<cx:lvl>` levels have
5827
+ // been cleared. Force structural rebuild to pick up the
5828
+ // mutated model (any stale `rawXml` from earlier mutations
5829
+ // would mask the rewrite).
5830
+ const xml = (0, chart_host_registry_1.getChartSupport)().renderChartEx(entry.model, { forceStructural: true });
5831
+ zip.append(xml, { name: (0, ooxml_paths_1.chartExPath)(n) });
5832
+ this._appendChartExSidecars(zip, model, n, entry);
5833
+ const chartExRels = this._buildChartExRels(n, entry.rels, model, entry);
5834
+ if (chartExRels.length > 0) {
5835
+ await this._renderToZip(zip, (0, ooxml_paths_1.chartExRelsPath)(n), relsXform, chartExRels);
5836
+ }
5837
+ }
5838
+ }
5839
+ _appendChartExSidecars(zip, model, n, entry) {
5840
+ if (entry?.model.style) {
5841
+ zip.append(new TextEncoder().encode((0, chart_host_registry_1.getChartSupport)().buildChartStyle(entry.model.style)), {
5842
+ name: (0, ooxml_paths_1.chartExStylePath)(n)
5843
+ });
5844
+ }
5845
+ else if (model.chartExStyles?.[n]) {
5846
+ zip.append(model.chartExStyles[n], { name: (0, ooxml_paths_1.chartExStylePath)(n) });
5847
+ }
5848
+ if (entry?.model.colors) {
5849
+ zip.append(new TextEncoder().encode((0, chart_host_registry_1.getChartSupport)().buildChartColors(entry.model.colors)), {
5850
+ name: (0, ooxml_paths_1.chartExColorsPath)(n)
5851
+ });
5852
+ }
5853
+ else if (model.chartExColors?.[n]) {
5854
+ zip.append(model.chartExColors[n], { name: (0, ooxml_paths_1.chartExColorsPath)(n) });
5855
+ }
5856
+ }
5857
+ _buildChartExRels(n, existing, model, entry) {
5858
+ const rels = Array.isArray(existing) ? [...existing] : [];
5859
+ const usedIds = new Set(rels.map(rel => rel.Id));
5860
+ const nextRId = () => {
5861
+ let i = 1;
5862
+ while (usedIds.has(`rId${i}`)) {
5863
+ i++;
5864
+ }
5865
+ const id = `rId${i}`;
5866
+ usedIds.add(id);
5867
+ return id;
5868
+ };
5869
+ const hasStyle = !!(model.chartExStyles?.[n] || entry?.model.style);
5870
+ const hasColors = !!(model.chartExColors?.[n] || entry?.model.colors);
5871
+ if (hasStyle && !rels.some(rel => rel.Type === rel_type_1.RelType.ChartStyle)) {
5872
+ rels.push({ Id: nextRId(), Type: rel_type_1.RelType.ChartStyle, Target: (0, ooxml_paths_1.chartExStyleRelTarget)(n) });
1630
5873
  }
5874
+ if (hasColors && !rels.some(rel => rel.Type === rel_type_1.RelType.ChartColors)) {
5875
+ rels.push({ Id: nextRId(), Type: rel_type_1.RelType.ChartColors, Target: (0, ooxml_paths_1.chartExColorsRelTarget)(n) });
5876
+ }
5877
+ return rels;
1631
5878
  }
1632
5879
  async addTables(zip, model) {
1633
5880
  const tableXform = new table_xform_1.TableXform();
@@ -1649,7 +5896,7 @@ class XLSX {
1649
5896
  * relative** `Target` whenever the user supplied one. This is the single
1650
5897
  * line that makes Office / WPS resolve the referenced workbook relative
1651
5898
  * to the current file's directory (not the `%USERPROFILE%\Documents`
1652
- * fallback) — the root of the behaviour reported in exceljs#3039.
5899
+ * fallback) — the root of the relative-path external-link behaviour.
1653
5900
  */
1654
5901
  async addExternalLinks(zip, model) {
1655
5902
  const externalLinks = (model.externalLinks ?? []);
@@ -1673,15 +5920,6 @@ class XLSX {
1673
5920
  ]);
1674
5921
  }
1675
5922
  }
1676
- /**
1677
- * Write passthrough files (charts, etc.) that were preserved during read.
1678
- * These files are written back unchanged to preserve unsupported features.
1679
- */
1680
- addPassthrough(zip, model) {
1681
- const passthroughManager = new passthrough_manager_1.PassthroughManager();
1682
- passthroughManager.fromRecord(model.passthrough || {});
1683
- passthroughManager.writeToZip(zip);
1684
- }
1685
5923
  async addPivotTables(zip, model) {
1686
5924
  if (!model.pivotTables.length) {
1687
5925
  return;
@@ -1788,6 +6026,20 @@ class XLSX {
1788
6026
  worksheetOptions.drawings = model.drawings = [];
1789
6027
  worksheetOptions.commentRefs = model.commentRefs = [];
1790
6028
  worksheetOptions.formControlRefs = model.formControlRefs = [];
6029
+ // Collect the list of worksheets that carry Office 365 threaded
6030
+ // comments so the Content Types override list can include them
6031
+ // and the ZIP writer knows which per-sheet parts to emit. Sheets
6032
+ // with zero threaded comments are skipped entirely — Excel treats
6033
+ // a missing part as "no threaded comments on this sheet".
6034
+ model.threadedCommentSheetIds = [];
6035
+ model.hasPersons = (model.persons?.length ?? 0) > 0;
6036
+ // Raw-passthrough parts captured on load. The Content-Types
6037
+ // override list and the content-types writer need these path
6038
+ // lists so the emitted bytes are registered in the package.
6039
+ model.slicerPartPaths = Object.keys(model.slicerParts ?? {}).filter(p => !p.includes("/_rels/"));
6040
+ model.slicerCachePartPaths = Object.keys(model.slicerCacheParts ?? {}).filter(p => !p.includes("/_rels/"));
6041
+ model.timelinePartPaths = Object.keys(model.timelineParts ?? {}).filter(p => !p.includes("/_rels/"));
6042
+ model.timelineCachePartPaths = Object.keys(model.timelineCacheParts ?? {}).filter(p => !p.includes("/_rels/"));
1791
6043
  model.hasHeaderWatermark = false;
1792
6044
  let tableCount = 0;
1793
6045
  model.tables = [];
@@ -1815,6 +6067,11 @@ class XLSX {
1815
6067
  model.tables.push(table);
1816
6068
  });
1817
6069
  worksheetXform.prepare(worksheet, worksheetOptions);
6070
+ // Register sheets that carry threaded comments so the Content
6071
+ // Types override list and the zip emission loop find them.
6072
+ if (worksheet.threadedComments && worksheet.threadedComments.length > 0) {
6073
+ model.threadedCommentSheetIds.push(worksheet.fileIndex);
6074
+ }
1818
6075
  });
1819
6076
  // ContentTypesXform expects this flag
1820
6077
  model.hasCheckboxes = model.styles.hasCheckboxes;
@@ -1836,11 +6093,98 @@ class XLSX {
1836
6093
  if (worksheetOptions.hasHeaderWatermark) {
1837
6094
  model.hasHeaderWatermark = true;
1838
6095
  }
1839
- // Build passthroughContentTypes for ContentTypesXform using PassthroughManager
1840
- const passthrough = model.passthrough || {};
1841
- const passthroughManager = new passthrough_manager_1.PassthroughManager();
1842
- passthroughManager.fromRecord(passthrough);
1843
- model.passthroughContentTypes = passthroughManager.getContentTypes();
6096
+ }
6097
+ prepareChartExSidecars(model) {
6098
+ const structured = (model.chartExStructuredEntries ?? {});
6099
+ for (const [n, entry] of Object.entries(structured)) {
6100
+ // Excel 2016+ requires chartEx `<cx:f>` to reference hidden
6101
+ // `_xlchart.vN.M` defined names, NOT direct worksheet ranges.
6102
+ // Walk the model and rewrite data refs BEFORE workbook.xml is
6103
+ // serialised so the newly-registered defined names end up in
6104
+ // `<definedNames>`. See `rewriteChartExDataRefsToDefinedNames`
6105
+ // for the full rationale.
6106
+ const chartExIndex = parseInt(n, 10);
6107
+ if (Number.isFinite(chartExIndex) &&
6108
+ model.definedNamesInstance &&
6109
+ typeof model.definedNamesInstance.addHidden === "function") {
6110
+ (0, chart_host_registry_1.getChartSupport)().rewriteChartExDataRefsToDefinedNames(entry.model, chartExIndex, (name, ref) => {
6111
+ model.definedNamesInstance.addHidden(ref, name);
6112
+ });
6113
+ // Re-materialise the array snapshot so addWorkbook picks up the
6114
+ // new hidden `_xlchart.*` names. `definedNames` in the write
6115
+ // model is the serialised form (array); the rewrite added
6116
+ // entries to the live `DefinedNames` instance on the workbook.
6117
+ model.definedNames = model.definedNamesInstance.model;
6118
+ }
6119
+ if (entry.model.style && !model.chartExStyles?.[n]) {
6120
+ model.chartExStyles ?? (model.chartExStyles = {});
6121
+ model.chartExStyles[n] = new TextEncoder().encode((0, chart_host_registry_1.getChartSupport)().buildChartStyle(entry.model.style));
6122
+ }
6123
+ if (entry.model.colors && !model.chartExColors?.[n]) {
6124
+ model.chartExColors ?? (model.chartExColors = {});
6125
+ model.chartExColors[n] = new TextEncoder().encode((0, chart_host_registry_1.getChartSupport)().buildChartColors(entry.model.colors));
6126
+ }
6127
+ }
6128
+ }
6129
+ prepareChartsheets(model) {
6130
+ if (!model.chartsheets || model.chartsheets.length === 0) {
6131
+ return;
6132
+ }
6133
+ const usedDrawingNumbers = new Set();
6134
+ for (const drawing of model.drawings ?? []) {
6135
+ const match = /^drawing(\d+)$/.exec(drawing.name ?? "");
6136
+ if (match) {
6137
+ usedDrawingNumbers.add(parseInt(match[1], 10));
6138
+ }
6139
+ }
6140
+ for (const cs of model.chartsheets) {
6141
+ const existingMatch = /^drawing(\d+)$/.exec(cs.drawingName ?? "");
6142
+ if (existingMatch) {
6143
+ usedDrawingNumbers.add(parseInt(existingMatch[1], 10));
6144
+ }
6145
+ }
6146
+ const nextDrawingName = () => {
6147
+ let n = 1;
6148
+ while (usedDrawingNumbers.has(n)) {
6149
+ n++;
6150
+ }
6151
+ usedDrawingNumbers.add(n);
6152
+ return `drawing${n}`;
6153
+ };
6154
+ for (const cs of model.chartsheets) {
6155
+ if (!cs.drawingName) {
6156
+ cs.drawingName = nextDrawingName();
6157
+ }
6158
+ if (!cs.drawing) {
6159
+ cs.drawing = { rId: "rId1" };
6160
+ }
6161
+ if (!model.drawings.some((drawing) => drawing.name === cs.drawingName)) {
6162
+ model.drawings.push({ name: cs.drawingName });
6163
+ }
6164
+ }
6165
+ // Signal the content-types writer that the `Default Extension="vml"`
6166
+ // declaration is required when ANY chartsheet carries a VML
6167
+ // relationship (e.g. `<legacyDrawing r:id="…"/>` referencing a
6168
+ // preserved `xl/drawings/vmlDrawing*.vml` part). Previously the
6169
+ // flag was only set inside `addChartsheets`, which runs AFTER
6170
+ // `addContentTypes` — so a chartsheet-only VML dependency silently
6171
+ // shipped without its content-type declaration, and Excel refused
6172
+ // to open the resulting package. Compute it here, during `prepare`,
6173
+ // before any part is written.
6174
+ if (model.vmlDrawings) {
6175
+ for (const cs of model.chartsheets) {
6176
+ if (!Array.isArray(cs.relationships)) {
6177
+ continue;
6178
+ }
6179
+ const hasVmlRel = cs.relationships.some((rel) => rel?.Type === rel_type_1.RelType.VmlDrawing &&
6180
+ typeof rel.Target === "string" &&
6181
+ model.vmlDrawings[(0, ooxml_paths_1.resolveRelTarget)("xl/chartsheets/", rel.Target)] !== undefined);
6182
+ if (hasVmlRel) {
6183
+ model.hasChartsheetVml = true;
6184
+ break;
6185
+ }
6186
+ }
6187
+ }
1844
6188
  }
1845
6189
  }
1846
6190
  exports.XLSX = XLSX;