@trebco/treb 36.1.4 → 37.0.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/api-config.json +1 -1
- package/build/package.json +119 -0
- package/build/treb-base-types/src/api_types.d.ts +11 -0
- package/build/treb-base-types/src/api_types.js +22 -0
- package/build/treb-base-types/src/api_types.js.map +1 -0
- package/build/treb-base-types/src/area-utils.d.ts +9 -0
- package/build/treb-base-types/src/area-utils.js +50 -0
- package/build/treb-base-types/src/area-utils.js.map +1 -0
- package/build/treb-base-types/src/area.d.ts +182 -0
- package/build/treb-base-types/src/area.js +715 -0
- package/build/treb-base-types/src/area.js.map +1 -0
- package/build/treb-base-types/src/basic_types.d.ts +20 -0
- package/build/treb-base-types/src/basic_types.js +22 -0
- package/build/treb-base-types/src/basic_types.js.map +1 -0
- package/build/treb-base-types/src/cell.d.ts +167 -0
- package/build/treb-base-types/src/cell.js +432 -0
- package/build/treb-base-types/src/cell.js.map +1 -0
- package/build/treb-base-types/src/cells.d.ts +251 -0
- package/build/treb-base-types/src/cells.js +1136 -0
- package/build/treb-base-types/src/cells.js.map +1 -0
- package/build/treb-base-types/src/color.d.ts +35 -0
- package/build/treb-base-types/src/color.js +162 -0
- package/build/treb-base-types/src/color.js.map +1 -0
- package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
- package/build/treb-base-types/src/dom-utilities.js +144 -0
- package/build/treb-base-types/src/dom-utilities.js.map +1 -0
- package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
- package/build/treb-base-types/src/evaluate-options.js +22 -0
- package/build/treb-base-types/src/evaluate-options.js.map +1 -0
- package/build/treb-base-types/src/font-stack.d.ts +37 -0
- package/build/treb-base-types/src/font-stack.js +93 -0
- package/build/treb-base-types/src/font-stack.js.map +1 -0
- package/build/treb-base-types/src/gradient.d.ts +18 -0
- package/build/treb-base-types/src/gradient.js +86 -0
- package/build/treb-base-types/src/gradient.js.map +1 -0
- package/build/treb-base-types/src/import.d.ts +48 -0
- package/build/treb-base-types/src/import.js +22 -0
- package/build/treb-base-types/src/import.js.map +1 -0
- package/build/treb-base-types/src/index-standalone.d.ts +6 -0
- package/build/treb-base-types/src/index-standalone.js +27 -0
- package/build/treb-base-types/src/index-standalone.js.map +1 -0
- package/build/treb-base-types/src/index.d.ts +22 -0
- package/build/treb-base-types/src/index.js +45 -0
- package/build/treb-base-types/src/index.js.map +1 -0
- package/build/treb-base-types/src/layout.d.ts +22 -0
- package/build/treb-base-types/src/layout.js +22 -0
- package/build/treb-base-types/src/layout.js.map +1 -0
- package/build/treb-base-types/src/localization.d.ts +37 -0
- package/build/treb-base-types/src/localization.js +157 -0
- package/build/treb-base-types/src/localization.js.map +1 -0
- package/build/treb-base-types/src/rectangle.d.ts +51 -0
- package/build/treb-base-types/src/rectangle.js +123 -0
- package/build/treb-base-types/src/rectangle.js.map +1 -0
- package/build/treb-base-types/src/render_text.d.ts +34 -0
- package/build/treb-base-types/src/render_text.js +22 -0
- package/build/treb-base-types/src/render_text.js.map +1 -0
- package/build/treb-base-types/src/style.d.ts +214 -0
- package/build/treb-base-types/src/style.js +373 -0
- package/build/treb-base-types/src/style.js.map +1 -0
- package/build/treb-base-types/src/table.d.ts +58 -0
- package/build/treb-base-types/src/table.js +27 -0
- package/build/treb-base-types/src/table.js.map +1 -0
- package/build/treb-base-types/src/text_part.d.ts +26 -0
- package/build/treb-base-types/src/text_part.js +47 -0
- package/build/treb-base-types/src/text_part.js.map +1 -0
- package/build/treb-base-types/src/theme.d.ts +120 -0
- package/build/treb-base-types/src/theme.js +460 -0
- package/build/treb-base-types/src/theme.js.map +1 -0
- package/build/treb-base-types/src/union.d.ts +73 -0
- package/build/treb-base-types/src/union.js +61 -0
- package/build/treb-base-types/src/union.js.map +1 -0
- package/build/treb-base-types/src/value-type.d.ts +86 -0
- package/build/treb-base-types/src/value-type.js +168 -0
- package/build/treb-base-types/src/value-type.js.map +1 -0
- package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
- package/build/treb-base-types/src/worker-proxy.js +221 -0
- package/build/treb-base-types/src/worker-proxy.js.map +1 -0
- package/build/treb-calculator/src/calculator.d.ts +249 -0
- package/build/treb-calculator/src/calculator.js +2755 -0
- package/build/treb-calculator/src/calculator.js.map +1 -0
- package/build/treb-calculator/src/complex-math.d.ts +75 -0
- package/build/treb-calculator/src/complex-math.js +559 -0
- package/build/treb-calculator/src/complex-math.js.map +1 -0
- package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/array-vertex.js +156 -0
- package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/graph.d.ts +134 -0
- package/build/treb-calculator/src/dag/graph.js +842 -0
- package/build/treb-calculator/src/dag/graph.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/vertex.js +274 -0
- package/build/treb-calculator/src/dag/vertex.js.map +1 -0
- package/build/treb-calculator/src/descriptors.d.ts +189 -0
- package/build/treb-calculator/src/descriptors.js +22 -0
- package/build/treb-calculator/src/descriptors.js.map +1 -0
- package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
- package/build/treb-calculator/src/expression-calculator.js +1033 -0
- package/build/treb-calculator/src/expression-calculator.js.map +1 -0
- package/build/treb-calculator/src/function-error.d.ts +35 -0
- package/build/treb-calculator/src/function-error.js +85 -0
- package/build/treb-calculator/src/function-error.js.map +1 -0
- package/build/treb-calculator/src/function-library.d.ts +22 -0
- package/build/treb-calculator/src/function-library.js +96 -0
- package/build/treb-calculator/src/function-library.js.map +1 -0
- package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
- package/build/treb-calculator/src/functions/base-functions.js +2611 -0
- package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/beta.d.ts +17 -0
- package/build/treb-calculator/src/functions/beta.js +201 -0
- package/build/treb-calculator/src/functions/beta.js.map +1 -0
- package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
- package/build/treb-calculator/src/functions/checkbox.js +128 -0
- package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
- package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/complex-functions.js +217 -0
- package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
- package/build/treb-calculator/src/functions/date-utils.js +59 -0
- package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
- package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/finance-functions.js +547 -0
- package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/fp.d.ts +2 -0
- package/build/treb-calculator/src/functions/fp.js +463 -0
- package/build/treb-calculator/src/functions/fp.js.map +1 -0
- package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
- package/build/treb-calculator/src/functions/function-utilities.js +36 -0
- package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
- package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
- package/build/treb-calculator/src/functions/gamma.js +142 -0
- package/build/treb-calculator/src/functions/gamma.js.map +1 -0
- package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/information-functions.js +71 -0
- package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
- package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
- package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/normal.d.ts +2 -0
- package/build/treb-calculator/src/functions/normal.js +32 -0
- package/build/treb-calculator/src/functions/normal.js.map +1 -0
- package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/regex-functions.js +188 -0
- package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
- package/build/treb-calculator/src/functions/sparkline.js +264 -0
- package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
- package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
- package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
- package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
- package/build/treb-calculator/src/functions/students-t.js +64 -0
- package/build/treb-calculator/src/functions/students-t.js.map +1 -0
- package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
- package/build/treb-calculator/src/functions/text-functions.js +320 -0
- package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
- package/build/treb-calculator/src/index.d.ts +2 -0
- package/build/treb-calculator/src/index.js +22 -0
- package/build/treb-calculator/src/index.js.map +1 -0
- package/build/treb-calculator/src/notifier-types.d.ts +26 -0
- package/build/treb-calculator/src/notifier-types.js +22 -0
- package/build/treb-calculator/src/notifier-types.js.map +1 -0
- package/build/treb-calculator/src/primitives.d.ts +15 -0
- package/build/treb-calculator/src/primitives.js +398 -0
- package/build/treb-calculator/src/primitives.js.map +1 -0
- package/build/treb-calculator/src/utilities.d.ts +68 -0
- package/build/treb-calculator/src/utilities.js +324 -0
- package/build/treb-calculator/src/utilities.js.map +1 -0
- package/build/treb-charts/src/chart-functions.d.ts +8 -0
- package/build/treb-charts/src/chart-functions.js +209 -0
- package/build/treb-charts/src/chart-functions.js.map +1 -0
- package/build/treb-charts/src/chart-types.d.ts +233 -0
- package/build/treb-charts/src/chart-types.js +57 -0
- package/build/treb-charts/src/chart-types.js.map +1 -0
- package/build/treb-charts/src/chart-utils.d.ts +106 -0
- package/build/treb-charts/src/chart-utils.js +1060 -0
- package/build/treb-charts/src/chart-utils.js.map +1 -0
- package/build/treb-charts/src/chart.d.ts +23 -0
- package/build/treb-charts/src/chart.js +94 -0
- package/build/treb-charts/src/chart.js.map +1 -0
- package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
- package/build/treb-charts/src/default-chart-renderer.js +533 -0
- package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
- package/build/treb-charts/src/index.d.ts +5 -0
- package/build/treb-charts/src/index.js +24 -0
- package/build/treb-charts/src/index.js.map +1 -0
- package/build/treb-charts/src/main.d.ts +1 -0
- package/build/treb-charts/src/main.js +34 -0
- package/build/treb-charts/src/main.js.map +1 -0
- package/build/treb-charts/src/quicksort.d.ts +1 -0
- package/build/treb-charts/src/quicksort.js +49 -0
- package/build/treb-charts/src/quicksort.js.map +1 -0
- package/build/treb-charts/src/rectangle.d.ts +18 -0
- package/build/treb-charts/src/rectangle.js +41 -0
- package/build/treb-charts/src/rectangle.js.map +1 -0
- package/build/treb-charts/src/renderer-type.d.ts +24 -0
- package/build/treb-charts/src/renderer-type.js +22 -0
- package/build/treb-charts/src/renderer-type.js.map +1 -0
- package/build/treb-charts/src/renderer.d.ts +127 -0
- package/build/treb-charts/src/renderer.js +1518 -0
- package/build/treb-charts/src/renderer.js.map +1 -0
- package/build/treb-charts/src/util.d.ts +18 -0
- package/build/treb-charts/src/util.js +71 -0
- package/build/treb-charts/src/util.js.map +1 -0
- package/build/treb-data-model/src/annotation.d.ts +167 -0
- package/build/treb-data-model/src/annotation.js +120 -0
- package/build/treb-data-model/src/annotation.js.map +1 -0
- package/build/treb-data-model/src/conditional_format.d.ts +155 -0
- package/build/treb-data-model/src/conditional_format.js +62 -0
- package/build/treb-data-model/src/conditional_format.js.map +1 -0
- package/build/treb-data-model/src/data-validation.d.ts +28 -0
- package/build/treb-data-model/src/data-validation.js +22 -0
- package/build/treb-data-model/src/data-validation.js.map +1 -0
- package/build/treb-data-model/src/data_model.d.ts +173 -0
- package/build/treb-data-model/src/data_model.js +637 -0
- package/build/treb-data-model/src/data_model.js.map +1 -0
- package/build/treb-data-model/src/index.d.ts +13 -0
- package/build/treb-data-model/src/index.js +28 -0
- package/build/treb-data-model/src/index.js.map +1 -0
- package/build/treb-data-model/src/language-model.d.ts +22 -0
- package/build/treb-data-model/src/language-model.js +22 -0
- package/build/treb-data-model/src/language-model.js.map +1 -0
- package/build/treb-data-model/src/named.d.ts +124 -0
- package/build/treb-data-model/src/named.js +372 -0
- package/build/treb-data-model/src/named.js.map +1 -0
- package/build/treb-data-model/src/serialize_options.d.ts +49 -0
- package/build/treb-data-model/src/serialize_options.js +22 -0
- package/build/treb-data-model/src/serialize_options.js.map +1 -0
- package/build/treb-data-model/src/sheet.d.ts +499 -0
- package/build/treb-data-model/src/sheet.js +2904 -0
- package/build/treb-data-model/src/sheet.js.map +1 -0
- package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
- package/build/treb-data-model/src/sheet_collection.js +112 -0
- package/build/treb-data-model/src/sheet_collection.js.map +1 -0
- package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
- package/build/treb-data-model/src/sheet_selection.js +39 -0
- package/build/treb-data-model/src/sheet_selection.js.map +1 -0
- package/build/treb-data-model/src/sheet_types.d.ts +104 -0
- package/build/treb-data-model/src/sheet_types.js +22 -0
- package/build/treb-data-model/src/sheet_types.js.map +1 -0
- package/build/treb-data-model/src/types.d.ts +59 -0
- package/build/treb-data-model/src/types.js +22 -0
- package/build/treb-data-model/src/types.js.map +1 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
- package/build/treb-embed/src/custom-element/treb-global.js +64 -0
- package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
- package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
- package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
- package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
- package/build/treb-embed/src/index.d.ts +12 -0
- package/build/treb-embed/src/index.js +34 -0
- package/build/treb-embed/src/index.js.map +1 -0
- package/build/treb-embed/src/options.d.ts +266 -0
- package/build/treb-embed/src/options.js +56 -0
- package/build/treb-embed/src/options.js.map +1 -0
- package/build/treb-embed/src/plugin.d.ts +9 -0
- package/build/treb-embed/src/plugin.js +22 -0
- package/build/treb-embed/src/plugin.js.map +1 -0
- package/build/treb-embed/src/progress-dialog.d.ts +49 -0
- package/build/treb-embed/src/progress-dialog.js +178 -0
- package/build/treb-embed/src/progress-dialog.js.map +1 -0
- package/build/treb-embed/src/selection-state.d.ts +15 -0
- package/build/treb-embed/src/selection-state.js +22 -0
- package/build/treb-embed/src/selection-state.js.map +1 -0
- package/build/treb-embed/src/spinner.d.ts +8 -0
- package/build/treb-embed/src/spinner.js +40 -0
- package/build/treb-embed/src/spinner.js.map +1 -0
- package/build/treb-embed/src/toolbar-message.d.ts +72 -0
- package/build/treb-embed/src/toolbar-message.js +22 -0
- package/build/treb-embed/src/toolbar-message.js.map +1 -0
- package/build/treb-embed/src/types.d.ts +185 -0
- package/build/treb-embed/src/types.js +45 -0
- package/build/treb-embed/src/types.js.map +1 -0
- package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
- package/build/treb-export/src/address-type.d.ts +34 -0
- package/build/treb-export/src/address-type.js +53 -0
- package/build/treb-export/src/address-type.js.map +1 -0
- package/build/treb-export/src/base-template.d.ts +1 -0
- package/build/treb-export/src/base-template.js +22 -0
- package/build/treb-export/src/base-template.js.map +1 -0
- package/build/treb-export/src/column-width.d.ts +2 -0
- package/build/treb-export/src/column-width.js +80 -0
- package/build/treb-export/src/column-width.js.map +1 -0
- package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
- package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
- package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
- package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
- package/build/treb-export/src/drawing/chart.d.ts +26 -0
- package/build/treb-export/src/drawing/chart.js +247 -0
- package/build/treb-export/src/drawing/chart.js.map +1 -0
- package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
- package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
- package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/drawing.d.ts +49 -0
- package/build/treb-export/src/drawing/drawing.js +193 -0
- package/build/treb-export/src/drawing/drawing.js.map +1 -0
- package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
- package/build/treb-export/src/drawing/embedded-image.js +54 -0
- package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
- package/build/treb-export/src/export.d.ts +72 -0
- package/build/treb-export/src/export.js +2039 -0
- package/build/treb-export/src/export.js.map +1 -0
- package/build/treb-export/src/import-export-messages.d.ts +31 -0
- package/build/treb-export/src/import-export-messages.js +22 -0
- package/build/treb-export/src/import-export-messages.js.map +1 -0
- package/build/treb-export/src/import.d.ts +33 -0
- package/build/treb-export/src/import.js +1258 -0
- package/build/treb-export/src/import.js.map +1 -0
- package/build/treb-export/src/index.worker.d.ts +1 -0
- package/build/treb-export/src/index.worker.js +93 -0
- package/build/treb-export/src/index.worker.js.map +1 -0
- package/build/treb-export/src/metadata.d.ts +51 -0
- package/build/treb-export/src/metadata.js +153 -0
- package/build/treb-export/src/metadata.js.map +1 -0
- package/build/treb-export/src/ooxml.d.ts +7 -0
- package/build/treb-export/src/ooxml.js +41 -0
- package/build/treb-export/src/ooxml.js.map +1 -0
- package/build/treb-export/src/relationship.d.ts +8 -0
- package/build/treb-export/src/relationship.js +27 -0
- package/build/treb-export/src/relationship.js.map +1 -0
- package/build/treb-export/src/shared-strings.d.ts +11 -0
- package/build/treb-export/src/shared-strings.js +105 -0
- package/build/treb-export/src/shared-strings.js.map +1 -0
- package/build/treb-export/src/template-2.d.ts +1 -0
- package/build/treb-export/src/template-2.js +22 -0
- package/build/treb-export/src/template-2.js.map +1 -0
- package/build/treb-export/src/unescape_xml.d.ts +1 -0
- package/build/treb-export/src/unescape_xml.js +61 -0
- package/build/treb-export/src/unescape_xml.js.map +1 -0
- package/build/treb-export/src/workbook-sheet.d.ts +75 -0
- package/build/treb-export/src/workbook-sheet.js +128 -0
- package/build/treb-export/src/workbook-sheet.js.map +1 -0
- package/build/treb-export/src/workbook-style.d.ts +110 -0
- package/build/treb-export/src/workbook-style.js +1134 -0
- package/build/treb-export/src/workbook-style.js.map +1 -0
- package/build/treb-export/src/workbook-theme.d.ts +13 -0
- package/build/treb-export/src/workbook-theme.js +85 -0
- package/build/treb-export/src/workbook-theme.js.map +1 -0
- package/build/treb-export/src/workbook.d.ts +123 -0
- package/build/treb-export/src/workbook.js +644 -0
- package/build/treb-export/src/workbook.js.map +1 -0
- package/build/treb-export/src/xml-test.d.ts +9 -0
- package/build/treb-export/src/xml-test.js +52 -0
- package/build/treb-export/src/xml-test.js.map +1 -0
- package/build/treb-export/src/xml-utils.d.ts +76 -0
- package/build/treb-export/src/xml-utils.js +223 -0
- package/build/treb-export/src/xml-utils.js.map +1 -0
- package/build/treb-export/src/zip-wrapper.d.ts +22 -0
- package/build/treb-export/src/zip-wrapper.js +93 -0
- package/build/treb-export/src/zip-wrapper.js.map +1 -0
- package/build/treb-format/src/format.d.ts +130 -0
- package/build/treb-format/src/format.js +805 -0
- package/build/treb-format/src/format.js.map +1 -0
- package/build/treb-format/src/format_cache.d.ts +55 -0
- package/build/treb-format/src/format_cache.js +166 -0
- package/build/treb-format/src/format_cache.js.map +1 -0
- package/build/treb-format/src/format_parser.d.ts +70 -0
- package/build/treb-format/src/format_parser.js +618 -0
- package/build/treb-format/src/format_parser.js.map +1 -0
- package/build/treb-format/src/index.d.ts +4 -0
- package/build/treb-format/src/index.js +25 -0
- package/build/treb-format/src/index.js.map +1 -0
- package/build/treb-format/src/number_format_section.d.ts +58 -0
- package/build/treb-format/src/number_format_section.js +78 -0
- package/build/treb-format/src/number_format_section.js.map +1 -0
- package/build/treb-format/src/value_parser.d.ts +48 -0
- package/build/treb-format/src/value_parser.js +244 -0
- package/build/treb-format/src/value_parser.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
- package/build/treb-grid/src/editors/autocomplete.js +316 -0
- package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
- package/build/treb-grid/src/editors/editor.d.ts +214 -0
- package/build/treb-grid/src/editors/editor.js +879 -0
- package/build/treb-grid/src/editors/editor.js.map +1 -0
- package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
- package/build/treb-grid/src/editors/external_editor.js +118 -0
- package/build/treb-grid/src/editors/external_editor.js.map +1 -0
- package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
- package/build/treb-grid/src/editors/formula_bar.js +444 -0
- package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
- package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
- package/build/treb-grid/src/editors/overlay_editor.js +353 -0
- package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
- package/build/treb-grid/src/index.d.ts +12 -0
- package/build/treb-grid/src/index.js +28 -0
- package/build/treb-grid/src/index.js.map +1 -0
- package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
- package/build/treb-grid/src/layout/base_layout.js +2050 -0
- package/build/treb-grid/src/layout/base_layout.js.map +1 -0
- package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
- package/build/treb-grid/src/layout/grid_layout.js +235 -0
- package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
- package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
- package/build/treb-grid/src/layout/mock-layout.js +37 -0
- package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
- package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
- package/build/treb-grid/src/render/selection-renderer.js +315 -0
- package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
- package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
- package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
- package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
- package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
- package/build/treb-grid/src/render/svg_selection_block.js +106 -0
- package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
- package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
- package/build/treb-grid/src/render/tile_renderer.js +1609 -0
- package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
- package/build/treb-grid/src/types/border_constants.d.ts +9 -0
- package/build/treb-grid/src/types/border_constants.js +34 -0
- package/build/treb-grid/src/types/border_constants.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
- package/build/treb-grid/src/types/clipboard_data.js +22 -0
- package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
- package/build/treb-grid/src/types/clipboard_data2.js +22 -0
- package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
- package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
- package/build/treb-grid/src/types/drag_mask.js +78 -0
- package/build/treb-grid/src/types/drag_mask.js.map +1 -0
- package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
- package/build/treb-grid/src/types/external_editor_config.js +22 -0
- package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
- package/build/treb-grid/src/types/grid.d.ts +806 -0
- package/build/treb-grid/src/types/grid.js +6410 -0
- package/build/treb-grid/src/types/grid.js.map +1 -0
- package/build/treb-grid/src/types/grid_base.d.ts +442 -0
- package/build/treb-grid/src/types/grid_base.js +3523 -0
- package/build/treb-grid/src/types/grid_base.js.map +1 -0
- package/build/treb-grid/src/types/grid_command.d.ts +408 -0
- package/build/treb-grid/src/types/grid_command.js +75 -0
- package/build/treb-grid/src/types/grid_command.js.map +1 -0
- package/build/treb-grid/src/types/grid_events.d.ts +93 -0
- package/build/treb-grid/src/types/grid_events.js +36 -0
- package/build/treb-grid/src/types/grid_events.js.map +1 -0
- package/build/treb-grid/src/types/grid_options.d.ts +50 -0
- package/build/treb-grid/src/types/grid_options.js +34 -0
- package/build/treb-grid/src/types/grid_options.js.map +1 -0
- package/build/treb-grid/src/types/scale-control.d.ts +21 -0
- package/build/treb-grid/src/types/scale-control.js +148 -0
- package/build/treb-grid/src/types/scale-control.js.map +1 -0
- package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
- package/build/treb-grid/src/types/set_range_options.js +22 -0
- package/build/treb-grid/src/types/set_range_options.js.map +1 -0
- package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
- package/build/treb-grid/src/types/tab_bar.js +426 -0
- package/build/treb-grid/src/types/tab_bar.js.map +1 -0
- package/build/treb-grid/src/types/tile.d.ts +29 -0
- package/build/treb-grid/src/types/tile.js +22 -0
- package/build/treb-grid/src/types/tile.js.map +1 -0
- package/build/treb-grid/src/types/update_flags.d.ts +48 -0
- package/build/treb-grid/src/types/update_flags.js +22 -0
- package/build/treb-grid/src/types/update_flags.js.map +1 -0
- package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
- package/build/treb-grid/src/util/fontmetrics.js +82 -0
- package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
- package/build/treb-grid/src/util/ua.d.ts +33 -0
- package/build/treb-grid/src/util/ua.js +86 -0
- package/build/treb-grid/src/util/ua.js.map +1 -0
- package/build/treb-parser/src/csv-parser.d.ts +13 -0
- package/build/treb-parser/src/csv-parser.js +107 -0
- package/build/treb-parser/src/csv-parser.js.map +1 -0
- package/build/treb-parser/src/index.d.ts +4 -0
- package/build/treb-parser/src/index.js +25 -0
- package/build/treb-parser/src/index.js.map +1 -0
- package/build/treb-parser/src/md-parser.d.ts +97 -0
- package/build/treb-parser/src/md-parser.js +403 -0
- package/build/treb-parser/src/md-parser.js.map +1 -0
- package/build/treb-parser/src/parser-types.d.ts +345 -0
- package/build/treb-parser/src/parser-types.js +53 -0
- package/build/treb-parser/src/parser-types.js.map +1 -0
- package/build/treb-parser/src/parser.d.ts +422 -0
- package/build/treb-parser/src/parser.js +2418 -0
- package/build/treb-parser/src/parser.js.map +1 -0
- package/build/treb-utils/src/event_source.d.ts +34 -0
- package/build/treb-utils/src/event_source.js +110 -0
- package/build/treb-utils/src/event_source.js.map +1 -0
- package/build/treb-utils/src/ievent_source.d.ts +9 -0
- package/build/treb-utils/src/ievent_source.js +22 -0
- package/build/treb-utils/src/ievent_source.js.map +1 -0
- package/build/treb-utils/src/index.d.ts +6 -0
- package/build/treb-utils/src/index.js +30 -0
- package/build/treb-utils/src/index.js.map +1 -0
- package/build/treb-utils/src/measurement.d.ts +42 -0
- package/build/treb-utils/src/measurement.js +145 -0
- package/build/treb-utils/src/measurement.js.map +1 -0
- package/build/treb-utils/src/scale.d.ts +16 -0
- package/build/treb-utils/src/scale.js +106 -0
- package/build/treb-utils/src/scale.js.map +1 -0
- package/build/treb-utils/src/serialize_html.d.ts +5 -0
- package/build/treb-utils/src/serialize_html.js +128 -0
- package/build/treb-utils/src/serialize_html.js.map +1 -0
- package/build/treb-utils/src/validate_uri.d.ts +20 -0
- package/build/treb-utils/src/validate_uri.js +55 -0
- package/build/treb-utils/src/validate_uri.js.map +1 -0
- package/dist/{chunk-Z4XFMZ2X.mjs → chunk-E35ONJUS.mjs} +1 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +4 -4
- package/dist/treb.d.ts +1 -1
- package/esbuild-composite.mjs +5 -1
- package/package.json +67 -3
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
- package/treb-embed/src/embedded-spreadsheet.ts +1 -1
- package/treb-grid/src/types/grid_options.ts +1 -1
- package/tsproject.json +1 -2
- package/dist/chunk-43DLP2OX.mjs +0 -11
- package/dist/chunk-4CKS56PE.mjs +0 -11
- package/dist/chunk-75PARUQE.mjs +0 -11
- package/dist/chunk-7QD63AZS.mjs +0 -24601
- package/dist/chunk-A55ARVRD.mjs +0 -11
- package/dist/chunk-DESAKYW4.mjs +0 -11
- package/dist/chunk-EQ2R5W6P.mjs +0 -24565
- package/dist/chunk-IYJU2J6D.mjs +0 -24601
- package/dist/chunk-KSJFPGXT.mjs +0 -11
- package/dist/chunk-MQK4DNXI.mjs +0 -11
- package/dist/chunk-ORQFKLXM.mjs +0 -24601
- package/dist/chunk-SFDNNDHY.mjs +0 -11
- package/dist/chunk-T47DX5MI.mjs +0 -11
- package/dist/chunk-T6ILBVEX.mjs +0 -11
- package/dist/chunk-TPRCDYYG.mjs +0 -11
- package/dist/chunk-YAHNOOHO.mjs +0 -11
- package/dist/chunk-YLCFKX2G.mjs +0 -24601
|
@@ -0,0 +1,2755 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2026 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
import { Localization, Area, ValueType, IsCellAddress } from 'treb-base-types';
|
|
22
|
+
import { Parser, DecimalMarkType, QuotedSheetNameRegex } from 'treb-parser';
|
|
23
|
+
import { Graph } from './dag/graph';
|
|
24
|
+
import { ExpressionCalculator, UnionIsMetadata } from './expression-calculator';
|
|
25
|
+
import * as Utilities from './utilities';
|
|
26
|
+
import { StringUnion } from './utilities';
|
|
27
|
+
import { FunctionLibrary } from './function-library';
|
|
28
|
+
// import * as Utils from './utilities';
|
|
29
|
+
import { AltFunctionLibrary, BaseFunctionLibrary } from './functions/base-functions';
|
|
30
|
+
import { FinanceFunctionLibrary } from './functions/finance-functions';
|
|
31
|
+
import { TextFunctionLibrary, TextFunctionAliases } from './functions/text-functions';
|
|
32
|
+
import { InformationFunctionLibrary } from './functions/information-functions';
|
|
33
|
+
import { StatisticsFunctionLibrary, StatisticsFunctionAliases } from './functions/statistics-functions';
|
|
34
|
+
import { ComplexFunctionLibrary } from './functions/complex-functions';
|
|
35
|
+
import { MatrixFunctionLibrary } from './functions/matrix-functions';
|
|
36
|
+
import { RegexFunctionLibrary } from './functions/regex-functions';
|
|
37
|
+
import { LambdaFunctionLibrary } from './functions/lambda-functions';
|
|
38
|
+
import { FPFunctionLibrary } from './functions/fp';
|
|
39
|
+
import { Variance } from './functions/statistics-functions';
|
|
40
|
+
import * as Primitives from './primitives';
|
|
41
|
+
import { ArgumentError, ReferenceError, UnknownError, ValueError, ExpressionError, NAError, DivideByZeroError, NotImplError } from './function-error';
|
|
42
|
+
import { StateLeafVertex } from './dag/state_leaf_vertex';
|
|
43
|
+
import { CalculationLeafVertex } from './dag/calculation_leaf_vertex';
|
|
44
|
+
import { Sheet } from 'treb-data-model';
|
|
45
|
+
import { ValueParser } from 'treb-format';
|
|
46
|
+
/**
|
|
47
|
+
* breaking this out so we can use it for export (TODO)
|
|
48
|
+
*
|
|
49
|
+
* @param type
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
const TranslateSubtotalType = (type) => {
|
|
53
|
+
if (typeof type === 'string') {
|
|
54
|
+
type = type.toUpperCase();
|
|
55
|
+
switch (type) {
|
|
56
|
+
case 'AVERAGE':
|
|
57
|
+
case 'MEAN':
|
|
58
|
+
type = 101;
|
|
59
|
+
break;
|
|
60
|
+
case 'COUNT':
|
|
61
|
+
type = 102;
|
|
62
|
+
break;
|
|
63
|
+
case 'COUNTA':
|
|
64
|
+
type = 103;
|
|
65
|
+
break;
|
|
66
|
+
case 'MAX':
|
|
67
|
+
type = 104;
|
|
68
|
+
break;
|
|
69
|
+
case 'MIN':
|
|
70
|
+
type = 105;
|
|
71
|
+
break;
|
|
72
|
+
case 'PRODUCT':
|
|
73
|
+
type = 106;
|
|
74
|
+
break;
|
|
75
|
+
case 'STDEV':
|
|
76
|
+
type = 107;
|
|
77
|
+
break;
|
|
78
|
+
case 'STDEVP':
|
|
79
|
+
type = 108;
|
|
80
|
+
break;
|
|
81
|
+
case 'SUM':
|
|
82
|
+
type = 109;
|
|
83
|
+
break;
|
|
84
|
+
case 'VAR':
|
|
85
|
+
type = 110;
|
|
86
|
+
break;
|
|
87
|
+
case 'VARP':
|
|
88
|
+
type = 111;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
type = 0;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return type;
|
|
96
|
+
};
|
|
97
|
+
const default_calculator_options = {
|
|
98
|
+
complex_numbers: 'off',
|
|
99
|
+
spill: false,
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Calculator now extends graph. there's a 1-1 relationship between the
|
|
103
|
+
* two, and we wind up passing a lot of operations from one to the other.
|
|
104
|
+
* this also simplifies the callback structure, as we can use local methods.
|
|
105
|
+
*
|
|
106
|
+
* NOTE: graph vertices hold references to cells. while that makes lookups
|
|
107
|
+
* more efficient, it causes problems if you mutate the sheet (adding or
|
|
108
|
+
* removing rows or columns).
|
|
109
|
+
*
|
|
110
|
+
* in that event, you need to flush the graph to force rebuilding references
|
|
111
|
+
* (TODO: just rebuild references). after mutating the sheet, call
|
|
112
|
+
* ```
|
|
113
|
+
* Calculator.Reset();
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
*/
|
|
117
|
+
export class Calculator extends Graph {
|
|
118
|
+
model;
|
|
119
|
+
/**
|
|
120
|
+
* localized parser instance. we're sharing.
|
|
121
|
+
* FIXME: remove local references so we can remove this accessor
|
|
122
|
+
*/
|
|
123
|
+
get parser() {
|
|
124
|
+
return this.model.parser;
|
|
125
|
+
}
|
|
126
|
+
library = new FunctionLibrary();
|
|
127
|
+
registered_libraries = {};
|
|
128
|
+
expression_calculator;
|
|
129
|
+
/** the next calculation must do a full rebuild -- set on reset */
|
|
130
|
+
full_rebuild_required = false;
|
|
131
|
+
options;
|
|
132
|
+
async_resource_init = false;
|
|
133
|
+
/**
|
|
134
|
+
* this is a flag we're using to communicate back to the embedded
|
|
135
|
+
* sheet, when the grid has expanded as a result of a calculation
|
|
136
|
+
* (because of a spill).
|
|
137
|
+
*/
|
|
138
|
+
grid_expanded = false;
|
|
139
|
+
constructor(model, calculator_options = {}) {
|
|
140
|
+
super();
|
|
141
|
+
this.model = model;
|
|
142
|
+
this.expression_calculator = new ExpressionCalculator(this.model, this.library, this.parser);
|
|
143
|
+
// at the moment options are only used here; in the future
|
|
144
|
+
// we may need to extend handling.
|
|
145
|
+
this.options = {
|
|
146
|
+
...default_calculator_options,
|
|
147
|
+
...calculator_options,
|
|
148
|
+
};
|
|
149
|
+
if (this.options.complex_numbers === 'on') {
|
|
150
|
+
// complex number handling: we need to change SQRT, POWER and ^
|
|
151
|
+
for (const key of Object.keys(AltFunctionLibrary)) {
|
|
152
|
+
BaseFunctionLibrary[key] = AltFunctionLibrary[key];
|
|
153
|
+
}
|
|
154
|
+
Primitives.UseComplex();
|
|
155
|
+
}
|
|
156
|
+
// FIXME: why is this called here, if model now owns it?
|
|
157
|
+
// TODO: move to model
|
|
158
|
+
this.UpdateLocale(); // for parser
|
|
159
|
+
// base functions
|
|
160
|
+
this.library.Register(BaseFunctionLibrary, TextFunctionLibrary, // we split out text functions
|
|
161
|
+
StatisticsFunctionLibrary, // also stats (wip)
|
|
162
|
+
FinanceFunctionLibrary, // also this (wip)
|
|
163
|
+
InformationFunctionLibrary, // etc
|
|
164
|
+
ComplexFunctionLibrary, MatrixFunctionLibrary, RegexFunctionLibrary, LambdaFunctionLibrary, FPFunctionLibrary);
|
|
165
|
+
// aliases
|
|
166
|
+
for (const key of Object.keys(StatisticsFunctionAliases)) {
|
|
167
|
+
this.library.Alias(key, StatisticsFunctionAliases[key]);
|
|
168
|
+
}
|
|
169
|
+
for (const key of Object.keys(TextFunctionAliases)) {
|
|
170
|
+
this.library.Alias(key, TextFunctionAliases[key]);
|
|
171
|
+
}
|
|
172
|
+
// special functions... need reference to the graph (this)
|
|
173
|
+
// moving countif here so we can reference it in COUNTIFS...
|
|
174
|
+
/*
|
|
175
|
+
const FlattenBooleans = (value: ArrayUnion) => {
|
|
176
|
+
const result: boolean[] = [];
|
|
177
|
+
for (const col of value.value) {
|
|
178
|
+
for (const entry of col) {
|
|
179
|
+
result.push(entry.type === ValueType.boolean && entry.value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
184
|
+
*/
|
|
185
|
+
/**
|
|
186
|
+
* this is a function that does sumif/averageif/countif.
|
|
187
|
+
* args is one or more sets of [criteria_range, criteria]
|
|
188
|
+
*/
|
|
189
|
+
const XIf = (type, value_range, ...args) => {
|
|
190
|
+
// there's a bug here if the value range is a single value?
|
|
191
|
+
// that happens if countif passes in a one-cell range... we should
|
|
192
|
+
// handle this in the caller, or here?
|
|
193
|
+
// NOTE we also have to address this in the set of
|
|
194
|
+
// arguments, in which each pair could have a single
|
|
195
|
+
// value as the criterion
|
|
196
|
+
if (!Array.isArray(value_range)) {
|
|
197
|
+
value_range = [[value_range]];
|
|
198
|
+
}
|
|
199
|
+
const filter = [];
|
|
200
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
201
|
+
let criteria_range = args[i];
|
|
202
|
+
if (!Array.isArray(criteria_range)) {
|
|
203
|
+
criteria_range = [[criteria_range]];
|
|
204
|
+
}
|
|
205
|
+
{ // if (Array.isArray(args[i])) {
|
|
206
|
+
const step = CountIfInternal(criteria_range, args[i + 1]);
|
|
207
|
+
if (step.type !== ValueType.array) {
|
|
208
|
+
return step;
|
|
209
|
+
}
|
|
210
|
+
for (const [r, cell] of step.value[0].entries()) {
|
|
211
|
+
filter[r] = (!!cell.value && (filter[r] !== false));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const values = Utilities.FlattenCellValues(value_range, true); // keep undefineds
|
|
216
|
+
let count = 0;
|
|
217
|
+
let sum = 0;
|
|
218
|
+
for (const [index, test] of filter.entries()) {
|
|
219
|
+
if (test) {
|
|
220
|
+
count++;
|
|
221
|
+
const value = values[index];
|
|
222
|
+
if (typeof value === 'number') {
|
|
223
|
+
sum += value;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
switch (type) {
|
|
228
|
+
case 'count':
|
|
229
|
+
return { type: ValueType.number, value: count };
|
|
230
|
+
case 'sum':
|
|
231
|
+
return { type: ValueType.number, value: sum };
|
|
232
|
+
case 'average':
|
|
233
|
+
if (count === 0) {
|
|
234
|
+
return DivideByZeroError();
|
|
235
|
+
}
|
|
236
|
+
return { type: ValueType.number, value: sum / count };
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const CountIfInternal = (range, criteria) => {
|
|
240
|
+
// do we really need parser/calculator for this? I think
|
|
241
|
+
// we've maybe gone overboard here, could we just use valueparser
|
|
242
|
+
// on the criteria and then calculate normally? I think we might...
|
|
243
|
+
// in any event there are no dynamic dependencies with this
|
|
244
|
+
// function.
|
|
245
|
+
const data = Utilities.FlattenCellValues(range, true); // keep undefineds, important for mapping
|
|
246
|
+
let parse_result;
|
|
247
|
+
let expression;
|
|
248
|
+
// we'll handle operator and operand separately
|
|
249
|
+
let operator = '=';
|
|
250
|
+
// handle wildcards first. if we have a wildcard we use a
|
|
251
|
+
// matching function so we can centralize.
|
|
252
|
+
if (typeof criteria === 'string') {
|
|
253
|
+
// normalize first, pull out operator
|
|
254
|
+
criteria = criteria.trim();
|
|
255
|
+
const match = criteria.match(/^([=<>]+)/);
|
|
256
|
+
if (match) {
|
|
257
|
+
operator = match[1];
|
|
258
|
+
criteria = criteria.substring(operator.length);
|
|
259
|
+
}
|
|
260
|
+
const value_parser_result = ValueParser.TryParse(criteria);
|
|
261
|
+
if (value_parser_result?.type === ValueType.string) {
|
|
262
|
+
criteria = `"${value_parser_result.value}"`;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
criteria = value_parser_result?.value?.toString() || '';
|
|
266
|
+
}
|
|
267
|
+
// console.info({operator, criteria});
|
|
268
|
+
// check for wildcards (this will false-positive on escaped
|
|
269
|
+
// wildcards, which will not break but will waste cycles. we
|
|
270
|
+
// could check. TOOD/FIXME)
|
|
271
|
+
if (/[?*]/.test(criteria)) {
|
|
272
|
+
// NOTE: we're not specifying an argument separator when writing
|
|
273
|
+
// functions, because that might break numbers passed as strings.
|
|
274
|
+
// so we write the function based on the current separator.
|
|
275
|
+
const separator = this.parser.argument_separator;
|
|
276
|
+
if (operator === '=' || operator === '<>') {
|
|
277
|
+
parse_result = this.parser.Parse(`=WildcardMatch({}${separator} ${criteria}${separator} ${operator === '<>'})`);
|
|
278
|
+
expression = parse_result.expression;
|
|
279
|
+
if (parse_result.error || !expression) {
|
|
280
|
+
return ExpressionError();
|
|
281
|
+
}
|
|
282
|
+
if (expression?.type === 'call' && expression.args[0]?.type === 'array') {
|
|
283
|
+
expression.args[0].values = [Utilities.FilterIntrinsics(data, true)];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// if it's not a string, by definition it doesn't have an
|
|
290
|
+
// operator so use equality (default). it does not need
|
|
291
|
+
// escaping.
|
|
292
|
+
criteria = (criteria || 0).toString();
|
|
293
|
+
}
|
|
294
|
+
if (!parse_result) {
|
|
295
|
+
parse_result = this.parser.Parse('{}' + operator + criteria);
|
|
296
|
+
expression = parse_result.expression;
|
|
297
|
+
if (parse_result.error || !expression) {
|
|
298
|
+
return ExpressionError();
|
|
299
|
+
}
|
|
300
|
+
if (expression.type !== 'binary') {
|
|
301
|
+
console.warn('invalid expression [1]', expression);
|
|
302
|
+
return ExpressionError();
|
|
303
|
+
}
|
|
304
|
+
if (expression.left.type !== 'array') {
|
|
305
|
+
console.warn('invalid expression [1]', expression);
|
|
306
|
+
return ExpressionError();
|
|
307
|
+
}
|
|
308
|
+
// this is only going to work for binary left/right. it won't
|
|
309
|
+
// work if we change this to a function (wildcard match)
|
|
310
|
+
// this will not happen anymore, we can remove
|
|
311
|
+
if (expression.right.type === 'identifier') {
|
|
312
|
+
console.warn('will never happen');
|
|
313
|
+
expression.right = {
|
|
314
|
+
...expression.right,
|
|
315
|
+
type: 'literal',
|
|
316
|
+
value: expression.right.name,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
expression.left.values = [Utilities.FilterIntrinsics(data, true)];
|
|
320
|
+
}
|
|
321
|
+
if (!expression) {
|
|
322
|
+
return ValueError();
|
|
323
|
+
}
|
|
324
|
+
const result = this.CalculateExpression(expression);
|
|
325
|
+
return result;
|
|
326
|
+
};
|
|
327
|
+
this.library.Register({
|
|
328
|
+
/**
|
|
329
|
+
* this function is here because it checks whether rows are hidden or
|
|
330
|
+
* not. cell dependencies don't track that, so we need to do it here.
|
|
331
|
+
* and it needs to be volatile. this is an ugly, ugly function.
|
|
332
|
+
*/
|
|
333
|
+
Subtotal: {
|
|
334
|
+
arguments: [
|
|
335
|
+
{ name: 'type' },
|
|
336
|
+
{ name: 'range', metadata: true, }
|
|
337
|
+
],
|
|
338
|
+
fn: (type, ...args) => {
|
|
339
|
+
type = TranslateSubtotalType(type);
|
|
340
|
+
// validate, I guess
|
|
341
|
+
if (type > 100) {
|
|
342
|
+
type -= 100;
|
|
343
|
+
}
|
|
344
|
+
if (type < 1 || type > 11) {
|
|
345
|
+
return ArgumentError();
|
|
346
|
+
}
|
|
347
|
+
// any number of ranges are allowed, they will inherit
|
|
348
|
+
// the properties of the last argument so they will all
|
|
349
|
+
// return metadata
|
|
350
|
+
const flat = Utilities.FlattenBoxed(args);
|
|
351
|
+
// values is the set of values from the arguments that
|
|
352
|
+
// are numbers -- not strings, not errors -- and are not
|
|
353
|
+
// hidden. that last thing is the hard part.
|
|
354
|
+
// there's one other thing we care about which is non-empty,
|
|
355
|
+
// for COUNTA -- we can do that separately
|
|
356
|
+
const values = [];
|
|
357
|
+
let counta = 0;
|
|
358
|
+
let sum = 0;
|
|
359
|
+
let sheet;
|
|
360
|
+
for (const entry of flat) {
|
|
361
|
+
// where is the metadata type? sigh
|
|
362
|
+
const address = (entry.value?.address);
|
|
363
|
+
if (!address) {
|
|
364
|
+
return ReferenceError();
|
|
365
|
+
}
|
|
366
|
+
if (!sheet || sheet.id !== address.sheet_id) {
|
|
367
|
+
if (!address.sheet_id) {
|
|
368
|
+
console.warn('invalid reference in metadata');
|
|
369
|
+
return ReferenceError();
|
|
370
|
+
}
|
|
371
|
+
sheet = this.model.sheets.Find(address.sheet_id);
|
|
372
|
+
if (!sheet) {
|
|
373
|
+
console.warn('invalid sheet in metadata');
|
|
374
|
+
return ReferenceError();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const height = sheet.GetRowHeight(address.row);
|
|
378
|
+
if (!height) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const entry_value = entry.value?.value;
|
|
382
|
+
// counta includes empty strings
|
|
383
|
+
if (typeof entry_value === 'undefined') {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
counta++;
|
|
387
|
+
if (typeof entry_value === 'number') {
|
|
388
|
+
sum += entry_value;
|
|
389
|
+
values.push(entry_value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let value = 0;
|
|
393
|
+
switch (type) {
|
|
394
|
+
case 1: // average
|
|
395
|
+
if (values.length === 0) {
|
|
396
|
+
return DivideByZeroError();
|
|
397
|
+
}
|
|
398
|
+
value = sum / values.length;
|
|
399
|
+
break;
|
|
400
|
+
case 2: // count
|
|
401
|
+
value = values.length;
|
|
402
|
+
break;
|
|
403
|
+
case 3: // counta
|
|
404
|
+
value = counta;
|
|
405
|
+
break;
|
|
406
|
+
case 4: // max
|
|
407
|
+
if (values.length === 0) {
|
|
408
|
+
return ValueError();
|
|
409
|
+
}
|
|
410
|
+
value = Math.max.apply(0, values);
|
|
411
|
+
break;
|
|
412
|
+
case 5: // min
|
|
413
|
+
if (values.length === 0) {
|
|
414
|
+
return ValueError();
|
|
415
|
+
}
|
|
416
|
+
value = Math.min.apply(0, values);
|
|
417
|
+
break;
|
|
418
|
+
case 6: // product
|
|
419
|
+
if (values.length === 0) {
|
|
420
|
+
return ValueError();
|
|
421
|
+
}
|
|
422
|
+
value = 1;
|
|
423
|
+
for (const entry of values) {
|
|
424
|
+
value *= entry;
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
case 7: // stdev.s
|
|
428
|
+
if (values.length < 2) {
|
|
429
|
+
return DivideByZeroError();
|
|
430
|
+
}
|
|
431
|
+
value = Math.sqrt(Variance(values, true));
|
|
432
|
+
break;
|
|
433
|
+
case 8: // stdev.p
|
|
434
|
+
if (values.length === 0) {
|
|
435
|
+
return DivideByZeroError();
|
|
436
|
+
}
|
|
437
|
+
value = Math.sqrt(Variance(values, false));
|
|
438
|
+
break;
|
|
439
|
+
case 9: // sum
|
|
440
|
+
value = sum;
|
|
441
|
+
break;
|
|
442
|
+
case 10: // var.s
|
|
443
|
+
if (values.length < 2) {
|
|
444
|
+
return DivideByZeroError();
|
|
445
|
+
}
|
|
446
|
+
value = Variance(values, true);
|
|
447
|
+
break;
|
|
448
|
+
case 11: // var.p
|
|
449
|
+
if (values.length === 0) {
|
|
450
|
+
return DivideByZeroError();
|
|
451
|
+
}
|
|
452
|
+
value = Variance(values, false);
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
// console.info({type, args, flat, values});
|
|
456
|
+
return {
|
|
457
|
+
type: ValueType.number,
|
|
458
|
+
value,
|
|
459
|
+
};
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
Cell: {
|
|
463
|
+
description: 'Returns data about a cell',
|
|
464
|
+
arguments: [
|
|
465
|
+
{ name: 'type', description: 'Type of data to return', unroll: true, },
|
|
466
|
+
{ name: 'reference', description: 'Cell reference', metadata: true, unroll: true, },
|
|
467
|
+
],
|
|
468
|
+
// there's no concept of "structure volatile", and structure events
|
|
469
|
+
// don't trigger recalc, so this is not helpful -- we may need to
|
|
470
|
+
// think about both of those things
|
|
471
|
+
// volatile: true,
|
|
472
|
+
fn: (type, reference) => {
|
|
473
|
+
if (!UnionIsMetadata(reference)) {
|
|
474
|
+
return ReferenceError();
|
|
475
|
+
}
|
|
476
|
+
if (type) {
|
|
477
|
+
switch (type.toString().toLowerCase()) {
|
|
478
|
+
case 'format':
|
|
479
|
+
return reference.value.format ? // || ReferenceError;
|
|
480
|
+
{ type: ValueType.string, value: reference.value.format } : ReferenceError();
|
|
481
|
+
case 'address':
|
|
482
|
+
{
|
|
483
|
+
let sheet_name = '';
|
|
484
|
+
if (reference.value.address.sheet_id) {
|
|
485
|
+
const sheet = this.model.sheets.Find(reference.value.address.sheet_id);
|
|
486
|
+
sheet_name = sheet?.name || '';
|
|
487
|
+
}
|
|
488
|
+
if (sheet_name) {
|
|
489
|
+
if (QuotedSheetNameRegex.test(sheet_name)) {
|
|
490
|
+
sheet_name = `'${sheet_name}'`;
|
|
491
|
+
}
|
|
492
|
+
sheet_name += '!';
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
type: ValueType.string,
|
|
496
|
+
value: '[]' + sheet_name + reference.value.address.label.replace(/\$/g, ''),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return { type: ValueType.error, value: NotImplError.error };
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
Address: {
|
|
505
|
+
arguments: [
|
|
506
|
+
{ name: 'row' },
|
|
507
|
+
{ name: 'column' },
|
|
508
|
+
{ name: 'absolute' },
|
|
509
|
+
{ name: 'a1' },
|
|
510
|
+
{ name: 'sheet name' }
|
|
511
|
+
],
|
|
512
|
+
fn: (row = 1, column = 1, absolute = 1, a1 = true, sheet_name) => {
|
|
513
|
+
const address = {
|
|
514
|
+
type: 'address',
|
|
515
|
+
id: 0, position: 0, label: '',
|
|
516
|
+
row: row - 1,
|
|
517
|
+
column: column - 1,
|
|
518
|
+
};
|
|
519
|
+
switch (absolute) {
|
|
520
|
+
case 2:
|
|
521
|
+
address.absolute_row = true;
|
|
522
|
+
break;
|
|
523
|
+
case 3:
|
|
524
|
+
address.absolute_column = true;
|
|
525
|
+
break;
|
|
526
|
+
case 4:
|
|
527
|
+
break;
|
|
528
|
+
default:
|
|
529
|
+
address.absolute_column = true;
|
|
530
|
+
address.absolute_row = true;
|
|
531
|
+
}
|
|
532
|
+
if (sheet_name) {
|
|
533
|
+
address.sheet = sheet_name;
|
|
534
|
+
}
|
|
535
|
+
return StringUnion(this.parser.Render(address, { r1c1: !a1 }));
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
/**
|
|
539
|
+
* anything I said about COUNTIF applies here, but worse.
|
|
540
|
+
* COUNTIFS is an AND operation across separate COUNTIFs.
|
|
541
|
+
* presumably they have to be the same shape.
|
|
542
|
+
*/
|
|
543
|
+
CountIfs: {
|
|
544
|
+
arguments: [
|
|
545
|
+
{ name: 'range', },
|
|
546
|
+
{ name: 'criteria', },
|
|
547
|
+
{ name: 'range', },
|
|
548
|
+
{ name: 'criteria', }
|
|
549
|
+
],
|
|
550
|
+
fn: (...args) => {
|
|
551
|
+
return XIf('count', args[0], ...args);
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
/** @see CountIf */
|
|
555
|
+
AverageIf: {
|
|
556
|
+
arguments: [
|
|
557
|
+
{ name: 'range', },
|
|
558
|
+
{ name: 'criteria', },
|
|
559
|
+
],
|
|
560
|
+
fn: (range, criteria, average_range) => {
|
|
561
|
+
return XIf('average', average_range || range, range, criteria);
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
/** @see CountIf */
|
|
565
|
+
AverageIfs: {
|
|
566
|
+
arguments: [
|
|
567
|
+
{ name: 'value range', },
|
|
568
|
+
{ name: 'criteria range', },
|
|
569
|
+
{ name: 'criteria', },
|
|
570
|
+
{ name: 'criteria range', },
|
|
571
|
+
{ name: 'criteria', },
|
|
572
|
+
],
|
|
573
|
+
fn: (range, ...args) => {
|
|
574
|
+
return XIf('average', range, ...args);
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
/** @see CountIf */
|
|
578
|
+
SumIf: {
|
|
579
|
+
arguments: [
|
|
580
|
+
{ name: 'range', },
|
|
581
|
+
{ name: 'criteria', },
|
|
582
|
+
],
|
|
583
|
+
fn: (range, criteria, sum_range) => {
|
|
584
|
+
return XIf('sum', sum_range || range, range, criteria);
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
/** @see CountIf */
|
|
588
|
+
SumIfs: {
|
|
589
|
+
arguments: [
|
|
590
|
+
{ name: 'value range', },
|
|
591
|
+
{ name: 'criteria range', },
|
|
592
|
+
{ name: 'criteria', },
|
|
593
|
+
{ name: 'criteria range', },
|
|
594
|
+
{ name: 'criteria', },
|
|
595
|
+
],
|
|
596
|
+
fn: (range, ...args) => {
|
|
597
|
+
return XIf('sum', range, ...args);
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
/**
|
|
601
|
+
* this function is here so it has access to the parser.
|
|
602
|
+
* this is crazy expensive. is there a way to reduce cost?
|
|
603
|
+
*
|
|
604
|
+
* we could, in theory, consider that there are only a few
|
|
605
|
+
* valid operations here -- all binary. instead of using a
|
|
606
|
+
* generic call to the CalculateExpression routine, we could
|
|
607
|
+
* short-cut and call the binary method.
|
|
608
|
+
*
|
|
609
|
+
* OTOH that makes it more fragile, and might not really
|
|
610
|
+
* provide that much in the way of savings. still, it would
|
|
611
|
+
* be good if we could somehow cache some of the effort,
|
|
612
|
+
* particularly if the list data changes but not the expression.
|
|
613
|
+
*
|
|
614
|
+
*/
|
|
615
|
+
CountIf: {
|
|
616
|
+
arguments: [
|
|
617
|
+
{ name: 'range', },
|
|
618
|
+
{ name: 'criteria', }
|
|
619
|
+
],
|
|
620
|
+
fn: (range, criteria) => {
|
|
621
|
+
return XIf('count', range, range, criteria);
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
/** like indirect, this creates dependencies at calc time */
|
|
625
|
+
Offset: {
|
|
626
|
+
arguments: [{
|
|
627
|
+
name: 'reference', description: 'Base reference', metadata: true,
|
|
628
|
+
}, {
|
|
629
|
+
name: 'rows', description: 'number of rows to offset'
|
|
630
|
+
}, {
|
|
631
|
+
name: 'columns', description: 'number of columns to offset'
|
|
632
|
+
}, {
|
|
633
|
+
name: 'height',
|
|
634
|
+
}, {
|
|
635
|
+
name: 'width',
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
return_type: 'reference',
|
|
639
|
+
volatile: true,
|
|
640
|
+
fn: ((reference, rows = 0, columns = 0, height, width) => {
|
|
641
|
+
if (!reference) {
|
|
642
|
+
return ArgumentError();
|
|
643
|
+
}
|
|
644
|
+
// const parse_result = this.parser.Parse(reference);
|
|
645
|
+
// if (parse_result.error || !parse_result.expression) {
|
|
646
|
+
// return ReferenceError;
|
|
647
|
+
//}
|
|
648
|
+
if (reference.type === ValueType.array) {
|
|
649
|
+
// subset array. this is constructed, so we can take ownership
|
|
650
|
+
// and modify it, although it would be safer to copy. also, what's
|
|
651
|
+
// the cost of functional vs imperative loops these days?
|
|
652
|
+
const end_row = typeof height === 'number' ? (rows + height) : undefined;
|
|
653
|
+
const end_column = typeof width === 'number' ? (columns + width) : undefined;
|
|
654
|
+
const result = {
|
|
655
|
+
type: ValueType.array,
|
|
656
|
+
value: reference.value.slice(rows, end_row).map(row => row.slice(columns, end_column)),
|
|
657
|
+
};
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
// we need a proper type for this... also it might be a range
|
|
661
|
+
if (!UnionIsMetadata(reference)) {
|
|
662
|
+
console.info('e2', { reference });
|
|
663
|
+
return ReferenceError();
|
|
664
|
+
}
|
|
665
|
+
const check_result = this.DynamicDependencies(reference.value.address, this.expression_calculator.context.address, true, rows, columns, width, height);
|
|
666
|
+
if (!check_result) {
|
|
667
|
+
console.info('e1', { check_result });
|
|
668
|
+
return ReferenceError();
|
|
669
|
+
}
|
|
670
|
+
if (check_result.dirty) {
|
|
671
|
+
const current_vertex = this.GetVertex(this.expression_calculator.context.address, true);
|
|
672
|
+
current_vertex.short_circuit = true;
|
|
673
|
+
return { type: ValueType.undefined, value: undefined };
|
|
674
|
+
}
|
|
675
|
+
if (check_result.area) {
|
|
676
|
+
const start = {
|
|
677
|
+
type: 'address', ...check_result.area.start,
|
|
678
|
+
label: '', position: 0,
|
|
679
|
+
// id: parse_result.expression.id,
|
|
680
|
+
id: 0,
|
|
681
|
+
};
|
|
682
|
+
const end = {
|
|
683
|
+
type: 'address', ...check_result.area.end,
|
|
684
|
+
label: '', position: 0,
|
|
685
|
+
// id: parse_result.expression.id,
|
|
686
|
+
id: 0,
|
|
687
|
+
};
|
|
688
|
+
const expression = check_result.area.count === 1 ? start : {
|
|
689
|
+
type: 'range', start, end,
|
|
690
|
+
label: '', position: 0,
|
|
691
|
+
// id: parse_result.expression.id,
|
|
692
|
+
id: 0,
|
|
693
|
+
};
|
|
694
|
+
// return this.CalculateExpression(expression, undefined, true);
|
|
695
|
+
// return expression;
|
|
696
|
+
return { type: ValueType.object, value: expression };
|
|
697
|
+
}
|
|
698
|
+
return ValueError();
|
|
699
|
+
}).bind(this),
|
|
700
|
+
},
|
|
701
|
+
Indirect: {
|
|
702
|
+
arguments: [
|
|
703
|
+
{ name: 'reference', description: 'Cell reference (string)' },
|
|
704
|
+
],
|
|
705
|
+
return_type: 'reference',
|
|
706
|
+
volatile: true, // necessary?
|
|
707
|
+
fn: ((reference) => {
|
|
708
|
+
if (!reference || (typeof reference !== 'string')) {
|
|
709
|
+
return ArgumentError();
|
|
710
|
+
}
|
|
711
|
+
const parse_result = this.parser.Parse(reference);
|
|
712
|
+
if (parse_result.error || !parse_result.expression ||
|
|
713
|
+
(parse_result.expression.type !== 'address' && parse_result.expression.type !== 'range')) {
|
|
714
|
+
return ReferenceError();
|
|
715
|
+
}
|
|
716
|
+
const check_result = this.DynamicDependencies(parse_result.expression, this.expression_calculator.context.address);
|
|
717
|
+
if (!check_result) {
|
|
718
|
+
return ReferenceError();
|
|
719
|
+
}
|
|
720
|
+
if (check_result.dirty) {
|
|
721
|
+
const current_vertex = this.GetVertex(this.expression_calculator.context.address, true);
|
|
722
|
+
current_vertex.short_circuit = true;
|
|
723
|
+
return { type: ValueType.undefined, value: undefined };
|
|
724
|
+
}
|
|
725
|
+
return { type: ValueType.object, value: parse_result.expression };
|
|
726
|
+
}).bind(this),
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* FIXME: there are cases we are not handling
|
|
730
|
+
*
|
|
731
|
+
* match seems to return either the matching row, in a column set,
|
|
732
|
+
* or matching column, in a row set. you can't search a 2d array.
|
|
733
|
+
* match also supports inexact matching but assumes data is ordered.
|
|
734
|
+
* (TODO).
|
|
735
|
+
*
|
|
736
|
+
* FIXME: we also need to icase match strings
|
|
737
|
+
*
|
|
738
|
+
*/
|
|
739
|
+
Match: {
|
|
740
|
+
arguments: [
|
|
741
|
+
{ name: 'value', boxed: true },
|
|
742
|
+
{ name: 'range', boxed: true },
|
|
743
|
+
{ name: 'type', },
|
|
744
|
+
],
|
|
745
|
+
fn: (value, range, type = 0) => {
|
|
746
|
+
if (type) {
|
|
747
|
+
console.warn('inexact match not supported', { value, range, type });
|
|
748
|
+
return NAError();
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
// I suppose you can match on a single value
|
|
752
|
+
if (range.type === ValueType.array) {
|
|
753
|
+
if (range.value.length === 1) {
|
|
754
|
+
const arr = range.value[0];
|
|
755
|
+
for (let i = 0; i < arr.length; i++) {
|
|
756
|
+
if (value.type == arr[i].type && value.value === arr[i].value) {
|
|
757
|
+
return { type: ValueType.number, value: i + 1 };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
for (let i = 0; i < range.value.length; i++) {
|
|
763
|
+
const arr = range.value[i];
|
|
764
|
+
if (arr.length !== 1) {
|
|
765
|
+
return NAError();
|
|
766
|
+
}
|
|
767
|
+
if (value.type == arr[0].type && value.value === arr[0].value) {
|
|
768
|
+
return { type: ValueType.number, value: i + 1 };
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return NAError();
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
if (value.type === range.type && value.value === range.value) {
|
|
776
|
+
return {
|
|
777
|
+
type: ValueType.number, value: 1,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
return NAError();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return ArgumentError();
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
/**
|
|
787
|
+
* FIXME: there are cases we are not handling
|
|
788
|
+
*
|
|
789
|
+
* update to return a reference so you can use it as part of a
|
|
790
|
+
* range. we're not handling literal arrays atm.
|
|
791
|
+
*/
|
|
792
|
+
Index: {
|
|
793
|
+
return_type: 'reference',
|
|
794
|
+
arguments: [
|
|
795
|
+
{ name: 'range', metadata: true, },
|
|
796
|
+
{ name: 'row', },
|
|
797
|
+
{ name: 'column', }
|
|
798
|
+
],
|
|
799
|
+
// volatile: true, // not sure this is necessary bc input is the range
|
|
800
|
+
volatile: false,
|
|
801
|
+
// FIXME: handle full row, full column calls
|
|
802
|
+
fn: (range, row, column) => {
|
|
803
|
+
if (!range) {
|
|
804
|
+
return ArgumentError();
|
|
805
|
+
}
|
|
806
|
+
// this is illegal, although we could just default to zeros
|
|
807
|
+
if (row === undefined && column === undefined) {
|
|
808
|
+
return ArgumentError();
|
|
809
|
+
}
|
|
810
|
+
row = row || 0;
|
|
811
|
+
column = column || 0;
|
|
812
|
+
let arr = [];
|
|
813
|
+
// NOTE: could be a single cell. in that case it won't be passed
|
|
814
|
+
// as an array so we'll need a second branch (or convert it)
|
|
815
|
+
if (range.type === ValueType.array) {
|
|
816
|
+
// FIXME: validate these are addresses (shouldn't be necessary
|
|
817
|
+
// if we're marking the argument as metadata? what about literals?)
|
|
818
|
+
// check rows and columns. we might need to return an array.
|
|
819
|
+
arr = range.value;
|
|
820
|
+
}
|
|
821
|
+
else if (range.type === ValueType.object) {
|
|
822
|
+
const metadata = range.value;
|
|
823
|
+
if (metadata.type === 'metadata' && metadata.address) {
|
|
824
|
+
arr.push([range]);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const columns = arr.length;
|
|
828
|
+
const rows = arr[0]?.length || 0;
|
|
829
|
+
if (rows <= 0 || columns <= 0 || row > rows || column > columns || row < 0 || column < 0) {
|
|
830
|
+
return ArgumentError();
|
|
831
|
+
}
|
|
832
|
+
// return everything if arguments are (0, 0)
|
|
833
|
+
if (column === 0 && row === 0) {
|
|
834
|
+
// because of the way we're structured this we might be
|
|
835
|
+
// returning a range of length 1; in that case we want
|
|
836
|
+
// to return it as an address
|
|
837
|
+
const expression = (columns === 1 && rows === 1) ? {
|
|
838
|
+
...arr[0][0].value.address,
|
|
839
|
+
} : {
|
|
840
|
+
type: 'range',
|
|
841
|
+
start: arr[0][0].value.address,
|
|
842
|
+
end: arr[columns - 1][rows - 1].value.address,
|
|
843
|
+
label: '', position: 0,
|
|
844
|
+
id: 0,
|
|
845
|
+
};
|
|
846
|
+
return {
|
|
847
|
+
type: ValueType.object,
|
|
848
|
+
value: expression,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
// single cell
|
|
852
|
+
if ((row || rows === 1) && (column || columns === 1)) {
|
|
853
|
+
return {
|
|
854
|
+
type: ValueType.object,
|
|
855
|
+
value: arr[column ? column - 1 : 0][row ? row - 1 : 0].value.address,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
// sub array
|
|
859
|
+
if (row) {
|
|
860
|
+
// return column
|
|
861
|
+
const expression = {
|
|
862
|
+
type: 'range',
|
|
863
|
+
start: arr[0][row - 1].value.address,
|
|
864
|
+
end: arr[columns - 1][row - 1].value.address,
|
|
865
|
+
label: '', position: 0,
|
|
866
|
+
id: 0,
|
|
867
|
+
};
|
|
868
|
+
return {
|
|
869
|
+
type: ValueType.object,
|
|
870
|
+
value: expression,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
else if (column) {
|
|
874
|
+
// return row
|
|
875
|
+
const expression = {
|
|
876
|
+
type: 'range',
|
|
877
|
+
start: arr[column - 1][0].value.address,
|
|
878
|
+
end: arr[column - 1][rows - 1].value.address,
|
|
879
|
+
label: '', position: 0,
|
|
880
|
+
id: 0,
|
|
881
|
+
};
|
|
882
|
+
return {
|
|
883
|
+
type: ValueType.object,
|
|
884
|
+
value: expression,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
return ArgumentError();
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
/**
|
|
891
|
+
* this one does not have to be here, it's just here because
|
|
892
|
+
* the rest of the reference/lookup functions are here
|
|
893
|
+
*/
|
|
894
|
+
Rows: {
|
|
895
|
+
arguments: [{
|
|
896
|
+
name: 'reference', description: 'Array or reference'
|
|
897
|
+
},
|
|
898
|
+
],
|
|
899
|
+
volatile: false,
|
|
900
|
+
fn: (reference) => {
|
|
901
|
+
if (!reference) {
|
|
902
|
+
return ArgumentError();
|
|
903
|
+
}
|
|
904
|
+
if (Array.isArray(reference)) {
|
|
905
|
+
const column = reference[0];
|
|
906
|
+
if (Array.isArray(column)) {
|
|
907
|
+
return { type: ValueType.number, value: column.length };
|
|
908
|
+
}
|
|
909
|
+
return ValueError();
|
|
910
|
+
}
|
|
911
|
+
return { type: ValueType.number, value: 1 };
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
/**
|
|
915
|
+
* this one does not have to be here, it's just here because
|
|
916
|
+
* the rest of the reference/lookup functions are here
|
|
917
|
+
*/
|
|
918
|
+
Columns: {
|
|
919
|
+
arguments: [{
|
|
920
|
+
name: 'reference', description: 'Array or reference'
|
|
921
|
+
},
|
|
922
|
+
],
|
|
923
|
+
volatile: false,
|
|
924
|
+
fn: (reference) => {
|
|
925
|
+
if (!reference) {
|
|
926
|
+
return ArgumentError();
|
|
927
|
+
}
|
|
928
|
+
if (Array.isArray(reference)) {
|
|
929
|
+
return { type: ValueType.number, value: reference.length };
|
|
930
|
+
}
|
|
931
|
+
return { type: ValueType.number, value: 1 };
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
/**
|
|
935
|
+
* not sure when this one appeared, but it's what I was looking for
|
|
936
|
+
*
|
|
937
|
+
* ---
|
|
938
|
+
* what an odd comment. what does that mean?
|
|
939
|
+
*/
|
|
940
|
+
FormulaText: {
|
|
941
|
+
description: 'Returns a formula as a string',
|
|
942
|
+
arguments: [
|
|
943
|
+
{ name: 'reference', description: 'Cell reference', metadata: true, unroll: true },
|
|
944
|
+
],
|
|
945
|
+
fn: (reference) => {
|
|
946
|
+
if (!UnionIsMetadata(reference)) {
|
|
947
|
+
return ReferenceError();
|
|
948
|
+
}
|
|
949
|
+
const sheet = this.model.sheets.Find(reference.value?.address?.sheet_id || 0);
|
|
950
|
+
if (sheet) {
|
|
951
|
+
const cell = sheet.cells.GetCell(reference.value.address, false);
|
|
952
|
+
return {
|
|
953
|
+
type: ValueType.string,
|
|
954
|
+
value: cell?.value?.toString() || '',
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
return ReferenceError();
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
/**
|
|
961
|
+
* this should be in the 'information' library but it needs reference
|
|
962
|
+
* to the underlying cell (unresolved)
|
|
963
|
+
*/
|
|
964
|
+
IsFormula: {
|
|
965
|
+
description: 'Returns true if the reference is a formula',
|
|
966
|
+
arguments: [{
|
|
967
|
+
name: 'Reference',
|
|
968
|
+
unroll: true,
|
|
969
|
+
metadata: true, /* OK with array metadata */
|
|
970
|
+
}],
|
|
971
|
+
fn: (ref) => {
|
|
972
|
+
// this is wasteful because we know that the range will all
|
|
973
|
+
// be in the same sheet... we don't need to look up every time
|
|
974
|
+
const addr = ref?.value?.address;
|
|
975
|
+
const sheet = this.model.sheets.Find(addr?.sheet_id || 0);
|
|
976
|
+
if (addr && sheet) {
|
|
977
|
+
const cell = sheet.cells.GetCell(addr, false);
|
|
978
|
+
return {
|
|
979
|
+
type: ValueType.boolean,
|
|
980
|
+
value: cell?.type === ValueType.formula,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
return {
|
|
984
|
+
type: ValueType.boolean, value: false,
|
|
985
|
+
};
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* new async init method. we need this for subclasses, but
|
|
992
|
+
* we should consider moving base methods in here as well.
|
|
993
|
+
*
|
|
994
|
+
* this function must be idempotent, so we can call it from
|
|
995
|
+
* multiple paths
|
|
996
|
+
*/
|
|
997
|
+
async InitResources() {
|
|
998
|
+
if (this.async_resource_init) {
|
|
999
|
+
// console.info("init resources noop");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
// console.info("init resources (async)");
|
|
1003
|
+
this.async_resource_init = true;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* support for co-editing. we need to export calculated values from
|
|
1007
|
+
* the leader instance, because things like RAND() and NOW() are
|
|
1008
|
+
* nondeterministic (within reason).
|
|
1009
|
+
*
|
|
1010
|
+
* so the leader does the calculation and then we broadcast calculated
|
|
1011
|
+
* values to followers.
|
|
1012
|
+
*/
|
|
1013
|
+
ExportCalculatedValues() {
|
|
1014
|
+
const data = {};
|
|
1015
|
+
for (const sheet of this.model.sheets.list) {
|
|
1016
|
+
const calculated = sheet.cells.toJSON({ calculated_value: true }).data;
|
|
1017
|
+
data[sheet.id] = calculated.filter(test => test.calculated !== undefined);
|
|
1018
|
+
}
|
|
1019
|
+
return data;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* support for co-editing. if we get calculated values from the leader,
|
|
1023
|
+
* we need to apply them to cells.
|
|
1024
|
+
*
|
|
1025
|
+
* to _see_ the data, you still have to make a couple of calls to repaint
|
|
1026
|
+
* and update annotations. see EmbeddedSpreadsheetBase.Recalculate for hints.
|
|
1027
|
+
*
|
|
1028
|
+
* note that we're checking for list mismatch in one direction but not the
|
|
1029
|
+
* other direction. should probably check both.
|
|
1030
|
+
*/
|
|
1031
|
+
ApplyCalculatedValues(data) {
|
|
1032
|
+
for (const sheet of this.model.sheets.list) {
|
|
1033
|
+
const cells = data[sheet.id];
|
|
1034
|
+
if (!cells) {
|
|
1035
|
+
console.info('mismatch', sheet.id);
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
for (const cell of cells) {
|
|
1039
|
+
sheet.cells.data[cell.row][cell.column].SetCalculatedValue(cell.calculated);
|
|
1040
|
+
// console.info(sheet.id, cell.row, cell.column, '->', cell.calculated);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
AttachSpillData(area, cells) {
|
|
1046
|
+
if (!cells) {
|
|
1047
|
+
// can we assume active sheet here? actually I guess not, we'll
|
|
1048
|
+
// need to set that...
|
|
1049
|
+
const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : undefined;
|
|
1050
|
+
cells = sheet?.cells;
|
|
1051
|
+
}
|
|
1052
|
+
if (!cells) {
|
|
1053
|
+
console.info({ area, cells });
|
|
1054
|
+
throw new Error('invalid sheet ID in attach spill data');
|
|
1055
|
+
}
|
|
1056
|
+
const vertex = new StateLeafVertex();
|
|
1057
|
+
let counter = 0;
|
|
1058
|
+
let error = false;
|
|
1059
|
+
for (const { cell, row, column } of cells.IterateRC(area, true)) {
|
|
1060
|
+
if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
|
|
1061
|
+
error = true; // spill error.
|
|
1062
|
+
}
|
|
1063
|
+
this.AddLeafVertexEdge({ row, column, sheet_id: area.start.sheet_id }, vertex);
|
|
1064
|
+
}
|
|
1065
|
+
// console.info("storing spill data");
|
|
1066
|
+
this.spill_data.push({ area, vertex });
|
|
1067
|
+
return { vertex, area, error };
|
|
1068
|
+
}
|
|
1069
|
+
SpillCallback(vertex, result) {
|
|
1070
|
+
const { reference, address } = vertex;
|
|
1071
|
+
const { value } = result;
|
|
1072
|
+
const recalculate_list = [];
|
|
1073
|
+
if (!reference) {
|
|
1074
|
+
// should throw but this is new and I don't want to break stuff rn
|
|
1075
|
+
console.error("invalid reference in spill callback");
|
|
1076
|
+
return recalculate_list;
|
|
1077
|
+
}
|
|
1078
|
+
if (!address || !address.sheet_id) {
|
|
1079
|
+
// should throw but this is new and I don't want to break stuff rn
|
|
1080
|
+
console.error("invalid address in spill callback");
|
|
1081
|
+
return recalculate_list;
|
|
1082
|
+
}
|
|
1083
|
+
// I guess we could do the one-cell version here
|
|
1084
|
+
if (value.length === 1 && value[0].length === 1) {
|
|
1085
|
+
reference.SetCalculatedValue(value[0][0].value);
|
|
1086
|
+
return recalculate_list;
|
|
1087
|
+
}
|
|
1088
|
+
if (!this.options.spill) {
|
|
1089
|
+
reference.SetCalculatedValue(value[0][0].value);
|
|
1090
|
+
return recalculate_list;
|
|
1091
|
+
}
|
|
1092
|
+
// console.info("SPILLING");
|
|
1093
|
+
const sheet = this.model.sheets.Find(address.sheet_id);
|
|
1094
|
+
const cells = sheet?.cells;
|
|
1095
|
+
if (cells) {
|
|
1096
|
+
// first thing we do is check for empty. if !empty, that's a
|
|
1097
|
+
// spill error and we can stop. also check for area, spill and
|
|
1098
|
+
// merge (and table).
|
|
1099
|
+
const columns = result.value.length;
|
|
1100
|
+
const rows = result.value[0].length;
|
|
1101
|
+
const area = new Area(address).Reshape(rows, columns);
|
|
1102
|
+
/*
|
|
1103
|
+
let counter = 0;
|
|
1104
|
+
let error = false;
|
|
1105
|
+
const leaf = new StateLeafVertex();
|
|
1106
|
+
|
|
1107
|
+
for (const {cell, row, column} of cells.IterateRC(area, true)) {
|
|
1108
|
+
if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
|
|
1109
|
+
error = true; // spill error.
|
|
1110
|
+
}
|
|
1111
|
+
this.AddLeafVertexEdge({row, column, sheet_id: area.start.sheet_id}, leaf);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
console.info("storing spill data");
|
|
1115
|
+
|
|
1116
|
+
// this.spills.push(new Area(area.start, area.end));
|
|
1117
|
+
this.spill_data.push({area, vertex: leaf});
|
|
1118
|
+
*/
|
|
1119
|
+
const { error } = this.AttachSpillData(area, cells);
|
|
1120
|
+
if (error) {
|
|
1121
|
+
// console.info("returning error");
|
|
1122
|
+
reference.SetCalculationError('SPILL');
|
|
1123
|
+
return recalculate_list;
|
|
1124
|
+
}
|
|
1125
|
+
// expand the sheet, if necessary (+1)
|
|
1126
|
+
if (sheet.rows < area.end.row + 1) {
|
|
1127
|
+
sheet.cells.EnsureRow(area.end.row + 1);
|
|
1128
|
+
this.grid_expanded = true;
|
|
1129
|
+
}
|
|
1130
|
+
if (sheet.columns < area.end.column + 1) {
|
|
1131
|
+
sheet.cells.EnsureColumn(area.end.column + 1);
|
|
1132
|
+
this.grid_expanded = true;
|
|
1133
|
+
}
|
|
1134
|
+
// hmmm... we need the grid to update... how can we ensure that?
|
|
1135
|
+
// we could use a flag that the embedded sheet checks after
|
|
1136
|
+
// calculation... which is kind of sloppy but I don't have a better
|
|
1137
|
+
// idea
|
|
1138
|
+
const sheet_id = address.sheet_id;
|
|
1139
|
+
// let dirty = false;
|
|
1140
|
+
for (const { row, column } of cells.IterateRC(area)) {
|
|
1141
|
+
if (row === address.row && column === address.column) {
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
const vertex = this.GetVertex({ sheet_id, row, column }, false);
|
|
1145
|
+
if (vertex) {
|
|
1146
|
+
// onsole.info("Have vertex @", row, column, "dirty?", vertex.dirty);
|
|
1147
|
+
if (!vertex.dirty) {
|
|
1148
|
+
recalculate_list.push(vertex);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
// do we need these edges? if so, what for? (...)
|
|
1152
|
+
// I guess to propagate dirty if there's a dependent?
|
|
1153
|
+
// apparently not, although I'm not sure why...
|
|
1154
|
+
// this.AddEdge(address, {sheet_id, row, column});
|
|
1155
|
+
}
|
|
1156
|
+
/*
|
|
1157
|
+
// ok, now we can go on: copying a little from dynamic dependencies,
|
|
1158
|
+
// we're going to add vertices and check for dirty:
|
|
1159
|
+
|
|
1160
|
+
const sheet_id = address.sheet_id;
|
|
1161
|
+
let dirty = false;
|
|
1162
|
+
|
|
1163
|
+
for (const {row, column} of cells.IterateRC(area)) {
|
|
1164
|
+
|
|
1165
|
+
if (row === address.row && column === address.column) { continue; }
|
|
1166
|
+
|
|
1167
|
+
const vertex = this.GetVertex({sheet_id, row, column}, true);
|
|
1168
|
+
if (vertex && vertex.dirty) {
|
|
1169
|
+
|
|
1170
|
+
console.info(`Adding edge from ${{row: address.row, column: address.column}} -> ${{row, column}}`)
|
|
1171
|
+
|
|
1172
|
+
// see comments in DynamicDependencies()
|
|
1173
|
+
|
|
1174
|
+
this.AddEdge(address, {row, column, sheet_id});
|
|
1175
|
+
dirty = true;
|
|
1176
|
+
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
console.info("DIRTY?", dirty);
|
|
1182
|
+
|
|
1183
|
+
if (dirty) {
|
|
1184
|
+
const current_vertex = this.GetVertex(address, true) as SpreadsheetVertex;
|
|
1185
|
+
current_vertex.short_circuit = true;
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
*/
|
|
1189
|
+
//
|
|
1190
|
+
// maybe we could use a vertex here?
|
|
1191
|
+
// actually we also need to do a loop check
|
|
1192
|
+
// so I think the approach is
|
|
1193
|
+
//
|
|
1194
|
+
// 1 - create a vertex (spill -- array vertex?)
|
|
1195
|
+
// 2 - check for loops
|
|
1196
|
+
// 3 - if no loop, check for empty
|
|
1197
|
+
// 4 - if empty, fill in values
|
|
1198
|
+
//
|
|
1199
|
+
// and then we need to flush spill vertices at
|
|
1200
|
+
// some point, either always on recalc, or on
|
|
1201
|
+
// recalc if something is dirty. flushing spill
|
|
1202
|
+
// vertices implies removing all spilled values
|
|
1203
|
+
// so they will be empty if something changes
|
|
1204
|
+
// PLAN: start by flushing all spill vertices on
|
|
1205
|
+
// every recalc, and then we can trim it back
|
|
1206
|
+
// spill ok, set values
|
|
1207
|
+
for (let { cell, row, column } of cells.IterateRC(area)) {
|
|
1208
|
+
cell.spill = area;
|
|
1209
|
+
row -= address.row;
|
|
1210
|
+
column -= address.column;
|
|
1211
|
+
const v = result.value[column][row];
|
|
1212
|
+
switch (v.type) {
|
|
1213
|
+
case ValueType.object:
|
|
1214
|
+
case ValueType.array:
|
|
1215
|
+
case ValueType.function:
|
|
1216
|
+
break;
|
|
1217
|
+
default:
|
|
1218
|
+
cell.SetCalculatedValue(v.value, v.type);
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return recalculate_list;
|
|
1223
|
+
}
|
|
1224
|
+
//
|
|
1225
|
+
console.error("invalid cell reference in spill callback");
|
|
1226
|
+
reference.SetCalculationError('SPILL');
|
|
1227
|
+
return [];
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* this is a mess [not as bad as it used to be]
|
|
1231
|
+
*/
|
|
1232
|
+
SpreadCallback(vertex, value) {
|
|
1233
|
+
if (!vertex.address || !vertex.address.sheet_id) {
|
|
1234
|
+
throw new Error('spread callback called without sheet id');
|
|
1235
|
+
}
|
|
1236
|
+
// const cells = this.cells_map[vertex.address.sheet_id];
|
|
1237
|
+
const cells = this.model.sheets.Find(vertex.address.sheet_id)?.cells;
|
|
1238
|
+
if (!cells) {
|
|
1239
|
+
throw new Error('spread callback called without cells');
|
|
1240
|
+
}
|
|
1241
|
+
if (!vertex || !vertex.reference)
|
|
1242
|
+
return;
|
|
1243
|
+
const area = vertex.reference.area;
|
|
1244
|
+
if (area) {
|
|
1245
|
+
const rows = area.rows;
|
|
1246
|
+
const columns = area.columns;
|
|
1247
|
+
// if (Array.isArray(value)) {
|
|
1248
|
+
if (value.type === ValueType.array) {
|
|
1249
|
+
// value = Utilities.Transpose2(value);
|
|
1250
|
+
const values = Utilities.Transpose2(value.value);
|
|
1251
|
+
// FIXME: recycle [?]
|
|
1252
|
+
for (let row = 0; row < rows; row++) {
|
|
1253
|
+
if (values[row]) {
|
|
1254
|
+
let column = 0;
|
|
1255
|
+
for (; column < columns && column < values[row].length; column++) {
|
|
1256
|
+
// if there's a nested array, take the first value. but
|
|
1257
|
+
// don't recurse; if there's another array in there set
|
|
1258
|
+
// as undefined (should be error?)
|
|
1259
|
+
let indexed_value = values[row][column];
|
|
1260
|
+
if (indexed_value.type === ValueType.array) {
|
|
1261
|
+
indexed_value = indexed_value.value[0][0];
|
|
1262
|
+
}
|
|
1263
|
+
switch (indexed_value.type) {
|
|
1264
|
+
case ValueType.array:
|
|
1265
|
+
case ValueType.object:
|
|
1266
|
+
case ValueType.function:
|
|
1267
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined); // error?
|
|
1268
|
+
break;
|
|
1269
|
+
default:
|
|
1270
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(indexed_value.value, indexed_value.type);
|
|
1271
|
+
}
|
|
1272
|
+
/*
|
|
1273
|
+
if (indexed_value.type !== ValueType.object) {
|
|
1274
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(
|
|
1275
|
+
indexed_value.value, indexed_value.type);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(
|
|
1279
|
+
values[row][column].value,
|
|
1280
|
+
values[row][column].type);
|
|
1281
|
+
*/
|
|
1282
|
+
}
|
|
1283
|
+
for (; column < columns; column++) {
|
|
1284
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined, ValueType.undefined);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
for (let column = 0; column < columns; column++) {
|
|
1289
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(undefined, ValueType.undefined);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
// single, recycle
|
|
1296
|
+
let applied = { ...value };
|
|
1297
|
+
if (applied.type === ValueType.object || applied.type === ValueType.function) {
|
|
1298
|
+
applied = { type: ValueType.undefined, value: undefined };
|
|
1299
|
+
}
|
|
1300
|
+
for (let row = 0; row < rows; row++) {
|
|
1301
|
+
for (let column = 0; column < columns; column++) {
|
|
1302
|
+
cells.data[row + area.start.row][column + area.start.column].SetCalculatedValue(applied.value, applied.type);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* FIXME: for this version, this should be synchronous; the whole thing
|
|
1310
|
+
* should run in a worker. should be much faster than context switching
|
|
1311
|
+
* every time.
|
|
1312
|
+
*/
|
|
1313
|
+
CalculationCallback(vertex) {
|
|
1314
|
+
// must have address [UPDATE: don't do this]
|
|
1315
|
+
if (!vertex.address)
|
|
1316
|
+
throw (new Error('vertex missing address'));
|
|
1317
|
+
if (vertex.expression_error) {
|
|
1318
|
+
return {
|
|
1319
|
+
value: UnknownError(),
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
return this.expression_calculator.Calculate(vertex.expression, vertex.address, vertex.reference?.area); // <- this one
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* generic function, broken out from the Indirect function. checks dynamic
|
|
1326
|
+
* dependency for missing edges, and adds those edges.
|
|
1327
|
+
*
|
|
1328
|
+
* returns error on bad reference or circular dependency. this method
|
|
1329
|
+
* does not set the "short circuit" flag, callers should set as appropriate.
|
|
1330
|
+
*/
|
|
1331
|
+
DynamicDependencies(expression, context, offset = false, offset_rows = 0, offset_columns = 0, resize_rows = 1, resize_columns = 1) {
|
|
1332
|
+
// UPDATE: use current context (passed in as argument) to resolve
|
|
1333
|
+
// relative references. otherwise the reference will change depending
|
|
1334
|
+
// on current/active sheet
|
|
1335
|
+
let area = this.ResolveExpressionAddress(expression, context);
|
|
1336
|
+
if (!area) {
|
|
1337
|
+
return undefined;
|
|
1338
|
+
}
|
|
1339
|
+
// flag. we're going to check _all_ dependencies at once, just in
|
|
1340
|
+
// case (for this function this would only happen if the argument
|
|
1341
|
+
// is an array).
|
|
1342
|
+
let dirty = false;
|
|
1343
|
+
// if (area) {
|
|
1344
|
+
let sheet;
|
|
1345
|
+
if (expression.type === 'address' || expression.type === 'range') {
|
|
1346
|
+
const address_expression = (expression.type === 'range') ? expression.start : expression;
|
|
1347
|
+
if (address_expression.sheet_id) {
|
|
1348
|
+
sheet = this.model.sheets.Find(address_expression.sheet_id);
|
|
1349
|
+
/*
|
|
1350
|
+
for (const test of this.model.sheets) {
|
|
1351
|
+
if (test.id === address_expression.sheet_id) {
|
|
1352
|
+
sheet = test;
|
|
1353
|
+
break;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
*/
|
|
1357
|
+
}
|
|
1358
|
+
else if (address_expression.sheet) {
|
|
1359
|
+
sheet = this.model.sheets.Find(address_expression.sheet);
|
|
1360
|
+
/*
|
|
1361
|
+
const lc = address_expression.sheet.toLowerCase();
|
|
1362
|
+
for (const test of this.model.sheets) {
|
|
1363
|
+
if (test.name.toLowerCase() === lc) {
|
|
1364
|
+
sheet = test;
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
*/
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
if (!sheet && context?.sheet_id) {
|
|
1372
|
+
sheet = this.model.sheets.Find(context.sheet_id);
|
|
1373
|
+
/*
|
|
1374
|
+
for (const test of this.model.sheets) {
|
|
1375
|
+
if (test.id === context.sheet_id) {
|
|
1376
|
+
sheet = test;
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
*/
|
|
1381
|
+
}
|
|
1382
|
+
if (!sheet) {
|
|
1383
|
+
throw new Error('missing sheet in dynamic dependencies [b21]');
|
|
1384
|
+
}
|
|
1385
|
+
// check any dirty...
|
|
1386
|
+
// THIS IS ALMOST CERTAINLY WRONG. we should not be using active_sheet
|
|
1387
|
+
// here, we should use the area sheet. FIXME
|
|
1388
|
+
area = sheet.RealArea(area);
|
|
1389
|
+
const sheet_id = area.start.sheet_id;
|
|
1390
|
+
if (offset) {
|
|
1391
|
+
area = new Area({
|
|
1392
|
+
column: area.start.column + offset_columns,
|
|
1393
|
+
row: area.start.row + offset_rows,
|
|
1394
|
+
sheet_id: area.start.sheet_id,
|
|
1395
|
+
}, {
|
|
1396
|
+
column: area.start.column + offset_columns + resize_rows - 1,
|
|
1397
|
+
row: area.start.row + offset_rows + resize_columns - 1,
|
|
1398
|
+
sheet_id: area.end.sheet_id,
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
for (let row = area.start.row; row <= area.end.row; row++) {
|
|
1402
|
+
for (let column = area.start.column; column <= area.end.column; column++) {
|
|
1403
|
+
const vertex = this.GetVertex({ row, column, sheet_id }, false);
|
|
1404
|
+
if (vertex && vertex.dirty) {
|
|
1405
|
+
// so we know, given the structure of calculation, that there
|
|
1406
|
+
// is not an edge between these two vertices. we know that
|
|
1407
|
+
// because calculate() is never called on a vertex that has
|
|
1408
|
+
// dirty dependencies.
|
|
1409
|
+
// so if we create an edge here, the calculate method can
|
|
1410
|
+
// short-circuit, and then this cell will be re-evaluated
|
|
1411
|
+
// when that cell is calculated.
|
|
1412
|
+
// so all we have to do is add the edge. the question is,
|
|
1413
|
+
// do we need to remove that edge after the calculation?
|
|
1414
|
+
// or can we just wait for it to clean up on a rebuild?
|
|
1415
|
+
// (...) don't know for sure atm, test.
|
|
1416
|
+
// actually we have to set some flag to tell the vertex to
|
|
1417
|
+
// short-circuit...
|
|
1418
|
+
// before you set the short-circuit flag, test result so we
|
|
1419
|
+
// can error on circular ref
|
|
1420
|
+
// const edge_result =
|
|
1421
|
+
this.AddEdge({ row, column, sheet_id }, this.expression_calculator.context.address);
|
|
1422
|
+
//if (edge_result) {
|
|
1423
|
+
// return ReferenceError;
|
|
1424
|
+
//}
|
|
1425
|
+
dirty = true;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
// }
|
|
1430
|
+
return { dirty, area };
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* if locale has changed in Localization, update local resources.
|
|
1434
|
+
* this is necessary because (in chrome) worker doesn't get the system
|
|
1435
|
+
* locale properly (also, we might change it via parameter). we used to
|
|
1436
|
+
* just drop and reconstruct calculator, but we want to stop doing that
|
|
1437
|
+
* as part of supporting dynamic extension.
|
|
1438
|
+
*/
|
|
1439
|
+
UpdateLocale() {
|
|
1440
|
+
// don't assume default, always set
|
|
1441
|
+
if (Localization.decimal_separator === ',') {
|
|
1442
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
1443
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1444
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
1448
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1449
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1450
|
+
}
|
|
1451
|
+
// this.expression_calculator.UpdateLocale();
|
|
1452
|
+
}
|
|
1453
|
+
/* *
|
|
1454
|
+
* lookup in function library
|
|
1455
|
+
*
|
|
1456
|
+
* it seems like the only place this is called is within this class,
|
|
1457
|
+
* so we could probably inline and drop this function
|
|
1458
|
+
*
|
|
1459
|
+
* @deprecated
|
|
1460
|
+
* /
|
|
1461
|
+
public GetFunction(name: string): ExtendedFunctionDescriptor {
|
|
1462
|
+
return this.library.Get(name);
|
|
1463
|
+
}
|
|
1464
|
+
*/
|
|
1465
|
+
/**
|
|
1466
|
+
* returns a list of available functions, for AC/tooltips
|
|
1467
|
+
* FIXME: categories?
|
|
1468
|
+
* FIXME: need to separate annotation functions and sheet functions
|
|
1469
|
+
*/
|
|
1470
|
+
SupportedFunctions() {
|
|
1471
|
+
const list = this.library.List();
|
|
1472
|
+
const function_list = Object.keys(list).map((key) => {
|
|
1473
|
+
let name = list[key].canonical_name;
|
|
1474
|
+
if (!name)
|
|
1475
|
+
name = key.replace(/_/g, '.');
|
|
1476
|
+
return {
|
|
1477
|
+
name,
|
|
1478
|
+
description: list[key].description,
|
|
1479
|
+
arguments: (list[key].arguments || []).map((argument) => {
|
|
1480
|
+
return { name: argument.name || '' };
|
|
1481
|
+
}),
|
|
1482
|
+
type: 'function', // DescriptorType.Function,
|
|
1483
|
+
};
|
|
1484
|
+
});
|
|
1485
|
+
// FIXME: this doesn't need to be here, if it's owned by model.
|
|
1486
|
+
// we should have model responsible for retrieving these names
|
|
1487
|
+
// (along with named ranges/expressions). also, should macro
|
|
1488
|
+
// functions support scoping?
|
|
1489
|
+
for (const macro of this.model.macro_functions.values()) {
|
|
1490
|
+
function_list.push({
|
|
1491
|
+
name: macro.name,
|
|
1492
|
+
description: macro.description,
|
|
1493
|
+
arguments: (macro.argument_names || []).map(argument => {
|
|
1494
|
+
return { name: argument };
|
|
1495
|
+
}),
|
|
1496
|
+
type: 'function', // DescriptorType.Function,
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
/*
|
|
1500
|
+
for (const key of Object.keys(this.model.macro_functions)) {
|
|
1501
|
+
const macro = this.model.macro_functions[key];
|
|
1502
|
+
function_list.push({
|
|
1503
|
+
name: macro.name,
|
|
1504
|
+
description: macro.description,
|
|
1505
|
+
arguments: (macro.argument_names || []).map(argument => {
|
|
1506
|
+
return { name: argument };
|
|
1507
|
+
}),
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
*/
|
|
1511
|
+
return function_list;
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
*
|
|
1515
|
+
* @param name
|
|
1516
|
+
* @param map
|
|
1517
|
+
*/
|
|
1518
|
+
RegisterLibrary(name, map) {
|
|
1519
|
+
if (this.registered_libraries[name]) {
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
this.RegisterFunction(map);
|
|
1523
|
+
this.registered_libraries[name] = true;
|
|
1524
|
+
return true;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* dynamic extension
|
|
1528
|
+
*/
|
|
1529
|
+
RegisterFunction(map) {
|
|
1530
|
+
for (const name of Object.keys(map)) {
|
|
1531
|
+
const descriptor = map[name];
|
|
1532
|
+
// the way we call this now, this is unecessary
|
|
1533
|
+
// @see `CallExpression` in `expression-calculator.ts`.
|
|
1534
|
+
/*
|
|
1535
|
+
const original_function = descriptor.fn;
|
|
1536
|
+
|
|
1537
|
+
// we don't bind to the actual context because that would allow
|
|
1538
|
+
// functions to change it, and potentially break subsequent functions
|
|
1539
|
+
// that rely on it. which is a pretty far-fetched scenario, but we might
|
|
1540
|
+
// as well protect against it.
|
|
1541
|
+
|
|
1542
|
+
console.info('wrapping...');
|
|
1543
|
+
|
|
1544
|
+
descriptor.fn = (...args: unknown[]) => {
|
|
1545
|
+
|
|
1546
|
+
console.info("wrapped?");
|
|
1547
|
+
|
|
1548
|
+
return original_function.apply({
|
|
1549
|
+
address: { ...this.expression_calculator.context.address},
|
|
1550
|
+
}, args);
|
|
1551
|
+
};
|
|
1552
|
+
*/
|
|
1553
|
+
this.library.Register({ [name]: descriptor });
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* wrapper method for calculation
|
|
1558
|
+
*/
|
|
1559
|
+
Calculate(subset) {
|
|
1560
|
+
// this.AttachModel();
|
|
1561
|
+
// this gets checked later, now... it would be better if we could
|
|
1562
|
+
// check it here are skip the later check, but that field is optional
|
|
1563
|
+
// it's better to report the error here so we can trace
|
|
1564
|
+
if (subset && !subset.start.sheet_id) {
|
|
1565
|
+
throw new Error('CalculateInternal called with subset w/out sheet ID');
|
|
1566
|
+
}
|
|
1567
|
+
if (this.full_rebuild_required) {
|
|
1568
|
+
subset = undefined;
|
|
1569
|
+
this.UpdateAnnotations();
|
|
1570
|
+
this.UpdateConditionals();
|
|
1571
|
+
this.UpdateConnectedElements();
|
|
1572
|
+
// this.UpdateNotifiers();
|
|
1573
|
+
this.full_rebuild_required = false; // unset
|
|
1574
|
+
}
|
|
1575
|
+
// this.expression_calculator.SetModel(model);
|
|
1576
|
+
this.RebuildGraph(subset);
|
|
1577
|
+
this.grid_expanded = false; // unset
|
|
1578
|
+
try {
|
|
1579
|
+
this.Recalculate();
|
|
1580
|
+
}
|
|
1581
|
+
catch (err) {
|
|
1582
|
+
console.error(err);
|
|
1583
|
+
console.info('calculation error trapped');
|
|
1584
|
+
}
|
|
1585
|
+
/*
|
|
1586
|
+
const callbacks: NotifierType[] = [];
|
|
1587
|
+
for (const notifier of this.notifiers) {
|
|
1588
|
+
if (notifier.vertex.state_id !== notifier.state) {
|
|
1589
|
+
notifier.state = notifier.vertex.state_id;
|
|
1590
|
+
if (notifier.notifier.callback) {
|
|
1591
|
+
callbacks.push(notifier.notifier);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if (callbacks.length) {
|
|
1597
|
+
Promise.resolve().then(() => {
|
|
1598
|
+
for (const notifier of callbacks) {
|
|
1599
|
+
if (notifier.callback) {
|
|
1600
|
+
notifier.callback.call(undefined, notifier);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
*/
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* resets graph and graph status. this is called when structure changes --
|
|
1609
|
+
* such as adding or removing sheets -- so we need to preserve notifiers
|
|
1610
|
+
* across resets. we need to either add a flag or add a separate method
|
|
1611
|
+
* to handle clearing notifiers.
|
|
1612
|
+
*/
|
|
1613
|
+
Reset() {
|
|
1614
|
+
this.FlushTree();
|
|
1615
|
+
// this.AttachModel();
|
|
1616
|
+
this.full_rebuild_required = true;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* get a list of functions that require decorating with "_xlfn" on
|
|
1620
|
+
* export. the embed caller will pass this to the export worker.
|
|
1621
|
+
* since we manage functions, we can manage the list.
|
|
1622
|
+
*
|
|
1623
|
+
* UPDATE: to support our MC functions (which may need _xll decoration),
|
|
1624
|
+
* map to type and then overload as necessary
|
|
1625
|
+
*
|
|
1626
|
+
*/
|
|
1627
|
+
DecoratedFunctionList() {
|
|
1628
|
+
// const list: string[] = [];
|
|
1629
|
+
const map = {};
|
|
1630
|
+
const lib = this.library.List();
|
|
1631
|
+
for (const key of Object.keys(lib)) {
|
|
1632
|
+
const def = lib[key];
|
|
1633
|
+
if (def.xlfn) {
|
|
1634
|
+
// list.push(key);
|
|
1635
|
+
map[key] = '_xlfn';
|
|
1636
|
+
}
|
|
1637
|
+
else if (def.extension) {
|
|
1638
|
+
map[key] = '_xll';
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return map;
|
|
1642
|
+
}
|
|
1643
|
+
/** moved from embedded sheet */
|
|
1644
|
+
Evaluate(expression, active_sheet, options = {}, raw_result = false) {
|
|
1645
|
+
let parse_expression = options?.preparsed;
|
|
1646
|
+
if (!parse_expression) {
|
|
1647
|
+
this.parser.Save();
|
|
1648
|
+
if (options.argument_separator) {
|
|
1649
|
+
if (options.argument_separator === ',') {
|
|
1650
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
1651
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Comma;
|
|
1652
|
+
// this.parser.decimal_mark = DecimalMarkType.Period;
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Comma);
|
|
1656
|
+
// this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
|
|
1657
|
+
// this.parser.decimal_mark = DecimalMarkType.Comma;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (options.r1c1) {
|
|
1661
|
+
this.parser.flags.r1c1 = options.r1c1;
|
|
1662
|
+
}
|
|
1663
|
+
const parse_result = this.parser.Parse(expression);
|
|
1664
|
+
// reset
|
|
1665
|
+
// this.parser.argument_separator = current;
|
|
1666
|
+
// this.parser.decimal_mark = (current === ArgumentSeparatorType.Comma) ? DecimalMarkType.Period : DecimalMarkType.Comma;
|
|
1667
|
+
// this.parser.flags.r1c1 = r1c1_state;
|
|
1668
|
+
this.parser.Restore();
|
|
1669
|
+
parse_expression = parse_result.expression;
|
|
1670
|
+
if (parse_result.error) {
|
|
1671
|
+
throw new Error(parse_result.error);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
// OK
|
|
1675
|
+
if (parse_expression) {
|
|
1676
|
+
this.parser.Walk(parse_expression, (unit) => {
|
|
1677
|
+
if (unit.type === 'address' || unit.type === 'range') {
|
|
1678
|
+
// don't allow offset references, even in R1C1
|
|
1679
|
+
if (unit.type === 'address') {
|
|
1680
|
+
if (unit.offset_column || unit.offset_row) {
|
|
1681
|
+
throw new Error(`Evaluate does not support offset references`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
if (unit.start.offset_column || unit.start.offset_row || unit.end.offset_column || unit.end.offset_row) {
|
|
1686
|
+
throw new Error(`Evaluate does not support offset references`);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
this.model.ResolveSheetID(unit, undefined, active_sheet);
|
|
1690
|
+
}
|
|
1691
|
+
return true;
|
|
1692
|
+
});
|
|
1693
|
+
// console.info({expression: parse_result.expression})
|
|
1694
|
+
const result = this.CalculateExpression(parse_expression, options.address);
|
|
1695
|
+
if (raw_result) {
|
|
1696
|
+
return result;
|
|
1697
|
+
}
|
|
1698
|
+
if (result.type === ValueType.array) {
|
|
1699
|
+
return result.value.map(row => row.map(value => value.value));
|
|
1700
|
+
}
|
|
1701
|
+
else {
|
|
1702
|
+
return result.value;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
// or? (...)
|
|
1706
|
+
throw new Error('invalid expression');
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* calculate an expression, optionally setting a fake cell address.
|
|
1710
|
+
* this may have weird side-effects.
|
|
1711
|
+
*/
|
|
1712
|
+
CalculateExpression(expression, address = { row: -1, column: -1 }, preserve_flags = false) {
|
|
1713
|
+
return this.expression_calculator.Calculate(expression, address, undefined, preserve_flags).value; // dropping volatile flag
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* rebuild the graph, and set cells as clean. the vertices need internal
|
|
1717
|
+
* references to the calculated value, so that's set via the vertex method.
|
|
1718
|
+
*
|
|
1719
|
+
* we also need to manage the list of volatile cells, which is normally
|
|
1720
|
+
* built as a side-effect of calculation.
|
|
1721
|
+
*
|
|
1722
|
+
* UPDATE: optionally recalculate if there are volatile cells. that's used
|
|
1723
|
+
* for loading documents.
|
|
1724
|
+
*/
|
|
1725
|
+
RebuildClean(recalculate_if_volatile = false) {
|
|
1726
|
+
this.full_rebuild_required = false; // unset
|
|
1727
|
+
this.RebuildGraph();
|
|
1728
|
+
// add leaf vertices for annotations
|
|
1729
|
+
this.UpdateAnnotations(); // all
|
|
1730
|
+
this.UpdateConditionals();
|
|
1731
|
+
this.UpdateConnectedElements();
|
|
1732
|
+
// and notifiers
|
|
1733
|
+
// this.UpdateNotifiers();
|
|
1734
|
+
// there's a weird back-and-forth that happens here
|
|
1735
|
+
// (calculator -> graph -> calculator) to check for volatile
|
|
1736
|
+
// cells. it could probably be simplified.
|
|
1737
|
+
this.InitializeGraph();
|
|
1738
|
+
if (recalculate_if_volatile && this.volatile_list.length) {
|
|
1739
|
+
this.Recalculate();
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* remove duplicates from list, dropping absolute
|
|
1744
|
+
*/
|
|
1745
|
+
FlattenCellList(list) {
|
|
1746
|
+
const map = {};
|
|
1747
|
+
const flattened = [];
|
|
1748
|
+
for (const entry of list) {
|
|
1749
|
+
const address = {
|
|
1750
|
+
column: entry.column,
|
|
1751
|
+
row: entry.row,
|
|
1752
|
+
sheet_id: entry.sheet_id,
|
|
1753
|
+
};
|
|
1754
|
+
const label = Area.CellAddressToLabel(address, true);
|
|
1755
|
+
if (map[label]) {
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
map[label] = label;
|
|
1759
|
+
flattened.push(address);
|
|
1760
|
+
}
|
|
1761
|
+
return flattened;
|
|
1762
|
+
}
|
|
1763
|
+
/* * remove all notifiers * /
|
|
1764
|
+
public RemoveNotifiers(): void {
|
|
1765
|
+
for (const internal of this.notifiers) {
|
|
1766
|
+
if (internal.vertex) {
|
|
1767
|
+
internal.vertex.Reset();
|
|
1768
|
+
this.RemoveLeafVertex(internal.vertex);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
this.notifiers = [];
|
|
1772
|
+
}
|
|
1773
|
+
*/
|
|
1774
|
+
/* *
|
|
1775
|
+
* remove specified notifier. you can pass the returned ID or the original
|
|
1776
|
+
* object used to create it.
|
|
1777
|
+
* /
|
|
1778
|
+
public RemoveNotifier(notifier: NotifierType|number): void {
|
|
1779
|
+
|
|
1780
|
+
let internal: InternalNotifierType|undefined;
|
|
1781
|
+
|
|
1782
|
+
this.notifiers = this.notifiers.filter(test => {
|
|
1783
|
+
if (test.id === notifier || test === notifier) {
|
|
1784
|
+
internal = test;
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
return true;
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
if (!internal) {
|
|
1791
|
+
// FIXME: error
|
|
1792
|
+
console.warn('invalid notifier');
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
|
|
1796
|
+
// remove vertex
|
|
1797
|
+
if (internal.vertex) {
|
|
1798
|
+
internal.vertex.Reset();
|
|
1799
|
+
this.RemoveLeafVertex(internal.vertex);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
}
|
|
1805
|
+
*/
|
|
1806
|
+
/* *
|
|
1807
|
+
* update a notifier or notifiers, or the entire list (default).
|
|
1808
|
+
* /
|
|
1809
|
+
protected UpdateNotifiers(notifiers: InternalNotifierType|InternalNotifierType[] = this.notifiers): void {
|
|
1810
|
+
|
|
1811
|
+
if (!Array.isArray(notifiers)) {
|
|
1812
|
+
notifiers = [notifiers];
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
for (const notifier of notifiers) {
|
|
1816
|
+
|
|
1817
|
+
if (notifier.vertex) {
|
|
1818
|
+
notifier.vertex.Reset();
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
notifier.vertex = new LeafVertex();
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// construct formula (inlining)
|
|
1825
|
+
|
|
1826
|
+
const string_reference = notifier.references.map(reference => {
|
|
1827
|
+
|
|
1828
|
+
// I don't want to go through strings here... OTOH if we build an
|
|
1829
|
+
// expression manually it's going to be fragile to changes in the
|
|
1830
|
+
// parser...
|
|
1831
|
+
|
|
1832
|
+
let sheet_name = '';
|
|
1833
|
+
let base: ICellAddress;
|
|
1834
|
+
let label = '';
|
|
1835
|
+
|
|
1836
|
+
if (reference.count === 1) {
|
|
1837
|
+
base = reference.start;
|
|
1838
|
+
label = Area.CellAddressToLabel(reference.start, false);
|
|
1839
|
+
}
|
|
1840
|
+
else {
|
|
1841
|
+
base = reference.start;
|
|
1842
|
+
label = Area.CellAddressToLabel(reference.start, false) + ':' +
|
|
1843
|
+
Area.CellAddressToLabel(reference.end, false);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
for (const sheet of this.model.sheets.list) {
|
|
1847
|
+
if (sheet.id === base.sheet_id) {
|
|
1848
|
+
sheet_name = sheet.name;
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
if (!sheet_name) {
|
|
1854
|
+
throw new Error('invalid sheet in reference');
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
if (QuotedSheetNameRegex.test(sheet_name)) {
|
|
1858
|
+
return `'${sheet_name}'!${label}`;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
return `${sheet_name}!${label}`;
|
|
1862
|
+
|
|
1863
|
+
}).join(',');
|
|
1864
|
+
|
|
1865
|
+
// the function (here "Notify") is never called. we're using a leaf
|
|
1866
|
+
// node, which bypasses the standard calculation system and only updates
|
|
1867
|
+
// a state reference when dirty. so here it's just an arbitrary string.
|
|
1868
|
+
|
|
1869
|
+
// still, we should use something that's not going to be used elsewhere
|
|
1870
|
+
// in the future...
|
|
1871
|
+
|
|
1872
|
+
const formula = `=Internal.Notify(${string_reference})`;
|
|
1873
|
+
// console.info('f', formula);
|
|
1874
|
+
|
|
1875
|
+
// we (theoretically) guarantee that all refeerences are qualified,
|
|
1876
|
+
// so we don't need a context (active sheet) for relative references.
|
|
1877
|
+
// we can just use model[0]
|
|
1878
|
+
|
|
1879
|
+
this.AddLeafVertex(notifier.vertex);
|
|
1880
|
+
this.UpdateLeafVertex(notifier.vertex, formula, this.model.sheets.list[0]);
|
|
1881
|
+
|
|
1882
|
+
// update state (gets reset?)
|
|
1883
|
+
|
|
1884
|
+
notifier.state = notifier.vertex.state_id;
|
|
1885
|
+
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
*/
|
|
1889
|
+
/* *
|
|
1890
|
+
* new notification API (testing)
|
|
1891
|
+
* /
|
|
1892
|
+
public AddNotifier(references: RangeReference|RangeReference[], notifier: NotifierType, context: Sheet): number {
|
|
1893
|
+
|
|
1894
|
+
if (!Array.isArray(references)) {
|
|
1895
|
+
references = [references];
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// even if these are strings we want to properly resolve them so
|
|
1899
|
+
// we can store qualified references
|
|
1900
|
+
|
|
1901
|
+
const qualified: Area[] = references.map(reference => {
|
|
1902
|
+
|
|
1903
|
+
if (typeof reference === 'string') {
|
|
1904
|
+
return this.ResolveArea(reference, context).Clone();
|
|
1905
|
+
}
|
|
1906
|
+
if (IsCellAddress(reference)) {
|
|
1907
|
+
return new Area({
|
|
1908
|
+
...reference,
|
|
1909
|
+
sheet_id: reference.sheet_id || context.id,
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
return new Area({
|
|
1914
|
+
...reference.start,
|
|
1915
|
+
sheet_id: reference.start.sheet_id || context.id,
|
|
1916
|
+
}, {
|
|
1917
|
+
...reference.end,
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
const internal: InternalNotifierType = {
|
|
1923
|
+
id: this.notifier_id_source++,
|
|
1924
|
+
notifier,
|
|
1925
|
+
references: qualified,
|
|
1926
|
+
vertex: new LeafVertex(),
|
|
1927
|
+
state: 0,
|
|
1928
|
+
};
|
|
1929
|
+
|
|
1930
|
+
// update
|
|
1931
|
+
this.UpdateNotifiers(internal);
|
|
1932
|
+
|
|
1933
|
+
// push to notifications
|
|
1934
|
+
this.notifiers.push(internal);
|
|
1935
|
+
|
|
1936
|
+
return internal.id;
|
|
1937
|
+
|
|
1938
|
+
}
|
|
1939
|
+
*/
|
|
1940
|
+
Unresolve(ref, context, qualified = true, named = true) {
|
|
1941
|
+
let range = '';
|
|
1942
|
+
const area = IsCellAddress(ref) ? new Area(ref) : new Area(ref.start, ref.end);
|
|
1943
|
+
if (named) {
|
|
1944
|
+
const named_range = this.model.named.MatchSelection(area);
|
|
1945
|
+
if (named_range) {
|
|
1946
|
+
return named_range;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
/*
|
|
1950
|
+
if (area.count > 1) {
|
|
1951
|
+
range = Area.CellAddressToLabel(area.start) + ':' + Area.CellAddressToLabel(area.end);
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
range = Area.CellAddressToLabel(area.start);
|
|
1955
|
+
}
|
|
1956
|
+
*/
|
|
1957
|
+
range = area.spreadsheet_label;
|
|
1958
|
+
if (!qualified) {
|
|
1959
|
+
return range;
|
|
1960
|
+
}
|
|
1961
|
+
// is there a function to resolve sheet? actually, don't we know that
|
|
1962
|
+
// the active selection must be on the active sheet? (...)
|
|
1963
|
+
const sheet_id = area.start.sheet_id || context?.id;
|
|
1964
|
+
const sheet_name = this.ResolveSheetName(sheet_id, true);
|
|
1965
|
+
return sheet_name ? sheet_name + '!' + range : range;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* FIXME: just add a quote option to the model method and we can drop this function
|
|
1969
|
+
*/
|
|
1970
|
+
ResolveSheetName(id, quote = false) {
|
|
1971
|
+
const sheet = this.model.sheets.Find(id);
|
|
1972
|
+
if (sheet) {
|
|
1973
|
+
if (quote && QuotedSheetNameRegex.test(sheet.name)) {
|
|
1974
|
+
return `'${sheet.name}'`;
|
|
1975
|
+
}
|
|
1976
|
+
return sheet.name;
|
|
1977
|
+
}
|
|
1978
|
+
return undefined;
|
|
1979
|
+
}
|
|
1980
|
+
RemoveConditional(conditional) {
|
|
1981
|
+
if (conditional.type === 'expression') {
|
|
1982
|
+
const vertex = conditional.internal?.vertex;
|
|
1983
|
+
if (vertex) {
|
|
1984
|
+
vertex.Reset();
|
|
1985
|
+
this.RemoveLeafVertex(vertex);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
RemoveConnectedELement(element) {
|
|
1990
|
+
const internal = element.internal;
|
|
1991
|
+
if (internal?.vertex) {
|
|
1992
|
+
this.RemoveLeafVertex(internal.vertex);
|
|
1993
|
+
return true;
|
|
1994
|
+
}
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
UpdateConnectedElements(context, element) {
|
|
1998
|
+
// we have a problem here in that these elements are not bound
|
|
1999
|
+
// to sheets, so we might have no context. for now we'll
|
|
2000
|
+
// just grab the first sheet, although that's not necessarily
|
|
2001
|
+
// what you want. we should enforce that these have hard sheet
|
|
2002
|
+
// references when created.
|
|
2003
|
+
if (!context) {
|
|
2004
|
+
context = this.model.sheets.list[0];
|
|
2005
|
+
}
|
|
2006
|
+
if (element) {
|
|
2007
|
+
const internal = element.internal;
|
|
2008
|
+
if (internal?.vertex) {
|
|
2009
|
+
this.RemoveLeafVertex(internal.vertex);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
const elements = element ? [element] : this.model.connected_elements.values();
|
|
2013
|
+
for (const element of elements) {
|
|
2014
|
+
let internal = element.internal;
|
|
2015
|
+
if (!internal) {
|
|
2016
|
+
internal = {
|
|
2017
|
+
vertex: new StateLeafVertex(),
|
|
2018
|
+
};
|
|
2019
|
+
element.internal = internal;
|
|
2020
|
+
}
|
|
2021
|
+
const vertex = internal.vertex;
|
|
2022
|
+
this.AddLeafVertex(vertex);
|
|
2023
|
+
this.UpdateLeafVertex(vertex, element.formula, context);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
UpdateConditionals(list, context) {
|
|
2027
|
+
// this method is (1) relying on the leaf vertex Set to avoid duplication,
|
|
2028
|
+
// and (2) leaving orphansed conditionals in place. we should look to
|
|
2029
|
+
// cleaning things up.
|
|
2030
|
+
// is it also (3) adding unecessary calculations (building the expression,
|
|
2031
|
+
// below)?
|
|
2032
|
+
// NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
|
|
2033
|
+
// make sure to evaluate them in this format.
|
|
2034
|
+
if (!list) {
|
|
2035
|
+
// we could in theory remove all of the leaves (the ones we know to
|
|
2036
|
+
// be used for conditionals), because they will be added back below.
|
|
2037
|
+
// how wasteful is that?
|
|
2038
|
+
// or maybe we could change the mark, and then use invalid marks
|
|
2039
|
+
// to check?
|
|
2040
|
+
// the alternative is just to leave them as orphans until the graph
|
|
2041
|
+
// is rebuilt. which is lazy, but probably not that bad...
|
|
2042
|
+
for (const sheet of this.model.sheets.list) {
|
|
2043
|
+
if (sheet.conditional_formats?.length) {
|
|
2044
|
+
this.UpdateConditionals(sheet.conditional_formats, sheet);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
if (!context) {
|
|
2050
|
+
throw new Error('invalid call to update conditionals without context');
|
|
2051
|
+
}
|
|
2052
|
+
if (list && !Array.isArray(list)) {
|
|
2053
|
+
list = [list];
|
|
2054
|
+
}
|
|
2055
|
+
for (const entry of list) {
|
|
2056
|
+
let expression = '';
|
|
2057
|
+
switch (entry.type) {
|
|
2058
|
+
case 'cell-match':
|
|
2059
|
+
if (entry.between) {
|
|
2060
|
+
const addr = this.Unresolve(entry.area, context, true, false);
|
|
2061
|
+
expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
|
|
2062
|
+
}
|
|
2063
|
+
else {
|
|
2064
|
+
expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
|
|
2065
|
+
}
|
|
2066
|
+
break;
|
|
2067
|
+
case 'expression':
|
|
2068
|
+
expression = entry.expression;
|
|
2069
|
+
break;
|
|
2070
|
+
case 'duplicate-values':
|
|
2071
|
+
expression = `UniqueValues(${this.Unresolve(entry.area, context, true, false)})`;
|
|
2072
|
+
if (!entry.unique) {
|
|
2073
|
+
expression = `NOT(${expression})`;
|
|
2074
|
+
}
|
|
2075
|
+
break;
|
|
2076
|
+
case 'data-bar':
|
|
2077
|
+
case 'gradient':
|
|
2078
|
+
expression = `=Gradient(${[
|
|
2079
|
+
this.Unresolve(entry.area, context, true, false),
|
|
2080
|
+
entry.min ?? '',
|
|
2081
|
+
entry.max ?? '',
|
|
2082
|
+
...(entry.type === 'data-bar' ? ['TRUE'] : []),
|
|
2083
|
+
].join(',') // is this correct? are we standardizing i18n? FIXME: check
|
|
2084
|
+
})`;
|
|
2085
|
+
break;
|
|
2086
|
+
default:
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
if (!expression) {
|
|
2090
|
+
continue; // FIXME: warn?
|
|
2091
|
+
}
|
|
2092
|
+
// console.info({type: entry.type, expression});
|
|
2093
|
+
if (!entry.internal) {
|
|
2094
|
+
entry.internal = {};
|
|
2095
|
+
}
|
|
2096
|
+
if (!entry.internal.vertex) {
|
|
2097
|
+
const vertex = new CalculationLeafVertex();
|
|
2098
|
+
vertex.use = 'conditional';
|
|
2099
|
+
entry.internal.vertex = vertex;
|
|
2100
|
+
let options = {
|
|
2101
|
+
argument_separator: ',',
|
|
2102
|
+
};
|
|
2103
|
+
if (entry.type !== 'gradient' && entry.type !== 'duplicate-values' && entry.type !== 'data-bar') {
|
|
2104
|
+
options = { ...entry.options, ...options };
|
|
2105
|
+
}
|
|
2106
|
+
// first pass, run the calculation
|
|
2107
|
+
const check = this.Evaluate(expression, context, options, true);
|
|
2108
|
+
entry.internal.vertex.result = check;
|
|
2109
|
+
entry.internal.vertex.updated = true;
|
|
2110
|
+
}
|
|
2111
|
+
const vertex = entry.internal.vertex;
|
|
2112
|
+
this.AddLeafVertex(vertex);
|
|
2113
|
+
this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
RemoveAnnotation(annotation) {
|
|
2117
|
+
const vertex_data = annotation.temp;
|
|
2118
|
+
if (vertex_data.vertex) {
|
|
2119
|
+
vertex_data.vertex.Reset();
|
|
2120
|
+
this.RemoveLeafVertex(vertex_data.vertex);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
UpdateAnnotations(list, context) {
|
|
2124
|
+
if (!list) {
|
|
2125
|
+
// update: since we don't have access to active_sheet,
|
|
2126
|
+
// just add all annotations. slightly less efficient
|
|
2127
|
+
// (perhaps) but better for handling multiple views.
|
|
2128
|
+
for (const sheet of this.model.sheets.list) {
|
|
2129
|
+
this.UpdateAnnotations(sheet.annotations, sheet);
|
|
2130
|
+
}
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (!context) {
|
|
2134
|
+
throw new Error('invalid call to UpdateAnnotations with list but no sheet');
|
|
2135
|
+
}
|
|
2136
|
+
if (typeof list !== 'undefined' && !Array.isArray(list)) {
|
|
2137
|
+
list = [list];
|
|
2138
|
+
}
|
|
2139
|
+
for (const entry of list) {
|
|
2140
|
+
if (entry.data.formula) {
|
|
2141
|
+
const vertex_data = entry.temp;
|
|
2142
|
+
if (!vertex_data.vertex) {
|
|
2143
|
+
vertex_data.vertex = new StateLeafVertex();
|
|
2144
|
+
}
|
|
2145
|
+
this.AddLeafVertex(vertex_data.vertex);
|
|
2146
|
+
this.UpdateLeafVertex(vertex_data.vertex, entry.data.formula, context);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
// --- protected -------------------------------------------------------------
|
|
2151
|
+
/**
|
|
2152
|
+
* assuming the expression is an address, range, or named range, resolve
|
|
2153
|
+
* to an address/area. returns undefined if the expression can't be resolved.
|
|
2154
|
+
*/
|
|
2155
|
+
ResolveExpressionAddress(expr, context) {
|
|
2156
|
+
switch (expr.type) {
|
|
2157
|
+
case 'address':
|
|
2158
|
+
if (this.model.ResolveSheetID(expr, context)) {
|
|
2159
|
+
return new Area(expr);
|
|
2160
|
+
}
|
|
2161
|
+
break;
|
|
2162
|
+
case 'range':
|
|
2163
|
+
if (this.model.ResolveSheetID(expr, context)) {
|
|
2164
|
+
return new Area(expr.start, expr.end);
|
|
2165
|
+
}
|
|
2166
|
+
break;
|
|
2167
|
+
case 'identifier':
|
|
2168
|
+
{
|
|
2169
|
+
const named_range = this.model.GetName(expr.name, context?.sheet_id || 0);
|
|
2170
|
+
if (named_range && named_range.type === 'range') {
|
|
2171
|
+
return new Area(named_range.area.start, named_range.area.end);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
break;
|
|
2175
|
+
}
|
|
2176
|
+
return undefined;
|
|
2177
|
+
}
|
|
2178
|
+
NamedRangeToAddressUnit(unit, context) {
|
|
2179
|
+
const normalized = unit.name.toUpperCase();
|
|
2180
|
+
const named_range = this.model.GetName(normalized, context.sheet_id || 0);
|
|
2181
|
+
if (named_range && named_range.type === 'range') {
|
|
2182
|
+
if (named_range.area.count === 1) {
|
|
2183
|
+
return this.ConstructAddressUnit(named_range.area.start, normalized, unit.id, unit.position);
|
|
2184
|
+
}
|
|
2185
|
+
else {
|
|
2186
|
+
return {
|
|
2187
|
+
type: 'range',
|
|
2188
|
+
start: this.ConstructAddressUnit(named_range.area.start, normalized, unit.id, unit.position),
|
|
2189
|
+
end: this.ConstructAddressUnit(named_range.area.end, normalized, unit.id, unit.position),
|
|
2190
|
+
label: normalized,
|
|
2191
|
+
id: unit.id,
|
|
2192
|
+
position: unit.position,
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
return undefined;
|
|
2197
|
+
}
|
|
2198
|
+
/** named range support */
|
|
2199
|
+
ConstructAddressUnit(address, label, id, position) {
|
|
2200
|
+
return {
|
|
2201
|
+
type: 'address',
|
|
2202
|
+
row: address.row,
|
|
2203
|
+
column: address.column,
|
|
2204
|
+
sheet_id: address.sheet_id,
|
|
2205
|
+
label,
|
|
2206
|
+
id,
|
|
2207
|
+
position,
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* rebuild dependencies for a single expression (might be a cell, or an
|
|
2212
|
+
* annotation/leaf node). can recurse on elements, so the return value
|
|
2213
|
+
* is passed through. the first (outer) call can just leave it blank and
|
|
2214
|
+
* use the return value.
|
|
2215
|
+
*
|
|
2216
|
+
* we're adding the sheet name so that (in mc expression calculator) we
|
|
2217
|
+
* can turn address parameters into qualified labels. the normal routine
|
|
2218
|
+
* will just use the ID as the name, that's fine, as long as it's unique
|
|
2219
|
+
* (which it is).
|
|
2220
|
+
*
|
|
2221
|
+
* this might cause issues if we ever try to actually resolve from the
|
|
2222
|
+
* sheet name, though, so (...)
|
|
2223
|
+
*
|
|
2224
|
+
*
|
|
2225
|
+
* Q: why does this not use the parser Walk/Walk2 routine?
|
|
2226
|
+
*
|
|
2227
|
+
*/
|
|
2228
|
+
RebuildDependencies(unit, relative_sheet_id, relative_sheet_name, dependencies = { addresses: {}, ranges: {} }, context_address) {
|
|
2229
|
+
if (!relative_sheet_name) {
|
|
2230
|
+
const sheet = this.model.sheets.Find(relative_sheet_id);
|
|
2231
|
+
if (sheet) {
|
|
2232
|
+
relative_sheet_name = sheet.name;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
switch (unit.type) {
|
|
2236
|
+
case 'literal':
|
|
2237
|
+
case 'missing':
|
|
2238
|
+
case 'operator':
|
|
2239
|
+
break;
|
|
2240
|
+
case 'identifier':
|
|
2241
|
+
{
|
|
2242
|
+
// update to handle named expressions. just descend into
|
|
2243
|
+
// the expression as if it were inline.
|
|
2244
|
+
const fetched = this.model.GetName(unit.name, context_address.sheet_id || 0);
|
|
2245
|
+
if (fetched?.type === 'expression') {
|
|
2246
|
+
this.RebuildDependencies(fetched.expression, relative_sheet_id, relative_sheet_name, dependencies, context_address);
|
|
2247
|
+
}
|
|
2248
|
+
else {
|
|
2249
|
+
const resolved = this.NamedRangeToAddressUnit(unit, context_address);
|
|
2250
|
+
if (resolved) {
|
|
2251
|
+
if (resolved.type === 'address') {
|
|
2252
|
+
dependencies.addresses[resolved.label] = resolved;
|
|
2253
|
+
}
|
|
2254
|
+
else {
|
|
2255
|
+
dependencies.ranges[resolved.label] = resolved;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
break;
|
|
2261
|
+
case 'structured-reference':
|
|
2262
|
+
// when building the graph, resolve the reference to the table.
|
|
2263
|
+
// this is the same thing we do in expression-calculator, and
|
|
2264
|
+
// we rely on the same rules to ensure that the reference either
|
|
2265
|
+
// stays consitent, or gets rebuilt.
|
|
2266
|
+
{
|
|
2267
|
+
const resolved = this.model.ResolveStructuredReference(unit, context_address);
|
|
2268
|
+
if (resolved) {
|
|
2269
|
+
if (resolved.type === 'address') {
|
|
2270
|
+
dependencies.addresses[resolved.sheet_id + '!' + resolved.label] = resolved;
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
dependencies.ranges[resolved.label] = resolved;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const table = this.model.tables.get(unit.table.toLowerCase());
|
|
2277
|
+
if (table) {
|
|
2278
|
+
// see ResolveStructuredReference in expression calculator
|
|
2279
|
+
const row = context_address.row; // "this row"
|
|
2280
|
+
if (row < table.area.start.row || row > table.area.end.row) {
|
|
2281
|
+
break;
|
|
2282
|
+
}
|
|
2283
|
+
const reference_column = unit.column.toLowerCase();
|
|
2284
|
+
let column = -1;
|
|
2285
|
+
if (table.columns) { // FIXME: make this required
|
|
2286
|
+
for (let i = 0; i < table.columns.length; i++) {
|
|
2287
|
+
if (reference_column === table.columns[i]) {
|
|
2288
|
+
column = table.area.start.column + i;
|
|
2289
|
+
break;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (column >= 0) {
|
|
2294
|
+
// does using the original label here, instead of a sheet
|
|
2295
|
+
// address as label, mean we potentially have multiple
|
|
2296
|
+
// references to the same cell? probably...
|
|
2297
|
+
const address = {
|
|
2298
|
+
label: unit.label,
|
|
2299
|
+
type: 'address',
|
|
2300
|
+
row,
|
|
2301
|
+
column,
|
|
2302
|
+
sheet_id: table.area.start.sheet_id,
|
|
2303
|
+
id: unit.id,
|
|
2304
|
+
position: unit.position,
|
|
2305
|
+
};
|
|
2306
|
+
dependencies.addresses[address.sheet_id + '!' + address.label] = address;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
break;
|
|
2311
|
+
case 'address':
|
|
2312
|
+
if (!unit.sheet_id) {
|
|
2313
|
+
if (unit.sheet) {
|
|
2314
|
+
const sheet = this.model.sheets.Find(unit.sheet);
|
|
2315
|
+
if (sheet) {
|
|
2316
|
+
unit.sheet_id = sheet.id;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
else {
|
|
2320
|
+
unit.sheet_id = relative_sheet_id;
|
|
2321
|
+
unit.sheet = relative_sheet_name;
|
|
2322
|
+
}
|
|
2323
|
+
/*
|
|
2324
|
+
unit.sheet_id = unit.sheet ?
|
|
2325
|
+
(sheet_name_map[unit.sheet.toLowerCase()] || 0) :
|
|
2326
|
+
relative_sheet_id;
|
|
2327
|
+
if (!unit.sheet) { unit.sheet = relative_sheet_name; }
|
|
2328
|
+
*/
|
|
2329
|
+
}
|
|
2330
|
+
if (!unit.sheet_id) {
|
|
2331
|
+
// FIXME: we don't necessarily need to warn here, because we'll
|
|
2332
|
+
// get a warning when it tries to calculate. still this is helpful
|
|
2333
|
+
// for debugging.
|
|
2334
|
+
console.warn('invalid address in range [9d]');
|
|
2335
|
+
}
|
|
2336
|
+
else {
|
|
2337
|
+
dependencies.addresses[unit.sheet_id + '!' + unit.label] = unit;
|
|
2338
|
+
}
|
|
2339
|
+
break; // this.AddressLabel(unit, offset);
|
|
2340
|
+
case 'range':
|
|
2341
|
+
if (!unit.start.sheet_id) {
|
|
2342
|
+
if (unit.start.sheet) {
|
|
2343
|
+
const sheet = this.model.sheets.Find(unit.start.sheet);
|
|
2344
|
+
if (sheet) {
|
|
2345
|
+
unit.start.sheet_id = sheet.id;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
else {
|
|
2349
|
+
unit.start.sheet_id = relative_sheet_id;
|
|
2350
|
+
unit.start.sheet = relative_sheet_name;
|
|
2351
|
+
}
|
|
2352
|
+
/*
|
|
2353
|
+
unit.start.sheet_id = unit.start.sheet ?
|
|
2354
|
+
(sheet_name_map[unit.start.sheet.toLowerCase()] || 0) :
|
|
2355
|
+
relative_sheet_id;
|
|
2356
|
+
if (!unit.start.sheet) { unit.start.sheet = relative_sheet_name; }
|
|
2357
|
+
*/
|
|
2358
|
+
}
|
|
2359
|
+
if (!unit.start.sheet_id) {
|
|
2360
|
+
// see above in the address handler
|
|
2361
|
+
console.warn('invalid sheet in range', unit);
|
|
2362
|
+
}
|
|
2363
|
+
else {
|
|
2364
|
+
dependencies.ranges[unit.start.sheet_id + '!' + unit.start.label + ':' + unit.end.label] = unit;
|
|
2365
|
+
}
|
|
2366
|
+
break;
|
|
2367
|
+
case 'unary':
|
|
2368
|
+
this.RebuildDependencies(unit.operand, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
|
|
2369
|
+
break;
|
|
2370
|
+
case 'binary':
|
|
2371
|
+
this.RebuildDependencies(unit.left, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
|
|
2372
|
+
this.RebuildDependencies(unit.right, relative_sheet_id, relative_sheet_name, dependencies, context_address); //, sheet_name_map);
|
|
2373
|
+
break;
|
|
2374
|
+
case 'group':
|
|
2375
|
+
unit.elements.forEach((element) => this.RebuildDependencies(element, relative_sheet_id, relative_sheet_name, dependencies, context_address)); //, sheet_name_map));
|
|
2376
|
+
break;
|
|
2377
|
+
case 'implicit-call':
|
|
2378
|
+
{
|
|
2379
|
+
for (const arg of unit.args) {
|
|
2380
|
+
this.RebuildDependencies(arg, relative_sheet_id, relative_sheet_name, dependencies, context_address);
|
|
2381
|
+
}
|
|
2382
|
+
this.RebuildDependencies(unit.call, relative_sheet_id, relative_sheet_name, dependencies, context_address);
|
|
2383
|
+
}
|
|
2384
|
+
break;
|
|
2385
|
+
case 'call':
|
|
2386
|
+
// this is where we diverge. if there's a known function that has
|
|
2387
|
+
// an "address" parameter, we don't treat it as a dependency. this is
|
|
2388
|
+
// to support our weird MV syntax (weird here, but useful in Excel).
|
|
2389
|
+
// UPDATE: this is broadly useful for some other functions, like OFFSET.
|
|
2390
|
+
{
|
|
2391
|
+
const args = unit.args.slice(0);
|
|
2392
|
+
const func = this.library.Get(unit.name);
|
|
2393
|
+
if (func && func.arguments) {
|
|
2394
|
+
func.arguments.forEach((descriptor, index) => {
|
|
2395
|
+
if (descriptor && descriptor.address) {
|
|
2396
|
+
// we still want to fix sheet addresses, though, even if we're
|
|
2397
|
+
// not tracking the dependency. to do that, we can recurse with
|
|
2398
|
+
// a new (empty) dependency list, and just drop the new list
|
|
2399
|
+
this.RebuildDependencies(args[index], relative_sheet_id, relative_sheet_name, undefined, context_address); //, sheet_name_map);
|
|
2400
|
+
args[index] = { type: 'missing', id: -1 };
|
|
2401
|
+
}
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
args.forEach((arg) => this.RebuildDependencies(arg, relative_sheet_id, relative_sheet_name, dependencies, context_address)); //, sheet_name_map));
|
|
2405
|
+
}
|
|
2406
|
+
break;
|
|
2407
|
+
}
|
|
2408
|
+
return dependencies;
|
|
2409
|
+
}
|
|
2410
|
+
UpdateLeafVertex(vertex, formula, context, decimal_mark) {
|
|
2411
|
+
vertex.Reset();
|
|
2412
|
+
if (decimal_mark) {
|
|
2413
|
+
this.parser.Save();
|
|
2414
|
+
this.parser.SetLocaleSettings(decimal_mark);
|
|
2415
|
+
}
|
|
2416
|
+
const parse_result = this.parser.Parse(formula);
|
|
2417
|
+
if (parse_result.expression) {
|
|
2418
|
+
const dependencies = this.RebuildDependencies(parse_result.expression,
|
|
2419
|
+
// this.model.active_sheet.id,
|
|
2420
|
+
// this.model.active_sheet.name,
|
|
2421
|
+
context.id, context.name, undefined, { row: 0, column: 0 });
|
|
2422
|
+
for (const key of Object.keys(dependencies.ranges)) {
|
|
2423
|
+
const unit = dependencies.ranges[key];
|
|
2424
|
+
const range = new Area(unit.start, unit.end);
|
|
2425
|
+
if (range.entire_column || range.entire_row || range.count > 1) {
|
|
2426
|
+
// this.AddLeafVertexEdge(range.start, vertex);
|
|
2427
|
+
this.AddLeafVertexArrayEdge(range, vertex);
|
|
2428
|
+
}
|
|
2429
|
+
else {
|
|
2430
|
+
this.AddLeafVertexEdge(range.start, vertex);
|
|
2431
|
+
}
|
|
2432
|
+
/*
|
|
2433
|
+
for (const address of range) {
|
|
2434
|
+
this.AddLeafVertexEdge(address, vertex);
|
|
2435
|
+
}
|
|
2436
|
+
*/
|
|
2437
|
+
}
|
|
2438
|
+
for (const key of Object.keys(dependencies.addresses)) {
|
|
2439
|
+
const address = dependencies.addresses[key];
|
|
2440
|
+
this.AddLeafVertexEdge(address, vertex);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
vertex.expression = parse_result.expression || { type: 'missing', id: -1 };
|
|
2444
|
+
vertex.expression_error = !parse_result.valid;
|
|
2445
|
+
// vertex.UpdateState();
|
|
2446
|
+
if (decimal_mark) {
|
|
2447
|
+
this.parser.Restore();
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
*
|
|
2452
|
+
*/
|
|
2453
|
+
RebuildGraphCell(cell, address) {
|
|
2454
|
+
// FIXME/TODO: if spill is not enabled, we'll need to clean up
|
|
2455
|
+
// rendered values from the spill here
|
|
2456
|
+
if (cell.spill) {
|
|
2457
|
+
if (this.options.spill) {
|
|
2458
|
+
if (cell.spill.start.row === address.row && cell.spill.start.column === address.column) {
|
|
2459
|
+
// this.spills.push(new Area(cell.spill.start, cell.spill.end));
|
|
2460
|
+
this.AttachSpillData(new Area(cell.spill.start, cell.spill.end));
|
|
2461
|
+
}
|
|
2462
|
+
else {
|
|
2463
|
+
// ...
|
|
2464
|
+
this.AddEdge(cell.spill.start, address);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
// array head
|
|
2469
|
+
if (cell.area && cell.area.start.column === address.column && cell.area.start.row === address.row) {
|
|
2470
|
+
const { start, end } = cell.area;
|
|
2471
|
+
const sheet_id = start.sheet_id || address.sheet_id; // ... should always be ===
|
|
2472
|
+
if (!start.sheet_id) {
|
|
2473
|
+
start.sheet_id = sheet_id;
|
|
2474
|
+
}
|
|
2475
|
+
for (let column = start.column; column <= end.column; column++) {
|
|
2476
|
+
for (let row = start.row; row <= end.row; row++) {
|
|
2477
|
+
this.ResetInbound({ column, row, sheet_id }, true, false); // set dirty, don't create
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
this.SetDirty(address); // implicitly creates vertex for array head (if it doesn't already exist)
|
|
2481
|
+
// implicit vertices from array head -> array members. this is required
|
|
2482
|
+
// to correctly propagate dirtiness if a referenced cell changes state
|
|
2483
|
+
// from array -> !array and vice-versa
|
|
2484
|
+
for (let column = start.column; column <= end.column; column++) {
|
|
2485
|
+
for (let row = start.row; row <= end.row; row++) {
|
|
2486
|
+
if (row === start.row && column === start.column) {
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
this.AddEdge(start, { ...start, row, column });
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
// formula?
|
|
2494
|
+
if (cell.type === ValueType.formula) {
|
|
2495
|
+
this.ResetInbound(address, true); // NOTE: sets dirty AND creates vertex if it doesn't exist
|
|
2496
|
+
const parse_result = this.parser.Parse(cell.value);
|
|
2497
|
+
// we have a couple of "magic" functions that can have loops
|
|
2498
|
+
// but shouldn't trigger circular references. we need to check
|
|
2499
|
+
// for those here...
|
|
2500
|
+
if (parse_result.expression) {
|
|
2501
|
+
// some functions have UI - render and/or click. we want to attach
|
|
2502
|
+
// those to the cell. this is complicated in the case of lambdas.
|
|
2503
|
+
// case 1: library function. check directly.
|
|
2504
|
+
// case 2: inline lambda. check the last parameter.
|
|
2505
|
+
// case 3: indirect lambda (implicit-call), also
|
|
2506
|
+
// case 4: named lambda: resolve, then handle like a lambda.
|
|
2507
|
+
// AND NOTE, these could be nested... ?
|
|
2508
|
+
// case 5: this is _not_ a pass through situation: an explicit call
|
|
2509
|
+
// to lambda, which just defines a function.
|
|
2510
|
+
let descriptor;
|
|
2511
|
+
let base = parse_result.expression;
|
|
2512
|
+
if (base?.type === 'implicit-call') {
|
|
2513
|
+
// if this is a function, it's easy
|
|
2514
|
+
if (base.call.type === 'call') {
|
|
2515
|
+
base = base.call;
|
|
2516
|
+
}
|
|
2517
|
+
else { // else if (base.call.type === 'address') {
|
|
2518
|
+
// if this is an address, we'll have to resolve it...
|
|
2519
|
+
// I don't _think_ we need to handle any other types here
|
|
2520
|
+
// TODO/FIXME
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
else if (base?.type === 'call') {
|
|
2524
|
+
// handle case 5. top-level call, but indirect. this is the
|
|
2525
|
+
// case for lambdas defined in cell functions but not _called_
|
|
2526
|
+
descriptor = this.library.Get(base.name);
|
|
2527
|
+
if (descriptor?.pass_through_ui === 'indirect') {
|
|
2528
|
+
base = undefined;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
while (base?.type === 'call') {
|
|
2532
|
+
descriptor = this.library.Get(base.name);
|
|
2533
|
+
if (descriptor) {
|
|
2534
|
+
if (descriptor.pass_through_ui && base.args.length) {
|
|
2535
|
+
base = base.args[base.args.length - 1];
|
|
2536
|
+
}
|
|
2537
|
+
else {
|
|
2538
|
+
break;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
else {
|
|
2542
|
+
const named = this.model.GetName(base.name, address.sheet_id);
|
|
2543
|
+
if (named?.type === 'expression') {
|
|
2544
|
+
base = named.expression;
|
|
2545
|
+
}
|
|
2546
|
+
else {
|
|
2547
|
+
break;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
if (descriptor) {
|
|
2552
|
+
cell.render_function = descriptor.render;
|
|
2553
|
+
cell.click_function = descriptor.click;
|
|
2554
|
+
}
|
|
2555
|
+
/*
|
|
2556
|
+
if (parse_result.expression.type === 'call') {
|
|
2557
|
+
|
|
2558
|
+
descriptor = this.library.Get(parse_result.expression.name);
|
|
2559
|
+
if (!descriptor) {
|
|
2560
|
+
const named = this.model.GetName(parse_result.expression.name, address.sheet_id);
|
|
2561
|
+
if (named?.type === 'expression' && named.expression.type === 'call') {
|
|
2562
|
+
descriptor = this.library.Get(named.expression.name);
|
|
2563
|
+
console.info("D", descriptor);
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
if (descriptor) {
|
|
2568
|
+
cell.render_function = descriptor.render;
|
|
2569
|
+
cell.click_function = descriptor.click;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
}
|
|
2573
|
+
*/
|
|
2574
|
+
/*
|
|
2575
|
+
if (parse_result.expression.type === 'call') {
|
|
2576
|
+
const func = this.library.Get(parse_result.expression.name);
|
|
2577
|
+
if (func) {
|
|
2578
|
+
|
|
2579
|
+
cell.render_function = func.render;
|
|
2580
|
+
cell.click_function = func.click;
|
|
2581
|
+
|
|
2582
|
+
if (func.pass_through_ui) {
|
|
2583
|
+
// ...
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
}
|
|
2587
|
+
else {
|
|
2588
|
+
const named = this.model.GetName(parse_result.expression.name, address.sheet_id);
|
|
2589
|
+
if (named?.type === 'expression') {
|
|
2590
|
+
if (named.expression.type === 'call') {
|
|
2591
|
+
const func = this.library.Get(named.expression.name);
|
|
2592
|
+
if (func?.pass_through_ui && named.expression.args.length) {
|
|
2593
|
+
|
|
2594
|
+
// check the _last_ argument
|
|
2595
|
+
const last = named.expression.args[named.expression.args.length - 1];
|
|
2596
|
+
console.info({last});
|
|
2597
|
+
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
*/
|
|
2604
|
+
const dependencies = this.RebuildDependencies(parse_result.expression, address.sheet_id, '', undefined, address); // cell.sheet_id);
|
|
2605
|
+
for (const key of Object.keys(dependencies.ranges)) {
|
|
2606
|
+
const unit = dependencies.ranges[key];
|
|
2607
|
+
const range = new Area(unit.start, unit.end);
|
|
2608
|
+
// testing out array vertices (vertices that represent ranges).
|
|
2609
|
+
// this is an effort to reduce the number of vertices in the graph,
|
|
2610
|
+
// especially since these are generally unecessary (except for
|
|
2611
|
+
// formula cells).
|
|
2612
|
+
// if you want to drop this, go back to the non-array code below
|
|
2613
|
+
// and it should go back to the old way (but there will still be
|
|
2614
|
+
// some cruft in graph.ts, tests that will need to be removed).
|
|
2615
|
+
// actually it's probably something that could be balanced based
|
|
2616
|
+
// on the number of constants vs the number of formulae in the
|
|
2617
|
+
// range. more (or all) constants, use a range. more/all formula,
|
|
2618
|
+
// iterate.
|
|
2619
|
+
// --- array version -----------------------------------------------
|
|
2620
|
+
/*
|
|
2621
|
+
const status = this.AddArrayVertexEdge(range, cell);
|
|
2622
|
+
|
|
2623
|
+
if (status !== GraphStatus.OK) {
|
|
2624
|
+
global_status = status;
|
|
2625
|
+
if (!initial_reference) initial_reference = { ...cell };
|
|
2626
|
+
}
|
|
2627
|
+
*/
|
|
2628
|
+
// --- non-array version -------------------------------------------
|
|
2629
|
+
/*
|
|
2630
|
+
range.Iterate((target: ICellAddress) => {
|
|
2631
|
+
this.AddEdge(target, address);
|
|
2632
|
+
});
|
|
2633
|
+
*/
|
|
2634
|
+
// --- trying again... ---------------------------------------------
|
|
2635
|
+
if (range.entire_row || range.entire_column) {
|
|
2636
|
+
this.AddArrayEdge(range, address);
|
|
2637
|
+
}
|
|
2638
|
+
else {
|
|
2639
|
+
for (const target of range) {
|
|
2640
|
+
this.AddEdge(target, address);
|
|
2641
|
+
}
|
|
2642
|
+
// range.Iterate((target: ICellAddress) => this.AddEdge(target, address));
|
|
2643
|
+
}
|
|
2644
|
+
// --- end ---------------------------------------------------------
|
|
2645
|
+
}
|
|
2646
|
+
for (const key of Object.keys(dependencies.addresses)) {
|
|
2647
|
+
const dependency = dependencies.addresses[key];
|
|
2648
|
+
this.AddEdge(dependency, address);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
const vertex = this.GetVertex(address, true);
|
|
2652
|
+
if (vertex) {
|
|
2653
|
+
vertex.expression = parse_result.expression || { type: 'missing', id: -1 };
|
|
2654
|
+
vertex.expression_error = !parse_result.valid;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
else if (cell.value !== cell.calculated) {
|
|
2658
|
+
// sets dirty and removes inbound edges (in case the cell
|
|
2659
|
+
// previously contained a formula and now it contains a constant).
|
|
2660
|
+
this.ResetInbound(address, true, false); // NOTE: sets dirty
|
|
2661
|
+
}
|
|
2662
|
+
else if (cell.type === ValueType.undefined) {
|
|
2663
|
+
// in the new framework, we get here on any cleared cell, but
|
|
2664
|
+
// the behavior is OK
|
|
2665
|
+
// if we get here, it means that this cell was cleared but is not
|
|
2666
|
+
// 'empty'; in practice, that means it has a merge cell. reset inbound
|
|
2667
|
+
// and set dirty.
|
|
2668
|
+
// is this unecessarily flagging a number of cells? (...)
|
|
2669
|
+
this.ResetInbound(address, true, false, true);
|
|
2670
|
+
// we should be able to remove this vertex altogether; watch
|
|
2671
|
+
// out for arrays here
|
|
2672
|
+
// this.RemoveVertex(address); // implicit
|
|
2673
|
+
}
|
|
2674
|
+
else {
|
|
2675
|
+
// the reason you never get here is that the standard case is
|
|
2676
|
+
// value !== calculated. if you enter a constant, we flush
|
|
2677
|
+
// calculated first; so while the value doesn't change, it no
|
|
2678
|
+
// longer === calculated.
|
|
2679
|
+
// actually we do get here in the case of an array head with
|
|
2680
|
+
// a constant value. so we should stop shouting about it.
|
|
2681
|
+
// this is just a constant?
|
|
2682
|
+
// console.warn('UNHANDLED CASE', cell);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* rebuild the graph; parse expressions, build a dependency map,
|
|
2687
|
+
* initialize edges between nodes.
|
|
2688
|
+
*
|
|
2689
|
+
* FIXME: if we want to compose functions, we could do that here,
|
|
2690
|
+
* which might result in some savings [?]
|
|
2691
|
+
*/
|
|
2692
|
+
RebuildGraph(subset) {
|
|
2693
|
+
if (subset) {
|
|
2694
|
+
if (!subset.start.sheet_id) {
|
|
2695
|
+
throw new Error('subset missing sheet id');
|
|
2696
|
+
}
|
|
2697
|
+
// const cells = this.cells_map[subset.start.sheet_id];
|
|
2698
|
+
const cells = this.model.sheets.Find(subset.start.sheet_id)?.cells;
|
|
2699
|
+
if (cells) {
|
|
2700
|
+
for (let row = subset.start.row; row <= subset.end.row; row++) {
|
|
2701
|
+
const row_array = cells.data[row];
|
|
2702
|
+
if (row_array) {
|
|
2703
|
+
for (let column = subset.start.column; column <= subset.end.column; column++) {
|
|
2704
|
+
const cell = row_array[column];
|
|
2705
|
+
if (cell) {
|
|
2706
|
+
this.RebuildGraphCell(cell, { row, column, sheet_id: subset.start.sheet_id });
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
else {
|
|
2714
|
+
for (const sheet of this.model.sheets.list || []) {
|
|
2715
|
+
const rows = sheet.cells.data.length;
|
|
2716
|
+
for (let row = 0; row < rows; row++) {
|
|
2717
|
+
const row_array = sheet.cells.data[row];
|
|
2718
|
+
if (row_array) {
|
|
2719
|
+
const columns = row_array.length;
|
|
2720
|
+
for (let column = 0; column < columns; column++) {
|
|
2721
|
+
const cell = row_array[column];
|
|
2722
|
+
if (cell) {
|
|
2723
|
+
this.RebuildGraphCell(cell, { row, column, sheet_id: sheet.id });
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
/*
|
|
2732
|
+
protected IsNativeOrTypedArray(val: unknown): boolean {
|
|
2733
|
+
return Array.isArray(val) || (val instanceof Float64Array) || (val instanceof Float32Array);
|
|
2734
|
+
}
|
|
2735
|
+
*/
|
|
2736
|
+
/**
|
|
2737
|
+
* check if a cell is volatile. normally this falls out of the calculation,
|
|
2738
|
+
* but if we build the graph and set values explicitly, we need to check.
|
|
2739
|
+
*/
|
|
2740
|
+
CheckVolatile(vertex) {
|
|
2741
|
+
if (!vertex.expression || vertex.expression_error)
|
|
2742
|
+
return false;
|
|
2743
|
+
let volatile = false;
|
|
2744
|
+
this.parser.Walk(vertex.expression, (unit) => {
|
|
2745
|
+
if (unit.type === 'call') {
|
|
2746
|
+
const func = this.library.Get(unit.name);
|
|
2747
|
+
if (func && func.volatile)
|
|
2748
|
+
volatile = true;
|
|
2749
|
+
}
|
|
2750
|
+
return !volatile; // short circuit
|
|
2751
|
+
});
|
|
2752
|
+
return volatile;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
//# sourceMappingURL=calculator.js.map
|