@cj-tech-master/excelts 5.0.6 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/dist/browser/index.browser.d.ts +1 -1
  2. package/dist/browser/index.d.ts +1 -1
  3. package/dist/browser/modules/archive/unzip/stream.base.js +19 -19
  4. package/dist/browser/modules/archive/unzip/stream.browser.js +3 -3
  5. package/dist/browser/modules/csv/csv-core.js +6 -3
  6. package/dist/browser/modules/csv/csv.browser.js +2 -2
  7. package/dist/browser/modules/csv/csv.js +1 -1
  8. package/dist/browser/modules/excel/anchor.js +4 -4
  9. package/dist/browser/modules/excel/cell.js +5 -5
  10. package/dist/browser/modules/excel/column.js +4 -4
  11. package/dist/browser/modules/excel/defined-names.js +1 -1
  12. package/dist/browser/modules/excel/form-control.js +1 -1
  13. package/dist/browser/modules/excel/pivot-table.d.ts +168 -17
  14. package/dist/browser/modules/excel/pivot-table.js +278 -70
  15. package/dist/browser/modules/excel/row.js +4 -4
  16. package/dist/browser/modules/excel/stream/workbook-reader.browser.js +4 -4
  17. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +4 -4
  18. package/dist/browser/modules/excel/stream/worksheet-reader.js +1 -1
  19. package/dist/browser/modules/excel/stream/worksheet-writer.js +4 -4
  20. package/dist/browser/modules/excel/table.js +2 -2
  21. package/dist/browser/modules/excel/types.d.ts +0 -4
  22. package/dist/browser/modules/excel/utils/cell-format.js +3 -3
  23. package/dist/browser/modules/excel/utils/shared-formula.js +1 -1
  24. package/dist/browser/modules/excel/utils/stream-buf.js +2 -2
  25. package/dist/browser/modules/excel/utils/string-buf.js +1 -1
  26. package/dist/browser/modules/excel/workbook.d.ts +0 -2
  27. package/dist/browser/modules/excel/workbook.js +4 -5
  28. package/dist/browser/modules/excel/worksheet.js +9 -9
  29. package/dist/browser/modules/excel/xlsx/xform/base-xform.d.ts +5 -5
  30. package/dist/browser/modules/excel/xlsx/xform/base-xform.js +1 -1
  31. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
  32. package/dist/browser/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
  33. package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
  34. package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.d.ts +1 -2
  35. package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.d.ts +1 -2
  36. package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-position-xform.d.ts +3 -4
  37. package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
  38. package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
  39. package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +1 -2
  40. package/dist/browser/modules/excel/xlsx/xform/comment/vml-notes-xform.d.ts +1 -2
  41. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
  42. package/dist/browser/modules/excel/xlsx/xform/comment/vml-textbox-xform.d.ts +1 -2
  43. package/dist/browser/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
  44. package/dist/browser/modules/excel/xlsx/xform/composite-xform.d.ts +1 -1
  45. package/dist/browser/modules/excel/xlsx/xform/core/app-xform.js +1 -1
  46. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
  47. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  48. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +1 -2
  49. package/dist/browser/modules/excel/xlsx/xform/drawing/cell-position-xform.d.ts +1 -2
  50. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -2
  51. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +1 -2
  52. package/dist/browser/modules/excel/xlsx/xform/drawing/ext-xform.d.ts +1 -2
  53. package/dist/browser/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
  54. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
  55. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
  56. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +1 -2
  57. package/dist/browser/modules/excel/xlsx/xform/list-xform.d.ts +1 -2
  58. package/dist/browser/modules/excel/xlsx/xform/list-xform.js +3 -3
  59. package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field-xform.d.ts +5 -15
  60. package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +134 -52
  61. package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field.d.ts +14 -15
  62. package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field.js +244 -70
  63. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +13 -29
  64. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +213 -37
  65. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +7 -34
  66. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +143 -41
  67. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +101 -27
  68. package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
  69. package/dist/browser/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.d.ts +78 -0
  70. package/dist/browser/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +149 -0
  71. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
  72. package/dist/browser/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
  73. package/dist/browser/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
  74. package/dist/browser/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
  75. package/dist/browser/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
  76. package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
  77. package/dist/browser/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
  78. package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
  79. package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -2
  80. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
  81. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.d.ts +1 -2
  82. package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
  83. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
  84. package/dist/browser/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
  85. package/dist/browser/modules/excel/xlsx/xform/style/color-xform.js +1 -1
  86. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +5 -5
  87. package/dist/browser/modules/excel/xlsx/xform/table/auto-filter-xform.d.ts +1 -2
  88. package/dist/browser/modules/excel/xlsx/xform/table/custom-filter-xform.d.ts +1 -2
  89. package/dist/browser/modules/excel/xlsx/xform/table/filter-column-xform.d.ts +1 -2
  90. package/dist/browser/modules/excel/xlsx/xform/table/filter-xform.d.ts +1 -2
  91. package/dist/browser/modules/excel/xlsx/xform/table/table-column-xform.d.ts +1 -2
  92. package/dist/browser/modules/excel/xlsx/xform/table/table-style-info-xform.d.ts +1 -2
  93. package/dist/browser/modules/excel/xlsx/xform/table/table-xform.d.ts +1 -2
  94. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +5 -2
  95. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +88 -54
  96. package/dist/browser/utils/env.d.ts +0 -5
  97. package/dist/browser/utils/env.js +0 -7
  98. package/dist/browser/utils/utils.base.d.ts +8 -13
  99. package/dist/browser/utils/utils.base.js +40 -47
  100. package/dist/browser/utils/utils.browser.d.ts +1 -1
  101. package/dist/browser/utils/utils.browser.js +1 -1
  102. package/dist/browser/utils/utils.d.ts +1 -1
  103. package/dist/browser/utils/utils.js +1 -1
  104. package/dist/cjs/modules/archive/unzip/stream.base.js +19 -19
  105. package/dist/cjs/modules/archive/unzip/stream.browser.js +3 -3
  106. package/dist/cjs/modules/csv/csv-core.js +6 -3
  107. package/dist/cjs/modules/csv/csv.browser.js +2 -2
  108. package/dist/cjs/modules/csv/csv.js +1 -1
  109. package/dist/cjs/modules/excel/anchor.js +4 -4
  110. package/dist/cjs/modules/excel/cell.js +5 -5
  111. package/dist/cjs/modules/excel/column.js +4 -4
  112. package/dist/cjs/modules/excel/defined-names.js +1 -1
  113. package/dist/cjs/modules/excel/form-control.js +1 -1
  114. package/dist/cjs/modules/excel/pivot-table.js +280 -70
  115. package/dist/cjs/modules/excel/row.js +4 -4
  116. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +4 -4
  117. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
  118. package/dist/cjs/modules/excel/stream/worksheet-reader.js +1 -1
  119. package/dist/cjs/modules/excel/stream/worksheet-writer.js +4 -4
  120. package/dist/cjs/modules/excel/table.js +2 -2
  121. package/dist/cjs/modules/excel/utils/cell-format.js +3 -3
  122. package/dist/cjs/modules/excel/utils/shared-formula.js +1 -1
  123. package/dist/cjs/modules/excel/utils/stream-buf.js +2 -2
  124. package/dist/cjs/modules/excel/utils/string-buf.js +1 -1
  125. package/dist/cjs/modules/excel/workbook.js +4 -5
  126. package/dist/cjs/modules/excel/worksheet.js +9 -9
  127. package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +1 -1
  128. package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
  129. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
  130. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
  131. package/dist/cjs/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
  132. package/dist/cjs/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
  133. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
  134. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
  135. package/dist/cjs/modules/excel/xlsx/xform/core/app-xform.js +1 -1
  136. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
  137. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  138. package/dist/cjs/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
  139. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
  140. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
  141. package/dist/cjs/modules/excel/xlsx/xform/list-xform.js +3 -3
  142. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +133 -51
  143. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/cache-field.js +245 -71
  144. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +212 -36
  145. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +142 -40
  146. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
  147. package/dist/cjs/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +153 -0
  148. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
  149. package/dist/cjs/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
  150. package/dist/cjs/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
  151. package/dist/cjs/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
  152. package/dist/cjs/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
  153. package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
  154. package/dist/cjs/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
  155. package/dist/cjs/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
  156. package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
  157. package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
  158. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
  159. package/dist/cjs/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
  160. package/dist/cjs/modules/excel/xlsx/xform/style/color-xform.js +1 -1
  161. package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +5 -5
  162. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +88 -54
  163. package/dist/cjs/utils/env.js +0 -8
  164. package/dist/cjs/utils/utils.base.js +41 -54
  165. package/dist/cjs/utils/utils.browser.js +2 -7
  166. package/dist/cjs/utils/utils.js +2 -7
  167. package/dist/esm/modules/archive/unzip/stream.base.js +19 -19
  168. package/dist/esm/modules/archive/unzip/stream.browser.js +3 -3
  169. package/dist/esm/modules/csv/csv-core.js +6 -3
  170. package/dist/esm/modules/csv/csv.browser.js +2 -2
  171. package/dist/esm/modules/csv/csv.js +1 -1
  172. package/dist/esm/modules/excel/anchor.js +4 -4
  173. package/dist/esm/modules/excel/cell.js +5 -5
  174. package/dist/esm/modules/excel/column.js +4 -4
  175. package/dist/esm/modules/excel/defined-names.js +1 -1
  176. package/dist/esm/modules/excel/form-control.js +1 -1
  177. package/dist/esm/modules/excel/pivot-table.js +278 -70
  178. package/dist/esm/modules/excel/row.js +4 -4
  179. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +4 -4
  180. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +4 -4
  181. package/dist/esm/modules/excel/stream/worksheet-reader.js +1 -1
  182. package/dist/esm/modules/excel/stream/worksheet-writer.js +4 -4
  183. package/dist/esm/modules/excel/table.js +2 -2
  184. package/dist/esm/modules/excel/utils/cell-format.js +3 -3
  185. package/dist/esm/modules/excel/utils/shared-formula.js +1 -1
  186. package/dist/esm/modules/excel/utils/stream-buf.js +2 -2
  187. package/dist/esm/modules/excel/utils/string-buf.js +1 -1
  188. package/dist/esm/modules/excel/workbook.js +4 -5
  189. package/dist/esm/modules/excel/worksheet.js +9 -9
  190. package/dist/esm/modules/excel/xlsx/xform/base-xform.js +1 -1
  191. package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
  192. package/dist/esm/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
  193. package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
  194. package/dist/esm/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
  195. package/dist/esm/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
  196. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
  197. package/dist/esm/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
  198. package/dist/esm/modules/excel/xlsx/xform/core/app-xform.js +1 -1
  199. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
  200. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
  201. package/dist/esm/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
  202. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
  203. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
  204. package/dist/esm/modules/excel/xlsx/xform/list-xform.js +3 -3
  205. package/dist/esm/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +134 -52
  206. package/dist/esm/modules/excel/xlsx/xform/pivot-table/cache-field.js +244 -70
  207. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +213 -37
  208. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +143 -41
  209. package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
  210. package/dist/esm/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +149 -0
  211. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
  212. package/dist/esm/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
  213. package/dist/esm/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
  214. package/dist/esm/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
  215. package/dist/esm/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
  216. package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
  217. package/dist/esm/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
  218. package/dist/esm/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
  219. package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
  220. package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
  221. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
  222. package/dist/esm/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
  223. package/dist/esm/modules/excel/xlsx/xform/style/color-xform.js +1 -1
  224. package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +5 -5
  225. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +88 -54
  226. package/dist/esm/utils/env.js +0 -7
  227. package/dist/esm/utils/utils.base.js +40 -47
  228. package/dist/esm/utils/utils.browser.js +1 -1
  229. package/dist/esm/utils/utils.js +1 -1
  230. package/dist/iife/excelts.iife.js +1553 -718
  231. package/dist/iife/excelts.iife.js.map +1 -1
  232. package/dist/iife/excelts.iife.min.js +36 -105
  233. package/dist/types/index.browser.d.ts +1 -1
  234. package/dist/types/index.d.ts +1 -1
  235. package/dist/types/modules/excel/pivot-table.d.ts +168 -17
  236. package/dist/types/modules/excel/types.d.ts +0 -4
  237. package/dist/types/modules/excel/workbook.d.ts +0 -2
  238. package/dist/types/modules/excel/xlsx/xform/base-xform.d.ts +5 -5
  239. package/dist/types/modules/excel/xlsx/xform/comment/comment-xform.d.ts +1 -2
  240. package/dist/types/modules/excel/xlsx/xform/comment/comments-xform.d.ts +1 -2
  241. package/dist/types/modules/excel/xlsx/xform/comment/style/vml-position-xform.d.ts +3 -4
  242. package/dist/types/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +1 -2
  243. package/dist/types/modules/excel/xlsx/xform/comment/vml-notes-xform.d.ts +1 -2
  244. package/dist/types/modules/excel/xlsx/xform/comment/vml-textbox-xform.d.ts +1 -2
  245. package/dist/types/modules/excel/xlsx/xform/composite-xform.d.ts +1 -1
  246. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +1 -2
  247. package/dist/types/modules/excel/xlsx/xform/drawing/cell-position-xform.d.ts +1 -2
  248. package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -2
  249. package/dist/types/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +1 -2
  250. package/dist/types/modules/excel/xlsx/xform/drawing/ext-xform.d.ts +1 -2
  251. package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +1 -2
  252. package/dist/types/modules/excel/xlsx/xform/list-xform.d.ts +1 -2
  253. package/dist/types/modules/excel/xlsx/xform/pivot-table/cache-field-xform.d.ts +5 -15
  254. package/dist/types/modules/excel/xlsx/xform/pivot-table/cache-field.d.ts +14 -15
  255. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +13 -29
  256. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +7 -34
  257. package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +101 -27
  258. package/dist/types/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.d.ts +78 -0
  259. package/dist/types/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -2
  260. package/dist/types/modules/excel/xlsx/xform/sheet/sheet-view-xform.d.ts +1 -2
  261. package/dist/types/modules/excel/xlsx/xform/table/auto-filter-xform.d.ts +1 -2
  262. package/dist/types/modules/excel/xlsx/xform/table/custom-filter-xform.d.ts +1 -2
  263. package/dist/types/modules/excel/xlsx/xform/table/filter-column-xform.d.ts +1 -2
  264. package/dist/types/modules/excel/xlsx/xform/table/filter-xform.d.ts +1 -2
  265. package/dist/types/modules/excel/xlsx/xform/table/table-column-xform.d.ts +1 -2
  266. package/dist/types/modules/excel/xlsx/xform/table/table-style-info-xform.d.ts +1 -2
  267. package/dist/types/modules/excel/xlsx/xform/table/table-xform.d.ts +1 -2
  268. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +5 -2
  269. package/dist/types/utils/env.d.ts +0 -5
  270. package/dist/types/utils/utils.base.d.ts +8 -13
  271. package/dist/types/utils/utils.browser.d.ts +1 -1
  272. package/dist/types/utils/utils.d.ts +1 -1
  273. package/package.json +1 -1
@@ -1,23 +1,48 @@
1
1
  import { XmlStream } from "../../../utils/xml-stream.js";
2
- import { xmlEncode, xmlDecode } from "../../../../../utils/utils.browser.js";
2
+ import { colCache } from "../../../utils/col-cache.js";
3
3
  import { BaseXform } from "../base-xform.js";
4
+ import { VALID_SUBTOTALS, METRIC_DISPLAY_NAMES } from "../../../pivot-table.js";
5
+ import { RawXmlCollector, serializeAttributes } from "./raw-xml-collector.js";
6
+ /** OOXML sentinel field index meaning "data values" pseudo-field (used in pivotArea references) */
7
+ const FIELD_INDEX_DATA_VALUES = 4294967294; // 0xFFFFFFFE
8
+ /**
9
+ * Signed sentinel for the "Values" pseudo-field in colFields/rowFields.
10
+ * OOXML represents this as x="-2" (signed int32 of 0xFFFFFFFE).
11
+ */
12
+ const VALUES_FIELD_INDEX = -2;
13
+ /** Default pivot table style name */
14
+ const DEFAULT_PIVOT_STYLE = "PivotStyleLight16";
15
+ /** Valid OOXML axis values for pivot fields */
16
+ const VALID_PIVOT_AXES = new Set(["axisRow", "axisCol", "axisPage", "axisValues"]);
17
+ /** PivotFieldItem attribute keys — used to build the attrs object for rendering */
18
+ const PIVOT_FIELD_ITEM_KEYS = ["x", "t", "h", "sd", "f", "m", "c", "d"];
19
+ /** Factory for default ParserState values */
20
+ function createDefaultParserState() {
21
+ return {
22
+ currentSection: null,
23
+ inPivotArea: false,
24
+ inAutoSortScope: false
25
+ };
26
+ }
27
+ /** Known pivotField attributes that we parse individually (hoisted to module scope) */
28
+ const KNOWN_PIVOT_FIELD_KEYS = new Set([
29
+ "axis",
30
+ "dataField",
31
+ "compact",
32
+ "outline",
33
+ "showAll",
34
+ "defaultSubtotal",
35
+ "numFmtId",
36
+ "sortType",
37
+ "subtotalTop",
38
+ "insertBlankRow",
39
+ "multipleItemSelectionAllowed"
40
+ ]);
4
41
  class PivotTableXform extends BaseXform {
5
42
  constructor() {
6
43
  super();
7
44
  // Parser state consolidated into object for easier reset
8
- this.state = {
9
- inPivotFields: false,
10
- inRowFields: false,
11
- inColFields: false,
12
- inDataFields: false,
13
- inRowItems: false,
14
- inColItems: false,
15
- inLocation: false,
16
- inItems: false,
17
- inPivotTableStyleInfo: false,
18
- inChartFormats: false,
19
- inPivotArea: false
20
- };
45
+ this.state = createDefaultParserState();
21
46
  // Current parsing context
22
47
  this.currentPivotField = null;
23
48
  this.currentRowItem = null;
@@ -25,37 +50,46 @@ class PivotTableXform extends BaseXform {
25
50
  this.currentChartFormat = null;
26
51
  // Buffer for collecting pivotArea XML
27
52
  this.pivotAreaXmlBuffer = [];
28
- this.pivotAreaDepth = 0;
29
- this.map = {};
53
+ // Buffer for collecting autoSortScope XML
54
+ this.autoSortScopeXmlBuffer = [];
55
+ // Raw XML collectors (replacing manual in/depth/buffer triples)
56
+ this.extLstCollector = new RawXmlCollector("extLst");
57
+ this.formatsCollector = new RawXmlCollector("formats");
58
+ this.conditionalFormatsCollector = new RawXmlCollector("conditionalFormats");
59
+ this.filtersCollector = new RawXmlCollector("filters");
60
+ this.unknownCollector = new RawXmlCollector("");
61
+ // Accumulated unknown elements XML strings (one per element)
62
+ this.unknownElementsXmlParts = [];
30
63
  this.model = null;
31
64
  }
32
- prepare(_model) {
33
- // No preparation needed
34
- }
35
65
  get tag() {
36
66
  // http://www.datypic.com/sc/ooxml/e-ssml_pivotTableDefinition.html
37
67
  return "pivotTableDefinition";
38
68
  }
39
69
  reset() {
40
70
  this.model = null;
41
- // Reset all parser state flags using object
42
- Object.keys(this.state).forEach(key => {
43
- this.state[key] = false;
44
- });
71
+ // Reset all parser state flags
72
+ this.state = createDefaultParserState();
45
73
  // Reset current context
46
74
  this.currentPivotField = null;
47
75
  this.currentRowItem = null;
48
76
  this.currentColItem = null;
49
77
  this.currentChartFormat = null;
50
78
  this.pivotAreaXmlBuffer = [];
51
- this.pivotAreaDepth = 0;
79
+ this.autoSortScopeXmlBuffer = [];
80
+ this.extLstCollector.reset();
81
+ this.formatsCollector.reset();
82
+ this.conditionalFormatsCollector.reset();
83
+ this.filtersCollector.reset();
84
+ this.unknownCollector.reset();
85
+ this.unknownElementsXmlParts = [];
52
86
  }
53
87
  /**
54
88
  * Render pivot table XML.
55
89
  * Supports both newly created models and loaded models.
56
90
  */
57
91
  render(xmlStream, model) {
58
- const isLoaded = model.isLoaded;
92
+ const isLoaded = "isLoaded" in model && model.isLoaded;
59
93
  if (isLoaded) {
60
94
  this.renderLoaded(xmlStream, model);
61
95
  }
@@ -67,33 +101,26 @@ class PivotTableXform extends BaseXform {
67
101
  * Render newly created pivot table
68
102
  */
69
103
  renderNew(xmlStream, model) {
70
- const { rows, columns, values, cacheFields, cacheId, applyWidthHeightFormats } = model;
71
- // Build rowItems - need one <i> for each unique value in row fields, plus grand total
72
- const rowItems = buildRowItems(rows, cacheFields);
73
- // Build colItems - need one <i> for each unique value in col fields, plus grand total
74
- const colItems = buildColItems(columns, cacheFields, values.length);
75
- // Calculate pivot table dimensions
76
- const rowFieldItemCount = rows.length > 0 ? cacheFields[rows[0]]?.sharedItems?.length || 0 : 0;
77
- const colFieldItemCount = columns.length > 0 ? cacheFields[columns[0]]?.sharedItems?.length || 0 : 0;
78
- // Location: A3 is where pivot table starts
79
- // - firstHeaderRow: 1 (column headers are in first row of pivot table)
80
- // - firstDataRow: 2 (data starts in second row)
81
- // - firstDataCol: 1 (data starts in second column, after row labels)
82
- // Calculate ref based on actual data size:
83
- // - Start row: 3
84
- // - Header rows: 2 (column label row + subheader row)
85
- // - Data rows: rowFieldItemCount
86
- // - Grand total row: 1
87
- // endRow = 3 + 2 + rowFieldItemCount + 1 - 1 = 5 + rowFieldItemCount
88
- // Or simplified: startRow (3) + 1 (column labels) + rowFieldItemCount (data) + 1 (grand total)
89
- const endRow = 3 + 1 + rowFieldItemCount + 1; // = 5 + rowFieldItemCount
90
- const endCol = 1 + colFieldItemCount + 1; // start col + data cols + grand total
91
- const endColLetter = String.fromCharCode(64 + endCol);
92
- const locationRef = `A3:${endColLetter}${endRow}`;
104
+ const { rows, columns, values, pages = [], cacheFields, cacheId, tableNumber, applyWidthHeightFormats } = model;
105
+ // Multi-value with no explicit columns: the "Values" pseudo-field occupies the column axis
106
+ const isMultiValueNoCol = columns.length === 0 && values.length > 1;
107
+ // Page fields offset: each page field adds 1 row above the pivot table,
108
+ // plus 1 blank separator row when any page fields are present.
109
+ const pageCount = pages.length;
110
+ const pageOffset = pageCount > 0 ? pageCount + 1 : 0;
111
+ // Location ref: firstDataCol = number of row fields (row label columns),
112
+ // endCol = row fields + data columns.
113
+ const firstDataCol = rows.length;
114
+ const startRow = 3 + pageOffset;
115
+ const endRow = startRow + 1; // header + 1 data row placeholder
116
+ const dataColCount = isMultiValueNoCol ? values.length : 1;
117
+ const endCol = firstDataCol + dataColCount;
118
+ const endColLetter = colCache.n2l(endCol);
119
+ const locationRef = `A${startRow}:${endColLetter}${endRow}`;
93
120
  xmlStream.openXml(XmlStream.StdDocAttributes);
94
121
  xmlStream.openNode(this.tag, {
95
122
  ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
96
- name: "PivotTable2",
123
+ name: `PivotTable${tableNumber}`,
97
124
  cacheId,
98
125
  applyNumberFormats: "0",
99
126
  applyBorderFormats: "0",
@@ -112,110 +139,121 @@ class PivotTableXform extends BaseXform {
112
139
  compactData: "0",
113
140
  multipleFieldFilters: "0"
114
141
  });
115
- xmlStream.writeXml(`
116
- <location ref="${locationRef}" firstHeaderRow="1" firstDataRow="2" firstDataCol="1" />
117
- <pivotFields count="${cacheFields.length}">
118
- ${renderPivotFields(model)}
119
- </pivotFields>
120
- <rowFields count="${rows.length}">
121
- ${rows.map(rowIndex => `<field x="${rowIndex}" />`).join("\n ")}
122
- </rowFields>
123
- <rowItems count="${rowItems.count}">
124
- ${rowItems.xml}
125
- </rowItems>
126
- <colFields count="${columns.length === 0 ? 1 : columns.length}">
127
- ${columns.length === 0
128
- ? '<field x="-2" />'
129
- : columns.map(columnIndex => `<field x="${columnIndex}" />`).join("\n ")}
130
- </colFields>
131
- <colItems count="${colItems.count}">
132
- ${colItems.xml}
133
- </colItems>
134
- <dataFields count="${values.length}">
135
- ${buildDataFields(cacheFields, values, model.metric)}
136
- </dataFields>
137
- <pivotTableStyleInfo
138
- name="PivotStyleLight16"
139
- showRowHeaders="1"
140
- showColHeaders="1"
141
- showRowStripes="0"
142
- showColStripes="0"
143
- showLastColumn="1"
144
- />
145
- <extLst>
146
- <ext
147
- uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}"
148
- xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
149
- >
150
- <x14:pivotTableDefinition
151
- hideValuesRow="1"
152
- xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"
153
- />
154
- </ext>
155
- <ext
156
- uri="{747A6164-185A-40DC-8AA5-F01512510D54}"
157
- xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"
158
- >
159
- <xpdl:pivotTableDefinition16
160
- EnabledSubtotalsDefault="0"
161
- SubtotalsOnTopDefault="0"
162
- />
163
- </ext>
164
- </extLst>
165
- `);
166
- xmlStream.closeNode();
167
- }
168
- /**
169
- * Render loaded pivot table (preserving original structure)
170
- */
171
- renderLoaded(xmlStream, model) {
172
- const attrs = {
173
- ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
174
- name: model.name || "PivotTable1",
175
- cacheId: model.cacheId,
176
- applyNumberFormats: model.applyNumberFormats || "0",
177
- applyBorderFormats: model.applyBorderFormats || "0",
178
- applyFontFormats: model.applyFontFormats || "0",
179
- applyPatternFormats: model.applyPatternFormats || "0",
180
- applyAlignmentFormats: model.applyAlignmentFormats || "0",
181
- // Preserve original value when present; default to Excel's typical "0".
182
- applyWidthHeightFormats: model.applyWidthHeightFormats ?? "0",
183
- dataCaption: model.dataCaption || "Values",
184
- updatedVersion: model.updatedVersion || "8",
185
- minRefreshableVersion: model.minRefreshableVersion || "3",
186
- useAutoFormatting: model.useAutoFormatting ? "1" : "0",
187
- itemPrintTitles: model.itemPrintTitles ? "1" : "0",
188
- createdVersion: model.createdVersion || "8",
189
- indent: model.indent !== undefined ? String(model.indent) : "0",
190
- multipleFieldFilters: model.multipleFieldFilters ? "1" : "0"
142
+ // Location
143
+ const locAttrs = {
144
+ ref: locationRef,
145
+ firstHeaderRow: 1,
146
+ firstDataRow: 1,
147
+ firstDataCol
191
148
  };
192
- // Add outline attributes if present
193
- if (model.outline) {
194
- attrs.outline = "1";
149
+ if (pageCount > 0) {
150
+ locAttrs.rowPageCount = pageCount;
151
+ locAttrs.colPageCount = 1;
195
152
  }
196
- if (model.outlineData) {
197
- attrs.outlineData = "1";
153
+ xmlStream.leafNode("location", locAttrs);
154
+ // Pivot fields
155
+ renderPivotFields(xmlStream, model);
156
+ // Row fields
157
+ xmlStream.openNode("rowFields", { count: rows.length });
158
+ for (const rowIndex of rows) {
159
+ xmlStream.leafNode("field", { x: rowIndex });
198
160
  }
199
- if (model.chartFormat !== undefined) {
200
- attrs.chartFormat = String(model.chartFormat);
161
+ xmlStream.closeNode();
162
+ // Row items: minimal grand total row. refreshOnLoad="1" causes Excel
163
+ // to rebuild the full row expansion on open.
164
+ xmlStream.openNode("rowItems", { count: 1 });
165
+ xmlStream.openNode("i", { t: "grand" });
166
+ xmlStream.leafNode("x");
167
+ xmlStream.closeNode(); // i
168
+ xmlStream.closeNode(); // rowItems
169
+ // colFields: lists the field indices on the column axis.
170
+ // When columns is non-empty, list those field indices.
171
+ // When columns is empty but there are multiple values, emit the synthetic
172
+ // "Values" pseudo-field (field x="-2") so Excel knows where to position
173
+ // the data field labels on the column axis.
174
+ // When columns is empty and there is only one value, omit colFields entirely.
175
+ if (columns.length > 0) {
176
+ const fieldCount = values.length > 1 ? columns.length + 1 : columns.length;
177
+ xmlStream.openNode("colFields", { count: fieldCount });
178
+ for (const colIndex of columns) {
179
+ xmlStream.leafNode("field", { x: colIndex });
180
+ }
181
+ if (values.length > 1) {
182
+ xmlStream.leafNode("field", { x: VALUES_FIELD_INDEX });
183
+ }
184
+ xmlStream.closeNode();
185
+ }
186
+ else if (isMultiValueNoCol) {
187
+ xmlStream.openNode("colFields", { count: 1 });
188
+ xmlStream.leafNode("field", { x: VALUES_FIELD_INDEX });
189
+ xmlStream.closeNode();
190
+ }
191
+ // colItems: for multi-value no-column pivots, one <i> per value field (referencing
192
+ // its index in dataFields via <x v="N"/>) plus a grand total <i>.
193
+ // For single-value or explicit-columns pivots, a single empty <i/>.
194
+ // These are required by Excel — omitting them causes "Repaired Records" errors.
195
+ if (isMultiValueNoCol) {
196
+ xmlStream.openNode("colItems", { count: values.length + 1 });
197
+ for (let idx = 0; idx < values.length; idx++) {
198
+ xmlStream.openNode("i");
199
+ xmlStream.leafNode("x", idx === 0 ? undefined : { v: idx });
200
+ xmlStream.closeNode(); // i
201
+ }
202
+ xmlStream.openNode("i", { t: "grand" });
203
+ xmlStream.leafNode("x");
204
+ xmlStream.closeNode(); // i
205
+ xmlStream.closeNode(); // colItems
201
206
  }
202
- // Only add compact/compactData if they are true (some files don't have them)
203
- if (model.compact) {
204
- attrs.compact = "1";
207
+ else {
208
+ xmlStream.openNode("colItems", { count: 1 });
209
+ xmlStream.leafNode("i");
210
+ xmlStream.closeNode();
205
211
  }
206
- if (model.compactData) {
207
- attrs.compactData = "1";
212
+ // Page fields (between colItems and dataFields per OOXML spec)
213
+ if (pageCount > 0) {
214
+ xmlStream.openNode("pageFields", { count: pageCount });
215
+ for (const fld of pages) {
216
+ xmlStream.leafNode("pageField", { fld, hier: -1 });
217
+ }
218
+ xmlStream.closeNode();
208
219
  }
220
+ // Data fields
221
+ renderDataFields(xmlStream, cacheFields, values, model.valueMetrics);
222
+ // Pivot table style info
223
+ xmlStream.leafNode("pivotTableStyleInfo", {
224
+ name: DEFAULT_PIVOT_STYLE,
225
+ showRowHeaders: "1",
226
+ showColHeaders: "1",
227
+ showRowStripes: "0",
228
+ showColStripes: "0",
229
+ showLastColumn: "1"
230
+ });
231
+ // Extensions
232
+ xmlStream.writeXml(PivotTableXform.EXTLST_XML);
233
+ xmlStream.closeNode();
234
+ }
235
+ /**
236
+ * Render loaded pivot table (preserving original structure)
237
+ */
238
+ renderLoaded(xmlStream, model) {
239
+ const attrs = this.buildLoadedRootAttributes(model);
209
240
  xmlStream.openXml(XmlStream.StdDocAttributes);
210
241
  xmlStream.openNode(this.tag, attrs);
211
242
  // Location
212
243
  if (model.location) {
213
- xmlStream.leafNode("location", {
244
+ const locAttrs = {
214
245
  ref: model.location.ref,
215
246
  firstHeaderRow: model.location.firstHeaderRow,
216
247
  firstDataRow: model.location.firstDataRow,
217
248
  firstDataCol: model.location.firstDataCol
218
- });
249
+ };
250
+ if (model.location.rowPageCount !== undefined) {
251
+ locAttrs.rowPageCount = model.location.rowPageCount;
252
+ }
253
+ if (model.location.colPageCount !== undefined) {
254
+ locAttrs.colPageCount = model.location.colPageCount;
255
+ }
256
+ xmlStream.leafNode("location", locAttrs);
219
257
  }
220
258
  // Pivot fields
221
259
  if (model.pivotFields.length > 0) {
@@ -241,24 +279,30 @@ class PivotTableXform extends BaseXform {
241
279
  }
242
280
  xmlStream.closeNode();
243
281
  }
244
- else {
282
+ else if (model.hasRowItems) {
245
283
  xmlStream.writeXml('<rowItems count="1"><i t="grand"><x/></i></rowItems>');
246
284
  }
247
285
  // Col fields
248
286
  // Only render colFields if it was present in the original file or if there are actual column fields
249
287
  // Some pivot tables don't have colFields element at all
250
288
  if (model.hasColFields || model.colFields.length > 0) {
251
- const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
252
- xmlStream.openNode("colFields", { count: colFieldCount });
253
- if (model.colFields.length === 0) {
254
- xmlStream.leafNode("field", { x: -2 });
289
+ if (model.colFields.length === 0 && model.dataFields.length <= 1) {
290
+ // Empty colFields with no multi-value need — preserve as empty element
291
+ xmlStream.leafNode("colFields", { count: 0 });
255
292
  }
256
293
  else {
257
- for (const fieldIndex of model.colFields) {
258
- xmlStream.leafNode("field", { x: fieldIndex });
294
+ const colFieldCount = model.colFields.length === 0 ? 1 : model.colFields.length;
295
+ xmlStream.openNode("colFields", { count: colFieldCount });
296
+ if (model.colFields.length === 0) {
297
+ xmlStream.leafNode("field", { x: VALUES_FIELD_INDEX });
259
298
  }
299
+ else {
300
+ for (const fieldIndex of model.colFields) {
301
+ xmlStream.leafNode("field", { x: fieldIndex });
302
+ }
303
+ }
304
+ xmlStream.closeNode();
260
305
  }
261
- xmlStream.closeNode();
262
306
  }
263
307
  // Col items - use parsed items if available
264
308
  if (model.colItems && model.colItems.length > 0) {
@@ -268,58 +312,194 @@ class PivotTableXform extends BaseXform {
268
312
  }
269
313
  xmlStream.closeNode();
270
314
  }
271
- else {
315
+ else if (model.hasColItems) {
272
316
  xmlStream.writeXml('<colItems count="1"><i t="grand"><x/></i></colItems>');
273
317
  }
318
+ // Page fields (report filters)
319
+ if (model.pageFields && model.pageFields.length > 0) {
320
+ xmlStream.openNode("pageFields", { count: model.pageFields.length });
321
+ for (const pf of model.pageFields) {
322
+ const pfAttrs = { fld: pf.fld };
323
+ if (pf.item !== undefined) {
324
+ pfAttrs.item = pf.item;
325
+ }
326
+ if (pf.hier !== undefined) {
327
+ pfAttrs.hier = pf.hier;
328
+ }
329
+ if (pf.name !== undefined) {
330
+ pfAttrs.name = pf.name;
331
+ }
332
+ xmlStream.leafNode("pageField", pfAttrs);
333
+ }
334
+ xmlStream.closeNode();
335
+ }
274
336
  // Data fields
275
337
  if (model.dataFields.length > 0) {
276
338
  xmlStream.openNode("dataFields", { count: model.dataFields.length });
277
339
  for (const dataField of model.dataFields) {
278
340
  const dfAttrs = {
279
341
  name: dataField.name,
280
- fld: dataField.fld,
281
- baseField: dataField.baseField ?? 0,
282
- baseItem: dataField.baseItem ?? 0
342
+ fld: dataField.fld
283
343
  };
284
- if (dataField.subtotal && dataField.subtotal !== "sum") {
344
+ if (dataField.baseField !== undefined) {
345
+ dfAttrs.baseField = dataField.baseField;
346
+ }
347
+ if (dataField.baseItem !== undefined) {
348
+ dfAttrs.baseItem = dataField.baseItem;
349
+ }
350
+ if (dataField.subtotal !== undefined && dataField.subtotal !== "sum") {
285
351
  dfAttrs.subtotal = dataField.subtotal;
286
352
  }
353
+ if (dataField.numFmtId !== undefined) {
354
+ dfAttrs.numFmtId = dataField.numFmtId;
355
+ }
287
356
  xmlStream.leafNode("dataField", dfAttrs);
288
357
  }
289
358
  xmlStream.closeNode();
290
359
  }
360
+ // Formats — preserved raw XML from loaded file
361
+ if (model.formatsXml) {
362
+ xmlStream.writeXml(model.formatsXml);
363
+ }
364
+ // Conditional formats — preserved raw XML from loaded file
365
+ // OOXML order: formats → conditionalFormats → chartFormats
366
+ if (model.conditionalFormatsXml) {
367
+ xmlStream.writeXml(model.conditionalFormatsXml);
368
+ }
291
369
  // Chart formats (for pivot charts) - preserve original pivotArea XML
292
370
  if (model.chartFormats && model.chartFormats.length > 0) {
293
- xmlStream.openNode("chartFormats", { count: model.chartFormats.length });
294
- for (const cf of model.chartFormats) {
295
- xmlStream.openNode("chartFormat", {
296
- chart: cf.chart,
297
- format: cf.format,
298
- series: cf.series ? "1" : undefined
299
- });
300
- // Use preserved pivotArea XML or fallback to default
301
- if (cf.pivotAreaXml) {
302
- xmlStream.writeXml(cf.pivotAreaXml);
303
- }
304
- else {
305
- // Fallback for newly created chart formats (shouldn't happen for loaded models)
306
- xmlStream.writeXml(`<pivotArea type="data" outline="0" fieldPosition="0"><references count="1"><reference field="4294967294" count="1" selected="0"><x v="0"/></reference></references></pivotArea>`);
307
- }
308
- xmlStream.closeNode();
309
- }
310
- xmlStream.closeNode();
371
+ this.renderChartFormats(xmlStream, model.chartFormats);
311
372
  }
312
373
  // Style info
374
+ const si = model.styleInfo;
313
375
  xmlStream.leafNode("pivotTableStyleInfo", {
314
- name: model.styleName || "PivotStyleLight16",
315
- showRowHeaders: "1",
316
- showColHeaders: "1",
317
- showRowStripes: "0",
318
- showColStripes: "0",
319
- showLastColumn: "1"
376
+ name: si?.name ?? model.styleName ?? DEFAULT_PIVOT_STYLE,
377
+ showRowHeaders: si?.showRowHeaders ?? "1",
378
+ showColHeaders: si?.showColHeaders ?? "1",
379
+ showRowStripes: si?.showRowStripes ?? "0",
380
+ showColStripes: si?.showColStripes ?? "0",
381
+ showLastColumn: si?.showLastColumn ?? "1"
320
382
  });
321
- // Extensions
322
- xmlStream.writeXml(`<extLst><ext uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x14:pivotTableDefinition hideValuesRow="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"/></ext><ext uri="{747A6164-185A-40DC-8AA5-F01512510D54}" xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout"><xpdl:pivotTableDefinition16/></ext></extLst>`);
383
+ // Filters — preserved raw XML from loaded file
384
+ // <filters> appears between pivotTableStyleInfo and extLst per OOXML schema
385
+ if (model.filtersXml) {
386
+ xmlStream.writeXml(model.filtersXml);
387
+ }
388
+ // Unknown top-level elements — preserved raw XML for roundtrip
389
+ if (model.unknownElementsXml) {
390
+ xmlStream.writeXml(model.unknownElementsXml);
391
+ }
392
+ // Extensions — use preserved XML from loaded file; only inject default for new tables
393
+ const extLstXml = model.extLstXml ?? (model.isLoaded ? "" : PivotTableXform.EXTLST_XML);
394
+ if (extLstXml) {
395
+ xmlStream.writeXml(extLstXml);
396
+ }
397
+ xmlStream.closeNode();
398
+ }
399
+ /**
400
+ * Build the root `<pivotTableDefinition>` attributes for a loaded (roundtrip) model.
401
+ * Extracted from renderLoaded to keep the render method focused on element structure.
402
+ */
403
+ buildLoadedRootAttributes(model) {
404
+ const attrs = {
405
+ ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
406
+ name: model.name ?? "PivotTable1",
407
+ cacheId: String(model.cacheId),
408
+ applyNumberFormats: model.applyNumberFormats ?? "0",
409
+ applyBorderFormats: model.applyBorderFormats ?? "0",
410
+ applyFontFormats: model.applyFontFormats ?? "0",
411
+ applyPatternFormats: model.applyPatternFormats ?? "0",
412
+ applyAlignmentFormats: model.applyAlignmentFormats ?? "0",
413
+ applyWidthHeightFormats: model.applyWidthHeightFormats ?? "0",
414
+ dataCaption: model.dataCaption ?? "Values",
415
+ updatedVersion: model.updatedVersion ?? "8",
416
+ minRefreshableVersion: model.minRefreshableVersion ?? "3"
417
+ };
418
+ // Only emit these boolean-style attributes when they were present in the original.
419
+ // Absent means the OOXML default applies; emitting "0" explicitly changes semantics.
420
+ // Placed before createdVersion to match Excel's attribute ordering.
421
+ if (model.useAutoFormatting !== undefined) {
422
+ attrs.useAutoFormatting = model.useAutoFormatting;
423
+ }
424
+ if (model.itemPrintTitles !== undefined) {
425
+ attrs.itemPrintTitles = model.itemPrintTitles;
426
+ }
427
+ if (model.multipleFieldFilters !== undefined) {
428
+ attrs.multipleFieldFilters = model.multipleFieldFilters;
429
+ }
430
+ attrs.createdVersion = model.createdVersion ?? "8";
431
+ if (model.indent !== undefined) {
432
+ attrs.indent = String(model.indent);
433
+ }
434
+ // Preserve xr:uid on roundtrip
435
+ if (model.uid) {
436
+ attrs["xmlns:xr"] = "http://schemas.microsoft.com/office/spreadsheetml/2014/revision";
437
+ attrs["xr:uid"] = model.uid;
438
+ }
439
+ // Add outline attributes if present
440
+ if (model.outline) {
441
+ attrs.outline = "1";
442
+ }
443
+ if (model.outlineData) {
444
+ attrs.outlineData = "1";
445
+ }
446
+ if (model.chartFormat !== undefined) {
447
+ attrs.chartFormat = String(model.chartFormat);
448
+ }
449
+ // Grand totals and display option attributes — only emit when present in original
450
+ if (model.colGrandTotals !== undefined) {
451
+ attrs.colGrandTotals = model.colGrandTotals;
452
+ }
453
+ if (model.rowGrandTotals !== undefined) {
454
+ attrs.rowGrandTotals = model.rowGrandTotals;
455
+ }
456
+ if (model.showError !== undefined) {
457
+ attrs.showError = model.showError;
458
+ }
459
+ if (model.errorCaption !== undefined) {
460
+ attrs.errorCaption = model.errorCaption;
461
+ }
462
+ if (model.showMissing !== undefined) {
463
+ attrs.showMissing = model.showMissing;
464
+ }
465
+ if (model.missingCaption !== undefined) {
466
+ attrs.missingCaption = model.missingCaption;
467
+ }
468
+ if (model.grandTotalCaption !== undefined) {
469
+ attrs.grandTotalCaption = model.grandTotalCaption;
470
+ }
471
+ // Only write compact/compactData when false (non-default).
472
+ // OOXML spec: absent = true (default). So if the original file had compact="0",
473
+ // we must preserve it; omitting it would change semantics from false to true.
474
+ if (model.compact === false) {
475
+ attrs.compact = "0";
476
+ }
477
+ if (model.compactData === false) {
478
+ attrs.compactData = "0";
479
+ }
480
+ return attrs;
481
+ }
482
+ /**
483
+ * Render `<chartFormats>` with preserved pivotArea XML for pivot chart roundtrip.
484
+ */
485
+ renderChartFormats(xmlStream, chartFormats) {
486
+ xmlStream.openNode("chartFormats", { count: chartFormats.length });
487
+ for (const cf of chartFormats) {
488
+ xmlStream.openNode("chartFormat", {
489
+ chart: cf.chart,
490
+ format: cf.format,
491
+ series: cf.series === true ? "1" : cf.series === false ? "0" : undefined
492
+ });
493
+ // Use preserved pivotArea XML or fallback to default
494
+ if (cf.pivotAreaXml) {
495
+ xmlStream.writeXml(cf.pivotAreaXml);
496
+ }
497
+ else {
498
+ // Fallback for newly created chart formats (shouldn't happen for loaded models)
499
+ xmlStream.writeXml(`<pivotArea type="data" outline="0" fieldPosition="0"><references count="1"><reference field="${FIELD_INDEX_DATA_VALUES}" count="1" selected="0"><x v="0"/></reference></references></pivotArea>`);
500
+ }
501
+ xmlStream.closeNode();
502
+ }
323
503
  xmlStream.closeNode();
324
504
  }
325
505
  /**
@@ -327,13 +507,19 @@ class PivotTableXform extends BaseXform {
327
507
  */
328
508
  renderRowColItem(xmlStream, item) {
329
509
  const attrs = {};
330
- if (item.t) {
510
+ if (item.t !== undefined) {
331
511
  attrs.t = item.t;
332
512
  }
333
- if (item.x && item.x.length > 0) {
513
+ if (item.r !== undefined) {
514
+ attrs.r = item.r;
515
+ }
516
+ if (item.i !== undefined) {
517
+ attrs.i = item.i;
518
+ }
519
+ if (item.x.length > 0) {
334
520
  xmlStream.openNode("i", attrs);
335
521
  for (const x of item.x) {
336
- if (x.v && x.v !== 0) {
522
+ if (x.v !== 0) {
337
523
  xmlStream.leafNode("x", { v: x.v });
338
524
  }
339
525
  else {
@@ -359,36 +545,103 @@ class PivotTableXform extends BaseXform {
359
545
  if (field.dataField) {
360
546
  attrs.dataField = "1";
361
547
  }
362
- // showAll is typically always present
548
+ if (field.numFmtId !== undefined) {
549
+ attrs.numFmtId = String(field.numFmtId);
550
+ }
551
+ if (field.sortType) {
552
+ attrs.sortType = field.sortType;
553
+ }
554
+ // OOXML defaults: compact=true, outline=true, defaultSubtotal=true when absent.
555
+ // Only write the attribute when false (non-default) to preserve round-trip fidelity.
556
+ if (field.compact === false) {
557
+ attrs.compact = "0";
558
+ }
559
+ if (field.outline === false) {
560
+ attrs.outline = "0";
561
+ }
562
+ // showAll is typically always present — placed before defaultSubtotal to match Excel's ordering
363
563
  attrs.showAll = field.showAll ? "1" : "0";
364
- if (field.items && field.items.length > 0) {
564
+ if (field.defaultSubtotal === false) {
565
+ attrs.defaultSubtotal = "0";
566
+ }
567
+ if (field.subtotalTop === false) {
568
+ attrs.subtotalTop = "0";
569
+ }
570
+ if (field.insertBlankRow === true) {
571
+ attrs.insertBlankRow = "1";
572
+ }
573
+ if (field.multipleItemSelectionAllowed === true) {
574
+ attrs.multipleItemSelectionAllowed = "1";
575
+ }
576
+ // Spread extra unknown attributes for roundtrip preservation
577
+ if (field.extraAttrs) {
578
+ for (const [k, v] of Object.entries(field.extraAttrs)) {
579
+ attrs[k] = v;
580
+ }
581
+ }
582
+ const hasChildren = (field.items !== undefined && field.items.length > 0) || field.autoSortScopeXml !== undefined;
583
+ if (hasChildren) {
365
584
  xmlStream.openNode("pivotField", attrs);
366
- xmlStream.openNode("items", { count: field.items.length + 1 });
367
- for (const itemIndex of field.items) {
368
- xmlStream.leafNode("item", { x: itemIndex });
585
+ if (field.items !== undefined && field.items.length > 0) {
586
+ xmlStream.openNode("items", { count: field.items.length });
587
+ for (const item of field.items) {
588
+ const itemAttrs = {};
589
+ for (const key of PIVOT_FIELD_ITEM_KEYS) {
590
+ if (item[key] !== undefined) {
591
+ itemAttrs[key] = item[key];
592
+ }
593
+ }
594
+ xmlStream.leafNode("item", itemAttrs);
595
+ }
596
+ xmlStream.closeNode(); // items
597
+ }
598
+ if (field.autoSortScopeXml) {
599
+ xmlStream.writeXml(field.autoSortScopeXml);
369
600
  }
370
- // Grand total item
371
- xmlStream.writeXml('<item t="default"/>');
372
- xmlStream.closeNode(); // items
373
601
  xmlStream.closeNode(); // pivotField
374
602
  }
375
603
  else {
376
604
  xmlStream.leafNode("pivotField", attrs);
377
605
  }
378
606
  }
607
+ // TODO: Consider migrating to map-based child xform delegation (like table-xform.ts)
608
+ // to replace this large manual switch. Currently kept as-is because the manual SAX
609
+ // approach, while verbose, handles all OOXML edge cases correctly.
379
610
  parseOpen(node) {
380
611
  const { name, attributes } = node;
612
+ // Collect raw XML verbatim for roundtrip preservation (5 collectors)
613
+ if (this.extLstCollector.active) {
614
+ this.extLstCollector.feedOpen(name, attributes);
615
+ return true;
616
+ }
617
+ if (this.formatsCollector.active) {
618
+ this.formatsCollector.feedOpen(name, attributes);
619
+ return true;
620
+ }
621
+ if (this.conditionalFormatsCollector.active) {
622
+ this.conditionalFormatsCollector.feedOpen(name, attributes);
623
+ return true;
624
+ }
625
+ if (this.filtersCollector.active) {
626
+ this.filtersCollector.feedOpen(name, attributes);
627
+ return true;
628
+ }
629
+ if (this.unknownCollector.active) {
630
+ this.unknownCollector.feedOpen(name, attributes);
631
+ return true;
632
+ }
381
633
  switch (name) {
382
634
  case this.tag:
383
635
  // pivotTableDefinition root element
384
636
  this.reset();
385
637
  this.model = {
386
638
  name: attributes.name,
387
- cacheId: parseInt(attributes.cacheId || "0", 10),
639
+ cacheId: parseInt(attributes.cacheId ?? "0", 10),
388
640
  uid: attributes["xr:uid"],
389
641
  pivotFields: [],
390
642
  rowFields: [],
391
643
  colFields: [],
644
+ pageFields: [],
392
645
  dataFields: [],
393
646
  applyNumberFormats: attributes.applyNumberFormats,
394
647
  applyBorderFormats: attributes.applyBorderFormats,
@@ -400,15 +653,22 @@ class PivotTableXform extends BaseXform {
400
653
  updatedVersion: attributes.updatedVersion,
401
654
  minRefreshableVersion: attributes.minRefreshableVersion,
402
655
  createdVersion: attributes.createdVersion,
403
- useAutoFormatting: attributes.useAutoFormatting === "1",
404
- itemPrintTitles: attributes.itemPrintTitles === "1",
405
- indent: attributes.indent ? parseInt(attributes.indent, 10) : 0,
406
- compact: attributes.compact === "1",
407
- compactData: attributes.compactData === "1",
408
- multipleFieldFilters: attributes.multipleFieldFilters === "1",
656
+ useAutoFormatting: attributes.useAutoFormatting,
657
+ itemPrintTitles: attributes.itemPrintTitles,
658
+ indent: attributes.indent !== undefined ? parseInt(attributes.indent, 10) : undefined,
659
+ compact: attributes.compact !== "0",
660
+ compactData: attributes.compactData !== "0",
661
+ multipleFieldFilters: attributes.multipleFieldFilters,
409
662
  outline: attributes.outline === "1",
410
663
  outlineData: attributes.outlineData === "1",
411
- chartFormat: attributes.chartFormat ? parseInt(attributes.chartFormat, 10) : undefined,
664
+ chartFormat: attributes.chartFormat !== undefined ? parseInt(attributes.chartFormat, 10) : undefined,
665
+ colGrandTotals: attributes.colGrandTotals,
666
+ rowGrandTotals: attributes.rowGrandTotals,
667
+ showError: attributes.showError,
668
+ errorCaption: attributes.errorCaption,
669
+ showMissing: attributes.showMissing,
670
+ missingCaption: attributes.missingCaption,
671
+ grandTotalCaption: attributes.grandTotalCaption,
412
672
  rowItems: [],
413
673
  colItems: [],
414
674
  chartFormats: [],
@@ -419,171 +679,346 @@ class PivotTableXform extends BaseXform {
419
679
  if (this.model) {
420
680
  this.model.location = {
421
681
  ref: attributes.ref,
422
- firstHeaderRow: attributes.firstHeaderRow
682
+ firstHeaderRow: attributes.firstHeaderRow !== undefined
423
683
  ? parseInt(attributes.firstHeaderRow, 10)
424
684
  : undefined,
425
- firstDataRow: attributes.firstDataRow
685
+ firstDataRow: attributes.firstDataRow !== undefined
426
686
  ? parseInt(attributes.firstDataRow, 10)
427
687
  : undefined,
428
- firstDataCol: attributes.firstDataCol
688
+ firstDataCol: attributes.firstDataCol !== undefined
429
689
  ? parseInt(attributes.firstDataCol, 10)
690
+ : undefined,
691
+ rowPageCount: attributes.rowPageCount !== undefined
692
+ ? parseInt(attributes.rowPageCount, 10)
693
+ : undefined,
694
+ colPageCount: attributes.colPageCount !== undefined
695
+ ? parseInt(attributes.colPageCount, 10)
430
696
  : undefined
431
697
  };
432
698
  }
433
699
  break;
434
700
  case "pivotFields":
435
- this.state.inPivotFields = true;
701
+ this.state.currentSection = "pivotFields";
436
702
  break;
437
703
  case "pivotField":
438
- if (this.state.inPivotFields) {
704
+ if (this.state.currentSection === "pivotFields") {
705
+ // Collect unknown attributes into extraAttrs bag for roundtrip preservation
706
+ const extraAttrs = {};
707
+ for (const [k, v] of Object.entries(attributes)) {
708
+ if (!KNOWN_PIVOT_FIELD_KEYS.has(k)) {
709
+ extraAttrs[k] = String(v);
710
+ }
711
+ }
439
712
  this.currentPivotField = {
440
- axis: attributes.axis,
713
+ axis: VALID_PIVOT_AXES.has(attributes.axis)
714
+ ? attributes.axis
715
+ : undefined,
441
716
  dataField: attributes.dataField === "1",
442
717
  items: [],
443
- compact: attributes.compact === "1",
444
- outline: attributes.outline === "1",
445
- showAll: attributes.showAll === "1",
446
- defaultSubtotal: attributes.defaultSubtotal === "1"
718
+ compact: attributes.compact !== "0",
719
+ outline: attributes.outline !== "0",
720
+ showAll: attributes.showAll !== "0",
721
+ defaultSubtotal: attributes.defaultSubtotal !== "0",
722
+ numFmtId: attributes.numFmtId !== undefined ? parseInt(attributes.numFmtId, 10) : undefined,
723
+ sortType: attributes.sortType,
724
+ subtotalTop: attributes.subtotalTop !== undefined ? attributes.subtotalTop === "1" : undefined,
725
+ insertBlankRow: attributes.insertBlankRow === "1" ? true : undefined,
726
+ multipleItemSelectionAllowed: attributes.multipleItemSelectionAllowed === "1" ? true : undefined,
727
+ extraAttrs: Object.keys(extraAttrs).length > 0 ? extraAttrs : undefined
447
728
  };
448
729
  }
449
730
  break;
450
731
  case "items":
732
+ // No state needed — item parsing is guarded by currentPivotField
733
+ break;
734
+ case "item":
451
735
  if (this.currentPivotField) {
452
- this.state.inItems = true;
736
+ // R8-O1: Parse item attributes using a loop over PIVOT_FIELD_ITEM_KEYS
737
+ const item = {};
738
+ if (attributes.x !== undefined) {
739
+ item.x = parseInt(attributes.x, 10);
740
+ }
741
+ for (const key of PIVOT_FIELD_ITEM_KEYS) {
742
+ if (key !== "x" && attributes[key] !== undefined) {
743
+ item[key] = attributes[key];
744
+ }
745
+ }
746
+ // items is always initialized as [] when currentPivotField is created (see "pivotField" case)
747
+ this.currentPivotField.items.push(item);
453
748
  }
454
749
  break;
455
- case "item":
456
- if (this.state.inItems && this.currentPivotField && attributes.x !== undefined) {
457
- this.currentPivotField.items.push(parseInt(attributes.x, 10));
750
+ case "autoSortScope":
751
+ // Start collecting autoSortScope XML for the current pivotField
752
+ if (this.currentPivotField) {
753
+ this.state.inAutoSortScope = true;
754
+ this.autoSortScopeXmlBuffer = ["<autoSortScope>"];
458
755
  }
459
756
  break;
460
757
  case "rowFields":
461
- this.state.inRowFields = true;
758
+ this.state.currentSection = "rowFields";
462
759
  break;
463
760
  case "colFields":
464
- this.state.inColFields = true;
761
+ this.state.currentSection = "colFields";
465
762
  // Track that colFields element was present in original file
466
763
  if (this.model) {
467
764
  this.model.hasColFields = true;
468
765
  }
469
766
  break;
470
767
  case "dataFields":
471
- this.state.inDataFields = true;
768
+ this.state.currentSection = "dataFields";
769
+ break;
770
+ case "pageFields":
771
+ this.state.currentSection = "pageFields";
772
+ break;
773
+ case "pageField":
774
+ if (this.state.currentSection === "pageFields" && this.model) {
775
+ this.model.pageFields.push({
776
+ fld: parseInt(attributes.fld ?? "0", 10),
777
+ item: attributes.item !== undefined ? parseInt(attributes.item, 10) : undefined,
778
+ hier: attributes.hier !== undefined ? parseInt(attributes.hier, 10) : undefined,
779
+ name: attributes.name
780
+ });
781
+ }
472
782
  break;
473
783
  case "rowItems":
474
- this.state.inRowItems = true;
784
+ this.state.currentSection = "rowItems";
785
+ if (this.model) {
786
+ this.model.hasRowItems = true;
787
+ }
475
788
  break;
476
789
  case "colItems":
477
- this.state.inColItems = true;
790
+ this.state.currentSection = "colItems";
791
+ if (this.model) {
792
+ this.model.hasColItems = true;
793
+ }
478
794
  break;
479
795
  case "i":
480
796
  // Handle row/col item element
481
- if (this.state.inRowItems && this.model) {
482
- this.currentRowItem = { t: attributes.t, x: [] };
483
- }
484
- else if (this.state.inColItems && this.model) {
485
- this.currentColItem = { t: attributes.t, x: [] };
797
+ if (this.model) {
798
+ const rowColItem = this.state.currentSection === "rowItems" || this.state.currentSection === "colItems"
799
+ ? parseRowColItem(attributes)
800
+ : null;
801
+ if (this.state.currentSection === "rowItems") {
802
+ this.currentRowItem = rowColItem;
803
+ }
804
+ else if (this.state.currentSection === "colItems") {
805
+ this.currentColItem = rowColItem;
806
+ }
486
807
  }
487
808
  break;
488
809
  case "x":
489
810
  // Handle x element inside row/col items or pivotArea
490
811
  if (this.state.inPivotArea) {
491
- // Collect x element for pivotArea XML
492
- const xAttrs = Object.entries(attributes)
493
- .map(([k, v]) => `${k}="${v}"`)
494
- .join(" ");
495
- this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
812
+ // Collect x element for pivotArea XML (re-encode attribute values for XML safety)
813
+ const xAttrs = serializeAttributes(attributes);
814
+ if (this.state.inAutoSortScope) {
815
+ this.autoSortScopeXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
816
+ }
817
+ else {
818
+ this.pivotAreaXmlBuffer.push(xAttrs ? `<x ${xAttrs}/>` : "<x/>");
819
+ }
496
820
  }
497
821
  else if (this.currentRowItem) {
498
- this.currentRowItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
822
+ this.currentRowItem.x.push({ v: parseInt(attributes.v ?? "0", 10) });
499
823
  }
500
824
  else if (this.currentColItem) {
501
- this.currentColItem.x.push({ v: attributes.v ? parseInt(attributes.v, 10) : 0 });
825
+ this.currentColItem.x.push({ v: parseInt(attributes.v ?? "0", 10) });
502
826
  }
503
827
  break;
504
828
  case "chartFormats":
505
- this.state.inChartFormats = true;
829
+ this.state.currentSection = "chartFormats";
506
830
  break;
507
831
  case "chartFormat":
508
- if (this.state.inChartFormats && this.model) {
832
+ if (this.state.currentSection === "chartFormats" && this.model) {
509
833
  this.currentChartFormat = {
510
- chart: attributes.chart ? parseInt(attributes.chart, 10) : 0,
511
- format: attributes.format ? parseInt(attributes.format, 10) : 0,
512
- series: attributes.series === "1"
834
+ chart: parseInt(attributes.chart ?? "0", 10),
835
+ format: parseInt(attributes.format ?? "0", 10),
836
+ series: attributes.series !== undefined ? attributes.series === "1" : undefined
513
837
  };
514
838
  }
515
839
  break;
516
840
  case "pivotArea":
517
- // Start collecting pivotArea XML for chartFormat
841
+ // Start collecting pivotArea XML for chartFormat or autoSortScope
518
842
  if (this.currentChartFormat) {
519
843
  this.state.inPivotArea = true;
520
- const attrsStr = Object.entries(attributes)
521
- .map(([k, v]) => `${k}="${v}"`)
522
- .join(" ");
844
+ const attrsStr = serializeAttributes(attributes);
523
845
  this.pivotAreaXmlBuffer = [attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>"];
524
846
  }
847
+ else if (this.state.inAutoSortScope) {
848
+ this.state.inPivotArea = true;
849
+ const attrsStr = serializeAttributes(attributes);
850
+ this.autoSortScopeXmlBuffer.push(attrsStr ? `<pivotArea ${attrsStr}>` : "<pivotArea>");
851
+ }
525
852
  break;
526
853
  case "references":
527
854
  case "reference":
528
855
  // Collect nested elements in pivotArea
529
856
  if (this.state.inPivotArea) {
530
- this.pivotAreaDepth++;
531
- const attrsStr = Object.entries(attributes)
532
- .map(([k, v]) => `${k}="${v}"`)
533
- .join(" ");
534
- this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
857
+ const attrsStr = serializeAttributes(attributes);
858
+ if (this.state.inAutoSortScope) {
859
+ this.autoSortScopeXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
860
+ }
861
+ else {
862
+ this.pivotAreaXmlBuffer.push(`<${name}${attrsStr ? " " + attrsStr : ""}>`);
863
+ }
535
864
  }
536
865
  break;
537
866
  case "field":
538
867
  // Handle field element (used in rowFields, colFields)
539
868
  if (this.model) {
540
- const fieldIndex = parseInt(attributes.x || "0", 10);
541
- if (this.state.inRowFields) {
869
+ const fieldIndex = parseInt(attributes.x ?? "0", 10);
870
+ if (this.state.currentSection === "rowFields") {
542
871
  this.model.rowFields.push(fieldIndex);
543
872
  }
544
- else if (this.state.inColFields) {
873
+ else if (this.state.currentSection === "colFields") {
545
874
  this.model.colFields.push(fieldIndex);
546
875
  }
547
876
  }
548
877
  break;
549
878
  case "dataField":
550
- if (this.state.inDataFields && this.model) {
879
+ if (this.state.currentSection === "dataFields" && this.model) {
551
880
  this.model.dataFields.push({
552
- name: xmlDecode(attributes.name || ""),
553
- fld: parseInt(attributes.fld || "0", 10),
554
- baseField: attributes.baseField ? parseInt(attributes.baseField, 10) : 0,
555
- baseItem: attributes.baseItem ? parseInt(attributes.baseItem, 10) : 0,
556
- subtotal: attributes.subtotal
881
+ name: attributes.name ?? "",
882
+ fld: parseInt(attributes.fld ?? "0", 10),
883
+ baseField: attributes.baseField !== undefined ? parseInt(attributes.baseField, 10) : undefined,
884
+ baseItem: attributes.baseItem !== undefined ? parseInt(attributes.baseItem, 10) : undefined,
885
+ subtotal: VALID_SUBTOTALS.has(attributes.subtotal)
886
+ ? attributes.subtotal
887
+ : undefined,
888
+ numFmtId: attributes.numFmtId !== undefined ? parseInt(attributes.numFmtId, 10) : undefined
557
889
  });
558
890
  }
559
891
  break;
560
892
  case "pivotTableStyleInfo":
561
893
  if (this.model) {
562
894
  this.model.styleName = attributes.name;
895
+ this.model.styleInfo = {
896
+ name: attributes.name,
897
+ showRowHeaders: attributes.showRowHeaders,
898
+ showColHeaders: attributes.showColHeaders,
899
+ showRowStripes: attributes.showRowStripes,
900
+ showColStripes: attributes.showColStripes,
901
+ showLastColumn: attributes.showLastColumn
902
+ };
903
+ }
904
+ break;
905
+ case "extLst":
906
+ // Start collecting extLst XML for roundtrip preservation
907
+ if (this.model) {
908
+ this.extLstCollector.start(attributes);
909
+ }
910
+ break;
911
+ case "formats":
912
+ // Start collecting formats XML for roundtrip preservation
913
+ if (this.model) {
914
+ this.formatsCollector.start(attributes);
915
+ }
916
+ break;
917
+ case "conditionalFormats":
918
+ // Start collecting conditionalFormats XML for roundtrip preservation
919
+ if (this.model) {
920
+ this.conditionalFormatsCollector.start(attributes);
921
+ }
922
+ break;
923
+ case "filters":
924
+ // Start collecting filters XML for roundtrip preservation
925
+ // <filters> appears between pivotTableStyleInfo and extLst per OOXML schema
926
+ if (this.model) {
927
+ this.filtersCollector.start(attributes);
928
+ }
929
+ break;
930
+ default:
931
+ // Catch-all: collect any unhandled top-level child element as raw XML.
932
+ // This preserves elements like pivotHierarchies, rowHierarchiesUsage,
933
+ // colHierarchiesUsage, etc. that we don't individually model.
934
+ // R8-B1: Only activate at the top level of pivotTableDefinition — NOT inside
935
+ // known sections (pivotFields, rowFields, etc.) or pivotArea/autoSortScope,
936
+ // otherwise the collector would steal subsequent tags from normal parsing.
937
+ if (this.model &&
938
+ this.state.currentSection === null &&
939
+ !this.state.inPivotArea &&
940
+ !this.state.inAutoSortScope) {
941
+ this.unknownCollector.startAs(name, attributes);
563
942
  }
564
943
  break;
565
944
  }
566
945
  return true;
567
946
  }
568
- parseText(_text) {
569
- // No text content in pivot table elements
947
+ parseText(text) {
948
+ // Forward text nodes to whichever raw-XML collector is active (B3 fix)
949
+ if (this.extLstCollector.active) {
950
+ this.extLstCollector.feedText(text);
951
+ }
952
+ else if (this.formatsCollector.active) {
953
+ this.formatsCollector.feedText(text);
954
+ }
955
+ else if (this.conditionalFormatsCollector.active) {
956
+ this.conditionalFormatsCollector.feedText(text);
957
+ }
958
+ else if (this.filtersCollector.active) {
959
+ this.filtersCollector.feedText(text);
960
+ }
961
+ else if (this.unknownCollector.active) {
962
+ this.unknownCollector.feedText(text);
963
+ }
964
+ }
965
+ /** Feed a close-tag to a collector; if it completes, store the result on the model. */
966
+ tryCloseCollector(collector, name, modelKey) {
967
+ if (collector.feedClose(name)) {
968
+ if (this.model) {
969
+ this.model[modelKey] = collector.result;
970
+ }
971
+ collector.reset();
972
+ }
570
973
  }
571
974
  parseClose(name) {
975
+ // Handle raw-XML collectors — close tags
976
+ if (this.extLstCollector.active) {
977
+ this.tryCloseCollector(this.extLstCollector, name, "extLstXml");
978
+ return true;
979
+ }
980
+ if (this.formatsCollector.active) {
981
+ this.tryCloseCollector(this.formatsCollector, name, "formatsXml");
982
+ return true;
983
+ }
984
+ if (this.conditionalFormatsCollector.active) {
985
+ this.tryCloseCollector(this.conditionalFormatsCollector, name, "conditionalFormatsXml");
986
+ return true;
987
+ }
988
+ if (this.filtersCollector.active) {
989
+ this.tryCloseCollector(this.filtersCollector, name, "filtersXml");
990
+ return true;
991
+ }
992
+ if (this.unknownCollector.active) {
993
+ if (this.unknownCollector.feedClose(name)) {
994
+ this.unknownElementsXmlParts.push(this.unknownCollector.result);
995
+ this.unknownCollector.reset();
996
+ }
997
+ return true;
998
+ }
572
999
  // Handle pivotArea nested elements - close tags
573
1000
  if (this.state.inPivotArea) {
574
1001
  if (name === "pivotArea") {
575
- this.pivotAreaXmlBuffer.push("</pivotArea>");
576
- if (this.currentChartFormat) {
577
- this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
1002
+ if (this.state.inAutoSortScope) {
1003
+ this.autoSortScopeXmlBuffer.push("</pivotArea>");
1004
+ }
1005
+ else {
1006
+ this.pivotAreaXmlBuffer.push("</pivotArea>");
1007
+ if (this.currentChartFormat) {
1008
+ this.currentChartFormat.pivotAreaXml = this.pivotAreaXmlBuffer.join("");
1009
+ }
1010
+ this.pivotAreaXmlBuffer = [];
578
1011
  }
579
1012
  this.state.inPivotArea = false;
580
- this.pivotAreaXmlBuffer = [];
581
- this.pivotAreaDepth = 0;
582
1013
  return true;
583
1014
  }
584
1015
  else if (name === "references" || name === "reference") {
585
- this.pivotAreaXmlBuffer.push(`</${name}>`);
586
- this.pivotAreaDepth--;
1016
+ if (this.state.inAutoSortScope) {
1017
+ this.autoSortScopeXmlBuffer.push(`</${name}>`);
1018
+ }
1019
+ else {
1020
+ this.pivotAreaXmlBuffer.push(`</${name}>`);
1021
+ }
587
1022
  return true;
588
1023
  }
589
1024
  // x elements are self-closing, no need to handle close
@@ -591,10 +1026,20 @@ class PivotTableXform extends BaseXform {
591
1026
  }
592
1027
  switch (name) {
593
1028
  case this.tag:
594
- // End of pivotTableDefinition
1029
+ // End of pivotTableDefinition — store any collected unknown elements
1030
+ if (this.model && this.unknownElementsXmlParts.length > 0) {
1031
+ this.model.unknownElementsXml = this.unknownElementsXmlParts.join("");
1032
+ }
595
1033
  return false;
596
1034
  case "pivotFields":
597
- this.state.inPivotFields = false;
1035
+ case "rowFields":
1036
+ case "colFields":
1037
+ case "dataFields":
1038
+ case "pageFields":
1039
+ case "rowItems":
1040
+ case "colItems":
1041
+ case "chartFormats":
1042
+ this.state.currentSection = null;
598
1043
  break;
599
1044
  case "pivotField":
600
1045
  if (this.currentPivotField && this.model) {
@@ -603,199 +1048,139 @@ class PivotTableXform extends BaseXform {
603
1048
  }
604
1049
  break;
605
1050
  case "items":
606
- this.state.inItems = false;
607
- break;
608
- case "rowFields":
609
- this.state.inRowFields = false;
610
- break;
611
- case "colFields":
612
- this.state.inColFields = false;
613
- break;
614
- case "dataFields":
615
- this.state.inDataFields = false;
616
- break;
617
- case "rowItems":
618
- this.state.inRowItems = false;
1051
+ // No close handling needed — item parsing guarded by currentPivotField
619
1052
  break;
620
- case "colItems":
621
- this.state.inColItems = false;
1053
+ case "autoSortScope":
1054
+ // Finish collecting autoSortScope XML
1055
+ if (this.state.inAutoSortScope && this.currentPivotField) {
1056
+ this.autoSortScopeXmlBuffer.push("</autoSortScope>");
1057
+ this.currentPivotField.autoSortScopeXml = this.autoSortScopeXmlBuffer.join("");
1058
+ this.autoSortScopeXmlBuffer = [];
1059
+ this.state.inAutoSortScope = false;
1060
+ }
622
1061
  break;
623
1062
  case "i":
624
1063
  // Finish row/col item
625
1064
  if (this.currentRowItem && this.model) {
626
- this.model.rowItems.push(this.currentRowItem);
1065
+ this.model.rowItems?.push(this.currentRowItem);
627
1066
  this.currentRowItem = null;
628
1067
  }
629
1068
  else if (this.currentColItem && this.model) {
630
- this.model.colItems.push(this.currentColItem);
1069
+ this.model.colItems?.push(this.currentColItem);
631
1070
  this.currentColItem = null;
632
1071
  }
633
1072
  break;
634
- case "chartFormats":
635
- this.state.inChartFormats = false;
636
- break;
637
1073
  case "chartFormat":
638
1074
  if (this.currentChartFormat && this.model) {
639
- this.model.chartFormats.push(this.currentChartFormat);
1075
+ this.model.chartFormats?.push(this.currentChartFormat);
640
1076
  this.currentChartFormat = null;
641
1077
  }
642
1078
  break;
643
1079
  }
644
1080
  return true;
645
1081
  }
646
- reconcile(_model, _options) {
647
- // No reconciliation needed
648
- }
649
1082
  }
650
1083
  PivotTableXform.PIVOT_TABLE_ATTRIBUTES = {
651
1084
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
652
1085
  };
1086
+ PivotTableXform.EXTLST_XML = "<extLst>" +
1087
+ '<ext uri="{962EF5D1-5CA2-4c93-8EF4-DBF5C05439D2}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">' +
1088
+ '<x14:pivotTableDefinition hideValuesRow="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"/>' +
1089
+ "</ext>" +
1090
+ '<ext uri="{747A6164-185A-40DC-8AA5-F01512510D54}" xmlns:xpdl="http://schemas.microsoft.com/office/spreadsheetml/2016/pivotdefaultlayout">' +
1091
+ '<xpdl:pivotTableDefinition16 EnabledSubtotalsDefault="0" SubtotalsOnTopDefault="0"/>' +
1092
+ "</ext>" +
1093
+ "</extLst>";
653
1094
  // Helpers
654
- /**
655
- * Build rowItems XML - one item for each unique value in row fields, plus grand total.
656
- * Each <i> represents a row in the pivot table.
657
- * - Regular items: <i><x/></i> for index 0, <i><x v="index"/></i> for index > 0
658
- * - Grand total: <i t="grand"><x/></i>
659
- * Note: When v=0, the v attribute should be omitted (Excel convention)
660
- */
661
- function buildRowItems(rows, cacheFields) {
662
- if (rows.length === 0) {
663
- // No row fields - just grand total
664
- return { count: 1, xml: '<i t="grand"><x /></i>' };
665
- }
666
- // Get unique values count from the first row field
667
- const rowFieldIndex = rows[0];
668
- const sharedItems = cacheFields[rowFieldIndex]?.sharedItems || [];
669
- const itemCount = sharedItems.length;
670
- // Build items: one for each unique value + grand total
671
- const items = [];
672
- // Regular items - reference each unique value by index
673
- // Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
674
- for (let i = 0; i < itemCount; i++) {
675
- if (i === 0) {
676
- items.push("<i><x /></i>");
677
- }
678
- else {
679
- items.push(`<i><x v="${i}" /></i>`);
680
- }
681
- }
682
- // Grand total row
683
- items.push('<i t="grand"><x /></i>');
1095
+ /** Parse attributes of a row/col `<i>` element into a RowColItem. */
1096
+ function parseRowColItem(attributes) {
684
1097
  return {
685
- count: items.length,
686
- xml: items.join("\n ")
1098
+ t: attributes.t,
1099
+ r: attributes.r !== undefined ? parseInt(attributes.r, 10) : undefined,
1100
+ i: attributes.i !== undefined ? parseInt(attributes.i, 10) : undefined,
1101
+ x: []
687
1102
  };
688
1103
  }
689
1104
  /**
690
- * Build colItems XML - one item for each unique value in column fields, plus grand total.
691
- * When there are multiple data fields (values), each column value may have sub-columns.
692
- * Note: When v=0, the v attribute should be omitted (Excel convention)
1105
+ * Render dataField XML elements for all values in the pivot table.
1106
+ * Each value field gets its own metric from the `valueMetrics` array.
693
1107
  */
694
- function buildColItems(columns, cacheFields, valueCount) {
695
- if (columns.length === 0) {
696
- // No column fields - columns are based on data fields (values)
697
- if (valueCount > 1) {
698
- // Multiple values: one column per value + grand total
699
- const items = [];
700
- for (let i = 0; i < valueCount; i++) {
701
- if (i === 0) {
702
- items.push("<i><x /></i>");
703
- }
704
- else {
705
- items.push(`<i><x v="${i}" /></i>`);
706
- }
707
- }
708
- items.push('<i t="grand"><x /></i>');
709
- return { count: items.length, xml: items.join("\n ") };
1108
+ function renderDataFields(xmlStream, cacheFields, values, valueMetrics) {
1109
+ xmlStream.openNode("dataFields", { count: values.length });
1110
+ for (let i = 0; i < values.length; i++) {
1111
+ const valueIndex = values[i];
1112
+ const metric = valueMetrics[i] ?? "sum";
1113
+ const metricName = METRIC_DISPLAY_NAMES[metric];
1114
+ const field = cacheFields[valueIndex];
1115
+ if (!field) {
1116
+ throw new Error(`Value field index ${valueIndex} is out of bounds (cacheFields has ${cacheFields.length} entries)`);
710
1117
  }
711
- // Single value: just grand total
712
- return { count: 1, xml: '<i t="grand"><x /></i>' };
713
- }
714
- // Get unique values count from the first column field
715
- const colFieldIndex = columns[0];
716
- const sharedItems = cacheFields[colFieldIndex]?.sharedItems || [];
717
- const itemCount = sharedItems.length;
718
- // Build items: one for each unique value + grand total
719
- const items = [];
720
- // Regular items - reference each unique value by index
721
- // Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
722
- for (let i = 0; i < itemCount; i++) {
723
- if (i === 0) {
724
- items.push("<i><x /></i>");
725
- }
726
- else {
727
- items.push(`<i><x v="${i}" /></i>`);
1118
+ const attrs = {
1119
+ name: `${metricName} of ${field.name}`,
1120
+ fld: valueIndex,
1121
+ baseField: 0,
1122
+ baseItem: 0
1123
+ };
1124
+ // OOXML default is "sum", so omit subtotal attribute for sum
1125
+ if (metric !== "sum") {
1126
+ attrs.subtotal = metric;
728
1127
  }
1128
+ xmlStream.leafNode("dataField", attrs);
729
1129
  }
730
- // Grand total column
731
- items.push('<i t="grand"><x /></i>');
732
- return {
733
- count: items.length,
734
- xml: items.join("\n ")
735
- };
1130
+ xmlStream.closeNode();
736
1131
  }
737
- /**
738
- * Build dataField XML elements for all values in the pivot table.
739
- * Supports multiple values when columns is empty.
740
- */
741
- function buildDataFields(cacheFields, values, metric) {
742
- const metricName = metric === "count" ? "Count" : "Sum";
743
- // For 'count' metric, Excel requires subtotal="count" attribute
744
- const subtotalAttr = metric === "count" ? ' subtotal="count"' : "";
745
- return values
746
- .map(valueIndex => `<dataField
747
- name="${metricName} of ${xmlEncode(cacheFields[valueIndex].name)}"
748
- fld="${valueIndex}"
749
- baseField="0"
750
- baseItem="0"${subtotalAttr}
751
- />`)
752
- .join("");
753
- }
754
- function renderPivotFields(pivotTable) {
1132
+ function renderPivotFields(xmlStream, pivotTable) {
755
1133
  // Pre-compute field type lookup for O(1) access
756
1134
  const rowSet = new Set(pivotTable.rows);
757
1135
  const colSet = new Set(pivotTable.columns);
758
1136
  const valueSet = new Set(pivotTable.values);
759
- return pivotTable.cacheFields
760
- .map((cacheField, fieldIndex) => {
1137
+ const pageSet = new Set(pivotTable.pages ?? []);
1138
+ xmlStream.openNode("pivotFields", { count: pivotTable.cacheFields.length });
1139
+ for (let fieldIndex = 0; fieldIndex < pivotTable.cacheFields.length; fieldIndex++) {
1140
+ const cacheField = pivotTable.cacheFields[fieldIndex];
761
1141
  const isRow = rowSet.has(fieldIndex);
762
1142
  const isCol = colSet.has(fieldIndex);
763
1143
  const isValue = valueSet.has(fieldIndex);
764
- return renderPivotField(isRow, isCol, isValue, cacheField.sharedItems);
765
- })
766
- .join("");
1144
+ const isPage = pageSet.has(fieldIndex);
1145
+ renderPivotField(xmlStream, isRow, isCol, isValue, isPage, cacheField.sharedItems);
1146
+ }
1147
+ xmlStream.closeNode();
767
1148
  }
768
- function renderPivotField(isRow, isCol, isValue, sharedItems) {
1149
+ function renderPivotField(xmlStream, isRow, isCol, isValue, isPage, sharedItems) {
769
1150
  // A field can be both a row/column field AND a value field
770
1151
  // In this case, it needs both axis attribute AND dataField="1"
771
- if (isRow || isCol) {
772
- const axis = isRow ? "axisRow" : "axisCol";
773
- // Row and column fields should NOT have defaultSubtotal="0"
774
- let axisAttributes = 'compact="0" outline="0" showAll="0"';
775
- // If also a value field, add dataField="1"
1152
+ if (isRow || isCol || isPage) {
1153
+ if (!sharedItems) {
1154
+ throw new Error("sharedItems is required for axis field (row/column/page)");
1155
+ }
1156
+ const axis = isRow ? "axisRow" : isCol ? "axisCol" : "axisPage";
1157
+ const attrs = { axis };
776
1158
  if (isValue) {
777
- axisAttributes = `dataField="1" ${axisAttributes}`;
1159
+ attrs.dataField = "1";
778
1160
  }
1161
+ attrs.compact = "0";
1162
+ attrs.outline = "0";
1163
+ attrs.showAll = "0";
1164
+ xmlStream.openNode("pivotField", attrs);
779
1165
  // items = one for each shared item + one default item
780
- const itemsXml = [
781
- ...sharedItems.map((_item, index) => `<item x="${index}" />`),
782
- '<item t="default" />' // Required default item for subtotals/grand totals
783
- ].join("\n ");
784
- return `
785
- <pivotField axis="${axis}" ${axisAttributes}>
786
- <items count="${sharedItems.length + 1}">
787
- ${itemsXml}
788
- </items>
789
- </pivotField>
790
- `;
1166
+ xmlStream.openNode("items", { count: sharedItems.length + 1 });
1167
+ for (let i = 0; i < sharedItems.length; i++) {
1168
+ xmlStream.leafNode("item", { x: i });
1169
+ }
1170
+ xmlStream.leafNode("item", { t: "default" }); // Required default item for subtotals/grand totals
1171
+ xmlStream.closeNode(); // items
1172
+ xmlStream.closeNode(); // pivotField
1173
+ return;
791
1174
  }
792
1175
  // Value fields and non-axis fields should have defaultSubtotal="0"
793
- const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
794
- return `
795
- <pivotField
796
- ${isValue ? 'dataField="1"' : ""}
797
- ${defaultAttributes}
798
- />
799
- `;
1176
+ const attrs = {};
1177
+ if (isValue) {
1178
+ attrs.dataField = "1";
1179
+ }
1180
+ attrs.compact = "0";
1181
+ attrs.outline = "0";
1182
+ attrs.showAll = "0";
1183
+ attrs.defaultSubtotal = "0";
1184
+ xmlStream.leafNode("pivotField", attrs);
800
1185
  }
801
1186
  export { PivotTableXform };