@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,1365 @@
|
|
|
1
|
+
import { formulaToRelativeReference } from "@rowsncolumns/calculator";
|
|
2
|
+
import { IndexedColors } from "@rowsncolumns/utils";
|
|
3
|
+
import { addressToCell, ssfFormat } from "@rowsncolumns/utils";
|
|
4
|
+
import { standardExcelFormatCodeById } from "./numfmt";
|
|
5
|
+
import { detectValueTypeAndPattern } from "./value-type-detection";
|
|
6
|
+
const getNode = (node, ...keys) => {
|
|
7
|
+
if (!node)
|
|
8
|
+
return undefined;
|
|
9
|
+
for (const key of keys) {
|
|
10
|
+
if (node[key] !== undefined) {
|
|
11
|
+
return node[key];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
};
|
|
16
|
+
const getNodeText = (node) => {
|
|
17
|
+
if (node === undefined || node === null) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (typeof node === "object") {
|
|
21
|
+
if ("t$" in node && node.t$ !== undefined) {
|
|
22
|
+
return node.t$;
|
|
23
|
+
}
|
|
24
|
+
if ("_" in node && node._ !== undefined) {
|
|
25
|
+
return node._;
|
|
26
|
+
}
|
|
27
|
+
if ("#text" in node && node["#text"] !== undefined) {
|
|
28
|
+
return node["#text"];
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return String(node);
|
|
33
|
+
};
|
|
34
|
+
const getInlineStringValue = (cell) => {
|
|
35
|
+
if (!cell.is) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (cell.is.t !== undefined) {
|
|
39
|
+
const inlineValue = cell.is.t;
|
|
40
|
+
return typeof inlineValue === "object"
|
|
41
|
+
? inlineValue.t$ ?? inlineValue._ ?? ""
|
|
42
|
+
: inlineValue;
|
|
43
|
+
}
|
|
44
|
+
if (cell.is.r) {
|
|
45
|
+
const runs = Array.isArray(cell.is.r) ? cell.is.r : [cell.is.r];
|
|
46
|
+
return runs
|
|
47
|
+
.map((run) => {
|
|
48
|
+
if (run.t === undefined)
|
|
49
|
+
return "";
|
|
50
|
+
return typeof run.t === "object" ? run.t.t$ ?? "" : run.t;
|
|
51
|
+
})
|
|
52
|
+
.join("");
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Extract cell value and format information from Excel cell data
|
|
58
|
+
* @param {Object} cell - The cell object from the sheet data
|
|
59
|
+
* @param {WorkBook} workbook - The workbook object containing shared strings
|
|
60
|
+
* @returns {Object} Object containing value and cellFormat properties
|
|
61
|
+
*/
|
|
62
|
+
export function getCellValueAndFormat(cell, workbook, enableCellXfsRegistry = false, enableSharedStrings = false, activeSheetName, mergeContext) {
|
|
63
|
+
if (!cell) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Extract raw value
|
|
67
|
+
const { rawValue: extractedRawValue, formulaValue, sharedStringIndex, isError, arrayFormulaRef, sharedFormulaSpec, } = extractCellValue(cell, workbook, activeSheetName);
|
|
68
|
+
let rawValue = extractedRawValue;
|
|
69
|
+
// Extract cell format
|
|
70
|
+
const cellFormat = extractCellFormat(cell, workbook.workbookStyles, mergeContext);
|
|
71
|
+
let ef = cellFormat;
|
|
72
|
+
// Cell-level protection — lives top-level on CellData, not on
|
|
73
|
+
// CellFormat. Excel's xf carries it, but it's a per-cell concept
|
|
74
|
+
// (toggled per cell to opt in/out of sheet protection), not a
|
|
75
|
+
// share-by-style property in our model.
|
|
76
|
+
const protection = extractCellProtection(cell, workbook.workbookStyles);
|
|
77
|
+
const isSharedStringCell = enableSharedStrings &&
|
|
78
|
+
sharedStringIndex !== undefined &&
|
|
79
|
+
formulaValue === null;
|
|
80
|
+
if (isSharedStringCell) {
|
|
81
|
+
if (enableCellXfsRegistry) {
|
|
82
|
+
const styleId = workbook.registerFormat(cellFormat);
|
|
83
|
+
ef = { sid: styleId };
|
|
84
|
+
}
|
|
85
|
+
const hasFormat = enableCellXfsRegistry || Object.keys(cellFormat ?? {}).length > 0;
|
|
86
|
+
// Cells only carry `ss`; rich-text runs (if any) live on the
|
|
87
|
+
// SharedStrings entry the index points at. The parser populates
|
|
88
|
+
// those rich entries via `loadSharedStrings`.
|
|
89
|
+
return {
|
|
90
|
+
...(hasFormat ? { uf: ef } : {}),
|
|
91
|
+
ss: String(sharedStringIndex),
|
|
92
|
+
...(protection ? { protection } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// If there is no numberFormat, coerce typed display strings ("$1,200",
|
|
96
|
+
// "50%") into a number plus a detected pattern. Only strings need this:
|
|
97
|
+
// a numeric `<v>` is already typed, and its missing numberFormat means
|
|
98
|
+
// General — synthesizing a pattern (e.g. NUMBER "#") here changed how
|
|
99
|
+
// untouched imported cells render and export vs the source workbook.
|
|
100
|
+
if (!cellFormat.numberFormat &&
|
|
101
|
+
typeof rawValue === "string" &&
|
|
102
|
+
formulaValue === null) {
|
|
103
|
+
const [detectedValueType, detectedNumberFormatType, detectedNumberValue, detectedPattern,] = detectValueTypeAndPattern(rawValue, undefined);
|
|
104
|
+
if (detectedValueType === "numberValue" &&
|
|
105
|
+
detectedNumberFormatType &&
|
|
106
|
+
detectedNumberValue !== undefined) {
|
|
107
|
+
rawValue = detectedNumberValue;
|
|
108
|
+
cellFormat.numberFormat = {
|
|
109
|
+
type: detectedNumberFormatType,
|
|
110
|
+
...(detectedPattern ? { pattern: detectedPattern } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (enableCellXfsRegistry) {
|
|
115
|
+
const styleId = workbook.registerFormat(cellFormat);
|
|
116
|
+
ef = { sid: styleId };
|
|
117
|
+
}
|
|
118
|
+
// User entered value
|
|
119
|
+
let ue;
|
|
120
|
+
let ev;
|
|
121
|
+
const extendedValue = getExtendedValue(rawValue);
|
|
122
|
+
if (isError) {
|
|
123
|
+
// Error cell: preserve the Excel error code on `name` so the renderer
|
|
124
|
+
// shows the original code (#REF!, #DIV/0!, …) instead of a generic
|
|
125
|
+
// "#ERROR!" fallback.
|
|
126
|
+
const code = typeof rawValue === "string" ? rawValue : String(rawValue);
|
|
127
|
+
const errorValue = {
|
|
128
|
+
type: "Error",
|
|
129
|
+
name: code,
|
|
130
|
+
message: code,
|
|
131
|
+
};
|
|
132
|
+
if (formulaValue) {
|
|
133
|
+
ue = { fv: `=` + formulaValue };
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
ue = { ev: errorValue };
|
|
137
|
+
}
|
|
138
|
+
ev = { ev: errorValue };
|
|
139
|
+
}
|
|
140
|
+
else if (formulaValue) {
|
|
141
|
+
ue = {
|
|
142
|
+
fv: `=` + formulaValue,
|
|
143
|
+
};
|
|
144
|
+
ev = extendedValue;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
ue = extendedValue;
|
|
148
|
+
ev = extendedValue;
|
|
149
|
+
}
|
|
150
|
+
const hasFormat = enableCellXfsRegistry || Object.keys(cellFormat ?? {}).length > 0;
|
|
151
|
+
const hasValue = formulaValue !== null && formulaValue !== undefined && formulaValue !== "";
|
|
152
|
+
const hasRawValue = rawValue !== null && rawValue !== undefined;
|
|
153
|
+
if (!hasRawValue && !hasValue) {
|
|
154
|
+
return hasFormat ? { uf: ef } : null;
|
|
155
|
+
}
|
|
156
|
+
const pattern = cellFormat?.numberFormat?.pattern ?? "GENERAL";
|
|
157
|
+
// Skip persisting `fv` for formula cells — the renderer derives the
|
|
158
|
+
// formatted value at paint time from the formula's effective value and
|
|
159
|
+
// the precedent-derived number format. Excel ships a cached `fv` in the
|
|
160
|
+
// xml (the result from the last save), but persisting it here makes
|
|
161
|
+
// `getFormattedValue` short-circuit on that stale string and skip the
|
|
162
|
+
// derivation pipeline (so e.g. a formula that depends on a currency
|
|
163
|
+
// source won't pick up the currency pattern at render).
|
|
164
|
+
const fv = formulaValue || rawValue === null || rawValue === undefined
|
|
165
|
+
? undefined
|
|
166
|
+
: typeof rawValue === "string"
|
|
167
|
+
? rawValue
|
|
168
|
+
: ssfFormat(pattern, rawValue);
|
|
169
|
+
return {
|
|
170
|
+
...(hasFormat ? { uf: ef } : {}),
|
|
171
|
+
...(fv !== undefined ? { fv } : {}),
|
|
172
|
+
...(ue ? { ue } : {}),
|
|
173
|
+
...(ev ? { ev } : {}),
|
|
174
|
+
...(arrayFormulaRef ? { af: arrayFormulaRef } : {}),
|
|
175
|
+
...(sharedFormulaSpec ? { sf: sharedFormulaSpec } : {}),
|
|
176
|
+
...(protection ? { protection } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Build a format-only CellData for a style index that a cell inherits from
|
|
181
|
+
* its row (`<row customFormat="1" s="N">`) or column (`<col style="N">`)
|
|
182
|
+
* rather than carrying itself. Excel paints such styles across every cell
|
|
183
|
+
* of the row/column even where no `<c>` element exists, so the importer
|
|
184
|
+
* materializes them as phantom cells. Returns null when the style resolves
|
|
185
|
+
* to an empty format (nothing to paint or inherit).
|
|
186
|
+
*/
|
|
187
|
+
export function getCellDataForStyleIndex(styleIndex, workbook, enableCellXfsRegistry = false) {
|
|
188
|
+
const syntheticCell = { a$: { s: String(styleIndex) } };
|
|
189
|
+
const cellFormat = extractCellFormat(syntheticCell, workbook.workbookStyles);
|
|
190
|
+
if (!cellFormat || Object.keys(cellFormat).length === 0) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const protection = extractCellProtection(syntheticCell, workbook.workbookStyles);
|
|
194
|
+
const ef = enableCellXfsRegistry
|
|
195
|
+
? { sid: workbook.registerFormat(cellFormat) }
|
|
196
|
+
: cellFormat;
|
|
197
|
+
return {
|
|
198
|
+
uf: ef,
|
|
199
|
+
...(protection ? { protection } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export const getExtendedValue = (rawValue) => {
|
|
203
|
+
const rawValueType = typeof rawValue;
|
|
204
|
+
if (rawValueType === "boolean") {
|
|
205
|
+
return { bv: rawValue };
|
|
206
|
+
}
|
|
207
|
+
else if (rawValueType === "string") {
|
|
208
|
+
return { sv: rawValue };
|
|
209
|
+
}
|
|
210
|
+
else if (rawValueType === "number") {
|
|
211
|
+
return { nv: rawValue };
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Extract the raw and formatted value from a cell
|
|
216
|
+
*/
|
|
217
|
+
function extractCellValue(cell, workbook, activeSheetName) {
|
|
218
|
+
// Handle namespaced properties (x:v and x:f)
|
|
219
|
+
const cellValueNode = cell.v ?? cell["x:v"];
|
|
220
|
+
const cellValueText = getNodeText(cellValueNode);
|
|
221
|
+
const inlineStringValue = getInlineStringValue(cell);
|
|
222
|
+
const cellFormula = cell.f ?? cell["x:f"];
|
|
223
|
+
const cellType = cell.a$ && cell.a$.t ? cell.a$.t : "n"; // Default to number
|
|
224
|
+
const hasPrimitiveValue = cellValueText !== undefined && cellValueText !== "";
|
|
225
|
+
const hasInlineValue = inlineStringValue !== undefined && inlineStringValue !== "";
|
|
226
|
+
// Check if the cell is empty
|
|
227
|
+
if (!hasPrimitiveValue &&
|
|
228
|
+
!hasInlineValue &&
|
|
229
|
+
!cellFormula &&
|
|
230
|
+
!cell.t$ &&
|
|
231
|
+
cellType !== "s") {
|
|
232
|
+
return { rawValue: null, formulaValue: null, sharedStringIndex: undefined };
|
|
233
|
+
}
|
|
234
|
+
let rawValue = null;
|
|
235
|
+
let formulaValue = null;
|
|
236
|
+
let sharedStringIndex;
|
|
237
|
+
let isError = false;
|
|
238
|
+
let arrayFormulaRef;
|
|
239
|
+
let sharedFormulaSpec;
|
|
240
|
+
if (cellFormula) {
|
|
241
|
+
// Shared formula. Capture provenance so the exporter can re-emit
|
|
242
|
+
// `<f t="shared" si="N" ref="…">FORMULA</f>` on the leader and
|
|
243
|
+
// `<f t="shared" si="N"/>` on followers. The leader cell carries both
|
|
244
|
+
// `t="shared"` and `ref` (the range the formula applies to); follower
|
|
245
|
+
// cells carry `t="shared"` + `si` but no `ref`. Without this, the
|
|
246
|
+
// exporter expands every follower to a standalone `<f>` and the
|
|
247
|
+
// structural compression is lost on round-trip.
|
|
248
|
+
if (cellFormula.a$ && cellFormula.a$.t === "shared") {
|
|
249
|
+
const sharedIndex = cellFormula.a$.si;
|
|
250
|
+
const sharedFormula = cellFormula.t$;
|
|
251
|
+
const siNum = sharedIndex !== undefined && sharedIndex !== null
|
|
252
|
+
? Number(sharedIndex)
|
|
253
|
+
: NaN;
|
|
254
|
+
if (Number.isFinite(siNum)) {
|
|
255
|
+
sharedFormulaSpec = {
|
|
256
|
+
si: siNum,
|
|
257
|
+
...(cellFormula.a$.ref ? { ref: cellFormula.a$.ref } : {}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// if (!sharedFormulaMap.has(sharedIndex)) {
|
|
261
|
+
if (sharedFormula && workbook.sharedFormulaMap) {
|
|
262
|
+
workbook.sharedFormulaMap.set(sharedIndex, [sharedFormula, cell.a$.r]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Array formula. The `ref` attribute names the spill range
|
|
266
|
+
// ("G7:G7" single-cell, "B5:B20" multi-cell). Only the anchor cell
|
|
267
|
+
// carries the `<f>` element; spilled cells in the range have no formula
|
|
268
|
+
// and therefore never reach this branch. Default to the cell's own
|
|
269
|
+
// coordinate when `ref` is absent (some writers omit it for single-cell).
|
|
270
|
+
if (cellFormula.a$ && cellFormula.a$.t === "array") {
|
|
271
|
+
const cellRef = cell.a$?.r;
|
|
272
|
+
arrayFormulaRef =
|
|
273
|
+
cellFormula.a$.ref ?? (cellRef ? `${cellRef}:${cellRef}` : undefined);
|
|
274
|
+
}
|
|
275
|
+
// Shared-formula followers parse as `{ a$: { t: "shared", si }, t$: "" }` —
|
|
276
|
+
// the self-closing `<f t="shared" si=".."/>` has no text content but the
|
|
277
|
+
// XML parser still creates an empty `t$`. The original `t$ !== undefined`
|
|
278
|
+
// check matched that empty string, returning `""` and skipping the
|
|
279
|
+
// shared-formula translation branch below. Require `t$` to be a non-empty
|
|
280
|
+
// string so followers fall through and pick up the leader's formula.
|
|
281
|
+
if (typeof cellFormula === "string") {
|
|
282
|
+
formulaValue = cellFormula;
|
|
283
|
+
}
|
|
284
|
+
else if (typeof cellFormula.t$ === "string" && cellFormula.t$ !== "") {
|
|
285
|
+
formulaValue = cellFormula.t$;
|
|
286
|
+
}
|
|
287
|
+
else if (cellFormula._ !== undefined && cellFormula._ !== "") {
|
|
288
|
+
formulaValue = cellFormula._;
|
|
289
|
+
}
|
|
290
|
+
else if (cellFormula.a$ && cellFormula.a$.t === "shared") {
|
|
291
|
+
// Shared-formula reference. Excel's shared-formula semantics preserve
|
|
292
|
+
// absolute references (`$A$1`) verbatim and only shift relative refs.
|
|
293
|
+
// Pass `preserveAbsolutes: true` explicitly — the default shifts
|
|
294
|
+
// `$A$1` along with relative refs, which corrupts followers.
|
|
295
|
+
const sharedIndex = cellFormula.a$.si;
|
|
296
|
+
const sFormula = workbook.sharedFormulaMap?.get(sharedIndex);
|
|
297
|
+
if (sFormula) {
|
|
298
|
+
const [sharedFormula, sourceCell] = sFormula;
|
|
299
|
+
const sourceCellInterface = addressToCell(sourceCell);
|
|
300
|
+
const destinationCell = addressToCell(cell.a$.r);
|
|
301
|
+
if (sourceCellInterface && destinationCell) {
|
|
302
|
+
try {
|
|
303
|
+
const finalFormula = formulaToRelativeReference(sharedFormula, sourceCellInterface, destinationCell, activeSheetName ?? "", { preserveAbsolutes: true });
|
|
304
|
+
formulaValue = finalFormula;
|
|
305
|
+
}
|
|
306
|
+
catch (err) { }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (formulaValue) {
|
|
312
|
+
formulaValue = normalizeFormulaSheetNames(formulaValue);
|
|
313
|
+
}
|
|
314
|
+
switch (cellType) {
|
|
315
|
+
case "s": {
|
|
316
|
+
// Shared string
|
|
317
|
+
if (hasPrimitiveValue) {
|
|
318
|
+
const stringIndex = Number(cellValueText);
|
|
319
|
+
if (!Number.isNaN(stringIndex)) {
|
|
320
|
+
sharedStringIndex = stringIndex;
|
|
321
|
+
rawValue =
|
|
322
|
+
workbook.sharedStrings?.[stringIndex] ?? cellValueText ?? "";
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
rawValue = cellValueText ?? "";
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else if (inlineStringValue !== undefined) {
|
|
329
|
+
rawValue = inlineStringValue;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
rawValue = "";
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case "n": // Number
|
|
337
|
+
rawValue =
|
|
338
|
+
cellValueText !== undefined && cellValueText !== ""
|
|
339
|
+
? parseFloat(cellValueText)
|
|
340
|
+
: undefined;
|
|
341
|
+
// For time
|
|
342
|
+
// Check if this number should be treated as a date/time based on its format
|
|
343
|
+
if (cell.a$ && cell.a$.s) {
|
|
344
|
+
const styleIndex = parseInt(cell.a$.s);
|
|
345
|
+
// You'll need a function to get the numFmtId from the style
|
|
346
|
+
const xf = workbook.workbookStyles.getCellXfs(styleIndex);
|
|
347
|
+
const numFmtId = xf?.a$?.numFmtId ? parseInt(xf.a$.numFmtId) : NaN;
|
|
348
|
+
// Time formats typically have numFmtId values: 18-21 and 45-47
|
|
349
|
+
// Date formats typically have numFmtId values: 14-17 and 22
|
|
350
|
+
const isTimeFormat = [18, 19, 20, 21, 45, 46, 47].includes(numFmtId);
|
|
351
|
+
const isDateFormat = [14, 15, 16, 17, 22].includes(numFmtId);
|
|
352
|
+
if (isTimeFormat || isDateFormat) {
|
|
353
|
+
// Convert Excel date/time to JS Date object
|
|
354
|
+
// rawValue = toDate(rawValue);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
case "b": // Boolean
|
|
359
|
+
rawValue =
|
|
360
|
+
cellValueText === "1" ||
|
|
361
|
+
cellValueText === "TRUE" ||
|
|
362
|
+
cellValueText === "true";
|
|
363
|
+
break;
|
|
364
|
+
case "d": // Date
|
|
365
|
+
rawValue =
|
|
366
|
+
cellValueText !== undefined && cellValueText !== ""
|
|
367
|
+
? parseFloat(cellValueText)
|
|
368
|
+
: undefined;
|
|
369
|
+
// Convert Excel date to JS Date object
|
|
370
|
+
// rawValue = toDate(rawValue);
|
|
371
|
+
break;
|
|
372
|
+
case "e": // Error
|
|
373
|
+
rawValue = cellValueText;
|
|
374
|
+
isError = true;
|
|
375
|
+
break;
|
|
376
|
+
case "str": // Formula string result
|
|
377
|
+
rawValue = cellValueText ?? "";
|
|
378
|
+
break;
|
|
379
|
+
case "inlineStr":
|
|
380
|
+
rawValue = inlineStringValue ?? "";
|
|
381
|
+
break;
|
|
382
|
+
default:
|
|
383
|
+
rawValue = cellValueText;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
rawValue,
|
|
388
|
+
formulaValue,
|
|
389
|
+
sharedStringIndex,
|
|
390
|
+
isError,
|
|
391
|
+
arrayFormulaRef,
|
|
392
|
+
sharedFormulaSpec,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function normalizeFormulaSheetNames(formula) {
|
|
396
|
+
return formula.replace(/('([^']|'')+'|[^'!,]+)!/g, (match, sheetName) => {
|
|
397
|
+
if (!sheetName.startsWith("'")) {
|
|
398
|
+
return match;
|
|
399
|
+
}
|
|
400
|
+
const unescaped = sheetName.slice(1, -1).replace(/''/g, "'");
|
|
401
|
+
if (canUnquoteSheetName(unescaped)) {
|
|
402
|
+
return `${unescaped}!`;
|
|
403
|
+
}
|
|
404
|
+
return `'${unescaped.replace(/'/g, "''")}'!`;
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
function canUnquoteSheetName(sheetName) {
|
|
408
|
+
if (!/^[A-Za-z0-9_]+$/.test(sheetName)) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
if (/^[A-Za-z]{1,3}\d+$/.test(sheetName)) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if (/^R\d+C\d+$/i.test(sheetName)) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Extract the cell formatting information
|
|
421
|
+
*/
|
|
422
|
+
// Per OOXML §18.8.45 (CT_Xf), a cellXf with xfId="N" (N>0) inherits from
|
|
423
|
+
// cellStyleXfs[N] (the named-style xf table). For each property X
|
|
424
|
+
// (numFmt, font, fill, border, alignment), the cell-level value is used
|
|
425
|
+
// only when applyX="1"; otherwise the parent named style provides X.
|
|
426
|
+
// Build an effective xf that merges the cell-level overrides on top of the
|
|
427
|
+
// parent named style so downstream extractors see the resolved values.
|
|
428
|
+
function buildEffectiveXf(cellXf, parentXf) {
|
|
429
|
+
if (!parentXf)
|
|
430
|
+
return cellXf;
|
|
431
|
+
const cellAttrs = cellXf.a$ || {};
|
|
432
|
+
const parentAttrs = parentXf.a$ || {};
|
|
433
|
+
const resolvedAttrs = { ...cellAttrs };
|
|
434
|
+
// Only inherit when applyX="0" is explicitly set on the cell-level xf —
|
|
435
|
+
// that means "ignore my X, take it from the parent named style." When
|
|
436
|
+
// applyX is absent, Excel treats the cell-level X as the active value
|
|
437
|
+
// (this matches openpyxl, LibreOffice, and Excel itself). The earlier
|
|
438
|
+
// `applyX !== "1"` rule was too aggressive and silently replaced
|
|
439
|
+
// explicit cell-level fontIds with the parent's default.
|
|
440
|
+
const inheritIfApplyZero = (applyKey, attrKey) => {
|
|
441
|
+
if (cellAttrs[applyKey] === "0" && parentAttrs[attrKey] !== undefined) {
|
|
442
|
+
resolvedAttrs[attrKey] = parentAttrs[attrKey];
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
inheritIfApplyZero("applyNumberFormat", "numFmtId");
|
|
446
|
+
inheritIfApplyZero("applyFont", "fontId");
|
|
447
|
+
inheritIfApplyZero("applyFill", "fillId");
|
|
448
|
+
inheritIfApplyZero("applyBorder", "borderId");
|
|
449
|
+
const effectiveXf = { ...cellXf, a$: resolvedAttrs };
|
|
450
|
+
if (cellAttrs.applyAlignment === "0") {
|
|
451
|
+
const parentAlignment = getNode(parentXf, "alignment", "x:alignment");
|
|
452
|
+
if (parentAlignment) {
|
|
453
|
+
effectiveXf.alignment = parentAlignment;
|
|
454
|
+
delete effectiveXf["x:alignment"];
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return effectiveXf;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Read OOXML `<protection>` flags off a resolved xf. Returns undefined
|
|
461
|
+
* when the cell uses Excel's defaults (locked=true, hidden=false) so the
|
|
462
|
+
* caller only writes a `protection` field on the cell when it deviates.
|
|
463
|
+
*/
|
|
464
|
+
function readProtectionFromXf(xf) {
|
|
465
|
+
const node = getNode(xf, "protection", "x:protection");
|
|
466
|
+
if (!node)
|
|
467
|
+
return undefined;
|
|
468
|
+
const attrs = node.a$ || {};
|
|
469
|
+
const lockedRaw = attrs.locked;
|
|
470
|
+
const hiddenRaw = attrs.hidden;
|
|
471
|
+
const protection = {};
|
|
472
|
+
if (lockedRaw !== undefined && lockedRaw !== null) {
|
|
473
|
+
const locked = lockedRaw === "0" || lockedRaw === "false" || lockedRaw === false
|
|
474
|
+
? false
|
|
475
|
+
: true;
|
|
476
|
+
if (locked === false)
|
|
477
|
+
protection.locked = false;
|
|
478
|
+
}
|
|
479
|
+
if (hiddenRaw !== undefined && hiddenRaw !== null) {
|
|
480
|
+
const hidden = hiddenRaw === "1" || hiddenRaw === "true" || hiddenRaw === true;
|
|
481
|
+
if (hidden === true)
|
|
482
|
+
protection.hidden = true;
|
|
483
|
+
}
|
|
484
|
+
return Object.keys(protection).length ? protection : undefined;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Resolve the xf for a cell and extract its protection flags. Returns
|
|
488
|
+
* undefined when the cell has no style or the style is at Excel defaults.
|
|
489
|
+
*/
|
|
490
|
+
export function extractCellProtection(cell, workbookStyles) {
|
|
491
|
+
if (!cell?.a$ || !cell.a$.s)
|
|
492
|
+
return undefined;
|
|
493
|
+
const styleIndex = parseInt(cell.a$.s);
|
|
494
|
+
const cellXf = workbookStyles.getCellXfs(styleIndex);
|
|
495
|
+
if (!cellXf)
|
|
496
|
+
return undefined;
|
|
497
|
+
const xfIdStr = cellXf.a$ && cellXf.a$.xfId;
|
|
498
|
+
const xfId = xfIdStr !== undefined ? parseInt(xfIdStr) : 0;
|
|
499
|
+
const parentXf = xfId > 0 ? workbookStyles.getCellStyleXf(xfId) : null;
|
|
500
|
+
const xf = buildEffectiveXf(cellXf, parentXf);
|
|
501
|
+
return readProtectionFromXf(xf);
|
|
502
|
+
}
|
|
503
|
+
function extractCellFormat(cell, workbookStyles, mergeContext) {
|
|
504
|
+
// Default empty cell format
|
|
505
|
+
const cellFormat = {};
|
|
506
|
+
// If no style info, return default
|
|
507
|
+
if (!cell.a$ || !cell.a$.s) {
|
|
508
|
+
return cellFormat;
|
|
509
|
+
}
|
|
510
|
+
const styleIndex = parseInt(cell.a$.s);
|
|
511
|
+
const cellXf = workbookStyles.getCellXfs(styleIndex);
|
|
512
|
+
if (!cellXf)
|
|
513
|
+
return cellFormat;
|
|
514
|
+
// Resolve named-style inheritance. Cells using a built-in or custom named
|
|
515
|
+
// style ("Currency", "Comma", custom company styles) reference the parent
|
|
516
|
+
// via xfId; properties the cell doesn't explicitly override (applyX != "1")
|
|
517
|
+
// come from the parent. Skipping this resolution silently drops the named
|
|
518
|
+
// style's formatting (font, fill, border, numFmt).
|
|
519
|
+
const xfIdStr = cellXf.a$ && cellXf.a$.xfId;
|
|
520
|
+
const xfId = xfIdStr !== undefined ? parseInt(xfIdStr) : 0;
|
|
521
|
+
const parentXf = xfId > 0 ? workbookStyles.getCellStyleXf(xfId) : null;
|
|
522
|
+
const xf = buildEffectiveXf(cellXf, parentXf);
|
|
523
|
+
// Extract number format
|
|
524
|
+
const numberFormat = extractNumberFormat(xf, workbookStyles);
|
|
525
|
+
if (!(numberFormat?.type === "GENERAL" && !numberFormat.pattern)) {
|
|
526
|
+
cellFormat.numberFormat = numberFormat;
|
|
527
|
+
}
|
|
528
|
+
// `[Red]`/`[ColorN]` number-format colors are a render-time concern:
|
|
529
|
+
// the canvas grid resolves the pattern's active section color against
|
|
530
|
+
// the CURRENT value at paint time. Import must store the cell's real
|
|
531
|
+
// font color — baking the format-derived color into `textFormat.color`
|
|
532
|
+
// froze the import-time value's color onto the cell (a cell that later
|
|
533
|
+
// changed sign kept the stale color) and destroyed the cell's actual
|
|
534
|
+
// explicit font color.
|
|
535
|
+
cellFormat.textFormat = extractTextFormat(xf, workbookStyles);
|
|
536
|
+
// Only set horizontalAlignment when the source xf carried an explicit
|
|
537
|
+
// <alignment horizontal="…"/>. Excel's default behavior (right-align
|
|
538
|
+
// numbers, left-align text) is a render-time concern, not a model-level
|
|
539
|
+
// attribute — fabricating it on import produces an alignment xf on export
|
|
540
|
+
// that wasn't in the source.
|
|
541
|
+
const xfAlignment = getNode(xf, "alignment", "x:alignment");
|
|
542
|
+
if (xfAlignment) {
|
|
543
|
+
const alignment = xfAlignment.a$ || {};
|
|
544
|
+
if (alignment.horizontal) {
|
|
545
|
+
cellFormat.horizontalAlignment = alignment.horizontal;
|
|
546
|
+
}
|
|
547
|
+
const verticalAlignment = mapVerticalAlignment(alignment.vertical);
|
|
548
|
+
if (verticalAlignment && verticalAlignment !== "bottom") {
|
|
549
|
+
cellFormat.verticalAlignment = verticalAlignment;
|
|
550
|
+
}
|
|
551
|
+
const wrapText = alignment.wrapText === "1" || alignment.wrapText === "true"
|
|
552
|
+
? "wrap"
|
|
553
|
+
: "overflow";
|
|
554
|
+
if (wrapText !== "overflow") {
|
|
555
|
+
cellFormat.wrapStrategy = wrapText;
|
|
556
|
+
}
|
|
557
|
+
const indent = alignment.indent ? parseInt(alignment.indent) : null;
|
|
558
|
+
if (indent)
|
|
559
|
+
cellFormat.indent = indent;
|
|
560
|
+
const relativeIndent = alignment.relativeIndent
|
|
561
|
+
? parseInt(alignment.relativeIndent)
|
|
562
|
+
: null;
|
|
563
|
+
if (relativeIndent)
|
|
564
|
+
cellFormat.relativeIndent = relativeIndent;
|
|
565
|
+
if (alignment.textRotation) {
|
|
566
|
+
const rotation = parseInt(alignment.textRotation);
|
|
567
|
+
cellFormat.textRotation = rotation === 255 ? "vertical" : rotation;
|
|
568
|
+
}
|
|
569
|
+
if (alignment.shrinkToFit === "1" || alignment.shrinkToFit === "true") {
|
|
570
|
+
cellFormat.shrinkToFit = true;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// Extract background color + pattern (if any). For solid fills only
|
|
574
|
+
// `backgroundColor` is populated (existing behavior). For patterned fills
|
|
575
|
+
// all three of `fillPattern` / `foregroundColor` / `backgroundColor` are
|
|
576
|
+
// set so the renderer can tile the pattern over the under-layer.
|
|
577
|
+
const fillInfo = extractFillInfo(xf, workbookStyles);
|
|
578
|
+
if (fillInfo) {
|
|
579
|
+
if (fillInfo.backgroundColor !== undefined) {
|
|
580
|
+
cellFormat.backgroundColor = fillInfo.backgroundColor;
|
|
581
|
+
}
|
|
582
|
+
if (fillInfo.foregroundColor !== undefined) {
|
|
583
|
+
cellFormat.foregroundColor = fillInfo.foregroundColor;
|
|
584
|
+
}
|
|
585
|
+
if (fillInfo.fillPattern !== undefined) {
|
|
586
|
+
cellFormat.fillPattern = fillInfo.fillPattern;
|
|
587
|
+
}
|
|
588
|
+
if (fillInfo.gradient !== undefined) {
|
|
589
|
+
cellFormat.gradient = fillInfo.gradient;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Extract borders
|
|
593
|
+
let borders = extractBorders(xf, workbookStyles);
|
|
594
|
+
borders = mergeBordersForAnchorCell(borders, cell, workbookStyles, mergeContext);
|
|
595
|
+
if (borders)
|
|
596
|
+
cellFormat.borders = borders;
|
|
597
|
+
return cellFormat;
|
|
598
|
+
}
|
|
599
|
+
function mergeBordersForAnchorCell(borders, cell, workbookStyles, mergeContext) {
|
|
600
|
+
if (!mergeContext)
|
|
601
|
+
return borders;
|
|
602
|
+
const cellRef = cell?.a$?.r;
|
|
603
|
+
if (!cellRef)
|
|
604
|
+
return borders;
|
|
605
|
+
const cellPosition = addressToCell(cellRef);
|
|
606
|
+
if (!cellPosition)
|
|
607
|
+
return borders;
|
|
608
|
+
const { range, rowCellsByColumnIndex } = mergeContext;
|
|
609
|
+
const isAnchorCell = cellPosition.rowIndex === range.startRowIndex &&
|
|
610
|
+
cellPosition.columnIndex === range.startColumnIndex;
|
|
611
|
+
if (!isAnchorCell)
|
|
612
|
+
return borders;
|
|
613
|
+
const mergedBorders = borders ? { ...borders } : {};
|
|
614
|
+
const leftCell = rowCellsByColumnIndex.get(range.startColumnIndex);
|
|
615
|
+
const rightCell = rowCellsByColumnIndex.get(range.endColumnIndex);
|
|
616
|
+
const leftBorders = leftCell
|
|
617
|
+
? extractBordersFromCell(leftCell, workbookStyles)
|
|
618
|
+
: null;
|
|
619
|
+
const rightBorders = rightCell
|
|
620
|
+
? extractBordersFromCell(rightCell, workbookStyles)
|
|
621
|
+
: null;
|
|
622
|
+
if (!mergedBorders.left && leftBorders?.left) {
|
|
623
|
+
mergedBorders.left = leftBorders.left;
|
|
624
|
+
}
|
|
625
|
+
if (!mergedBorders.right && rightBorders?.right) {
|
|
626
|
+
mergedBorders.right = rightBorders.right;
|
|
627
|
+
}
|
|
628
|
+
if (range.startRowIndex === range.endRowIndex) {
|
|
629
|
+
const rowEdgeCell = leftCell ?? rightCell;
|
|
630
|
+
const rowBorders = rowEdgeCell
|
|
631
|
+
? extractBordersFromCell(rowEdgeCell, workbookStyles)
|
|
632
|
+
: null;
|
|
633
|
+
if (!mergedBorders.top && rowBorders?.top) {
|
|
634
|
+
mergedBorders.top = rowBorders.top;
|
|
635
|
+
}
|
|
636
|
+
if (!mergedBorders.bottom && rowBorders?.bottom) {
|
|
637
|
+
mergedBorders.bottom = rowBorders.bottom;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return Object.keys(mergedBorders).length > 0 ? mergedBorders : null;
|
|
641
|
+
}
|
|
642
|
+
function extractBordersFromCell(cell, workbookStyles) {
|
|
643
|
+
if (!cell?.a$?.s)
|
|
644
|
+
return null;
|
|
645
|
+
const styleIndex = parseInt(cell.a$.s);
|
|
646
|
+
const xf = workbookStyles.getCellXfs(styleIndex);
|
|
647
|
+
if (!xf)
|
|
648
|
+
return null;
|
|
649
|
+
return extractBorders(xf, workbookStyles);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Extract text formatting information from a style
|
|
653
|
+
*/
|
|
654
|
+
function extractTextFormat(xf, workbookStyles) {
|
|
655
|
+
if (!xf.a$ || !xf.a$.fontId) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
const fontId = parseInt(xf.a$.fontId);
|
|
659
|
+
const font = workbookStyles.getFont(fontId);
|
|
660
|
+
return fontNodeToTextFormat(font, workbookStyles);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Parse a raw styles.xml `<font>` node into a TextFormat. Also used for the
|
|
664
|
+
* workbook default font (fonts[0]) so the exporter can round-trip it.
|
|
665
|
+
*/
|
|
666
|
+
export function fontNodeToTextFormat(font, workbookStyles) {
|
|
667
|
+
if (!font) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
// Create text format object
|
|
671
|
+
const textFormat = {};
|
|
672
|
+
// Extract properties. An explicit `<name val>` always wins; a font that
|
|
673
|
+
// instead carries `<scheme val="major|minor"/>` follows the workbook
|
|
674
|
+
// theme's `<a:fontScheme>` typefaces (headings/body). Without this,
|
|
675
|
+
// scheme-only fonts (Excel writes them for theme-styled cells) had no
|
|
676
|
+
// fontFamily at all and silently rendered in the renderer default.
|
|
677
|
+
// Missing theme → leave unresolved (mirrors rnc-import's
|
|
678
|
+
// `resolve_scheme_fonts`).
|
|
679
|
+
const fontName = getNode(font, "name", "x:name");
|
|
680
|
+
if (fontName?.a$?.val) {
|
|
681
|
+
textFormat.fontFamily = fontName.a$.val;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
const schemeVal = getNode(font, "scheme", "x:scheme")?.a$?.val;
|
|
685
|
+
const themeFonts = workbookStyles.themeFonts;
|
|
686
|
+
const resolved = schemeVal === "major"
|
|
687
|
+
? themeFonts?.major
|
|
688
|
+
: schemeVal === "minor"
|
|
689
|
+
? themeFonts?.minor
|
|
690
|
+
: undefined;
|
|
691
|
+
if (resolved) {
|
|
692
|
+
textFormat.fontFamily = resolved;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const fontSize = getNode(font, "sz", "x:sz");
|
|
696
|
+
if (fontSize?.a$?.val) {
|
|
697
|
+
textFormat.fontSize = parseInt(fontSize.a$.val);
|
|
698
|
+
}
|
|
699
|
+
const fontColor = getNode(font, "color", "x:color");
|
|
700
|
+
if (fontColor) {
|
|
701
|
+
textFormat.color = extractColor(fontColor, workbookStyles.indexedColors);
|
|
702
|
+
}
|
|
703
|
+
// Boolean font properties. OOXML allows `<X/>` (truthy by default),
|
|
704
|
+
// `<X val="true"/>` or `<X val="1"/>` (truthy), and `<X val="false"/>`,
|
|
705
|
+
// `<X val="0"/>`, or `<u val="none"/>` (explicitly falsy). The original
|
|
706
|
+
// code only checked element presence and treated `<u val="none"/>` as
|
|
707
|
+
// underlined — which fabricated underline on every cell whose source
|
|
708
|
+
// font carried that explicit "no underline" marker.
|
|
709
|
+
const isTruthyFlagNode = (node) => {
|
|
710
|
+
if (node === undefined || node === null)
|
|
711
|
+
return false;
|
|
712
|
+
const val = typeof node === "object"
|
|
713
|
+
? (node.a$?.val ?? node["#text"] ?? node)
|
|
714
|
+
: node;
|
|
715
|
+
if (val === undefined || val === null || val === true)
|
|
716
|
+
return true;
|
|
717
|
+
const str = String(val).toLowerCase();
|
|
718
|
+
if (str === "" || str === "true" || str === "1")
|
|
719
|
+
return true;
|
|
720
|
+
return false;
|
|
721
|
+
};
|
|
722
|
+
if (isTruthyFlagNode(getNode(font, "b", "x:b")))
|
|
723
|
+
textFormat.bold = true;
|
|
724
|
+
if (isTruthyFlagNode(getNode(font, "i", "x:i")))
|
|
725
|
+
textFormat.italic = true;
|
|
726
|
+
if (isTruthyFlagNode(getNode(font, "strike", "x:strike"))) {
|
|
727
|
+
textFormat.strikethrough = true;
|
|
728
|
+
}
|
|
729
|
+
// Underline is not a boolean — `<u val="none"/>` means no underline,
|
|
730
|
+
// anything else (including absent val attribute) is some form of
|
|
731
|
+
// underline. Treat "none" specifically as falsy.
|
|
732
|
+
const uNode = getNode(font, "u", "x:u");
|
|
733
|
+
if (uNode !== undefined) {
|
|
734
|
+
const uVal = typeof uNode === "object"
|
|
735
|
+
? (uNode.a$?.val ?? uNode["#text"])
|
|
736
|
+
: uNode;
|
|
737
|
+
const uStr = uVal === undefined || uVal === null ? "" : String(uVal).toLowerCase();
|
|
738
|
+
if (uStr !== "none") {
|
|
739
|
+
textFormat.underline = true;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const vertAlignNode = getNode(font, "vertAlign", "x:vertAlign");
|
|
743
|
+
if (vertAlignNode?.a$?.val) {
|
|
744
|
+
const v = String(vertAlignNode.a$.val).toLowerCase();
|
|
745
|
+
if (v === "superscript")
|
|
746
|
+
textFormat.baselineOffset = "superscript";
|
|
747
|
+
else if (v === "subscript")
|
|
748
|
+
textFormat.baselineOffset = "subscript";
|
|
749
|
+
}
|
|
750
|
+
return textFormat;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Extract color information
|
|
754
|
+
*/
|
|
755
|
+
export function extractColor(colorObj, indexedColors) {
|
|
756
|
+
if (!colorObj || !colorObj.a$) {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
if (colorObj.a$.auto &&
|
|
760
|
+
(colorObj.a$.auto === "1" || colorObj.a$.auto === "true")) {
|
|
761
|
+
return {
|
|
762
|
+
theme: 1,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
if (colorObj.a$.indexed) {
|
|
766
|
+
const index = parseInt(colorObj.a$.indexed);
|
|
767
|
+
// Last color
|
|
768
|
+
if (index === 64) {
|
|
769
|
+
return {
|
|
770
|
+
theme: 1,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (indexedColors && indexedColors.length > 0) {
|
|
774
|
+
const paletteColor = indexedColors[index];
|
|
775
|
+
if (paletteColor) {
|
|
776
|
+
return paletteColor;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return IndexedColors[index];
|
|
780
|
+
}
|
|
781
|
+
else if (colorObj.a$.rgb) {
|
|
782
|
+
// OOXML §18.3.1.15 says <color rgb> is 8-char aRGB, but real-world
|
|
783
|
+
// files commonly emit the 6-char RGB form (alpha implied FF — this
|
|
784
|
+
// is what Office's default theme accent colors look like in many
|
|
785
|
+
// sheets). Only strip the alpha-prefix when the input is actually
|
|
786
|
+
// 8 chars; otherwise pass through verbatim so the exporter can
|
|
787
|
+
// canonicalize 6→8 by prepending FF. Blindly substring(2) turned
|
|
788
|
+
// `44546A` into `546A`, which downstream consumers (openpyxl) then
|
|
789
|
+
// refuse to load.
|
|
790
|
+
const raw = String(colorObj.a$.rgb).trim();
|
|
791
|
+
return "#" + (raw.length === 8 ? raw.substring(2) : raw);
|
|
792
|
+
}
|
|
793
|
+
else if (colorObj.a$.theme) {
|
|
794
|
+
return {
|
|
795
|
+
theme: parseInt(colorObj.a$.theme),
|
|
796
|
+
tint: colorObj.a$.tint ? parseFloat(colorObj.a$.tint) : null,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Extract number format information
|
|
803
|
+
*/
|
|
804
|
+
function extractNumberFormat(xf, workbookStyles) {
|
|
805
|
+
if (!xf.a$ || !xf.a$.numFmtId)
|
|
806
|
+
return null;
|
|
807
|
+
const numFmtId = parseInt(xf.a$.numFmtId);
|
|
808
|
+
let formatCode = standardExcelFormatCodeById.get(numFmtId) ?? null;
|
|
809
|
+
// Check for custom number formats
|
|
810
|
+
if (workbookStyles.numFmts?.length) {
|
|
811
|
+
const numFmts = workbookStyles.numFmts;
|
|
812
|
+
const customFormat = numFmts.find((fmt) => fmt.a$ && parseInt(fmt.a$.numFmtId) === numFmtId);
|
|
813
|
+
if (customFormat && customFormat.a$ && customFormat.a$.formatCode) {
|
|
814
|
+
formatCode = customFormat.a$.formatCode;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// Map number format type based on id or format code
|
|
818
|
+
const type = mapNumberFormatType(numFmtId, formatCode) ?? "GENERAL";
|
|
819
|
+
return {
|
|
820
|
+
type,
|
|
821
|
+
pattern: formatCode,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Map Excel number format ID to our NumberFormatType
|
|
826
|
+
*/
|
|
827
|
+
function mapNumberFormatType(numFmtId, formatCode) {
|
|
828
|
+
// Built-in format ids
|
|
829
|
+
switch (numFmtId) {
|
|
830
|
+
case 0:
|
|
831
|
+
return "GENERAL";
|
|
832
|
+
case 1:
|
|
833
|
+
case 2:
|
|
834
|
+
case 3:
|
|
835
|
+
case 4:
|
|
836
|
+
case 6:
|
|
837
|
+
case 7:
|
|
838
|
+
case 8:
|
|
839
|
+
case 11:
|
|
840
|
+
return "NUMBER";
|
|
841
|
+
case 14:
|
|
842
|
+
case 15:
|
|
843
|
+
case 16:
|
|
844
|
+
case 17:
|
|
845
|
+
return "DATE";
|
|
846
|
+
case 18:
|
|
847
|
+
case 19:
|
|
848
|
+
case 20:
|
|
849
|
+
case 21:
|
|
850
|
+
case 45:
|
|
851
|
+
case 46:
|
|
852
|
+
case 47:
|
|
853
|
+
return "TIME";
|
|
854
|
+
case 22:
|
|
855
|
+
return "DATE_TIME";
|
|
856
|
+
case 9:
|
|
857
|
+
case 10:
|
|
858
|
+
return "PERCENT";
|
|
859
|
+
case 12:
|
|
860
|
+
case 13:
|
|
861
|
+
return "FRACTION";
|
|
862
|
+
case 48:
|
|
863
|
+
case 49:
|
|
864
|
+
return "TEXT";
|
|
865
|
+
case 5:
|
|
866
|
+
case 37:
|
|
867
|
+
case 38:
|
|
868
|
+
return "CURRENCY";
|
|
869
|
+
case 39:
|
|
870
|
+
case 40:
|
|
871
|
+
case 41:
|
|
872
|
+
case 42:
|
|
873
|
+
case 43:
|
|
874
|
+
case 44:
|
|
875
|
+
return "ACCOUNTING";
|
|
876
|
+
}
|
|
877
|
+
// For custom formats, analyze the pattern
|
|
878
|
+
if (formatCode) {
|
|
879
|
+
// Get the first section (positive numbers format)
|
|
880
|
+
const mainSection = formatCode.split(";")[0];
|
|
881
|
+
const { cleanedSection, hasBracketedTimeToken } = stripQuotedAndBracketLiterals(mainSection);
|
|
882
|
+
// Check for date components - d, m, y that aren't escaped
|
|
883
|
+
let hasDateComponent = false;
|
|
884
|
+
let i = 0;
|
|
885
|
+
while (i < cleanedSection.length) {
|
|
886
|
+
if (cleanedSection[i] === "\\") {
|
|
887
|
+
// Skip escaped character
|
|
888
|
+
i += 2;
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
const char = cleanedSection[i].toLowerCase();
|
|
892
|
+
if (char === "d" || char === "m" || char === "y") {
|
|
893
|
+
hasDateComponent = true;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
i++;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (hasDateComponent) {
|
|
900
|
+
// Check if it also has time components - h, s, :
|
|
901
|
+
let hasTimeComponent = false;
|
|
902
|
+
i = 0;
|
|
903
|
+
while (i < cleanedSection.length) {
|
|
904
|
+
if (cleanedSection[i] === "\\") {
|
|
905
|
+
// Skip escaped character
|
|
906
|
+
i += 2;
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
const char = cleanedSection[i].toLowerCase();
|
|
910
|
+
if (char === "h" || char === "s" || char === ":") {
|
|
911
|
+
hasTimeComponent = true;
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
i++;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (hasTimeComponent || hasBracketedTimeToken) {
|
|
918
|
+
return "DATE_TIME";
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
return "DATE";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// Check for currency symbols. They appear in formats as either:
|
|
925
|
+
// - a quoted literal: "$"#,##0 (Excel writes it this way)
|
|
926
|
+
// - a bracketed currency: [$$-409]#,##0 (international forms)
|
|
927
|
+
// - a bare symbol: $#,##0 (older format strings)
|
|
928
|
+
// `cleanedSection` has stripped quotes and brackets, so we check the
|
|
929
|
+
// RAW main section, but first remove the locale specifier pattern
|
|
930
|
+
// `[$-XXXX]` (the `$` there is structural, not a currency symbol).
|
|
931
|
+
const localeSpecifierPattern = /\[\$-[0-9A-Fa-f]+\]/g;
|
|
932
|
+
const rawWithoutLocale = mainSection.replace(localeSpecifierPattern, "");
|
|
933
|
+
if (rawWithoutLocale.includes("$") ||
|
|
934
|
+
rawWithoutLocale.includes("€") ||
|
|
935
|
+
rawWithoutLocale.includes("£") ||
|
|
936
|
+
rawWithoutLocale.includes("¥")) {
|
|
937
|
+
return "CURRENCY";
|
|
938
|
+
}
|
|
939
|
+
// Other types
|
|
940
|
+
if (cleanedSection.includes("%")) {
|
|
941
|
+
return "PERCENT";
|
|
942
|
+
}
|
|
943
|
+
if (cleanedSection.includes("/")) {
|
|
944
|
+
return "FRACTION";
|
|
945
|
+
}
|
|
946
|
+
// Check for time patterns that aren't escaped
|
|
947
|
+
let hasTimePattern = false;
|
|
948
|
+
i = 0;
|
|
949
|
+
while (i < cleanedSection.length) {
|
|
950
|
+
if (cleanedSection[i] === "\\") {
|
|
951
|
+
// Skip escaped character
|
|
952
|
+
i += 2;
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
const char = cleanedSection[i].toLowerCase();
|
|
956
|
+
if (char === "h" ||
|
|
957
|
+
char === "s" ||
|
|
958
|
+
char === ":" ||
|
|
959
|
+
cleanedSection.substring(i).startsWith("AM/PM") ||
|
|
960
|
+
cleanedSection.substring(i).startsWith("am/pm")) {
|
|
961
|
+
hasTimePattern = true;
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
i++;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (hasTimePattern || hasBracketedTimeToken) {
|
|
968
|
+
return "TIME";
|
|
969
|
+
}
|
|
970
|
+
if (cleanedSection.match(/[0#?]+(\.[0#?]+)?[eE][+-]?[0#?]+/)) {
|
|
971
|
+
return "SCIENTIFIC";
|
|
972
|
+
}
|
|
973
|
+
if (cleanedSection.includes("@")) {
|
|
974
|
+
return "TEXT";
|
|
975
|
+
}
|
|
976
|
+
const hasNumberPlaceholder = cleanedSection.match(/[0#?]/);
|
|
977
|
+
if (!hasNumberPlaceholder) {
|
|
978
|
+
const trimmed = cleanedSection.trim();
|
|
979
|
+
if (trimmed === "" || trimmed.toLowerCase() === "general") {
|
|
980
|
+
return "GENERAL";
|
|
981
|
+
}
|
|
982
|
+
return "TEXT";
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// Default to number for any other custom format
|
|
986
|
+
return "NUMBER";
|
|
987
|
+
}
|
|
988
|
+
function stripQuotedAndBracketLiterals(section) {
|
|
989
|
+
let result = "";
|
|
990
|
+
let inQuotes = false;
|
|
991
|
+
let i = 0;
|
|
992
|
+
let hasBracketedTimeToken = false;
|
|
993
|
+
while (i < section.length) {
|
|
994
|
+
const char = section[i];
|
|
995
|
+
if (char === "\\") {
|
|
996
|
+
// Skip escaped character entirely.
|
|
997
|
+
i += 2;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (char === '"') {
|
|
1001
|
+
if (inQuotes && section[i + 1] === '"') {
|
|
1002
|
+
// Escaped quote inside a quoted literal.
|
|
1003
|
+
i += 2;
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
inQuotes = !inQuotes;
|
|
1007
|
+
i += 1;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (inQuotes) {
|
|
1011
|
+
i += 1;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (char === "[") {
|
|
1015
|
+
const end = section.indexOf("]", i + 1);
|
|
1016
|
+
const content = end === -1 ? section.substring(i + 1) : section.substring(i + 1, end);
|
|
1017
|
+
if (content.match(/^[hms]+$/i)) {
|
|
1018
|
+
hasBracketedTimeToken = true;
|
|
1019
|
+
}
|
|
1020
|
+
i = end === -1 ? section.length : end + 1;
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
result += char;
|
|
1024
|
+
i += 1;
|
|
1025
|
+
}
|
|
1026
|
+
return { cleanedSection: result, hasBracketedTimeToken };
|
|
1027
|
+
}
|
|
1028
|
+
// Patterned-fill type → engine enum. Excel writes ~18 distinct
|
|
1029
|
+
// patternType values; the engine carries the same names. `none` is treated
|
|
1030
|
+
// as "no fill" (returns null).
|
|
1031
|
+
const KNOWN_FILL_PATTERNS = new Set([
|
|
1032
|
+
"solid",
|
|
1033
|
+
"mediumGray",
|
|
1034
|
+
"darkGray",
|
|
1035
|
+
"lightGray",
|
|
1036
|
+
"darkHorizontal",
|
|
1037
|
+
"darkVertical",
|
|
1038
|
+
"darkDown",
|
|
1039
|
+
"darkUp",
|
|
1040
|
+
"darkGrid",
|
|
1041
|
+
"darkTrellis",
|
|
1042
|
+
"lightHorizontal",
|
|
1043
|
+
"lightVertical",
|
|
1044
|
+
"lightDown",
|
|
1045
|
+
"lightUp",
|
|
1046
|
+
"lightGrid",
|
|
1047
|
+
"lightTrellis",
|
|
1048
|
+
"gray125",
|
|
1049
|
+
"gray0625",
|
|
1050
|
+
]);
|
|
1051
|
+
/**
|
|
1052
|
+
* Extract patterned-fill information (pattern type + fg/bg colors). For
|
|
1053
|
+
* `solid` patterns the foreground color is hoisted to `backgroundColor` so
|
|
1054
|
+
* the existing renderer / consumers keep working — that's also OOXML's
|
|
1055
|
+
* authoring convention (a "solid" fill puts the visible color in fgColor).
|
|
1056
|
+
*
|
|
1057
|
+
* Also handles `<gradientFill>` (Excel Phase 8) — both `linear` (with a
|
|
1058
|
+
* `degree`) and `path` (radial/rectangular, with `left`/`right`/`top`/
|
|
1059
|
+
* `bottom` insets). Gradient fills coexist with — but take precedence
|
|
1060
|
+
* over — pattern fills, matching Excel's UI (you pick ONE or the other).
|
|
1061
|
+
*/
|
|
1062
|
+
function extractFillInfo(xf, workbookStyles) {
|
|
1063
|
+
if (!xf.a$ || !xf.a$.fillId)
|
|
1064
|
+
return null;
|
|
1065
|
+
const fillId = parseInt(xf.a$.fillId);
|
|
1066
|
+
if (fillId === 0)
|
|
1067
|
+
return null;
|
|
1068
|
+
const fill = workbookStyles.getFill(fillId);
|
|
1069
|
+
if (!fill)
|
|
1070
|
+
return null;
|
|
1071
|
+
// <gradientFill> branch — extract type + stops + axis attrs.
|
|
1072
|
+
const gradientFill = getNode(fill, "gradientFill", "x:gradientFill");
|
|
1073
|
+
if (gradientFill) {
|
|
1074
|
+
const gradient = extractGradientFill(gradientFill, workbookStyles);
|
|
1075
|
+
if (gradient)
|
|
1076
|
+
return { gradient };
|
|
1077
|
+
}
|
|
1078
|
+
const patternFill = getNode(fill, "patternFill", "x:patternFill");
|
|
1079
|
+
if (!patternFill)
|
|
1080
|
+
return null;
|
|
1081
|
+
const patternAttrs = patternFill.a$ ?? {};
|
|
1082
|
+
const patternType = patternAttrs.patternType;
|
|
1083
|
+
if (patternType === "none" || patternType === undefined)
|
|
1084
|
+
return null;
|
|
1085
|
+
const fgColorNode = getNode(patternFill, "fgColor", "x:fgColor");
|
|
1086
|
+
const bgColorNode = getNode(patternFill, "bgColor", "x:bgColor");
|
|
1087
|
+
const fgColor = fgColorNode
|
|
1088
|
+
? extractColor(fgColorNode, workbookStyles.indexedColors)
|
|
1089
|
+
: undefined;
|
|
1090
|
+
const bgColor = bgColorNode
|
|
1091
|
+
? extractColor(bgColorNode, workbookStyles.indexedColors)
|
|
1092
|
+
: undefined;
|
|
1093
|
+
// gray125 is Excel's default placeholder fill — historically dropped on
|
|
1094
|
+
// import. Keep that behavior unless the file explicitly carries colors,
|
|
1095
|
+
// in which case it's a real pattern the user chose.
|
|
1096
|
+
if (patternType === "gray125" && !fgColor && !bgColor)
|
|
1097
|
+
return null;
|
|
1098
|
+
// For "solid" pattern, hoist the foreground into the legacy
|
|
1099
|
+
// `backgroundColor` slot so the existing renderer path stays the
|
|
1100
|
+
// hot path. The `fillPattern: "solid"` field is still emitted for
|
|
1101
|
+
// round-trip identity on export.
|
|
1102
|
+
if (patternType === "solid") {
|
|
1103
|
+
return {
|
|
1104
|
+
fillPattern: "solid",
|
|
1105
|
+
...(fgColor !== undefined ? { backgroundColor: fgColor } : {}),
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
// True patterned fill — preserve all three fields.
|
|
1109
|
+
return {
|
|
1110
|
+
fillPattern: KNOWN_FILL_PATTERNS.has(patternType) ? patternType : undefined,
|
|
1111
|
+
...(fgColor !== undefined ? { foregroundColor: fgColor } : {}),
|
|
1112
|
+
...(bgColor !== undefined ? { backgroundColor: bgColor } : {}),
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Parse a `<gradientFill>` node into our `GradientFill` shape. Returns
|
|
1117
|
+
* `null` if the gradient is unusable (no parseable stops). Stops are
|
|
1118
|
+
* sorted by `position` ascending to match the renderer's expectations.
|
|
1119
|
+
*/
|
|
1120
|
+
function extractGradientFill(gradientFill, workbookStyles) {
|
|
1121
|
+
const attrs = gradientFill.a$ ?? {};
|
|
1122
|
+
const type = attrs.type === "path" ? "path" : "linear";
|
|
1123
|
+
// The fast-xml-parser surfaces repeated <stop> children as either an
|
|
1124
|
+
// array (multiple) or a single object (one). Normalize.
|
|
1125
|
+
let rawStops = getNode(gradientFill, "stop", "x:stop") ??
|
|
1126
|
+
getNode(gradientFill, "stops", "x:stops");
|
|
1127
|
+
if (rawStops == null)
|
|
1128
|
+
return null;
|
|
1129
|
+
if (!Array.isArray(rawStops))
|
|
1130
|
+
rawStops = [rawStops];
|
|
1131
|
+
const stops = [];
|
|
1132
|
+
for (const s of rawStops) {
|
|
1133
|
+
if (!s)
|
|
1134
|
+
continue;
|
|
1135
|
+
const sAttrs = s.a$ ?? {};
|
|
1136
|
+
const positionRaw = sAttrs.position;
|
|
1137
|
+
const position = typeof positionRaw === "number"
|
|
1138
|
+
? positionRaw
|
|
1139
|
+
: positionRaw != null
|
|
1140
|
+
? parseFloat(positionRaw)
|
|
1141
|
+
: NaN;
|
|
1142
|
+
if (!Number.isFinite(position))
|
|
1143
|
+
continue;
|
|
1144
|
+
const colorNode = getNode(s, "color", "x:color");
|
|
1145
|
+
if (!colorNode)
|
|
1146
|
+
continue;
|
|
1147
|
+
const color = extractColor(colorNode, workbookStyles.indexedColors);
|
|
1148
|
+
if (color == null)
|
|
1149
|
+
continue;
|
|
1150
|
+
stops.push({ position, color: String(color) });
|
|
1151
|
+
}
|
|
1152
|
+
if (stops.length === 0)
|
|
1153
|
+
return null;
|
|
1154
|
+
stops.sort((a, b) => a.position - b.position);
|
|
1155
|
+
const numAttr = (k) => {
|
|
1156
|
+
const v = attrs[k];
|
|
1157
|
+
if (v == null)
|
|
1158
|
+
return undefined;
|
|
1159
|
+
const n = typeof v === "number" ? v : parseFloat(v);
|
|
1160
|
+
return Number.isFinite(n) ? n : undefined;
|
|
1161
|
+
};
|
|
1162
|
+
return {
|
|
1163
|
+
type,
|
|
1164
|
+
...(type === "linear"
|
|
1165
|
+
? { degree: numAttr("degree") ?? 0 }
|
|
1166
|
+
: {
|
|
1167
|
+
left: numAttr("left") ?? 0,
|
|
1168
|
+
right: numAttr("right") ?? 0,
|
|
1169
|
+
top: numAttr("top") ?? 0,
|
|
1170
|
+
bottom: numAttr("bottom") ?? 0,
|
|
1171
|
+
}),
|
|
1172
|
+
stops,
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Extract borders information
|
|
1177
|
+
*/
|
|
1178
|
+
function extractBorders(xf, workbookStyles) {
|
|
1179
|
+
if (!xf.a$ || !xf.a$.borderId)
|
|
1180
|
+
return null;
|
|
1181
|
+
const borderId = parseInt(xf.a$.borderId);
|
|
1182
|
+
// Default no borders
|
|
1183
|
+
if (borderId === 0)
|
|
1184
|
+
return null;
|
|
1185
|
+
const borders = workbookStyles.borders;
|
|
1186
|
+
const border = borders[borderId];
|
|
1187
|
+
if (!border)
|
|
1188
|
+
return null;
|
|
1189
|
+
const result = {};
|
|
1190
|
+
// Process each side. Source XML often uses `<side style="none"/>` to
|
|
1191
|
+
// indicate "no border on this edge"; the original truthy check on `style`
|
|
1192
|
+
// was treating "none" as a real style and `mapBorderStyle`'s default
|
|
1193
|
+
// branch returns "solid", producing fabricated solid borders on every
|
|
1194
|
+
// edge that the source explicitly marked as none.
|
|
1195
|
+
["top", "right", "bottom", "left"].forEach((side) => {
|
|
1196
|
+
const sideNode = getNode(border, side, `x:${side}`);
|
|
1197
|
+
const style = sideNode?.a$?.style;
|
|
1198
|
+
if (style && style !== "none") {
|
|
1199
|
+
result[side] = {
|
|
1200
|
+
style: mapBorderStyle(style),
|
|
1201
|
+
width: mapBorderWidth(style),
|
|
1202
|
+
color: getNode(sideNode, "color", "x:color")
|
|
1203
|
+
? extractColor(getNode(sideNode, "color", "x:color"), workbookStyles.indexedColors)
|
|
1204
|
+
: null,
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
// Diagonals: OOXML uses a single <diagonal> child for the style/color,
|
|
1209
|
+
// and `diagonalUp="1"` / `diagonalDown="1"` attributes on the parent
|
|
1210
|
+
// <border> to pick which corner-to-corner line(s) get drawn.
|
|
1211
|
+
const diagonalNode = getNode(border, "diagonal", "x:diagonal");
|
|
1212
|
+
const diagonalStyle = diagonalNode?.a$?.style;
|
|
1213
|
+
if (diagonalStyle && diagonalStyle !== "none") {
|
|
1214
|
+
const diagonalBorder = {
|
|
1215
|
+
style: mapBorderStyle(diagonalStyle),
|
|
1216
|
+
width: mapBorderWidth(diagonalStyle),
|
|
1217
|
+
color: getNode(diagonalNode, "color", "x:color")
|
|
1218
|
+
? extractColor(getNode(diagonalNode, "color", "x:color"), workbookStyles.indexedColors)
|
|
1219
|
+
: null,
|
|
1220
|
+
};
|
|
1221
|
+
const diagonalUpFlag = border?.a$?.diagonalUp;
|
|
1222
|
+
const diagonalDownFlag = border?.a$?.diagonalDown;
|
|
1223
|
+
const isTruthy = (v) => v !== undefined && v !== "0" && v !== "false" && v !== false;
|
|
1224
|
+
if (isTruthy(diagonalUpFlag))
|
|
1225
|
+
result.diagonalUp = diagonalBorder;
|
|
1226
|
+
if (isTruthy(diagonalDownFlag))
|
|
1227
|
+
result.diagonalDown = diagonalBorder;
|
|
1228
|
+
}
|
|
1229
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Map Excel border style to our BorderStyle. Covers all 13 OOXML
|
|
1233
|
+
* styles; the previous reduced set silently collapsed `hair` →
|
|
1234
|
+
* `thin` and lost `mediumDashed` / `dashDot` / `dashDotDot` /
|
|
1235
|
+
* `slantDashDot` entirely.
|
|
1236
|
+
*/
|
|
1237
|
+
function mapBorderStyle(excelStyle) {
|
|
1238
|
+
switch (excelStyle) {
|
|
1239
|
+
case "hair":
|
|
1240
|
+
return "hair";
|
|
1241
|
+
case "thin":
|
|
1242
|
+
return "solid";
|
|
1243
|
+
case "medium":
|
|
1244
|
+
return "solid_medium";
|
|
1245
|
+
case "thick":
|
|
1246
|
+
return "solid_thick";
|
|
1247
|
+
case "dashed":
|
|
1248
|
+
return "dashed";
|
|
1249
|
+
case "dotted":
|
|
1250
|
+
return "dotted";
|
|
1251
|
+
case "double":
|
|
1252
|
+
return "double";
|
|
1253
|
+
case "mediumDashed":
|
|
1254
|
+
return "mediumDashed";
|
|
1255
|
+
case "mediumDashDot":
|
|
1256
|
+
return "mediumDashDot";
|
|
1257
|
+
case "dashDot":
|
|
1258
|
+
return "dashDot";
|
|
1259
|
+
case "dashDotDot":
|
|
1260
|
+
return "dashDotDot";
|
|
1261
|
+
case "slantDashDot":
|
|
1262
|
+
return "slantDashDot";
|
|
1263
|
+
default:
|
|
1264
|
+
return "solid";
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Map border width based on style
|
|
1269
|
+
*/
|
|
1270
|
+
function mapBorderWidth(excelStyle) {
|
|
1271
|
+
switch (excelStyle) {
|
|
1272
|
+
case "hair":
|
|
1273
|
+
// Hair is the lightest border in OOXML — slightly thinner than
|
|
1274
|
+
// thin but the canvas can't paint sub-pixel lines, so it gets
|
|
1275
|
+
// the same 1px width but a different dash signature.
|
|
1276
|
+
return 1;
|
|
1277
|
+
case "thin":
|
|
1278
|
+
return 1;
|
|
1279
|
+
case "medium":
|
|
1280
|
+
case "mediumDashed":
|
|
1281
|
+
case "mediumDashDot":
|
|
1282
|
+
return 2;
|
|
1283
|
+
case "thick":
|
|
1284
|
+
return 3;
|
|
1285
|
+
case "double":
|
|
1286
|
+
return 2;
|
|
1287
|
+
case "dashDot":
|
|
1288
|
+
case "dashDotDot":
|
|
1289
|
+
case "slantDashDot":
|
|
1290
|
+
return 1;
|
|
1291
|
+
default:
|
|
1292
|
+
return 1;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Map horizontal alignment
|
|
1297
|
+
*/
|
|
1298
|
+
function mapHorizontalAlignment(excelAlign) {
|
|
1299
|
+
switch (excelAlign) {
|
|
1300
|
+
case "left":
|
|
1301
|
+
return "left";
|
|
1302
|
+
case "center":
|
|
1303
|
+
return "center";
|
|
1304
|
+
case "right":
|
|
1305
|
+
return "right";
|
|
1306
|
+
default:
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Map vertical alignment
|
|
1312
|
+
*/
|
|
1313
|
+
function mapVerticalAlignment(excelAlign) {
|
|
1314
|
+
switch (excelAlign) {
|
|
1315
|
+
case "top":
|
|
1316
|
+
return "top";
|
|
1317
|
+
case "center":
|
|
1318
|
+
return "middle";
|
|
1319
|
+
case "bottom":
|
|
1320
|
+
return "bottom";
|
|
1321
|
+
default:
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function getDefaultHorizontalAlignment(cell, xf, numberFormat) {
|
|
1326
|
+
const cellType = cell.a$ && cell.a$.t ? cell.a$.t : undefined; // Default to number
|
|
1327
|
+
if (cellType === "s") {
|
|
1328
|
+
return undefined;
|
|
1329
|
+
}
|
|
1330
|
+
if (cellType === "b") {
|
|
1331
|
+
return "center";
|
|
1332
|
+
}
|
|
1333
|
+
// If there's no explicit alignment, check the number format
|
|
1334
|
+
if (xf.a$ && xf.a$.numFmtId) {
|
|
1335
|
+
const numFmtId = parseInt(xf.a$.numFmtId);
|
|
1336
|
+
// Currency formats (including custom ones) are typically right-aligned
|
|
1337
|
+
// Custom number formats (numFmtId >= 164)
|
|
1338
|
+
if (numFmtId >= 164) {
|
|
1339
|
+
// Get the format code if workbookStyles is available
|
|
1340
|
+
if (numberFormat?.type === "NUMBER") {
|
|
1341
|
+
return "right";
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if ((numFmtId >= 5 && numFmtId <= 8) ||
|
|
1345
|
+
(numFmtId >= 37 && numFmtId <= 44)) {
|
|
1346
|
+
return "right";
|
|
1347
|
+
}
|
|
1348
|
+
// Most number formats are right-aligned
|
|
1349
|
+
if ((numFmtId >= 1 && numFmtId <= 4) ||
|
|
1350
|
+
(numFmtId >= 9 && numFmtId <= 11) ||
|
|
1351
|
+
numFmtId === 37 ||
|
|
1352
|
+
numFmtId === 38 ||
|
|
1353
|
+
(numFmtId >= 39 && numFmtId <= 44)) {
|
|
1354
|
+
return "right";
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (cellType === "n") {
|
|
1358
|
+
return "right";
|
|
1359
|
+
}
|
|
1360
|
+
if (cellType === "b") {
|
|
1361
|
+
return "center";
|
|
1362
|
+
}
|
|
1363
|
+
// Default for text
|
|
1364
|
+
return undefined;
|
|
1365
|
+
}
|