@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.
- package/dist/browser/index.browser.d.ts +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/modules/archive/unzip/stream.base.js +19 -19
- package/dist/browser/modules/archive/unzip/stream.browser.js +3 -3
- package/dist/browser/modules/csv/csv-core.js +6 -3
- package/dist/browser/modules/csv/csv.browser.js +2 -2
- package/dist/browser/modules/csv/csv.js +1 -1
- package/dist/browser/modules/excel/anchor.js +4 -4
- package/dist/browser/modules/excel/cell.js +5 -5
- package/dist/browser/modules/excel/column.js +4 -4
- package/dist/browser/modules/excel/defined-names.js +1 -1
- package/dist/browser/modules/excel/form-control.js +1 -1
- package/dist/browser/modules/excel/pivot-table.d.ts +168 -17
- package/dist/browser/modules/excel/pivot-table.js +278 -70
- package/dist/browser/modules/excel/row.js +4 -4
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +4 -4
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +4 -4
- package/dist/browser/modules/excel/stream/worksheet-reader.js +1 -1
- package/dist/browser/modules/excel/stream/worksheet-writer.js +4 -4
- package/dist/browser/modules/excel/table.js +2 -2
- package/dist/browser/modules/excel/types.d.ts +0 -4
- package/dist/browser/modules/excel/utils/cell-format.js +3 -3
- package/dist/browser/modules/excel/utils/shared-formula.js +1 -1
- package/dist/browser/modules/excel/utils/stream-buf.js +2 -2
- package/dist/browser/modules/excel/utils/string-buf.js +1 -1
- package/dist/browser/modules/excel/workbook.d.ts +0 -2
- package/dist/browser/modules/excel/workbook.js +4 -5
- package/dist/browser/modules/excel/worksheet.js +9 -9
- package/dist/browser/modules/excel/xlsx/xform/base-xform.d.ts +5 -5
- package/dist/browser/modules/excel/xlsx/xform/base-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-position-xform.d.ts +3 -4
- package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-notes-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-textbox-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/composite-xform.d.ts +1 -1
- package/dist/browser/modules/excel/xlsx/xform/core/app-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/cell-position-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/ext-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/list-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/list-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field-xform.d.ts +5 -15
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +134 -52
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field.d.ts +14 -15
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/cache-field.js +244 -70
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +13 -29
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +213 -37
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +7 -34
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +143 -41
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +101 -27
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.d.ts +78 -0
- package/dist/browser/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +149 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
- package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
- package/dist/browser/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
- package/dist/browser/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
- package/dist/browser/modules/excel/xlsx/xform/style/color-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +5 -5
- package/dist/browser/modules/excel/xlsx/xform/table/auto-filter-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/custom-filter-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/filter-column-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/filter-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/table-column-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/table-style-info-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xform/table/table-xform.d.ts +1 -2
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +5 -2
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +88 -54
- package/dist/browser/utils/env.d.ts +0 -5
- package/dist/browser/utils/env.js +0 -7
- package/dist/browser/utils/utils.base.d.ts +8 -13
- package/dist/browser/utils/utils.base.js +40 -47
- package/dist/browser/utils/utils.browser.d.ts +1 -1
- package/dist/browser/utils/utils.browser.js +1 -1
- package/dist/browser/utils/utils.d.ts +1 -1
- package/dist/browser/utils/utils.js +1 -1
- package/dist/cjs/modules/archive/unzip/stream.base.js +19 -19
- package/dist/cjs/modules/archive/unzip/stream.browser.js +3 -3
- package/dist/cjs/modules/csv/csv-core.js +6 -3
- package/dist/cjs/modules/csv/csv.browser.js +2 -2
- package/dist/cjs/modules/csv/csv.js +1 -1
- package/dist/cjs/modules/excel/anchor.js +4 -4
- package/dist/cjs/modules/excel/cell.js +5 -5
- package/dist/cjs/modules/excel/column.js +4 -4
- package/dist/cjs/modules/excel/defined-names.js +1 -1
- package/dist/cjs/modules/excel/form-control.js +1 -1
- package/dist/cjs/modules/excel/pivot-table.js +280 -70
- package/dist/cjs/modules/excel/row.js +4 -4
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +4 -4
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +1 -1
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +4 -4
- package/dist/cjs/modules/excel/table.js +2 -2
- package/dist/cjs/modules/excel/utils/cell-format.js +3 -3
- package/dist/cjs/modules/excel/utils/shared-formula.js +1 -1
- package/dist/cjs/modules/excel/utils/stream-buf.js +2 -2
- package/dist/cjs/modules/excel/utils/string-buf.js +1 -1
- package/dist/cjs/modules/excel/workbook.js +4 -5
- package/dist/cjs/modules/excel/worksheet.js +9 -9
- package/dist/cjs/modules/excel/xlsx/xform/base-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
- package/dist/cjs/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/app-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
- package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/list-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +133 -51
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/cache-field.js +245 -71
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +212 -36
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +142 -40
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
- package/dist/cjs/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +153 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
- package/dist/cjs/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
- package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
- package/dist/cjs/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
- package/dist/cjs/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
- package/dist/cjs/modules/excel/xlsx/xform/style/color-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +5 -5
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +88 -54
- package/dist/cjs/utils/env.js +0 -8
- package/dist/cjs/utils/utils.base.js +41 -54
- package/dist/cjs/utils/utils.browser.js +2 -7
- package/dist/cjs/utils/utils.js +2 -7
- package/dist/esm/modules/archive/unzip/stream.base.js +19 -19
- package/dist/esm/modules/archive/unzip/stream.browser.js +3 -3
- package/dist/esm/modules/csv/csv-core.js +6 -3
- package/dist/esm/modules/csv/csv.browser.js +2 -2
- package/dist/esm/modules/csv/csv.js +1 -1
- package/dist/esm/modules/excel/anchor.js +4 -4
- package/dist/esm/modules/excel/cell.js +5 -5
- package/dist/esm/modules/excel/column.js +4 -4
- package/dist/esm/modules/excel/defined-names.js +1 -1
- package/dist/esm/modules/excel/form-control.js +1 -1
- package/dist/esm/modules/excel/pivot-table.js +278 -70
- package/dist/esm/modules/excel/row.js +4 -4
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +4 -4
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +4 -4
- package/dist/esm/modules/excel/stream/worksheet-reader.js +1 -1
- package/dist/esm/modules/excel/stream/worksheet-writer.js +4 -4
- package/dist/esm/modules/excel/table.js +2 -2
- package/dist/esm/modules/excel/utils/cell-format.js +3 -3
- package/dist/esm/modules/excel/utils/shared-formula.js +1 -1
- package/dist/esm/modules/excel/utils/stream-buf.js +2 -2
- package/dist/esm/modules/excel/utils/string-buf.js +1 -1
- package/dist/esm/modules/excel/workbook.js +4 -5
- package/dist/esm/modules/excel/worksheet.js +9 -9
- package/dist/esm/modules/excel/xlsx/xform/base-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +2 -2
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-view-xform.js +4 -4
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +16 -4
- package/dist/esm/modules/excel/xlsx/xform/comment/style/vml-position-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/comment/style/vml-protection-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-textbox-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/core/app-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +24 -11
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/ext-xform.js +2 -2
- package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/list-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/cache-field-xform.js +134 -52
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/cache-field.js +244 -70
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +213 -37
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.js +143 -41
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.js +793 -408
- package/dist/esm/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.js +149 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/cf/cf-rule-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/cf/conditional-formattings-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/col-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/sheet/header-footer-xform.js +6 -6
- package/dist/esm/modules/excel/xlsx/xform/sheet/page-setup-xform.js +11 -11
- package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-format-properties-xform.js +3 -3
- package/dist/esm/modules/excel/xlsx/xform/sheet/sheet-view-xform.js +10 -10
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +12 -12
- package/dist/esm/modules/excel/xlsx/xform/strings/phonetic-text-xform.js +2 -2
- package/dist/esm/modules/excel/xlsx/xform/style/color-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +5 -5
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +88 -54
- package/dist/esm/utils/env.js +0 -7
- package/dist/esm/utils/utils.base.js +40 -47
- package/dist/esm/utils/utils.browser.js +1 -1
- package/dist/esm/utils/utils.js +1 -1
- package/dist/iife/excelts.iife.js +1553 -718
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +36 -105
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/modules/excel/pivot-table.d.ts +168 -17
- package/dist/types/modules/excel/types.d.ts +0 -4
- package/dist/types/modules/excel/workbook.d.ts +0 -2
- package/dist/types/modules/excel/xlsx/xform/base-xform.d.ts +5 -5
- package/dist/types/modules/excel/xlsx/xform/comment/comment-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/comment/comments-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/comment/style/vml-position-xform.d.ts +3 -4
- package/dist/types/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/comment/vml-notes-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/comment/vml-textbox-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/composite-xform.d.ts +1 -1
- package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/drawing/cell-position-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/drawing/drawing-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/drawing/ext-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/list-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/pivot-table/cache-field-xform.d.ts +5 -15
- package/dist/types/modules/excel/xlsx/xform/pivot-table/cache-field.d.ts +14 -15
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +13 -29
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-cache-records-xform.d.ts +7 -34
- package/dist/types/modules/excel/xlsx/xform/pivot-table/pivot-table-xform.d.ts +101 -27
- package/dist/types/modules/excel/xlsx/xform/pivot-table/raw-xml-collector.d.ts +78 -0
- package/dist/types/modules/excel/xlsx/xform/sheet/row-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/sheet/sheet-view-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/auto-filter-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/custom-filter-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/filter-column-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/filter-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/table-column-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/table-style-info-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xform/table/table-xform.d.ts +1 -2
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +5 -2
- package/dist/types/utils/env.d.ts +0 -5
- package/dist/types/utils/utils.base.d.ts +8 -13
- package/dist/types/utils/utils.browser.d.ts +1 -1
- package/dist/types/utils/utils.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
import { XmlStream } from "../../../utils/xml-stream.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
29
|
-
this.
|
|
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
|
|
42
|
-
|
|
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.
|
|
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
|
-
//
|
|
72
|
-
const
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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:
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
149
|
+
if (pageCount > 0) {
|
|
150
|
+
locAttrs.rowPageCount = pageCount;
|
|
151
|
+
locAttrs.colPageCount = 1;
|
|
195
152
|
}
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
else {
|
|
208
|
+
xmlStream.openNode("colItems", { count: 1 });
|
|
209
|
+
xmlStream.leafNode("i");
|
|
210
|
+
xmlStream.closeNode();
|
|
205
211
|
}
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
322
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
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
|
|
404
|
-
itemPrintTitles: attributes.itemPrintTitles
|
|
405
|
-
indent: attributes.indent ? parseInt(attributes.indent, 10) :
|
|
406
|
-
compact: attributes.compact
|
|
407
|
-
compactData: attributes.compactData
|
|
408
|
-
multipleFieldFilters: attributes.multipleFieldFilters
|
|
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.
|
|
701
|
+
this.state.currentSection = "pivotFields";
|
|
436
702
|
break;
|
|
437
703
|
case "pivotField":
|
|
438
|
-
if (this.state.
|
|
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
|
|
444
|
-
outline: attributes.outline
|
|
445
|
-
showAll: attributes.showAll
|
|
446
|
-
defaultSubtotal: attributes.defaultSubtotal
|
|
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
|
-
|
|
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 "
|
|
456
|
-
|
|
457
|
-
|
|
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.
|
|
758
|
+
this.state.currentSection = "rowFields";
|
|
462
759
|
break;
|
|
463
760
|
case "colFields":
|
|
464
|
-
this.state.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
482
|
-
this.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
this.
|
|
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 =
|
|
493
|
-
|
|
494
|
-
.
|
|
495
|
-
|
|
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
|
|
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
|
|
825
|
+
this.currentColItem.x.push({ v: parseInt(attributes.v ?? "0", 10) });
|
|
502
826
|
}
|
|
503
827
|
break;
|
|
504
828
|
case "chartFormats":
|
|
505
|
-
this.state.
|
|
829
|
+
this.state.currentSection = "chartFormats";
|
|
506
830
|
break;
|
|
507
831
|
case "chartFormat":
|
|
508
|
-
if (this.state.
|
|
832
|
+
if (this.state.currentSection === "chartFormats" && this.model) {
|
|
509
833
|
this.currentChartFormat = {
|
|
510
|
-
chart: attributes.chart
|
|
511
|
-
format: attributes.format
|
|
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 =
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
.
|
|
533
|
-
|
|
534
|
-
|
|
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
|
|
541
|
-
if (this.state.
|
|
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.
|
|
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.
|
|
879
|
+
if (this.state.currentSection === "dataFields" && this.model) {
|
|
551
880
|
this.model.dataFields.push({
|
|
552
|
-
name:
|
|
553
|
-
fld: parseInt(attributes.fld
|
|
554
|
-
baseField: attributes.baseField ? parseInt(attributes.baseField, 10) :
|
|
555
|
-
baseItem: attributes.baseItem ? parseInt(attributes.baseItem, 10) :
|
|
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(
|
|
569
|
-
//
|
|
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.
|
|
576
|
-
|
|
577
|
-
|
|
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.
|
|
586
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
621
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
686
|
-
|
|
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
|
-
*
|
|
691
|
-
*
|
|
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
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
760
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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 };
|