@rowsncolumns/toolkit 24.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +100 -0
- package/Readme.md +27 -0
- package/dist/cjs/autofilter-condition.d.ts +43 -0
- package/dist/cjs/autofilter-condition.d.ts.map +1 -0
- package/dist/cjs/autofilter-condition.js +155 -0
- package/dist/cjs/csv-export.d.ts +49 -0
- package/dist/cjs/csv-export.d.ts.map +1 -0
- package/dist/cjs/csv-export.js +142 -0
- package/dist/cjs/csv-import-worker.d.ts +16 -0
- package/dist/cjs/csv-import-worker.d.ts.map +1 -0
- package/dist/cjs/csv-import-worker.js +60 -0
- package/dist/cjs/csv-import.d.ts +38 -0
- package/dist/cjs/csv-import.d.ts.map +1 -0
- package/dist/cjs/csv-import.js +166 -0
- package/dist/cjs/csv-parser/helpers.d.ts +14 -0
- package/dist/cjs/csv-parser/helpers.d.ts.map +1 -0
- package/dist/cjs/csv-parser/helpers.js +145 -0
- package/dist/cjs/csv-parser/types.d.ts +66 -0
- package/dist/cjs/csv-parser/types.d.ts.map +1 -0
- package/dist/cjs/csv-parser/types.js +14 -0
- package/dist/cjs/csv-parser/worker.d.ts +2 -0
- package/dist/cjs/csv-parser/worker.d.ts.map +1 -0
- package/dist/cjs/csv-parser/worker.js +200 -0
- package/dist/cjs/excel-export.d.ts +11 -0
- package/dist/cjs/excel-export.d.ts.map +1 -0
- package/dist/cjs/excel-export.js +22 -0
- package/dist/cjs/excel-import-session.d.ts +51 -0
- package/dist/cjs/excel-import-session.d.ts.map +1 -0
- package/dist/cjs/excel-import-session.js +307 -0
- package/dist/cjs/excel-import-worker.d.ts +7 -0
- package/dist/cjs/excel-import-worker.d.ts.map +1 -0
- package/dist/cjs/excel-import-worker.js +119 -0
- package/dist/cjs/excel-parser/builtin-numfmts.d.ts +24 -0
- package/dist/cjs/excel-parser/builtin-numfmts.d.ts.map +1 -0
- package/dist/cjs/excel-parser/builtin-numfmts.js +63 -0
- package/dist/cjs/excel-parser/cfb-decryptor.d.ts +53 -0
- package/dist/cjs/excel-parser/cfb-decryptor.d.ts.map +1 -0
- package/dist/cjs/excel-parser/cfb-decryptor.js +553 -0
- package/dist/cjs/excel-parser/cfb-encryptor.d.ts +23 -0
- package/dist/cjs/excel-parser/cfb-encryptor.d.ts.map +1 -0
- package/dist/cjs/excel-parser/cfb-encryptor.js +200 -0
- package/dist/cjs/excel-parser/dv-cell-stamp.d.ts +29 -0
- package/dist/cjs/excel-parser/dv-cell-stamp.d.ts.map +1 -0
- package/dist/cjs/excel-parser/dv-cell-stamp.js +239 -0
- package/dist/cjs/excel-parser/helpers.d.ts +28 -0
- package/dist/cjs/excel-parser/helpers.d.ts.map +1 -0
- package/dist/cjs/excel-parser/helpers.js +188 -0
- package/dist/cjs/excel-parser/interface.d.ts +179 -0
- package/dist/cjs/excel-parser/interface.d.ts.map +1 -0
- package/dist/cjs/excel-parser/interface.js +1 -0
- package/dist/cjs/excel-parser/plugins/base/index.d.ts +31 -0
- package/dist/cjs/excel-parser/plugins/base/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/base/index.js +101 -0
- package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.d.ts +16 -0
- package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/conditional-formatting.js +488 -0
- package/dist/cjs/excel-parser/plugins/ods/data-validations.d.ts +15 -0
- package/dist/cjs/excel-parser/plugins/ods/data-validations.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/data-validations.js +353 -0
- package/dist/cjs/excel-parser/plugins/ods/formula.d.ts +8 -0
- package/dist/cjs/excel-parser/plugins/ods/formula.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/formula.js +82 -0
- package/dist/cjs/excel-parser/plugins/ods/index.d.ts +122 -0
- package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/index.js +3642 -0
- package/dist/cjs/excel-parser/plugins/ods/sparklines.d.ts +25 -0
- package/dist/cjs/excel-parser/plugins/ods/sparklines.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/sparklines.js +221 -0
- package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +60 -0
- package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/styles.js +808 -0
- package/dist/cjs/excel-parser/plugins/ods/types.d.ts +7 -0
- package/dist/cjs/excel-parser/plugins/ods/types.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/ods/types.js +1 -0
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts +5 -0
- package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xls/index.js +19 -0
- package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.d.ts +13 -0
- package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsb/biff-record-reader.js +49 -0
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +117 -0
- package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsb/index.js +2972 -0
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts +18 -0
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsb/rgce-parser.js +527 -0
- package/dist/cjs/excel-parser/plugins/xlsm/index.d.ts +5 -0
- package/dist/cjs/excel-parser/plugins/xlsm/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsm/index.js +10 -0
- package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +60 -0
- package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/charts.js +2178 -0
- package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts +18 -0
- package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.js +620 -0
- package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts +4 -0
- package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/data-validations.js +515 -0
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +39 -0
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +941 -0
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +320 -0
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/index.js +6056 -0
- package/dist/cjs/excel-parser/plugins/xlsx/numfmt.d.ts +2 -0
- package/dist/cjs/excel-parser/plugins/xlsx/numfmt.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/numfmt.js +3 -0
- package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts +40 -0
- package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +342 -0
- package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts +8 -0
- package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/relationships.js +26 -0
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts +26 -0
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.js +59 -0
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts +44 -0
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/styles.js +1377 -0
- package/dist/cjs/excel-parser/plugins/xlsx/theme.d.ts +50 -0
- package/dist/cjs/excel-parser/plugins/xlsx/theme.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/theme.js +150 -0
- package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.d.ts +47 -0
- package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/threaded-comments.js +141 -0
- package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.d.ts +44 -0
- package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.d.ts.map +1 -0
- package/dist/cjs/excel-parser/plugins/xlsx/value-type-detection.js +407 -0
- package/dist/cjs/excel-parser/registry.d.ts +5 -0
- package/dist/cjs/excel-parser/registry.d.ts.map +1 -0
- package/dist/cjs/excel-parser/registry.js +21 -0
- package/dist/cjs/excel-parser/style-registry.d.ts +51 -0
- package/dist/cjs/excel-parser/style-registry.d.ts.map +1 -0
- package/dist/cjs/excel-parser/style-registry.js +175 -0
- package/dist/cjs/excel-parser/theme-default.d.ts +3 -0
- package/dist/cjs/excel-parser/theme-default.d.ts.map +1 -0
- package/dist/cjs/excel-parser/theme-default.js +19 -0
- package/dist/cjs/excel-parser/types.d.ts +365 -0
- package/dist/cjs/excel-parser/types.d.ts.map +1 -0
- package/dist/cjs/excel-parser/types.js +59 -0
- package/dist/cjs/excel-parser/utils.d.ts +6 -0
- package/dist/cjs/excel-parser/utils.d.ts.map +1 -0
- package/dist/cjs/excel-parser/utils.js +30 -0
- package/dist/cjs/excel-parser/workbook.d.ts +51 -0
- package/dist/cjs/excel-parser/workbook.d.ts.map +1 -0
- package/dist/cjs/excel-parser/workbook.js +317 -0
- package/dist/cjs/excel-parser/worker.d.ts +2 -0
- package/dist/cjs/excel-parser/worker.d.ts.map +1 -0
- package/dist/cjs/excel-parser/worker.js +1149 -0
- package/dist/cjs/excel-parser/zip-parser-streaming.d.ts +13 -0
- package/dist/cjs/excel-parser/zip-parser-streaming.d.ts.map +1 -0
- package/dist/cjs/excel-parser/zip-parser-streaming.js +200 -0
- package/dist/cjs/excel-parser/zip-parser.d.ts +11 -0
- package/dist/cjs/excel-parser/zip-parser.d.ts.map +1 -0
- package/dist/cjs/excel-parser/zip-parser.js +49 -0
- package/dist/cjs/exporter/invalid-data-validation.d.ts +19 -0
- package/dist/cjs/exporter/invalid-data-validation.d.ts.map +1 -0
- package/dist/cjs/exporter/invalid-data-validation.js +39 -0
- package/dist/cjs/exporter/ods/ods-export.d.ts +26 -0
- package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -0
- package/dist/cjs/exporter/ods/ods-export.js +3714 -0
- package/dist/cjs/exporter/ods/sparkline-builder.d.ts +33 -0
- package/dist/cjs/exporter/ods/sparkline-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/ods/sparkline-builder.js +221 -0
- package/dist/cjs/exporter/xlsx/chart-builder.d.ts +3 -0
- package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/chart-builder.js +1901 -0
- package/dist/cjs/exporter/xlsx/chart-templates.d.ts +1781 -0
- package/dist/cjs/exporter/xlsx/chart-templates.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/chart-templates.js +1798 -0
- package/dist/cjs/exporter/xlsx/future-functions.d.ts +10 -0
- package/dist/cjs/exporter/xlsx/future-functions.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/future-functions.js +264 -0
- package/dist/cjs/exporter/xlsx/pivot-builder.d.ts +58 -0
- package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/pivot-builder.js +277 -0
- package/dist/cjs/exporter/xlsx/power-query-builder.d.ts +26 -0
- package/dist/cjs/exporter/xlsx/power-query-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/power-query-builder.js +306 -0
- package/dist/cjs/exporter/xlsx/slicer-builder.d.ts +11 -0
- package/dist/cjs/exporter/xlsx/slicer-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/slicer-builder.js +87 -0
- package/dist/cjs/exporter/xlsx/sparkline-builder.d.ts +61 -0
- package/dist/cjs/exporter/xlsx/sparkline-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/sparkline-builder.js +489 -0
- package/dist/cjs/exporter/xlsx/style-builder.d.ts +93 -0
- package/dist/cjs/exporter/xlsx/style-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/style-builder.js +813 -0
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts +58 -0
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/threaded-comments-builder.js +189 -0
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +121 -0
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -0
- package/dist/cjs/exporter/xlsx/xlsx-export.js +4596 -0
- package/dist/cjs/helpers/theme-xml-generator.d.ts +8 -0
- package/dist/cjs/helpers/theme-xml-generator.d.ts.map +1 -0
- package/dist/cjs/helpers/theme-xml-generator.js +33 -0
- package/dist/cjs/helpers.d.ts +34 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +189 -0
- package/dist/cjs/index.d.ts +16 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/indexed-colors.d.ts +2 -0
- package/dist/cjs/indexed-colors.d.ts.map +1 -0
- package/dist/cjs/indexed-colors.js +75 -0
- package/dist/cjs/ods-export.d.ts +24 -0
- package/dist/cjs/ods-export.d.ts.map +1 -0
- package/dist/cjs/ods-export.js +23 -0
- package/dist/cjs/parse.d.ts +24 -0
- package/dist/cjs/parse.d.ts.map +1 -0
- package/dist/cjs/parse.js +68 -0
- package/dist/cjs/power-query.d.ts +41 -0
- package/dist/cjs/power-query.d.ts.map +1 -0
- package/dist/cjs/power-query.js +22 -0
- package/dist/cjs/server.d.ts +9 -0
- package/dist/cjs/server.d.ts.map +1 -0
- package/dist/cjs/server.js +8 -0
- package/dist/cjs/theme1.d.ts +3 -0
- package/dist/cjs/theme1.d.ts.map +1 -0
- package/dist/cjs/theme1.js +2 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/cjs/types.d.ts +14 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/vitest.config.d.ts +3 -0
- package/dist/cjs/vitest.config.d.ts.map +1 -0
- package/dist/cjs/vitest.config.js +16 -0
- package/dist/esm/autofilter-condition.d.ts +43 -0
- package/dist/esm/autofilter-condition.d.ts.map +1 -0
- package/dist/esm/autofilter-condition.js +145 -0
- package/dist/esm/csv-export.d.ts +49 -0
- package/dist/esm/csv-export.d.ts.map +1 -0
- package/dist/esm/csv-export.js +126 -0
- package/dist/esm/csv-import-worker.d.ts +16 -0
- package/dist/esm/csv-import-worker.d.ts.map +1 -0
- package/dist/esm/csv-import-worker.js +60 -0
- package/dist/esm/csv-import.d.ts +38 -0
- package/dist/esm/csv-import.d.ts.map +1 -0
- package/dist/esm/csv-import.js +132 -0
- package/dist/esm/csv-parser/helpers.d.ts +14 -0
- package/dist/esm/csv-parser/helpers.d.ts.map +1 -0
- package/dist/esm/csv-parser/helpers.js +0 -0
- package/dist/esm/csv-parser/types.d.ts +66 -0
- package/dist/esm/csv-parser/types.d.ts.map +1 -0
- package/dist/esm/csv-parser/types.js +14 -0
- package/dist/esm/csv-parser/worker.d.ts +2 -0
- package/dist/esm/csv-parser/worker.d.ts.map +1 -0
- package/dist/esm/csv-parser/worker.js +154 -0
- package/dist/esm/excel-export.d.ts +11 -0
- package/dist/esm/excel-export.d.ts.map +1 -0
- package/dist/esm/excel-export.js +13 -0
- package/dist/esm/excel-import-session.d.ts +51 -0
- package/dist/esm/excel-import-session.d.ts.map +1 -0
- package/dist/esm/excel-import-session.js +255 -0
- package/dist/esm/excel-import-worker.d.ts +7 -0
- package/dist/esm/excel-import-worker.d.ts.map +1 -0
- package/dist/esm/excel-import-worker.js +116 -0
- package/dist/esm/excel-parser/builtin-numfmts.d.ts +24 -0
- package/dist/esm/excel-parser/builtin-numfmts.d.ts.map +1 -0
- package/dist/esm/excel-parser/builtin-numfmts.js +59 -0
- package/dist/esm/excel-parser/cfb-decryptor.d.ts +53 -0
- package/dist/esm/excel-parser/cfb-decryptor.d.ts.map +1 -0
- package/dist/esm/excel-parser/cfb-decryptor.js +493 -0
- package/dist/esm/excel-parser/cfb-encryptor.d.ts +23 -0
- package/dist/esm/excel-parser/cfb-encryptor.d.ts.map +1 -0
- package/dist/esm/excel-parser/cfb-encryptor.js +181 -0
- package/dist/esm/excel-parser/dv-cell-stamp.d.ts +29 -0
- package/dist/esm/excel-parser/dv-cell-stamp.d.ts.map +1 -0
- package/dist/esm/excel-parser/dv-cell-stamp.js +155 -0
- package/dist/esm/excel-parser/helpers.d.ts +28 -0
- package/dist/esm/excel-parser/helpers.d.ts.map +1 -0
- package/dist/esm/excel-parser/helpers.js +160 -0
- package/dist/esm/excel-parser/interface.d.ts +179 -0
- package/dist/esm/excel-parser/interface.d.ts.map +1 -0
- package/dist/esm/excel-parser/interface.js +1 -0
- package/dist/esm/excel-parser/plugins/base/index.d.ts +31 -0
- package/dist/esm/excel-parser/plugins/base/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/base/index.js +64 -0
- package/dist/esm/excel-parser/plugins/ods/conditional-formatting.d.ts +16 -0
- package/dist/esm/excel-parser/plugins/ods/conditional-formatting.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/conditional-formatting.js +405 -0
- package/dist/esm/excel-parser/plugins/ods/data-validations.d.ts +15 -0
- package/dist/esm/excel-parser/plugins/ods/data-validations.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/data-validations.js +331 -0
- package/dist/esm/excel-parser/plugins/ods/formula.d.ts +8 -0
- package/dist/esm/excel-parser/plugins/ods/formula.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/formula.js +82 -0
- package/dist/esm/excel-parser/plugins/ods/index.d.ts +122 -0
- package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/index.js +2896 -0
- package/dist/esm/excel-parser/plugins/ods/sparklines.d.ts +25 -0
- package/dist/esm/excel-parser/plugins/ods/sparklines.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/sparklines.js +188 -0
- package/dist/esm/excel-parser/plugins/ods/styles.d.ts +60 -0
- package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/styles.js +732 -0
- package/dist/esm/excel-parser/plugins/ods/types.d.ts +7 -0
- package/dist/esm/excel-parser/plugins/ods/types.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/ods/types.js +1 -0
- package/dist/esm/excel-parser/plugins/xls/index.d.ts +5 -0
- package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xls/index.js +7 -0
- package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.d.ts +13 -0
- package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsb/biff-record-reader.js +48 -0
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +117 -0
- package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsb/index.js +2339 -0
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts +18 -0
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsb/rgce-parser.js +522 -0
- package/dist/esm/excel-parser/plugins/xlsm/index.d.ts +5 -0
- package/dist/esm/excel-parser/plugins/xlsm/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsm/index.js +6 -0
- package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +60 -0
- package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/charts.js +2036 -0
- package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts +18 -0
- package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.js +621 -0
- package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts +4 -0
- package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/data-validations.js +493 -0
- package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +39 -0
- package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/drawing.js +905 -0
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +320 -0
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/index.js +4654 -0
- package/dist/esm/excel-parser/plugins/xlsx/numfmt.d.ts +2 -0
- package/dist/esm/excel-parser/plugins/xlsx/numfmt.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/numfmt.js +3 -0
- package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts +40 -0
- package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/pivots.js +254 -0
- package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts +8 -0
- package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/relationships.js +13 -0
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts +26 -0
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/sheet-views.js +64 -0
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +44 -0
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/styles.js +1365 -0
- package/dist/esm/excel-parser/plugins/xlsx/theme.d.ts +50 -0
- package/dist/esm/excel-parser/plugins/xlsx/theme.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/theme.js +127 -0
- package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.d.ts +47 -0
- package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/threaded-comments.js +103 -0
- package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.d.ts +44 -0
- package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.d.ts.map +1 -0
- package/dist/esm/excel-parser/plugins/xlsx/value-type-detection.js +399 -0
- package/dist/esm/excel-parser/registry.d.ts +5 -0
- package/dist/esm/excel-parser/registry.d.ts.map +1 -0
- package/dist/esm/excel-parser/registry.js +20 -0
- package/dist/esm/excel-parser/style-registry.d.ts +51 -0
- package/dist/esm/excel-parser/style-registry.d.ts.map +1 -0
- package/dist/esm/excel-parser/style-registry.js +163 -0
- package/dist/esm/excel-parser/theme-default.d.ts +3 -0
- package/dist/esm/excel-parser/theme-default.d.ts.map +1 -0
- package/dist/esm/excel-parser/theme-default.js +19 -0
- package/dist/esm/excel-parser/types.d.ts +365 -0
- package/dist/esm/excel-parser/types.d.ts.map +1 -0
- package/dist/esm/excel-parser/types.js +59 -0
- package/dist/esm/excel-parser/utils.d.ts +6 -0
- package/dist/esm/excel-parser/utils.d.ts.map +1 -0
- package/dist/esm/excel-parser/utils.js +25 -0
- package/dist/esm/excel-parser/workbook.d.ts +51 -0
- package/dist/esm/excel-parser/workbook.d.ts.map +1 -0
- package/dist/esm/excel-parser/workbook.js +192 -0
- package/dist/esm/excel-parser/worker.d.ts +2 -0
- package/dist/esm/excel-parser/worker.d.ts.map +1 -0
- package/dist/esm/excel-parser/worker.js +862 -0
- package/dist/esm/excel-parser/zip-parser-streaming.d.ts +13 -0
- package/dist/esm/excel-parser/zip-parser-streaming.d.ts.map +1 -0
- package/dist/esm/excel-parser/zip-parser-streaming.js +119 -0
- package/dist/esm/excel-parser/zip-parser.d.ts +11 -0
- package/dist/esm/excel-parser/zip-parser.d.ts.map +1 -0
- package/dist/esm/excel-parser/zip-parser.js +43 -0
- package/dist/esm/exporter/invalid-data-validation.d.ts +19 -0
- package/dist/esm/exporter/invalid-data-validation.d.ts.map +1 -0
- package/dist/esm/exporter/invalid-data-validation.js +37 -0
- package/dist/esm/exporter/ods/ods-export.d.ts +26 -0
- package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -0
- package/dist/esm/exporter/ods/ods-export.js +3238 -0
- package/dist/esm/exporter/ods/sparkline-builder.d.ts +33 -0
- package/dist/esm/exporter/ods/sparkline-builder.d.ts.map +1 -0
- package/dist/esm/exporter/ods/sparkline-builder.js +165 -0
- package/dist/esm/exporter/xlsx/chart-builder.d.ts +3 -0
- package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/chart-builder.js +1805 -0
- package/dist/esm/exporter/xlsx/chart-templates.d.ts +1781 -0
- package/dist/esm/exporter/xlsx/chart-templates.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/chart-templates.js +1798 -0
- package/dist/esm/exporter/xlsx/future-functions.d.ts +10 -0
- package/dist/esm/exporter/xlsx/future-functions.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/future-functions.js +264 -0
- package/dist/esm/exporter/xlsx/pivot-builder.d.ts +58 -0
- package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/pivot-builder.js +230 -0
- package/dist/esm/exporter/xlsx/power-query-builder.d.ts +26 -0
- package/dist/esm/exporter/xlsx/power-query-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/power-query-builder.js +257 -0
- package/dist/esm/exporter/xlsx/slicer-builder.d.ts +11 -0
- package/dist/esm/exporter/xlsx/slicer-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/slicer-builder.js +84 -0
- package/dist/esm/exporter/xlsx/sparkline-builder.d.ts +61 -0
- package/dist/esm/exporter/xlsx/sparkline-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/sparkline-builder.js +437 -0
- package/dist/esm/exporter/xlsx/style-builder.d.ts +93 -0
- package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/style-builder.js +795 -0
- package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts +58 -0
- package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/threaded-comments-builder.js +175 -0
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts +121 -0
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -0
- package/dist/esm/exporter/xlsx/xlsx-export.js +4062 -0
- package/dist/esm/helpers/theme-xml-generator.d.ts +8 -0
- package/dist/esm/helpers/theme-xml-generator.d.ts.map +1 -0
- package/dist/esm/helpers/theme-xml-generator.js +33 -0
- package/dist/esm/helpers.d.ts +34 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +182 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/indexed-colors.d.ts +2 -0
- package/dist/esm/indexed-colors.d.ts.map +1 -0
- package/dist/esm/indexed-colors.js +75 -0
- package/dist/esm/ods-export.d.ts +24 -0
- package/dist/esm/ods-export.d.ts.map +1 -0
- package/dist/esm/ods-export.js +12 -0
- package/dist/esm/parse.d.ts +24 -0
- package/dist/esm/parse.d.ts.map +1 -0
- package/dist/esm/parse.js +55 -0
- package/dist/esm/power-query.d.ts +41 -0
- package/dist/esm/power-query.d.ts.map +1 -0
- package/dist/esm/power-query.js +19 -0
- package/dist/esm/server.d.ts +9 -0
- package/dist/esm/server.d.ts.map +1 -0
- package/dist/esm/server.js +8 -0
- package/dist/esm/theme1.d.ts +3 -0
- package/dist/esm/theme1.d.ts.map +1 -0
- package/dist/esm/theme1.js +2 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/types.d.ts +14 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +14 -0
- package/dist/esm/vitest.config.d.ts +3 -0
- package/dist/esm/vitest.config.d.ts.map +1 -0
- package/dist/esm/vitest.config.js +16 -0
- package/package.json +62 -0
- package/xlsx.d.ts +52 -0
|
@@ -0,0 +1,2896 @@
|
|
|
1
|
+
import { addressToCell, cellToAddress, getCellXfsKey, uuid, } from "@rowsncolumns/utils";
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
import { get } from "lodash-es";
|
|
4
|
+
import { Unescape } from "../../utils";
|
|
5
|
+
import { ZipParser } from "../../zip-parser";
|
|
6
|
+
import { SheetDataValidationStamper } from "../../dv-cell-stamp";
|
|
7
|
+
import { BaseWorkbookPlugin } from "../base/index";
|
|
8
|
+
import { detectValueTypeAndPattern } from "../xlsx/value-type-detection";
|
|
9
|
+
import { extractConditionalFormats } from "./conditional-formatting";
|
|
10
|
+
import { extractDataValidations } from "./data-validations";
|
|
11
|
+
import { convertOdsFormulaToExcel } from "./formula";
|
|
12
|
+
import { extractSparklines, extractSparklineData, } from "./sparklines";
|
|
13
|
+
import { OdsStyles } from "./styles";
|
|
14
|
+
const ODS_XML_OPTIONS = {
|
|
15
|
+
ignoreAttributes: false,
|
|
16
|
+
attributeNamePrefix: "@_",
|
|
17
|
+
ignoreDeclaration: true,
|
|
18
|
+
// We keep namespaces because ODS relies on them (table:, office:, text:)
|
|
19
|
+
removeNSPrefix: false,
|
|
20
|
+
// Disable entity processing to avoid expansion limits on large files,
|
|
21
|
+
// then decode the bounded set of entities ourselves (same approach as
|
|
22
|
+
// the XLSX plugin's XMLTagProcessor) so text and attribute values don't
|
|
23
|
+
// keep literal &/</ sequences.
|
|
24
|
+
processEntities: false,
|
|
25
|
+
tagValueProcessor: (_name, value) => Unescape(value),
|
|
26
|
+
attributeValueProcessor: (_name, value) => Unescape(value),
|
|
27
|
+
};
|
|
28
|
+
const ODS_ORDERED_XML_OPTIONS = {
|
|
29
|
+
...ODS_XML_OPTIONS,
|
|
30
|
+
preserveOrder: true,
|
|
31
|
+
};
|
|
32
|
+
const DISPLAY_CURRENCY_REGEX = /[$€¥£₹]/;
|
|
33
|
+
function findChild(children, tagName) {
|
|
34
|
+
return children?.find((child) => child[tagName]);
|
|
35
|
+
}
|
|
36
|
+
function getChildren(node, tagName) {
|
|
37
|
+
const found = findChild(node, tagName);
|
|
38
|
+
return found ? found[tagName] : undefined;
|
|
39
|
+
}
|
|
40
|
+
function getAttribute(node, attributeName) {
|
|
41
|
+
if (!node)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (node[`@_${attributeName}`] !== undefined) {
|
|
44
|
+
return node[`@_${attributeName}`];
|
|
45
|
+
}
|
|
46
|
+
if (node[":@"] && node[":@"][`@_${attributeName}`] !== undefined) {
|
|
47
|
+
return node[":@"][`@_${attributeName}`];
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
function fastMergeCellFormats(...formats) {
|
|
52
|
+
let result;
|
|
53
|
+
for (const format of formats) {
|
|
54
|
+
if (!format)
|
|
55
|
+
continue;
|
|
56
|
+
if (!result) {
|
|
57
|
+
result = { ...format };
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Merge properties
|
|
61
|
+
if (format.backgroundColor !== undefined)
|
|
62
|
+
result.backgroundColor = format.backgroundColor;
|
|
63
|
+
if (format.horizontalAlignment !== undefined)
|
|
64
|
+
result.horizontalAlignment = format.horizontalAlignment;
|
|
65
|
+
if (format.verticalAlignment !== undefined)
|
|
66
|
+
result.verticalAlignment = format.verticalAlignment;
|
|
67
|
+
if (format.wrapStrategy !== undefined)
|
|
68
|
+
result.wrapStrategy = format.wrapStrategy;
|
|
69
|
+
if (format.textRotation !== undefined)
|
|
70
|
+
result.textRotation = format.textRotation;
|
|
71
|
+
if (format.indent !== undefined)
|
|
72
|
+
result.indent = format.indent;
|
|
73
|
+
// Objects
|
|
74
|
+
if (format.textFormat) {
|
|
75
|
+
result.textFormat = result.textFormat
|
|
76
|
+
? { ...result.textFormat, ...format.textFormat }
|
|
77
|
+
: { ...format.textFormat };
|
|
78
|
+
}
|
|
79
|
+
if (format.numberFormat) {
|
|
80
|
+
result.numberFormat = { ...format.numberFormat };
|
|
81
|
+
}
|
|
82
|
+
if (format.borders) {
|
|
83
|
+
result.borders = result.borders
|
|
84
|
+
? { ...result.borders }
|
|
85
|
+
: { ...format.borders };
|
|
86
|
+
if (format.borders.top)
|
|
87
|
+
result.borders.top = format.borders.top;
|
|
88
|
+
if (format.borders.bottom)
|
|
89
|
+
result.borders.bottom = format.borders.bottom;
|
|
90
|
+
if (format.borders.left)
|
|
91
|
+
result.borders.left = format.borders.left;
|
|
92
|
+
if (format.borders.right)
|
|
93
|
+
result.borders.right = format.borders.right;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
// Inverse of the exporter's mapConditionToFilterCondition: ODS
|
|
99
|
+
// <table:filter-condition table:operator> -> in-app condition type. "=" is
|
|
100
|
+
// intentionally absent — it round-trips as a value-list (the common case),
|
|
101
|
+
// which yields the same visible rows as NUMBER_EQ.
|
|
102
|
+
const ODS_OPERATOR_TO_CONDITION = {
|
|
103
|
+
"!=": "NUMBER_NOT_EQ",
|
|
104
|
+
"<>": "NUMBER_NOT_EQ",
|
|
105
|
+
">": "NUMBER_GREATER",
|
|
106
|
+
">=": "NUMBER_GREATER_THAN_EQ",
|
|
107
|
+
"<": "NUMBER_LESS",
|
|
108
|
+
"<=": "NUMBER_LESS_THAN_EQ",
|
|
109
|
+
between: "NUMBER_BETWEEN",
|
|
110
|
+
"not-between": "NUMBER_NOT_BETWEEN",
|
|
111
|
+
contains: "TEXT_CONTAINS",
|
|
112
|
+
"does-not-contain": "TEXT_NOT_CONTAINS",
|
|
113
|
+
"begins-with": "TEXT_STARTS_WITH",
|
|
114
|
+
begins: "TEXT_STARTS_WITH",
|
|
115
|
+
"ends-with": "TEXT_ENDS_WITH",
|
|
116
|
+
endswith: "TEXT_ENDS_WITH",
|
|
117
|
+
empty: "BLANK",
|
|
118
|
+
"non-empty": "NOT_BLANK",
|
|
119
|
+
};
|
|
120
|
+
export class OdsPlugin extends BaseWorkbookPlugin {
|
|
121
|
+
zipParser;
|
|
122
|
+
xmlParser;
|
|
123
|
+
xmlOrderedParser;
|
|
124
|
+
contentXml;
|
|
125
|
+
contentXmlOrdered;
|
|
126
|
+
settingsXml;
|
|
127
|
+
odsStyles;
|
|
128
|
+
cellXfs = new Map();
|
|
129
|
+
sharedStrings = [];
|
|
130
|
+
richSharedStringRuns = new Map();
|
|
131
|
+
sharedStringIndexByValue = new Map();
|
|
132
|
+
sheetCache = null;
|
|
133
|
+
sparklinesBySheet = new Map();
|
|
134
|
+
constructor() {
|
|
135
|
+
super();
|
|
136
|
+
this.zipParser = new ZipParser();
|
|
137
|
+
this.xmlParser = new XMLParser(ODS_XML_OPTIONS);
|
|
138
|
+
this.xmlOrderedParser = new XMLParser(ODS_ORDERED_XML_OPTIONS);
|
|
139
|
+
this.odsStyles = new OdsStyles();
|
|
140
|
+
}
|
|
141
|
+
async load(buffer, password) {
|
|
142
|
+
const source = buffer instanceof File ? await buffer.arrayBuffer() : buffer;
|
|
143
|
+
await this.zipParser.load(source, password);
|
|
144
|
+
this.cellXfs.clear();
|
|
145
|
+
this.odsStyles.reset();
|
|
146
|
+
this.sharedStrings = [];
|
|
147
|
+
this.richSharedStringRuns = new Map();
|
|
148
|
+
this.sharedStringIndexByValue.clear();
|
|
149
|
+
this.sheetCache = null;
|
|
150
|
+
this.sparklinesBySheet = new Map();
|
|
151
|
+
this.settingsXml = null;
|
|
152
|
+
try {
|
|
153
|
+
const content = this.zipParser.Get("content.xml");
|
|
154
|
+
if (content) {
|
|
155
|
+
this.contentXml = this.xmlParser.parse(content);
|
|
156
|
+
this.contentXmlOrdered = this.xmlOrderedParser.parse(content);
|
|
157
|
+
this.odsStyles.parseContentXml(this.contentXml);
|
|
158
|
+
}
|
|
159
|
+
const styles = this.zipParser.Get("styles.xml");
|
|
160
|
+
if (styles) {
|
|
161
|
+
const parsedStyles = this.xmlParser.parse(styles);
|
|
162
|
+
this.odsStyles.parseStylesXml(parsedStyles);
|
|
163
|
+
}
|
|
164
|
+
const settings = this.zipParser.Get("settings.xml");
|
|
165
|
+
if (settings) {
|
|
166
|
+
this.settingsXml = this.xmlParser.parse(settings);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
console.error("Failed to parse ODS content.xml", e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
getCellXfs() {
|
|
174
|
+
return this.cellXfs;
|
|
175
|
+
}
|
|
176
|
+
getSharedStrings() {
|
|
177
|
+
return this.sharedStrings;
|
|
178
|
+
}
|
|
179
|
+
getOrCreateSharedStringIndex(value, runs) {
|
|
180
|
+
const key = runs?.length ? JSON.stringify({ value, runs }) : value;
|
|
181
|
+
const existing = this.sharedStringIndexByValue.get(key);
|
|
182
|
+
if (existing !== undefined) {
|
|
183
|
+
return existing;
|
|
184
|
+
}
|
|
185
|
+
const index = this.sharedStrings.length;
|
|
186
|
+
this.sharedStrings.push(value);
|
|
187
|
+
if (runs?.length) {
|
|
188
|
+
this.richSharedStringRuns.set(index, runs);
|
|
189
|
+
}
|
|
190
|
+
this.sharedStringIndexByValue.set(key, index);
|
|
191
|
+
return index;
|
|
192
|
+
}
|
|
193
|
+
registerFormat(format) {
|
|
194
|
+
const key = getCellXfsKey(format);
|
|
195
|
+
if (!this.cellXfs.has(key)) {
|
|
196
|
+
this.cellXfs.set(key, format);
|
|
197
|
+
}
|
|
198
|
+
return key;
|
|
199
|
+
}
|
|
200
|
+
dispose() {
|
|
201
|
+
this.zipParser.Dispose();
|
|
202
|
+
this.contentXml = null;
|
|
203
|
+
this.contentXmlOrdered = null;
|
|
204
|
+
this.settingsXml = null;
|
|
205
|
+
this.sheetCache = null;
|
|
206
|
+
}
|
|
207
|
+
getSpreadsheetNode() {
|
|
208
|
+
if (!this.contentXml)
|
|
209
|
+
return undefined;
|
|
210
|
+
return (get(this.contentXml, "office:document-content.office:body.office:spreadsheet") || get(this.contentXml, "document-content.body.spreadsheet"));
|
|
211
|
+
}
|
|
212
|
+
getTables() {
|
|
213
|
+
const body = this.getSpreadsheetNode();
|
|
214
|
+
if (!body)
|
|
215
|
+
return [];
|
|
216
|
+
const tables = get(body, "table:table") || get(body, "table");
|
|
217
|
+
return Array.isArray(tables) ? tables : tables ? [tables] : [];
|
|
218
|
+
}
|
|
219
|
+
getSheetCount() {
|
|
220
|
+
return this.getTables().length;
|
|
221
|
+
}
|
|
222
|
+
ensureSheetCache() {
|
|
223
|
+
if (this.sheetCache) {
|
|
224
|
+
return this.sheetCache;
|
|
225
|
+
}
|
|
226
|
+
const cache = this.buildSheetCache();
|
|
227
|
+
this.sheetCache = cache;
|
|
228
|
+
return cache;
|
|
229
|
+
}
|
|
230
|
+
buildSheetCache() {
|
|
231
|
+
const tables = this.getTables();
|
|
232
|
+
const sheets = [];
|
|
233
|
+
const sheetContexts = [];
|
|
234
|
+
tables.forEach((table, index) => {
|
|
235
|
+
const name = table["@_table:name"] ||
|
|
236
|
+
table["@_name"] ||
|
|
237
|
+
table["table:name"] ||
|
|
238
|
+
table["name"] ||
|
|
239
|
+
`Sheet${index + 1}`;
|
|
240
|
+
const sheet = {
|
|
241
|
+
sheetId: index + 1,
|
|
242
|
+
title: name,
|
|
243
|
+
hidden: table["@_table:style-name"]?.includes("hidden") ||
|
|
244
|
+
table["table:style-name"]?.includes("hidden") ||
|
|
245
|
+
false,
|
|
246
|
+
rowMetadata: [],
|
|
247
|
+
columnMetadata: [],
|
|
248
|
+
merges: [],
|
|
249
|
+
};
|
|
250
|
+
// Column metadata from table-column elements
|
|
251
|
+
const columnNodes = get(table, "table:table-column") || get(table, "table-column") || [];
|
|
252
|
+
const allColumns = Array.isArray(columnNodes)
|
|
253
|
+
? columnNodes
|
|
254
|
+
: columnNodes
|
|
255
|
+
? [columnNodes]
|
|
256
|
+
: [];
|
|
257
|
+
let currentColumnIndex = 1;
|
|
258
|
+
let detectedColumnCount = 0;
|
|
259
|
+
let defaultColumnWidth;
|
|
260
|
+
let defaultColumnStyleName;
|
|
261
|
+
for (const column of allColumns) {
|
|
262
|
+
if (!column)
|
|
263
|
+
continue;
|
|
264
|
+
const colsRepeated = parseInt(getAttribute(column, "table:number-columns-repeated") ||
|
|
265
|
+
getAttribute(column, "number-columns-repeated") ||
|
|
266
|
+
"1", 10);
|
|
267
|
+
const styleName = getAttribute(column, "table:style-name") ||
|
|
268
|
+
getAttribute(column, "style-name");
|
|
269
|
+
const visibility = getAttribute(column, "table:visibility") ||
|
|
270
|
+
getAttribute(column, "visibility");
|
|
271
|
+
const columnStyle = styleName
|
|
272
|
+
? this.odsStyles.getColumnStyle(styleName)
|
|
273
|
+
: undefined;
|
|
274
|
+
const width = columnStyle?.width;
|
|
275
|
+
const isHidden = visibility === "collapse" ||
|
|
276
|
+
visibility === "hidden" ||
|
|
277
|
+
visibility === "none" ||
|
|
278
|
+
columnStyle?.hidden;
|
|
279
|
+
for (let c = 0; c < colsRepeated; c++) {
|
|
280
|
+
const props = {};
|
|
281
|
+
let shouldAssign = false;
|
|
282
|
+
if (typeof width === "number") {
|
|
283
|
+
props.size = width;
|
|
284
|
+
if (defaultColumnWidth === undefined) {
|
|
285
|
+
defaultColumnWidth = width;
|
|
286
|
+
defaultColumnStyleName = styleName;
|
|
287
|
+
}
|
|
288
|
+
const widthDiffers = defaultColumnWidth === undefined
|
|
289
|
+
? true
|
|
290
|
+
: Math.abs(width - defaultColumnWidth) > 0.5;
|
|
291
|
+
const styleDiffers = styleName &&
|
|
292
|
+
defaultColumnStyleName &&
|
|
293
|
+
styleName !== defaultColumnStyleName;
|
|
294
|
+
if (widthDiffers || styleDiffers) {
|
|
295
|
+
props.resizedByUser = true;
|
|
296
|
+
}
|
|
297
|
+
shouldAssign = true;
|
|
298
|
+
}
|
|
299
|
+
if (isHidden) {
|
|
300
|
+
props.hiddenByUser = true;
|
|
301
|
+
shouldAssign = true;
|
|
302
|
+
}
|
|
303
|
+
if (shouldAssign) {
|
|
304
|
+
sheet.columnMetadata[currentColumnIndex] = props;
|
|
305
|
+
}
|
|
306
|
+
currentColumnIndex++;
|
|
307
|
+
}
|
|
308
|
+
detectedColumnCount = Math.max(detectedColumnCount, currentColumnIndex - 1);
|
|
309
|
+
}
|
|
310
|
+
// Row metadata from table-row elements
|
|
311
|
+
const rowNodes = get(table, "table:table-row") || get(table, "table-row") || [];
|
|
312
|
+
const allRows = Array.isArray(rowNodes) ? rowNodes : [rowNodes];
|
|
313
|
+
// DO NOT DELETE
|
|
314
|
+
// console.log("allRows", allRows);
|
|
315
|
+
let currentRowIndex = 1;
|
|
316
|
+
let defaultRowHeight;
|
|
317
|
+
let defaultRowStyleName;
|
|
318
|
+
let detectedRowCount = 0;
|
|
319
|
+
for (const row of allRows) {
|
|
320
|
+
if (!row)
|
|
321
|
+
continue;
|
|
322
|
+
const rowsRepeated = parseInt(getAttribute(row, "table:number-rows-repeated") ||
|
|
323
|
+
getAttribute(row, "number-rows-repeated") ||
|
|
324
|
+
"1", 10);
|
|
325
|
+
const styleName = getAttribute(row, "table:style-name") ||
|
|
326
|
+
getAttribute(row, "style-name");
|
|
327
|
+
const visibility = getAttribute(row, "table:visibility") ||
|
|
328
|
+
getAttribute(row, "visibility");
|
|
329
|
+
const rowStyle = styleName
|
|
330
|
+
? this.odsStyles.getRowStyle(styleName)
|
|
331
|
+
: undefined;
|
|
332
|
+
const height = rowStyle?.height;
|
|
333
|
+
const isHidden = visibility === "collapse" ||
|
|
334
|
+
visibility === "hidden" ||
|
|
335
|
+
visibility === "none" ||
|
|
336
|
+
rowStyle?.hidden;
|
|
337
|
+
const effectiveRepeats = Math.min(rowsRepeated, 10000);
|
|
338
|
+
const shouldProcessRowMetadata = typeof height === "number" || isHidden;
|
|
339
|
+
if (!shouldProcessRowMetadata) {
|
|
340
|
+
currentRowIndex += effectiveRepeats;
|
|
341
|
+
detectedRowCount = Math.max(detectedRowCount, currentRowIndex - 1);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
for (let r = 0; r < effectiveRepeats; r++) {
|
|
345
|
+
const props = {};
|
|
346
|
+
let shouldAssign = false;
|
|
347
|
+
if (typeof height === "number") {
|
|
348
|
+
props.size = height;
|
|
349
|
+
if (defaultRowHeight === undefined) {
|
|
350
|
+
defaultRowHeight = height;
|
|
351
|
+
defaultRowStyleName = styleName;
|
|
352
|
+
}
|
|
353
|
+
const heightDiffers = defaultRowHeight === undefined
|
|
354
|
+
? true
|
|
355
|
+
: Math.abs(height - defaultRowHeight) > 0.5;
|
|
356
|
+
const styleDiffers = styleName &&
|
|
357
|
+
defaultRowStyleName &&
|
|
358
|
+
styleName !== defaultRowStyleName;
|
|
359
|
+
if (heightDiffers || styleDiffers) {
|
|
360
|
+
props.resizedByUser = true;
|
|
361
|
+
}
|
|
362
|
+
shouldAssign = true;
|
|
363
|
+
}
|
|
364
|
+
if (isHidden) {
|
|
365
|
+
props.hiddenByUser = true;
|
|
366
|
+
shouldAssign = true;
|
|
367
|
+
}
|
|
368
|
+
if (shouldAssign) {
|
|
369
|
+
sheet.rowMetadata[currentRowIndex] = props;
|
|
370
|
+
}
|
|
371
|
+
currentRowIndex++;
|
|
372
|
+
}
|
|
373
|
+
detectedRowCount = Math.max(detectedRowCount, currentRowIndex - 1);
|
|
374
|
+
}
|
|
375
|
+
if (detectedRowCount > 0) {
|
|
376
|
+
sheet.rowCount = Math.max(sheet.rowCount ?? 0, detectedRowCount);
|
|
377
|
+
}
|
|
378
|
+
if (detectedColumnCount > 0) {
|
|
379
|
+
sheet.columnCount = Math.max(sheet.columnCount ?? 0, detectedColumnCount);
|
|
380
|
+
}
|
|
381
|
+
sheets.push(sheet);
|
|
382
|
+
sheetContexts.push({ node: table, sheetId: sheet.sheetId, name });
|
|
383
|
+
});
|
|
384
|
+
const sheetNameMap = this.buildSheetNameMap(sheetContexts);
|
|
385
|
+
const spreadsheetNode = this.getSpreadsheetNode();
|
|
386
|
+
const tableViews = this.extractTableViews(sheetContexts);
|
|
387
|
+
const conditionalFormatting = extractConditionalFormats({
|
|
388
|
+
sheetContexts,
|
|
389
|
+
sheetNameMap,
|
|
390
|
+
odsStyles: this.odsStyles,
|
|
391
|
+
getAttributes: this.getAttributes.bind(this),
|
|
392
|
+
parseRangeList: this.parseRangeList.bind(this),
|
|
393
|
+
normalizeExpression: this.normalizeExpression.bind(this),
|
|
394
|
+
normalizeConditionExpression: this.normalizeConditionExpression.bind(this),
|
|
395
|
+
toArray: this.toArray.bind(this),
|
|
396
|
+
});
|
|
397
|
+
const dataValidations = extractDataValidations({
|
|
398
|
+
sheetContexts,
|
|
399
|
+
sheetNameMap,
|
|
400
|
+
spreadsheetNode,
|
|
401
|
+
getAttributes: this.getAttributes.bind(this),
|
|
402
|
+
parseRangeList: this.parseRangeList.bind(this),
|
|
403
|
+
normalizeConditionExpression: this.normalizeConditionExpression.bind(this),
|
|
404
|
+
parseBooleanAttribute: this.parseBooleanAttribute.bind(this),
|
|
405
|
+
mapAlertStyle: this.mapAlertStyle.bind(this),
|
|
406
|
+
toArray: this.toArray.bind(this),
|
|
407
|
+
});
|
|
408
|
+
const namedRanges = this.extractNamedRanges(sheetContexts);
|
|
409
|
+
const { drawings, charts } = this.extractEmbeddedObjects(sheetContexts, sheetNameMap);
|
|
410
|
+
// Extract LibreOffice sparklines (calcext:sparkline-groups)
|
|
411
|
+
this.sparklinesBySheet = extractSparklines({
|
|
412
|
+
sheetContexts,
|
|
413
|
+
sheetNameMap,
|
|
414
|
+
getAttributes: this.getAttributes.bind(this),
|
|
415
|
+
toArray: this.toArray.bind(this),
|
|
416
|
+
});
|
|
417
|
+
const mergesMap = this.extractMerges(sheetContexts);
|
|
418
|
+
this.extractOutlineGroups(sheets);
|
|
419
|
+
const sheetSettings = this.extractSheetSettings(sheetContexts);
|
|
420
|
+
const basicFilters = this.extractBasicFilters(sheetContexts, sheetNameMap, spreadsheetNode);
|
|
421
|
+
for (const sheet of sheets) {
|
|
422
|
+
sheet.merges = mergesMap.get(sheet.sheetId) ?? sheet.merges ?? [];
|
|
423
|
+
const settings = sheetSettings.get(sheet.sheetId);
|
|
424
|
+
if (settings) {
|
|
425
|
+
if (settings.frozenRowCount !== undefined) {
|
|
426
|
+
sheet.frozenRowCount = settings.frozenRowCount;
|
|
427
|
+
}
|
|
428
|
+
if (settings.frozenColumnCount !== undefined) {
|
|
429
|
+
sheet.frozenColumnCount = settings.frozenColumnCount;
|
|
430
|
+
}
|
|
431
|
+
if (settings.showGridLines !== undefined) {
|
|
432
|
+
sheet.showGridLines = settings.showGridLines;
|
|
433
|
+
}
|
|
434
|
+
if (settings.tabColor) {
|
|
435
|
+
sheet.tabColor = settings.tabColor;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const filter = basicFilters.get(sheet.sheetId);
|
|
439
|
+
if (filter) {
|
|
440
|
+
sheet.basicFilter = filter;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
sheets,
|
|
445
|
+
tables: tableViews,
|
|
446
|
+
conditionalFormatting,
|
|
447
|
+
dataValidations,
|
|
448
|
+
namedRanges,
|
|
449
|
+
drawings,
|
|
450
|
+
charts,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
extractTableViews(sheetContexts) {
|
|
454
|
+
if (!sheetContexts.length)
|
|
455
|
+
return [];
|
|
456
|
+
const spreadsheet = this.getSpreadsheetNode();
|
|
457
|
+
if (!spreadsheet)
|
|
458
|
+
return [];
|
|
459
|
+
const sheetNameMap = this.buildSheetNameMap(sheetContexts);
|
|
460
|
+
const tableViews = [];
|
|
461
|
+
const globalRanges = this.toArray(get(spreadsheet, "table:database-ranges.table:database-range") ||
|
|
462
|
+
get(spreadsheet, "database-ranges.database-range"));
|
|
463
|
+
for (const node of globalRanges) {
|
|
464
|
+
const tableView = this.convertDatabaseRangeNode(node, sheetNameMap, undefined, sheetContexts);
|
|
465
|
+
if (tableView) {
|
|
466
|
+
tableViews.push(tableView);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (const context of sheetContexts) {
|
|
470
|
+
const inlineRanges = this.toArray(get(context.node, "table:database-range") ||
|
|
471
|
+
get(context.node, "database-range"));
|
|
472
|
+
for (const node of inlineRanges) {
|
|
473
|
+
const tableView = this.convertDatabaseRangeNode(node, sheetNameMap, context, sheetContexts);
|
|
474
|
+
if (tableView) {
|
|
475
|
+
tableViews.push(tableView);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return tableViews;
|
|
480
|
+
}
|
|
481
|
+
convertDatabaseRangeNode(node, sheetNameMap, context, sheetContexts) {
|
|
482
|
+
const attrs = this.getAttributes(node);
|
|
483
|
+
if (!attrs)
|
|
484
|
+
return undefined;
|
|
485
|
+
const rangeAttr = attrs["@_table:target-range-address"] ||
|
|
486
|
+
attrs["@_target-range-address"] ||
|
|
487
|
+
attrs["@_table:cell-range-address"];
|
|
488
|
+
const ranges = this.parseRangeList(rangeAttr, sheetNameMap, context);
|
|
489
|
+
if (!ranges.length)
|
|
490
|
+
return undefined;
|
|
491
|
+
const [range] = ranges;
|
|
492
|
+
const title = attrs["@_table:display-label"] ||
|
|
493
|
+
attrs["@_table:name"] ||
|
|
494
|
+
attrs["@_name"] ||
|
|
495
|
+
`Table${range.sheetId}`;
|
|
496
|
+
const tableView = {
|
|
497
|
+
id: attrs["@_table:id"] || attrs["@_id"] || title,
|
|
498
|
+
sheetId: range.sheetId,
|
|
499
|
+
title,
|
|
500
|
+
range,
|
|
501
|
+
};
|
|
502
|
+
const headerRow = this.parseBooleanAttribute(attrs["@_table:contains-header"] || attrs["@_contains-header"]);
|
|
503
|
+
if (headerRow !== undefined) {
|
|
504
|
+
tableView.headerRow = headerRow;
|
|
505
|
+
}
|
|
506
|
+
const totalRow = this.parseBooleanAttribute(attrs["@_table:contains-total-row"] || attrs["@_contains-total-row"]);
|
|
507
|
+
if (totalRow !== undefined) {
|
|
508
|
+
tableView.totalRow = totalRow;
|
|
509
|
+
}
|
|
510
|
+
const filterButton = this.parseBooleanAttribute(attrs["@_table:display-filter-buttons"] ||
|
|
511
|
+
attrs["@_display-filter-buttons"]);
|
|
512
|
+
if (filterButton !== undefined) {
|
|
513
|
+
tableView.filterButton = filterButton;
|
|
514
|
+
}
|
|
515
|
+
const showRowStripes = this.parseBooleanAttribute(attrs["@_table:use-banding-rows"] || attrs["@_use-banding-rows"]);
|
|
516
|
+
if (showRowStripes !== undefined) {
|
|
517
|
+
tableView.showRowStripes = showRowStripes;
|
|
518
|
+
}
|
|
519
|
+
const showColumnStripes = this.parseBooleanAttribute(attrs["@_table:use-banding-columns"] || attrs["@_use-banding-columns"]);
|
|
520
|
+
if (showColumnStripes !== undefined) {
|
|
521
|
+
tableView.showColumnStripes = showColumnStripes;
|
|
522
|
+
}
|
|
523
|
+
const effectiveContext = context ??
|
|
524
|
+
sheetContexts?.find((entry) => entry.sheetId === range.sheetId);
|
|
525
|
+
const columns = this.extractTableColumnsFromHeader(range, effectiveContext, tableView.headerRow !== false);
|
|
526
|
+
if (columns?.length) {
|
|
527
|
+
tableView.columns = columns;
|
|
528
|
+
}
|
|
529
|
+
return tableView;
|
|
530
|
+
}
|
|
531
|
+
extractNamedRanges(sheetContexts) {
|
|
532
|
+
const spreadsheet = this.getSpreadsheetNode();
|
|
533
|
+
if (!spreadsheet)
|
|
534
|
+
return [];
|
|
535
|
+
const sheetNameMap = this.buildSheetNameMap(sheetContexts);
|
|
536
|
+
const namedRanges = [];
|
|
537
|
+
const processContainer = (container, context) => {
|
|
538
|
+
if (!container)
|
|
539
|
+
return;
|
|
540
|
+
const rangeNodes = this.toArray(get(container, "table:named-range") || get(container, "named-range"));
|
|
541
|
+
for (const node of rangeNodes) {
|
|
542
|
+
const attrs = this.getAttributes(node);
|
|
543
|
+
if (!attrs)
|
|
544
|
+
continue;
|
|
545
|
+
const name = attrs["@_table:name"] ||
|
|
546
|
+
attrs["@_name"] ||
|
|
547
|
+
attrs["@_table:display-name"];
|
|
548
|
+
if (!name)
|
|
549
|
+
continue;
|
|
550
|
+
const addressAttr = attrs["@_table:cell-range-address"] ||
|
|
551
|
+
attrs["@_cell-range-address"] ||
|
|
552
|
+
attrs["@_table:base-cell-address"] ||
|
|
553
|
+
attrs["@_base-cell-address"];
|
|
554
|
+
const ranges = this.parseRangeList(addressAttr, sheetNameMap, context);
|
|
555
|
+
for (const range of ranges) {
|
|
556
|
+
namedRanges.push({
|
|
557
|
+
namedRangeId: uuid(),
|
|
558
|
+
name,
|
|
559
|
+
range,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const expressionNodes = this.toArray(get(container, "table:named-expression") ||
|
|
564
|
+
get(container, "named-expression"));
|
|
565
|
+
for (const node of expressionNodes) {
|
|
566
|
+
const attrs = this.getAttributes(node);
|
|
567
|
+
if (!attrs)
|
|
568
|
+
continue;
|
|
569
|
+
const name = attrs["@_table:name"] ||
|
|
570
|
+
attrs["@_name"] ||
|
|
571
|
+
attrs["@_table:display-name"];
|
|
572
|
+
if (!name)
|
|
573
|
+
continue;
|
|
574
|
+
const expressionAttr = attrs["@_table:expression"] || attrs["@_expression"];
|
|
575
|
+
const excelFormula = this.normalizeExpression(expressionAttr);
|
|
576
|
+
if (excelFormula) {
|
|
577
|
+
namedRanges.push({
|
|
578
|
+
namedRangeId: uuid(),
|
|
579
|
+
name,
|
|
580
|
+
value: excelFormula,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const globalContainers = this.toArray(get(spreadsheet, "table:named-expressions") ||
|
|
586
|
+
get(spreadsheet, "named-expressions"));
|
|
587
|
+
for (const container of globalContainers) {
|
|
588
|
+
processContainer(container);
|
|
589
|
+
}
|
|
590
|
+
for (const context of sheetContexts) {
|
|
591
|
+
const containers = this.toArray(get(context.node, "table:named-expressions") ||
|
|
592
|
+
get(context.node, "named-expressions"));
|
|
593
|
+
for (const container of containers) {
|
|
594
|
+
processContainer(container, context);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return namedRanges;
|
|
598
|
+
}
|
|
599
|
+
extractBasicFilters(sheetContexts, sheetNameMap, spreadsheetNode) {
|
|
600
|
+
const filters = new Map();
|
|
601
|
+
if (!sheetContexts.length) {
|
|
602
|
+
return filters;
|
|
603
|
+
}
|
|
604
|
+
const processNode = (node, context) => {
|
|
605
|
+
if (!node)
|
|
606
|
+
return;
|
|
607
|
+
const filterNode = get(node, "table:filter") || get(node, "filter");
|
|
608
|
+
if (!filterNode)
|
|
609
|
+
return;
|
|
610
|
+
const attrs = this.getAttributes(node);
|
|
611
|
+
if (!attrs)
|
|
612
|
+
return;
|
|
613
|
+
const rangeAttr = attrs["@_table:target-range-address"] ||
|
|
614
|
+
attrs["@_target-range-address"] ||
|
|
615
|
+
attrs["@_table:cell-range-address"] ||
|
|
616
|
+
attrs["@_cell-range-address"];
|
|
617
|
+
const ranges = this.parseRangeList(rangeAttr, sheetNameMap, context);
|
|
618
|
+
if (!ranges.length)
|
|
619
|
+
return;
|
|
620
|
+
const filterSpecs = this.convertFilterNodeToSpecs(filterNode);
|
|
621
|
+
if (!filterSpecs.length)
|
|
622
|
+
return;
|
|
623
|
+
const [range] = ranges;
|
|
624
|
+
const filterView = {
|
|
625
|
+
id: attrs["@_table:name"] || attrs["@_name"] || uuid(),
|
|
626
|
+
title: attrs["@_table:display-label"] ||
|
|
627
|
+
attrs["@_table:name"] ||
|
|
628
|
+
attrs["@_name"],
|
|
629
|
+
range,
|
|
630
|
+
filterSpecs,
|
|
631
|
+
};
|
|
632
|
+
filters.set(range.sheetId, filterView);
|
|
633
|
+
};
|
|
634
|
+
if (spreadsheetNode) {
|
|
635
|
+
const globalRanges = this.toArray(get(spreadsheetNode, "table:database-ranges.table:database-range") ||
|
|
636
|
+
get(spreadsheetNode, "database-ranges.database-range"));
|
|
637
|
+
for (const node of globalRanges) {
|
|
638
|
+
processNode(node);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
for (const context of sheetContexts) {
|
|
642
|
+
const inlineRanges = this.toArray(get(context.node, "table:database-range") ||
|
|
643
|
+
get(context.node, "database-range"));
|
|
644
|
+
for (const node of inlineRanges) {
|
|
645
|
+
processNode(node, context);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return filters;
|
|
649
|
+
}
|
|
650
|
+
convertFilterNodeToSpecs(filterNode) {
|
|
651
|
+
const specMap = new Map();
|
|
652
|
+
const addConditions = (container) => {
|
|
653
|
+
const conditions = this.toArray(get(container, "table:filter-condition") ||
|
|
654
|
+
get(container, "filter-condition"));
|
|
655
|
+
for (const condition of conditions) {
|
|
656
|
+
this.applyFilterCondition(condition, specMap);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
addConditions(filterNode);
|
|
660
|
+
const andNodes = this.toArray(get(filterNode, "table:filter-and") || get(filterNode, "filter-and"));
|
|
661
|
+
for (const node of andNodes) {
|
|
662
|
+
addConditions(node);
|
|
663
|
+
}
|
|
664
|
+
const orNodes = this.toArray(get(filterNode, "table:filter-or") || get(filterNode, "filter-or"));
|
|
665
|
+
for (const node of orNodes) {
|
|
666
|
+
addConditions(node);
|
|
667
|
+
}
|
|
668
|
+
return Array.from(specMap.values()).sort((a, b) => a.columnIndex - b.columnIndex);
|
|
669
|
+
}
|
|
670
|
+
applyFilterCondition(node, specMap) {
|
|
671
|
+
const attrs = this.getAttributes(node);
|
|
672
|
+
if (!attrs)
|
|
673
|
+
return;
|
|
674
|
+
const field = attrs["@_table:field-number"] ||
|
|
675
|
+
attrs["@_field-number"] ||
|
|
676
|
+
attrs["@_table:column-number"] ||
|
|
677
|
+
attrs["@_column-number"];
|
|
678
|
+
if (field === undefined)
|
|
679
|
+
return;
|
|
680
|
+
const columnIndex = parseInt(field, 10);
|
|
681
|
+
if (isNaN(columnIndex))
|
|
682
|
+
return;
|
|
683
|
+
const operator = String(attrs["@_table:operator"] ?? attrs["@_operator"] ?? "=");
|
|
684
|
+
const value = attrs["@_table:value"] ?? attrs["@_value"];
|
|
685
|
+
const value2 = attrs["@_table:value2"] ?? attrs["@_value2"];
|
|
686
|
+
let spec = specMap.get(columnIndex);
|
|
687
|
+
if (!spec) {
|
|
688
|
+
spec = {
|
|
689
|
+
columnIndex,
|
|
690
|
+
filterCriteria: {},
|
|
691
|
+
};
|
|
692
|
+
specMap.set(columnIndex, spec);
|
|
693
|
+
}
|
|
694
|
+
// A non-"=" operator is a CONDITION filter (greater-than / contains /
|
|
695
|
+
// between / blank / …). Map it back to filterCriteria.condition so it
|
|
696
|
+
// survives the round-trip instead of being flattened into a value list.
|
|
697
|
+
const conditionType = ODS_OPERATOR_TO_CONDITION[operator];
|
|
698
|
+
if (conditionType) {
|
|
699
|
+
const conditionValues = [];
|
|
700
|
+
if (value !== undefined) {
|
|
701
|
+
conditionValues.push({ userEnteredValue: String(value) });
|
|
702
|
+
}
|
|
703
|
+
if (value2 !== undefined) {
|
|
704
|
+
conditionValues.push({ userEnteredValue: String(value2) });
|
|
705
|
+
}
|
|
706
|
+
spec.filterCriteria.condition = {
|
|
707
|
+
type: conditionType,
|
|
708
|
+
values: conditionValues,
|
|
709
|
+
};
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// operator "=" (or unknown) -> value-list entry.
|
|
713
|
+
if (value === undefined)
|
|
714
|
+
return;
|
|
715
|
+
if (!spec.filterCriteria.visibleValues) {
|
|
716
|
+
spec.filterCriteria.visibleValues = [];
|
|
717
|
+
}
|
|
718
|
+
spec.filterCriteria.visibleValues.push(String(value));
|
|
719
|
+
}
|
|
720
|
+
extractTextContent(node) {
|
|
721
|
+
if (node === null || node === undefined)
|
|
722
|
+
return "";
|
|
723
|
+
const primitiveTypes = ["string", "number", "boolean"];
|
|
724
|
+
if (primitiveTypes.includes(typeof node)) {
|
|
725
|
+
return String(node);
|
|
726
|
+
}
|
|
727
|
+
if (Array.isArray(node)) {
|
|
728
|
+
return node.map((entry) => this.extractTextContent(entry)).join("");
|
|
729
|
+
}
|
|
730
|
+
let result = "";
|
|
731
|
+
if (node["text:s"]) {
|
|
732
|
+
result += this.extractSpaceNodes(node["text:s"], node);
|
|
733
|
+
}
|
|
734
|
+
if (node["text:line-break"]) {
|
|
735
|
+
const breaks = this.toArray(node["text:line-break"]);
|
|
736
|
+
result += "\n".repeat(breaks.length || 1);
|
|
737
|
+
}
|
|
738
|
+
if (node["text:span"]) {
|
|
739
|
+
result += this.extractTextContent(node["text:span"]);
|
|
740
|
+
}
|
|
741
|
+
if (node["text:a"]) {
|
|
742
|
+
result += this.extractTextContent(node["text:a"]);
|
|
743
|
+
}
|
|
744
|
+
if (node["text:p"]) {
|
|
745
|
+
result += this.extractTextContent(node["text:p"]);
|
|
746
|
+
}
|
|
747
|
+
if (node["#text"] !== undefined && node["#text"] !== null) {
|
|
748
|
+
const textValue = node["#text"];
|
|
749
|
+
if (typeof textValue === "string" ||
|
|
750
|
+
typeof textValue === "number" ||
|
|
751
|
+
typeof textValue === "boolean") {
|
|
752
|
+
result += String(textValue);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
extractTextContentWithRuns(node) {
|
|
758
|
+
let text = "";
|
|
759
|
+
const runs = [];
|
|
760
|
+
const hasFormat = (format) => !!format && Object.keys(format).length > 0;
|
|
761
|
+
const mergeFormat = (base, next) => (base || next ? { ...(base ?? {}), ...(next ?? {}) } : undefined);
|
|
762
|
+
const appendText = (value) => {
|
|
763
|
+
if (typeof value === "string" ||
|
|
764
|
+
typeof value === "number" ||
|
|
765
|
+
typeof value === "boolean") {
|
|
766
|
+
text += String(value);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
const walk = (value, inherited) => {
|
|
770
|
+
if (value === null || value === undefined)
|
|
771
|
+
return;
|
|
772
|
+
if (Array.isArray(value)) {
|
|
773
|
+
value.forEach((entry) => walk(entry, inherited));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (typeof value === "string" ||
|
|
777
|
+
typeof value === "number" ||
|
|
778
|
+
typeof value === "boolean") {
|
|
779
|
+
appendText(value);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (value["text:s"]) {
|
|
783
|
+
text += this.extractSpaceNodes(value["text:s"], value);
|
|
784
|
+
}
|
|
785
|
+
if (value["text:line-break"]) {
|
|
786
|
+
const breaks = this.toArray(value["text:line-break"]);
|
|
787
|
+
text += "\n".repeat(breaks.length || 1);
|
|
788
|
+
}
|
|
789
|
+
if (value["#text"] !== undefined && value["#text"] !== null) {
|
|
790
|
+
appendText(value["#text"]);
|
|
791
|
+
}
|
|
792
|
+
const spanNodes = value["text:span"];
|
|
793
|
+
if (spanNodes) {
|
|
794
|
+
for (const span of this.toArray(spanNodes)) {
|
|
795
|
+
const attrs = this.getAttributes(span) ?? {};
|
|
796
|
+
const styleName = attrs["@_text:style-name"] || attrs["@_style-name"];
|
|
797
|
+
const spanFormat = mergeFormat(inherited, this.odsStyles.getTextFormat(styleName));
|
|
798
|
+
const startIndex = text.length;
|
|
799
|
+
walk(span, spanFormat);
|
|
800
|
+
if (hasFormat(spanFormat) && text.length > startIndex) {
|
|
801
|
+
const format = spanFormat;
|
|
802
|
+
runs.push({
|
|
803
|
+
startIndex,
|
|
804
|
+
endIndex: text.length,
|
|
805
|
+
format,
|
|
806
|
+
nodeType: "text",
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (value["text:a"]) {
|
|
812
|
+
walk(value["text:a"], inherited);
|
|
813
|
+
}
|
|
814
|
+
if (value["text:p"]) {
|
|
815
|
+
walk(value["text:p"], inherited);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
walk(node);
|
|
819
|
+
return { text, runs };
|
|
820
|
+
}
|
|
821
|
+
normalizeInternalHyperlinkLocation(href) {
|
|
822
|
+
let location = href.trim();
|
|
823
|
+
if (location.startsWith("#")) {
|
|
824
|
+
location = location.slice(1);
|
|
825
|
+
}
|
|
826
|
+
else if (location.startsWith("sheet://")) {
|
|
827
|
+
location = location.slice("sheet://".length);
|
|
828
|
+
}
|
|
829
|
+
else if (location.startsWith("vnd.sun.star.sheet:")) {
|
|
830
|
+
location = location.slice("vnd.sun.star.sheet:".length);
|
|
831
|
+
}
|
|
832
|
+
if (location.includes("!")) {
|
|
833
|
+
return location;
|
|
834
|
+
}
|
|
835
|
+
const match = location.match(/^(.*)\.([A-Za-z]+[0-9].*)$/);
|
|
836
|
+
if (match) {
|
|
837
|
+
return `${match[1]}!${match[2]}`;
|
|
838
|
+
}
|
|
839
|
+
return location;
|
|
840
|
+
}
|
|
841
|
+
extractHyperlinkValue(node) {
|
|
842
|
+
if (node === null || node === undefined)
|
|
843
|
+
return undefined;
|
|
844
|
+
if (Array.isArray(node)) {
|
|
845
|
+
for (const entry of node) {
|
|
846
|
+
const result = this.extractHyperlinkValue(entry);
|
|
847
|
+
if (result)
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
return undefined;
|
|
851
|
+
}
|
|
852
|
+
if (typeof node !== "object") {
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
if (node["text:a"]) {
|
|
856
|
+
const anchors = Array.isArray(node["text:a"])
|
|
857
|
+
? node["text:a"]
|
|
858
|
+
: [node["text:a"]];
|
|
859
|
+
for (const anchor of anchors) {
|
|
860
|
+
const attrs = anchor?.[":@"] || anchor;
|
|
861
|
+
const href = attrs?.["@_xlink:href"] || attrs?.["@_href"];
|
|
862
|
+
if (!href) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const display = this.extractTextContent(anchor);
|
|
866
|
+
if (href.startsWith("#") ||
|
|
867
|
+
href.startsWith("sheet://") ||
|
|
868
|
+
href.startsWith("vnd.sun.star.sheet:")) {
|
|
869
|
+
return {
|
|
870
|
+
kind: "internal",
|
|
871
|
+
location: this.normalizeInternalHyperlinkLocation(href),
|
|
872
|
+
...(display ? { display } : {}),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
kind: "external",
|
|
877
|
+
url: href,
|
|
878
|
+
...(display ? { display } : {}),
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
for (const value of Object.values(node)) {
|
|
883
|
+
const result = this.extractHyperlinkValue(value);
|
|
884
|
+
if (result)
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
return undefined;
|
|
888
|
+
}
|
|
889
|
+
extractSpaceNodes(node, fallback) {
|
|
890
|
+
const nodes = this.toArray(node);
|
|
891
|
+
if (!nodes.length) {
|
|
892
|
+
const fallbackAttrs = this.getAttributes(fallback);
|
|
893
|
+
if (!fallbackAttrs)
|
|
894
|
+
return " ";
|
|
895
|
+
const repeat = this.getSpaceRepeatCount(fallbackAttrs);
|
|
896
|
+
return " ".repeat(repeat);
|
|
897
|
+
}
|
|
898
|
+
return nodes
|
|
899
|
+
.map((entry) => {
|
|
900
|
+
const attrs = this.getAttributes(entry);
|
|
901
|
+
const repeat = this.getSpaceRepeatCount(attrs);
|
|
902
|
+
return " ".repeat(repeat);
|
|
903
|
+
})
|
|
904
|
+
.join("");
|
|
905
|
+
}
|
|
906
|
+
getSpaceRepeatCount(attrs) {
|
|
907
|
+
if (!attrs)
|
|
908
|
+
return 1;
|
|
909
|
+
const rawCount = attrs["@_text:c"] || attrs["@_c"] || attrs["@_text:count"];
|
|
910
|
+
if (rawCount === undefined) {
|
|
911
|
+
return 1;
|
|
912
|
+
}
|
|
913
|
+
const count = parseInt(rawCount, 10);
|
|
914
|
+
if (isNaN(count) || count < 0) {
|
|
915
|
+
return 1;
|
|
916
|
+
}
|
|
917
|
+
return Math.max(1, count + 1);
|
|
918
|
+
}
|
|
919
|
+
extractEmbeddedObjects(sheetContexts, sheetNameMap) {
|
|
920
|
+
const tables = this.getOrderedTableWrappers();
|
|
921
|
+
if (!tables.length || !sheetContexts.length) {
|
|
922
|
+
return { drawings: [], charts: [] };
|
|
923
|
+
}
|
|
924
|
+
const drawings = [];
|
|
925
|
+
const charts = [];
|
|
926
|
+
const chartAnchors = new Set();
|
|
927
|
+
const imageCache = new Map();
|
|
928
|
+
const tableCount = Math.min(tables.length, sheetContexts.length);
|
|
929
|
+
for (let sheetIndex = 0; sheetIndex < tableCount; sheetIndex++) {
|
|
930
|
+
const tableWrapper = tables[sheetIndex];
|
|
931
|
+
const tableNameKey = tableWrapper["table:table"]
|
|
932
|
+
? "table:table"
|
|
933
|
+
: "table";
|
|
934
|
+
const tableChildren = tableWrapper[tableNameKey];
|
|
935
|
+
if (!tableChildren)
|
|
936
|
+
continue;
|
|
937
|
+
const rows = tableChildren.filter((child) => child["table:table-row"] || child["table-row"]);
|
|
938
|
+
let currentRowIndex = 1;
|
|
939
|
+
const sheetContext = sheetContexts[sheetIndex];
|
|
940
|
+
const sheetId = sheetContext?.sheetId ?? sheetIndex + 1;
|
|
941
|
+
for (const rowWrapper of rows) {
|
|
942
|
+
const rowNameKey = rowWrapper["table:table-row"]
|
|
943
|
+
? "table:table-row"
|
|
944
|
+
: "table-row";
|
|
945
|
+
const rowChildren = rowWrapper[rowNameKey];
|
|
946
|
+
const rowAttrs = rowWrapper[":@"] || {};
|
|
947
|
+
const rowsRepeated = parseInt(rowAttrs["@_table:number-rows-repeated"] ||
|
|
948
|
+
rowAttrs["@_number-rows-repeated"] ||
|
|
949
|
+
"1", 10);
|
|
950
|
+
const effectiveRepeats = Math.min(rowsRepeated, 10000);
|
|
951
|
+
for (let repeat = 0; repeat < effectiveRepeats; repeat++) {
|
|
952
|
+
let columnIndex = 1;
|
|
953
|
+
const normalizedRowChildren = rowChildren
|
|
954
|
+
? this.expandRowCells(rowChildren)
|
|
955
|
+
: null;
|
|
956
|
+
if (normalizedRowChildren) {
|
|
957
|
+
for (const cellWrapper of normalizedRowChildren) {
|
|
958
|
+
const isCell = !!cellWrapper["table:table-cell"];
|
|
959
|
+
const isCovered = !!cellWrapper["table:covered-table-cell"];
|
|
960
|
+
if (!isCell && !isCovered)
|
|
961
|
+
continue;
|
|
962
|
+
const cellKey = isCell
|
|
963
|
+
? "table:table-cell"
|
|
964
|
+
: "table:covered-table-cell";
|
|
965
|
+
const cellChildren = cellWrapper[cellKey];
|
|
966
|
+
const cellAttrs = cellWrapper[":@"] || {};
|
|
967
|
+
const colsRepeated = parseInt(cellAttrs["@_table:number-columns-repeated"] ||
|
|
968
|
+
cellAttrs["@_number-columns-repeated"] ||
|
|
969
|
+
"1", 10);
|
|
970
|
+
if (isCovered) {
|
|
971
|
+
columnIndex += colsRepeated;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
const frameNodes = this.collectFrameNodes(cellChildren, cellAttrs);
|
|
975
|
+
if (frameNodes.length) {
|
|
976
|
+
for (let c = 0; c < colsRepeated; c++) {
|
|
977
|
+
const anchorColumnIndex = columnIndex + c;
|
|
978
|
+
for (const { frame, cellAttrs: frameCellAttrs, } of frameNodes) {
|
|
979
|
+
const chart = this.convertFrameToEmbeddedChart(frame, sheetContext, sheetId, currentRowIndex, anchorColumnIndex, sheetNameMap, frameCellAttrs);
|
|
980
|
+
if (chart) {
|
|
981
|
+
chartAnchors.add(this.buildAnchorKey(sheetId, currentRowIndex, anchorColumnIndex));
|
|
982
|
+
charts.push(chart);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const anchorKey = this.buildAnchorKey(sheetId, currentRowIndex, anchorColumnIndex);
|
|
986
|
+
if (chartAnchors.has(anchorKey)) {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const embedded = this.convertFrameToEmbeddedObject(frame, sheetId, currentRowIndex, anchorColumnIndex, imageCache, frameCellAttrs);
|
|
990
|
+
if (embedded) {
|
|
991
|
+
drawings.push(embedded);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
columnIndex += colsRepeated;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
currentRowIndex++;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return { drawings, charts };
|
|
1004
|
+
}
|
|
1005
|
+
buildAnchorKey(sheetId, rowIndex, columnIndex) {
|
|
1006
|
+
return `${sheetId}:${rowIndex}:${columnIndex}`;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Walk the ordered content XML for each table and assign outline
|
|
1010
|
+
* level + collapsed/hiddenByGroup state on `rowMetadata` and
|
|
1011
|
+
* `columnMetadata`. ODS encodes outline as `<table:table-row-group>`
|
|
1012
|
+
* / `<table:table-column-group>` wrappers (nest for level > 1); a
|
|
1013
|
+
* group is collapsed when its `table:display="false"`. Cells inside
|
|
1014
|
+
* a collapsed wrapper get `hiddenByGroup`; the summary cell (per
|
|
1015
|
+
* `outlinePr.summaryBelow`/`summaryRight`, default true = below /
|
|
1016
|
+
* right of the group) gets `collapsed = true`.
|
|
1017
|
+
*/
|
|
1018
|
+
extractOutlineGroups(sheets) {
|
|
1019
|
+
if (!this.contentXmlOrdered || !sheets.length)
|
|
1020
|
+
return;
|
|
1021
|
+
const docContent = this.contentXmlOrdered[0]?.["office:document-content"] ||
|
|
1022
|
+
this.contentXmlOrdered[0]?.["document-content"];
|
|
1023
|
+
if (!docContent)
|
|
1024
|
+
return;
|
|
1025
|
+
const body = getChildren(docContent, "office:body") || getChildren(docContent, "body");
|
|
1026
|
+
if (!body)
|
|
1027
|
+
return;
|
|
1028
|
+
const spreadsheet = getChildren(body, "office:spreadsheet") ||
|
|
1029
|
+
getChildren(body, "spreadsheet");
|
|
1030
|
+
if (!spreadsheet)
|
|
1031
|
+
return;
|
|
1032
|
+
const tables = spreadsheet.filter((child) => child["table:table"] || child["table"]);
|
|
1033
|
+
const tableCount = Math.min(tables.length, sheets.length);
|
|
1034
|
+
for (let sheetIndex = 0; sheetIndex < tableCount; sheetIndex++) {
|
|
1035
|
+
const tableWrapper = tables[sheetIndex];
|
|
1036
|
+
const tableNameKey = tableWrapper["table:table"]
|
|
1037
|
+
? "table:table"
|
|
1038
|
+
: "table";
|
|
1039
|
+
const tableChildren = tableWrapper[tableNameKey];
|
|
1040
|
+
if (!Array.isArray(tableChildren))
|
|
1041
|
+
continue;
|
|
1042
|
+
const sheet = sheets[sheetIndex];
|
|
1043
|
+
if (!sheet)
|
|
1044
|
+
continue;
|
|
1045
|
+
const summaryBelow = sheet.outlinePr?.summaryBelow !== false;
|
|
1046
|
+
const summaryRight = sheet.outlinePr?.summaryRight !== false;
|
|
1047
|
+
const rowMetadata = (sheet.rowMetadata ?? []);
|
|
1048
|
+
const columnMetadata = (sheet.columnMetadata ?? []);
|
|
1049
|
+
const setMeta = (meta, index, patch) => {
|
|
1050
|
+
if (!meta[index])
|
|
1051
|
+
meta[index] = {};
|
|
1052
|
+
Object.assign(meta[index], patch);
|
|
1053
|
+
};
|
|
1054
|
+
const walk = (nodes, itemTag, groupTag, meta, repeatAttrs, summaryAfter) => {
|
|
1055
|
+
let cursor = 1;
|
|
1056
|
+
const openStack = [];
|
|
1057
|
+
const collapsedDepth = () => openStack.some((g) => g.collapsed);
|
|
1058
|
+
const currentLevel = () => openStack.length;
|
|
1059
|
+
const visit = (children) => {
|
|
1060
|
+
for (const child of children) {
|
|
1061
|
+
if (!child)
|
|
1062
|
+
continue;
|
|
1063
|
+
if (child[groupTag]) {
|
|
1064
|
+
const attrs = child[":@"] || {};
|
|
1065
|
+
const display = attrs["@_table:display"] ?? attrs["@_display"];
|
|
1066
|
+
const collapsed = display === "false" || display === false;
|
|
1067
|
+
const startIndex = cursor;
|
|
1068
|
+
openStack.push({
|
|
1069
|
+
level: openStack.length + 1,
|
|
1070
|
+
startIndex,
|
|
1071
|
+
collapsed,
|
|
1072
|
+
});
|
|
1073
|
+
visit(child[groupTag] || []);
|
|
1074
|
+
const closing = openStack.pop();
|
|
1075
|
+
if (closing.collapsed) {
|
|
1076
|
+
const summaryIndex = summaryAfter
|
|
1077
|
+
? cursor
|
|
1078
|
+
: closing.startIndex - 1;
|
|
1079
|
+
if (summaryIndex >= 1) {
|
|
1080
|
+
setMeta(meta, summaryIndex, { collapsed: true });
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
else if (child[itemTag]) {
|
|
1085
|
+
const attrs = child[":@"] || {};
|
|
1086
|
+
let repeat = 1;
|
|
1087
|
+
for (const attrName of repeatAttrs) {
|
|
1088
|
+
const raw = attrs[`@_${attrName}`];
|
|
1089
|
+
if (raw !== undefined) {
|
|
1090
|
+
repeat = Math.max(1, parseInt(String(raw), 10) || 1);
|
|
1091
|
+
break;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
repeat = Math.min(repeat, 10000);
|
|
1095
|
+
const level = currentLevel();
|
|
1096
|
+
const hidden = collapsedDepth();
|
|
1097
|
+
if (level > 0 || hidden) {
|
|
1098
|
+
for (let r = 0; r < repeat; r++) {
|
|
1099
|
+
const patch = {};
|
|
1100
|
+
if (level > 0)
|
|
1101
|
+
patch.outlineLevel = level;
|
|
1102
|
+
if (hidden)
|
|
1103
|
+
patch.hiddenByGroup = true;
|
|
1104
|
+
setMeta(meta, cursor + r, patch);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
cursor += repeat;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
visit(nodes);
|
|
1112
|
+
};
|
|
1113
|
+
walk(tableChildren, "table:table-column", "table:table-column-group", columnMetadata, ["table:number-columns-repeated", "number-columns-repeated"], summaryRight);
|
|
1114
|
+
walk(tableChildren, "table:table-row", "table:table-row-group", rowMetadata, ["table:number-rows-repeated", "number-rows-repeated"], summaryBelow);
|
|
1115
|
+
if (columnMetadata.length > 0)
|
|
1116
|
+
sheet.columnMetadata = columnMetadata;
|
|
1117
|
+
if (rowMetadata.length > 0)
|
|
1118
|
+
sheet.rowMetadata = rowMetadata;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
extractMerges(sheetContexts) {
|
|
1122
|
+
const merges = new Map();
|
|
1123
|
+
if (!this.contentXmlOrdered || !sheetContexts.length) {
|
|
1124
|
+
return merges;
|
|
1125
|
+
}
|
|
1126
|
+
const docContent = this.contentXmlOrdered[0]?.["office:document-content"] ||
|
|
1127
|
+
this.contentXmlOrdered[0]?.["document-content"];
|
|
1128
|
+
if (!docContent)
|
|
1129
|
+
return merges;
|
|
1130
|
+
const body = getChildren(docContent, "office:body") || getChildren(docContent, "body");
|
|
1131
|
+
if (!body)
|
|
1132
|
+
return merges;
|
|
1133
|
+
const spreadsheet = getChildren(body, "office:spreadsheet") ||
|
|
1134
|
+
getChildren(body, "spreadsheet");
|
|
1135
|
+
if (!spreadsheet)
|
|
1136
|
+
return merges;
|
|
1137
|
+
const tables = spreadsheet.filter((child) => child["table:table"] || child["table"]);
|
|
1138
|
+
const tableCount = Math.min(tables.length, sheetContexts.length);
|
|
1139
|
+
const seen = new Set();
|
|
1140
|
+
for (let sheetIndex = 0; sheetIndex < tableCount; sheetIndex++) {
|
|
1141
|
+
const tableWrapper = tables[sheetIndex];
|
|
1142
|
+
const tableNameKey = tableWrapper["table:table"]
|
|
1143
|
+
? "table:table"
|
|
1144
|
+
: "table";
|
|
1145
|
+
const tableChildren = tableWrapper[tableNameKey];
|
|
1146
|
+
if (!tableChildren)
|
|
1147
|
+
continue;
|
|
1148
|
+
const rows = tableChildren.filter((child) => child["table:table-row"] || child["table-row"]);
|
|
1149
|
+
const sheetId = sheetContexts[sheetIndex]?.sheetId ?? sheetIndex + 1;
|
|
1150
|
+
let currentRowIndex = 1;
|
|
1151
|
+
for (const rowWrapper of rows) {
|
|
1152
|
+
const rowNameKey = rowWrapper["table:table-row"]
|
|
1153
|
+
? "table:table-row"
|
|
1154
|
+
: "table-row";
|
|
1155
|
+
const rowChildren = rowWrapper[rowNameKey];
|
|
1156
|
+
const rowAttrs = rowWrapper[":@"] || {};
|
|
1157
|
+
const rowsRepeated = parseInt(rowAttrs["@_table:number-rows-repeated"] ||
|
|
1158
|
+
rowAttrs["@_number-rows-repeated"] ||
|
|
1159
|
+
"1", 10);
|
|
1160
|
+
const effectiveRepeats = Math.min(rowsRepeated, 10000);
|
|
1161
|
+
for (let repeat = 0; repeat < effectiveRepeats; repeat++) {
|
|
1162
|
+
let columnIndex = 1;
|
|
1163
|
+
if (rowChildren) {
|
|
1164
|
+
for (const cellWrapper of rowChildren) {
|
|
1165
|
+
const isCell = !!cellWrapper["table:table-cell"];
|
|
1166
|
+
const isCovered = !!cellWrapper["table:covered-table-cell"];
|
|
1167
|
+
if (!isCell && !isCovered)
|
|
1168
|
+
continue;
|
|
1169
|
+
const cellKey = isCell
|
|
1170
|
+
? "table:table-cell"
|
|
1171
|
+
: "table:covered-table-cell";
|
|
1172
|
+
const cellAttrs = cellWrapper[":@"] || {};
|
|
1173
|
+
const colsRepeated = parseInt(cellAttrs["@_table:number-columns-repeated"] ||
|
|
1174
|
+
cellAttrs["@_number-columns-repeated"] ||
|
|
1175
|
+
"1", 10);
|
|
1176
|
+
if (isCovered) {
|
|
1177
|
+
columnIndex += colsRepeated;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
const rowsSpannedRaw = parseInt(cellAttrs["@_table:number-rows-spanned"] ||
|
|
1181
|
+
cellAttrs["@_number-rows-spanned"] ||
|
|
1182
|
+
"1", 10);
|
|
1183
|
+
const colsSpannedRaw = parseInt(cellAttrs["@_table:number-columns-spanned"] ||
|
|
1184
|
+
cellAttrs["@_number-columns-spanned"] ||
|
|
1185
|
+
"1", 10);
|
|
1186
|
+
const rowsSpanned = Math.max(1, rowsSpannedRaw || 1);
|
|
1187
|
+
const colsSpanned = Math.max(1, colsSpannedRaw || 1);
|
|
1188
|
+
if (rowsSpanned > 1 || colsSpanned > 1) {
|
|
1189
|
+
for (let colOffset = 0; colOffset < colsRepeated; colOffset++) {
|
|
1190
|
+
const startColumnIndex = columnIndex + colOffset;
|
|
1191
|
+
const range = {
|
|
1192
|
+
sheetId,
|
|
1193
|
+
startRowIndex: currentRowIndex,
|
|
1194
|
+
endRowIndex: currentRowIndex + rowsSpanned - 1,
|
|
1195
|
+
startColumnIndex,
|
|
1196
|
+
endColumnIndex: startColumnIndex + colsSpanned - 1,
|
|
1197
|
+
};
|
|
1198
|
+
const key = `${range.sheetId}:${range.startRowIndex}:${range.endRowIndex}:${range.startColumnIndex}:${range.endColumnIndex}`;
|
|
1199
|
+
if (!seen.has(key)) {
|
|
1200
|
+
seen.add(key);
|
|
1201
|
+
if (!merges.has(sheetId)) {
|
|
1202
|
+
merges.set(sheetId, []);
|
|
1203
|
+
}
|
|
1204
|
+
merges.get(sheetId).push(range);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
columnIndex += colsRepeated;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
currentRowIndex++;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return merges;
|
|
1216
|
+
}
|
|
1217
|
+
getOrderedTableWrappers() {
|
|
1218
|
+
if (!this.contentXmlOrdered)
|
|
1219
|
+
return [];
|
|
1220
|
+
const docContent = this.contentXmlOrdered[0]?.["office:document-content"] ||
|
|
1221
|
+
this.contentXmlOrdered[0]?.["document-content"];
|
|
1222
|
+
if (!docContent)
|
|
1223
|
+
return [];
|
|
1224
|
+
const body = getChildren(docContent, "office:body") || getChildren(docContent, "body");
|
|
1225
|
+
if (!body)
|
|
1226
|
+
return [];
|
|
1227
|
+
const spreadsheet = getChildren(body, "office:spreadsheet") ||
|
|
1228
|
+
getChildren(body, "spreadsheet");
|
|
1229
|
+
if (!spreadsheet)
|
|
1230
|
+
return [];
|
|
1231
|
+
return spreadsheet.filter((child) => child["table:table"] || child["table"]);
|
|
1232
|
+
}
|
|
1233
|
+
findOrderedTableWrapperForContext(sheetContext, sheetId) {
|
|
1234
|
+
const tables = this.getOrderedTableWrappers();
|
|
1235
|
+
if (!tables.length)
|
|
1236
|
+
return undefined;
|
|
1237
|
+
if (sheetContext?.name) {
|
|
1238
|
+
const normalizedTarget = this.normalizeSheetName(sheetContext.name);
|
|
1239
|
+
for (const tableWrapper of tables) {
|
|
1240
|
+
const attrs = tableWrapper[":@"] || {};
|
|
1241
|
+
const rawName = attrs["@_table:name"] ||
|
|
1242
|
+
attrs["@_name"] ||
|
|
1243
|
+
attrs["@_table:display-name"];
|
|
1244
|
+
const normalizedCandidate = this.normalizeSheetName(rawName);
|
|
1245
|
+
if ((rawName && rawName === sheetContext.name) ||
|
|
1246
|
+
(normalizedCandidate &&
|
|
1247
|
+
normalizedTarget &&
|
|
1248
|
+
normalizedCandidate === normalizedTarget)) {
|
|
1249
|
+
return tableWrapper;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
if (sheetId && tables[sheetId - 1]) {
|
|
1254
|
+
return tables[sheetId - 1];
|
|
1255
|
+
}
|
|
1256
|
+
return tables[0];
|
|
1257
|
+
}
|
|
1258
|
+
extractTableColumnsFromHeader(range, sheetContext, useHeaderRow = true) {
|
|
1259
|
+
const startColumn = Math.max(1, range.startColumnIndex ?? 1);
|
|
1260
|
+
const endColumn = Math.max(startColumn, range.endColumnIndex ?? startColumn);
|
|
1261
|
+
const columnCount = Math.max(1, endColumn - startColumn + 1);
|
|
1262
|
+
const buildDefaultColumns = () => Array.from({ length: columnCount }, (_, index) => ({
|
|
1263
|
+
name: `Column ${index + 1}`,
|
|
1264
|
+
}));
|
|
1265
|
+
if (!useHeaderRow) {
|
|
1266
|
+
return buildDefaultColumns();
|
|
1267
|
+
}
|
|
1268
|
+
const tableWrapper = this.findOrderedTableWrapperForContext(sheetContext, range.sheetId);
|
|
1269
|
+
if (!tableWrapper) {
|
|
1270
|
+
return buildDefaultColumns();
|
|
1271
|
+
}
|
|
1272
|
+
const tableNameKey = tableWrapper["table:table"] ? "table:table" : "table";
|
|
1273
|
+
const tableChildren = tableWrapper[tableNameKey];
|
|
1274
|
+
if (!tableChildren) {
|
|
1275
|
+
return buildDefaultColumns();
|
|
1276
|
+
}
|
|
1277
|
+
const rows = tableChildren.filter((child) => child["table:table-row"] || child["table-row"]);
|
|
1278
|
+
if (!rows.length) {
|
|
1279
|
+
return buildDefaultColumns();
|
|
1280
|
+
}
|
|
1281
|
+
const headerRowIndex = Math.max(1, range.startRowIndex ?? 1);
|
|
1282
|
+
const headerValues = [];
|
|
1283
|
+
let currentRowIndex = 1;
|
|
1284
|
+
outer: for (const rowWrapper of rows) {
|
|
1285
|
+
const rowNameKey = rowWrapper["table:table-row"]
|
|
1286
|
+
? "table:table-row"
|
|
1287
|
+
: "table-row";
|
|
1288
|
+
const rowChildren = rowWrapper[rowNameKey];
|
|
1289
|
+
const rowAttrs = rowWrapper[":@"] || {};
|
|
1290
|
+
const rowsRepeated = parseInt(rowAttrs["@_table:number-rows-repeated"] ||
|
|
1291
|
+
rowAttrs["@_number-rows-repeated"] ||
|
|
1292
|
+
"1", 10);
|
|
1293
|
+
const effectiveRepeats = Math.min(rowsRepeated, 10000);
|
|
1294
|
+
const normalizedRowChildren = rowChildren
|
|
1295
|
+
? this.expandRowCells(rowChildren)
|
|
1296
|
+
: null;
|
|
1297
|
+
for (let repeat = 0; repeat < effectiveRepeats; repeat++) {
|
|
1298
|
+
if (currentRowIndex === headerRowIndex) {
|
|
1299
|
+
let columnIndex = 1;
|
|
1300
|
+
if (normalizedRowChildren) {
|
|
1301
|
+
for (const cellWrapper of normalizedRowChildren) {
|
|
1302
|
+
const isCell = !!cellWrapper["table:table-cell"];
|
|
1303
|
+
const isCovered = !!cellWrapper["table:covered-table-cell"];
|
|
1304
|
+
if (!isCell && !isCovered) {
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
const cellNameKey = isCell
|
|
1308
|
+
? "table:table-cell"
|
|
1309
|
+
: "table:covered-table-cell";
|
|
1310
|
+
const cellChildren = cellWrapper[cellNameKey];
|
|
1311
|
+
const cellAttrs = cellWrapper[":@"] || {};
|
|
1312
|
+
const colsRepeated = parseInt(cellAttrs["@_table:number-columns-repeated"] ||
|
|
1313
|
+
cellAttrs["@_number-columns-repeated"] ||
|
|
1314
|
+
"1", 10);
|
|
1315
|
+
if (isCovered) {
|
|
1316
|
+
columnIndex += colsRepeated;
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
let displayValue = "";
|
|
1320
|
+
if (cellChildren) {
|
|
1321
|
+
const textWrapper = findChild(cellChildren, "text:p") ||
|
|
1322
|
+
findChild(cellChildren, "p");
|
|
1323
|
+
if (textWrapper) {
|
|
1324
|
+
const key = textWrapper["text:p"] ? "text:p" : "p";
|
|
1325
|
+
displayValue = this.extractTextContent(textWrapper[key]);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
displayValue = this.extractTextContent(cellChildren);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
for (let c = 0; c < colsRepeated; c++) {
|
|
1332
|
+
const targetColumnIndex = columnIndex;
|
|
1333
|
+
if (targetColumnIndex >= startColumn &&
|
|
1334
|
+
targetColumnIndex <= endColumn) {
|
|
1335
|
+
const relativeIndex = headerValues.length;
|
|
1336
|
+
const fallbackName = `Column ${relativeIndex + 1}`;
|
|
1337
|
+
const trimmed = displayValue?.trim();
|
|
1338
|
+
headerValues.push(trimmed && trimmed.length ? trimmed : fallbackName);
|
|
1339
|
+
if (headerValues.length >= columnCount) {
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
columnIndex++;
|
|
1344
|
+
}
|
|
1345
|
+
if (headerValues.length >= columnCount) {
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
break outer;
|
|
1351
|
+
}
|
|
1352
|
+
currentRowIndex++;
|
|
1353
|
+
}
|
|
1354
|
+
if (currentRowIndex > headerRowIndex) {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (!headerValues.length) {
|
|
1359
|
+
return buildDefaultColumns();
|
|
1360
|
+
}
|
|
1361
|
+
const columns = [];
|
|
1362
|
+
for (let i = 0; i < columnCount; i++) {
|
|
1363
|
+
const label = headerValues[i];
|
|
1364
|
+
columns.push({
|
|
1365
|
+
name: label && label.trim().length ? label : `Column ${i + 1}`,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
return columns;
|
|
1369
|
+
}
|
|
1370
|
+
collectFrameNodes(cellChildren, cellAttrs) {
|
|
1371
|
+
if (!cellChildren)
|
|
1372
|
+
return [];
|
|
1373
|
+
const children = Array.isArray(cellChildren)
|
|
1374
|
+
? cellChildren
|
|
1375
|
+
: [cellChildren];
|
|
1376
|
+
const frames = [];
|
|
1377
|
+
for (const child of children) {
|
|
1378
|
+
if (!child || typeof child !== "object")
|
|
1379
|
+
continue;
|
|
1380
|
+
const frameNode = child["draw:frame"] || child["frame"];
|
|
1381
|
+
if (!frameNode)
|
|
1382
|
+
continue;
|
|
1383
|
+
if (Array.isArray(frameNode)) {
|
|
1384
|
+
for (const node of frameNode) {
|
|
1385
|
+
frames.push({ frame: node, cellAttrs: child[":@"] || cellAttrs });
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
frames.push({ frame: frameNode, cellAttrs: child[":@"] || cellAttrs });
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return frames;
|
|
1393
|
+
}
|
|
1394
|
+
extractSheetSettings(sheetContexts) {
|
|
1395
|
+
const map = new Map();
|
|
1396
|
+
if (!this.settingsXml || !sheetContexts.length) {
|
|
1397
|
+
return map;
|
|
1398
|
+
}
|
|
1399
|
+
const documentSettings = get(this.settingsXml, "office:document-settings") ||
|
|
1400
|
+
get(this.settingsXml, "document-settings");
|
|
1401
|
+
if (!documentSettings)
|
|
1402
|
+
return map;
|
|
1403
|
+
const settingsNode = get(documentSettings, "office:settings") ||
|
|
1404
|
+
get(documentSettings, "settings");
|
|
1405
|
+
if (!settingsNode)
|
|
1406
|
+
return map;
|
|
1407
|
+
const nameMap = new Map();
|
|
1408
|
+
const traverse = (node) => {
|
|
1409
|
+
if (!node || typeof node !== "object")
|
|
1410
|
+
return;
|
|
1411
|
+
const entries = this.toArray(node["config:config-item-map-entry"] || node["config-item-map-entry"]);
|
|
1412
|
+
for (const entry of entries) {
|
|
1413
|
+
const attrs = this.getAttributes(entry);
|
|
1414
|
+
const entryName = attrs?.["@_config:name"] ||
|
|
1415
|
+
attrs?.["@_name"] ||
|
|
1416
|
+
attrs?.["@_table:name"];
|
|
1417
|
+
if (entryName) {
|
|
1418
|
+
const parsed = this.parseConfigItemEntry(entry);
|
|
1419
|
+
if (parsed) {
|
|
1420
|
+
const existing = nameMap.get(entryName) || {};
|
|
1421
|
+
const combined = { ...existing, ...parsed };
|
|
1422
|
+
nameMap.set(entryName, combined);
|
|
1423
|
+
const normalized = this.normalizeSheetName(entryName);
|
|
1424
|
+
if (normalized) {
|
|
1425
|
+
nameMap.set(normalized, combined);
|
|
1426
|
+
nameMap.set(`'${normalized}'`, combined);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
traverse(entry);
|
|
1431
|
+
}
|
|
1432
|
+
const namedMaps = this.toArray(node["config:config-item-map-named"] || node["config-item-map-named"]);
|
|
1433
|
+
for (const named of namedMaps) {
|
|
1434
|
+
traverse(named);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
const itemSets = this.toArray(get(settingsNode, "config:config-item-set") ||
|
|
1438
|
+
get(settingsNode, "config-item-set"));
|
|
1439
|
+
for (const set of itemSets) {
|
|
1440
|
+
traverse(set);
|
|
1441
|
+
}
|
|
1442
|
+
for (const context of sheetContexts) {
|
|
1443
|
+
const normalized = this.normalizeSheetName(context.name);
|
|
1444
|
+
const settings = nameMap.get(context.name) ||
|
|
1445
|
+
(normalized ? nameMap.get(normalized) : undefined);
|
|
1446
|
+
if (settings) {
|
|
1447
|
+
map.set(context.sheetId, settings);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return map;
|
|
1451
|
+
}
|
|
1452
|
+
parseConfigItemEntry(entry) {
|
|
1453
|
+
const configItems = this.toArray(get(entry, "config:config-item") || get(entry, "config-item"));
|
|
1454
|
+
if (!configItems.length)
|
|
1455
|
+
return undefined;
|
|
1456
|
+
const settings = {};
|
|
1457
|
+
for (const item of configItems) {
|
|
1458
|
+
const attrs = this.getAttributes(item);
|
|
1459
|
+
if (!attrs)
|
|
1460
|
+
continue;
|
|
1461
|
+
const name = attrs["@_config:name"] || attrs["@_name"];
|
|
1462
|
+
if (!name)
|
|
1463
|
+
continue;
|
|
1464
|
+
const value = this.getNodeText(item);
|
|
1465
|
+
if (value === undefined)
|
|
1466
|
+
continue;
|
|
1467
|
+
const normalized = value.trim();
|
|
1468
|
+
if (!normalized.length)
|
|
1469
|
+
continue;
|
|
1470
|
+
if (name === "ShowGrid" || name === "ShowGridLines") {
|
|
1471
|
+
const parsed = this.parseBooleanAttribute(normalized);
|
|
1472
|
+
if (parsed !== undefined) {
|
|
1473
|
+
settings.showGridLines = parsed;
|
|
1474
|
+
}
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
if (name === "FreezeCell") {
|
|
1478
|
+
const freeze = this.parseFreezeCoordinates(normalized);
|
|
1479
|
+
if (freeze) {
|
|
1480
|
+
Object.assign(settings, freeze);
|
|
1481
|
+
}
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (name === "VerticalSplitPosition") {
|
|
1485
|
+
const parsed = this.parseIntegerValue(normalized);
|
|
1486
|
+
if (parsed !== undefined) {
|
|
1487
|
+
settings.frozenRowCount = parsed;
|
|
1488
|
+
}
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
if (name === "HorizontalSplitPosition") {
|
|
1492
|
+
const parsed = this.parseIntegerValue(normalized);
|
|
1493
|
+
if (parsed !== undefined) {
|
|
1494
|
+
settings.frozenColumnCount = parsed;
|
|
1495
|
+
}
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
if (name === "TabColor") {
|
|
1499
|
+
const color = this.normalizeOdsColor(normalized);
|
|
1500
|
+
if (color) {
|
|
1501
|
+
settings.tabColor = color;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return Object.keys(settings).length ? settings : undefined;
|
|
1506
|
+
}
|
|
1507
|
+
parseFreezeCoordinates(value) {
|
|
1508
|
+
const normalized = value.replace(/\$/g, "").trim();
|
|
1509
|
+
if (!normalized)
|
|
1510
|
+
return undefined;
|
|
1511
|
+
const cell = addressToCell(normalized.toUpperCase());
|
|
1512
|
+
if (!cell)
|
|
1513
|
+
return undefined;
|
|
1514
|
+
return {
|
|
1515
|
+
frozenRowCount: Math.max(0, cell.rowIndex - 1),
|
|
1516
|
+
frozenColumnCount: Math.max(0, cell.columnIndex - 1),
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
convertFrameToEmbeddedObject(frameNode, sheetId, rowIndex, columnIndex, imageCache, cellAttrs) {
|
|
1520
|
+
const frameAttrs = this.getAttributes(frameNode);
|
|
1521
|
+
const imageNode = this.findFrameImageNode(frameNode);
|
|
1522
|
+
const imageAttrs = this.getAttributes(imageNode);
|
|
1523
|
+
const imagePath = this.normalizeImagePath(imageAttrs?.["@_xlink:href"] ||
|
|
1524
|
+
imageAttrs?.["@_href"] ||
|
|
1525
|
+
frameAttrs?.["@_xlink:href"] ||
|
|
1526
|
+
frameAttrs?.["@_href"]);
|
|
1527
|
+
if (!imagePath)
|
|
1528
|
+
return undefined;
|
|
1529
|
+
const imageUrl = this.getImageDataUrl(imagePath, imageCache);
|
|
1530
|
+
if (!imageUrl)
|
|
1531
|
+
return undefined;
|
|
1532
|
+
const { overlayPosition, isInsideCell } = this.buildOverlayPlacement(frameAttrs, cellAttrs, rowIndex, columnIndex);
|
|
1533
|
+
const description = frameAttrs?.["@_draw:name"] || cellAttrs?.["@_draw:name"] || undefined;
|
|
1534
|
+
return {
|
|
1535
|
+
embedId: uuid(),
|
|
1536
|
+
spec: {
|
|
1537
|
+
type: "image",
|
|
1538
|
+
imageUrl,
|
|
1539
|
+
description,
|
|
1540
|
+
},
|
|
1541
|
+
position: {
|
|
1542
|
+
sheetId,
|
|
1543
|
+
overlayPosition,
|
|
1544
|
+
},
|
|
1545
|
+
draggable: true,
|
|
1546
|
+
lockAspectRatio: true,
|
|
1547
|
+
isInsideCell,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
convertFrameToEmbeddedChart(frameNode, sheetContext, sheetId, rowIndex, columnIndex, sheetNameMap, cellAttrs) {
|
|
1551
|
+
const frameAttrs = this.getAttributes(frameNode);
|
|
1552
|
+
const inlineChart = this.findInlineChartNode(frameNode);
|
|
1553
|
+
let chartNode = inlineChart;
|
|
1554
|
+
if (!chartNode) {
|
|
1555
|
+
const objectNode = this.findFrameObjectNode(frameNode);
|
|
1556
|
+
const chartFromObject = this.loadChartNodeFromObject(objectNode, frameAttrs);
|
|
1557
|
+
chartNode = chartFromObject;
|
|
1558
|
+
}
|
|
1559
|
+
if (!chartNode)
|
|
1560
|
+
return undefined;
|
|
1561
|
+
const spec = this.convertChartNodeToSpec(chartNode, sheetNameMap, sheetContext);
|
|
1562
|
+
if (!spec)
|
|
1563
|
+
return undefined;
|
|
1564
|
+
const { overlayPosition } = this.buildOverlayPlacement(frameAttrs, cellAttrs, rowIndex, columnIndex);
|
|
1565
|
+
const chartId = uuid();
|
|
1566
|
+
return {
|
|
1567
|
+
chartId,
|
|
1568
|
+
spec,
|
|
1569
|
+
position: {
|
|
1570
|
+
sheetId,
|
|
1571
|
+
overlayPosition,
|
|
1572
|
+
},
|
|
1573
|
+
draggable: true,
|
|
1574
|
+
resizable: true,
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
findInlineChartNode(frameNode) {
|
|
1578
|
+
if (!frameNode)
|
|
1579
|
+
return undefined;
|
|
1580
|
+
if (Array.isArray(frameNode)) {
|
|
1581
|
+
for (const entry of frameNode) {
|
|
1582
|
+
const found = this.findInlineChartNode(entry);
|
|
1583
|
+
if (found)
|
|
1584
|
+
return found;
|
|
1585
|
+
}
|
|
1586
|
+
return undefined;
|
|
1587
|
+
}
|
|
1588
|
+
const chartNode = frameNode["chart:chart"] || frameNode["chart"];
|
|
1589
|
+
if (chartNode) {
|
|
1590
|
+
return Array.isArray(chartNode) ? chartNode[0] : chartNode;
|
|
1591
|
+
}
|
|
1592
|
+
return undefined;
|
|
1593
|
+
}
|
|
1594
|
+
findFrameObjectNode(frameNode) {
|
|
1595
|
+
if (!frameNode)
|
|
1596
|
+
return undefined;
|
|
1597
|
+
if (Array.isArray(frameNode)) {
|
|
1598
|
+
for (const entry of frameNode) {
|
|
1599
|
+
const found = this.findFrameObjectNode(entry);
|
|
1600
|
+
if (found)
|
|
1601
|
+
return found;
|
|
1602
|
+
}
|
|
1603
|
+
return undefined;
|
|
1604
|
+
}
|
|
1605
|
+
const objectNode = frameNode["draw:object"] || frameNode["object"];
|
|
1606
|
+
if (objectNode) {
|
|
1607
|
+
return Array.isArray(objectNode) ? objectNode[0] : objectNode;
|
|
1608
|
+
}
|
|
1609
|
+
return undefined;
|
|
1610
|
+
}
|
|
1611
|
+
findFrameImageNode(frameNode) {
|
|
1612
|
+
if (!frameNode)
|
|
1613
|
+
return undefined;
|
|
1614
|
+
if (Array.isArray(frameNode)) {
|
|
1615
|
+
for (const entry of frameNode) {
|
|
1616
|
+
const found = this.findFrameImageNode(entry);
|
|
1617
|
+
if (found)
|
|
1618
|
+
return found;
|
|
1619
|
+
}
|
|
1620
|
+
return undefined;
|
|
1621
|
+
}
|
|
1622
|
+
const direct = frameNode["draw:image"] || frameNode["image"] || frameNode["draw:object"];
|
|
1623
|
+
if (direct) {
|
|
1624
|
+
return Array.isArray(direct) ? direct[0] : direct;
|
|
1625
|
+
}
|
|
1626
|
+
return undefined;
|
|
1627
|
+
}
|
|
1628
|
+
loadChartNodeFromObject(objectNode, frameAttrs) {
|
|
1629
|
+
if (!objectNode)
|
|
1630
|
+
return undefined;
|
|
1631
|
+
const objectAttrs = this.getAttributes(objectNode);
|
|
1632
|
+
const href = objectAttrs?.["@_xlink:href"] ||
|
|
1633
|
+
objectAttrs?.["@_href"] ||
|
|
1634
|
+
frameAttrs?.["@_xlink:href"] ||
|
|
1635
|
+
frameAttrs?.["@_href"];
|
|
1636
|
+
return this.loadChartNodeFromHref(href);
|
|
1637
|
+
}
|
|
1638
|
+
loadChartNodeFromHref(href) {
|
|
1639
|
+
const path = this.resolveObjectPath(href);
|
|
1640
|
+
if (!path)
|
|
1641
|
+
return undefined;
|
|
1642
|
+
try {
|
|
1643
|
+
const content = this.zipParser.Get(path);
|
|
1644
|
+
const chartDoc = this.xmlParser.parse(content);
|
|
1645
|
+
this.odsStyles.parseContentXml(chartDoc);
|
|
1646
|
+
return this.findChartNodeInDocument(chartDoc);
|
|
1647
|
+
}
|
|
1648
|
+
catch {
|
|
1649
|
+
return undefined;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
resolveObjectPath(href) {
|
|
1653
|
+
if (!href)
|
|
1654
|
+
return undefined;
|
|
1655
|
+
let normalized = href.trim();
|
|
1656
|
+
if (!normalized)
|
|
1657
|
+
return undefined;
|
|
1658
|
+
if (normalized.startsWith("#")) {
|
|
1659
|
+
normalized = normalized.slice(1);
|
|
1660
|
+
}
|
|
1661
|
+
normalized = normalized.replace(/^\.\/+/, "");
|
|
1662
|
+
while (normalized.startsWith("../")) {
|
|
1663
|
+
normalized = normalized.slice(3);
|
|
1664
|
+
}
|
|
1665
|
+
if (!normalized)
|
|
1666
|
+
return undefined;
|
|
1667
|
+
const candidates = new Set();
|
|
1668
|
+
if (normalized.endsWith(".xml")) {
|
|
1669
|
+
candidates.add(normalized);
|
|
1670
|
+
candidates.add(`ObjectReplacements/${normalized}`);
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
candidates.add(`${normalized}/content.xml`);
|
|
1674
|
+
candidates.add(`${normalized}/Content.xml`);
|
|
1675
|
+
candidates.add(`${normalized}.xml`);
|
|
1676
|
+
candidates.add(`ObjectReplacements/${normalized}/content.xml`);
|
|
1677
|
+
candidates.add(`ObjectReplacements/${normalized}/Content.xml`);
|
|
1678
|
+
}
|
|
1679
|
+
for (const candidate of candidates) {
|
|
1680
|
+
if (!candidate)
|
|
1681
|
+
continue;
|
|
1682
|
+
if (this.zipParser.Has(candidate)) {
|
|
1683
|
+
return candidate;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return undefined;
|
|
1687
|
+
}
|
|
1688
|
+
findChartNodeInDocument(doc) {
|
|
1689
|
+
if (!doc)
|
|
1690
|
+
return undefined;
|
|
1691
|
+
const chartNode = get(doc, "office:document-content.office:body.office:chart.chart:chart") ||
|
|
1692
|
+
get(doc, "office:document-content.office:body.chart:chart") ||
|
|
1693
|
+
get(doc, "chart:chart");
|
|
1694
|
+
if (chartNode) {
|
|
1695
|
+
return Array.isArray(chartNode) ? chartNode[0] : chartNode;
|
|
1696
|
+
}
|
|
1697
|
+
return this.findNodeRecursive(doc, "chart:chart");
|
|
1698
|
+
}
|
|
1699
|
+
findNodeRecursive(node, key) {
|
|
1700
|
+
if (!node || typeof node !== "object")
|
|
1701
|
+
return undefined;
|
|
1702
|
+
if (node[key]) {
|
|
1703
|
+
return Array.isArray(node[key]) ? node[key][0] : node[key];
|
|
1704
|
+
}
|
|
1705
|
+
for (const value of Object.values(node)) {
|
|
1706
|
+
if (Array.isArray(value)) {
|
|
1707
|
+
for (const entry of value) {
|
|
1708
|
+
const found = this.findNodeRecursive(entry, key);
|
|
1709
|
+
if (found)
|
|
1710
|
+
return found;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
else if (typeof value === "object") {
|
|
1714
|
+
const found = this.findNodeRecursive(value, key);
|
|
1715
|
+
if (found)
|
|
1716
|
+
return found;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return undefined;
|
|
1720
|
+
}
|
|
1721
|
+
convertChartNodeToSpec(chartNode, sheetNameMap, context) {
|
|
1722
|
+
if (!chartNode)
|
|
1723
|
+
return undefined;
|
|
1724
|
+
const chartAttrs = this.getAttributes(chartNode);
|
|
1725
|
+
const chartType = this.mapOdsChartClassToType(chartAttrs?.["@_chart:class"] || chartAttrs?.["@_class"]);
|
|
1726
|
+
const titleNode = get(chartNode, "chart:title.chart:title-text") ||
|
|
1727
|
+
get(chartNode, "chart:title") ||
|
|
1728
|
+
get(chartNode, "title");
|
|
1729
|
+
const rawTitle = titleNode ? this.extractTextContent(titleNode) : "";
|
|
1730
|
+
const title = rawTitle?.trim() ? rawTitle.trim() : undefined;
|
|
1731
|
+
const plotArea = get(chartNode, "chart:plot-area") || get(chartNode, "plot-area");
|
|
1732
|
+
if (!plotArea)
|
|
1733
|
+
return undefined;
|
|
1734
|
+
const seriesNodes = this.toArray(get(plotArea, "chart:series") ||
|
|
1735
|
+
get(plotArea, "chart:data-series") ||
|
|
1736
|
+
get(plotArea, "series"));
|
|
1737
|
+
if (!seriesNodes.length)
|
|
1738
|
+
return undefined;
|
|
1739
|
+
const domains = [];
|
|
1740
|
+
const series = [];
|
|
1741
|
+
let seriesIndex = 1;
|
|
1742
|
+
for (const seriesNode of seriesNodes) {
|
|
1743
|
+
const domainNode = get(seriesNode, "chart:domain") ||
|
|
1744
|
+
get(seriesNode, "domain") ||
|
|
1745
|
+
get(seriesNode, "chart:categories");
|
|
1746
|
+
if (domainNode) {
|
|
1747
|
+
const domainAddress = this.findRangeString(domainNode);
|
|
1748
|
+
if (domainAddress) {
|
|
1749
|
+
const chartData = this.createChartDataFromAddress(domainAddress, sheetNameMap, context);
|
|
1750
|
+
if (chartData) {
|
|
1751
|
+
domains.push(chartData);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
const valueAddress = this.findRangeString(seriesNode, new Set(["chart:domain", "domain", "chart:categories"]));
|
|
1756
|
+
if (valueAddress) {
|
|
1757
|
+
const seriesData = this.createChartDataFromAddress(valueAddress, sheetNameMap, context);
|
|
1758
|
+
if (seriesData) {
|
|
1759
|
+
const label = this.extractSeriesLabel(seriesNode);
|
|
1760
|
+
seriesData.dataLabel = label || `Series ${seriesIndex}`;
|
|
1761
|
+
const color = this.extractChartSeriesColor(seriesNode);
|
|
1762
|
+
if (color) {
|
|
1763
|
+
seriesData.color = color;
|
|
1764
|
+
}
|
|
1765
|
+
series.push(seriesData);
|
|
1766
|
+
seriesIndex++;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (!series.length) {
|
|
1771
|
+
return undefined;
|
|
1772
|
+
}
|
|
1773
|
+
const finalizedDomains = domains.length ? domains : [series[0]];
|
|
1774
|
+
return {
|
|
1775
|
+
chartType,
|
|
1776
|
+
title,
|
|
1777
|
+
domains: finalizedDomains,
|
|
1778
|
+
series,
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
extractSeriesLabel(seriesNode) {
|
|
1782
|
+
if (!seriesNode)
|
|
1783
|
+
return undefined;
|
|
1784
|
+
const labelNode = get(seriesNode, "chart:label") ||
|
|
1785
|
+
get(seriesNode, "label") ||
|
|
1786
|
+
get(seriesNode, "chart:series-label");
|
|
1787
|
+
const label = labelNode ? this.extractTextContent(labelNode) : undefined;
|
|
1788
|
+
return label?.trim() || undefined;
|
|
1789
|
+
}
|
|
1790
|
+
extractChartSeriesColor(seriesNode) {
|
|
1791
|
+
if (!seriesNode)
|
|
1792
|
+
return undefined;
|
|
1793
|
+
const attrs = this.getAttributes(seriesNode);
|
|
1794
|
+
const styleCandidates = [
|
|
1795
|
+
attrs?.["@_chart:style-name"],
|
|
1796
|
+
attrs?.["@_chart:data-style-name"],
|
|
1797
|
+
attrs?.["@_style-name"],
|
|
1798
|
+
];
|
|
1799
|
+
for (const candidate of styleCandidates) {
|
|
1800
|
+
const color = this.odsStyles.getChartStyleColor(candidate);
|
|
1801
|
+
if (color) {
|
|
1802
|
+
return typeof color === "string" ? color : undefined;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
const seriesProps = get(seriesNode, "chart:data-point-properties") ||
|
|
1806
|
+
get(seriesNode, "chart:series-properties");
|
|
1807
|
+
const inlineColor = this.extractColorFromChartProperties(seriesProps);
|
|
1808
|
+
if (inlineColor) {
|
|
1809
|
+
return inlineColor;
|
|
1810
|
+
}
|
|
1811
|
+
const dataPoints = this.toArray(get(seriesNode, "chart:data-point") ||
|
|
1812
|
+
get(seriesNode, "chart:data-points") ||
|
|
1813
|
+
get(seriesNode, "data-point"));
|
|
1814
|
+
for (const dataPoint of dataPoints) {
|
|
1815
|
+
const pointAttrs = this.getAttributes(dataPoint);
|
|
1816
|
+
const pointStyleCandidates = [
|
|
1817
|
+
pointAttrs?.["@_chart:style-name"],
|
|
1818
|
+
pointAttrs?.["@_chart:data-style-name"],
|
|
1819
|
+
pointAttrs?.["@_style-name"],
|
|
1820
|
+
];
|
|
1821
|
+
for (const candidate of pointStyleCandidates) {
|
|
1822
|
+
const color = this.odsStyles.getChartStyleColor(candidate);
|
|
1823
|
+
if (color)
|
|
1824
|
+
return typeof color === "string" ? color : undefined;
|
|
1825
|
+
}
|
|
1826
|
+
const pointColor = this.extractColorFromChartProperties(get(dataPoint, "chart:data-point-properties") || dataPoint);
|
|
1827
|
+
if (pointColor)
|
|
1828
|
+
return pointColor;
|
|
1829
|
+
}
|
|
1830
|
+
return undefined;
|
|
1831
|
+
}
|
|
1832
|
+
extractColorFromChartProperties(node) {
|
|
1833
|
+
if (!node)
|
|
1834
|
+
return undefined;
|
|
1835
|
+
const attrs = this.getAttributes(node);
|
|
1836
|
+
if (!attrs)
|
|
1837
|
+
return undefined;
|
|
1838
|
+
const rawValue = attrs["@_draw:fill-color"] ||
|
|
1839
|
+
attrs["@_svg:fill-color"] ||
|
|
1840
|
+
attrs["@_chart:color"] ||
|
|
1841
|
+
attrs["@_fo:background-color"] ||
|
|
1842
|
+
attrs["@_style:background-color"];
|
|
1843
|
+
if (!rawValue)
|
|
1844
|
+
return undefined;
|
|
1845
|
+
const normalized = this.normalizeOdsColor(rawValue);
|
|
1846
|
+
if (normalized &&
|
|
1847
|
+
normalized.toLowerCase() !== "none" &&
|
|
1848
|
+
normalized.toLowerCase() !== "transparent") {
|
|
1849
|
+
return normalized;
|
|
1850
|
+
}
|
|
1851
|
+
return undefined;
|
|
1852
|
+
}
|
|
1853
|
+
mapOdsChartClassToType(value) {
|
|
1854
|
+
const normalized = value?.toLowerCase() || "";
|
|
1855
|
+
if (normalized.includes("line") || normalized.includes("scatter")) {
|
|
1856
|
+
return "line";
|
|
1857
|
+
}
|
|
1858
|
+
if (normalized.includes("pie")) {
|
|
1859
|
+
return "pie";
|
|
1860
|
+
}
|
|
1861
|
+
if (normalized.includes("area")) {
|
|
1862
|
+
return "area";
|
|
1863
|
+
}
|
|
1864
|
+
return "bar";
|
|
1865
|
+
}
|
|
1866
|
+
findRangeString(node, skipKeys) {
|
|
1867
|
+
if (!node)
|
|
1868
|
+
return undefined;
|
|
1869
|
+
const stack = [node];
|
|
1870
|
+
while (stack.length) {
|
|
1871
|
+
const current = stack.shift();
|
|
1872
|
+
if (current === undefined || current === null)
|
|
1873
|
+
continue;
|
|
1874
|
+
if (typeof current === "string") {
|
|
1875
|
+
if (this.looksLikeCellRangeAddress(current)) {
|
|
1876
|
+
return current;
|
|
1877
|
+
}
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
if (Array.isArray(current)) {
|
|
1881
|
+
stack.unshift(...current);
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
if (typeof current === "object") {
|
|
1885
|
+
const attrs = current[":@"] || current;
|
|
1886
|
+
for (const [key, attrValue] of Object.entries(attrs)) {
|
|
1887
|
+
if (key.startsWith("@_") &&
|
|
1888
|
+
typeof attrValue === "string" &&
|
|
1889
|
+
this.looksLikeCellRangeAddress(attrValue)) {
|
|
1890
|
+
return attrValue;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
for (const [childKey, childValue] of Object.entries(current)) {
|
|
1894
|
+
if (childKey === ":@")
|
|
1895
|
+
continue;
|
|
1896
|
+
if (skipKeys && skipKeys.has(childKey))
|
|
1897
|
+
continue;
|
|
1898
|
+
stack.push(childValue);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return undefined;
|
|
1903
|
+
}
|
|
1904
|
+
looksLikeCellRangeAddress(value) {
|
|
1905
|
+
if (!value)
|
|
1906
|
+
return false;
|
|
1907
|
+
const trimmed = value.trim();
|
|
1908
|
+
if (!trimmed)
|
|
1909
|
+
return false;
|
|
1910
|
+
if (trimmed.includes("#REF"))
|
|
1911
|
+
return false;
|
|
1912
|
+
if (!/[A-Za-z]/.test(trimmed) || !/\d/.test(trimmed))
|
|
1913
|
+
return false;
|
|
1914
|
+
return trimmed.includes(".") || trimmed.includes(":");
|
|
1915
|
+
}
|
|
1916
|
+
createChartDataFromAddress(address, sheetNameMap, context) {
|
|
1917
|
+
if (!address)
|
|
1918
|
+
return undefined;
|
|
1919
|
+
let ranges = this.parseRangeList(address, sheetNameMap, context);
|
|
1920
|
+
if (!ranges.length && context?.name) {
|
|
1921
|
+
const augmented = `'${context.name}'.${address}`;
|
|
1922
|
+
ranges = this.parseRangeList(augmented, sheetNameMap, context);
|
|
1923
|
+
}
|
|
1924
|
+
if (!ranges.length)
|
|
1925
|
+
return undefined;
|
|
1926
|
+
return {
|
|
1927
|
+
sources: ranges,
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
buildOverlayPlacement(frameAttrs, cellAttrs, rowIndex, columnIndex) {
|
|
1931
|
+
const widthPixels = this.convertLengthToPixels(frameAttrs?.["@_svg:width"] ||
|
|
1932
|
+
frameAttrs?.["@_width"] ||
|
|
1933
|
+
cellAttrs?.["@_svg:width"] ||
|
|
1934
|
+
cellAttrs?.["@_width"]);
|
|
1935
|
+
const heightPixels = this.convertLengthToPixels(frameAttrs?.["@_svg:height"] ||
|
|
1936
|
+
frameAttrs?.["@_height"] ||
|
|
1937
|
+
cellAttrs?.["@_svg:height"] ||
|
|
1938
|
+
cellAttrs?.["@_height"]);
|
|
1939
|
+
const offsetXPixels = this.convertLengthToPixels(frameAttrs?.["@_svg:x"] ||
|
|
1940
|
+
frameAttrs?.["@_x"] ||
|
|
1941
|
+
cellAttrs?.["@_svg:x"] ||
|
|
1942
|
+
cellAttrs?.["@_x"]);
|
|
1943
|
+
const offsetYPixels = this.convertLengthToPixels(frameAttrs?.["@_svg:y"] ||
|
|
1944
|
+
frameAttrs?.["@_y"] ||
|
|
1945
|
+
cellAttrs?.["@_svg:y"] ||
|
|
1946
|
+
cellAttrs?.["@_y"]);
|
|
1947
|
+
const overlayPosition = {
|
|
1948
|
+
anchorCell: { rowIndex, columnIndex },
|
|
1949
|
+
};
|
|
1950
|
+
if (typeof widthPixels === "number") {
|
|
1951
|
+
overlayPosition.widthPixels = widthPixels;
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof heightPixels === "number") {
|
|
1954
|
+
overlayPosition.heightPixels = heightPixels;
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof offsetXPixels === "number") {
|
|
1957
|
+
overlayPosition.offsetXPixels = offsetXPixels;
|
|
1958
|
+
}
|
|
1959
|
+
if (typeof offsetYPixels === "number") {
|
|
1960
|
+
overlayPosition.offsetYPixels = offsetYPixels;
|
|
1961
|
+
}
|
|
1962
|
+
const DEFAULT_CELL_IMAGE_MAX_SIZE = 60;
|
|
1963
|
+
const exceedsCellSize = (typeof widthPixels === "number" &&
|
|
1964
|
+
widthPixels > DEFAULT_CELL_IMAGE_MAX_SIZE) ||
|
|
1965
|
+
(typeof heightPixels === "number" &&
|
|
1966
|
+
heightPixels > DEFAULT_CELL_IMAGE_MAX_SIZE);
|
|
1967
|
+
const isInsideCell = !exceedsCellSize &&
|
|
1968
|
+
(offsetXPixels === undefined || Math.abs(offsetXPixels) < 0.01) &&
|
|
1969
|
+
(offsetYPixels === undefined || Math.abs(offsetYPixels) < 0.01);
|
|
1970
|
+
return { overlayPosition, isInsideCell };
|
|
1971
|
+
}
|
|
1972
|
+
getImageDataUrl(imagePath, cache) {
|
|
1973
|
+
if (cache.has(imagePath)) {
|
|
1974
|
+
return cache.get(imagePath);
|
|
1975
|
+
}
|
|
1976
|
+
try {
|
|
1977
|
+
const binary = this.zipParser.GetBinary(imagePath);
|
|
1978
|
+
const dataUrl = this.createDataUrlFromBytes(binary, this.getImageMimeType(imagePath));
|
|
1979
|
+
cache.set(imagePath, dataUrl);
|
|
1980
|
+
return dataUrl;
|
|
1981
|
+
}
|
|
1982
|
+
catch {
|
|
1983
|
+
return undefined;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
normalizeImagePath(href) {
|
|
1987
|
+
if (!href)
|
|
1988
|
+
return undefined;
|
|
1989
|
+
let normalized = href.trim();
|
|
1990
|
+
if (!normalized || normalized.startsWith("#"))
|
|
1991
|
+
return undefined;
|
|
1992
|
+
normalized = normalized.replace(/^\.\/+/, "");
|
|
1993
|
+
while (normalized.startsWith("../")) {
|
|
1994
|
+
normalized = normalized.slice(3);
|
|
1995
|
+
}
|
|
1996
|
+
normalized = normalized.replace(/^\/+/, "");
|
|
1997
|
+
return normalized;
|
|
1998
|
+
}
|
|
1999
|
+
getImageMimeType(path) {
|
|
2000
|
+
const extension = path.split(".").pop()?.toLowerCase();
|
|
2001
|
+
switch (extension) {
|
|
2002
|
+
case "jpg":
|
|
2003
|
+
case "jpeg":
|
|
2004
|
+
return "image/jpeg";
|
|
2005
|
+
case "gif":
|
|
2006
|
+
return "image/gif";
|
|
2007
|
+
case "bmp":
|
|
2008
|
+
return "image/bmp";
|
|
2009
|
+
case "svg":
|
|
2010
|
+
return "image/svg+xml";
|
|
2011
|
+
case "webp":
|
|
2012
|
+
return "image/webp";
|
|
2013
|
+
default:
|
|
2014
|
+
return "image/png";
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
createDataUrlFromBytes(data, mimeType) {
|
|
2018
|
+
const base64 = this.bytesToBase64(data);
|
|
2019
|
+
return `data:${mimeType};base64,${base64}`;
|
|
2020
|
+
}
|
|
2021
|
+
bytesToBase64(data) {
|
|
2022
|
+
const globalRef = typeof globalThis !== "undefined" ? globalThis : undefined;
|
|
2023
|
+
if (globalRef?.Buffer) {
|
|
2024
|
+
return globalRef.Buffer.from(data).toString("base64");
|
|
2025
|
+
}
|
|
2026
|
+
const chunkSize = 0x8000;
|
|
2027
|
+
let binary = "";
|
|
2028
|
+
for (let i = 0; i < data.length; i += chunkSize) {
|
|
2029
|
+
const chunk = data.subarray(i, i + chunkSize);
|
|
2030
|
+
binary += String.fromCharCode(...Array.from(chunk));
|
|
2031
|
+
}
|
|
2032
|
+
if (typeof btoa === "function") {
|
|
2033
|
+
return btoa(binary);
|
|
2034
|
+
}
|
|
2035
|
+
if (globalRef?.btoa) {
|
|
2036
|
+
return globalRef.btoa(binary);
|
|
2037
|
+
}
|
|
2038
|
+
throw new Error("Unable to encode image data");
|
|
2039
|
+
}
|
|
2040
|
+
convertLengthToPixels(value) {
|
|
2041
|
+
if (!value)
|
|
2042
|
+
return undefined;
|
|
2043
|
+
const match = value
|
|
2044
|
+
.toString()
|
|
2045
|
+
.trim()
|
|
2046
|
+
.match(/^(-?\d+(?:\.\d+)?)(cm|mm|in|pt|pc|px)?$/i);
|
|
2047
|
+
if (!match)
|
|
2048
|
+
return undefined;
|
|
2049
|
+
const amount = parseFloat(match[1]);
|
|
2050
|
+
const unit = (match[2] || "px").toLowerCase();
|
|
2051
|
+
const unitMap = {
|
|
2052
|
+
px: 1,
|
|
2053
|
+
cm: 37.7952755906,
|
|
2054
|
+
mm: 3.77952755906,
|
|
2055
|
+
in: 96,
|
|
2056
|
+
pt: 96 / 72,
|
|
2057
|
+
pc: 16,
|
|
2058
|
+
};
|
|
2059
|
+
const factor = unitMap[unit] ?? 1;
|
|
2060
|
+
return amount * factor;
|
|
2061
|
+
}
|
|
2062
|
+
normalizeOdsColor(value) {
|
|
2063
|
+
if (!value)
|
|
2064
|
+
return undefined;
|
|
2065
|
+
const trimmed = value.trim();
|
|
2066
|
+
if (!trimmed)
|
|
2067
|
+
return undefined;
|
|
2068
|
+
if (trimmed.startsWith("#")) {
|
|
2069
|
+
return trimmed.toUpperCase();
|
|
2070
|
+
}
|
|
2071
|
+
if (/^\d+$/.test(trimmed)) {
|
|
2072
|
+
const numeric = parseInt(trimmed, 10);
|
|
2073
|
+
if (!isNaN(numeric)) {
|
|
2074
|
+
return `#${(numeric & 0xffffff)
|
|
2075
|
+
.toString(16)
|
|
2076
|
+
.padStart(6, "0")
|
|
2077
|
+
.toUpperCase()}`;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
return trimmed;
|
|
2081
|
+
}
|
|
2082
|
+
parseIntegerValue(value) {
|
|
2083
|
+
if (value === undefined || value === null)
|
|
2084
|
+
return undefined;
|
|
2085
|
+
const parsed = parseInt(value, 10);
|
|
2086
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
2087
|
+
}
|
|
2088
|
+
getNodeText(node) {
|
|
2089
|
+
if (node === undefined || node === null)
|
|
2090
|
+
return undefined;
|
|
2091
|
+
if (typeof node === "string")
|
|
2092
|
+
return node;
|
|
2093
|
+
if (typeof node["#text"] === "string")
|
|
2094
|
+
return node["#text"];
|
|
2095
|
+
if (typeof node["_text"] === "string")
|
|
2096
|
+
return node["_text"];
|
|
2097
|
+
return undefined;
|
|
2098
|
+
}
|
|
2099
|
+
buildSheetNameMap(sheetContexts) {
|
|
2100
|
+
const map = new Map();
|
|
2101
|
+
for (const context of sheetContexts) {
|
|
2102
|
+
this.registerSheetNameVariants(map, context.name, context.sheetId);
|
|
2103
|
+
}
|
|
2104
|
+
return map;
|
|
2105
|
+
}
|
|
2106
|
+
registerSheetNameVariants(map, name, sheetId) {
|
|
2107
|
+
if (!name)
|
|
2108
|
+
return;
|
|
2109
|
+
map.set(name, sheetId);
|
|
2110
|
+
const normalized = this.normalizeSheetName(name);
|
|
2111
|
+
if (normalized && normalized !== name) {
|
|
2112
|
+
map.set(normalized, sheetId);
|
|
2113
|
+
}
|
|
2114
|
+
if (normalized) {
|
|
2115
|
+
map.set(`'${normalized}'`, sheetId);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
normalizeSheetName(name) {
|
|
2119
|
+
if (!name)
|
|
2120
|
+
return undefined;
|
|
2121
|
+
let normalized = name.trim();
|
|
2122
|
+
normalized = normalized.replace(/^'+|'+$/g, "");
|
|
2123
|
+
normalized = normalized.replace(/^"+|"+$/g, "");
|
|
2124
|
+
normalized = normalized.replace(/^\$+/, "");
|
|
2125
|
+
return normalized;
|
|
2126
|
+
}
|
|
2127
|
+
parseRangeList(addresses, sheetNameMap, context) {
|
|
2128
|
+
if (!addresses)
|
|
2129
|
+
return [];
|
|
2130
|
+
const segments = this.splitAddressSegments(addresses);
|
|
2131
|
+
const ranges = [];
|
|
2132
|
+
for (const segment of segments) {
|
|
2133
|
+
const range = this.parseRange(segment, sheetNameMap, context);
|
|
2134
|
+
if (range) {
|
|
2135
|
+
ranges.push(range);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
return ranges;
|
|
2139
|
+
}
|
|
2140
|
+
splitAddressSegments(addresses) {
|
|
2141
|
+
const segments = [];
|
|
2142
|
+
let buffer = "";
|
|
2143
|
+
let inQuotes = false;
|
|
2144
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
2145
|
+
const char = addresses[i];
|
|
2146
|
+
if (char === "'") {
|
|
2147
|
+
if (inQuotes && addresses[i + 1] === "'") {
|
|
2148
|
+
buffer += "''";
|
|
2149
|
+
i++;
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
inQuotes = !inQuotes;
|
|
2153
|
+
buffer += char;
|
|
2154
|
+
continue;
|
|
2155
|
+
}
|
|
2156
|
+
if (!inQuotes && (char === "," || /\s/.test(char))) {
|
|
2157
|
+
if (buffer.trim()) {
|
|
2158
|
+
segments.push(buffer.trim());
|
|
2159
|
+
buffer = "";
|
|
2160
|
+
}
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
buffer += char;
|
|
2164
|
+
}
|
|
2165
|
+
if (buffer.trim()) {
|
|
2166
|
+
segments.push(buffer.trim());
|
|
2167
|
+
}
|
|
2168
|
+
return segments;
|
|
2169
|
+
}
|
|
2170
|
+
parseRange(address, sheetNameMap, context) {
|
|
2171
|
+
if (!address)
|
|
2172
|
+
return undefined;
|
|
2173
|
+
const normalized = address.replace(/\$/g, "");
|
|
2174
|
+
const [startRef, endRef] = normalized.split(":");
|
|
2175
|
+
const start = this.parseCellReference(startRef, sheetNameMap, context);
|
|
2176
|
+
if (!start)
|
|
2177
|
+
return undefined;
|
|
2178
|
+
const end = this.parseCellReference(endRef ?? startRef, sheetNameMap, context) ||
|
|
2179
|
+
start;
|
|
2180
|
+
const sheetId = end.sheetId ?? start.sheetId ?? context?.sheetId;
|
|
2181
|
+
if (!sheetId)
|
|
2182
|
+
return undefined;
|
|
2183
|
+
return {
|
|
2184
|
+
sheetId,
|
|
2185
|
+
startRowIndex: Math.min(start.rowIndex, end.rowIndex),
|
|
2186
|
+
endRowIndex: Math.max(start.rowIndex, end.rowIndex),
|
|
2187
|
+
startColumnIndex: Math.min(start.columnIndex, end.columnIndex),
|
|
2188
|
+
endColumnIndex: Math.max(start.columnIndex, end.columnIndex),
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
parseCellReference(reference, sheetNameMap, context) {
|
|
2192
|
+
if (!reference)
|
|
2193
|
+
return undefined;
|
|
2194
|
+
let ref = reference.trim();
|
|
2195
|
+
if (!ref)
|
|
2196
|
+
return undefined;
|
|
2197
|
+
ref = ref.replace(/\$/g, "");
|
|
2198
|
+
let sheetName;
|
|
2199
|
+
let cellPart = ref;
|
|
2200
|
+
const dotMatch = ref.match(/^(.*)[.!]([A-Za-z]+\d+)$/);
|
|
2201
|
+
if (dotMatch) {
|
|
2202
|
+
sheetName = dotMatch[1];
|
|
2203
|
+
cellPart = dotMatch[2];
|
|
2204
|
+
}
|
|
2205
|
+
const bangMatch = ref.match(/^(.*)!([A-Za-z]+\d+)$/);
|
|
2206
|
+
if (!sheetName && bangMatch) {
|
|
2207
|
+
sheetName = bangMatch[1];
|
|
2208
|
+
cellPart = bangMatch[2];
|
|
2209
|
+
}
|
|
2210
|
+
const quotedMatch = ref.match(/^'([^']+)'\.([A-Za-z]+\d+)$/);
|
|
2211
|
+
if (!sheetName && quotedMatch) {
|
|
2212
|
+
sheetName = quotedMatch[1];
|
|
2213
|
+
cellPart = quotedMatch[2];
|
|
2214
|
+
}
|
|
2215
|
+
const cell = addressToCell(cellPart.toUpperCase());
|
|
2216
|
+
if (!cell)
|
|
2217
|
+
return undefined;
|
|
2218
|
+
const normalizedSheet = sheetName
|
|
2219
|
+
? this.normalizeSheetName(sheetName)
|
|
2220
|
+
: undefined;
|
|
2221
|
+
const sheetId = (normalizedSheet && sheetNameMap.get(normalizedSheet)) ??
|
|
2222
|
+
(sheetName && sheetNameMap.get(sheetName)) ??
|
|
2223
|
+
context?.sheetId;
|
|
2224
|
+
return {
|
|
2225
|
+
rowIndex: cell.rowIndex,
|
|
2226
|
+
columnIndex: cell.columnIndex,
|
|
2227
|
+
sheetId,
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
parseBooleanAttribute(value) {
|
|
2231
|
+
if (value === undefined || value === null)
|
|
2232
|
+
return undefined;
|
|
2233
|
+
if (typeof value === "boolean")
|
|
2234
|
+
return value;
|
|
2235
|
+
const normalized = value.toString().toLowerCase();
|
|
2236
|
+
if (normalized === "true" || normalized === "1")
|
|
2237
|
+
return true;
|
|
2238
|
+
if (normalized === "false" || normalized === "0")
|
|
2239
|
+
return false;
|
|
2240
|
+
return undefined;
|
|
2241
|
+
}
|
|
2242
|
+
normalizeConditionExpression(expression) {
|
|
2243
|
+
if (!expression)
|
|
2244
|
+
return undefined;
|
|
2245
|
+
let normalized = expression.trim();
|
|
2246
|
+
if (!normalized)
|
|
2247
|
+
return undefined;
|
|
2248
|
+
if (normalized.startsWith("of:=")) {
|
|
2249
|
+
normalized = normalized.slice(4);
|
|
2250
|
+
}
|
|
2251
|
+
else if (normalized.startsWith("of:")) {
|
|
2252
|
+
normalized = normalized.slice(3);
|
|
2253
|
+
}
|
|
2254
|
+
if (!normalized.startsWith("=")) {
|
|
2255
|
+
normalized = `=${normalized}`;
|
|
2256
|
+
}
|
|
2257
|
+
return normalized;
|
|
2258
|
+
}
|
|
2259
|
+
normalizeExpression(expression) {
|
|
2260
|
+
if (!expression)
|
|
2261
|
+
return undefined;
|
|
2262
|
+
let normalized = expression.trim();
|
|
2263
|
+
if (!normalized)
|
|
2264
|
+
return undefined;
|
|
2265
|
+
normalized = convertOdsFormulaToExcel(normalized);
|
|
2266
|
+
normalized = normalized.trim();
|
|
2267
|
+
if (!normalized)
|
|
2268
|
+
return undefined;
|
|
2269
|
+
return normalized.startsWith("=") ? normalized : `=${normalized}`;
|
|
2270
|
+
}
|
|
2271
|
+
toArray(value) {
|
|
2272
|
+
if (!value)
|
|
2273
|
+
return [];
|
|
2274
|
+
return Array.isArray(value) ? value : [value];
|
|
2275
|
+
}
|
|
2276
|
+
expandRowCells(rowChildren) {
|
|
2277
|
+
if (!rowChildren) {
|
|
2278
|
+
return [];
|
|
2279
|
+
}
|
|
2280
|
+
const entries = Array.isArray(rowChildren) ? rowChildren : [rowChildren];
|
|
2281
|
+
const normalized = [];
|
|
2282
|
+
for (const entry of entries) {
|
|
2283
|
+
if (!entry)
|
|
2284
|
+
continue;
|
|
2285
|
+
if (entry["table:table-cell"] || entry["table:covered-table-cell"]) {
|
|
2286
|
+
normalized.push(entry);
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
const normalizedEntry = this.wrapRawCellEntry(entry);
|
|
2290
|
+
if (normalizedEntry) {
|
|
2291
|
+
normalized.push(normalizedEntry);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
return normalized;
|
|
2295
|
+
}
|
|
2296
|
+
wrapRawCellEntry(entry) {
|
|
2297
|
+
if (entry === undefined || entry === null)
|
|
2298
|
+
return undefined;
|
|
2299
|
+
const { attributes, childNodes } = this.extractAttributesFromNode(entry);
|
|
2300
|
+
const wrapper = {
|
|
2301
|
+
"table:table-cell": childNodes.length ? childNodes : [],
|
|
2302
|
+
};
|
|
2303
|
+
if (Object.keys(attributes).length) {
|
|
2304
|
+
wrapper[":@"] = attributes;
|
|
2305
|
+
}
|
|
2306
|
+
return wrapper;
|
|
2307
|
+
}
|
|
2308
|
+
extractAttributesFromNode(node) {
|
|
2309
|
+
const attributes = {};
|
|
2310
|
+
const childNodes = [];
|
|
2311
|
+
if (node === undefined || node === null) {
|
|
2312
|
+
return { attributes, childNodes };
|
|
2313
|
+
}
|
|
2314
|
+
if (typeof node !== "object" || Array.isArray(node)) {
|
|
2315
|
+
childNodes.push(typeof node === "object" ? node : { "#text": node ?? "" });
|
|
2316
|
+
return { attributes, childNodes };
|
|
2317
|
+
}
|
|
2318
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2319
|
+
if (key === ":@") {
|
|
2320
|
+
if (value && typeof value === "object") {
|
|
2321
|
+
Object.assign(attributes, value);
|
|
2322
|
+
}
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
if (key.startsWith("@_")) {
|
|
2326
|
+
attributes[key] = value;
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
childNodes.push({ [key]: value });
|
|
2330
|
+
}
|
|
2331
|
+
return { attributes, childNodes };
|
|
2332
|
+
}
|
|
2333
|
+
getAttributes(node) {
|
|
2334
|
+
if (!node)
|
|
2335
|
+
return undefined;
|
|
2336
|
+
if (Array.isArray(node)) {
|
|
2337
|
+
return node.length ? this.getAttributes(node[0]) : undefined;
|
|
2338
|
+
}
|
|
2339
|
+
return node[":@"] || node;
|
|
2340
|
+
}
|
|
2341
|
+
mapAlertStyle(value) {
|
|
2342
|
+
if (!value)
|
|
2343
|
+
return undefined;
|
|
2344
|
+
const normalized = value.toLowerCase();
|
|
2345
|
+
if (normalized.includes("warn"))
|
|
2346
|
+
return "warning";
|
|
2347
|
+
if (normalized.includes("info"))
|
|
2348
|
+
return "information";
|
|
2349
|
+
return "stop";
|
|
2350
|
+
}
|
|
2351
|
+
async getSheets(minRowCount = 0, minColumnCount = 0) {
|
|
2352
|
+
const cache = this.ensureSheetCache();
|
|
2353
|
+
if (!minRowCount && !minColumnCount) {
|
|
2354
|
+
return [cache.sheets, cache.tables];
|
|
2355
|
+
}
|
|
2356
|
+
const sheets = cache.sheets.map((sheet) => {
|
|
2357
|
+
const rowCount = Math.max(sheet.rowCount ?? 0, minRowCount);
|
|
2358
|
+
const columnCount = Math.max(sheet.columnCount ?? 0, minColumnCount);
|
|
2359
|
+
if (rowCount === sheet.rowCount && columnCount === sheet.columnCount) {
|
|
2360
|
+
return sheet;
|
|
2361
|
+
}
|
|
2362
|
+
return {
|
|
2363
|
+
...sheet,
|
|
2364
|
+
rowCount,
|
|
2365
|
+
columnCount,
|
|
2366
|
+
};
|
|
2367
|
+
});
|
|
2368
|
+
return [sheets, cache.tables];
|
|
2369
|
+
}
|
|
2370
|
+
async getConditionalFormatting() {
|
|
2371
|
+
return this.ensureSheetCache().conditionalFormatting;
|
|
2372
|
+
}
|
|
2373
|
+
async getDrawings() {
|
|
2374
|
+
return this.ensureSheetCache().drawings;
|
|
2375
|
+
}
|
|
2376
|
+
async getCharts() {
|
|
2377
|
+
return this.ensureSheetCache().charts;
|
|
2378
|
+
}
|
|
2379
|
+
async getDataValidations() {
|
|
2380
|
+
return this.ensureSheetCache().dataValidations;
|
|
2381
|
+
}
|
|
2382
|
+
getNamedRanges() {
|
|
2383
|
+
return this.ensureSheetCache().namedRanges;
|
|
2384
|
+
}
|
|
2385
|
+
findCalculationSettings(node) {
|
|
2386
|
+
if (!node)
|
|
2387
|
+
return undefined;
|
|
2388
|
+
if (Array.isArray(node)) {
|
|
2389
|
+
for (const child of node) {
|
|
2390
|
+
const found = this.findCalculationSettings(child);
|
|
2391
|
+
if (found)
|
|
2392
|
+
return found;
|
|
2393
|
+
}
|
|
2394
|
+
return undefined;
|
|
2395
|
+
}
|
|
2396
|
+
if (node["table:calculation-settings"]) {
|
|
2397
|
+
const settingsNode = node["table:calculation-settings"];
|
|
2398
|
+
if (Array.isArray(settingsNode)) {
|
|
2399
|
+
return settingsNode[0];
|
|
2400
|
+
}
|
|
2401
|
+
return settingsNode;
|
|
2402
|
+
}
|
|
2403
|
+
return (this.findCalculationSettings(node["office:spreadsheet"]) ||
|
|
2404
|
+
this.findCalculationSettings(node["office:body"]) ||
|
|
2405
|
+
this.findCalculationSettings(node["office:document-content"]));
|
|
2406
|
+
}
|
|
2407
|
+
getIterativeCalculationOptions() {
|
|
2408
|
+
try {
|
|
2409
|
+
const calcSettings = this.findCalculationSettings(this.contentXml) ??
|
|
2410
|
+
this.findCalculationSettings(this.contentXmlOrdered);
|
|
2411
|
+
if (!calcSettings)
|
|
2412
|
+
return undefined;
|
|
2413
|
+
const enabledAttr = getAttribute(calcSettings, "table:use-iteration");
|
|
2414
|
+
const maxIterationsAttr = getAttribute(calcSettings, "table:iteration-count");
|
|
2415
|
+
const maxChangeAttr = getAttribute(calcSettings, "table:minimum-step");
|
|
2416
|
+
if (enabledAttr === undefined &&
|
|
2417
|
+
maxIterationsAttr === undefined &&
|
|
2418
|
+
maxChangeAttr === undefined) {
|
|
2419
|
+
return undefined;
|
|
2420
|
+
}
|
|
2421
|
+
const enabled = enabledAttr === "true" || enabledAttr === true || enabledAttr === "1";
|
|
2422
|
+
const options = { enabled };
|
|
2423
|
+
if (maxIterationsAttr !== undefined) {
|
|
2424
|
+
const parsed = Number(maxIterationsAttr);
|
|
2425
|
+
if (!Number.isNaN(parsed)) {
|
|
2426
|
+
options.maxIterations = parsed;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (maxChangeAttr !== undefined) {
|
|
2430
|
+
const parsed = Number(maxChangeAttr);
|
|
2431
|
+
if (!Number.isNaN(parsed)) {
|
|
2432
|
+
options.maxChange = parsed;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return options;
|
|
2436
|
+
}
|
|
2437
|
+
catch {
|
|
2438
|
+
return undefined;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
async *processSheetData(chunkSize = 100, enableCellXfsRegistry = false, enableSharedStrings = true) {
|
|
2442
|
+
try {
|
|
2443
|
+
if (!this.contentXmlOrdered)
|
|
2444
|
+
return;
|
|
2445
|
+
const docContent = this.contentXmlOrdered[0]?.["office:document-content"] ||
|
|
2446
|
+
this.contentXmlOrdered[0]?.["document-content"];
|
|
2447
|
+
if (!docContent)
|
|
2448
|
+
return;
|
|
2449
|
+
const body = getChildren(docContent, "office:body") ||
|
|
2450
|
+
getChildren(docContent, "body");
|
|
2451
|
+
if (!body)
|
|
2452
|
+
return;
|
|
2453
|
+
const spreadsheet = getChildren(body, "office:spreadsheet") ||
|
|
2454
|
+
getChildren(body, "spreadsheet");
|
|
2455
|
+
if (!spreadsheet)
|
|
2456
|
+
return;
|
|
2457
|
+
const tables = spreadsheet.filter((child) => child["table:table"] || child["table"]);
|
|
2458
|
+
const totalSheets = tables.length;
|
|
2459
|
+
for (let currentSheetIndex = 0; currentSheetIndex < totalSheets; currentSheetIndex++) {
|
|
2460
|
+
const tableWrapper = tables[currentSheetIndex];
|
|
2461
|
+
const tableNameKey = tableWrapper["table:table"]
|
|
2462
|
+
? "table:table"
|
|
2463
|
+
: "table";
|
|
2464
|
+
const tableChildren = tableWrapper[tableNameKey];
|
|
2465
|
+
const tableAttrs = tableWrapper[":@"] || {};
|
|
2466
|
+
const name = tableAttrs["@_table:name"] ||
|
|
2467
|
+
tableAttrs["@_name"] ||
|
|
2468
|
+
`Sheet${currentSheetIndex + 1}`;
|
|
2469
|
+
const sheetId = currentSheetIndex + 1; // 1-based ID
|
|
2470
|
+
const tableStyleName = tableAttrs["@_table:style-name"] || tableAttrs["@_style-name"];
|
|
2471
|
+
const tableCellFormat = tableStyleName
|
|
2472
|
+
? this.odsStyles.getTableCellFormat(tableStyleName)
|
|
2473
|
+
: undefined;
|
|
2474
|
+
// Get rows
|
|
2475
|
+
const rows = tableChildren.filter((child) => child["table:table-row"] || child["table-row"]);
|
|
2476
|
+
const columnWrappers = tableChildren.filter((child) => child["table:table-column"] || child["table-column"]);
|
|
2477
|
+
const columnFormats = new Map();
|
|
2478
|
+
let formattedColumnIndex = 1;
|
|
2479
|
+
for (const columnWrapper of columnWrappers) {
|
|
2480
|
+
const columnAttrs = columnWrapper[":@"] || {};
|
|
2481
|
+
const colsRepeated = parseInt(columnAttrs["@_table:number-columns-repeated"] ||
|
|
2482
|
+
columnAttrs["@_number-columns-repeated"] ||
|
|
2483
|
+
"1", 10);
|
|
2484
|
+
const columnStyleName = columnAttrs["@_table:style-name"] || columnAttrs["@_style-name"];
|
|
2485
|
+
const columnStyle = columnStyleName
|
|
2486
|
+
? this.odsStyles.getColumnStyle(columnStyleName)
|
|
2487
|
+
: undefined;
|
|
2488
|
+
const columnCellFormat = columnStyle?.format;
|
|
2489
|
+
for (let col = 0; col < colsRepeated; col++) {
|
|
2490
|
+
if (columnCellFormat) {
|
|
2491
|
+
columnFormats.set(formattedColumnIndex, columnCellFormat);
|
|
2492
|
+
}
|
|
2493
|
+
formattedColumnIndex++;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
let chunkIndex = 0;
|
|
2497
|
+
let currentChunk = [];
|
|
2498
|
+
let currentChunkRowIndices = [];
|
|
2499
|
+
let currentRowIndex = 1;
|
|
2500
|
+
// Stamp `cellData.dataValidation` refs while rows stream (see
|
|
2501
|
+
// dv-cell-stamp.ts) — hosts resolve a cell's rule only through the
|
|
2502
|
+
// per-cell reference. Same cached parse as `getDataValidations()`,
|
|
2503
|
+
// so the stamped ids match the rule list consumers persist.
|
|
2504
|
+
const dvStamper = new SheetDataValidationStamper(await this.getDataValidations(), sheetId);
|
|
2505
|
+
for (const rowWrapper of rows) {
|
|
2506
|
+
const rowNameKey = rowWrapper["table:table-row"]
|
|
2507
|
+
? "table:table-row"
|
|
2508
|
+
: "table-row";
|
|
2509
|
+
const rowChildren = rowWrapper[rowNameKey];
|
|
2510
|
+
const rowAttrs = rowWrapper[":@"] || {};
|
|
2511
|
+
const rowsRepeated = parseInt(rowAttrs["@_table:number-rows-repeated"] ||
|
|
2512
|
+
rowAttrs["@_number-rows-repeated"] ||
|
|
2513
|
+
"1");
|
|
2514
|
+
const rowStyleName = rowAttrs["@_table:style-name"] || rowAttrs["@_style-name"];
|
|
2515
|
+
const rowStyle = rowStyleName
|
|
2516
|
+
? this.odsStyles.getRowStyle(rowStyleName)
|
|
2517
|
+
: undefined;
|
|
2518
|
+
const rowCellFormat = rowStyle?.format;
|
|
2519
|
+
const rowBaseFormat = fastMergeCellFormats(tableCellFormat, rowCellFormat);
|
|
2520
|
+
const effectiveRepeats = Math.min(rowsRepeated, 10000); // Cap to avoid infinite loops on empty sheets
|
|
2521
|
+
for (let r = 0; r < effectiveRepeats; r++) {
|
|
2522
|
+
const rowData = {
|
|
2523
|
+
values: [],
|
|
2524
|
+
};
|
|
2525
|
+
let columnIndex = 1;
|
|
2526
|
+
const normalizedRowChildren = rowChildren
|
|
2527
|
+
? this.expandRowCells(rowChildren)
|
|
2528
|
+
: null;
|
|
2529
|
+
if (normalizedRowChildren) {
|
|
2530
|
+
for (const cellWrapper of normalizedRowChildren) {
|
|
2531
|
+
const isCell = !!cellWrapper["table:table-cell"];
|
|
2532
|
+
const isCovered = !!cellWrapper["table:covered-table-cell"];
|
|
2533
|
+
if (!isCell && !isCovered)
|
|
2534
|
+
continue;
|
|
2535
|
+
const cellNameKey = isCell
|
|
2536
|
+
? "table:table-cell"
|
|
2537
|
+
: "table:covered-table-cell";
|
|
2538
|
+
const cellChildren = cellWrapper[cellNameKey];
|
|
2539
|
+
const cellAttrs = cellWrapper[":@"] || {};
|
|
2540
|
+
const colsRepeated = parseInt(cellAttrs["@_table:number-columns-repeated"] ||
|
|
2541
|
+
cellAttrs["@_number-columns-repeated"] ||
|
|
2542
|
+
"1");
|
|
2543
|
+
if (isCovered) {
|
|
2544
|
+
columnIndex += colsRepeated;
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
// Extract value
|
|
2548
|
+
const valueType = cellAttrs["@_office:value-type"] || cellAttrs["@_value-type"];
|
|
2549
|
+
let cellValue = null;
|
|
2550
|
+
let displayValue = "";
|
|
2551
|
+
let textFormatRuns;
|
|
2552
|
+
// Get text content
|
|
2553
|
+
const textPWrapper = findChild(cellChildren, "text:p") ||
|
|
2554
|
+
findChild(cellChildren, "p");
|
|
2555
|
+
if (textPWrapper) {
|
|
2556
|
+
const textPKey = textPWrapper["text:p"] ? "text:p" : "p";
|
|
2557
|
+
const richText = this.extractTextContentWithRuns(textPWrapper[textPKey]);
|
|
2558
|
+
displayValue = richText.text;
|
|
2559
|
+
if (richText.runs.length > 0) {
|
|
2560
|
+
textFormatRuns = richText.runs;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
const hyperlinkValue = this.extractHyperlinkValue(cellChildren);
|
|
2564
|
+
if (valueType === "float" ||
|
|
2565
|
+
valueType === "currency" ||
|
|
2566
|
+
valueType === "percentage") {
|
|
2567
|
+
cellValue = parseFloat(cellAttrs["@_office:value"] || cellAttrs["@_value"]);
|
|
2568
|
+
}
|
|
2569
|
+
else if (valueType === "date") {
|
|
2570
|
+
cellValue =
|
|
2571
|
+
cellAttrs["@_office:date-value"] ||
|
|
2572
|
+
cellAttrs["@_date-value"];
|
|
2573
|
+
}
|
|
2574
|
+
else if (valueType === "string") {
|
|
2575
|
+
cellValue =
|
|
2576
|
+
cellAttrs["@_office:string-value"] ||
|
|
2577
|
+
cellAttrs["@_string-value"] ||
|
|
2578
|
+
displayValue;
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
cellValue = displayValue;
|
|
2582
|
+
}
|
|
2583
|
+
const cellData = {};
|
|
2584
|
+
if (hyperlinkValue) {
|
|
2585
|
+
cellData.hyperlink = hyperlinkValue;
|
|
2586
|
+
}
|
|
2587
|
+
if (cellValue !== null && cellValue !== "") {
|
|
2588
|
+
const formula = cellAttrs["@_table:formula"] || cellAttrs["@_formula"];
|
|
2589
|
+
const isSharedString = enableSharedStrings &&
|
|
2590
|
+
!formula &&
|
|
2591
|
+
valueType === "string" &&
|
|
2592
|
+
typeof cellValue === "string";
|
|
2593
|
+
if (isSharedString) {
|
|
2594
|
+
cellData.ss = String(this.getOrCreateSharedStringIndex(String(cellValue), textFormatRuns));
|
|
2595
|
+
}
|
|
2596
|
+
else {
|
|
2597
|
+
if (valueType === "float" ||
|
|
2598
|
+
valueType === "currency" ||
|
|
2599
|
+
valueType === "percentage") {
|
|
2600
|
+
cellData.ev = { nv: cellValue };
|
|
2601
|
+
}
|
|
2602
|
+
else {
|
|
2603
|
+
cellData.ev = { sv: String(cellValue) };
|
|
2604
|
+
}
|
|
2605
|
+
if (formula) {
|
|
2606
|
+
const excelFormula = convertOdsFormulaToExcel(formula);
|
|
2607
|
+
cellData.ue = { fv: excelFormula };
|
|
2608
|
+
// Array (matrix) formula anchor: ODF marks the spill
|
|
2609
|
+
// shape with table:number-matrix-{columns,rows}-spanned.
|
|
2610
|
+
// Convert to A1 spill ref so xlsx export can emit
|
|
2611
|
+
// <f t="array" ref="..."/>. Spilled cells in the matrix
|
|
2612
|
+
// appear as ordinary table cells without `table:formula`
|
|
2613
|
+
// and so don't reach this branch.
|
|
2614
|
+
const matrixCols = parseInt(cellAttrs["@_table:number-matrix-columns-spanned"] ||
|
|
2615
|
+
cellAttrs["@_number-matrix-columns-spanned"] ||
|
|
2616
|
+
"0");
|
|
2617
|
+
const matrixRows = parseInt(cellAttrs["@_table:number-matrix-rows-spanned"] ||
|
|
2618
|
+
cellAttrs["@_number-matrix-rows-spanned"] ||
|
|
2619
|
+
"0");
|
|
2620
|
+
if (matrixCols > 0 || matrixRows > 0) {
|
|
2621
|
+
const cols = Math.max(matrixCols, 1);
|
|
2622
|
+
const rows = Math.max(matrixRows, 1);
|
|
2623
|
+
const start = cellToAddress({
|
|
2624
|
+
rowIndex: currentRowIndex,
|
|
2625
|
+
columnIndex,
|
|
2626
|
+
});
|
|
2627
|
+
const end = cellToAddress({
|
|
2628
|
+
rowIndex: currentRowIndex + rows - 1,
|
|
2629
|
+
columnIndex: columnIndex + cols - 1,
|
|
2630
|
+
});
|
|
2631
|
+
if (start && end) {
|
|
2632
|
+
cellData.af = `${start}:${end}`;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
else {
|
|
2637
|
+
cellData.ue = cellData.ev;
|
|
2638
|
+
}
|
|
2639
|
+
// Skip persisted `fv` for formula cells — renderer
|
|
2640
|
+
// derives the formatted value at paint time.
|
|
2641
|
+
if (!formula) {
|
|
2642
|
+
cellData.fv = displayValue;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
const cellStyleName = cellAttrs["@_table:style-name"] || cellAttrs["@_style-name"];
|
|
2647
|
+
const cellFormat = cellStyleName
|
|
2648
|
+
? this.odsStyles.getStyle(cellStyleName)
|
|
2649
|
+
: undefined;
|
|
2650
|
+
const hasCellValue = Object.keys(cellData).length > 0;
|
|
2651
|
+
// Fill repeated columns
|
|
2652
|
+
for (let c = 0; c < colsRepeated; c++) {
|
|
2653
|
+
const targetColumnIndex = columnIndex;
|
|
2654
|
+
let mergedFormat = fastMergeCellFormats(rowBaseFormat, columnFormats.get(targetColumnIndex), cellFormat);
|
|
2655
|
+
if (mergedFormat) {
|
|
2656
|
+
if (mergedFormat.__alignValueType) {
|
|
2657
|
+
const alignment = valueType === "float" ||
|
|
2658
|
+
valueType === "currency" ||
|
|
2659
|
+
valueType === "percentage"
|
|
2660
|
+
? "right"
|
|
2661
|
+
: "left";
|
|
2662
|
+
mergedFormat.horizontalAlignment = alignment;
|
|
2663
|
+
delete mergedFormat.__alignValueType;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
const needsDefaultRightAlign = (valueType === "float" ||
|
|
2667
|
+
valueType === "currency" ||
|
|
2668
|
+
valueType === "percentage" ||
|
|
2669
|
+
valueType === "date") &&
|
|
2670
|
+
(!mergedFormat || !mergedFormat.horizontalAlignment);
|
|
2671
|
+
if (needsDefaultRightAlign) {
|
|
2672
|
+
mergedFormat = mergedFormat
|
|
2673
|
+
? { ...mergedFormat }
|
|
2674
|
+
: {};
|
|
2675
|
+
mergedFormat.horizontalAlignment = "right";
|
|
2676
|
+
}
|
|
2677
|
+
const displaySuggestsCurrency = typeof displayValue === "string" &&
|
|
2678
|
+
DISPLAY_CURRENCY_REGEX.test(displayValue);
|
|
2679
|
+
if (valueType === "currency") {
|
|
2680
|
+
mergedFormat = mergedFormat
|
|
2681
|
+
? { ...mergedFormat }
|
|
2682
|
+
: {};
|
|
2683
|
+
const currencySymbol = cellAttrs["@_office:currency"] ||
|
|
2684
|
+
cellAttrs["@_currency"] ||
|
|
2685
|
+
"$";
|
|
2686
|
+
if (!mergedFormat.numberFormat ||
|
|
2687
|
+
mergedFormat.numberFormat.type === "NUMBER") {
|
|
2688
|
+
const pattern = mergedFormat.numberFormat?.pattern || "#,##0.00";
|
|
2689
|
+
mergedFormat.numberFormat = {
|
|
2690
|
+
type: "CURRENCY",
|
|
2691
|
+
pattern: pattern.includes(currencySymbol)
|
|
2692
|
+
? pattern
|
|
2693
|
+
: `${currencySymbol}${pattern.startsWith("#") || pattern.startsWith("0")
|
|
2694
|
+
? " "
|
|
2695
|
+
: ""}${pattern}`,
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const shouldInferNumberFormat = valueType === "float" &&
|
|
2700
|
+
(!mergedFormat ||
|
|
2701
|
+
!mergedFormat.numberFormat ||
|
|
2702
|
+
displaySuggestsCurrency);
|
|
2703
|
+
if (shouldInferNumberFormat) {
|
|
2704
|
+
const detectionSource = typeof displayValue === "string" &&
|
|
2705
|
+
displayValue.trim() !== ""
|
|
2706
|
+
? displayValue
|
|
2707
|
+
: cellValue;
|
|
2708
|
+
if (detectionSource !== undefined) {
|
|
2709
|
+
const [detectedValueType, detectedNumberFormatType, , detectedPattern,] = detectValueTypeAndPattern(detectionSource, undefined);
|
|
2710
|
+
if (detectedValueType === "numberValue" &&
|
|
2711
|
+
detectedNumberFormatType) {
|
|
2712
|
+
const shouldOverrideExistingFormat = !mergedFormat ||
|
|
2713
|
+
!mergedFormat.numberFormat ||
|
|
2714
|
+
mergedFormat.numberFormat.type === "NUMBER" ||
|
|
2715
|
+
(displaySuggestsCurrency &&
|
|
2716
|
+
detectedNumberFormatType === "CURRENCY");
|
|
2717
|
+
if (shouldOverrideExistingFormat) {
|
|
2718
|
+
mergedFormat = mergedFormat
|
|
2719
|
+
? { ...mergedFormat }
|
|
2720
|
+
: {};
|
|
2721
|
+
mergedFormat.numberFormat = {
|
|
2722
|
+
type: detectedNumberFormatType,
|
|
2723
|
+
...(detectedPattern
|
|
2724
|
+
? { pattern: detectedPattern }
|
|
2725
|
+
: {}),
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
if (!hasCellValue && !mergedFormat) {
|
|
2732
|
+
columnIndex++;
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
const finalCellData = hasCellValue
|
|
2736
|
+
? { ...cellData }
|
|
2737
|
+
: {};
|
|
2738
|
+
if (mergedFormat) {
|
|
2739
|
+
if (enableCellXfsRegistry) {
|
|
2740
|
+
const styleId = this.registerFormat(mergedFormat);
|
|
2741
|
+
finalCellData.ef = { sid: styleId };
|
|
2742
|
+
finalCellData.uf = { sid: styleId };
|
|
2743
|
+
}
|
|
2744
|
+
else {
|
|
2745
|
+
finalCellData.userEnteredFormat = mergedFormat;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
rowData.values[targetColumnIndex] = finalCellData;
|
|
2749
|
+
columnIndex++;
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
// Inject sparkline formulas for this row
|
|
2754
|
+
const sheetSparklines = this.sparklinesBySheet.get(sheetId);
|
|
2755
|
+
if (sheetSparklines) {
|
|
2756
|
+
const rowSparklines = sheetSparklines.get(currentRowIndex);
|
|
2757
|
+
if (rowSparklines) {
|
|
2758
|
+
for (const [colIndex, sparkline] of rowSparklines.entries()) {
|
|
2759
|
+
const existingCell = rowData.values[colIndex];
|
|
2760
|
+
// Only set formula if cell doesn't already have one
|
|
2761
|
+
if (existingCell?.ue?.fv) {
|
|
2762
|
+
continue;
|
|
2763
|
+
}
|
|
2764
|
+
// Extract data values for effective value
|
|
2765
|
+
const sparklineData = extractSparklineData(sparkline, currentRowIndex, rowData.values);
|
|
2766
|
+
// Build options object for sparkline result
|
|
2767
|
+
const sparklineOptions = {};
|
|
2768
|
+
if (sparkline.chartType !== "line") {
|
|
2769
|
+
sparklineOptions.charttype = sparkline.chartType;
|
|
2770
|
+
}
|
|
2771
|
+
if (sparkline.color) {
|
|
2772
|
+
sparklineOptions.color = sparkline.color;
|
|
2773
|
+
}
|
|
2774
|
+
const cellData = {
|
|
2775
|
+
...existingCell,
|
|
2776
|
+
ue: { fv: sparkline.formula },
|
|
2777
|
+
};
|
|
2778
|
+
// Only set effective value if we have enough data points
|
|
2779
|
+
if (sparklineData.length >= 2) {
|
|
2780
|
+
cellData.ev = {
|
|
2781
|
+
structuredValue: {
|
|
2782
|
+
kind: "sparkline",
|
|
2783
|
+
data: sparklineData,
|
|
2784
|
+
options: sparklineOptions,
|
|
2785
|
+
},
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
rowData.values[colIndex] = cellData;
|
|
2789
|
+
}
|
|
2790
|
+
// Clean up processed sparklines
|
|
2791
|
+
sheetSparklines.delete(currentRowIndex);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
dvStamper.stampRow(currentRowIndex, rowData.values);
|
|
2795
|
+
const isRowEmpty = rowData.values.length === 0;
|
|
2796
|
+
if (isRowEmpty) {
|
|
2797
|
+
currentRowIndex++;
|
|
2798
|
+
if (r === 0 && effectiveRepeats > 1) {
|
|
2799
|
+
currentRowIndex += effectiveRepeats - 1;
|
|
2800
|
+
break;
|
|
2801
|
+
}
|
|
2802
|
+
continue;
|
|
2803
|
+
}
|
|
2804
|
+
currentChunk.push(rowData);
|
|
2805
|
+
currentChunkRowIndices.push(currentRowIndex);
|
|
2806
|
+
currentRowIndex++;
|
|
2807
|
+
if (currentChunk.length >= chunkSize) {
|
|
2808
|
+
yield {
|
|
2809
|
+
sheetId,
|
|
2810
|
+
title: name,
|
|
2811
|
+
currentSheetIndex,
|
|
2812
|
+
totalSheets,
|
|
2813
|
+
chunkIndex,
|
|
2814
|
+
rows: currentChunk,
|
|
2815
|
+
rowIndices: currentChunkRowIndices,
|
|
2816
|
+
startRowIndex: currentChunkRowIndices[0],
|
|
2817
|
+
endRowIndex: currentChunkRowIndices[currentChunk.length - 1],
|
|
2818
|
+
totalRows: -1,
|
|
2819
|
+
isLastChunk: false,
|
|
2820
|
+
cellXfs: enableCellXfsRegistry ? this.cellXfs : new Map(),
|
|
2821
|
+
};
|
|
2822
|
+
currentChunk = [];
|
|
2823
|
+
currentChunkRowIndices = [];
|
|
2824
|
+
chunkIndex++;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
// Rows fully absent from the content (or skipped by the repeated-
|
|
2829
|
+
// empty-row fast path) but covered by a bounded DV range still need
|
|
2830
|
+
// phantom `{ dataValidation }` cells or blank template rows lose
|
|
2831
|
+
// their dropdowns.
|
|
2832
|
+
for (const trailing of dvStamper.trailingRows()) {
|
|
2833
|
+
currentChunk.push({ values: trailing.values });
|
|
2834
|
+
currentChunkRowIndices.push(trailing.rowIndex);
|
|
2835
|
+
if (currentChunk.length >= chunkSize) {
|
|
2836
|
+
yield {
|
|
2837
|
+
sheetId,
|
|
2838
|
+
title: name,
|
|
2839
|
+
currentSheetIndex,
|
|
2840
|
+
totalSheets,
|
|
2841
|
+
chunkIndex,
|
|
2842
|
+
rows: currentChunk,
|
|
2843
|
+
rowIndices: currentChunkRowIndices,
|
|
2844
|
+
startRowIndex: currentChunkRowIndices[0],
|
|
2845
|
+
endRowIndex: currentChunkRowIndices[currentChunk.length - 1],
|
|
2846
|
+
totalRows: -1,
|
|
2847
|
+
isLastChunk: false,
|
|
2848
|
+
cellXfs: enableCellXfsRegistry ? this.cellXfs : new Map(),
|
|
2849
|
+
};
|
|
2850
|
+
currentChunk = [];
|
|
2851
|
+
currentChunkRowIndices = [];
|
|
2852
|
+
chunkIndex++;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
// Yield remaining
|
|
2856
|
+
if (currentChunk.length > 0) {
|
|
2857
|
+
yield {
|
|
2858
|
+
sheetId,
|
|
2859
|
+
title: name,
|
|
2860
|
+
currentSheetIndex,
|
|
2861
|
+
totalSheets,
|
|
2862
|
+
chunkIndex,
|
|
2863
|
+
rows: currentChunk,
|
|
2864
|
+
rowIndices: currentChunkRowIndices,
|
|
2865
|
+
startRowIndex: currentChunkRowIndices[0],
|
|
2866
|
+
endRowIndex: currentChunkRowIndices[currentChunk.length - 1],
|
|
2867
|
+
totalRows: currentRowIndex - 1,
|
|
2868
|
+
isLastChunk: true,
|
|
2869
|
+
cellXfs: enableCellXfsRegistry ? this.cellXfs : new Map(),
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
else {
|
|
2873
|
+
// Empty sheet
|
|
2874
|
+
yield {
|
|
2875
|
+
sheetId,
|
|
2876
|
+
title: name,
|
|
2877
|
+
currentSheetIndex,
|
|
2878
|
+
totalSheets,
|
|
2879
|
+
chunkIndex,
|
|
2880
|
+
rows: [],
|
|
2881
|
+
rowIndices: [],
|
|
2882
|
+
startRowIndex: 0,
|
|
2883
|
+
endRowIndex: 0,
|
|
2884
|
+
totalRows: 0,
|
|
2885
|
+
isLastChunk: true,
|
|
2886
|
+
cellXfs: enableCellXfsRegistry ? this.cellXfs : new Map(),
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
catch (error) {
|
|
2892
|
+
console.error("Error processing ODS sheet data", error);
|
|
2893
|
+
throw error;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|