From 0e2e1bddbab93c04f2e05b124b6d3eec1f75b4e6 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 22 Sep 2025 13:53:06 +0000 Subject: [PATCH] Add xlsxwriter-based Excel generation scripts with openpyxl implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created create_excel_xlsxwriter.py and update_excel_xlsxwriter.py - Uses openpyxl exclusively to preserve Excel formatting and formulas - Updated server.js to use new xlsxwriter scripts for form submissions - Maintains all original functionality while ensuring proper Excel file handling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 32 + .gitkeep | 0 ...tail Media Business Case Calculations.xlsx | Bin 0 -> 201562 bytes README.md | 77 + SOLUTION_EXCEL_CORRUPTION.md | 126 + clean_excel_template.py | 160 + config.json | 69 + create_excel.py | 149 + create_excel_clean.py | 326 + create_excel_openpyxl.py | 149 + create_excel_v2.py | 331 + create_excel_xlsxwriter.py | 152 + diagnose_excel_issue.py | 138 + excel_repair_solution_proposal.md | 260 + excel_table_repair_analysis.md | 117 + fix_excel_corruption.py | 207 + footprints_ai_test5_complete.xml | 51954 ++++++++++++++++ index.html | 1606 + index.js | 187 + llm_prompt_retail_media.md | 92 + package-lock.json | 2290 + package.json | 22 + server.js | 132 + template/.gitkeep | 0 ...tail Media Business Case Calculations.xlsx | Bin 0 -> 201653 bytes test_copy.xlsx | Bin 0 -> 201562 bytes test_opensave.xlsx | Bin 0 -> 144168 bytes thank-you.html | 50 + update_excel.py | 227 + update_excel_openpyxl.py | 225 + update_excel_xlsxwriter.py | 229 + venv/bin/Activate.ps1 | 247 + venv/bin/activate | 70 + venv/bin/activate.csh | 27 + venv/bin/activate.fish | 69 + venv/bin/pip | 8 + venv/bin/pip3 | 8 + venv/bin/pip3.12 | 8 + venv/bin/python | 1 + venv/bin/python3 | 1 + venv/bin/python3.12 | 1 + venv/bin/vba_extract.py | 79 + .../site-packages/dateutil/__init__.py | 24 + .../site-packages/dateutil/_common.py | 43 + .../site-packages/dateutil/_version.py | 4 + .../site-packages/dateutil/easter.py | 89 + .../site-packages/dateutil/parser/__init__.py | 61 + .../site-packages/dateutil/parser/_parser.py | 1613 + .../dateutil/parser/isoparser.py | 416 + .../site-packages/dateutil/relativedelta.py | 599 + .../site-packages/dateutil/rrule.py | 1737 + .../site-packages/dateutil/tz/__init__.py | 12 + .../site-packages/dateutil/tz/_common.py | 419 + .../site-packages/dateutil/tz/_factories.py | 80 + .../site-packages/dateutil/tz/tz.py | 1849 + .../site-packages/dateutil/tz/win.py | 370 + .../site-packages/dateutil/tzwin.py | 2 + .../site-packages/dateutil/utils.py | 71 + .../dateutil/zoneinfo/__init__.py | 167 + .../zoneinfo/dateutil-zoneinfo.tar.gz | Bin 0 -> 156400 bytes .../dateutil/zoneinfo/rebuild.py | 75 + .../et_xmlfile-2.0.0.dist-info/AUTHORS.txt | 5 + .../et_xmlfile-2.0.0.dist-info/INSTALLER | 1 + .../et_xmlfile-2.0.0.dist-info/LICENCE.python | 298 + .../et_xmlfile-2.0.0.dist-info/LICENCE.rst | 23 + .../et_xmlfile-2.0.0.dist-info/METADATA | 51 + .../et_xmlfile-2.0.0.dist-info/RECORD | 14 + .../et_xmlfile-2.0.0.dist-info/WHEEL | 5 + .../et_xmlfile-2.0.0.dist-info/top_level.txt | 1 + .../site-packages/et_xmlfile/__init__.py | 8 + .../et_xmlfile/incremental_tree.py | 917 + .../site-packages/et_xmlfile/xmlfile.py | 158 + .../openpyxl-3.1.5.dist-info/INSTALLER | 1 + .../openpyxl-3.1.5.dist-info/LICENCE.rst | 23 + .../openpyxl-3.1.5.dist-info/METADATA | 86 + .../openpyxl-3.1.5.dist-info/RECORD | 387 + .../openpyxl-3.1.5.dist-info/REQUESTED | 0 .../openpyxl-3.1.5.dist-info/WHEEL | 6 + .../openpyxl-3.1.5.dist-info/top_level.txt | 1 + .../site-packages/openpyxl/__init__.py | 19 + .../site-packages/openpyxl/_constants.py | 13 + .../site-packages/openpyxl/cell/__init__.py | 4 + .../site-packages/openpyxl/cell/_writer.py | 136 + .../site-packages/openpyxl/cell/cell.py | 332 + .../site-packages/openpyxl/cell/read_only.py | 136 + .../site-packages/openpyxl/cell/rich_text.py | 202 + .../site-packages/openpyxl/cell/text.py | 184 + .../site-packages/openpyxl/chart/_3d.py | 105 + .../site-packages/openpyxl/chart/__init__.py | 19 + .../site-packages/openpyxl/chart/_chart.py | 199 + .../openpyxl/chart/area_chart.py | 106 + .../site-packages/openpyxl/chart/axis.py | 401 + .../site-packages/openpyxl/chart/bar_chart.py | 144 + .../openpyxl/chart/bubble_chart.py | 67 + .../openpyxl/chart/chartspace.py | 195 + .../openpyxl/chart/data_source.py | 246 + .../openpyxl/chart/descriptors.py | 43 + .../site-packages/openpyxl/chart/error_bar.py | 62 + .../site-packages/openpyxl/chart/label.py | 127 + .../site-packages/openpyxl/chart/layout.py | 74 + .../site-packages/openpyxl/chart/legend.py | 75 + .../openpyxl/chart/line_chart.py | 129 + .../site-packages/openpyxl/chart/marker.py | 90 + .../site-packages/openpyxl/chart/picture.py | 35 + .../site-packages/openpyxl/chart/pie_chart.py | 177 + .../site-packages/openpyxl/chart/pivot.py | 65 + .../site-packages/openpyxl/chart/plotarea.py | 162 + .../openpyxl/chart/print_settings.py | 57 + .../openpyxl/chart/radar_chart.py | 55 + .../site-packages/openpyxl/chart/reader.py | 32 + .../site-packages/openpyxl/chart/reference.py | 124 + .../openpyxl/chart/scatter_chart.py | 53 + .../site-packages/openpyxl/chart/series.py | 197 + .../openpyxl/chart/series_factory.py | 41 + .../site-packages/openpyxl/chart/shapes.py | 89 + .../openpyxl/chart/stock_chart.py | 54 + .../openpyxl/chart/surface_chart.py | 119 + .../site-packages/openpyxl/chart/text.py | 78 + .../site-packages/openpyxl/chart/title.py | 76 + .../site-packages/openpyxl/chart/trendline.py | 98 + .../openpyxl/chart/updown_bars.py | 31 + .../openpyxl/chartsheet/__init__.py | 3 + .../openpyxl/chartsheet/chartsheet.py | 107 + .../openpyxl/chartsheet/custom.py | 61 + .../openpyxl/chartsheet/properties.py | 28 + .../openpyxl/chartsheet/protection.py | 41 + .../openpyxl/chartsheet/publish.py | 58 + .../openpyxl/chartsheet/relation.py | 97 + .../openpyxl/chartsheet/views.py | 51 + .../openpyxl/comments/__init__.py | 4 + .../site-packages/openpyxl/comments/author.py | 21 + .../openpyxl/comments/comment_sheet.py | 211 + .../openpyxl/comments/comments.py | 62 + .../openpyxl/comments/shape_writer.py | 112 + .../site-packages/openpyxl/compat/__init__.py | 54 + .../site-packages/openpyxl/compat/abc.py | 8 + .../site-packages/openpyxl/compat/numbers.py | 43 + .../site-packages/openpyxl/compat/product.py | 17 + .../openpyxl/compat/singleton.py | 40 + .../site-packages/openpyxl/compat/strings.py | 25 + .../openpyxl/descriptors/__init__.py | 58 + .../openpyxl/descriptors/base.py | 272 + .../openpyxl/descriptors/container.py | 41 + .../openpyxl/descriptors/excel.py | 112 + .../openpyxl/descriptors/namespace.py | 12 + .../openpyxl/descriptors/nested.py | 129 + .../openpyxl/descriptors/sequence.py | 136 + .../openpyxl/descriptors/serialisable.py | 240 + .../openpyxl/descriptors/slots.py | 18 + .../openpyxl/drawing/__init__.py | 4 + .../site-packages/openpyxl/drawing/colors.py | 435 + .../openpyxl/drawing/connector.py | 144 + .../site-packages/openpyxl/drawing/drawing.py | 92 + .../site-packages/openpyxl/drawing/effect.py | 407 + .../site-packages/openpyxl/drawing/fill.py | 425 + .../openpyxl/drawing/geometry.py | 584 + .../site-packages/openpyxl/drawing/graphic.py | 177 + .../site-packages/openpyxl/drawing/image.py | 65 + .../site-packages/openpyxl/drawing/line.py | 144 + .../site-packages/openpyxl/drawing/picture.py | 144 + .../openpyxl/drawing/properties.py | 174 + .../openpyxl/drawing/relation.py | 17 + .../openpyxl/drawing/spreadsheet_drawing.py | 382 + .../site-packages/openpyxl/drawing/text.py | 717 + .../site-packages/openpyxl/drawing/xdr.py | 33 + .../openpyxl/formatting/__init__.py | 3 + .../openpyxl/formatting/formatting.py | 114 + .../site-packages/openpyxl/formatting/rule.py | 291 + .../openpyxl/formula/__init__.py | 3 + .../openpyxl/formula/tokenizer.py | 446 + .../openpyxl/formula/translate.py | 166 + .../openpyxl/packaging/__init__.py | 3 + .../site-packages/openpyxl/packaging/core.py | 115 + .../openpyxl/packaging/custom.py | 289 + .../openpyxl/packaging/extended.py | 137 + .../openpyxl/packaging/interface.py | 56 + .../openpyxl/packaging/manifest.py | 194 + .../openpyxl/packaging/relationship.py | 158 + .../openpyxl/packaging/workbook.py | 185 + .../site-packages/openpyxl/pivot/__init__.py | 1 + .../site-packages/openpyxl/pivot/cache.py | 965 + .../site-packages/openpyxl/pivot/fields.py | 326 + .../site-packages/openpyxl/pivot/record.py | 111 + .../site-packages/openpyxl/pivot/table.py | 1261 + .../site-packages/openpyxl/reader/__init__.py | 1 + .../site-packages/openpyxl/reader/drawings.py | 71 + .../site-packages/openpyxl/reader/excel.py | 349 + .../site-packages/openpyxl/reader/strings.py | 44 + .../site-packages/openpyxl/reader/workbook.py | 133 + .../site-packages/openpyxl/styles/__init__.py | 11 + .../openpyxl/styles/alignment.py | 62 + .../site-packages/openpyxl/styles/borders.py | 103 + .../site-packages/openpyxl/styles/builtins.py | 1397 + .../openpyxl/styles/cell_style.py | 206 + .../site-packages/openpyxl/styles/colors.py | 172 + .../openpyxl/styles/differential.py | 95 + .../site-packages/openpyxl/styles/fills.py | 224 + .../site-packages/openpyxl/styles/fonts.py | 113 + .../openpyxl/styles/named_styles.py | 282 + .../site-packages/openpyxl/styles/numbers.py | 200 + .../openpyxl/styles/protection.py | 17 + .../site-packages/openpyxl/styles/proxy.py | 62 + .../openpyxl/styles/styleable.py | 151 + .../openpyxl/styles/stylesheet.py | 274 + .../site-packages/openpyxl/styles/table.py | 94 + .../site-packages/openpyxl/utils/__init__.py | 17 + .../openpyxl/utils/bound_dictionary.py | 26 + .../site-packages/openpyxl/utils/cell.py | 240 + .../site-packages/openpyxl/utils/dataframe.py | 87 + .../site-packages/openpyxl/utils/datetime.py | 140 + .../site-packages/openpyxl/utils/escape.py | 43 + .../openpyxl/utils/exceptions.py | 34 + .../site-packages/openpyxl/utils/formulas.py | 24 + .../openpyxl/utils/indexed_list.py | 49 + .../site-packages/openpyxl/utils/inference.py | 60 + .../openpyxl/utils/protection.py | 22 + .../site-packages/openpyxl/utils/units.py | 108 + .../openpyxl/workbook/__init__.py | 4 + .../openpyxl/workbook/_writer.py | 197 + .../site-packages/openpyxl/workbook/child.py | 166 + .../openpyxl/workbook/defined_name.py | 189 + .../workbook/external_link/__init__.py | 3 + .../workbook/external_link/external.py | 190 + .../openpyxl/workbook/external_reference.py | 18 + .../openpyxl/workbook/function_group.py | 36 + .../openpyxl/workbook/properties.py | 151 + .../openpyxl/workbook/protection.py | 163 + .../openpyxl/workbook/smart_tags.py | 56 + .../site-packages/openpyxl/workbook/views.py | 155 + .../site-packages/openpyxl/workbook/web.py | 98 + .../openpyxl/workbook/workbook.py | 438 + .../openpyxl/worksheet/__init__.py | 1 + .../openpyxl/worksheet/_read_only.py | 190 + .../openpyxl/worksheet/_reader.py | 472 + .../openpyxl/worksheet/_write_only.py | 160 + .../openpyxl/worksheet/_writer.py | 390 + .../openpyxl/worksheet/cell_range.py | 512 + .../openpyxl/worksheet/cell_watch.py | 34 + .../openpyxl/worksheet/controls.py | 107 + .../openpyxl/worksheet/copier.py | 70 + .../openpyxl/worksheet/custom.py | 35 + .../openpyxl/worksheet/datavalidation.py | 202 + .../openpyxl/worksheet/dimensions.py | 306 + .../openpyxl/worksheet/drawing.py | 14 + .../openpyxl/worksheet/errors.py | 93 + .../openpyxl/worksheet/filters.py | 486 + .../openpyxl/worksheet/formula.py | 51 + .../openpyxl/worksheet/header_footer.py | 270 + .../openpyxl/worksheet/hyperlink.py | 46 + .../site-packages/openpyxl/worksheet/merge.py | 141 + .../site-packages/openpyxl/worksheet/ole.py | 133 + .../site-packages/openpyxl/worksheet/page.py | 174 + .../openpyxl/worksheet/pagebreak.py | 94 + .../openpyxl/worksheet/picture.py | 8 + .../openpyxl/worksheet/print_settings.py | 184 + .../openpyxl/worksheet/properties.py | 97 + .../openpyxl/worksheet/protection.py | 120 + .../openpyxl/worksheet/related.py | 17 + .../openpyxl/worksheet/scenario.py | 105 + .../openpyxl/worksheet/smart_tag.py | 78 + .../site-packages/openpyxl/worksheet/table.py | 385 + .../site-packages/openpyxl/worksheet/views.py | 155 + .../openpyxl/worksheet/worksheet.py | 907 + .../site-packages/openpyxl/writer/__init__.py | 1 + .../site-packages/openpyxl/writer/excel.py | 295 + .../site-packages/openpyxl/writer/theme.py | 291 + .../site-packages/openpyxl/xml/__init__.py | 42 + .../site-packages/openpyxl/xml/constants.py | 129 + .../site-packages/openpyxl/xml/functions.py | 87 + .../pip-24.0.dist-info/AUTHORS.txt | 760 + .../pip-24.0.dist-info/INSTALLER | 1 + .../pip-24.0.dist-info/LICENSE.txt | 20 + .../site-packages/pip-24.0.dist-info/METADATA | 88 + .../site-packages/pip-24.0.dist-info/RECORD | 1005 + .../pip-24.0.dist-info/REQUESTED | 0 .../site-packages/pip-24.0.dist-info/WHEEL | 5 + .../pip-24.0.dist-info/entry_points.txt | 4 + .../pip-24.0.dist-info/top_level.txt | 1 + .../python3.12/site-packages/pip/__init__.py | 13 + .../python3.12/site-packages/pip/__main__.py | 24 + .../site-packages/pip/__pip-runner__.py | 50 + .../site-packages/pip/_internal/__init__.py | 18 + .../site-packages/pip/_internal/build_env.py | 311 + .../site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../pip/_internal/cli/autocompletion.py | 172 + .../pip/_internal/cli/base_command.py | 236 + .../pip/_internal/cli/cmdoptions.py | 1074 + .../pip/_internal/cli/command_context.py | 27 + .../site-packages/pip/_internal/cli/main.py | 79 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 68 + .../pip/_internal/cli/req_command.py | 505 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 54 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 147 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 774 + .../pip/_internal/commands/list.py | 370 + .../pip/_internal/commands/search.py | 174 + .../pip/_internal/commands/show.py | 189 + .../pip/_internal/commands/uninstall.py | 113 + .../pip/_internal/commands/wheel.py | 183 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../pip/_internal/distributions/base.py | 51 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 156 + .../pip/_internal/distributions/wheel.py | 40 + .../site-packages/pip/_internal/exceptions.py | 728 + .../pip/_internal/index/__init__.py | 2 + .../pip/_internal/index/collector.py | 507 + .../pip/_internal/index/package_finder.py | 1027 + .../pip/_internal/index/sources.py | 285 + .../pip/_internal/locations/__init__.py | 467 + .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 213 + .../pip/_internal/locations/base.py | 81 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 702 + .../_internal/metadata/importlib/__init__.py | 6 + .../_internal/metadata/importlib/_compat.py | 55 + .../_internal/metadata/importlib/_dists.py | 227 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 278 + .../pip/_internal/models/__init__.py | 2 + .../pip/_internal/models/candidate.py | 30 + .../pip/_internal/models/direct_url.py | 235 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 579 + .../pip/_internal/models/scheme.py | 31 + .../pip/_internal/models/search_scope.py | 132 + .../pip/_internal/models/selection_prefs.py | 51 + .../pip/_internal/models/target_python.py | 122 + .../pip/_internal/models/wheel.py | 92 + .../pip/_internal/network/__init__.py | 2 + .../pip/_internal/network/auth.py | 561 + .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 186 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 520 + .../pip/_internal/network/utils.py | 96 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../_internal/operations/build/__init__.py | 0 .../operations/build/build_tracker.py | 139 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 41 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 187 + .../pip/_internal/operations/freeze.py | 255 + .../_internal/operations/install/__init__.py | 2 + .../operations/install/editable_legacy.py | 46 + .../pip/_internal/operations/install/wheel.py | 734 + .../pip/_internal/operations/prepare.py | 730 + .../site-packages/pip/_internal/pyproject.py | 179 + .../pip/_internal/req/__init__.py | 92 + .../pip/_internal/req/constructors.py | 576 + .../pip/_internal/req/req_file.py | 554 + .../pip/_internal/req/req_install.py | 923 + .../pip/_internal/req/req_set.py | 119 + .../pip/_internal/req/req_uninstall.py | 649 + .../pip/_internal/resolution/__init__.py | 0 .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../_internal/resolution/legacy/resolver.py | 598 + .../resolution/resolvelib/__init__.py | 0 .../_internal/resolution/resolvelib/base.py | 141 + .../resolution/resolvelib/candidates.py | 597 + .../resolution/resolvelib/factory.py | 812 + .../resolution/resolvelib/found_candidates.py | 155 + .../resolution/resolvelib/provider.py | 255 + .../resolution/resolvelib/reporter.py | 80 + .../resolution/resolvelib/requirements.py | 166 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 248 + .../pip/_internal/utils/__init__.py | 0 .../pip/_internal/utils/_jaraco_text.py | 109 + .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 63 + .../pip/_internal/utils/compatibility_tags.py | 165 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 120 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/egg_link.py | 80 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 153 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 88 + .../pip/_internal/utils/hashes.py | 151 + .../pip/_internal/utils/logging.py | 348 + .../site-packages/pip/_internal/utils/misc.py | 783 + .../pip/_internal/utils/models.py | 39 + .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 260 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 257 + .../site-packages/pip/_internal/utils/urls.py | 62 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 526 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 705 + .../pip/_internal/wheel_builder.py | 354 + .../site-packages/pip/_vendor/__init__.py | 121 + .../pip/_vendor/cachecontrol/__init__.py | 28 + .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 74 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../_vendor/cachecontrol/caches/file_cache.py | 181 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 494 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/serialize.py | 206 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../pip/_vendor/certifi/cacert.pem | 4635 ++ .../site-packages/pip/_vendor/certifi/core.py | 119 + .../pip/_vendor/chardet/__init__.py | 115 + .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 261 + .../pip/_vendor/chardet/charsetgroupprober.py | 106 + .../pip/_vendor/chardet/charsetprober.py | 147 + .../pip/_vendor/chardet/cli/__init__.py | 0 .../pip/_vendor/chardet/cli/chardetect.py | 112 + .../pip/_vendor/chardet/codingstatemachine.py | 90 + .../_vendor/chardet/codingstatemachinedict.py | 19 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 85 + .../pip/_vendor/chardet/escprober.py | 102 + .../pip/_vendor/chardet/escsm.py | 261 + .../pip/_vendor/chardet/eucjpprober.py | 102 + .../pip/_vendor/chardet/euckrfreq.py | 196 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 388 + .../pip/_vendor/chardet/euctwprober.py | 47 + .../pip/_vendor/chardet/gb2312freq.py | 284 + .../pip/_vendor/chardet/gb2312prober.py | 47 + .../pip/_vendor/chardet/hebrewprober.py | 316 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/johabfreq.py | 2382 + .../pip/_vendor/chardet/johabprober.py | 47 + .../pip/_vendor/chardet/jpcntx.py | 238 + .../pip/_vendor/chardet/langbulgarianmodel.py | 4649 ++ .../pip/_vendor/chardet/langgreekmodel.py | 4397 ++ .../pip/_vendor/chardet/langhebrewmodel.py | 4380 ++ .../pip/_vendor/chardet/langhungarianmodel.py | 4649 ++ .../pip/_vendor/chardet/langrussianmodel.py | 5725 ++ .../pip/_vendor/chardet/langthaimodel.py | 4380 ++ .../pip/_vendor/chardet/langturkishmodel.py | 4380 ++ .../pip/_vendor/chardet/latin1prober.py | 147 + .../pip/_vendor/chardet/macromanprober.py | 162 + .../pip/_vendor/chardet/mbcharsetprober.py | 95 + .../pip/_vendor/chardet/mbcsgroupprober.py | 57 + .../pip/_vendor/chardet/mbcssm.py | 661 + .../pip/_vendor/chardet/metadata/__init__.py | 0 .../pip/_vendor/chardet/metadata/languages.py | 352 + .../pip/_vendor/chardet/resultdict.py | 16 + .../pip/_vendor/chardet/sbcharsetprober.py | 162 + .../pip/_vendor/chardet/sbcsgroupprober.py | 88 + .../pip/_vendor/chardet/sjisprober.py | 105 + .../pip/_vendor/chardet/universaldetector.py | 362 + .../pip/_vendor/chardet/utf1632prober.py | 225 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 7 + .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 277 + .../pip/_vendor/colorama/initialise.py | 121 + .../pip/_vendor/colorama/tests/__init__.py | 1 + .../pip/_vendor/colorama/tests/ansi_test.py | 76 + .../colorama/tests/ansitowin32_test.py | 294 + .../_vendor/colorama/tests/initialise_test.py | 189 + .../pip/_vendor/colorama/tests/isatty_test.py | 57 + .../pip/_vendor/colorama/tests/utils.py | 49 + .../_vendor/colorama/tests/winterm_test.py | 131 + .../pip/_vendor/colorama/win32.py | 180 + .../pip/_vendor/colorama/winterm.py | 195 + .../pip/_vendor/distlib/__init__.py | 33 + .../pip/_vendor/distlib/compat.py | 1138 + .../pip/_vendor/distlib/database.py | 1359 + .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1303 + .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 167 + .../pip/_vendor/distlib/metadata.py | 1068 + .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 452 + .../site-packages/pip/_vendor/distlib/util.py | 2025 + .../pip/_vendor/distlib/version.py | 751 + .../pip/_vendor/distlib/wheel.py | 1099 + .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../pip/_vendor/distro/distro.py | 1399 + .../pip/_vendor/idna/__init__.py | 44 + .../site-packages/pip/_vendor/idna/codec.py | 112 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 400 + .../pip/_vendor/idna/idnadata.py | 2151 + .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8600 +++ .../pip/_vendor/msgpack/__init__.py | 57 + .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 193 + .../pip/_vendor/msgpack/fallback.py | 1010 + .../pip/_vendor/packaging/__about__.py | 26 + .../pip/_vendor/packaging/__init__.py | 25 + .../pip/_vendor/packaging/_manylinux.py | 301 + .../pip/_vendor/packaging/_musllinux.py | 136 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/markers.py | 304 + .../pip/_vendor/packaging/requirements.py | 146 + .../pip/_vendor/packaging/specifiers.py | 802 + .../pip/_vendor/packaging/tags.py | 487 + .../pip/_vendor/packaging/utils.py | 136 + .../pip/_vendor/packaging/version.py | 504 + .../pip/_vendor/pkg_resources/__init__.py | 3361 + .../pip/_vendor/platformdirs/__init__.py | 566 + .../pip/_vendor/platformdirs/__main__.py | 53 + .../pip/_vendor/platformdirs/android.py | 210 + .../pip/_vendor/platformdirs/api.py | 223 + .../pip/_vendor/platformdirs/macos.py | 91 + .../pip/_vendor/platformdirs/unix.py | 223 + .../pip/_vendor/platformdirs/version.py | 4 + .../pip/_vendor/platformdirs/windows.py | 255 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../pip/_vendor/pygments/cmdline.py | 668 + .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 71 + .../pip/_vendor/pygments/filters/__init__.py | 940 + .../pip/_vendor/pygments/formatter.py | 124 + .../_vendor/pygments/formatters/__init__.py | 158 + .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 989 + .../pip/_vendor/pygments/formatters/img.py | 645 + .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 521 + .../pip/_vendor/pygments/formatters/other.py | 161 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 146 + .../pip/_vendor/pygments/formatters/svg.py | 188 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 943 + .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../pip/_vendor/pygments/lexers/_mapping.py | 559 + .../pip/_vendor/pygments/lexers/python.py | 1198 + .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 88 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 217 + .../pip/_vendor/pygments/style.py | 197 + .../pip/_vendor/pygments/styles/__init__.py | 103 + .../pip/_vendor/pygments/token.py | 213 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 330 + .../pip/_vendor/pyparsing/__init__.py | 322 + .../pip/_vendor/pyparsing/actions.py | 217 + .../pip/_vendor/pyparsing/common.py | 432 + .../pip/_vendor/pyparsing/core.py | 6115 ++ .../pip/_vendor/pyparsing/diagram/__init__.py | 656 + .../pip/_vendor/pyparsing/exceptions.py | 299 + .../pip/_vendor/pyparsing/helpers.py | 1100 + .../pip/_vendor/pyparsing/results.py | 796 + .../pip/_vendor/pyparsing/testing.py | 331 + .../pip/_vendor/pyparsing/unicode.py | 361 + .../pip/_vendor/pyparsing/util.py | 284 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../pip/_vendor/pyproject_hooks/_compat.py | 8 + .../pip/_vendor/pyproject_hooks/_impl.py | 330 + .../pyproject_hooks/_in_process/__init__.py | 18 + .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 182 + .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 538 + .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 315 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 67 + .../pip/_vendor/requests/cookies.py | 561 + .../pip/_vendor/requests/exceptions.py | 141 + .../pip/_vendor/requests/help.py | 131 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1034 + .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 833 + .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1094 + .../pip/_vendor/resolvelib/__init__.py | 26 + .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 + .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 274 + .../pip/_vendor/rich/_cell_widths.py | 451 + .../pip/_vendor/rich/_emoji_codes.py | 3610 ++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 270 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 160 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 662 + .../pip/_vendor/rich/_windows.py | 72 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 56 + .../site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../site-packages/pip/_vendor/rich/bar.py | 94 + .../site-packages/pip/_vendor/rich/box.py | 517 + .../site-packages/pip/_vendor/rich/cells.py | 154 + .../site-packages/pip/_vendor/rich/color.py | 622 + .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2633 + .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 190 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 89 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 140 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 443 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 113 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 246 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 308 + .../site-packages/pip/_vendor/rich/pretty.py | 994 + .../pip/_vendor/rich/progress.py | 1702 + .../pip/_vendor/rich/progress_bar.py | 224 + .../site-packages/pip/_vendor/rich/prompt.py | 376 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 739 + .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 132 + .../site-packages/pip/_vendor/rich/style.py | 796 + .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 948 + .../site-packages/pip/_vendor/rich/table.py | 1002 + .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1307 + .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 756 + .../site-packages/pip/_vendor/rich/tree.py | 251 + .../site-packages/pip/_vendor/six.py | 998 + .../pip/_vendor/tenacity/__init__.py | 608 + .../pip/_vendor/tenacity/_asyncio.py | 94 + .../pip/_vendor/tenacity/_utils.py | 76 + .../pip/_vendor/tenacity/after.py | 51 + .../pip/_vendor/tenacity/before.py | 46 + .../pip/_vendor/tenacity/before_sleep.py | 71 + .../site-packages/pip/_vendor/tenacity/nap.py | 43 + .../pip/_vendor/tenacity/retry.py | 272 + .../pip/_vendor/tenacity/stop.py | 103 + .../pip/_vendor/tenacity/tornadoweb.py | 59 + .../pip/_vendor/tenacity/wait.py | 228 + .../pip/_vendor/tomli/__init__.py | 11 + .../pip/_vendor/tomli/_parser.py | 691 + .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../pip/_vendor/truststore/__init__.py | 13 + .../pip/_vendor/truststore/_api.py | 302 + .../pip/_vendor/truststore/_macos.py | 501 + .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 554 + .../pip/_vendor/typing_extensions.py | 3072 + .../pip/_vendor/urllib3/__init__.py | 102 + .../pip/_vendor/urllib3/_collections.py | 337 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 + .../pip/_vendor/urllib3/connectionpool.py | 1132 + .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 518 + .../urllib3/contrib/securetransport.py | 921 + .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 0 .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 + .../pip/_vendor/urllib3/poolmanager.py | 553 + .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 + .../pip/_vendor/urllib3/util/__init__.py | 49 + .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 622 + .../pip/_vendor/urllib3/util/ssl_.py | 495 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + .../site-packages/pip/_vendor/vendor.txt | 24 + .../pip/_vendor/webencodings/__init__.py | 342 + .../pip/_vendor/webencodings/labels.py | 231 + .../pip/_vendor/webencodings/mklabels.py | 59 + .../pip/_vendor/webencodings/tests.py | 153 + .../_vendor/webencodings/x_user_defined.py | 325 + .../lib/python3.12/site-packages/pip/py.typed | 4 + .../INSTALLER | 1 + .../LICENSE | 54 + .../METADATA | 204 + .../RECORD | 45 + .../REQUESTED | 0 .../WHEEL | 6 + .../top_level.txt | 1 + .../zip-safe | 1 + .../six-1.17.0.dist-info/INSTALLER | 1 + .../six-1.17.0.dist-info/LICENSE | 18 + .../six-1.17.0.dist-info/METADATA | 43 + .../site-packages/six-1.17.0.dist-info/RECORD | 8 + .../site-packages/six-1.17.0.dist-info/WHEEL | 6 + .../six-1.17.0.dist-info/top_level.txt | 1 + venv/lib/python3.12/site-packages/six.py | 1003 + .../xlsxwriter-3.2.9.dist-info/INSTALLER | 1 + .../xlsxwriter-3.2.9.dist-info/LICENSE.txt | 25 + .../xlsxwriter-3.2.9.dist-info/METADATA | 95 + .../xlsxwriter-3.2.9.dist-info/RECORD | 93 + .../xlsxwriter-3.2.9.dist-info/REQUESTED | 0 .../xlsxwriter-3.2.9.dist-info/WHEEL | 5 + .../xlsxwriter-3.2.9.dist-info/top_level.txt | 1 + .../site-packages/xlsxwriter/__init__.py | 10 + .../site-packages/xlsxwriter/app.py | 202 + .../site-packages/xlsxwriter/chart.py | 4387 ++ .../site-packages/xlsxwriter/chart_area.py | 104 + .../site-packages/xlsxwriter/chart_bar.py | 177 + .../site-packages/xlsxwriter/chart_column.py | 135 + .../xlsxwriter/chart_doughnut.py | 101 + .../site-packages/xlsxwriter/chart_line.py | 146 + .../site-packages/xlsxwriter/chart_pie.py | 263 + .../site-packages/xlsxwriter/chart_radar.py | 105 + .../site-packages/xlsxwriter/chart_scatter.py | 337 + .../site-packages/xlsxwriter/chart_stock.py | 125 + .../site-packages/xlsxwriter/chart_title.py | 110 + .../site-packages/xlsxwriter/chartsheet.py | 203 + .../site-packages/xlsxwriter/color.py | 431 + .../site-packages/xlsxwriter/comments.py | 392 + .../site-packages/xlsxwriter/contenttypes.py | 270 + .../site-packages/xlsxwriter/core.py | 182 + .../site-packages/xlsxwriter/custom.py | 138 + .../site-packages/xlsxwriter/drawing.py | 1158 + .../site-packages/xlsxwriter/exceptions.py | 56 + .../xlsxwriter/feature_property_bag.py | 156 + .../site-packages/xlsxwriter/format.py | 1274 + .../site-packages/xlsxwriter/image.py | 401 + .../site-packages/xlsxwriter/metadata.py | 266 + .../site-packages/xlsxwriter/packager.py | 878 + .../site-packages/xlsxwriter/relationships.py | 143 + .../site-packages/xlsxwriter/rich_value.py | 98 + .../xlsxwriter/rich_value_rel.py | 82 + .../xlsxwriter/rich_value_structure.py | 99 + .../xlsxwriter/rich_value_types.py | 111 + .../site-packages/xlsxwriter/shape.py | 433 + .../site-packages/xlsxwriter/sharedstrings.py | 138 + .../site-packages/xlsxwriter/styles.py | 788 + .../site-packages/xlsxwriter/table.py | 194 + .../site-packages/xlsxwriter/theme.py | 69 + .../site-packages/xlsxwriter/url.py | 268 + .../site-packages/xlsxwriter/utility.py | 954 + .../site-packages/xlsxwriter/vml.py | 783 + .../site-packages/xlsxwriter/workbook.py | 1817 + .../site-packages/xlsxwriter/worksheet.py | 8381 +++ .../site-packages/xlsxwriter/xmlwriter.py | 235 + venv/lib64 | 1 + venv/pyvenv.cfg | 5 + 842 files changed, 316330 insertions(+) create mode 100644 .gitignore create mode 100644 .gitkeep create mode 100644 Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx create mode 100644 README.md create mode 100644 SOLUTION_EXCEL_CORRUPTION.md create mode 100755 clean_excel_template.py create mode 100644 config.json create mode 100644 create_excel.py create mode 100755 create_excel_clean.py create mode 100644 create_excel_openpyxl.py create mode 100644 create_excel_v2.py create mode 100644 create_excel_xlsxwriter.py create mode 100644 diagnose_excel_issue.py create mode 100644 excel_repair_solution_proposal.md create mode 100644 excel_table_repair_analysis.md create mode 100644 fix_excel_corruption.py create mode 100644 footprints_ai_test5_complete.xml create mode 100644 index.html create mode 100644 index.js create mode 100644 llm_prompt_retail_media.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js create mode 100644 template/.gitkeep create mode 100644 template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx create mode 100644 test_copy.xlsx create mode 100644 test_opensave.xlsx create mode 100644 thank-you.html create mode 100644 update_excel.py create mode 100644 update_excel_openpyxl.py create mode 100644 update_excel_xlsxwriter.py create mode 100644 venv/bin/Activate.ps1 create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip3 create mode 100755 venv/bin/pip3.12 create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 120000 venv/bin/python3.12 create mode 100755 venv/bin/vba_extract.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/_common.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/_version.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/easter.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/parser/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/parser/_parser.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/parser/isoparser.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/relativedelta.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/rrule.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tz/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tz/_common.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tz/_factories.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tz/tz.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tz/win.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/tzwin.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/utils.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz create mode 100644 venv/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/AUTHORS.txt create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.python create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.rst create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile/__init__.py create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile/incremental_tree.py create mode 100644 venv/lib/python3.12/site-packages/et_xmlfile/xmlfile.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/LICENCE.rst create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/openpyxl/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/_constants.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/cell.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/cell/text.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/axis.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/label.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/layout.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/legend.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/marker.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/picture.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/reader.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/reference.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/series.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/text.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/title.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/comments/author.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/comments/comments.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/abc.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/product.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/compat/strings.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/image.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/line.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/text.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/formula/translate.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/core.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/pivot/record.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/pivot/table.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/reader/excel.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/reader/strings.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/borders.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/colors.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/differential.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/fills.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/protection.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/styles/table.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/bound_dictionary.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/cell.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/escape.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/formulas.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/indexed_list.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/inference.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/protection.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/utils/units.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/child.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/views.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/web.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/_read_only.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/_reader.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/_write_only.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_range.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/cell_watch.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/controls.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/copier.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/custom.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/datavalidation.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/dimensions.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/drawing.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/errors.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/filters.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/formula.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/hyperlink.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/merge.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/ole.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/page.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/pagebreak.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/picture.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/print_settings.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/properties.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/protection.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/related.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/scenario.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/smart_tag.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/table.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/views.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/worksheet/worksheet.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/writer/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/writer/excel.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/writer/theme.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/xml/__init__.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/xml/constants.py create mode 100644 venv/lib/python3.12/site-packages/openpyxl/xml/functions.py create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/AUTHORS.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/pip/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__pip-runner__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/list.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/search.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/show.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/sdist.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/collector.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/package_finder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/sources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/_json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/direct_url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/installation_report.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/scheme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/session.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/pyproject.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/self_outdated_check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_log.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/datetime.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/egg_link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/encoding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filetypes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/models.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/unpacking.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/urls.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/wheel_builder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5freq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/chardistribution.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/chardetect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachine.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachinedict.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cp949prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/enums.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/escprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/escsm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/eucjpprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312freq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/hebrewprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/jisfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/jpcntx.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langgreekmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langrussianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langthaimodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langturkishmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/latin1prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/macromanprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcssm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/languages.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/resultdict.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sjisprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/universaldetector.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf1632prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf8prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansi.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansitowin32.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/initialise.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansi_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansitowin32_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/initialise_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/isatty_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/winterm_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/win32.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/winterm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/distro.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/token.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/actions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/common.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/diagram/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/layout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/markup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/measure.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/padding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/palette.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/panel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pretty.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/prompt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/protocol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/region.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/repr.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/rule.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/screen.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/segment.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/spinner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/status.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/styled.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/syntax.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/table.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/themes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/traceback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/tree.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/six.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_asyncio.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/after.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before_sleep.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/nap.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/stop.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/tornadoweb.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/wait.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/typing_extensions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/vendor.txt create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/labels.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/mklabels.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/tests.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/x_user_defined.py create mode 100644 venv/lib/python3.12/site-packages/pip/py.typed create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/zip-safe create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/six.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/__init__.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/app.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_area.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_bar.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_column.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_doughnut.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_line.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_pie.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_radar.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_scatter.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_stock.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chart_title.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/chartsheet.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/color.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/comments.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/contenttypes.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/core.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/custom.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/drawing.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/feature_property_bag.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/format.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/image.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/metadata.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/packager.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/relationships.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/rich_value.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/rich_value_rel.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/rich_value_structure.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/rich_value_types.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/shape.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/sharedstrings.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/styles.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/table.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/theme.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/url.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/utility.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/vml.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/workbook.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/worksheet.py create mode 100644 venv/lib/python3.12/site-packages/xlsxwriter/xmlwriter.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd5f421 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Dependency directories +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log + +# Optional npm cache directory +.npm + +# Environment variables +.env + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Log files +logs +*.log + +# Python +__pycache__/ +*.py[cod] +*$py.class + +# Output directory +output/ \ No newline at end of file diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx b/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fe183195666f0c80b92c84f6f1c6b27e0d681d2c GIT binary patch literal 201562 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 mi~Thg0>TyM* Info > Properties > Advanced Properties) + +## Verification + +To verify a file is clean: +```bash +python3 diagnose_excel_issue.py +``` + +Look for: +- ✅ "File is clean - no SharePoint metadata found" +- ✅ No ContentTypeId or MediaService tags + +## Prevention Best Practices + +1. **Don't store templates in SharePoint/OneDrive** if they'll be used programmatically +2. **Always clean templates** downloaded from cloud services before use +3. **Run the diagnostic script** if you see corruption warnings +4. **Keep local backups** of clean templates + +## Technical Details + +The corruption is specifically in the `docProps/custom.xml` file within the Excel ZIP structure: + +```xml + + + 0x0101000AE797D2C7FAC04B99DEE11AFEDCE578 + + + + +``` + +The fix replaces this with a clean, empty custom properties file that Excel accepts without warnings. + +## Results + +✅ All Excel files have been cleaned +✅ Template has been cleaned for future use +✅ Files now open without corruption warnings +✅ No data or functionality lost +✅ Future files will be generated clean + +--- + +*Solution implemented: 2025-09-22* \ No newline at end of file diff --git a/clean_excel_template.py b/clean_excel_template.py new file mode 100755 index 0000000..1884897 --- /dev/null +++ b/clean_excel_template.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +Utility to clean Excel files from SharePoint/OneDrive metadata that causes +cross-platform compatibility issues. +""" +import os +import sys +import openpyxl +from pathlib import Path +import tempfile +import shutil + + +def clean_excel_file(input_path, output_path=None): + """ + Clean an Excel file from SharePoint/OneDrive metadata. + + Args: + input_path (str): Path to the input Excel file + output_path (str): Path for the cleaned file (optional) + + Returns: + bool: True if successful, False otherwise + """ + if not os.path.exists(input_path): + print(f"Error: File not found: {input_path}") + return False + + if output_path is None: + # Create cleaned version with _clean suffix + path = Path(input_path) + output_path = path.parent / f"{path.stem}_clean{path.suffix}" + + try: + print(f"Loading Excel file: {input_path}") + + # Load workbook without VBA to avoid macro issues + wb = openpyxl.load_workbook(input_path, data_only=False, keep_vba=False) + + # Clean metadata + print("Cleaning metadata...") + + # Clear custom document properties + if hasattr(wb, 'custom_doc_props') and wb.custom_doc_props: + wb.custom_doc_props.props.clear() + print(" ✓ Cleared custom document properties") + + # Clear custom XML + if hasattr(wb, 'custom_xml'): + wb.custom_xml = [] + print(" ✓ Cleared custom XML") + + # Clean core properties + if wb.properties: + # Keep only essential properties + wb.properties.creator = "Excel Generator" + wb.properties.lastModifiedBy = "Excel Generator" + wb.properties.keywords = "" + wb.properties.category = "" + wb.properties.contentStatus = "" + wb.properties.subject = "" + wb.properties.description = "" + print(" ✓ Cleaned core properties") + + # Create temporary file for double-save cleaning + with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp: + tmp_path = tmp.name + + print("Saving cleaned file...") + + # First save to temp file + wb.save(tmp_path) + wb.close() + + # Re-open and save again to ensure clean structure + print("Re-processing for maximum cleanliness...") + wb_clean = openpyxl.load_workbook(tmp_path, data_only=False) + + # Additional cleaning on the re-opened file + if hasattr(wb_clean, 'custom_doc_props') and wb_clean.custom_doc_props: + wb_clean.custom_doc_props.props.clear() + + if hasattr(wb_clean, 'custom_xml'): + wb_clean.custom_xml = [] + + # Save final clean version + wb_clean.save(output_path) + wb_clean.close() + + # Clean up temporary file + os.unlink(tmp_path) + + print(f"✓ Cleaned Excel file saved to: {output_path}") + + # Compare file sizes + input_size = os.path.getsize(input_path) + output_size = os.path.getsize(output_path) + + print(f"File size: {input_size:,} → {output_size:,} bytes") + if input_size > output_size: + print(f"Reduced by {input_size - output_size:,} bytes ({((input_size - output_size) / input_size * 100):.1f}%)") + + return True + + except Exception as e: + print(f"Error cleaning Excel file: {e}") + import traceback + traceback.print_exc() + return False + + +def clean_template(): + """ + Clean the template file in the template directory. + """ + script_dir = os.path.dirname(os.path.abspath(__file__)) + template_dir = os.path.join(script_dir, 'template') + + # Look for template files + possible_templates = [ + 'Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx', + 'Footprints AI for store_name - Retail Media Business Case Calculations.xlsx' + ] + + template_path = None + for template_name in possible_templates: + full_path = os.path.join(template_dir, template_name) + if os.path.exists(full_path): + template_path = full_path + print(f"Found template: {template_name}") + break + + if not template_path: + print(f"Error: No template found in {template_dir}") + return False + + # Create cleaned template + cleaned_path = os.path.join(template_dir, "cleaned_template.xlsx") + + return clean_excel_file(template_path, cleaned_path) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + # Clean specific file + input_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else None + + if clean_excel_file(input_file, output_file): + print("✓ File cleaned successfully") + else: + print("✗ Failed to clean file") + sys.exit(1) + else: + # Clean template + if clean_template(): + print("✓ Template cleaned successfully") + else: + print("✗ Failed to clean template") + sys.exit(1) \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..bb85b32 --- /dev/null +++ b/config.json @@ -0,0 +1,69 @@ +{ + "user_data": { + "first_name": "Denisa", + "last_name": "Cirsteas", + "company_name": "footprints", + "email": "test@test.ro", + "phone": "1231231231", + "store_name": "TEST", + "country": "Romania", + "starting_date": "2026-01-01", + "duration": 36, + "store_types": [ + "Convenience", + "Supermarket" + ], + "open_days_per_month": 30, + "convenience_store_type": { + "stores_number": 4000, + "monthly_transactions": 40404040, + "has_digital_screens": true, + "screen_count": 2, + "screen_percentage": 100, + "has_in_store_radio": true, + "radio_percentage": 100, + "open_days_per_month": 30 + }, + "supermarket_store_type": { + "stores_number": 200, + "monthly_transactions": 20202020, + "has_digital_screens": true, + "screen_count": 4, + "screen_percentage": 100, + "has_in_store_radio": true, + "radio_percentage": 100, + "open_days_per_month": 30 + }, + "hypermarket_store_type": { + "stores_number": 0, + "monthly_transactions": 0, + "has_digital_screens": false, + "screen_count": 0, + "screen_percentage": 0, + "has_in_store_radio": false, + "radio_percentage": 0, + "open_days_per_month": 30 + }, + "on_site_channels": [ + "Website" + ], + "website_visitors": 1001001, + "app_users": 0, + "loyalty_users": 0, + "off_site_channels": [ + "Email" + ], + "facebook_followers": 0, + "instagram_followers": 0, + "google_views": 0, + "email_subscribers": 100000, + "sms_users": 0, + "whatsapp_contacts": 0, + "potential_reach_in_store": 0, + "unique_impressions_in_store": 0, + "potential_reach_on_site": 0, + "unique_impressions_on_site": 0, + "potential_reach_off_site": 0, + "unique_impressions_off_site": 0 + } +} \ No newline at end of file diff --git a/create_excel.py b/create_excel.py new file mode 100644 index 0000000..432a2da --- /dev/null +++ b/create_excel.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +import json +import os +import shutil +import datetime +import re +from pathlib import Path +from dateutil.relativedelta import relativedelta +from update_excel import update_excel_variables + +def create_excel_from_template(): + """ + Create a copy of the Excel template and save it to the output folder, + then inject variables from config.json into the Variables sheet. + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + # Look for any Excel template in the template directory + template_dir = os.path.join(script_dir, 'template') + template_files = [f for f in os.listdir(template_dir) if f.endswith('.xlsx')] + if not template_files: + print("Error: No Excel template found in the template directory") + return False + template_path = os.path.join(template_dir, template_files[0]) + output_dir = os.path.join(script_dir, 'output') + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Read config.json to get store_name, starting_date, and duration + try: + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + store_name = user_data.get('store_name', '') + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + + # If store_name is empty, use a default value + if not store_name: + store_name = "Your Store" + + # Calculate years array based on starting_date and duration + years = calculate_years(starting_date, duration) + print(f"Years in the period: {years}") + except Exception as e: + print(f"Error reading config file: {e}") + return False + + # Use first and last years from the array in the filename + year_range = "" + if years and len(years) > 0: + if len(years) == 1: + year_range = f"{years[0]}" + else: + year_range = f"{years[0]}-{years[-1]}" + else: + # Fallback to current year if years array is empty + current_year = datetime.datetime.now().year + year_range = f"{current_year}" + + # Create output filename with store_name and year range + output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" + output_path = os.path.join(output_dir, output_filename) + + # Copy the template to the output directory with the new name + try: + shutil.copy2(template_path, output_path) + print(f"Excel file created successfully: {output_path}") + + # Update the Excel file with variables from config.json + print("Updating Excel file with variables from config.json...") + update_result = update_excel_variables(output_path) + + if update_result: + print("Excel file updated successfully with variables from config.json") + else: + print("Warning: Failed to update Excel file with variables from config.json") + + return True + except Exception as e: + print(f"Error creating Excel file: {e}") + return False + +def calculate_years(starting_date, duration): + """ + Calculate an array of years that appear in the period from starting_date for duration months. + + Args: + starting_date (str): Date in format dd/mm/yyyy or dd.mm.yyyy + duration (int): Number of months, including the starting month + + Returns: + list: Array of years in the period [year1, year2, ...] + """ + # Default result if we can't parse the date + default_years = [datetime.datetime.now().year] + + # If starting_date is empty, return current year + if not starting_date: + return default_years + + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if '/' in starting_date: + day, month, year = map(int, starting_date.split('/')) + elif '.' in starting_date: + day, month, year = map(int, starting_date.split('.')) + elif '-' in starting_date: + # Handle ISO format (yyyy-mm-dd) + date_parts = starting_date.split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + return default_years + else: + # If format is not recognized, return default + return default_years + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + return sorted(list(years_set)) + + except Exception as e: + print(f"Error calculating years: {e}") + return default_years + +if __name__ == "__main__": + create_excel_from_template() \ No newline at end of file diff --git a/create_excel_clean.py b/create_excel_clean.py new file mode 100755 index 0000000..fc31560 --- /dev/null +++ b/create_excel_clean.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +""" +Cross-platform Excel generation script using openpyxl. +This version ensures clean Excel files without SharePoint/OneDrive metadata. +""" +import json +import os +import datetime +from pathlib import Path +from dateutil.relativedelta import relativedelta +import openpyxl +from openpyxl.workbook import Workbook +from openpyxl.utils import get_column_letter +from openpyxl.styles import Font, PatternFill, Alignment, Border, Side +import tempfile +import shutil + + + + +def create_excel_from_template(): + """ + Create an Excel file from template with all placeholders replaced. + Uses openpyxl for maximum cross-platform compatibility. + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + template_dir = os.path.join(script_dir, 'template') + + # Try to find the template with either naming convention + possible_templates = [ + 'cleaned_template.xlsx', # Prefer cleaned template + 'Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx', + 'Footprints AI for store_name - Retail Media Business Case Calculations.xlsx' + ] + + template_path = None + for template_name in possible_templates: + full_path = os.path.join(template_dir, template_name) + if os.path.exists(full_path): + template_path = full_path + print(f"Found template: {template_name}") + break + + if not template_path: + print(f"Error: No template found in {template_dir}") + return False + + output_dir = os.path.join(script_dir, 'output') + os.makedirs(output_dir, exist_ok=True) + + # Read config.json + try: + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + store_name = user_data.get('store_name', 'Your Store') + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + + if not store_name: + store_name = "Your Store" + + print(f"Processing for store: {store_name}") + + # Calculate years array + years = calculate_years(starting_date, duration) + calculated_years = years + print(f"Years in the period: {years}") + except Exception as e: + print(f"Error reading config file: {e}") + return False + + # Determine year range for filename + year_range = "" + if years and len(years) > 0: + if len(years) == 1: + year_range = f"{years[0]}" + else: + year_range = f"{years[0]}-{years[-1]}" + else: + year_range = f"{datetime.datetime.now().year}" + + # Create output filename + output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" + output_path = os.path.join(output_dir, output_filename) + + try: + # Load template with data_only=False to preserve formulas + print("Loading template...") + wb = openpyxl.load_workbook(template_path, data_only=False, keep_vba=False) + + + # Build mapping of placeholder patterns to actual values + placeholder_patterns = [ + ('{store_name}', store_name), + ('store_name', store_name) + ] + + # Step 1: Create sheet name mappings + print("Processing sheet names...") + sheet_name_mappings = {} + sheets_to_rename = [] + + for sheet in wb.worksheets: + old_title = sheet.title + new_title = old_title + + for placeholder, replacement in placeholder_patterns: + if placeholder in new_title: + new_title = new_title.replace(placeholder, replacement) + + if old_title != new_title: + sheet_name_mappings[old_title] = new_title + sheet_name_mappings[f"'{old_title}'"] = f"'{new_title}'" + sheets_to_rename.append((sheet, new_title)) + print(f" Will rename: '{old_title}' -> '{new_title}'") + + # Step 2: Update all formulas and values + print("Updating formulas and cell values...") + total_updates = 0 + + for sheet in wb.worksheets: + if 'Variables' in sheet.title: + continue + + updates_in_sheet = 0 + for row in sheet.iter_rows(): + for cell in row: + try: + # Handle formulas + if hasattr(cell, '_value') and isinstance(cell._value, str) and cell._value.startswith('='): + original = cell._value + updated = original + + # Update sheet references + for old_ref, new_ref in sheet_name_mappings.items(): + updated = updated.replace(old_ref, new_ref) + + # Update placeholders + for placeholder, replacement in placeholder_patterns: + updated = updated.replace(placeholder, replacement) + + if updated != original: + cell._value = updated + updates_in_sheet += 1 + + # Handle regular text values + elif cell.value and isinstance(cell.value, str): + original = cell.value + updated = original + + for placeholder, replacement in placeholder_patterns: + updated = updated.replace(placeholder, replacement) + + if updated != original: + cell.value = updated + updates_in_sheet += 1 + except Exception as e: + # Skip cells that cause issues + continue + + if updates_in_sheet > 0: + print(f" {sheet.title}: {updates_in_sheet} updates") + total_updates += updates_in_sheet + + print(f"Total updates: {total_updates}") + + # Step 3: Rename sheets + print("Renaming sheets...") + for sheet, new_title in sheets_to_rename: + old_title = sheet.title + sheet.title = new_title + print(f" Renamed: '{old_title}' -> '{new_title}'") + + # Hide forecast sheets not in calculated years + if "Forecast" in new_title: + try: + sheet_year = int(new_title.split()[0]) + if sheet_year not in calculated_years: + sheet.sheet_state = 'hidden' + print(f" Hidden sheet '{new_title}' (year {sheet_year} not in range)") + except (ValueError, IndexError): + pass + + # Step 4: Update Variables sheet + print("Updating Variables sheet...") + if 'Variables' in wb.sheetnames: + update_variables_sheet(wb['Variables'], user_data) + + # Step 5: Save as a clean Excel file + print(f"Saving clean Excel file to: {output_path}") + + # Create a temporary file first + with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp: + tmp_path = tmp.name + + # Save to temporary file + wb.save(tmp_path) + + # Re-open and save again to ensure clean structure + wb_clean = openpyxl.load_workbook(tmp_path, data_only=False) + wb_clean.save(output_path) + wb_clean.close() + + # Clean up temporary file + os.unlink(tmp_path) + + print(f"✓ Excel file created successfully: {output_filename}") + return True + + except Exception as e: + print(f"Error creating Excel file: {e}") + import traceback + traceback.print_exc() + return False + + +def update_variables_sheet(sheet, user_data): + """ + Update the Variables sheet with values from config.json + """ + cell_mappings = { + 'B2': user_data.get('store_name', ''), + 'B31': user_data.get('starting_date', ''), + 'B32': user_data.get('duration', 36), + 'B37': user_data.get('open_days_per_month', 0), + + # Store types + 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), + 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), + 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, + 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), + 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), + 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, + 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), + + 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), + 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), + 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), + 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), + 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), + + 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), + 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), + 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), + 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), + 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), + + 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), + 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), + 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), + 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), + 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), + + # Channels + 'B43': user_data.get('website_visitors', 0), + 'B44': user_data.get('app_users', 0), + 'B45': user_data.get('loyalty_users', 0), + 'B49': user_data.get('facebook_followers', 0), + 'B50': user_data.get('instagram_followers', 0), + 'B51': user_data.get('google_views', 0), + 'B52': user_data.get('email_subscribers', 0), + 'B53': user_data.get('sms_users', 0), + 'B54': user_data.get('whatsapp_contacts', 0) + } + + for cell_ref, value in cell_mappings.items(): + try: + sheet[cell_ref].value = value + print(f" Updated {cell_ref} = {value}") + except Exception as e: + print(f" Warning: Could not update {cell_ref}: {e}") + + +def calculate_years(starting_date, duration): + """ + Calculate an array of years that appear in the period. + """ + default_years = [datetime.datetime.now().year] + + if not starting_date: + return default_years + + try: + # Parse date - support multiple formats + if '/' in str(starting_date): + day, month, year = map(int, str(starting_date).split('/')) + elif '.' in str(starting_date): + day, month, year = map(int, str(starting_date).split('.')) + elif '-' in str(starting_date): + # ISO format (yyyy-mm-dd) + date_parts = str(starting_date).split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + return default_years + else: + return default_years + + start_date = datetime.datetime(year, month, day) + end_date = start_date + relativedelta(months=duration-1) + + years_set = set() + years_set.add(start_date.year) + years_set.add(end_date.year) + + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + return sorted(list(years_set)) + + except Exception as e: + print(f"Error calculating years: {e}") + return default_years + + +if __name__ == "__main__": + create_excel_from_template() \ No newline at end of file diff --git a/create_excel_openpyxl.py b/create_excel_openpyxl.py new file mode 100644 index 0000000..d0719a6 --- /dev/null +++ b/create_excel_openpyxl.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +import json +import os +import shutil +import datetime +import re +from pathlib import Path +from dateutil.relativedelta import relativedelta +from update_excel import update_excel_variables + +def create_excel_from_template(): + """ + Create a copy of the Excel template and save it to the output folder, + then inject variables from config.json into the Variables sheet. + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + # Look for any Excel template in the template directory + template_dir = os.path.join(script_dir, 'template') + template_files = [f for f in os.listdir(template_dir) if f.endswith('.xlsx')] + if not template_files: + print("Error: No Excel template found in the template directory") + return False + template_path = os.path.join(template_dir, template_files[0]) + output_dir = os.path.join(script_dir, 'output') + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Read config.json to get store_name, starting_date, and duration + try: + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + store_name = user_data.get('store_name', '') + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + + # If store_name is empty, use a default value + if not store_name: + store_name = "Your Store" + + # Calculate years array based on starting_date and duration + years = calculate_years(starting_date, duration) + print(f"Years in the period: {years}") + except Exception as e: + print(f"Error reading config file: {e}") + return False + + # Use first and last years from the array in the filename + year_range = "" + if years and len(years) > 0: + if len(years) == 1: + year_range = f"{years[0]}" + else: + year_range = f"{years[0]}-{years[-1]}" + else: + # Fallback to current year if years array is empty + current_year = datetime.datetime.now().year + year_range = f"{current_year}" + + # Create output filename with store_name and year range + output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" + output_path = os.path.join(output_dir, output_filename) + + # Copy the template to the output directory with the new name + try: + shutil.copy2(template_path, output_path) + print(f"Excel file created successfully: {output_path}") + + # Update the Excel file with variables from config.json + print("Updating Excel file with variables from config.json...") + update_result = update_excel_variables(output_path) + + if update_result: + print("Excel file updated successfully with variables from config.json") + else: + print("Warning: Failed to update Excel file with variables from config.json") + + return True + except Exception as e: + print(f"Error creating Excel file: {e}") + return False + +def calculate_years(starting_date, duration): + """ + Calculate an array of years that appear in the period from starting_date for duration months. + + Args: + starting_date (str): Date in format dd/mm/yyyy or dd.mm.yyyy + duration (int): Number of months, including the starting month + + Returns: + list: Array of years in the period [year1, year2, ...] + """ + # Default result if we can't parse the date + default_years = [datetime.datetime.now().year] + + # If starting_date is empty, return current year + if not starting_date: + return default_years + + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if '/' in starting_date: + day, month, year = map(int, starting_date.split('/')) + elif '.' in starting_date: + day, month, year = map(int, starting_date.split('.')) + elif '-' in starting_date: + # Handle ISO format (yyyy-mm-dd) + date_parts = starting_date.split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + return default_years + else: + # If format is not recognized, return default + return default_years + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + return sorted(list(years_set)) + + except Exception as e: + print(f"Error calculating years: {e}") + return default_years + +if __name__ == "__main__": + create_excel_from_template() diff --git a/create_excel_v2.py b/create_excel_v2.py new file mode 100644 index 0000000..21f5f5b --- /dev/null +++ b/create_excel_v2.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +""" +Improved Excel creation script that processes templates in memory +to prevent external link issues in Excel. +""" +import json +import os +import datetime +from pathlib import Path +from dateutil.relativedelta import relativedelta +import openpyxl +from openpyxl.utils import get_column_letter + + +def create_excel_from_template(): + """ + Create an Excel file from template with all placeholders replaced in memory + before saving to prevent external link issues. + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + # Check for both possible template names + template_dir = os.path.join(script_dir, 'template') + + # Try to find the template with either naming convention + possible_templates = [ + 'Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx', + 'Footprints AI for store_name - Retail Media Business Case Calculations.xlsx' + ] + + template_path = None + for template_name in possible_templates: + full_path = os.path.join(template_dir, template_name) + if os.path.exists(full_path): + template_path = full_path + print(f"Found template: {template_name}") + break + + if not template_path: + print(f"Error: No template found in {template_dir}") + return False + + output_dir = os.path.join(script_dir, 'output') + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Read config.json + try: + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + store_name = user_data.get('store_name', 'Your Store') + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + + if not store_name: + store_name = "Your Store" + + print(f"Processing for store: {store_name}") + + # Calculate years array + years = calculate_years(starting_date, duration) + calculated_years = years # For sheet visibility later + print(f"Years in the period: {years}") + except Exception as e: + print(f"Error reading config file: {e}") + return False + + # Determine year range for filename + year_range = "" + if years and len(years) > 0: + if len(years) == 1: + year_range = f"{years[0]}" + else: + year_range = f"{years[0]}-{years[-1]}" + else: + year_range = f"{datetime.datetime.now().year}" + + # Create output filename + output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" + output_path = os.path.join(output_dir, output_filename) + + try: + # STAGE 1: Load template and replace all placeholders in memory + print("Loading template in memory...") + wb = openpyxl.load_workbook(template_path, data_only=False) + + # Build mapping of placeholder patterns to actual values + # Support both {store_name} and store_name formats + placeholder_patterns = [ + ('{store_name}', store_name), + ('store_name', store_name) # New format without curly braces + ] + + # STAGE 2: Replace placeholders in sheet names first + print("Replacing placeholders in sheet names...") + sheet_name_mappings = {} + + for sheet in wb.worksheets: + old_title = sheet.title + new_title = old_title + + # Replace all placeholder patterns in sheet name + for placeholder, replacement in placeholder_patterns: + if placeholder in new_title: + new_title = new_title.replace(placeholder, replacement) + print(f" Sheet name: '{old_title}' -> '{new_title}'") + + if old_title != new_title: + # Store the mapping for formula updates + sheet_name_mappings[old_title] = new_title + # Also store with quotes for formula references + sheet_name_mappings[f"'{old_title}'"] = f"'{new_title}'" + + # STAGE 3: Update all formulas and cell values BEFORE renaming sheets + print("Updating formulas and cell values...") + total_replacements = 0 + + for sheet in wb.worksheets: + sheet_name = sheet.title + replacements_in_sheet = 0 + + # Skip Variables sheet to avoid issues + if 'Variables' in sheet_name: + continue + + for row in sheet.iter_rows(): + for cell in row: + # Handle formulas + if cell.data_type == 'f' and cell.value: + original_formula = str(cell.value) + new_formula = original_formula + + # First replace sheet references + for old_ref, new_ref in sheet_name_mappings.items(): + if old_ref in new_formula: + new_formula = new_formula.replace(old_ref, new_ref) + + # Then replace any remaining placeholders + for placeholder, replacement in placeholder_patterns: + if placeholder in new_formula: + new_formula = new_formula.replace(placeholder, replacement) + + if new_formula != original_formula: + cell.value = new_formula + replacements_in_sheet += 1 + + # Handle text values + elif cell.value and isinstance(cell.value, str): + original_value = str(cell.value) + new_value = original_value + + for placeholder, replacement in placeholder_patterns: + if placeholder in new_value: + new_value = new_value.replace(placeholder, replacement) + + if new_value != original_value: + cell.value = new_value + replacements_in_sheet += 1 + + if replacements_in_sheet > 0: + print(f" {sheet_name}: {replacements_in_sheet} replacements") + total_replacements += replacements_in_sheet + + print(f"Total replacements: {total_replacements}") + + # STAGE 4: Now rename the sheets (after formulas are updated) + print("Renaming sheets...") + for sheet in wb.worksheets: + old_title = sheet.title + new_title = old_title + + for placeholder, replacement in placeholder_patterns: + if placeholder in new_title: + new_title = new_title.replace(placeholder, replacement) + + if old_title != new_title: + sheet.title = new_title + print(f" Renamed: '{old_title}' -> '{new_title}'") + + # Check if this is a forecast sheet and hide if needed + if "Forecast" in new_title: + try: + # Extract year from sheet name + sheet_year = int(new_title.split()[0]) + if sheet_year not in calculated_years: + sheet.sheet_state = 'hidden' + print(f" Hidden sheet '{new_title}' (year {sheet_year} not in range)") + except (ValueError, IndexError): + pass + + # STAGE 5: Update Variables sheet with config values + print("Updating Variables sheet...") + if 'Variables' in wb.sheetnames: + update_variables_sheet(wb['Variables'], user_data) + + # STAGE 6: Save the fully processed workbook + print(f"Saving to: {output_path}") + wb.save(output_path) + + print(f"✓ Excel file created successfully: {output_filename}") + return True + + except Exception as e: + print(f"Error creating Excel file: {e}") + import traceback + traceback.print_exc() + return False + + +def update_variables_sheet(sheet, user_data): + """ + Update the Variables sheet with values from config.json + """ + # Map config variables to Excel cells + cell_mappings = { + 'B2': user_data.get('store_name', ''), + 'B31': user_data.get('starting_date', ''), + 'B32': user_data.get('duration', 36), + 'B37': user_data.get('open_days_per_month', 0), + + # Convenience store type + 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), + 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), + 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, + 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), + 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), + 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, + 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), + + # Minimarket store type + 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), + 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), + 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), + 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), + 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), + + # Supermarket store type + 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), + 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), + 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), + 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), + 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), + + # Hypermarket store type + 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), + 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), + 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), + 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), + 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), + + # On-site channels + 'B43': user_data.get('website_visitors', 0), + 'B44': user_data.get('app_users', 0), + 'B45': user_data.get('loyalty_users', 0), + + # Off-site channels + 'B49': user_data.get('facebook_followers', 0), + 'B50': user_data.get('instagram_followers', 0), + 'B51': user_data.get('google_views', 0), + 'B52': user_data.get('email_subscribers', 0), + 'B53': user_data.get('sms_users', 0), + 'B54': user_data.get('whatsapp_contacts', 0) + } + + # Update the cells + for cell_ref, value in cell_mappings.items(): + try: + sheet[cell_ref].value = value + print(f" Updated {cell_ref} = {value}") + except Exception as e: + print(f" Warning: Could not update {cell_ref}: {e}") + + +def calculate_years(starting_date, duration): + """ + Calculate an array of years that appear in the period. + """ + default_years = [datetime.datetime.now().year] + + if not starting_date: + return default_years + + try: + # Parse date - support multiple formats + if '/' in str(starting_date): + day, month, year = map(int, str(starting_date).split('/')) + elif '.' in str(starting_date): + day, month, year = map(int, str(starting_date).split('.')) + elif '-' in str(starting_date): + # ISO format (yyyy-mm-dd) + date_parts = str(starting_date).split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + return default_years + else: + return default_years + + # Create datetime object + start_date = datetime.datetime(year, month, day) + + # Calculate end date + end_date = start_date + relativedelta(months=duration-1) + + # Create set of years + years_set = set() + years_set.add(start_date.year) + years_set.add(end_date.year) + + # Add any years in between + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + return sorted(list(years_set)) + + except Exception as e: + print(f"Error calculating years: {e}") + return default_years + + +if __name__ == "__main__": + create_excel_from_template() \ No newline at end of file diff --git a/create_excel_xlsxwriter.py b/create_excel_xlsxwriter.py new file mode 100644 index 0000000..c156e2d --- /dev/null +++ b/create_excel_xlsxwriter.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +import json +import os +import shutil +import datetime +import re +from pathlib import Path +from dateutil.relativedelta import relativedelta +from update_excel_xlsxwriter import update_excel_variables + +def create_excel_from_template(): + """ + Create a copy of the Excel template and save it to the output folder, + then inject variables from config.json into the Variables sheet. + + This version uses openpyxl exclusively for modifying existing Excel files + to preserve all formatting, formulas, and Excel features. + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + # Look for any Excel template in the template directory + template_dir = os.path.join(script_dir, 'template') + template_files = [f for f in os.listdir(template_dir) if f.endswith('.xlsx')] + if not template_files: + print("Error: No Excel template found in the template directory") + return False + template_path = os.path.join(template_dir, template_files[0]) + output_dir = os.path.join(script_dir, 'output') + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Read config.json to get store_name, starting_date, and duration + try: + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + store_name = user_data.get('store_name', '') + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + + # If store_name is empty, use a default value + if not store_name: + store_name = "Your Store" + + # Calculate years array based on starting_date and duration + years = calculate_years(starting_date, duration) + print(f"Years in the period: {years}") + except Exception as e: + print(f"Error reading config file: {e}") + return False + + # Use first and last years from the array in the filename + year_range = "" + if years and len(years) > 0: + if len(years) == 1: + year_range = f"{years[0]}" + else: + year_range = f"{years[0]}-{years[-1]}" + else: + # Fallback to current year if years array is empty + current_year = datetime.datetime.now().year + year_range = f"{current_year}" + + # Create output filename with store_name and year range + output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" + output_path = os.path.join(output_dir, output_filename) + + # Copy the template to the output directory with the new name + try: + shutil.copy2(template_path, output_path) + print(f"Excel file created successfully: {output_path}") + + # Update the Excel file with variables from config.json + print("Updating Excel file with variables from config.json...") + update_result = update_excel_variables(output_path) + + if update_result: + print("Excel file updated successfully with variables from config.json") + else: + print("Warning: Failed to update Excel file with variables from config.json") + + return True + except Exception as e: + print(f"Error creating Excel file: {e}") + return False + +def calculate_years(starting_date, duration): + """ + Calculate an array of years that appear in the period from starting_date for duration months. + + Args: + starting_date (str): Date in format dd/mm/yyyy or dd.mm.yyyy + duration (int): Number of months, including the starting month + + Returns: + list: Array of years in the period [year1, year2, ...] + """ + # Default result if we can't parse the date + default_years = [datetime.datetime.now().year] + + # If starting_date is empty, return current year + if not starting_date: + return default_years + + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if '/' in starting_date: + day, month, year = map(int, starting_date.split('/')) + elif '.' in starting_date: + day, month, year = map(int, starting_date.split('.')) + elif '-' in starting_date: + # Handle ISO format (yyyy-mm-dd) + date_parts = starting_date.split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + return default_years + else: + # If format is not recognized, return default + return default_years + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + return sorted(list(years_set)) + + except Exception as e: + print(f"Error calculating years: {e}") + return default_years + +if __name__ == "__main__": + create_excel_from_template() \ No newline at end of file diff --git a/diagnose_excel_issue.py b/diagnose_excel_issue.py new file mode 100644 index 0000000..9dcdc04 --- /dev/null +++ b/diagnose_excel_issue.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +import os +import zipfile +import xml.etree.ElementTree as ET +import openpyxl +from openpyxl.xml.functions import fromstring, tostring +from pathlib import Path + +def diagnose_excel_file(file_path): + """Diagnose Excel file for corruption issues""" + print(f"Diagnosing: {file_path}") + print("=" * 50) + + # 1. Check if file exists + if not os.path.exists(file_path): + print(f"ERROR: File not found: {file_path}") + return + + # 2. Try to open with openpyxl + print("\n1. Testing openpyxl compatibility:") + try: + wb = openpyxl.load_workbook(file_path, read_only=False, keep_vba=True, data_only=False) + print(f" ✓ Successfully loaded with openpyxl") + print(f" - Sheets: {wb.sheetnames}") + + # Check for custom properties + if hasattr(wb, 'custom_doc_props'): + print(f" - Custom properties: {wb.custom_doc_props}") + + wb.close() + except Exception as e: + print(f" ✗ Failed to load with openpyxl: {e}") + + # 3. Analyze ZIP structure + print("\n2. Analyzing ZIP/XML structure:") + try: + with zipfile.ZipFile(file_path, 'r') as zf: + # Check for custom XML + custom_xml_files = [f for f in zf.namelist() if 'customXml' in f or 'custom' in f.lower()] + if custom_xml_files: + print(f" ! Found custom XML files: {custom_xml_files}") + + for custom_file in custom_xml_files: + try: + content = zf.read(custom_file) + print(f"\n Content of {custom_file}:") + print(f" {content[:500].decode('utf-8', errors='ignore')}") + except Exception as e: + print(f" Error reading {custom_file}: {e}") + + # Check for tables + table_files = [f for f in zf.namelist() if 'xl/tables/' in f] + if table_files: + print(f" - Found table files: {table_files}") + for table_file in table_files: + content = zf.read(table_file) + # Check if XML declaration is present + if not content.startswith(b'', + 'namespaces': { + 'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + 'mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006', + 'xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision', + 'xr3': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision3' + }, + 'compatibility': 'mc:Ignorable="xr xr3"', + 'uid_pattern': '{00000000-000C-0000-FFFF-FFFF{:02d}000000}' + } + } + return template_tables +``` + +#### Step 2: XML Generation Functions +```python +def generate_proper_table_xml(table_data, table_id): + """Generate Excel-compliant table XML with proper format""" + + # XML Declaration + xml_content = '\n' + + # Table element with all namespaces + xml_content += f'\n' + + # Table columns with UIDs + xml_content += generate_table_columns_xml(table_data.columns, table_id) + + # Table style info + xml_content += generate_table_style_xml(table_data.style) + + xml_content += '
' + + return xml_content + +def generate_table_uid(table_id): + """Generate proper UIDs for tables""" + return f"{{00000000-000C-0000-FFFF-FFFF{table_id:02d}000000}}" + +def generate_column_uid(table_id, column_id): + """Generate proper UIDs for table columns""" + return f"{{00000000-0010-0000-{table_id:04d}-{column_id:06d}000000}}" +``` + +#### Step 3: File Assembly Improvements +```python +def create_excel_file_with_proper_compression(): + """Create Excel file with consistent ZIP compression""" + + # Use consistent compression settings + with zipfile.ZipFile(output_path, 'w', + compression=zipfile.ZIP_DEFLATED, + compresslevel=6, # Consistent compression level + allowZip64=False) as zipf: + + # Set consistent file timestamps + fixed_time = (2023, 1, 1, 0, 0, 0) + + for file_path, content in excel_files.items(): + zinfo = zipfile.ZipInfo(file_path) + zinfo.date_time = fixed_time + zinfo.compress_type = zipfile.ZIP_DEFLATED + + zipf.writestr(zinfo, content) +``` + +### Phase 2: Testing and Validation + +#### Cross-Platform Testing Matrix +| Platform | Python Version | Library Versions | Test Status | +|----------|---------------|-----------------|-------------| +| Ubuntu 22.04 | 3.10+ | openpyxl==3.x | ⏳ Pending | +| macOS | 3.10+ | openpyxl==3.x | ✅ Working | +| Windows | 3.10+ | openpyxl==3.x | ⏳ TBD | + +#### Validation Script +```python +def validate_excel_file(file_path): + """Validate generated Excel file for repair issues""" + + checks = { + 'table_xml_format': check_table_xml_declarations, + 'namespace_compliance': check_namespace_declarations, + 'uid_presence': check_unique_identifiers, + 'zip_metadata': check_zip_file_metadata, + 'excel_compatibility': test_excel_opening + } + + results = {} + for check_name, check_func in checks.items(): + results[check_name] = check_func(file_path) + + return results +``` + +### Phase 3: Long-term Improvements + +#### Migration to openpyxl +```python +# Example migration approach +from openpyxl import Workbook +from openpyxl.worksheet.table import Table, TableStyleInfo + +def create_excel_with_openpyxl(business_case_data): + """Generate Excel using openpyxl for cross-platform compatibility""" + + wb = Workbook() + ws = wb.active + + # Add data + for row in business_case_data: + ws.append(row) + + # Create table with proper formatting + table = Table(displayName="BusinessCaseTable", ref="A1:H47") + style = TableStyleInfo(name="TableStyleMedium3", + showFirstColumn=False, + showLastColumn=False, + showRowStripes=True, + showColumnStripes=False) + table.tableStyleInfo = style + + ws.add_table(table) + + # Save with consistent settings + wb.save(output_path) +``` + +## Implementation Checklist + +### Immediate Actions (Week 1) +- [ ] Extract XML patterns from working template +- [ ] Implement proper XML declaration generation +- [ ] Add namespace declarations and compatibility directives +- [ ] Implement UID generation algorithms +- [ ] Fix table ID sequencing logic +- [ ] Test on Ubuntu environment + +### Validation Actions (Week 2) +- [ ] Create comprehensive test suite +- [ ] Validate across multiple platforms +- [ ] Performance testing with large datasets +- [ ] Excel compatibility testing (different versions) +- [ ] Automated repair detection + +### Future Improvements (Month 2) +- [ ] Migration to openpyxl library +- [ ] Docker containerization for consistent environment +- [ ] CI/CD pipeline with cross-platform testing +- [ ] Comprehensive documentation updates + +## Risk Assessment + +### High Priority Risks +- **Platform dependency**: Current solution may not work on Windows +- **Excel version compatibility**: Different Excel versions may have different validation +- **Performance impact**: Proper XML generation may be slower + +### Mitigation Strategies +- **Comprehensive testing**: Test on all target platforms before deployment +- **Fallback mechanism**: Keep current generation as backup +- **Performance optimization**: Profile and optimize XML generation code + +## Success Metrics + +### Primary Goals +- ✅ Zero Excel repair dialogs on Ubuntu-generated files +- ✅ Identical behavior across macOS and Ubuntu +- ✅ No data loss or functionality regression + +### Secondary Goals +- ✅ Improved file generation performance +- ✅ Better code maintainability +- ✅ Enhanced error handling and logging + +## Conclusion + +The recommended solution addresses the root cause by implementing proper Excel XML format generation while maintaining cross-platform compatibility. The template-based approach provides immediate relief while the library migration offers long-term stability. + +**Next Steps**: Begin with Phase 1 implementation focusing on proper XML generation, followed by comprehensive testing across platforms. + +--- + +*Proposal created: 2025-09-19* +*Estimated implementation time: 2-3 weeks* +*Priority: High - affects production workflows* \ No newline at end of file diff --git a/excel_table_repair_analysis.md b/excel_table_repair_analysis.md new file mode 100644 index 0000000..7dca8ff --- /dev/null +++ b/excel_table_repair_analysis.md @@ -0,0 +1,117 @@ +# Excel Table Repair Error Analysis + +## Issue Summary +When opening Ubuntu-generated Excel files, Excel displays repair errors specifically for tables: +- **Repaired Records: Table from /xl/tables/table1.xml part (Table)** +- **Repaired Records: Table from /xl/tables/table2.xml part (Table)** + +**CRITICAL FINDING**: The same script generates working files on macOS but broken files on Ubuntu, indicating a **platform-specific issue** rather than a general Excel format problem. + +## Investigation Findings + +### Three-Way Table Structure Comparison + +#### Template File (Original - Working) +- Contains proper XML declaration: `` +- Includes comprehensive namespace declarations: + - `xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"` + - `xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision"` + - `xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"` +- Has `mc:Ignorable="xr xr3"` compatibility directive +- Contains unique identifiers (`xr:uid`, `xr3:uid`) for tables and columns +- Proper table ID sequence (table1 has id="2", table2 has id="3") + +#### macOS Generated File (Working - No Repair Errors) +- **Missing XML declaration** - no `` +- **Missing namespace declarations** for revision extensions +- **No compatibility directives** (`mc:Ignorable`) +- **Missing unique identifiers** for tables and columns +- **Different table ID sequence** (table1 has id="1", table2 has id="2") +- **File sizes: 1,032 bytes (table1), 1,121 bytes (table2)** + +#### Ubuntu Generated File (Broken - Requires Repair) +- **Missing XML declaration** - no `` +- **Missing namespace declarations** for revision extensions +- **No compatibility directives** (`mc:Ignorable`) +- **Missing unique identifiers** for tables and columns +- **Same table ID sequence as macOS** (table1 has id="1", table2 has id="2") +- **Identical file sizes to macOS: 1,032 bytes (table1), 1,121 bytes (table2)** + +### Key Discovery: XML Content is Identical + +**SHOCKING REVELATION**: The table XML content between macOS and Ubuntu generated files is **byte-for-byte identical**. Both have: + +1. **Missing XML declarations** +2. **Missing namespace extensions** +3. **Missing unique identifiers** +4. **Same table ID sequence** (1, 2) +5. **Identical file sizes** + +**macOS table1.xml vs Ubuntu table1.xml:** +```xml +... +``` +*(Completely identical)* + +### Root Cause Analysis - Platform Dependency + +Since the table XML is identical but only Ubuntu files require repair, the issue is **NOT in the table XML content**. The problem must be: + +1. **File encoding differences** during ZIP assembly +2. **ZIP compression algorithm differences** between platforms +3. **File timestamp/metadata differences** in the ZIP archive +4. **Different Python library versions** handling ZIP creation differently +5. **Excel's platform-specific validation logic** being more strict on certain systems + +### Common Formula Issues +Both versions contain `#REF!` errors in calculated columns: +```xml +#REF! +``` +This suggests broken cell references but doesn't cause repair errors. + +### Impact Assessment +- **Functionality:** No data loss, tables work after repair +- **User Experience:** Excel shows warning dialog requiring user action **only on Ubuntu-generated files** +- **Automation:** Breaks automated processing workflows **only for Ubuntu deployments** +- **Platform Consistency:** Same code produces different results across platforms + +## Recommendations + +### Platform-Specific Investigation Priorities +1. **Compare Python library versions** between macOS and Ubuntu environments +2. **Check ZIP file metadata** (timestamps, compression levels, file attributes) +3. **Examine file encoding** during Excel assembly process +4. **Test with different Python Excel libraries** (openpyxl vs xlsxwriter vs others) +5. **Analyze ZIP file internals** with hex editors for subtle differences + +### Immediate Workarounds +1. **Document platform dependency** in deployment guides +2. **Test all generated files** on target Excel environment before distribution +3. **Consider generating files on macOS** for production use +4. **Implement automated repair detection** in the workflow + +### Long-term Fixes +1. **Standardize to template format** with proper XML declarations and namespaces +2. **Use established Excel libraries** with proven cross-platform compatibility +3. **Implement comprehensive testing** across multiple platforms +4. **Add ZIP file validation** to detect platform-specific differences + +## Technical Details + +### File Comparison Results +| File | Template | macOS Generated | Ubuntu Generated | Ubuntu vs macOS | +|------|----------|----------------|------------------|-----------------| +| table1.xml | 1,755 bytes | 1,032 bytes | 1,032 bytes | **Identical** | +| table2.xml | 1,844 bytes | 1,121 bytes | 1,121 bytes | **Identical** | + +### Platform Dependency Evidence +- **Identical table XML content** between macOS and Ubuntu +- **Same missing features** (declarations, namespaces, UIDs) +- **Different Excel behavior** (repair required only on Ubuntu) +- **Suggests ZIP-level or metadata differences** + +--- + +*Analysis completed: 2025-09-19* +*Files examined: Template vs Test5 generated Excel workbooks* \ No newline at end of file diff --git a/fix_excel_corruption.py b/fix_excel_corruption.py new file mode 100644 index 0000000..82f98fb --- /dev/null +++ b/fix_excel_corruption.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +""" +Fix Excel corruption issues caused by SharePoint/OneDrive metadata +""" +import os +import shutil +import zipfile +import xml.etree.ElementTree as ET +from pathlib import Path +import tempfile +import openpyxl + +def remove_sharepoint_metadata(excel_path, output_path=None): + """ + Remove SharePoint/OneDrive metadata from Excel file that causes corruption warnings + + Args: + excel_path: Path to the Excel file to fix + output_path: Optional path for the fixed file (if None, overwrites original) + + Returns: + bool: True if successful, False otherwise + """ + if not output_path: + output_path = excel_path + + print(f"Processing: {excel_path}") + + try: + # Method 1: Use openpyxl to remove custom properties + print("Method 1: Using openpyxl to clean custom properties...") + wb = openpyxl.load_workbook(excel_path, keep_vba=True) + + # Remove custom document properties + if hasattr(wb, 'custom_doc_props'): + # Clear all custom properties + wb.custom_doc_props.props.clear() + print(" ✓ Cleared custom document properties") + + # Save to temporary file first + temp_file = Path(output_path).with_suffix('.tmp.xlsx') + wb.save(temp_file) + wb.close() + + # Method 2: Direct ZIP manipulation to ensure complete removal + print("Method 2: Direct ZIP manipulation for complete cleanup...") + with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp: + tmp_path = tmp.name + + with zipfile.ZipFile(temp_file, 'r') as zin: + with zipfile.ZipFile(tmp_path, 'w', compression=zipfile.ZIP_DEFLATED) as zout: + # Copy all files except custom.xml or create a clean one + for item in zin.infolist(): + if item.filename == 'docProps/custom.xml': + # Create a clean custom.xml without SharePoint metadata + clean_custom_xml = create_clean_custom_xml() + zout.writestr(item, clean_custom_xml) + print(" ✓ Replaced custom.xml with clean version") + else: + # Copy the file as-is + zout.writestr(item, zin.read(item.filename)) + + # Replace original file with cleaned version + shutil.move(tmp_path, output_path) + + # Clean up temporary file + if temp_file.exists(): + temp_file.unlink() + + print(f" ✓ Successfully cleaned: {output_path}") + return True + + except Exception as e: + print(f" ✗ Error cleaning file: {e}") + return False + +def create_clean_custom_xml(): + """ + Create a clean custom.xml without SharePoint metadata + """ + # Create a minimal valid custom.xml + xml_content = ''' + +''' + return xml_content.encode('utf-8') + +def clean_template_file(): + """ + Clean the template file to prevent future corruption + """ + template_dir = Path(__file__).parent / "template" + template_files = list(template_dir.glob("*.xlsx")) + + if not template_files: + print("No template files found") + return False + + for template_file in template_files: + print(f"\nCleaning template: {template_file.name}") + + # Create backup + backup_path = template_file.with_suffix('.backup.xlsx') + shutil.copy2(template_file, backup_path) + print(f" ✓ Created backup: {backup_path.name}") + + # Clean the template + if remove_sharepoint_metadata(str(template_file)): + print(f" ✓ Template cleaned successfully") + else: + print(f" ✗ Failed to clean template") + # Restore from backup + shutil.copy2(backup_path, template_file) + print(f" ✓ Restored from backup") + + return True + +def clean_all_output_files(): + """ + Clean all Excel files in the output directory + """ + output_dir = Path(__file__).parent / "output" + excel_files = list(output_dir.glob("*.xlsx")) + + if not excel_files: + print("No Excel files found in output directory") + return False + + print(f"Found {len(excel_files)} Excel files to clean") + + for excel_file in excel_files: + print(f"\nCleaning: {excel_file.name}") + if remove_sharepoint_metadata(str(excel_file)): + print(f" ✓ Cleaned successfully") + else: + print(f" ✗ Failed to clean") + + return True + +def verify_file_is_clean(excel_path): + """ + Verify that an Excel file is free from SharePoint metadata + """ + print(f"\nVerifying: {excel_path}") + + try: + with zipfile.ZipFile(excel_path, 'r') as zf: + if 'docProps/custom.xml' in zf.namelist(): + content = zf.read('docProps/custom.xml') + + # Check for problematic metadata + if b'ContentTypeId' in content: + print(" ✗ Still contains SharePoint ContentTypeId") + return False + if b'MediaService' in content: + print(" ✗ Still contains MediaService tags") + return False + + print(" ✓ File is clean - no SharePoint metadata found") + return True + else: + print(" ✓ File is clean - no custom.xml present") + return True + + except Exception as e: + print(f" ✗ Error verifying file: {e}") + return False + +def main(): + """Main function to clean Excel files""" + print("=" * 60) + print("Excel SharePoint Metadata Cleaner") + print("=" * 60) + + # Step 1: Clean the template + print("\nStep 1: Cleaning template file...") + print("-" * 40) + clean_template_file() + + # Step 2: Clean all output files + print("\n\nStep 2: Cleaning output files...") + print("-" * 40) + clean_all_output_files() + + # Step 3: Verify cleaning + print("\n\nStep 3: Verifying cleaned files...") + print("-" * 40) + + # Verify template + template_dir = Path(__file__).parent / "template" + for template_file in template_dir.glob("*.xlsx"): + if not template_file.name.endswith('.backup.xlsx'): + verify_file_is_clean(str(template_file)) + + # Verify output files + output_dir = Path(__file__).parent / "output" + for excel_file in output_dir.glob("*.xlsx"): + verify_file_is_clean(str(excel_file)) + + print("\n" + "=" * 60) + print("Cleaning complete!") + print("\nNOTE: The Excel files should now open without corruption warnings.") + print("The SharePoint/OneDrive metadata has been removed.") + print("\nFuture files generated from the cleaned template should not have this issue.") + print("=" * 60) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/footprints_ai_test5_complete.xml b/footprints_ai_test5_complete.xml new file mode 100644 index 0000000..13f7944 --- /dev/null +++ b/footprints_ai_test5_complete.xml @@ -0,0 +1,51954 @@ + + + + Footprints AI for Test5 - Retail Media Business Case Calculations 2025-2028.xlsx + 2025-09-19 + Complete XML extraction from Test5 Excel workbook containing all worksheets, styles, charts, drawings, and relationships + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + Dan Marc + 2015-06-05T18:17:20Z + 2025-09-19T14:28:57Z + Denisa Cirstea + +
+
+ + + 0x0101000AE797D2C7FAC04B99DEE11AFEDCE578 + + + + + +
+
+ + Microsoft Excel + 3.1 + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + "Retail Media Forecast " & Variables!B2 & " - 2028" + + + + + + + + + + + + + Total Retail Media Impressions + + + + + + + + + + In-Store + + + + + Digital Screens & Radio + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!I$15:I$1048576) +) + + + + + + + + + + On-Site + + + + + Website & Mobile App + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!J$15:J$1048576) +) + + + + + + + + + + Off-Site + + + + + Social Media & Direct Comms + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!K$15:K$1048576) +) + + + + + + + + + + + + + + + Forecasted Retail Media Sales + + + + + + + + + + In-Store + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!V$15:V$1048576) +) + + + + + + + + + + On-Site + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!W$15:W$1048576) +) + + + + + + + + + + Off-Site + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!X$15:X$1048576) +) + + + + + + + + + + + + Total Forecasted Media Sales + + + + + SUM(C10:E12) + + + + + + + + + Costs (€) + + + + + + + + + + Commisions to Media Agencies + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AB$15:AB$1048576) +) + + + + + + + + + Cost of Sales + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AC$15:AC$1048576) +) + + + + + + + + + Cost of Campaign Management + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AD$15:AD$1048576) +) + + + + + + + + Cost of Platform + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AE$15:AE$1048576) +) + + + + + + + + + Total Operating Cost + + + + + SUM(D16:E19) + + + + + + + + + Software Setup & Integrations + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AI$15:AI$1048576) +) + + + + + + + + + Cloud & Processing Costs + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AJ$15:AJ$1048576) +) + + + + + + + + + FTE Consumption: Commercial + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AK$15:AK$1048576) +) + + + + + + + + + FTE Consumption: Marketing + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AL$15:AL$1048576) +) + + + + + + + + + FTE Consumptions: IT & Digital + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AM$15:AM$1048576) +) + + + + + + + + + CAPEX Investments + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!J9, + 'Retail Media Investment Case'!AN$15:AN$1048576) +) + + + + + + + + + Total Cost Including Setup & Cloud + + + + + SUM(D21:E26) + + + + + + + + + + + + + + + Operating Profit - 2026 + + + + + D13+D20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + "Retail Media Forecast " & Variables!B2 & " - 2029" + + + + + + + + + + + + + Total Retail Media Impressions + + + + + + + + + + In-Store + + + + + Digital Screens & Radio + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!I$15:I$1048576) +) + + + + + + + + + + On-Site + + + + + Website & Mobile App + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!J$15:J$1048576) +) + + + + + + + + + + Off-Site + + + + + Social Media & Direct Comms + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!K$15:K$1048576) +) + + + + + + + + + + + + + + + Forecasted Retail Media Sales + + + + + + + + + + In-Store + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!V$15:V$1048576) +) + + + + + + + + + + On-Site + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!W$15:W$1048576) +) + + + + + + + + + + Off-Site + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!X$15:X$1048576) +) + + + + + + + + + + + + Total Forecasted Media Sales + + + + + SUM(C10:E12) + + + + + + + + + Costs (€) + + + + + + + + + + Commisions to Media Agencies + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AB$15:AB$1048576) +) + + + + + + + + + Cost of Sales + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AC$15:AC$1048576) +) + + + + + + + + + Cost of Campaign Management + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AD$15:AD$1048576) +) + + + + + + + + Cost of Platform + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AE$15:AE$1048576) +) + + + + + + + + + Total Operating Cost + + + + + SUM(D16:E19) + + + + + + + + + Software Setup & Integrations + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AI$15:AI$1048576) +) + + + + + + + + + Cloud & Processing Costs + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AJ$15:AJ$1048576) +) + + + + + + + + + FTE Consumption: Commercial + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AK$15:AK$1048576) +) + + + + + + + + + FTE Consumption: Marketing + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AL$15:AL$1048576) +) + + + + + + + + + FTE Consumptions: IT & Digital + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AM$15:AM$1048576) +) + + + + + + + + + CAPEX Investments + + + + + IF(LEN('Retail Media Investment Case'!K9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!K9, + 'Retail Media Investment Case'!AN$15:AN$1048576) +) + + + + + + + + + Total Cost Including Setup & Cloud + + + + + SUM(D21:E26) + + + + + + + + + + + + + + + Operating Profit - 2026 + + + + + D13+D20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + RETAIL MEDIA INVESTMENT CASE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DATA ASSET VALUATION + + + + + + + RETAIL MEDIA INVENTORY: IMPRESSIONS + + + + + + RETAIL MEDIA INVENTORY VALUE + + + + + + RETAIL MEDIA PROJECTED SALES + + + + + + OPERATING PROFIT FROM RETAIL MEDIA + + + + + + BALANCE PROJECTION + + + + + + + + IFERROR(VLOOKUP(2025, $E:$E, 1, FALSE), "") + + + + IFERROR(VLOOKUP(2026, $E:$E, 1, FALSE), "") + + + + IFERROR(VLOOKUP(2027, $E:$E, 1, FALSE), "") + + + + IFERROR(VLOOKUP(2028, $E:$E, 1, FALSE), "") + + + + IFERROR(VLOOKUP(2029, $E:$E, 1, FALSE), "") + + + + + + G9 + + + + H9 + + + + I9 + + + + J9 + + + + K9 + + + + G9 + + + + H9 + + + + I9 + + + + J9 + + + + K9 + + + + + G9 + + + + H9 + + + + I9 + + + + J9 + + + + K9 + + + + + G9 + + + + + H9 + + + + + I9 + + + + + J9 + + + + K9 + + + + + + Variables!B3*AE10 + + + + + + IF(LEN(G9)=0, "", SUMIFS($L$15:$L$1048576, $E$15:$E$1048576, G9)) + + + + IF(LEN(H9)=0, "", SUMIFS($L$15:$L$1048576, $E$15:$E$1048576, H9)) + + + + IF(LEN(I9)=0, "", SUMIFS($L$15:$L$1048576, $E$15:$E$1048576, I9)) + + + + IF(LEN(J9)=0, "", SUMIFS($L$15:$L$1048576, $E$15:$E$1048576, J9)) + + + + IF(LEN(K9)=0, "", SUMIFS($L$15:$L$1048576, $E$15:$E$1048576, K9)) + + + + + + IF(LEN(O9)=0, "", SUMIFS($R$15:$R$1048576, $E$15:$E$1048576, O9)) + + + + IF(LEN(P9)=0, "", SUMIFS($R$15:$R$1048576, $E$15:$E$1048576, P9)) + + + + IF(LEN(Q9)=0, "", SUMIFS($R$15:$R$1048576, $E$15:$E$1048576, Q9)) + + + + IF(LEN(R9)=0, "", SUMIFS($R$15:$R$1048576, $E$15:$E$1048576, R9)) + + + + IF(LEN(S9)=0, "", SUMIFS($R$15:$R$1048576, $E$15:$E$1048576, S9)) + + + + IF(LEN(U9)=0, "", SUMIFS($Y$15:$Y$1048576, $E$15:$E$1048576, U9)) + + + + IF(LEN(V9)=0, "", SUMIFS($Y$15:$Y$1048576, $E$15:$E$1048576, V9)) + + + + IF(LEN(W9)=0, "", SUMIFS($Y$15:$Y$1048576, $E$15:$E$1048576, W9)) + + + + IF(LEN(X9)=0, "", SUMIFS($Y$15:$Y$1048576, $E$15:$E$1048576, X9)) + + + + IF(LEN(Y9)=0, "", SUMIFS($Y$15:$Y$1048576, $E$15:$E$1048576, Y9)) + + + + + IF(LEN(AB9)=0, "", SUMIFS($AF$15:$AF$1048576, $E$15:$E$1048576, AB9)) + + + + IF(LEN(AC9)=0, "", SUMIFS($AF$15:$AF$1048576, $E$15:$E$1048576, AC9)) + + + + IF(LEN(AD9)=0, "", SUMIFS($AF$15:$AF$1048576, $E$15:$E$1048576, AD9)) + + + + IF(LEN(AE9)=0, "", SUMIFS($AF$15:$AF$1048576, $E$15:$E$1048576, AE9)) + + + + IF(LEN(AF9)=0, "", SUMIFS($AF$15:$AF$1048576, $E$15:$E$1048576, AF9)) + + + + + IF(LEN(AI9)=0, "", SUMIFS($AO$15:$AO$1048576, $E$15:$E$1048576, AI9)) + + + + IF(LEN(AK9)=0, "", SUMIFS($AO$15:$AO$1048576, $E$15:$E$1048576, AK9)) + + + + IF(LEN(AM9)=0, "", SUMIFS($AO$15:$AO$1048576, $E$15:$E$1048576, AM9)) + + + + IF(LEN(AO9)=0, "", SUMIFS($AO$15:$AO$1048576, $E$15:$E$1048576, AO9)) + + + + IF(LEN(AP9)=0, "", SUMIFS($AO$15:$AO$1048576, $E$15:$E$1048576, AP9)) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Year / Month + + + + + + + MONTHY REACH UNIQUE + + + + + POTENTIAL IMPRESSIONS: +IN-STORE + + + + + POTENTIAL IMPRESSIONS: +ON-SITE + + + + + POTENTIAL IMPRESSIONS: +OFF-SITE + + + + + TOTAL POTENTIAL IMPRESSIONS + + + + + Evolution + + + + + + POTENTIAL MEDIA VALUE + + + + + TOTAL POTENTIAL VALUE + + + + + Evolution + + + + + + Velocity + + + + + Sales/Month +IN-STORE + + + + + Sales/Month +ON-SITE + + + + + Sales / Month +OFF-SITE + + + + + TOTAL PROJECTED SALES + + + + + Evolution + + + + + + Commisions to Media Agency: 20% - applied to approx. 50% of total revenues + + + + + Cost of Sales: 10% off Gross Sales after Commisions (5% if Lead Gen by Commercial Dep) + + + + + Cost of Campaign Management: 5% off Gross Sales excl. Rebates + + + + + Cost of Platform: 25% off Gross Sales excl. Rebates + + + + + OPERATING PROFIT + + + + + Evolution + + + + + + SOFTWARE SETUP & INTEGRATIONS + + + + + CLOUD & PROCESSING COSTS + + + + + FTE CONSUMPTION: COMMERCIAL + + + + + FTE CONSUMPTION: MARKETING + + + + + FTE CONSUMPTION: IT & DIGITAL + + + + + MEDIA INFRASTRUCTURE INVESTMENT + + + + + BALANCE + + + + + Evolution + + + + + + + + + IN-STORE: +Digital Screens, In-Store Radio + + + + + ON-SITE: +Website, Mobile App + + + + + OFF-SITE: +Social Media, Direct Comms + + + + + F13 + + + + G13 + + + + H13 + + + + + + + + + + + + + + + + + + + + + + Variables!B11 + + + + Variables!B12 + + + + Variables!B13 + + + + + + + + + % Total Potential Sales + + + + + + + + + + + + + + + + + + + + + + + DATE(YEAR(Variables!B31),MONTH(Variables!B31),DAY(Variables!B31)) + + + + IFERROR(IF(ISNUMBER(E15),0,1),"") + + + + IFERROR(YEAR(C15),"") + + + + Variables!B4 + + + + Variables!B5 + + + + Variables!B6 + + + + IFERROR((F15*Variables!$B$8),"") + + + + IFERROR((G15*Variables!$B$9),"") + + + + IFERROR((H15*Variables!$B$10),"") + + + + IFERROR(SUM(I15:K15),"") + + + + + + IFERROR(I15/1000*$O$14,"") + + + + IFERROR(J15/1000*$P$14,"") + + + + IFERROR(K15/1000*$Q$14,"") + + + + SUM(O15:Q15) + + + + + + IFERROR(Variables!B14,"") + + + + IFERROR(O15*U15,"") + + + + IFERROR(P15*U15,"") + + + + IFERROR(Q15*U15,"") + + + + SUM(V15:X15) + + + + + + -Y15*Variables!$B$16 + + + + -SUM(Y15,AB15)*Variables!$B$17 + + + + -SUM(Y15,AB15)*Variables!$B$18 + + + + -SUM(Y15,AB15)*0.25 + + + + Y15+SUM(AB15:AE15) + + + + + + -Variables!B20 + + + + -Variables!B21 + + + + Variables!B23 + + + + Variables!B25 + + + + Variables!B27 + + + + Variables!B29 + + + + IFERROR(AF15+(SUM(AI15:AN15)),0) + + + + + + + + IF(C15<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C15,1),"") + + + + IFERROR(IF(ISNUMBER(E16),0,1),"") + + + + IFERROR(YEAR(C16),"") + + + + IF(D16=0,F15*Variables!$B$7,"") + + + + IF(D16=0,G15*Variables!$B$7,"") + + + + IF(D16=0,H15*Variables!$B$7,"") + + + + IFERROR((F16*Variables!$B$8),"") + + + + IFERROR((G16*Variables!$B$9),"") + + + + IFERROR((H16*Variables!$B$10),"") + + + + IFERROR(SUM(I16:K16),"") + + + + + + IFERROR(I16/1000*$O$14,"") + + + + IFERROR(J16/1000*$P$14,"") + + + + IFERROR(K16/1000*$Q$14,"") + + + + SUM(O16:Q16) + + + + + + IFERROR(U15+Variables!$B$15,"") + + + + IFERROR(O16*U16,"") + + + + IFERROR(P16*U16,"") + + + + IFERROR(Q16*U16,"") + + + + SUM(V16:X16) + + + + + + -Y16*Variables!$B$16 + + + + -SUM(Y16,AB16)*Variables!$B$17 + + + + -SUM(Y16,AB16)*Variables!$B$18 + + + + -SUM(Y16,AB16)*0.25 + + + + Y16+SUM(AB16:AE16) + + + + + + 0 + + + IF(D16=0,AJ15*Variables!$B$22,"") + + + + IF(D16=0,AK15*Variables!$B$24,"") + + + + IF(D16=0,AL15*Variables!$B$26,"") + + + + IF(D16=0,AM15*Variables!$B$28,"") + + + + IF(D16=0,AN15+Variables!$B$30,"") + + + + IFERROR(AF16+(SUM(AI16:AN16)),0) + + + + + + + + IF(C16<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C16,1),"") + + + + IFERROR(IF(ISNUMBER(E17),0,1),"") + + + + IFERROR(YEAR(C17),"") + + + + IF(D17=0,F16*Variables!$B$7,"") + + + + IF(D17=0,G16*Variables!$B$7,"") + + + + IF(D17=0,H16*Variables!$B$7,"") + + + + IFERROR((F17*Variables!$B$8),"") + + + + IFERROR((G17*Variables!$B$9),"") + + + + IFERROR((H17*Variables!$B$10),"") + + + + IFERROR(SUM(I17:K17),"") + + + + + + IFERROR(I17/1000*$O$14,"") + + + + IFERROR(J17/1000*$P$14,"") + + + + IFERROR(K17/1000*$Q$14,"") + + + + SUM(O17:Q17) + + + + + + IFERROR(U16+Variables!$B$15,"") + + + + IFERROR(O17*U17,"") + + + + IFERROR(P17*U17,"") + + + + IFERROR(Q17*U17,"") + + + + SUM(V17:X17) + + + + + + -Y17*Variables!$B$16 + + + + -SUM(Y17,AB17)*Variables!$B$17 + + + + -SUM(Y17,AB17)*Variables!$B$18 + + + + -SUM(Y17,AB17)*0.25 + + + + Y17+SUM(AB17:AE17) + + + + + + 0 + + + IF(D17=0,AJ16*Variables!$B$22,"") + + + + IF(D17=0,AK16*Variables!$B$24,"") + + + + IF(D17=0,AL16*Variables!$B$26,"") + + + + IF(D17=0,AM16*Variables!$B$28,"") + + + + IF(D17=0,AN16+Variables!$B$30,"") + + + + IFERROR(AF17+(SUM(AI17:AN17)),0) + + + + + + + + IF(C17<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C17,1),"") + + + + IFERROR(IF(ISNUMBER(E18),0,1),"") + + + + IFERROR(YEAR(C18),"") + + + + IF(D18=0,F17*Variables!$B$7,"") + + + + IF(D18=0,G17*Variables!$B$7,"") + + + + IF(D18=0,H17*Variables!$B$7,"") + + + + IFERROR((F18*Variables!$B$8),"") + + + + IFERROR((G18*Variables!$B$9),"") + + + + IFERROR((H18*Variables!$B$10),"") + + + + IFERROR(SUM(I18:K18),"") + + + + + + IFERROR(I18/1000*$O$14,"") + + + + IFERROR(J18/1000*$P$14,"") + + + + IFERROR(K18/1000*$Q$14,"") + + + + SUM(O18:Q18) + + + + + + IFERROR(U17+Variables!$B$15,"") + + + + IFERROR(O18*U18,"") + + + + IFERROR(P18*U18,"") + + + + IFERROR(Q18*U18,"") + + + + SUM(V18:X18) + + + + + + -Y18*Variables!$B$16 + + + + -SUM(Y18,AB18)*Variables!$B$17 + + + + -SUM(Y18,AB18)*Variables!$B$18 + + + + -SUM(Y18,AB18)*0.25 + + + + Y18+SUM(AB18:AE18) + + + + + + 0 + + + IF(D18=0,AJ17*Variables!$B$22,"") + + + + IF(D18=0,AK17*Variables!$B$24,"") + + + + IF(D18=0,AL17*Variables!$B$26,"") + + + + IF(D18=0,AM17*Variables!$B$28,"") + + + + IF(D18=0,AN17+Variables!$B$30,"") + + + + IFERROR(AF18+(SUM(AI18:AN18)),0) + + + + + + + + IF(C18<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C18,1),"") + + + + IFERROR(IF(ISNUMBER(E19),0,1),"") + + + + IFERROR(YEAR(C19),"") + + + + IF(D19=0,F18*Variables!$B$7,"") + + + + IF(D19=0,G18*Variables!$B$7,"") + + + + IF(D19=0,H18*Variables!$B$7,"") + + + + IFERROR((F19*Variables!$B$8),"") + + + + IFERROR((G19*Variables!$B$9),"") + + + + IFERROR((H19*Variables!$B$10),"") + + + + IFERROR(SUM(I19:K19),"") + + + + + + IFERROR(I19/1000*$O$14,"") + + + + IFERROR(J19/1000*$P$14,"") + + + + IFERROR(K19/1000*$Q$14,"") + + + + SUM(O19:Q19) + + + + + + IFERROR(U18+Variables!$B$15,"") + + + + IFERROR(O19*U19,"") + + + + IFERROR(P19*U19,"") + + + + IFERROR(Q19*U19,"") + + + + SUM(V19:X19) + + + + + + -Y19*Variables!$B$16 + + + + -SUM(Y19,AB19)*Variables!$B$17 + + + + -SUM(Y19,AB19)*Variables!$B$18 + + + + -SUM(Y19,AB19)*0.25 + + + + Y19+SUM(AB19:AE19) + + + + + + 0 + + + IF(D19=0,AJ18*Variables!$B$22,"") + + + + IF(D19=0,AK18*Variables!$B$24,"") + + + + IF(D19=0,AL18*Variables!$B$26,"") + + + + IF(D19=0,AM18*Variables!$B$28,"") + + + + IF(D19=0,AN18+Variables!$B$30,"") + + + + IFERROR(AF19+(SUM(AI19:AN19)),0) + + + + + + + + IF(C19<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C19,1),"") + + + + IFERROR(IF(ISNUMBER(E20),0,1),"") + + + + IFERROR(YEAR(C20),"") + + + + IF(D20=0,F19*Variables!$B$7,"") + + + + IF(D20=0,G19*Variables!$B$7,"") + + + + IF(D20=0,H19*Variables!$B$7,"") + + + + IFERROR((F20*Variables!$B$8),"") + + + + IFERROR((G20*Variables!$B$9),"") + + + + IFERROR((H20*Variables!$B$10),"") + + + + IFERROR(SUM(I20:K20),"") + + + + + + IFERROR(I20/1000*$O$14,"") + + + + IFERROR(J20/1000*$P$14,"") + + + + IFERROR(K20/1000*$Q$14,"") + + + + SUM(O20:Q20) + + + + + + IFERROR(U19+Variables!$B$15,"") + + + + IFERROR(O20*U20,"") + + + + IFERROR(P20*U20,"") + + + + IFERROR(Q20*U20,"") + + + + SUM(V20:X20) + + + + + + -Y20*Variables!$B$16 + + + + -SUM(Y20,AB20)*Variables!$B$17 + + + + -SUM(Y20,AB20)*Variables!$B$18 + + + + -SUM(Y20,AB20)*0.25 + + + + Y20+SUM(AB20:AE20) + + + + + + 0 + + + IF(D20=0,AJ19*Variables!$B$22,"") + + + + IF(D20=0,AK19*Variables!$B$24,"") + + + + IF(D20=0,AL19*Variables!$B$26,"") + + + + IF(D20=0,AM19*Variables!$B$28,"") + + + + IF(D20=0,AN19+Variables!$B$30,"") + + + + IFERROR(AF20+(SUM(AI20:AN20)),0) + + + + + + + + IF(C20<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C20,1),"") + + + + IFERROR(IF(ISNUMBER(E21),0,1),"") + + + + IFERROR(YEAR(C21),"") + + + + IF(D21=0,F20*Variables!$B$7,"") + + + + IF(D21=0,G20*Variables!$B$7,"") + + + + IF(D21=0,H20*Variables!$B$7,"") + + + + IFERROR((F21*Variables!$B$8),"") + + + + IFERROR((G21*Variables!$B$9),"") + + + + IFERROR((H21*Variables!$B$10),"") + + + + IFERROR(SUM(I21:K21),"") + + + + + + IFERROR(I21/1000*$O$14,"") + + + + IFERROR(J21/1000*$P$14,"") + + + + IFERROR(K21/1000*$Q$14,"") + + + + SUM(O21:Q21) + + + + + + IFERROR(U20+Variables!$B$15,"") + + + + IFERROR(O21*U21,"") + + + + IFERROR(P21*U21,"") + + + + IFERROR(Q21*U21,"") + + + + SUM(V21:X21) + + + + + + -Y21*Variables!$B$16 + + + + -SUM(Y21,AB21)*Variables!$B$17 + + + + -SUM(Y21,AB21)*Variables!$B$18 + + + + -SUM(Y21,AB21)*0.25 + + + + Y21+SUM(AB21:AE21) + + + + + + 0 + + + IF(D21=0,AJ20*Variables!$B$22,"") + + + + IF(D21=0,AK20*Variables!$B$24,"") + + + + IF(D21=0,AL20*Variables!$B$26,"") + + + + IF(D21=0,AM20*Variables!$B$28,"") + + + + IF(D21=0,AN20+Variables!$B$30,"") + + + + IFERROR(AF21+(SUM(AI21:AN21)),0) + + + + + + + + IF(C21<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C21,1),"") + + + + IFERROR(IF(ISNUMBER(E22),0,1),"") + + + + IFERROR(YEAR(C22),"") + + + + IF(D22=0,F21*Variables!$B$7,"") + + + + IF(D22=0,G21*Variables!$B$7,"") + + + + IF(D22=0,H21*Variables!$B$7,"") + + + + IFERROR((F22*Variables!$B$8),"") + + + + IFERROR((G22*Variables!$B$9),"") + + + + IFERROR((H22*Variables!$B$10),"") + + + + IFERROR(SUM(I22:K22),"") + + + + + + IFERROR(I22/1000*$O$14,"") + + + + IFERROR(J22/1000*$P$14,"") + + + + IFERROR(K22/1000*$Q$14,"") + + + + SUM(O22:Q22) + + + + + + IFERROR(U21+Variables!$B$15,"") + + + + IFERROR(O22*U22,"") + + + + IFERROR(P22*U22,"") + + + + IFERROR(Q22*U22,"") + + + + SUM(V22:X22) + + + + + + -Y22*Variables!$B$16 + + + + -SUM(Y22,AB22)*Variables!$B$17 + + + + -SUM(Y22,AB22)*Variables!$B$18 + + + + -SUM(Y22,AB22)*0.25 + + + + Y22+SUM(AB22:AE22) + + + + + + 0 + + + IF(D22=0,AJ21*Variables!$B$22,"") + + + + IF(D22=0,AK21*Variables!$B$24,"") + + + + IF(D22=0,AL21*Variables!$B$26,"") + + + + IF(D22=0,AM21*Variables!$B$28,"") + + + + IF(D22=0,AN21+Variables!$B$30,"") + + + + IFERROR(AF22+(SUM(AI22:AN22)),0) + + + + + + + + IF(C22<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C22,1),"") + + + + IFERROR(IF(ISNUMBER(E23),0,1),"") + + + + IFERROR(YEAR(C23),"") + + + + IF(D23=0,F22*Variables!$B$7,"") + + + + IF(D23=0,G22*Variables!$B$7,"") + + + + IF(D23=0,H22*Variables!$B$7,"") + + + + IFERROR((F23*Variables!$B$8),"") + + + + IFERROR((G23*Variables!$B$9),"") + + + + IFERROR((H23*Variables!$B$10),"") + + + + IFERROR(SUM(I23:K23),"") + + + + + + IFERROR(I23/1000*$O$14,"") + + + + IFERROR(J23/1000*$P$14,"") + + + + IFERROR(K23/1000*$Q$14,"") + + + + SUM(O23:Q23) + + + + + + IFERROR(U22+Variables!$B$15,"") + + + + IFERROR(O23*U23,"") + + + + IFERROR(P23*U23,"") + + + + IFERROR(Q23*U23,"") + + + + SUM(V23:X23) + + + + + + -Y23*Variables!$B$16 + + + + -SUM(Y23,AB23)*Variables!$B$17 + + + + -SUM(Y23,AB23)*Variables!$B$18 + + + + -SUM(Y23,AB23)*0.25 + + + + Y23+SUM(AB23:AE23) + + + + + + 0 + + + IF(D23=0,AJ22*Variables!$B$22,"") + + + + IF(D23=0,AK22*Variables!$B$24,"") + + + + IF(D23=0,AL22*Variables!$B$26,"") + + + + IF(D23=0,AM22*Variables!$B$28,"") + + + + IF(D23=0,AN22+Variables!$B$30,"") + + + + IFERROR(AF23+(SUM(AI23:AN23)),0) + + + + + + + + IF(C23<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C23,1),"") + + + + IFERROR(IF(ISNUMBER(E24),0,1),"") + + + + IFERROR(YEAR(C24),"") + + + + IF(D24=0,F23*Variables!$B$7,"") + + + + IF(D24=0,G23*Variables!$B$7,"") + + + + IF(D24=0,H23*Variables!$B$7,"") + + + + IFERROR((F24*Variables!$B$8),"") + + + + IFERROR((G24*Variables!$B$9),"") + + + + IFERROR((H24*Variables!$B$10),"") + + + + IFERROR(SUM(I24:K24),"") + + + + + + IFERROR(I24/1000*$O$14,"") + + + + IFERROR(J24/1000*$P$14,"") + + + + IFERROR(K24/1000*$Q$14,"") + + + + SUM(O24:Q24) + + + + + + IFERROR(U23+Variables!$B$15,"") + + + + IFERROR(O24*U24,"") + + + + IFERROR(P24*U24,"") + + + + IFERROR(Q24*U24,"") + + + + SUM(V24:X24) + + + + + + -Y24*Variables!$B$16 + + + + -SUM(Y24,AB24)*Variables!$B$17 + + + + -SUM(Y24,AB24)*Variables!$B$18 + + + + -SUM(Y24,AB24)*0.25 + + + + Y24+SUM(AB24:AE24) + + + + + + 0 + + + IF(D24=0,AJ23*Variables!$B$22,"") + + + + IF(D24=0,AK23*Variables!$B$24,"") + + + + IF(D24=0,AL23*Variables!$B$26,"") + + + + IF(D24=0,AM23*Variables!$B$28,"") + + + + IF(D24=0,AN23+Variables!$B$30,"") + + + + IFERROR(AF24+(SUM(AI24:AN24)),0) + + + + + + + + IF(C24<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C24,1),"") + + + + IFERROR(IF(ISNUMBER(E25),0,1),"") + + + + IFERROR(YEAR(C25),"") + + + + IF(D25=0,F24*Variables!$B$7,"") + + + + IF(D25=0,G24*Variables!$B$7,"") + + + + IF(D25=0,H24*Variables!$B$7,"") + + + + IFERROR((F25*Variables!$B$8),"") + + + + IFERROR((G25*Variables!$B$9),"") + + + + IFERROR((H25*Variables!$B$10),"") + + + + IFERROR(SUM(I25:K25),"") + + + + + + IFERROR(I25/1000*$O$14,"") + + + + IFERROR(J25/1000*$P$14,"") + + + + IFERROR(K25/1000*$Q$14,"") + + + + SUM(O25:Q25) + + + + + + IFERROR(U24+Variables!$B$15,"") + + + + IFERROR(O25*U25,"") + + + + IFERROR(P25*U25,"") + + + + IFERROR(Q25*U25,"") + + + + SUM(V25:X25) + + + + + + -Y25*Variables!$B$16 + + + + -SUM(Y25,AB25)*Variables!$B$17 + + + + -SUM(Y25,AB25)*Variables!$B$18 + + + + -SUM(Y25,AB25)*0.25 + + + + Y25+SUM(AB25:AE25) + + + + + + 0 + + + IF(D25=0,AJ24*Variables!$B$22,"") + + + + IF(D25=0,AK24*Variables!$B$24,"") + + + + IF(D25=0,AL24*Variables!$B$26,"") + + + + IF(D25=0,AM24*Variables!$B$28,"") + + + + IF(D25=0,AN24+Variables!$B$30,"") + + + + IFERROR(AF25+(SUM(AI25:AN25)),0) + + + + + + + + IF(C25<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C25,1),"") + + + + IFERROR(IF(ISNUMBER(E26),0,1),"") + + + + IFERROR(YEAR(C26),"") + + + + IF(D26=0,F25*Variables!$B$7,"") + + + + IF(D26=0,G25*Variables!$B$7,"") + + + + IF(D26=0,H25*Variables!$B$7,"") + + + + IFERROR((F26*Variables!$B$8),"") + + + + IFERROR((G26*Variables!$B$9),"") + + + + IFERROR((H26*Variables!$B$10),"") + + + + IFERROR(SUM(I26:K26),"") + + + + + + IFERROR(I26/1000*$O$14,"") + + + + IFERROR(J26/1000*$P$14,"") + + + + IFERROR(K26/1000*$Q$14,"") + + + + SUM(O26:Q26) + + + + + + IFERROR(U25+Variables!$B$15,"") + + + + IFERROR(O26*U26,"") + + + + IFERROR(P26*U26,"") + + + + IFERROR(Q26*U26,"") + + + + SUM(V26:X26) + + + + + + -Y26*Variables!$B$16 + + + + -SUM(Y26,AB26)*Variables!$B$17 + + + + -SUM(Y26,AB26)*Variables!$B$18 + + + + -SUM(Y26,AB26)*0.25 + + + + Y26+SUM(AB26:AE26) + + + + + + 0 + + + IF(D26=0,AJ25*Variables!$B$22,"") + + + + IF(D26=0,AK25*Variables!$B$24,"") + + + + IF(D26=0,AL25*Variables!$B$26,"") + + + + IF(D26=0,AM25*Variables!$B$28,"") + + + + IF(D26=0,AN25+Variables!$B$30,"") + + + + IFERROR(AF26+(SUM(AI26:AN26)),0) + + + + + + + + IF(C26<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C26,1),"") + + + + IFERROR(IF(ISNUMBER(E27),0,1),"") + + + + IFERROR(YEAR(C27),"") + + + + IF(D27=0,F26*Variables!$B$7,"") + + + + IF(D27=0,G26*Variables!$B$7,"") + + + + IF(D27=0,H26*Variables!$B$7,"") + + + + IFERROR((F27*Variables!$B$8),"") + + + + IFERROR((G27*Variables!$B$9),"") + + + + IFERROR((H27*Variables!$B$10),"") + + + + IFERROR(SUM(I27:K27),"") + + + + + + IFERROR(I27/1000*$O$14,"") + + + + IFERROR(J27/1000*$P$14,"") + + + + IFERROR(K27/1000*$Q$14,"") + + + + SUM(O27:Q27) + + + + + + IFERROR(U26+Variables!$B$15,"") + + + + IFERROR(O27*U27,"") + + + + IFERROR(P27*U27,"") + + + + IFERROR(Q27*U27,"") + + + + SUM(V27:X27) + + + + + + -Y27*Variables!$B$16 + + + + -SUM(Y27,AB27)*Variables!$B$17 + + + + -SUM(Y27,AB27)*Variables!$B$18 + + + + -SUM(Y27,AB27)*0.25 + + + + Y27+SUM(AB27:AE27) + + + + + + 0 + + + IF(D27=0,AJ26*Variables!$B$22,"") + + + + IF(D27=0,AK26*Variables!$B$24,"") + + + + IF(D27=0,AL26*Variables!$B$26,"") + + + + IF(D27=0,AM26*Variables!$B$28,"") + + + + IF(D27=0,AN26+Variables!$B$30,"") + + + + IFERROR(AF27+(SUM(AI27:AN27)),0) + + + + + + + + IF(C27<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C27,1),"") + + + + IFERROR(IF(ISNUMBER(E28),0,1),"") + + + + IFERROR(YEAR(C28),"") + + + + IF(D28=0,F27*Variables!$B$7,"") + + + + IF(D28=0,G27*Variables!$B$7,"") + + + + IF(D28=0,H27*Variables!$B$7,"") + + + + IFERROR((F28*Variables!$B$8),"") + + + + IFERROR((G28*Variables!$B$9),"") + + + + IFERROR((H28*Variables!$B$10),"") + + + + IFERROR(SUM(I28:K28),"") + + + + + + IFERROR(I28/1000*$O$14,"") + + + + IFERROR(J28/1000*$P$14,"") + + + + IFERROR(K28/1000*$Q$14,"") + + + + SUM(O28:Q28) + + + + + + IFERROR(U27+Variables!$B$15,"") + + + + IFERROR(O28*U28,"") + + + + IFERROR(P28*U28,"") + + + + IFERROR(Q28*U28,"") + + + + SUM(V28:X28) + + + + + + -Y28*Variables!$B$16 + + + + -SUM(Y28,AB28)*Variables!$B$17 + + + + -SUM(Y28,AB28)*Variables!$B$18 + + + + -SUM(Y28,AB28)*0.25 + + + + Y28+SUM(AB28:AE28) + + + + + + 0 + + + IF(D28=0,AJ27*Variables!$B$22,"") + + + + IF(D28=0,AK27*Variables!$B$24,"") + + + + IF(D28=0,AL27*Variables!$B$26,"") + + + + IF(D28=0,AM27*Variables!$B$28,"") + + + + IF(D28=0,AN27+Variables!$B$30,"") + + + + IFERROR(AF28+(SUM(AI28:AN28)),0) + + + + + + + + IF(C28<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C28,1),"") + + + + IFERROR(IF(ISNUMBER(E29),0,1),"") + + + + IFERROR(YEAR(C29),"") + + + + IF(D29=0,F28*Variables!$B$7,"") + + + + IF(D29=0,G28*Variables!$B$7,"") + + + + IF(D29=0,H28*Variables!$B$7,"") + + + + IFERROR((F29*Variables!$B$8),"") + + + + IFERROR((G29*Variables!$B$9),"") + + + + IFERROR((H29*Variables!$B$10),"") + + + + IFERROR(SUM(I29:K29),"") + + + + + + IFERROR(I29/1000*$O$14,"") + + + + IFERROR(J29/1000*$P$14,"") + + + + IFERROR(K29/1000*$Q$14,"") + + + + SUM(O29:Q29) + + + + + + IFERROR(U28+Variables!$B$15,"") + + + + IFERROR(O29*U29,"") + + + + IFERROR(P29*U29,"") + + + + IFERROR(Q29*U29,"") + + + + SUM(V29:X29) + + + + + + -Y29*Variables!$B$16 + + + + -SUM(Y29,AB29)*Variables!$B$17 + + + + -SUM(Y29,AB29)*Variables!$B$18 + + + + -SUM(Y29,AB29)*0.25 + + + + Y29+SUM(AB29:AE29) + + + + + + 0 + + + IF(D29=0,AJ28*Variables!$B$22,"") + + + + IF(D29=0,AK28*Variables!$B$24,"") + + + + IF(D29=0,AL28*Variables!$B$26,"") + + + + IF(D29=0,AM28*Variables!$B$28,"") + + + + IF(D29=0,AN28+Variables!$B$30,"") + + + + IFERROR(AF29+(SUM(AI29:AN29)),0) + + + + + + + + IF(C29<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C29,1),"") + + + + IFERROR(IF(ISNUMBER(E30),0,1),"") + + + + IFERROR(YEAR(C30),"") + + + + IF(D30=0,F29*Variables!$B$7,"") + + + + IF(D30=0,G29*Variables!$B$7,"") + + + + IF(D30=0,H29*Variables!$B$7,"") + + + + IFERROR((F30*Variables!$B$8),"") + + + + IFERROR((G30*Variables!$B$9),"") + + + + IFERROR((H30*Variables!$B$10),"") + + + + IFERROR(SUM(I30:K30),"") + + + + + + IFERROR(I30/1000*$O$14,"") + + + + IFERROR(J30/1000*$P$14,"") + + + + IFERROR(K30/1000*$Q$14,"") + + + + SUM(O30:Q30) + + + + + + IFERROR(U29+Variables!$B$15,"") + + + + IFERROR(O30*U30,"") + + + + IFERROR(P30*U30,"") + + + + IFERROR(Q30*U30,"") + + + + SUM(V30:X30) + + + + + + -Y30*Variables!$B$16 + + + + -SUM(Y30,AB30)*Variables!$B$17 + + + + -SUM(Y30,AB30)*Variables!$B$18 + + + + -SUM(Y30,AB30)*0.25 + + + + Y30+SUM(AB30:AE30) + + + + + + 0 + + + IF(D30=0,AJ29*Variables!$B$22,"") + + + + IF(D30=0,AK29*Variables!$B$24,"") + + + + IF(D30=0,AL29*Variables!$B$26,"") + + + + IF(D30=0,AM29*Variables!$B$28,"") + + + + IF(D30=0,AN29+Variables!$B$30,"") + + + + IFERROR(AF30+(SUM(AI30:AN30)),0) + + + + + + + + IF(C30<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C30,1),"") + + + + IFERROR(IF(ISNUMBER(E31),0,1),"") + + + + IFERROR(YEAR(C31),"") + + + + IF(D31=0,F30*Variables!$B$7,"") + + + + IF(D31=0,G30*Variables!$B$7,"") + + + + IF(D31=0,H30*Variables!$B$7,"") + + + + IFERROR((F31*Variables!$B$8),"") + + + + IFERROR((G31*Variables!$B$9),"") + + + + IFERROR((H31*Variables!$B$10),"") + + + + IFERROR(SUM(I31:K31),"") + + + + + + IFERROR(I31/1000*$O$14,"") + + + + IFERROR(J31/1000*$P$14,"") + + + + IFERROR(K31/1000*$Q$14,"") + + + + SUM(O31:Q31) + + + + + + IFERROR(U30+Variables!$B$15,"") + + + + IFERROR(O31*U31,"") + + + + IFERROR(P31*U31,"") + + + + IFERROR(Q31*U31,"") + + + + SUM(V31:X31) + + + + + + -Y31*Variables!$B$16 + + + + -SUM(Y31,AB31)*Variables!$B$17 + + + + -SUM(Y31,AB31)*Variables!$B$18 + + + + -SUM(Y31,AB31)*0.25 + + + + Y31+SUM(AB31:AE31) + + + + + + 0 + + + IF(D31=0,AJ30*Variables!$B$22,"") + + + + IF(D31=0,AK30*Variables!$B$24,"") + + + + IF(D31=0,AL30*Variables!$B$26,"") + + + + IF(D31=0,AM30*Variables!$B$28,"") + + + + IF(D31=0,AN30+Variables!$B$30,"") + + + + IFERROR(AF31+(SUM(AI31:AN31)),0) + + + + + + + + IF(C31<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C31,1),"") + + + + IFERROR(IF(ISNUMBER(E32),0,1),"") + + + + IFERROR(YEAR(C32),"") + + + + IF(D32=0,F31*Variables!$B$7,"") + + + + IF(D32=0,G31*Variables!$B$7,"") + + + + IF(D32=0,H31*Variables!$B$7,"") + + + + IFERROR((F32*Variables!$B$8),"") + + + + IFERROR((G32*Variables!$B$9),"") + + + + IFERROR((H32*Variables!$B$10),"") + + + + IFERROR(SUM(I32:K32),"") + + + + + + IFERROR(I32/1000*$O$14,"") + + + + IFERROR(J32/1000*$P$14,"") + + + + IFERROR(K32/1000*$Q$14,"") + + + + SUM(O32:Q32) + + + + + + IFERROR(U31+Variables!$B$15,"") + + + + IFERROR(O32*U32,"") + + + + IFERROR(P32*U32,"") + + + + IFERROR(Q32*U32,"") + + + + SUM(V32:X32) + + + + + + -Y32*Variables!$B$16 + + + + -SUM(Y32,AB32)*Variables!$B$17 + + + + -SUM(Y32,AB32)*Variables!$B$18 + + + + -SUM(Y32,AB32)*0.25 + + + + Y32+SUM(AB32:AE32) + + + + + + 0 + + + IF(D32=0,AJ31*Variables!$B$22,"") + + + + IF(D32=0,AK31*Variables!$B$24,"") + + + + IF(D32=0,AL31*Variables!$B$26,"") + + + + IF(D32=0,AM31*Variables!$B$28,"") + + + + IF(D32=0,AN31+Variables!$B$30,"") + + + + IFERROR(AF32+(SUM(AI32:AN32)),0) + + + + + + + + IF(C32<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C32,1),"") + + + + IFERROR(IF(ISNUMBER(E33),0,1),"") + + + + IFERROR(YEAR(C33),"") + + + + IF(D33=0,F32*Variables!$B$7,"") + + + + IF(D33=0,G32*Variables!$B$7,"") + + + + IF(D33=0,H32*Variables!$B$7,"") + + + + IFERROR((F33*Variables!$B$8),"") + + + + IFERROR((G33*Variables!$B$9),"") + + + + IFERROR((H33*Variables!$B$10),"") + + + + IFERROR(SUM(I33:K33),"") + + + + + + IFERROR(I33/1000*$O$14,"") + + + + IFERROR(J33/1000*$P$14,"") + + + + IFERROR(K33/1000*$Q$14,"") + + + + SUM(O33:Q33) + + + + + + IFERROR(U32+Variables!$B$15,"") + + + + IFERROR(O33*U33,"") + + + + IFERROR(P33*U33,"") + + + + IFERROR(Q33*U33,"") + + + + SUM(V33:X33) + + + + + + -Y33*Variables!$B$16 + + + + -SUM(Y33,AB33)*Variables!$B$17 + + + + -SUM(Y33,AB33)*Variables!$B$18 + + + + -SUM(Y33,AB33)*0.25 + + + + Y33+SUM(AB33:AE33) + + + + + + 0 + + + IF(D33=0,AJ32*Variables!$B$22,"") + + + + IF(D33=0,AK32*Variables!$B$24,"") + + + + IF(D33=0,AL32*Variables!$B$26,"") + + + + IF(D33=0,AM32*Variables!$B$28,"") + + + + IF(D33=0,AN32+Variables!$B$30,"") + + + + IFERROR(AF33+(SUM(AI33:AN33)),0) + + + + + + + + IF(C33<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C33,1),"") + + + + IFERROR(IF(ISNUMBER(E34),0,1),"") + + + + IFERROR(YEAR(C34),"") + + + + IF(D34=0,F33*Variables!$B$7,"") + + + + IF(D34=0,G33*Variables!$B$7,"") + + + + IF(D34=0,H33*Variables!$B$7,"") + + + + IFERROR((F34*Variables!$B$8),"") + + + + IFERROR((G34*Variables!$B$9),"") + + + + IFERROR((H34*Variables!$B$10),"") + + + + IFERROR(SUM(I34:K34),"") + + + + + + IFERROR(I34/1000*$O$14,"") + + + + IFERROR(J34/1000*$P$14,"") + + + + IFERROR(K34/1000*$Q$14,"") + + + + SUM(O34:Q34) + + + + + + IFERROR(U33+Variables!$B$15,"") + + + + IFERROR(O34*U34,"") + + + + IFERROR(P34*U34,"") + + + + IFERROR(Q34*U34,"") + + + + SUM(V34:X34) + + + + + + -Y34*Variables!$B$16 + + + + -SUM(Y34,AB34)*Variables!$B$17 + + + + -SUM(Y34,AB34)*Variables!$B$18 + + + + -SUM(Y34,AB34)*0.25 + + + + Y34+SUM(AB34:AE34) + + + + + + 0 + + + IF(D34=0,AJ33*Variables!$B$22,"") + + + + IF(D34=0,AK33*Variables!$B$24,"") + + + + IF(D34=0,AL33*Variables!$B$26,"") + + + + IF(D34=0,AM33*Variables!$B$28,"") + + + + IF(D34=0,AN33+Variables!$B$30,"") + + + + IFERROR(AF34+(SUM(AI34:AN34)),0) + + + + + + + + IF(C34<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C34,1),"") + + + + IFERROR(IF(ISNUMBER(E35),0,1),"") + + + + IFERROR(YEAR(C35),"") + + + + IF(D35=0,F34*Variables!$B$7,"") + + + + IF(D35=0,G34*Variables!$B$7,"") + + + + IF(D35=0,H34*Variables!$B$7,"") + + + + IFERROR((F35*Variables!$B$8),"") + + + + IFERROR((G35*Variables!$B$9),"") + + + + IFERROR((H35*Variables!$B$10),"") + + + + IFERROR(SUM(I35:K35),"") + + + + + + IFERROR(I35/1000*$O$14,"") + + + + IFERROR(J35/1000*$P$14,"") + + + + IFERROR(K35/1000*$Q$14,"") + + + + SUM(O35:Q35) + + + + + + IFERROR(U34+Variables!$B$15,"") + + + + IFERROR(O35*U35,"") + + + + IFERROR(P35*U35,"") + + + + IFERROR(Q35*U35,"") + + + + SUM(V35:X35) + + + + + + -Y35*Variables!$B$16 + + + + -SUM(Y35,AB35)*Variables!$B$17 + + + + -SUM(Y35,AB35)*Variables!$B$18 + + + + -SUM(Y35,AB35)*0.25 + + + + Y35+SUM(AB35:AE35) + + + + + + 0 + + + IF(D35=0,AJ34*Variables!$B$22,"") + + + + IF(D35=0,AK34*Variables!$B$24,"") + + + + IF(D35=0,AL34*Variables!$B$26,"") + + + + IF(D35=0,AM34*Variables!$B$28,"") + + + + IF(D35=0,AN34+Variables!$B$30,"") + + + + IFERROR(AF35+(SUM(AI35:AN35)),0) + + + + + + + + IF(C35<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C35,1),"") + + + + IFERROR(IF(ISNUMBER(E36),0,1),"") + + + + IFERROR(YEAR(C36),"") + + + + IF(D36=0,F35*Variables!$B$7,"") + + + + IF(D36=0,G35*Variables!$B$7,"") + + + + IF(D36=0,H35*Variables!$B$7,"") + + + + IFERROR((F36*Variables!$B$8),"") + + + + IFERROR((G36*Variables!$B$9),"") + + + + IFERROR((H36*Variables!$B$10),"") + + + + IFERROR(SUM(I36:K36),"") + + + + + + IFERROR(I36/1000*$O$14,"") + + + + IFERROR(J36/1000*$P$14,"") + + + + IFERROR(K36/1000*$Q$14,"") + + + + SUM(O36:Q36) + + + + + + IFERROR(U35+Variables!$B$15,"") + + + + IFERROR(O36*U36,"") + + + + IFERROR(P36*U36,"") + + + + IFERROR(Q36*U36,"") + + + + SUM(V36:X36) + + + + + + -Y36*Variables!$B$16 + + + + -SUM(Y36,AB36)*Variables!$B$17 + + + + -SUM(Y36,AB36)*Variables!$B$18 + + + + -SUM(Y36,AB36)*0.25 + + + + Y36+SUM(AB36:AE36) + + + + + + 0 + + + IF(D36=0,AJ35*Variables!$B$22,"") + + + + IF(D36=0,AK35*Variables!$B$24,"") + + + + IF(D36=0,AL35*Variables!$B$26,"") + + + + IF(D36=0,AM35*Variables!$B$28,"") + + + + IF(D36=0,AN35+Variables!$B$30,"") + + + + IFERROR(AF36+(SUM(AI36:AN36)),0) + + + + + + + + IF(C36<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C36,1),"") + + + + IFERROR(IF(ISNUMBER(E37),0,1),"") + + + + IFERROR(YEAR(C37),"") + + + + IF(D37=0,F36*Variables!$B$7,"") + + + + IF(D37=0,G36*Variables!$B$7,"") + + + + IF(D37=0,H36*Variables!$B$7,"") + + + + IFERROR((F37*Variables!$B$8),"") + + + + IFERROR((G37*Variables!$B$9),"") + + + + IFERROR((H37*Variables!$B$10),"") + + + + IFERROR(SUM(I37:K37),"") + + + + + + IFERROR(I37/1000*$O$14,"") + + + + IFERROR(J37/1000*$P$14,"") + + + + IFERROR(K37/1000*$Q$14,"") + + + + SUM(O37:Q37) + + + + + + IFERROR(U36+Variables!$B$15,"") + + + + IFERROR(O37*U37,"") + + + + IFERROR(P37*U37,"") + + + + IFERROR(Q37*U37,"") + + + + SUM(V37:X37) + + + + + + -Y37*Variables!$B$16 + + + + -SUM(Y37,AB37)*Variables!$B$17 + + + + -SUM(Y37,AB37)*Variables!$B$18 + + + + -SUM(Y37,AB37)*0.25 + + + + Y37+SUM(AB37:AE37) + + + + + + 0 + + + IF(D37=0,AJ36*Variables!$B$22,"") + + + + IF(D37=0,AK36*Variables!$B$24,"") + + + + IF(D37=0,AL36*Variables!$B$26,"") + + + + IF(D37=0,AM36*Variables!$B$28,"") + + + + IF(D37=0,AN36+Variables!$B$30,"") + + + + IFERROR(AF37+(SUM(AI37:AN37)),0) + + + + + + + + IF(C37<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C37,1),"") + + + + IFERROR(IF(ISNUMBER(E38),0,1),"") + + + + IFERROR(YEAR(C38),"") + + + + IF(D38=0,F37*Variables!$B$7,"") + + + + IF(D38=0,G37*Variables!$B$7,"") + + + + IF(D38=0,H37*Variables!$B$7,"") + + + + IFERROR((F38*Variables!$B$8),"") + + + + IFERROR((G38*Variables!$B$9),"") + + + + IFERROR((H38*Variables!$B$10),"") + + + + IFERROR(SUM(I38:K38),"") + + + + + + IFERROR(I38/1000*$O$14,"") + + + + IFERROR(J38/1000*$P$14,"") + + + + IFERROR(K38/1000*$Q$14,"") + + + + SUM(O38:Q38) + + + + + + IFERROR(U37+Variables!$B$15,"") + + + + IFERROR(O38*U38,"") + + + + IFERROR(P38*U38,"") + + + + IFERROR(Q38*U38,"") + + + + SUM(V38:X38) + + + + + + -Y38*Variables!$B$16 + + + + -SUM(Y38,AB38)*Variables!$B$17 + + + + -SUM(Y38,AB38)*Variables!$B$18 + + + + -SUM(Y38,AB38)*0.25 + + + + Y38+SUM(AB38:AE38) + + + + + + 0 + + + IF(D38=0,AJ37*Variables!$B$22,"") + + + + IF(D38=0,AK37*Variables!$B$24,"") + + + + IF(D38=0,AL37*Variables!$B$26,"") + + + + IF(D38=0,AM37*Variables!$B$28,"") + + + + IF(D38=0,AN37+Variables!$B$30,"") + + + + IFERROR(AF38+(SUM(AI38:AN38)),0) + + + + + + + + IF(C38<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C38,1),"") + + + + IFERROR(IF(ISNUMBER(E39),0,1),"") + + + + IFERROR(YEAR(C39),"") + + + + IF(D39=0,F38*Variables!$B$7,"") + + + + IF(D39=0,G38*Variables!$B$7,"") + + + + IF(D39=0,H38*Variables!$B$7,"") + + + + IFERROR((F39*Variables!$B$8),"") + + + + IFERROR((G39*Variables!$B$9),"") + + + + IFERROR((H39*Variables!$B$10),"") + + + + IFERROR(SUM(I39:K39),"") + + + + + + IFERROR(I39/1000*$O$14,"") + + + + IFERROR(J39/1000*$P$14,"") + + + + IFERROR(K39/1000*$Q$14,"") + + + + SUM(O39:Q39) + + + + + + IFERROR(U38+Variables!$B$15,"") + + + + IFERROR(O39*U39,"") + + + + IFERROR(P39*U39,"") + + + + IFERROR(Q39*U39,"") + + + + SUM(V39:X39) + + + + + + -Y39*Variables!$B$16 + + + + -SUM(Y39,AB39)*Variables!$B$17 + + + + -SUM(Y39,AB39)*Variables!$B$18 + + + + -SUM(Y39,AB39)*0.25 + + + + Y39+SUM(AB39:AE39) + + + + + + 0 + + + IF(D39=0,AJ38*Variables!$B$22,"") + + + + IF(D39=0,AK38*Variables!$B$24,"") + + + + IF(D39=0,AL38*Variables!$B$26,"") + + + + IF(D39=0,AM38*Variables!$B$28,"") + + + + IF(D39=0,AN38+Variables!$B$30,"") + + + + IFERROR(AF39+(SUM(AI39:AN39)),0) + + + + + + + + IF(C39<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C39,1),"") + + + + IFERROR(IF(ISNUMBER(E40),0,1),"") + + + + IFERROR(YEAR(C40),"") + + + + IF(D40=0,F39*Variables!$B$7,"") + + + + IF(D40=0,G39*Variables!$B$7,"") + + + + IF(D40=0,H39*Variables!$B$7,"") + + + + IFERROR((F40*Variables!$B$8),"") + + + + IFERROR((G40*Variables!$B$9),"") + + + + IFERROR((H40*Variables!$B$10),"") + + + + IFERROR(SUM(I40:K40),"") + + + + + + IFERROR(I40/1000*$O$14,"") + + + + IFERROR(J40/1000*$P$14,"") + + + + IFERROR(K40/1000*$Q$14,"") + + + + SUM(O40:Q40) + + + + + + IFERROR(U39+Variables!$B$15,"") + + + + IFERROR(O40*U40,"") + + + + IFERROR(P40*U40,"") + + + + IFERROR(Q40*U40,"") + + + + SUM(V40:X40) + + + + + + -Y40*Variables!$B$16 + + + + -SUM(Y40,AB40)*Variables!$B$17 + + + + -SUM(Y40,AB40)*Variables!$B$18 + + + + -SUM(Y40,AB40)*0.25 + + + + Y40+SUM(AB40:AE40) + + + + + + 0 + + + IF(D40=0,AJ39*Variables!$B$22,"") + + + + IF(D40=0,AK39*Variables!$B$24,"") + + + + IF(D40=0,AL39*Variables!$B$26,"") + + + + IF(D40=0,AM39*Variables!$B$28,"") + + + + IF(D40=0,AN39+Variables!$B$30,"") + + + + IFERROR(AF40+(SUM(AI40:AN40)),0) + + + + + + + + IF(C40<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C40,1),"") + + + + IFERROR(IF(ISNUMBER(E41),0,1),"") + + + + IFERROR(YEAR(C41),"") + + + + IF(D41=0,F40*Variables!$B$7,"") + + + + IF(D41=0,G40*Variables!$B$7,"") + + + + IF(D41=0,H40*Variables!$B$7,"") + + + + IFERROR((F41*Variables!$B$8),"") + + + + IFERROR((G41*Variables!$B$9),"") + + + + IFERROR((H41*Variables!$B$10),"") + + + + IFERROR(SUM(I41:K41),"") + + + + + + IFERROR(I41/1000*$O$14,"") + + + + IFERROR(J41/1000*$P$14,"") + + + + IFERROR(K41/1000*$Q$14,"") + + + + SUM(O41:Q41) + + + + + + IFERROR(U40+Variables!$B$15,"") + + + + IFERROR(O41*U41,"") + + + + IFERROR(P41*U41,"") + + + + IFERROR(Q41*U41,"") + + + + SUM(V41:X41) + + + + + + -Y41*Variables!$B$16 + + + + -SUM(Y41,AB41)*Variables!$B$17 + + + + -SUM(Y41,AB41)*Variables!$B$18 + + + + -SUM(Y41,AB41)*0.25 + + + + Y41+SUM(AB41:AE41) + + + + + + 0 + + + IF(D41=0,AJ40*Variables!$B$22,"") + + + + IF(D41=0,AK40*Variables!$B$24,"") + + + + IF(D41=0,AL40*Variables!$B$26,"") + + + + IF(D41=0,AM40*Variables!$B$28,"") + + + + IF(D41=0,AN40+Variables!$B$30,"") + + + + IFERROR(AF41+(SUM(AI41:AN41)),0) + + + + + + + + IF(C41<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C41,1),"") + + + + IFERROR(IF(ISNUMBER(E42),0,1),"") + + + + IFERROR(YEAR(C42),"") + + + + IF(D42=0,F41*Variables!$B$7,"") + + + + IF(D42=0,G41*Variables!$B$7,"") + + + + IF(D42=0,H41*Variables!$B$7,"") + + + + IFERROR((F42*Variables!$B$8),"") + + + + IFERROR((G42*Variables!$B$9),"") + + + + IFERROR((H42*Variables!$B$10),"") + + + + IFERROR(SUM(I42:K42),"") + + + + + + IFERROR(I42/1000*$O$14,"") + + + + IFERROR(J42/1000*$P$14,"") + + + + IFERROR(K42/1000*$Q$14,"") + + + + SUM(O42:Q42) + + + + + + IFERROR(U41+Variables!$B$15,"") + + + + IFERROR(O42*U42,"") + + + + IFERROR(P42*U42,"") + + + + IFERROR(Q42*U42,"") + + + + SUM(V42:X42) + + + + + + -Y42*Variables!$B$16 + + + + -SUM(Y42,AB42)*Variables!$B$17 + + + + -SUM(Y42,AB42)*Variables!$B$18 + + + + -SUM(Y42,AB42)*0.25 + + + + Y42+SUM(AB42:AE42) + + + + + + 0 + + + IF(D42=0,AJ41*Variables!$B$22,"") + + + + IF(D42=0,AK41*Variables!$B$24,"") + + + + IF(D42=0,AL41*Variables!$B$26,"") + + + + IF(D42=0,AM41*Variables!$B$28,"") + + + + IF(D42=0,AN41+Variables!$B$30,"") + + + + IFERROR(AF42+(SUM(AI42:AN42)),0) + + + + + + + + IF(C42<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C42,1),"") + + + + IFERROR(IF(ISNUMBER(E43),0,1),"") + + + + IFERROR(YEAR(C43),"") + + + + IF(D43=0,F42*Variables!$B$7,"") + + + + IF(D43=0,G42*Variables!$B$7,"") + + + + IF(D43=0,H42*Variables!$B$7,"") + + + + IFERROR((F43*Variables!$B$8),"") + + + + IFERROR((G43*Variables!$B$9),"") + + + + IFERROR((H43*Variables!$B$10),"") + + + + IFERROR(SUM(I43:K43),"") + + + + + + IFERROR(I43/1000*$O$14,"") + + + + IFERROR(J43/1000*$P$14,"") + + + + IFERROR(K43/1000*$Q$14,"") + + + + SUM(O43:Q43) + + + + + + IFERROR(U42+Variables!$B$15,"") + + + + IFERROR(O43*U43,"") + + + + IFERROR(P43*U43,"") + + + + IFERROR(Q43*U43,"") + + + + SUM(V43:X43) + + + + + + -Y43*Variables!$B$16 + + + + -SUM(Y43,AB43)*Variables!$B$17 + + + + -SUM(Y43,AB43)*Variables!$B$18 + + + + -SUM(Y43,AB43)*0.25 + + + + Y43+SUM(AB43:AE43) + + + + + + 0 + + + IF(D43=0,AJ42*Variables!$B$22,"") + + + + IF(D43=0,AK42*Variables!$B$24,"") + + + + IF(D43=0,AL42*Variables!$B$26,"") + + + + IF(D43=0,AM42*Variables!$B$28,"") + + + + IF(D43=0,AN42+Variables!$B$30,"") + + + + IFERROR(AF43+(SUM(AI43:AN43)),0) + + + + + + + + IF(C43<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C43,1),"") + + + + IFERROR(IF(ISNUMBER(E44),0,1),"") + + + + IFERROR(YEAR(C44),"") + + + + IF(D44=0,F43*Variables!$B$7,"") + + + + IF(D44=0,G43*Variables!$B$7,"") + + + + IF(D44=0,H43*Variables!$B$7,"") + + + + IFERROR((F44*Variables!$B$8),"") + + + + IFERROR((G44*Variables!$B$9),"") + + + + IFERROR((H44*Variables!$B$10),"") + + + + IFERROR(SUM(I44:K44),"") + + + + + + IFERROR(I44/1000*$O$14,"") + + + + IFERROR(J44/1000*$P$14,"") + + + + IFERROR(K44/1000*$Q$14,"") + + + + SUM(O44:Q44) + + + + + + IFERROR(U43+Variables!$B$15,"") + + + + IFERROR(O44*U44,"") + + + + IFERROR(P44*U44,"") + + + + IFERROR(Q44*U44,"") + + + + SUM(V44:X44) + + + + + + -Y44*Variables!$B$16 + + + + -SUM(Y44,AB44)*Variables!$B$17 + + + + -SUM(Y44,AB44)*Variables!$B$18 + + + + -SUM(Y44,AB44)*0.25 + + + + Y44+SUM(AB44:AE44) + + + + + + 0 + + + IF(D44=0,AJ43*Variables!$B$22,"") + + + + IF(D44=0,AK43*Variables!$B$24,"") + + + + IF(D44=0,AL43*Variables!$B$26,"") + + + + IF(D44=0,AM43*Variables!$B$28,"") + + + + IF(D44=0,AN43+Variables!$B$30,"") + + + + IFERROR(AF44+(SUM(AI44:AN44)),0) + + + + + + + + IF(C44<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C44,1),"") + + + + IFERROR(IF(ISNUMBER(E45),0,1),"") + + + + IFERROR(YEAR(C45),"") + + + + IF(D45=0,F44*Variables!$B$7,"") + + + + IF(D45=0,G44*Variables!$B$7,"") + + + + IF(D45=0,H44*Variables!$B$7,"") + + + + IFERROR((F45*Variables!$B$8),"") + + + + IFERROR((G45*Variables!$B$9),"") + + + + IFERROR((H45*Variables!$B$10),"") + + + + IFERROR(SUM(I45:K45),"") + + + + + + IFERROR(I45/1000*$O$14,"") + + + + IFERROR(J45/1000*$P$14,"") + + + + IFERROR(K45/1000*$Q$14,"") + + + + SUM(O45:Q45) + + + + + + IFERROR(U44+Variables!$B$15,"") + + + + IFERROR(O45*U45,"") + + + + IFERROR(P45*U45,"") + + + + IFERROR(Q45*U45,"") + + + + SUM(V45:X45) + + + + + + -Y45*Variables!$B$16 + + + + -SUM(Y45,AB45)*Variables!$B$17 + + + + -SUM(Y45,AB45)*Variables!$B$18 + + + + -SUM(Y45,AB45)*0.25 + + + + Y45+SUM(AB45:AE45) + + + + + + 0 + + + IF(D45=0,AJ44*Variables!$B$22,"") + + + + IF(D45=0,AK44*Variables!$B$24,"") + + + + IF(D45=0,AL44*Variables!$B$26,"") + + + + IF(D45=0,AM44*Variables!$B$28,"") + + + + IF(D45=0,AN44+Variables!$B$30,"") + + + + IFERROR(AF45+(SUM(AI45:AN45)),0) + + + + + + + + IF(C45<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C45,1),"") + + + + IFERROR(IF(ISNUMBER(E46),0,1),"") + + + + IFERROR(YEAR(C46),"") + + + + IF(D46=0,F45*Variables!$B$7,"") + + + + IF(D46=0,G45*Variables!$B$7,"") + + + + IF(D46=0,H45*Variables!$B$7,"") + + + + IFERROR((F46*Variables!$B$8),"") + + + + IFERROR((G46*Variables!$B$9),"") + + + + IFERROR((H46*Variables!$B$10),"") + + + + IFERROR(SUM(I46:K46),"") + + + + + + IFERROR(I46/1000*$O$14,"") + + + + IFERROR(J46/1000*$P$14,"") + + + + IFERROR(K46/1000*$Q$14,"") + + + + SUM(O46:Q46) + + + + + + IFERROR(U45+Variables!$B$15,"") + + + + IFERROR(O46*U46,"") + + + + IFERROR(P46*U46,"") + + + + IFERROR(Q46*U46,"") + + + + SUM(V46:X46) + + + + + + -Y46*Variables!$B$16 + + + + -SUM(Y46,AB46)*Variables!$B$17 + + + + -SUM(Y46,AB46)*Variables!$B$18 + + + + -SUM(Y46,AB46)*0.25 + + + + Y46+SUM(AB46:AE46) + + + + + + 0 + + + IF(D46=0,AJ45*Variables!$B$22,"") + + + + IF(D46=0,AK45*Variables!$B$24,"") + + + + IF(D46=0,AL45*Variables!$B$26,"") + + + + IF(D46=0,AM45*Variables!$B$28,"") + + + + IF(D46=0,AN45+Variables!$B$30,"") + + + + IFERROR(AF46+(SUM(AI46:AN46)),0) + + + + + + + + IF(C46<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C46,1),"") + + + + IFERROR(IF(ISNUMBER(E47),0,1),"") + + + + IFERROR(YEAR(C47),"") + + + + IF(D47=0,F46*Variables!$B$7,"") + + + + IF(D47=0,G46*Variables!$B$7,"") + + + + IF(D47=0,H46*Variables!$B$7,"") + + + + IFERROR((F47*Variables!$B$8),"") + + + + IFERROR((G47*Variables!$B$9),"") + + + + IFERROR((H47*Variables!$B$10),"") + + + + IFERROR(SUM(I47:K47),"") + + + + + + IFERROR(I47/1000*$O$14,"") + + + + IFERROR(J47/1000*$P$14,"") + + + + IFERROR(K47/1000*$Q$14,"") + + + + SUM(O47:Q47) + + + + + + IFERROR(U46+Variables!$B$15,"") + + + + IFERROR(O47*U47,"") + + + + IFERROR(P47*U47,"") + + + + IFERROR(Q47*U47,"") + + + + SUM(V47:X47) + + + + + + -Y47*Variables!$B$16 + + + + -SUM(Y47,AB47)*Variables!$B$17 + + + + -SUM(Y47,AB47)*Variables!$B$18 + + + + -SUM(Y47,AB47)*0.25 + + + + Y47+SUM(AB47:AE47) + + + + + + 0 + + + IF(D47=0,AJ46*Variables!$B$22,"") + + + + IF(D47=0,AK46*Variables!$B$24,"") + + + + IF(D47=0,AL46*Variables!$B$26,"") + + + + IF(D47=0,AM46*Variables!$B$28,"") + + + + IF(D47=0,AN46+Variables!$B$30,"") + + + + IFERROR(AF47+(SUM(AI47:AN47)),0) + + + + + + + + IF(C47<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C47,1),"") + + + + IFERROR(IF(ISNUMBER(E48),0,1),"") + + + + IFERROR(YEAR(C48),"") + + + + IF(D48=0,F47*Variables!$B$7,"") + + + + IF(D48=0,G47*Variables!$B$7,"") + + + + IF(D48=0,H47*Variables!$B$7,"") + + + + IFERROR((F48*Variables!$B$8),"") + + + + IFERROR((G48*Variables!$B$9),"") + + + + IFERROR((H48*Variables!$B$10),"") + + + + IFERROR(SUM(I48:K48),"") + + + + + + IFERROR(I48/1000*$O$14,"") + + + + IFERROR(J48/1000*$P$14,"") + + + + IFERROR(K48/1000*$Q$14,"") + + + + SUM(O48:Q48) + + + + + + IFERROR(U47+Variables!$B$15,"") + + + + IFERROR(O48*U48,"") + + + + IFERROR(P48*U48,"") + + + + IFERROR(Q48*U48,"") + + + + SUM(V48:X48) + + + + + + -Y48*Variables!$B$16 + + + + -SUM(Y48,AB48)*Variables!$B$17 + + + + -SUM(Y48,AB48)*Variables!$B$18 + + + + -SUM(Y48,AB48)*0.25 + + + + Y48+SUM(AB48:AE48) + + + + + + 0 + + + IF(D48=0,AJ47*Variables!$B$22,"") + + + + IF(D48=0,AK47*Variables!$B$24,"") + + + + IF(D48=0,AL47*Variables!$B$26,"") + + + + IF(D48=0,AM47*Variables!$B$28,"") + + + + IF(D48=0,AN47+Variables!$B$30,"") + + + + IFERROR(AF48+(SUM(AI48:AN48)),0) + + + + + + + + IF(C48<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C48,1),"") + + + + IFERROR(IF(ISNUMBER(E49),0,1),"") + + + + IFERROR(YEAR(C49),"") + + + + IF(D49=0,F48*Variables!$B$7,"") + + + + IF(D49=0,G48*Variables!$B$7,"") + + + + IF(D49=0,H48*Variables!$B$7,"") + + + + IFERROR((F49*Variables!$B$8),"") + + + + IFERROR((G49*Variables!$B$9),"") + + + + IFERROR((H49*Variables!$B$10),"") + + + + IFERROR(SUM(I49:K49),"") + + + + + + IFERROR(I49/1000*$O$14,"") + + + + IFERROR(J49/1000*$P$14,"") + + + + IFERROR(K49/1000*$Q$14,"") + + + + SUM(O49:Q49) + + + + + + IFERROR(U48+Variables!$B$15,"") + + + + IFERROR(O49*U49,"") + + + + IFERROR(P49*U49,"") + + + + IFERROR(Q49*U49,"") + + + + SUM(V49:X49) + + + + + + -Y49*Variables!$B$16 + + + + -SUM(Y49,AB49)*Variables!$B$17 + + + + -SUM(Y49,AB49)*Variables!$B$18 + + + + -SUM(Y49,AB49)*0.25 + + + + Y49+SUM(AB49:AE49) + + + + + + 0 + + + IF(D49=0,AJ48*Variables!$B$22,"") + + + + IF(D49=0,AK48*Variables!$B$24,"") + + + + IF(D49=0,AL48*Variables!$B$26,"") + + + + IF(D49=0,AM48*Variables!$B$28,"") + + + + IF(D49=0,AN48+Variables!$B$30,"") + + + + IFERROR(AF49+(SUM(AI49:AN49)),0) + + + + + + + + IF(C49<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C49,1),"") + + + + IFERROR(IF(ISNUMBER(E50),0,1),"") + + + + IFERROR(YEAR(C50),"") + + + + IF(D50=0,F49*Variables!$B$7,"") + + + + IF(D50=0,G49*Variables!$B$7,"") + + + + IF(D50=0,H49*Variables!$B$7,"") + + + + IFERROR((F50*Variables!$B$8),"") + + + + IFERROR((G50*Variables!$B$9),"") + + + + IFERROR((H50*Variables!$B$10),"") + + + + IFERROR(SUM(I50:K50),"") + + + + + + IFERROR(I50/1000*$O$14,"") + + + + IFERROR(J50/1000*$P$14,"") + + + + IFERROR(K50/1000*$Q$14,"") + + + + SUM(O50:Q50) + + + + + + IFERROR(U49+Variables!$B$15,"") + + + + IFERROR(O50*U50,"") + + + + IFERROR(P50*U50,"") + + + + IFERROR(Q50*U50,"") + + + + SUM(V50:X50) + + + + + + -Y50*Variables!$B$16 + + + + -SUM(Y50,AB50)*Variables!$B$17 + + + + -SUM(Y50,AB50)*Variables!$B$18 + + + + -SUM(Y50,AB50)*0.25 + + + + Y50+SUM(AB50:AE50) + + + + + + 0 + + + IF(D50=0,AJ49*Variables!$B$22,"") + + + + IF(D50=0,AK49*Variables!$B$24,"") + + + + IF(D50=0,AL49*Variables!$B$26,"") + + + + IF(D50=0,AM49*Variables!$B$28,"") + + + + IF(D50=0,AN49+Variables!$B$30,"") + + + + IFERROR(AF50+(SUM(AI50:AN50)),0) + + + + + + + + IF(C50<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C50,1),"") + + + + IFERROR(IF(ISNUMBER(E51),0,1),"") + + + + IFERROR(YEAR(C51),"") + + + + IF(D51=0,F50*Variables!$B$7,"") + + + + IF(D51=0,G50*Variables!$B$7,"") + + + + IF(D51=0,H50*Variables!$B$7,"") + + + + IFERROR((F51*Variables!$B$8),"") + + + + IFERROR((G51*Variables!$B$9),"") + + + + IFERROR((H51*Variables!$B$10),"") + + + + IFERROR(SUM(I51:K51),"") + + + + + + IFERROR(I51/1000*$O$14,"") + + + + IFERROR(J51/1000*$P$14,"") + + + + IFERROR(K51/1000*$Q$14,"") + + + + SUM(O51:Q51) + + + + + + IFERROR(U50+Variables!$B$15,"") + + + + IFERROR(O51*U51,"") + + + + IFERROR(P51*U51,"") + + + + IFERROR(Q51*U51,"") + + + + SUM(V51:X51) + + + + + + -Y51*Variables!$B$16 + + + + -SUM(Y51,AB51)*Variables!$B$17 + + + + -SUM(Y51,AB51)*Variables!$B$18 + + + + -SUM(Y51,AB51)*0.25 + + + + Y51+SUM(AB51:AE51) + + + + + + 0 + + + IF(D51=0,AJ50*Variables!$B$22,"") + + + + IF(D51=0,AK50*Variables!$B$24,"") + + + + IF(D51=0,AL50*Variables!$B$26,"") + + + + IF(D51=0,AM50*Variables!$B$28,"") + + + + IF(D51=0,AN50+Variables!$B$30,"") + + + + IFERROR(AF51+(SUM(AI51:AN51)),0) + + + + + + + + IF(C51<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C51,1),"") + + + + IFERROR(IF(ISNUMBER(E52),0,1),"") + + + + IFERROR(YEAR(C52),"") + + + + IF(D52=0,F51*Variables!$B$7,"") + + + + IF(D52=0,G51*Variables!$B$7,"") + + + + IF(D52=0,H51*Variables!$B$7,"") + + + + IFERROR((F52*Variables!$B$8),"") + + + + IFERROR((G52*Variables!$B$9),"") + + + + IFERROR((H52*Variables!$B$10),"") + + + + IFERROR(SUM(I52:K52),"") + + + + + + IFERROR(I52/1000*$O$14,"") + + + + IFERROR(J52/1000*$P$14,"") + + + + IFERROR(K52/1000*$Q$14,"") + + + + SUM(O52:Q52) + + + + + + IFERROR(U51+Variables!$B$15,"") + + + + IFERROR(O52*U52,"") + + + + IFERROR(P52*U52,"") + + + + IFERROR(Q52*U52,"") + + + + SUM(V52:X52) + + + + + + -Y52*Variables!$B$16 + + + + -SUM(Y52,AB52)*Variables!$B$17 + + + + -SUM(Y52,AB52)*Variables!$B$18 + + + + -SUM(Y52,AB52)*0.25 + + + + Y52+SUM(AB52:AE52) + + + + + + 0 + + + IF(D52=0,AJ51*Variables!$B$22,"") + + + + IF(D52=0,AK51*Variables!$B$24,"") + + + + IF(D52=0,AL51*Variables!$B$26,"") + + + + IF(D52=0,AM51*Variables!$B$28,"") + + + + IF(D52=0,AN51+Variables!$B$30,"") + + + + IFERROR(AF52+(SUM(AI52:AN52)),0) + + + + + + + + IF(C52<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C52,1),"") + + + + IFERROR(IF(ISNUMBER(E53),0,1),"") + + + + IFERROR(YEAR(C53),"") + + + + IF(D53=0,F52*Variables!$B$7,"") + + + + IF(D53=0,G52*Variables!$B$7,"") + + + + IF(D53=0,H52*Variables!$B$7,"") + + + + IFERROR((F53*Variables!$B$8),"") + + + + IFERROR((G53*Variables!$B$9),"") + + + + IFERROR((H53*Variables!$B$10),"") + + + + IFERROR(SUM(I53:K53),"") + + + + + + IFERROR(I53/1000*$O$14,"") + + + + IFERROR(J53/1000*$P$14,"") + + + + IFERROR(K53/1000*$Q$14,"") + + + + SUM(O53:Q53) + + + + + + IFERROR(U52+Variables!$B$15,"") + + + + IFERROR(O53*U53,"") + + + + IFERROR(P53*U53,"") + + + + IFERROR(Q53*U53,"") + + + + SUM(V53:X53) + + + + + + -Y53*Variables!$B$16 + + + + -SUM(Y53,AB53)*Variables!$B$17 + + + + -SUM(Y53,AB53)*Variables!$B$18 + + + + -SUM(Y53,AB53)*0.25 + + + + Y53+SUM(AB53:AE53) + + + + + + 0 + + + IF(D53=0,AJ52*Variables!$B$22,"") + + + + IF(D53=0,AK52*Variables!$B$24,"") + + + + IF(D53=0,AL52*Variables!$B$26,"") + + + + IF(D53=0,AM52*Variables!$B$28,"") + + + + IF(D53=0,AN52+Variables!$B$30,"") + + + + IFERROR(AF53+(SUM(AI53:AN53)),0) + + + + + + + + IF(C53<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C53,1),"") + + + + IFERROR(IF(ISNUMBER(E54),0,1),"") + + + + IFERROR(YEAR(C54),"") + + + + IF(D54=0,F53*Variables!$B$7,"") + + + + IF(D54=0,G53*Variables!$B$7,"") + + + + IF(D54=0,H53*Variables!$B$7,"") + + + + IFERROR((F54*Variables!$B$8),"") + + + + IFERROR((G54*Variables!$B$9),"") + + + + IFERROR((H54*Variables!$B$10),"") + + + + IFERROR(SUM(I54:K54),"") + + + + + + IFERROR(I54/1000*$O$14,"") + + + + IFERROR(J54/1000*$P$14,"") + + + + IFERROR(K54/1000*$Q$14,"") + + + + SUM(O54:Q54) + + + + + + IFERROR(U53+Variables!$B$15,"") + + + + IFERROR(O54*U54,"") + + + + IFERROR(P54*U54,"") + + + + IFERROR(Q54*U54,"") + + + + SUM(V54:X54) + + + + + + -Y54*Variables!$B$16 + + + + -SUM(Y54,AB54)*Variables!$B$17 + + + + -SUM(Y54,AB54)*Variables!$B$18 + + + + -SUM(Y54,AB54)*0.25 + + + + Y54+SUM(AB54:AE54) + + + + + + 0 + + + IF(D54=0,AJ53*Variables!$B$22,"") + + + + IF(D54=0,AK53*Variables!$B$24,"") + + + + IF(D54=0,AL53*Variables!$B$26,"") + + + + IF(D54=0,AM53*Variables!$B$28,"") + + + + IF(D54=0,AN53+Variables!$B$30,"") + + + + IFERROR(AF54+(SUM(AI54:AN54)),0) + + + + + + + + IF(C54<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C54,1),"") + + + + IFERROR(IF(ISNUMBER(E55),0,1),"") + + + + IFERROR(YEAR(C55),"") + + + + IF(D55=0,F54*Variables!$B$7,"") + + + + IF(D55=0,G54*Variables!$B$7,"") + + + + IF(D55=0,H54*Variables!$B$7,"") + + + + IFERROR((F55*Variables!$B$8),"") + + + + IFERROR((G55*Variables!$B$9),"") + + + + IFERROR((H55*Variables!$B$10),"") + + + + IFERROR(SUM(I55:K55),"") + + + + + + IFERROR(I55/1000*$O$14,"") + + + + IFERROR(J55/1000*$P$14,"") + + + + IFERROR(K55/1000*$Q$14,"") + + + + SUM(O55:Q55) + + + + + + IFERROR(U54+Variables!$B$15,"") + + + + IFERROR(O55*U55,"") + + + + IFERROR(P55*U55,"") + + + + IFERROR(Q55*U55,"") + + + + SUM(V55:X55) + + + + + + -Y55*Variables!$B$16 + + + + -SUM(Y55,AB55)*Variables!$B$17 + + + + -SUM(Y55,AB55)*Variables!$B$18 + + + + -SUM(Y55,AB55)*0.25 + + + + Y55+SUM(AB55:AE55) + + + + + + 0 + + + IF(D55=0,AJ54*Variables!$B$22,"") + + + + IF(D55=0,AK54*Variables!$B$24,"") + + + + IF(D55=0,AL54*Variables!$B$26,"") + + + + IF(D55=0,AM54*Variables!$B$28,"") + + + + IF(D55=0,AN54+Variables!$B$30,"") + + + + IFERROR(AF55+(SUM(AI55:AN55)),0) + + + + + + + + IF(C55<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C55,1),"") + + + + IFERROR(IF(ISNUMBER(E56),0,1),"") + + + + IFERROR(YEAR(C56),"") + + + + IF(D56=0,F55*Variables!$B$7,"") + + + + IF(D56=0,G55*Variables!$B$7,"") + + + + IF(D56=0,H55*Variables!$B$7,"") + + + + IFERROR((F56*Variables!$B$8),"") + + + + IFERROR((G56*Variables!$B$9),"") + + + + IFERROR((H56*Variables!$B$10),"") + + + + IFERROR(SUM(I56:K56),"") + + + + + + IFERROR(I56/1000*$O$14,"") + + + + IFERROR(J56/1000*$P$14,"") + + + + IFERROR(K56/1000*$Q$14,"") + + + + SUM(O56:Q56) + + + + + + IFERROR(U55+Variables!$B$15,"") + + + + IFERROR(O56*U56,"") + + + + IFERROR(P56*U56,"") + + + + IFERROR(Q56*U56,"") + + + + SUM(V56:X56) + + + + + + -Y56*Variables!$B$16 + + + + -SUM(Y56,AB56)*Variables!$B$17 + + + + -SUM(Y56,AB56)*Variables!$B$18 + + + + -SUM(Y56,AB56)*0.25 + + + + Y56+SUM(AB56:AE56) + + + + + + 0 + + + IF(D56=0,AJ55*Variables!$B$22,"") + + + + IF(D56=0,AK55*Variables!$B$24,"") + + + + IF(D56=0,AL55*Variables!$B$26,"") + + + + IF(D56=0,AM55*Variables!$B$28,"") + + + + IF(D56=0,AN55+Variables!$B$30,"") + + + + IFERROR(AF56+(SUM(AI56:AN56)),0) + + + + + + + + IF(C56<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C56,1),"") + + + + IFERROR(IF(ISNUMBER(E57),0,1),"") + + + + IFERROR(YEAR(C57),"") + + + + IF(D57=0,F56*Variables!$B$7,"") + + + + IF(D57=0,G56*Variables!$B$7,"") + + + + IF(D57=0,H56*Variables!$B$7,"") + + + + IFERROR((F57*Variables!$B$8),"") + + + + IFERROR((G57*Variables!$B$9),"") + + + + IFERROR((H57*Variables!$B$10),"") + + + + IFERROR(SUM(I57:K57),"") + + + + + + IFERROR(I57/1000*$O$14,"") + + + + IFERROR(J57/1000*$P$14,"") + + + + IFERROR(K57/1000*$Q$14,"") + + + + SUM(O57:Q57) + + + + + + IFERROR(U56+Variables!$B$15,"") + + + + IFERROR(O57*U57,"") + + + + IFERROR(P57*U57,"") + + + + IFERROR(Q57*U57,"") + + + + SUM(V57:X57) + + + + + + -Y57*Variables!$B$16 + + + + -SUM(Y57,AB57)*Variables!$B$17 + + + + -SUM(Y57,AB57)*Variables!$B$18 + + + + -SUM(Y57,AB57)*0.25 + + + + Y57+SUM(AB57:AE57) + + + + + + 0 + + + IF(D57=0,AJ56*Variables!$B$22,"") + + + + IF(D57=0,AK56*Variables!$B$24,"") + + + + IF(D57=0,AL56*Variables!$B$26,"") + + + + IF(D57=0,AM56*Variables!$B$28,"") + + + + IF(D57=0,AN56+Variables!$B$30,"") + + + + IFERROR(AF57+(SUM(AI57:AN57)),0) + + + + + + + + IF(C57<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C57,1),"") + + + + IFERROR(IF(ISNUMBER(E58),0,1),"") + + + + IFERROR(YEAR(C58),"") + + + + IF(D58=0,F57*Variables!$B$7,"") + + + + IF(D58=0,G57*Variables!$B$7,"") + + + + IF(D58=0,H57*Variables!$B$7,"") + + + + IFERROR((F58*Variables!$B$8),"") + + + + IFERROR((G58*Variables!$B$9),"") + + + + IFERROR((H58*Variables!$B$10),"") + + + + IFERROR(SUM(I58:K58),"") + + + + + + IFERROR(I58/1000*$O$14,"") + + + + IFERROR(J58/1000*$P$14,"") + + + + IFERROR(K58/1000*$Q$14,"") + + + + SUM(O58:Q58) + + + + + + IFERROR(U57+Variables!$B$15,"") + + + + IFERROR(O58*U58,"") + + + + IFERROR(P58*U58,"") + + + + IFERROR(Q58*U58,"") + + + + SUM(V58:X58) + + + + + + -Y58*Variables!$B$16 + + + + -SUM(Y58,AB58)*Variables!$B$17 + + + + -SUM(Y58,AB58)*Variables!$B$18 + + + + -SUM(Y58,AB58)*0.25 + + + + Y58+SUM(AB58:AE58) + + + + + + 0 + + + IF(D58=0,AJ57*Variables!$B$22,"") + + + + IF(D58=0,AK57*Variables!$B$24,"") + + + + IF(D58=0,AL57*Variables!$B$26,"") + + + + IF(D58=0,AM57*Variables!$B$28,"") + + + + IF(D58=0,AN57+Variables!$B$30,"") + + + + IFERROR(AF58+(SUM(AI58:AN58)),0) + + + + + + + + IF(C58<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C58,1),"") + + + + IFERROR(IF(ISNUMBER(E59),0,1),"") + + + + IFERROR(YEAR(C59),"") + + + + IF(D59=0,F58*Variables!$B$7,"") + + + + IF(D59=0,G58*Variables!$B$7,"") + + + + IF(D59=0,H58*Variables!$B$7,"") + + + + IFERROR((F59*Variables!$B$8),"") + + + + IFERROR((G59*Variables!$B$9),"") + + + + IFERROR((H59*Variables!$B$10),"") + + + + IFERROR(SUM(I59:K59),"") + + + + + + IFERROR(I59/1000*$O$14,"") + + + + IFERROR(J59/1000*$P$14,"") + + + + IFERROR(K59/1000*$Q$14,"") + + + + SUM(O59:Q59) + + + + + + IFERROR(U58+Variables!$B$15,"") + + + + IFERROR(O59*U59,"") + + + + IFERROR(P59*U59,"") + + + + IFERROR(Q59*U59,"") + + + + SUM(V59:X59) + + + + + + -Y59*Variables!$B$16 + + + + -SUM(Y59,AB59)*Variables!$B$17 + + + + -SUM(Y59,AB59)*Variables!$B$18 + + + + -SUM(Y59,AB59)*0.25 + + + + Y59+SUM(AB59:AE59) + + + + + + 0 + + + IF(D59=0,AJ58*Variables!$B$22,"") + + + + IF(D59=0,AK58*Variables!$B$24,"") + + + + IF(D59=0,AL58*Variables!$B$26,"") + + + + IF(D59=0,AM58*Variables!$B$28,"") + + + + IF(D59=0,AN58+Variables!$B$30,"") + + + + IFERROR(AF59+(SUM(AI59:AN59)),0) + + + + + + + + IF(C59<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C59,1),"") + + + + IFERROR(IF(ISNUMBER(E60),0,1),"") + + + + IFERROR(YEAR(C60),"") + + + + IF(D60=0,F59*Variables!$B$7,"") + + + + IF(D60=0,G59*Variables!$B$7,"") + + + + IF(D60=0,H59*Variables!$B$7,"") + + + + IFERROR((F60*Variables!$B$8),"") + + + + IFERROR((G60*Variables!$B$9),"") + + + + IFERROR((H60*Variables!$B$10),"") + + + + IFERROR(SUM(I60:K60),"") + + + + + + IFERROR(I60/1000*$O$14,"") + + + + IFERROR(J60/1000*$P$14,"") + + + + IFERROR(K60/1000*$Q$14,"") + + + + SUM(O60:Q60) + + + + + + IFERROR(U59+Variables!$B$15,"") + + + + IFERROR(O60*U60,"") + + + + IFERROR(P60*U60,"") + + + + IFERROR(Q60*U60,"") + + + + SUM(V60:X60) + + + + + + -Y60*Variables!$B$16 + + + + -SUM(Y60,AB60)*Variables!$B$17 + + + + -SUM(Y60,AB60)*Variables!$B$18 + + + + -SUM(Y60,AB60)*0.25 + + + + Y60+SUM(AB60:AE60) + + + + + + 0 + + + IF(D60=0,AJ59*Variables!$B$22,"") + + + + IF(D60=0,AK59*Variables!$B$24,"") + + + + IF(D60=0,AL59*Variables!$B$26,"") + + + + IF(D60=0,AM59*Variables!$B$28,"") + + + + IF(D60=0,AN59+Variables!$B$30,"") + + + + IFERROR(AF60+(SUM(AI60:AN60)),0) + + + + + + + + IF(C60<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C60,1),"") + + + + IFERROR(IF(ISNUMBER(E61),0,1),"") + + + + IFERROR(YEAR(C61),"") + + + + IF(D61=0,F60*Variables!$B$7,"") + + + + IF(D61=0,G60*Variables!$B$7,"") + + + + IF(D61=0,H60*Variables!$B$7,"") + + + + IFERROR((F61*Variables!$B$8),"") + + + + IFERROR((G61*Variables!$B$9),"") + + + + IFERROR((H61*Variables!$B$10),"") + + + + IFERROR(SUM(I61:K61),"") + + + + + + IFERROR(I61/1000*$O$14,"") + + + + IFERROR(J61/1000*$P$14,"") + + + + IFERROR(K61/1000*$Q$14,"") + + + + SUM(O61:Q61) + + + + + + IFERROR(U60+Variables!$B$15,"") + + + + IFERROR(O61*U61,"") + + + + IFERROR(P61*U61,"") + + + + IFERROR(Q61*U61,"") + + + + SUM(V61:X61) + + + + + + -Y61*Variables!$B$16 + + + + -SUM(Y61,AB61)*Variables!$B$17 + + + + -SUM(Y61,AB61)*Variables!$B$18 + + + + -SUM(Y61,AB61)*0.25 + + + + Y61+SUM(AB61:AE61) + + + + + + 0 + + + IF(D61=0,AJ60*Variables!$B$22,"") + + + + IF(D61=0,AK60*Variables!$B$24,"") + + + + IF(D61=0,AL60*Variables!$B$26,"") + + + + IF(D61=0,AM60*Variables!$B$28,"") + + + + IF(D61=0,AN60+Variables!$B$30,"") + + + + IFERROR(AF61+(SUM(AI61:AN61)),0) + + + + + + + + IF(C61<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C61,1),"") + + + + IFERROR(IF(ISNUMBER(E62),0,1),"") + + + + IFERROR(YEAR(C62),"") + + + + IF(D62=0,F61*Variables!$B$7,"") + + + + IF(D62=0,G61*Variables!$B$7,"") + + + + IF(D62=0,H61*Variables!$B$7,"") + + + + IFERROR((F62*Variables!$B$8),"") + + + + IFERROR((G62*Variables!$B$9),"") + + + + IFERROR((H62*Variables!$B$10),"") + + + + IFERROR(SUM(I62:K62),"") + + + + + + IFERROR(I62/1000*$O$14,"") + + + + IFERROR(J62/1000*$P$14,"") + + + + IFERROR(K62/1000*$Q$14,"") + + + + SUM(O62:Q62) + + + + + + IFERROR(U61+Variables!$B$15,"") + + + + IFERROR(O62*U62,"") + + + + IFERROR(P62*U62,"") + + + + IFERROR(Q62*U62,"") + + + + SUM(V62:X62) + + + + + + -Y62*Variables!$B$16 + + + + -SUM(Y62,AB62)*Variables!$B$17 + + + + -SUM(Y62,AB62)*Variables!$B$18 + + + + -SUM(Y62,AB62)*0.25 + + + + Y62+SUM(AB62:AE62) + + + + + + 0 + + + IF(D62=0,AJ61*Variables!$B$22,"") + + + + IF(D62=0,AK61*Variables!$B$24,"") + + + + IF(D62=0,AL61*Variables!$B$26,"") + + + + IF(D62=0,AM61*Variables!$B$28,"") + + + + IF(D62=0,AN61+Variables!$B$30,"") + + + + IFERROR(AF62+(SUM(AI62:AN62)),0) + + + + + + + + IF(C62<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C62,1),"") + + + + IFERROR(IF(ISNUMBER(E63),0,1),"") + + + + IFERROR(YEAR(C63),"") + + + + IF(D63=0,F62*Variables!$B$7,"") + + + + IF(D63=0,G62*Variables!$B$7,"") + + + + IF(D63=0,H62*Variables!$B$7,"") + + + + IFERROR((F63*Variables!$B$8),"") + + + + IFERROR((G63*Variables!$B$9),"") + + + + IFERROR((H63*Variables!$B$10),"") + + + + IFERROR(SUM(I63:K63),"") + + + + + + IFERROR(I63/1000*$O$14,"") + + + + IFERROR(J63/1000*$P$14,"") + + + + IFERROR(K63/1000*$Q$14,"") + + + + SUM(O63:Q63) + + + + + + IFERROR(U62+Variables!$B$15,"") + + + + IFERROR(O63*U63,"") + + + + IFERROR(P63*U63,"") + + + + IFERROR(Q63*U63,"") + + + + SUM(V63:X63) + + + + + + -Y63*Variables!$B$16 + + + + -SUM(Y63,AB63)*Variables!$B$17 + + + + -SUM(Y63,AB63)*Variables!$B$18 + + + + -SUM(Y63,AB63)*0.25 + + + + Y63+SUM(AB63:AE63) + + + + + + 0 + + + IF(D63=0,AJ62*Variables!$B$22,"") + + + + IF(D63=0,AK62*Variables!$B$24,"") + + + + IF(D63=0,AL62*Variables!$B$26,"") + + + + IF(D63=0,AM62*Variables!$B$28,"") + + + + IF(D63=0,AN62+Variables!$B$30,"") + + + + IFERROR(AF63+(SUM(AI63:AN63)),0) + + + + + + + + IF(C63<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C63,1),"") + + + + IFERROR(IF(ISNUMBER(E64),0,1),"") + + + + IFERROR(YEAR(C64),"") + + + + IF(D64=0,F63*Variables!$B$7,"") + + + + IF(D64=0,G63*Variables!$B$7,"") + + + + IF(D64=0,H63*Variables!$B$7,"") + + + + IFERROR((F64*Variables!$B$8),"") + + + + IFERROR((G64*Variables!$B$9),"") + + + + IFERROR((H64*Variables!$B$10),"") + + + + IFERROR(SUM(I64:K64),"") + + + + + + IFERROR(I64/1000*$O$14,"") + + + + IFERROR(J64/1000*$P$14,"") + + + + IFERROR(K64/1000*$Q$14,"") + + + + SUM(O64:Q64) + + + + + + IFERROR(U63+Variables!$B$15,"") + + + + IFERROR(O64*U64,"") + + + + IFERROR(P64*U64,"") + + + + IFERROR(Q64*U64,"") + + + + SUM(V64:X64) + + + + + + -Y64*Variables!$B$16 + + + + -SUM(Y64,AB64)*Variables!$B$17 + + + + -SUM(Y64,AB64)*Variables!$B$18 + + + + -SUM(Y64,AB64)*0.25 + + + + Y64+SUM(AB64:AE64) + + + + + + 0 + + + IF(D64=0,AJ63*Variables!$B$22,"") + + + + IF(D64=0,AK63*Variables!$B$24,"") + + + + IF(D64=0,AL63*Variables!$B$26,"") + + + + IF(D64=0,AM63*Variables!$B$28,"") + + + + IF(D64=0,AN63+Variables!$B$30,"") + + + + IFERROR(AF64+(SUM(AI64:AN64)),0) + + + + + + + + IF(C64<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C64,1),"") + + + + IFERROR(IF(ISNUMBER(E65),0,1),"") + + + + IFERROR(YEAR(C65),"") + + + + IF(D65=0,F64*Variables!$B$7,"") + + + + IF(D65=0,G64*Variables!$B$7,"") + + + + IF(D65=0,H64*Variables!$B$7,"") + + + + IFERROR((F65*Variables!$B$8),"") + + + + IFERROR((G65*Variables!$B$9),"") + + + + IFERROR((H65*Variables!$B$10),"") + + + + IFERROR(SUM(I65:K65),"") + + + + + + IFERROR(I65/1000*$O$14,"") + + + + IFERROR(J65/1000*$P$14,"") + + + + IFERROR(K65/1000*$Q$14,"") + + + + SUM(O65:Q65) + + + + + + IFERROR(U64+Variables!$B$15,"") + + + + IFERROR(O65*U65,"") + + + + IFERROR(P65*U65,"") + + + + IFERROR(Q65*U65,"") + + + + SUM(V65:X65) + + + + + + -Y65*Variables!$B$16 + + + + -SUM(Y65,AB65)*Variables!$B$17 + + + + -SUM(Y65,AB65)*Variables!$B$18 + + + + -SUM(Y65,AB65)*0.25 + + + + Y65+SUM(AB65:AE65) + + + + + + 0 + + + IF(D65=0,AJ64*Variables!$B$22,"") + + + + IF(D65=0,AK64*Variables!$B$24,"") + + + + IF(D65=0,AL64*Variables!$B$26,"") + + + + IF(D65=0,AM64*Variables!$B$28,"") + + + + IF(D65=0,AN64+Variables!$B$30,"") + + + + IFERROR(AF65+(SUM(AI65:AN65)),0) + + + + + + + + IF(C65<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C65,1),"") + + + + IFERROR(IF(ISNUMBER(E66),0,1),"") + + + + IFERROR(YEAR(C66),"") + + + + IF(D66=0,F65*Variables!$B$7,"") + + + + IF(D66=0,G65*Variables!$B$7,"") + + + + IF(D66=0,H65*Variables!$B$7,"") + + + + IFERROR((F66*Variables!$B$8),"") + + + + IFERROR((G66*Variables!$B$9),"") + + + + IFERROR((H66*Variables!$B$10),"") + + + + IFERROR(SUM(I66:K66),"") + + + + + + IFERROR(I66/1000*$O$14,"") + + + + IFERROR(J66/1000*$P$14,"") + + + + IFERROR(K66/1000*$Q$14,"") + + + + SUM(O66:Q66) + + + + + + IFERROR(U65+Variables!$B$15,"") + + + + IFERROR(O66*U66,"") + + + + IFERROR(P66*U66,"") + + + + IFERROR(Q66*U66,"") + + + + SUM(V66:X66) + + + + + + -Y66*Variables!$B$16 + + + + -SUM(Y66,AB66)*Variables!$B$17 + + + + -SUM(Y66,AB66)*Variables!$B$18 + + + + -SUM(Y66,AB66)*0.25 + + + + Y66+SUM(AB66:AE66) + + + + + + 0 + + + IF(D66=0,AJ65*Variables!$B$22,"") + + + + IF(D66=0,AK65*Variables!$B$24,"") + + + + IF(D66=0,AL65*Variables!$B$26,"") + + + + IF(D66=0,AM65*Variables!$B$28,"") + + + + IF(D66=0,AN65+Variables!$B$30,"") + + + + IFERROR(AF66+(SUM(AI66:AN66)),0) + + + + + + + + IF(C66<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C66,1),"") + + + + IFERROR(IF(ISNUMBER(E67),0,1),"") + + + + IFERROR(YEAR(C67),"") + + + + IF(D67=0,F66*Variables!$B$7,"") + + + + IF(D67=0,G66*Variables!$B$7,"") + + + + IF(D67=0,H66*Variables!$B$7,"") + + + + IFERROR((F67*Variables!$B$8),"") + + + + IFERROR((G67*Variables!$B$9),"") + + + + IFERROR((H67*Variables!$B$10),"") + + + + IFERROR(SUM(I67:K67),"") + + + + + + IFERROR(I67/1000*$O$14,"") + + + + IFERROR(J67/1000*$P$14,"") + + + + IFERROR(K67/1000*$Q$14,"") + + + + SUM(O67:Q67) + + + + + + IFERROR(U66+Variables!$B$15,"") + + + + IFERROR(O67*U67,"") + + + + IFERROR(P67*U67,"") + + + + IFERROR(Q67*U67,"") + + + + SUM(V67:X67) + + + + + + -Y67*Variables!$B$16 + + + + -SUM(Y67,AB67)*Variables!$B$17 + + + + -SUM(Y67,AB67)*Variables!$B$18 + + + + -SUM(Y67,AB67)*0.25 + + + + Y67+SUM(AB67:AE67) + + + + + + 0 + + + IF(D67=0,AJ66*Variables!$B$22,"") + + + + IF(D67=0,AK66*Variables!$B$24,"") + + + + IF(D67=0,AL66*Variables!$B$26,"") + + + + IF(D67=0,AM66*Variables!$B$28,"") + + + + IF(D67=0,AN66+Variables!$B$30,"") + + + + IFERROR(AF67+(SUM(AI67:AN67)),0) + + + + + + + + IF(C67<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C67,1),"") + + + + IFERROR(IF(ISNUMBER(E68),0,1),"") + + + + IFERROR(YEAR(C68),"") + + + + IF(D68=0,F67*Variables!$B$7,"") + + + + IF(D68=0,G67*Variables!$B$7,"") + + + + IF(D68=0,H67*Variables!$B$7,"") + + + + IFERROR((F68*Variables!$B$8),"") + + + + IFERROR((G68*Variables!$B$9),"") + + + + IFERROR((H68*Variables!$B$10),"") + + + + IFERROR(SUM(I68:K68),"") + + + + + + IFERROR(I68/1000*$O$14,"") + + + + IFERROR(J68/1000*$P$14,"") + + + + IFERROR(K68/1000*$Q$14,"") + + + + SUM(O68:Q68) + + + + + + IFERROR(U67+Variables!$B$15,"") + + + + IFERROR(O68*U68,"") + + + + IFERROR(P68*U68,"") + + + + IFERROR(Q68*U68,"") + + + + SUM(V68:X68) + + + + + + -Y68*Variables!$B$16 + + + + -SUM(Y68,AB68)*Variables!$B$17 + + + + -SUM(Y68,AB68)*Variables!$B$18 + + + + -SUM(Y68,AB68)*0.25 + + + + Y68+SUM(AB68:AE68) + + + + + + 0 + + + IF(D68=0,AJ67*Variables!$B$22,"") + + + + IF(D68=0,AK67*Variables!$B$24,"") + + + + IF(D68=0,AL67*Variables!$B$26,"") + + + + IF(D68=0,AM67*Variables!$B$28,"") + + + + IF(D68=0,AN67+Variables!$B$30,"") + + + + IFERROR(AF68+(SUM(AI68:AN68)),0) + + + + + + + + IF(C68<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C68,1),"") + + + + IFERROR(IF(ISNUMBER(E69),0,1),"") + + + + IFERROR(YEAR(C69),"") + + + + IF(D69=0,F68*Variables!$B$7,"") + + + + IF(D69=0,G68*Variables!$B$7,"") + + + + IF(D69=0,H68*Variables!$B$7,"") + + + + IFERROR((F69*Variables!$B$8),"") + + + + IFERROR((G69*Variables!$B$9),"") + + + + IFERROR((H69*Variables!$B$10),"") + + + + IFERROR(SUM(I69:K69),"") + + + + + + IFERROR(I69/1000*$O$14,"") + + + + IFERROR(J69/1000*$P$14,"") + + + + IFERROR(K69/1000*$Q$14,"") + + + + SUM(O69:Q69) + + + + + + IFERROR(U68+Variables!$B$15,"") + + + + IFERROR(O69*U69,"") + + + + IFERROR(P69*U69,"") + + + + IFERROR(Q69*U69,"") + + + + SUM(V69:X69) + + + + + + -Y69*Variables!$B$16 + + + + -SUM(Y69,AB69)*Variables!$B$17 + + + + -SUM(Y69,AB69)*Variables!$B$18 + + + + -SUM(Y69,AB69)*0.25 + + + + Y69+SUM(AB69:AE69) + + + + + + 0 + + + IF(D69=0,AJ68*Variables!$B$22,"") + + + + IF(D69=0,AK68*Variables!$B$24,"") + + + + IF(D69=0,AL68*Variables!$B$26,"") + + + + IF(D69=0,AM68*Variables!$B$28,"") + + + + IF(D69=0,AN68+Variables!$B$30,"") + + + + IFERROR(AF69+(SUM(AI69:AN69)),0) + + + + + + + + IF(C69<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C69,1),"") + + + + IFERROR(IF(ISNUMBER(E70),0,1),"") + + + + IFERROR(YEAR(C70),"") + + + + IF(D70=0,F69*Variables!$B$7,"") + + + + IF(D70=0,G69*Variables!$B$7,"") + + + + IF(D70=0,H69*Variables!$B$7,"") + + + + IFERROR((F70*Variables!$B$8),"") + + + + IFERROR((G70*Variables!$B$9),"") + + + + IFERROR((H70*Variables!$B$10),"") + + + + IFERROR(SUM(I70:K70),"") + + + + + + IFERROR(I70/1000*$O$14,"") + + + + IFERROR(J70/1000*$P$14,"") + + + + IFERROR(K70/1000*$Q$14,"") + + + + SUM(O70:Q70) + + + + + + IFERROR(U69+Variables!$B$15,"") + + + + IFERROR(O70*U70,"") + + + + IFERROR(P70*U70,"") + + + + IFERROR(Q70*U70,"") + + + + SUM(V70:X70) + + + + + + -Y70*Variables!$B$16 + + + + -SUM(Y70,AB70)*Variables!$B$17 + + + + -SUM(Y70,AB70)*Variables!$B$18 + + + + -SUM(Y70,AB70)*0.25 + + + + Y70+SUM(AB70:AE70) + + + + + + 0 + + + IF(D70=0,AJ69*Variables!$B$22,"") + + + + IF(D70=0,AK69*Variables!$B$24,"") + + + + IF(D70=0,AL69*Variables!$B$26,"") + + + + IF(D70=0,AM69*Variables!$B$28,"") + + + + IF(D70=0,AN69+Variables!$B$30,"") + + + + IFERROR(AF70+(SUM(AI70:AN70)),0) + + + + + + + + IF(C70<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C70,1),"") + + + + IFERROR(IF(ISNUMBER(E71),0,1),"") + + + + IFERROR(YEAR(C71),"") + + + + IF(D71=0,F70*Variables!$B$7,"") + + + + IF(D71=0,G70*Variables!$B$7,"") + + + + IF(D71=0,H70*Variables!$B$7,"") + + + + IFERROR((F71*Variables!$B$8),"") + + + + IFERROR((G71*Variables!$B$9),"") + + + + IFERROR((H71*Variables!$B$10),"") + + + + IFERROR(SUM(I71:K71),"") + + + + + + IFERROR(I71/1000*$O$14,"") + + + + IFERROR(J71/1000*$P$14,"") + + + + IFERROR(K71/1000*$Q$14,"") + + + + SUM(O71:Q71) + + + + + + IFERROR(U70+Variables!$B$15,"") + + + + IFERROR(O71*U71,"") + + + + IFERROR(P71*U71,"") + + + + IFERROR(Q71*U71,"") + + + + SUM(V71:X71) + + + + + + -Y71*Variables!$B$16 + + + + -SUM(Y71,AB71)*Variables!$B$17 + + + + -SUM(Y71,AB71)*Variables!$B$18 + + + + -SUM(Y71,AB71)*0.25 + + + + Y71+SUM(AB71:AE71) + + + + + + 0 + + + IF(D71=0,AJ70*Variables!$B$22,"") + + + + IF(D71=0,AK70*Variables!$B$24,"") + + + + IF(D71=0,AL70*Variables!$B$26,"") + + + + IF(D71=0,AM70*Variables!$B$28,"") + + + + IF(D71=0,AN70+Variables!$B$30,"") + + + + IFERROR(AF71+(SUM(AI71:AN71)),0) + + + + + + + + IF(C71<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C71,1),"") + + + + IFERROR(IF(ISNUMBER(E72),0,1),"") + + + + IFERROR(YEAR(C72),"") + + + + IF(D72=0,F71*Variables!$B$7,"") + + + + IF(D72=0,G71*Variables!$B$7,"") + + + + IF(D72=0,H71*Variables!$B$7,"") + + + + IFERROR((F72*Variables!$B$8),"") + + + + IFERROR((G72*Variables!$B$9),"") + + + + IFERROR((H72*Variables!$B$10),"") + + + + IFERROR(SUM(I72:K72),"") + + + + + + IFERROR(I72/1000*$O$14,"") + + + + IFERROR(J72/1000*$P$14,"") + + + + IFERROR(K72/1000*$Q$14,"") + + + + SUM(O72:Q72) + + + + + + IFERROR(U71+Variables!$B$15,"") + + + + IFERROR(O72*U72,"") + + + + IFERROR(P72*U72,"") + + + + IFERROR(Q72*U72,"") + + + + SUM(V72:X72) + + + + + + -Y72*Variables!$B$16 + + + + -SUM(Y72,AB72)*Variables!$B$17 + + + + -SUM(Y72,AB72)*Variables!$B$18 + + + + -SUM(Y72,AB72)*0.25 + + + + Y72+SUM(AB72:AE72) + + + + + + 0 + + + IF(D72=0,AJ71*Variables!$B$22,"") + + + + IF(D72=0,AK71*Variables!$B$24,"") + + + + IF(D72=0,AL71*Variables!$B$26,"") + + + + IF(D72=0,AM71*Variables!$B$28,"") + + + + IF(D72=0,AN71+Variables!$B$30,"") + + + + IFERROR(AF72+(SUM(AI72:AN72)),0) + + + + + + + + IF(C72<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C72,1),"") + + + + IFERROR(IF(ISNUMBER(E73),0,1),"") + + + + IFERROR(YEAR(C73),"") + + + + IF(D73=0,F72*Variables!$B$7,"") + + + + IF(D73=0,G72*Variables!$B$7,"") + + + + IF(D73=0,H72*Variables!$B$7,"") + + + + IFERROR((F73*Variables!$B$8),"") + + + + IFERROR((G73*Variables!$B$9),"") + + + + IFERROR((H73*Variables!$B$10),"") + + + + IFERROR(SUM(I73:K73),"") + + + + + + IFERROR(I73/1000*$O$14,"") + + + + IFERROR(J73/1000*$P$14,"") + + + + IFERROR(K73/1000*$Q$14,"") + + + + SUM(O73:Q73) + + + + + + IFERROR(U72+Variables!$B$15,"") + + + + IFERROR(O73*U73,"") + + + + IFERROR(P73*U73,"") + + + + IFERROR(Q73*U73,"") + + + + SUM(V73:X73) + + + + + + -Y73*Variables!$B$16 + + + + -SUM(Y73,AB73)*Variables!$B$17 + + + + -SUM(Y73,AB73)*Variables!$B$18 + + + + -SUM(Y73,AB73)*0.25 + + + + Y73+SUM(AB73:AE73) + + + + + + 0 + + + IF(D73=0,AJ72*Variables!$B$22,"") + + + + IF(D73=0,AK72*Variables!$B$24,"") + + + + IF(D73=0,AL72*Variables!$B$26,"") + + + + IF(D73=0,AM72*Variables!$B$28,"") + + + + IF(D73=0,AN72+Variables!$B$30,"") + + + + IFERROR(AF73+(SUM(AI73:AN73)),0) + + + + + + + + IF(C73<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C73,1),"") + + + + IFERROR(IF(ISNUMBER(E74),0,1),"") + + + + IFERROR(YEAR(C74),"") + + + + IF(D74=0,F73*Variables!$B$7,"") + + + + IF(D74=0,G73*Variables!$B$7,"") + + + + IF(D74=0,H73*Variables!$B$7,"") + + + + IFERROR((F74*Variables!$B$8),"") + + + + IFERROR((G74*Variables!$B$9),"") + + + + IFERROR((H74*Variables!$B$10),"") + + + + IFERROR(SUM(I74:K74),"") + + + + + + IFERROR(I74/1000*$O$14,"") + + + + IFERROR(J74/1000*$P$14,"") + + + + IFERROR(K74/1000*$Q$14,"") + + + + SUM(O74:Q74) + + + + + + IFERROR(U73+Variables!$B$15,"") + + + + IFERROR(O74*U74,"") + + + + IFERROR(P74*U74,"") + + + + IFERROR(Q74*U74,"") + + + + SUM(V74:X74) + + + + + + -Y74*Variables!$B$16 + + + + -SUM(Y74,AB74)*Variables!$B$17 + + + + -SUM(Y74,AB74)*Variables!$B$18 + + + + -SUM(Y74,AB74)*0.25 + + + + Y74+SUM(AB74:AE74) + + + + + + 0 + + + IF(D74=0,AJ73*Variables!$B$22,"") + + + + IF(D74=0,AK73*Variables!$B$24,"") + + + + IF(D74=0,AL73*Variables!$B$26,"") + + + + IF(D74=0,AM73*Variables!$B$28,"") + + + + IF(D74=0,AN73+Variables!$B$30,"") + + + + IFERROR(AF74+(SUM(AI74:AN74)),0) + + + + + + + + IF(C74<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C74,1),"") + + + + IFERROR(IF(ISNUMBER(E75),0,1),"") + + + + IFERROR(YEAR(C75),"") + + + + IF(D75=0,F74*Variables!$B$7,"") + + + + IF(D75=0,G74*Variables!$B$7,"") + + + + IF(D75=0,H74*Variables!$B$7,"") + + + + IFERROR((F75*Variables!$B$8),"") + + + + IFERROR((G75*Variables!$B$9),"") + + + + IFERROR((H75*Variables!$B$10),"") + + + + IFERROR(SUM(I75:K75),"") + + + + + + IFERROR(I75/1000*$O$14,"") + + + + IFERROR(J75/1000*$P$14,"") + + + + IFERROR(K75/1000*$Q$14,"") + + + + SUM(O75:Q75) + + + + + + IFERROR(U74+Variables!$B$15,"") + + + + IFERROR(O75*U75,"") + + + + IFERROR(P75*U75,"") + + + + IFERROR(Q75*U75,"") + + + + SUM(V75:X75) + + + + + + -Y75*Variables!$B$16 + + + + -SUM(Y75,AB75)*Variables!$B$17 + + + + -SUM(Y75,AB75)*Variables!$B$18 + + + + -SUM(Y75,AB75)*0.25 + + + + Y75+SUM(AB75:AE75) + + + + + + 1 + + + IF(D75=0,AJ74*Variables!$B$22,"") + + + + IF(D75=0,AK74*Variables!$B$24,"") + + + + IF(D75=0,AL74*Variables!$B$26,"") + + + + IF(D75=0,AM74*Variables!$B$28,"") + + + + IF(D75=0,AN74+Variables!$B$30,"") + + + + IFERROR(AF75+(SUM(AI75:AN75)),0) + + + + + + + + IF(C75<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C75,1),"") + + + + IFERROR(IF(ISNUMBER(E76),0,1),"") + + + + IFERROR(YEAR(C76),"") + + + + IF(D76=0,F75*Variables!$B$7,"") + + + + IF(D76=0,G75*Variables!$B$7,"") + + + + IF(D76=0,H75*Variables!$B$7,"") + + + + IFERROR((F76*Variables!$B$8),"") + + + + IFERROR((G76*Variables!$B$9),"") + + + + IFERROR((H76*Variables!$B$10),"") + + + + IFERROR(SUM(I76:K76),"") + + + + + + IFERROR(I76/1000*$O$14,"") + + + + IFERROR(J76/1000*$P$14,"") + + + + IFERROR(K76/1000*$Q$14,"") + + + + SUM(O76:Q76) + + + + + + IFERROR(U75+Variables!$B$15,"") + + + + IFERROR(O76*U76,"") + + + + IFERROR(P76*U76,"") + + + + IFERROR(Q76*U76,"") + + + + SUM(V76:X76) + + + + + + -Y76*Variables!$B$16 + + + + -SUM(Y76,AB76)*Variables!$B$17 + + + + -SUM(Y76,AB76)*Variables!$B$18 + + + + -SUM(Y76,AB76)*0.25 + + + + Y76+SUM(AB76:AE76) + + + + + + 2 + + + IF(D76=0,AJ75*Variables!$B$22,"") + + + + IF(D76=0,AK75*Variables!$B$24,"") + + + + IF(D76=0,AL75*Variables!$B$26,"") + + + + IF(D76=0,AM75*Variables!$B$28,"") + + + + IF(D76=0,AN75+Variables!$B$30,"") + + + + IFERROR(AF76+(SUM(AI76:AN76)),0) + + + + + + + + IF(C76<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C76,1),"") + + + + IFERROR(IF(ISNUMBER(E77),0,1),"") + + + + IFERROR(YEAR(C77),"") + + + + IF(D77=0,F76*Variables!$B$7,"") + + + + IF(D77=0,G76*Variables!$B$7,"") + + + + IF(D77=0,H76*Variables!$B$7,"") + + + + IFERROR((F77*Variables!$B$8),"") + + + + IFERROR((G77*Variables!$B$9),"") + + + + IFERROR((H77*Variables!$B$10),"") + + + + IFERROR(SUM(I77:K77),"") + + + + + + IFERROR(I77/1000*$O$14,"") + + + + IFERROR(J77/1000*$P$14,"") + + + + IFERROR(K77/1000*$Q$14,"") + + + + SUM(O77:Q77) + + + + + + IFERROR(U76+Variables!$B$15,"") + + + + IFERROR(O77*U77,"") + + + + IFERROR(P77*U77,"") + + + + IFERROR(Q77*U77,"") + + + + SUM(V77:X77) + + + + + + -Y77*Variables!$B$16 + + + + -SUM(Y77,AB77)*Variables!$B$17 + + + + -SUM(Y77,AB77)*Variables!$B$18 + + + + -SUM(Y77,AB77)*0.25 + + + + Y77+SUM(AB77:AE77) + + + + + + 3 + + + IF(D77=0,AJ76*Variables!$B$22,"") + + + + IF(D77=0,AK76*Variables!$B$24,"") + + + + IF(D77=0,AL76*Variables!$B$26,"") + + + + IF(D77=0,AM76*Variables!$B$28,"") + + + + IF(D77=0,AN76+Variables!$B$30,"") + + + + IFERROR(AF77+(SUM(AI77:AN77)),0) + + + + + + + + IF(C77<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C77,1),"") + + + + IFERROR(IF(ISNUMBER(E78),0,1),"") + + + + IFERROR(YEAR(C78),"") + + + + IF(D78=0,F77*Variables!$B$7,"") + + + + IF(D78=0,G77*Variables!$B$7,"") + + + + IF(D78=0,H77*Variables!$B$7,"") + + + + IFERROR((F78*Variables!$B$8),"") + + + + IFERROR((G78*Variables!$B$9),"") + + + + IFERROR((H78*Variables!$B$10),"") + + + + IFERROR(SUM(I78:K78),"") + + + + + + IFERROR(I78/1000*$O$14,"") + + + + IFERROR(J78/1000*$P$14,"") + + + + IFERROR(K78/1000*$Q$14,"") + + + + SUM(O78:Q78) + + + + + + IFERROR(U77+Variables!$B$15,"") + + + + IFERROR(O78*U78,"") + + + + IFERROR(P78*U78,"") + + + + IFERROR(Q78*U78,"") + + + + SUM(V78:X78) + + + + + + -Y78*Variables!$B$16 + + + + -SUM(Y78,AB78)*Variables!$B$17 + + + + -SUM(Y78,AB78)*Variables!$B$18 + + + + -SUM(Y78,AB78)*0.25 + + + + Y78+SUM(AB78:AE78) + + + + + + 4 + + + IF(D78=0,AJ77*Variables!$B$22,"") + + + + IF(D78=0,AK77*Variables!$B$24,"") + + + + IF(D78=0,AL77*Variables!$B$26,"") + + + + IF(D78=0,AM77*Variables!$B$28,"") + + + + IF(D78=0,AN77+Variables!$B$30,"") + + + + IFERROR(AF78+(SUM(AI78:AN78)),0) + + + + + + + + IF(C78<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C78,1),"") + + + + IFERROR(IF(ISNUMBER(E79),0,1),"") + + + + IFERROR(YEAR(C79),"") + + + + IF(D79=0,F78*Variables!$B$7,"") + + + + IF(D79=0,G78*Variables!$B$7,"") + + + + IF(D79=0,H78*Variables!$B$7,"") + + + + IFERROR((F79*Variables!$B$8),"") + + + + IFERROR((G79*Variables!$B$9),"") + + + + IFERROR((H79*Variables!$B$10),"") + + + + IFERROR(SUM(I79:K79),"") + + + + + + IFERROR(I79/1000*$O$14,"") + + + + IFERROR(J79/1000*$P$14,"") + + + + IFERROR(K79/1000*$Q$14,"") + + + + SUM(O79:Q79) + + + + + + IFERROR(U78+Variables!$B$15,"") + + + + IFERROR(O79*U79,"") + + + + IFERROR(P79*U79,"") + + + + IFERROR(Q79*U79,"") + + + + SUM(V79:X79) + + + + + + -Y79*Variables!$B$16 + + + + -SUM(Y79,AB79)*Variables!$B$17 + + + + -SUM(Y79,AB79)*Variables!$B$18 + + + + -SUM(Y79,AB79)*0.25 + + + + Y79+SUM(AB79:AE79) + + + + + + 5 + + + IF(D79=0,AJ78*Variables!$B$22,"") + + + + IF(D79=0,AK78*Variables!$B$24,"") + + + + IF(D79=0,AL78*Variables!$B$26,"") + + + + IF(D79=0,AM78*Variables!$B$28,"") + + + + IF(D79=0,AN78+Variables!$B$30,"") + + + + IFERROR(AF79+(SUM(AI79:AN79)),0) + + + + + + + + IF(C79<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C79,1),"") + + + + IFERROR(IF(ISNUMBER(E80),0,1),"") + + + + IFERROR(YEAR(C80),"") + + + + IF(D80=0,F79*Variables!$B$7,"") + + + + IF(D80=0,G79*Variables!$B$7,"") + + + + IF(D80=0,H79*Variables!$B$7,"") + + + + IFERROR((F80*Variables!$B$8),"") + + + + IFERROR((G80*Variables!$B$9),"") + + + + IFERROR((H80*Variables!$B$10),"") + + + + IFERROR(SUM(I80:K80),"") + + + + + + IFERROR(I80/1000*$O$14,"") + + + + IFERROR(J80/1000*$P$14,"") + + + + IFERROR(K80/1000*$Q$14,"") + + + + SUM(O80:Q80) + + + + + + IFERROR(U79+Variables!$B$15,"") + + + + IFERROR(O80*U80,"") + + + + IFERROR(P80*U80,"") + + + + IFERROR(Q80*U80,"") + + + + SUM(V80:X80) + + + + + + -Y80*Variables!$B$16 + + + + -SUM(Y80,AB80)*Variables!$B$17 + + + + -SUM(Y80,AB80)*Variables!$B$18 + + + + -SUM(Y80,AB80)*0.25 + + + + Y80+SUM(AB80:AE80) + + + + + + 6 + + + IF(D80=0,AJ79*Variables!$B$22,"") + + + + IF(D80=0,AK79*Variables!$B$24,"") + + + + IF(D80=0,AL79*Variables!$B$26,"") + + + + IF(D80=0,AM79*Variables!$B$28,"") + + + + IF(D80=0,AN79+Variables!$B$30,"") + + + + IFERROR(AF80+(SUM(AI80:AN80)),0) + + + + + + + + IF(C80<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C80,1),"") + + + + IFERROR(IF(ISNUMBER(E81),0,1),"") + + + + IFERROR(YEAR(C81),"") + + + + IF(D81=0,F80*Variables!$B$7,"") + + + + IF(D81=0,G80*Variables!$B$7,"") + + + + IF(D81=0,H80*Variables!$B$7,"") + + + + IFERROR((F81*Variables!$B$8),"") + + + + IFERROR((G81*Variables!$B$9),"") + + + + IFERROR((H81*Variables!$B$10),"") + + + + IFERROR(SUM(I81:K81),"") + + + + + + IFERROR(I81/1000*$O$14,"") + + + + IFERROR(J81/1000*$P$14,"") + + + + IFERROR(K81/1000*$Q$14,"") + + + + SUM(O81:Q81) + + + + + + IFERROR(U80+Variables!$B$15,"") + + + + IFERROR(O81*U81,"") + + + + IFERROR(P81*U81,"") + + + + IFERROR(Q81*U81,"") + + + + SUM(V81:X81) + + + + + + -Y81*Variables!$B$16 + + + + -SUM(Y81,AB81)*Variables!$B$17 + + + + -SUM(Y81,AB81)*Variables!$B$18 + + + + -SUM(Y81,AB81)*0.25 + + + + Y81+SUM(AB81:AE81) + + + + + + 7 + + + IF(D81=0,AJ80*Variables!$B$22,"") + + + + IF(D81=0,AK80*Variables!$B$24,"") + + + + IF(D81=0,AL80*Variables!$B$26,"") + + + + IF(D81=0,AM80*Variables!$B$28,"") + + + + IF(D81=0,AN80+Variables!$B$30,"") + + + + IFERROR(AF81+(SUM(AI81:AN81)),0) + + + + + + + + IF(C81<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C81,1),"") + + + + IFERROR(IF(ISNUMBER(E82),0,1),"") + + + + IFERROR(YEAR(C82),"") + + + + IF(D82=0,F81*Variables!$B$7,"") + + + + IF(D82=0,G81*Variables!$B$7,"") + + + + IF(D82=0,H81*Variables!$B$7,"") + + + + IFERROR((F82*Variables!$B$8),"") + + + + IFERROR((G82*Variables!$B$9),"") + + + + IFERROR((H82*Variables!$B$10),"") + + + + IFERROR(SUM(I82:K82),"") + + + + + + IFERROR(I82/1000*$O$14,"") + + + + IFERROR(J82/1000*$P$14,"") + + + + IFERROR(K82/1000*$Q$14,"") + + + + SUM(O82:Q82) + + + + + + IFERROR(U81+Variables!$B$15,"") + + + + IFERROR(O82*U82,"") + + + + IFERROR(P82*U82,"") + + + + IFERROR(Q82*U82,"") + + + + SUM(V82:X82) + + + + + + -Y82*Variables!$B$16 + + + + -SUM(Y82,AB82)*Variables!$B$17 + + + + -SUM(Y82,AB82)*Variables!$B$18 + + + + -SUM(Y82,AB82)*0.25 + + + + Y82+SUM(AB82:AE82) + + + + + + 8 + + + IF(D82=0,AJ81*Variables!$B$22,"") + + + + IF(D82=0,AK81*Variables!$B$24,"") + + + + IF(D82=0,AL81*Variables!$B$26,"") + + + + IF(D82=0,AM81*Variables!$B$28,"") + + + + IF(D82=0,AN81+Variables!$B$30,"") + + + + IFERROR(AF82+(SUM(AI82:AN82)),0) + + + + + + + + IF(C82<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C82,1),"") + + + + IFERROR(IF(ISNUMBER(E83),0,1),"") + + + + IFERROR(YEAR(C83),"") + + + + IF(D83=0,F82*Variables!$B$7,"") + + + + IF(D83=0,G82*Variables!$B$7,"") + + + + IF(D83=0,H82*Variables!$B$7,"") + + + + IFERROR((F83*Variables!$B$8),"") + + + + IFERROR((G83*Variables!$B$9),"") + + + + IFERROR((H83*Variables!$B$10),"") + + + + IFERROR(SUM(I83:K83),"") + + + + + + IFERROR(I83/1000*$O$14,"") + + + + IFERROR(J83/1000*$P$14,"") + + + + IFERROR(K83/1000*$Q$14,"") + + + + SUM(O83:Q83) + + + + + + IFERROR(U82+Variables!$B$15,"") + + + + IFERROR(O83*U83,"") + + + + IFERROR(P83*U83,"") + + + + IFERROR(Q83*U83,"") + + + + SUM(V83:X83) + + + + + + -Y83*Variables!$B$16 + + + + -SUM(Y83,AB83)*Variables!$B$17 + + + + -SUM(Y83,AB83)*Variables!$B$18 + + + + -SUM(Y83,AB83)*0.25 + + + + Y83+SUM(AB83:AE83) + + + + + + 9 + + + IF(D83=0,AJ82*Variables!$B$22,"") + + + + IF(D83=0,AK82*Variables!$B$24,"") + + + + IF(D83=0,AL82*Variables!$B$26,"") + + + + IF(D83=0,AM82*Variables!$B$28,"") + + + + IF(D83=0,AN82+Variables!$B$30,"") + + + + IFERROR(AF83+(SUM(AI83:AN83)),0) + + + + + + + + IF(C83<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C83,1),"") + + + + IFERROR(IF(ISNUMBER(E84),0,1),"") + + + + IFERROR(YEAR(C84),"") + + + + IF(D84=0,F83*Variables!$B$7,"") + + + + IF(D84=0,G83*Variables!$B$7,"") + + + + IF(D84=0,H83*Variables!$B$7,"") + + + + IFERROR((F84*Variables!$B$8),"") + + + + IFERROR((G84*Variables!$B$9),"") + + + + IFERROR((H84*Variables!$B$10),"") + + + + IFERROR(SUM(I84:K84),"") + + + + + + IFERROR(I84/1000*$O$14,"") + + + + IFERROR(J84/1000*$P$14,"") + + + + IFERROR(K84/1000*$Q$14,"") + + + + SUM(O84:Q84) + + + + + + IFERROR(U83+Variables!$B$15,"") + + + + IFERROR(O84*U84,"") + + + + IFERROR(P84*U84,"") + + + + IFERROR(Q84*U84,"") + + + + SUM(V84:X84) + + + + + + -Y84*Variables!$B$16 + + + + -SUM(Y84,AB84)*Variables!$B$17 + + + + -SUM(Y84,AB84)*Variables!$B$18 + + + + -SUM(Y84,AB84)*0.25 + + + + Y84+SUM(AB84:AE84) + + + + + + 10 + + + IF(D84=0,AJ83*Variables!$B$22,"") + + + + IF(D84=0,AK83*Variables!$B$24,"") + + + + IF(D84=0,AL83*Variables!$B$26,"") + + + + IF(D84=0,AM83*Variables!$B$28,"") + + + + IF(D84=0,AN83+Variables!$B$30,"") + + + + IFERROR(AF84+(SUM(AI84:AN84)),0) + + + + + + + + IF(C84<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C84,1),"") + + + + IFERROR(IF(ISNUMBER(E85),0,1),"") + + + + IFERROR(YEAR(C85),"") + + + + IF(D85=0,F84*Variables!$B$7,"") + + + + IF(D85=0,G84*Variables!$B$7,"") + + + + IF(D85=0,H84*Variables!$B$7,"") + + + + IFERROR((F85*Variables!$B$8),"") + + + + IFERROR((G85*Variables!$B$9),"") + + + + IFERROR((H85*Variables!$B$10),"") + + + + IFERROR(SUM(I85:K85),"") + + + + + + IFERROR(I85/1000*$O$14,"") + + + + IFERROR(J85/1000*$P$14,"") + + + + IFERROR(K85/1000*$Q$14,"") + + + + SUM(O85:Q85) + + + + + + IFERROR(U84+Variables!$B$15,"") + + + + IFERROR(O85*U85,"") + + + + IFERROR(P85*U85,"") + + + + IFERROR(Q85*U85,"") + + + + SUM(V85:X85) + + + + + + -Y85*Variables!$B$16 + + + + -SUM(Y85,AB85)*Variables!$B$17 + + + + -SUM(Y85,AB85)*Variables!$B$18 + + + + -SUM(Y85,AB85)*0.25 + + + + Y85+SUM(AB85:AE85) + + + + + + 11 + + + IF(D85=0,AJ84*Variables!$B$22,"") + + + + IF(D85=0,AK84*Variables!$B$24,"") + + + + IF(D85=0,AL84*Variables!$B$26,"") + + + + IF(D85=0,AM84*Variables!$B$28,"") + + + + IF(D85=0,AN84+Variables!$B$30,"") + + + + IFERROR(AF85+(SUM(AI85:AN85)),0) + + + + + + + + IF(C85<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C85,1),"") + + + + IFERROR(IF(ISNUMBER(E86),0,1),"") + + + + IFERROR(YEAR(C86),"") + + + + IF(D86=0,F85*Variables!$B$7,"") + + + + IF(D86=0,G85*Variables!$B$7,"") + + + + IF(D86=0,H85*Variables!$B$7,"") + + + + IFERROR((F86*Variables!$B$8),"") + + + + IFERROR((G86*Variables!$B$9),"") + + + + IFERROR((H86*Variables!$B$10),"") + + + + IFERROR(SUM(I86:K86),"") + + + + + + IFERROR(I86/1000*$O$14,"") + + + + IFERROR(J86/1000*$P$14,"") + + + + IFERROR(K86/1000*$Q$14,"") + + + + SUM(O86:Q86) + + + + + + IFERROR(U85+Variables!$B$15,"") + + + + IFERROR(O86*U86,"") + + + + IFERROR(P86*U86,"") + + + + IFERROR(Q86*U86,"") + + + + SUM(V86:X86) + + + + + + -Y86*Variables!$B$16 + + + + -SUM(Y86,AB86)*Variables!$B$17 + + + + -SUM(Y86,AB86)*Variables!$B$18 + + + + -SUM(Y86,AB86)*0.25 + + + + Y86+SUM(AB86:AE86) + + + + + + 12 + + + IF(D86=0,AJ85*Variables!$B$22,"") + + + + IF(D86=0,AK85*Variables!$B$24,"") + + + + IF(D86=0,AL85*Variables!$B$26,"") + + + + IF(D86=0,AM85*Variables!$B$28,"") + + + + IF(D86=0,AN85+Variables!$B$30,"") + + + + IFERROR(AF86+(SUM(AI86:AN86)),0) + + + + + + + + IF(C86<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C86,1),"") + + + + IFERROR(IF(ISNUMBER(E87),0,1),"") + + + + IFERROR(YEAR(C87),"") + + + + IF(D87=0,F86*Variables!$B$7,"") + + + + IF(D87=0,G86*Variables!$B$7,"") + + + + IF(D87=0,H86*Variables!$B$7,"") + + + + IFERROR((F87*Variables!$B$8),"") + + + + IFERROR((G87*Variables!$B$9),"") + + + + IFERROR((H87*Variables!$B$10),"") + + + + IFERROR(SUM(I87:K87),"") + + + + + + IFERROR(I87/1000*$O$14,"") + + + + IFERROR(J87/1000*$P$14,"") + + + + IFERROR(K87/1000*$Q$14,"") + + + + SUM(O87:Q87) + + + + + + IFERROR(U86+Variables!$B$15,"") + + + + IFERROR(O87*U87,"") + + + + IFERROR(P87*U87,"") + + + + IFERROR(Q87*U87,"") + + + + SUM(V87:X87) + + + + + + -Y87*Variables!$B$16 + + + + -SUM(Y87,AB87)*Variables!$B$17 + + + + -SUM(Y87,AB87)*Variables!$B$18 + + + + -SUM(Y87,AB87)*0.25 + + + + Y87+SUM(AB87:AE87) + + + + + + 13 + + + IF(D87=0,AJ86*Variables!$B$22,"") + + + + IF(D87=0,AK86*Variables!$B$24,"") + + + + IF(D87=0,AL86*Variables!$B$26,"") + + + + IF(D87=0,AM86*Variables!$B$28,"") + + + + IF(D87=0,AN86+Variables!$B$30,"") + + + + IFERROR(AF87+(SUM(AI87:AN87)),0) + + + + + + + + IF(C87<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C87,1),"") + + + + IFERROR(IF(ISNUMBER(E88),0,1),"") + + + + IFERROR(YEAR(C88),"") + + + + IF(D88=0,F87*Variables!$B$7,"") + + + + IF(D88=0,G87*Variables!$B$7,"") + + + + IF(D88=0,H87*Variables!$B$7,"") + + + + IFERROR((F88*Variables!$B$8),"") + + + + IFERROR((G88*Variables!$B$9),"") + + + + IFERROR((H88*Variables!$B$10),"") + + + + IFERROR(SUM(I88:K88),"") + + + + + + IFERROR(I88/1000*$O$14,"") + + + + IFERROR(J88/1000*$P$14,"") + + + + IFERROR(K88/1000*$Q$14,"") + + + + SUM(O88:Q88) + + + + + + IFERROR(U87+Variables!$B$15,"") + + + + IFERROR(O88*U88,"") + + + + IFERROR(P88*U88,"") + + + + IFERROR(Q88*U88,"") + + + + SUM(V88:X88) + + + + + + -Y88*Variables!$B$16 + + + + -SUM(Y88,AB88)*Variables!$B$17 + + + + -SUM(Y88,AB88)*Variables!$B$18 + + + + -SUM(Y88,AB88)*0.25 + + + + Y88+SUM(AB88:AE88) + + + + + + 14 + + + IF(D88=0,AJ87*Variables!$B$22,"") + + + + IF(D88=0,AK87*Variables!$B$24,"") + + + + IF(D88=0,AL87*Variables!$B$26,"") + + + + IF(D88=0,AM87*Variables!$B$28,"") + + + + IF(D88=0,AN87+Variables!$B$30,"") + + + + IFERROR(AF88+(SUM(AI88:AN88)),0) + + + + + + + + IF(C88<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C88,1),"") + + + + IFERROR(IF(ISNUMBER(E89),0,1),"") + + + + IFERROR(YEAR(C89),"") + + + + IF(D89=0,F88*Variables!$B$7,"") + + + + IF(D89=0,G88*Variables!$B$7,"") + + + + IF(D89=0,H88*Variables!$B$7,"") + + + + IFERROR((F89*Variables!$B$8),"") + + + + IFERROR((G89*Variables!$B$9),"") + + + + IFERROR((H89*Variables!$B$10),"") + + + + IFERROR(SUM(I89:K89),"") + + + + + + IFERROR(I89/1000*$O$14,"") + + + + IFERROR(J89/1000*$P$14,"") + + + + IFERROR(K89/1000*$Q$14,"") + + + + SUM(O89:Q89) + + + + + + IFERROR(U88+Variables!$B$15,"") + + + + IFERROR(O89*U89,"") + + + + IFERROR(P89*U89,"") + + + + IFERROR(Q89*U89,"") + + + + SUM(V89:X89) + + + + + + -Y89*Variables!$B$16 + + + + -SUM(Y89,AB89)*Variables!$B$17 + + + + -SUM(Y89,AB89)*Variables!$B$18 + + + + -SUM(Y89,AB89)*0.25 + + + + Y89+SUM(AB89:AE89) + + + + + + 15 + + + IF(D89=0,AJ88*Variables!$B$22,"") + + + + IF(D89=0,AK88*Variables!$B$24,"") + + + + IF(D89=0,AL88*Variables!$B$26,"") + + + + IF(D89=0,AM88*Variables!$B$28,"") + + + + IF(D89=0,AN88+Variables!$B$30,"") + + + + IFERROR(AF89+(SUM(AI89:AN89)),0) + + + + + + + + IF(C89<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C89,1),"") + + + + IFERROR(IF(ISNUMBER(E90),0,1),"") + + + + IFERROR(YEAR(C90),"") + + + + IF(D90=0,F89*Variables!$B$7,"") + + + + IF(D90=0,G89*Variables!$B$7,"") + + + + IF(D90=0,H89*Variables!$B$7,"") + + + + IFERROR((F90*Variables!$B$8),"") + + + + IFERROR((G90*Variables!$B$9),"") + + + + IFERROR((H90*Variables!$B$10),"") + + + + IFERROR(SUM(I90:K90),"") + + + + + + IFERROR(I90/1000*$O$14,"") + + + + IFERROR(J90/1000*$P$14,"") + + + + IFERROR(K90/1000*$Q$14,"") + + + + SUM(O90:Q90) + + + + + + IFERROR(U89+Variables!$B$15,"") + + + + IFERROR(O90*U90,"") + + + + IFERROR(P90*U90,"") + + + + IFERROR(Q90*U90,"") + + + + SUM(V90:X90) + + + + + + -Y90*Variables!$B$16 + + + + -SUM(Y90,AB90)*Variables!$B$17 + + + + -SUM(Y90,AB90)*Variables!$B$18 + + + + -SUM(Y90,AB90)*0.25 + + + + Y90+SUM(AB90:AE90) + + + + + + 16 + + + IF(D90=0,AJ89*Variables!$B$22,"") + + + + IF(D90=0,AK89*Variables!$B$24,"") + + + + IF(D90=0,AL89*Variables!$B$26,"") + + + + IF(D90=0,AM89*Variables!$B$28,"") + + + + IF(D90=0,AN89+Variables!$B$30,"") + + + + IFERROR(AF90+(SUM(AI90:AN90)),0) + + + + + + + + IF(C90<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C90,1),"") + + + + IFERROR(IF(ISNUMBER(E91),0,1),"") + + + + IFERROR(YEAR(C91),"") + + + + IF(D91=0,F90*Variables!$B$7,"") + + + + IF(D91=0,G90*Variables!$B$7,"") + + + + IF(D91=0,H90*Variables!$B$7,"") + + + + IFERROR((F91*Variables!$B$8),"") + + + + IFERROR((G91*Variables!$B$9),"") + + + + IFERROR((H91*Variables!$B$10),"") + + + + IFERROR(SUM(I91:K91),"") + + + + + + IFERROR(I91/1000*$O$14,"") + + + + IFERROR(J91/1000*$P$14,"") + + + + IFERROR(K91/1000*$Q$14,"") + + + + SUM(O91:Q91) + + + + + + IFERROR(U90+Variables!$B$15,"") + + + + IFERROR(O91*U91,"") + + + + IFERROR(P91*U91,"") + + + + IFERROR(Q91*U91,"") + + + + SUM(V91:X91) + + + + + + -Y91*Variables!$B$16 + + + + -SUM(Y91,AB91)*Variables!$B$17 + + + + -SUM(Y91,AB91)*Variables!$B$18 + + + + -SUM(Y91,AB91)*0.25 + + + + Y91+SUM(AB91:AE91) + + + + + + 17 + + + IF(D91=0,AJ90*Variables!$B$22,"") + + + + IF(D91=0,AK90*Variables!$B$24,"") + + + + IF(D91=0,AL90*Variables!$B$26,"") + + + + IF(D91=0,AM90*Variables!$B$28,"") + + + + IF(D91=0,AN90+Variables!$B$30,"") + + + + IFERROR(AF91+(SUM(AI91:AN91)),0) + + + + + + + + IF(C91<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C91,1),"") + + + + IFERROR(IF(ISNUMBER(E92),0,1),"") + + + + IFERROR(YEAR(C92),"") + + + + IF(D92=0,F91*Variables!$B$7,"") + + + + IF(D92=0,G91*Variables!$B$7,"") + + + + IF(D92=0,H91*Variables!$B$7,"") + + + + IFERROR((F92*Variables!$B$8),"") + + + + IFERROR((G92*Variables!$B$9),"") + + + + IFERROR((H92*Variables!$B$10),"") + + + + IFERROR(SUM(I92:K92),"") + + + + + + IFERROR(I92/1000*$O$14,"") + + + + IFERROR(J92/1000*$P$14,"") + + + + IFERROR(K92/1000*$Q$14,"") + + + + SUM(O92:Q92) + + + + + + IFERROR(U91+Variables!$B$15,"") + + + + IFERROR(O92*U92,"") + + + + IFERROR(P92*U92,"") + + + + IFERROR(Q92*U92,"") + + + + SUM(V92:X92) + + + + + + -Y92*Variables!$B$16 + + + + -SUM(Y92,AB92)*Variables!$B$17 + + + + -SUM(Y92,AB92)*Variables!$B$18 + + + + -SUM(Y92,AB92)*0.25 + + + + Y92+SUM(AB92:AE92) + + + + + + 18 + + + IF(D92=0,AJ91*Variables!$B$22,"") + + + + IF(D92=0,AK91*Variables!$B$24,"") + + + + IF(D92=0,AL91*Variables!$B$26,"") + + + + IF(D92=0,AM91*Variables!$B$28,"") + + + + IF(D92=0,AN91+Variables!$B$30,"") + + + + IFERROR(AF92+(SUM(AI92:AN92)),0) + + + + + + + + IF(C92<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C92,1),"") + + + + IFERROR(IF(ISNUMBER(E93),0,1),"") + + + + IFERROR(YEAR(C93),"") + + + + IF(D93=0,F92*Variables!$B$7,"") + + + + IF(D93=0,G92*Variables!$B$7,"") + + + + IF(D93=0,H92*Variables!$B$7,"") + + + + IFERROR((F93*Variables!$B$8),"") + + + + IFERROR((G93*Variables!$B$9),"") + + + + IFERROR((H93*Variables!$B$10),"") + + + + IFERROR(SUM(I93:K93),"") + + + + + + IFERROR(I93/1000*$O$14,"") + + + + IFERROR(J93/1000*$P$14,"") + + + + IFERROR(K93/1000*$Q$14,"") + + + + SUM(O93:Q93) + + + + + + IFERROR(U92+Variables!$B$15,"") + + + + IFERROR(O93*U93,"") + + + + IFERROR(P93*U93,"") + + + + IFERROR(Q93*U93,"") + + + + SUM(V93:X93) + + + + + + -Y93*Variables!$B$16 + + + + -SUM(Y93,AB93)*Variables!$B$17 + + + + -SUM(Y93,AB93)*Variables!$B$18 + + + + -SUM(Y93,AB93)*0.25 + + + + Y93+SUM(AB93:AE93) + + + + + + 19 + + + IF(D93=0,AJ92*Variables!$B$22,"") + + + + IF(D93=0,AK92*Variables!$B$24,"") + + + + IF(D93=0,AL92*Variables!$B$26,"") + + + + IF(D93=0,AM92*Variables!$B$28,"") + + + + IF(D93=0,AN92+Variables!$B$30,"") + + + + IFERROR(AF93+(SUM(AI93:AN93)),0) + + + + + + + + IF(C93<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C93,1),"") + + + + IFERROR(IF(ISNUMBER(E94),0,1),"") + + + + IFERROR(YEAR(C94),"") + + + + IF(D94=0,F93*Variables!$B$7,"") + + + + IF(D94=0,G93*Variables!$B$7,"") + + + + IF(D94=0,H93*Variables!$B$7,"") + + + + IFERROR((F94*Variables!$B$8),"") + + + + IFERROR((G94*Variables!$B$9),"") + + + + IFERROR((H94*Variables!$B$10),"") + + + + IFERROR(SUM(I94:K94),"") + + + + + + IFERROR(I94/1000*$O$14,"") + + + + IFERROR(J94/1000*$P$14,"") + + + + IFERROR(K94/1000*$Q$14,"") + + + + SUM(O94:Q94) + + + + + + IFERROR(U93+Variables!$B$15,"") + + + + IFERROR(O94*U94,"") + + + + IFERROR(P94*U94,"") + + + + IFERROR(Q94*U94,"") + + + + SUM(V94:X94) + + + + + + -Y94*Variables!$B$16 + + + + -SUM(Y94,AB94)*Variables!$B$17 + + + + -SUM(Y94,AB94)*Variables!$B$18 + + + + -SUM(Y94,AB94)*0.25 + + + + Y94+SUM(AB94:AE94) + + + + + + 20 + + + IF(D94=0,AJ93*Variables!$B$22,"") + + + + IF(D94=0,AK93*Variables!$B$24,"") + + + + IF(D94=0,AL93*Variables!$B$26,"") + + + + IF(D94=0,AM93*Variables!$B$28,"") + + + + IF(D94=0,AN93+Variables!$B$30,"") + + + + IFERROR(AF94+(SUM(AI94:AN94)),0) + + + + + + + + IF(C94<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C94,1),"") + + + + IFERROR(IF(ISNUMBER(E95),0,1),"") + + + + IFERROR(YEAR(C95),"") + + + + IF(D95=0,F94*Variables!$B$7,"") + + + + IF(D95=0,G94*Variables!$B$7,"") + + + + IF(D95=0,H94*Variables!$B$7,"") + + + + IFERROR((F95*Variables!$B$8),"") + + + + IFERROR((G95*Variables!$B$9),"") + + + + IFERROR((H95*Variables!$B$10),"") + + + + IFERROR(SUM(I95:K95),"") + + + + + + IFERROR(I95/1000*$O$14,"") + + + + IFERROR(J95/1000*$P$14,"") + + + + IFERROR(K95/1000*$Q$14,"") + + + + SUM(O95:Q95) + + + + + + IFERROR(U94+Variables!$B$15,"") + + + + IFERROR(O95*U95,"") + + + + IFERROR(P95*U95,"") + + + + IFERROR(Q95*U95,"") + + + + SUM(V95:X95) + + + + + + + + + + + + + + + + + + + + + + + + IF(C95<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C95,1),"") + + + + IFERROR(IF(ISNUMBER(E96),0,1),"") + + + + IFERROR(YEAR(C96),"") + + + + IF(D96=0,F95*Variables!$B$7,"") + + + + IF(D96=0,G95*Variables!$B$7,"") + + + + IF(D96=0,H95*Variables!$B$7,"") + + + + IFERROR((F96*Variables!$B$8),"") + + + + IFERROR((G96*Variables!$B$9),"") + + + + IFERROR((H96*Variables!$B$10),"") + + + + IFERROR(SUM(I96:K96),"") + + + + + + IFERROR(I96/1000*$O$14,"") + + + + IFERROR(J96/1000*$P$14,"") + + + + IFERROR(K96/1000*$Q$14,"") + + + + + + + IFERROR(U95+Variables!$B$15,"") + + + + IFERROR(O96*U96,"") + + + + IFERROR(P96*U96,"") + + + + IFERROR(Q96*U96,"") + + + + SUM(V96:X96) + + + + + + + + + + + + + + + + + + + + + + + + IF(C96<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C96,1),"") + + + + IFERROR(IF(ISNUMBER(E97),0,1),"") + + + + IFERROR(YEAR(C97),"") + + + + IF(D97=0,F96*Variables!$B$7,"") + + + + IF(D97=0,G96*Variables!$B$7,"") + + + + IF(D97=0,H96*Variables!$B$7,"") + + + + IFERROR((F97*Variables!$B$8),"") + + + + IFERROR((G97*Variables!$B$9),"") + + + + IFERROR((H97*Variables!$B$10),"") + + + + IFERROR(SUM(I97:K97),"") + + + + + + IFERROR(I97/1000*$O$14,"") + + + + IFERROR(J97/1000*$P$14,"") + + + + IFERROR(K97/1000*$Q$14,"") + + + + + + + IFERROR(U96+Variables!$B$15,"") + + + + IFERROR(O97*U97,"") + + + + IFERROR(P97*U97,"") + + + + IFERROR(Q97*U97,"") + + + + SUM(V97:X97) + + + + + + + + + + + + + + + + + + + + + + + + IF(C97<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C97,1),"") + + + + IFERROR(IF(ISNUMBER(E98),0,1),"") + + + + IFERROR(YEAR(C98),"") + + + + IF(D98=0,F97*Variables!$B$7,"") + + + + IF(D98=0,G97*Variables!$B$7,"") + + + + IF(D98=0,H97*Variables!$B$7,"") + + + + IFERROR((F98*Variables!$B$8),"") + + + + IFERROR((G98*Variables!$B$9),"") + + + + IFERROR((H98*Variables!$B$10),"") + + + + IFERROR(SUM(I98:K98),"") + + + + + + IFERROR(I98/1000*$O$14,"") + + + + IFERROR(J98/1000*$P$14,"") + + + + IFERROR(K98/1000*$Q$14,"") + + + + + + + IFERROR(U97+Variables!$B$15,"") + + + + IFERROR(O98*U98,"") + + + + IFERROR(P98*U98,"") + + + + IFERROR(Q98*U98,"") + + + + SUM(V98:X98) + + + + + + + + + + + + + + + + + + + + + + + + IF(C98<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C98,1),"") + + + + IFERROR(IF(ISNUMBER(E99),0,1),"") + + + + IFERROR(YEAR(C99),"") + + + + IF(D99=0,F98*Variables!$B$7,"") + + + + IF(D99=0,G98*Variables!$B$7,"") + + + + IF(D99=0,H98*Variables!$B$7,"") + + + + IFERROR((F99*Variables!$B$8),"") + + + + IFERROR((G99*Variables!$B$9),"") + + + + IFERROR((H99*Variables!$B$10),"") + + + + IFERROR(SUM(I99:K99),"") + + + + + + IFERROR(I99/1000*$O$14,"") + + + + IFERROR(J99/1000*$P$14,"") + + + + IFERROR(K99/1000*$Q$14,"") + + + + + + + IFERROR(U98+Variables!$B$15,"") + + + + IFERROR(O99*U99,"") + + + + IFERROR(P99*U99,"") + + + + IFERROR(Q99*U99,"") + + + + SUM(V99:X99) + + + + + + + + + + + + + + + + + + + + + + + + IF(C99<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C99,1),"") + + + + IFERROR(IF(ISNUMBER(E100),0,1),"") + + + + IFERROR(YEAR(C100),"") + + + + IF(D100=0,F99*Variables!$B$7,"") + + + + IF(D100=0,G99*Variables!$B$7,"") + + + + IF(D100=0,H99*Variables!$B$7,"") + + + + IFERROR((F100*Variables!$B$8),"") + + + + IFERROR((G100*Variables!$B$9),"") + + + + IFERROR((H100*Variables!$B$10),"") + + + + IFERROR(SUM(I100:K100),"") + + + + + + IFERROR(I100/1000*$O$14,"") + + + + IFERROR(J100/1000*$P$14,"") + + + + IFERROR(K100/1000*$Q$14,"") + + + + + + + IFERROR(U99+Variables!$B$15,"") + + + + IFERROR(O100*U100,"") + + + + IFERROR(P100*U100,"") + + + + IFERROR(Q100*U100,"") + + + + SUM(V100:X100) + + + + + + + + + + + + + + + + + + + + + + + + IF(C100<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C100,1),"") + + + + IFERROR(IF(ISNUMBER(E101),0,1),"") + + + + IFERROR(YEAR(C101),"") + + + + IF(D101=0,F100*Variables!$B$7,"") + + + + IF(D101=0,G100*Variables!$B$7,"") + + + + IF(D101=0,H100*Variables!$B$7,"") + + + + IFERROR((F101*Variables!$B$8),"") + + + + IFERROR((G101*Variables!$B$9),"") + + + + IFERROR((H101*Variables!$B$10),"") + + + + IFERROR(SUM(I101:K101),"") + + + + + + IFERROR(I101/1000*$O$14,"") + + + + IFERROR(J101/1000*$P$14,"") + + + + IFERROR(K101/1000*$Q$14,"") + + + + + + + IFERROR(U100+Variables!$B$15,"") + + + + IFERROR(O101*U101,"") + + + + IFERROR(P101*U101,"") + + + + IFERROR(Q101*U101,"") + + + + SUM(V101:X101) + + + + + + + + + + + + + + + + + + + + + + + + IF(C101<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C101,1),"") + + + + IFERROR(IF(ISNUMBER(E102),0,1),"") + + + + IFERROR(YEAR(C102),"") + + + + IF(D102=0,F101*Variables!$B$7,"") + + + + IF(D102=0,G101*Variables!$B$7,"") + + + + IF(D102=0,H101*Variables!$B$7,"") + + + + IFERROR((F102*Variables!$B$8),"") + + + + IFERROR((G102*Variables!$B$9),"") + + + + IFERROR((H102*Variables!$B$10),"") + + + + IFERROR(SUM(I102:K102),"") + + + + + + IFERROR(I102/1000*$O$14,"") + + + + IFERROR(J102/1000*$P$14,"") + + + + IFERROR(K102/1000*$Q$14,"") + + + + + + + IFERROR(U101+Variables!$B$15,"") + + + + IFERROR(O102*U102,"") + + + + IFERROR(P102*U102,"") + + + + IFERROR(Q102*U102,"") + + + + SUM(V102:X102) + + + + + + + + + + + + + + + + + + + + + + + + IF(C102<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C102,1),"") + + + + IFERROR(IF(ISNUMBER(E103),0,1),"") + + + + IFERROR(YEAR(C103),"") + + + + IF(D103=0,F102*Variables!$B$7,"") + + + + IF(D103=0,G102*Variables!$B$7,"") + + + + IF(D103=0,H102*Variables!$B$7,"") + + + + IFERROR((F103*Variables!$B$8),"") + + + + IFERROR((G103*Variables!$B$9),"") + + + + IFERROR((H103*Variables!$B$10),"") + + + + IFERROR(SUM(I103:K103),"") + + + + + + IFERROR(I103/1000*$O$14,"") + + + + IFERROR(J103/1000*$P$14,"") + + + + IFERROR(K103/1000*$Q$14,"") + + + + + + + IFERROR(U102+Variables!$B$15,"") + + + + IFERROR(O103*U103,"") + + + + IFERROR(P103*U103,"") + + + + IFERROR(Q103*U103,"") + + + + SUM(V103:X103) + + + + + + + + + + + + + + + + + + + + + + + + IF(C103<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C103,1),"") + + + + IFERROR(IF(ISNUMBER(E104),0,1),"") + + + + IFERROR(YEAR(C104),"") + + + + IF(D104=0,F103*Variables!$B$7,"") + + + + IF(D104=0,G103*Variables!$B$7,"") + + + + IF(D104=0,H103*Variables!$B$7,"") + + + + IFERROR((F104*Variables!$B$8),"") + + + + IFERROR((G104*Variables!$B$9),"") + + + + IFERROR((H104*Variables!$B$10),"") + + + + IFERROR(SUM(I104:K104),"") + + + + + + IFERROR(I104/1000*$O$14,"") + + + + IFERROR(J104/1000*$P$14,"") + + + + IFERROR(K104/1000*$Q$14,"") + + + + + + + IFERROR(U103+Variables!$B$15,"") + + + + IFERROR(O104*U104,"") + + + + IFERROR(P104*U104,"") + + + + IFERROR(Q104*U104,"") + + + + SUM(V104:X104) + + + + + + + + + + + + + + + + + + + + + + + + IF(C104<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C104,1),"") + + + + IFERROR(IF(ISNUMBER(E105),0,1),"") + + + + IFERROR(YEAR(C105),"") + + + + IF(D105=0,F104*Variables!$B$7,"") + + + + IF(D105=0,G104*Variables!$B$7,"") + + + + IF(D105=0,H104*Variables!$B$7,"") + + + + IFERROR((F105*Variables!$B$8),"") + + + + IFERROR((G105*Variables!$B$9),"") + + + + IFERROR((H105*Variables!$B$10),"") + + + + IFERROR(SUM(I105:K105),"") + + + + + + IFERROR(I105/1000*$O$14,"") + + + + IFERROR(J105/1000*$P$14,"") + + + + IFERROR(K105/1000*$Q$14,"") + + + + + + + IFERROR(U104+Variables!$B$15,"") + + + + IFERROR(O105*U105,"") + + + + IFERROR(P105*U105,"") + + + + IFERROR(Q105*U105,"") + + + + SUM(V105:X105) + + + + + + + + + + + + + + + + + + + + + + + + IF(C105<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C105,1),"") + + + + IFERROR(IF(ISNUMBER(E106),0,1),"") + + + + IFERROR(YEAR(C106),"") + + + + IF(D106=0,F105*Variables!$B$7,"") + + + + IF(D106=0,G105*Variables!$B$7,"") + + + + IF(D106=0,H105*Variables!$B$7,"") + + + + IFERROR((F106*Variables!$B$8),"") + + + + IFERROR((G106*Variables!$B$9),"") + + + + IFERROR((H106*Variables!$B$10),"") + + + + IFERROR(SUM(I106:K106),"") + + + + + + IFERROR(I106/1000*$O$14,"") + + + + IFERROR(J106/1000*$P$14,"") + + + + IFERROR(K106/1000*$Q$14,"") + + + + + + + IFERROR(U105+Variables!$B$15,"") + + + + IFERROR(O106*U106,"") + + + + IFERROR(P106*U106,"") + + + + IFERROR(Q106*U106,"") + + + + SUM(V106:X106) + + + + + + + + + + + + + + + + + + + + + + + + IF(C106<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C106,1),"") + + + + IFERROR(IF(ISNUMBER(E107),0,1),"") + + + + IFERROR(YEAR(C107),"") + + + + IF(D107=0,F106*Variables!$B$7,"") + + + + IF(D107=0,G106*Variables!$B$7,"") + + + + IF(D107=0,H106*Variables!$B$7,"") + + + + IFERROR((F107*Variables!$B$8),"") + + + + IFERROR((G107*Variables!$B$9),"") + + + + IFERROR((H107*Variables!$B$10),"") + + + + IFERROR(SUM(I107:K107),"") + + + + + + IFERROR(I107/1000*$O$14,"") + + + + IFERROR(J107/1000*$P$14,"") + + + + IFERROR(K107/1000*$Q$14,"") + + + + + + + IFERROR(U106+Variables!$B$15,"") + + + + IFERROR(O107*U107,"") + + + + IFERROR(P107*U107,"") + + + + IFERROR(Q107*U107,"") + + + + SUM(V107:X107) + + + + + + + + + + + + + + + + + + + + + + + + IF(C107<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C107,1),"") + + + + IFERROR(IF(ISNUMBER(E108),0,1),"") + + + + IFERROR(YEAR(C108),"") + + + + IF(D108=0,F107*Variables!$B$7,"") + + + + IF(D108=0,G107*Variables!$B$7,"") + + + + IF(D108=0,H107*Variables!$B$7,"") + + + + IFERROR((F108*Variables!$B$8),"") + + + + IFERROR((G108*Variables!$B$9),"") + + + + IFERROR((H108*Variables!$B$10),"") + + + + IFERROR(SUM(I108:K108),"") + + + + + + IFERROR(I108/1000*$O$14,"") + + + + IFERROR(J108/1000*$P$14,"") + + + + IFERROR(K108/1000*$Q$14,"") + + + + + + + IFERROR(U107+Variables!$B$15,"") + + + + IFERROR(O108*U108,"") + + + + IFERROR(P108*U108,"") + + + + IFERROR(Q108*U108,"") + + + + SUM(V108:X108) + + + + + + + + + + + + + + + + + + + + + + + + IF(C108<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C108,1),"") + + + + IFERROR(IF(ISNUMBER(E109),0,1),"") + + + + IFERROR(YEAR(C109),"") + + + + IF(D109=0,F108*Variables!$B$7,"") + + + + IF(D109=0,G108*Variables!$B$7,"") + + + + IF(D109=0,H108*Variables!$B$7,"") + + + + IFERROR((F109*Variables!$B$8),"") + + + + IFERROR((G109*Variables!$B$9),"") + + + + IFERROR((H109*Variables!$B$10),"") + + + + IFERROR(SUM(I109:K109),"") + + + + + + IFERROR(I109/1000*$O$14,"") + + + + IFERROR(J109/1000*$P$14,"") + + + + IFERROR(K109/1000*$Q$14,"") + + + + + + + IFERROR(U108+Variables!$B$15,"") + + + + IFERROR(O109*U109,"") + + + + IFERROR(P109*U109,"") + + + + IFERROR(Q109*U109,"") + + + + SUM(V109:X109) + + + + + + + + + + + + + + + + + + + + + + + + IF(C109<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C109,1),"") + + + + IFERROR(IF(ISNUMBER(E110),0,1),"") + + + + IFERROR(YEAR(C110),"") + + + + IF(D110=0,F109*Variables!$B$7,"") + + + + IF(D110=0,G109*Variables!$B$7,"") + + + + IF(D110=0,H109*Variables!$B$7,"") + + + + IFERROR((F110*Variables!$B$8),"") + + + + IFERROR((G110*Variables!$B$9),"") + + + + IFERROR((H110*Variables!$B$10),"") + + + + IFERROR(SUM(I110:K110),"") + + + + + + IFERROR(I110/1000*$O$14,"") + + + + IFERROR(J110/1000*$P$14,"") + + + + IFERROR(K110/1000*$Q$14,"") + + + + + + + IFERROR(U109+Variables!$B$15,"") + + + + IFERROR(O110*U110,"") + + + + IFERROR(P110*U110,"") + + + + IFERROR(Q110*U110,"") + + + + SUM(V110:X110) + + + + + + + + + + + + + + + + + + + + + + + + IF(C110<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C110,1),"") + + + + IFERROR(IF(ISNUMBER(E111),0,1),"") + + + + IFERROR(YEAR(C111),"") + + + + IF(D111=0,F110*Variables!$B$7,"") + + + + IF(D111=0,G110*Variables!$B$7,"") + + + + IF(D111=0,H110*Variables!$B$7,"") + + + + IFERROR((F111*Variables!$B$8),"") + + + + IFERROR((G111*Variables!$B$9),"") + + + + IFERROR((H111*Variables!$B$10),"") + + + + IFERROR(SUM(I111:K111),"") + + + + + + IFERROR(I111/1000*$O$14,"") + + + + IFERROR(J111/1000*$P$14,"") + + + + IFERROR(K111/1000*$Q$14,"") + + + + + + + IFERROR(U110+Variables!$B$15,"") + + + + IFERROR(O111*U111,"") + + + + IFERROR(P111*U111,"") + + + + IFERROR(Q111*U111,"") + + + + SUM(V111:X111) + + + + + + + + + + + + + + + + + + + + + + + + IF(C111<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C111,1),"") + + + + IFERROR(IF(ISNUMBER(E112),0,1),"") + + + + IFERROR(YEAR(C112),"") + + + + IF(D112=0,F111*Variables!$B$7,"") + + + + IF(D112=0,G111*Variables!$B$7,"") + + + + IF(D112=0,H111*Variables!$B$7,"") + + + + IFERROR((F112*Variables!$B$8),"") + + + + IFERROR((G112*Variables!$B$9),"") + + + + IFERROR((H112*Variables!$B$10),"") + + + + IFERROR(SUM(I112:K112),"") + + + + + + IFERROR(I112/1000*$O$14,"") + + + + IFERROR(J112/1000*$P$14,"") + + + + IFERROR(K112/1000*$Q$14,"") + + + + + + + IFERROR(U111+Variables!$B$15,"") + + + + IFERROR(O112*U112,"") + + + + IFERROR(P112*U112,"") + + + + IFERROR(Q112*U112,"") + + + + SUM(V112:X112) + + + + + + + + + + + + + + + + + + + + + + + + IF(C112<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C112,1),"") + + + + IFERROR(IF(ISNUMBER(E113),0,1),"") + + + + IFERROR(YEAR(C113),"") + + + + IF(D113=0,F112*Variables!$B$7,"") + + + + IF(D113=0,G112*Variables!$B$7,"") + + + + IF(D113=0,H112*Variables!$B$7,"") + + + + IFERROR((F113*Variables!$B$8),"") + + + + IFERROR((G113*Variables!$B$9),"") + + + + IFERROR((H113*Variables!$B$10),"") + + + + IFERROR(SUM(I113:K113),"") + + + + + + IFERROR(I113/1000*$O$14,"") + + + + IFERROR(J113/1000*$P$14,"") + + + + IFERROR(K113/1000*$Q$14,"") + + + + + + + IFERROR(U112+Variables!$B$15,"") + + + + IFERROR(O113*U113,"") + + + + IFERROR(P113*U113,"") + + + + IFERROR(Q113*U113,"") + + + + SUM(V113:X113) + + + + + + + + + + + + + + + + + + + + + + + + IF(C113<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C113,1),"") + + + + IFERROR(IF(ISNUMBER(E114),0,1),"") + + + + IFERROR(YEAR(C114),"") + + + + IF(D114=0,F113*Variables!$B$7,"") + + + + IF(D114=0,G113*Variables!$B$7,"") + + + + IF(D114=0,H113*Variables!$B$7,"") + + + + IFERROR((F114*Variables!$B$8),"") + + + + IFERROR((G114*Variables!$B$9),"") + + + + IFERROR((H114*Variables!$B$10),"") + + + + IFERROR(SUM(I114:K114),"") + + + + + + IFERROR(I114/1000*$O$14,"") + + + + IFERROR(J114/1000*$P$14,"") + + + + IFERROR(K114/1000*$Q$14,"") + + + + + + + IFERROR(U113+Variables!$B$15,"") + + + + IFERROR(O114*U114,"") + + + + IFERROR(P114*U114,"") + + + + IFERROR(Q114*U114,"") + + + + SUM(V114:X114) + + + + + + + + + + + + + + + + + + + + + + + + IF(C114<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C114,1),"") + + + + IFERROR(IF(ISNUMBER(E115),0,1),"") + + + + IFERROR(YEAR(C115),"") + + + + IF(D115=0,F114*Variables!$B$7,"") + + + + IF(D115=0,G114*Variables!$B$7,"") + + + + IF(D115=0,H114*Variables!$B$7,"") + + + + IFERROR((F115*Variables!$B$8),"") + + + + IFERROR((G115*Variables!$B$9),"") + + + + IFERROR((H115*Variables!$B$10),"") + + + + IFERROR(SUM(I115:K115),"") + + + + + + IFERROR(I115/1000*$O$14,"") + + + + IFERROR(J115/1000*$P$14,"") + + + + IFERROR(K115/1000*$Q$14,"") + + + + + + + IFERROR(U114+Variables!$B$15,"") + + + + IFERROR(O115*U115,"") + + + + IFERROR(P115*U115,"") + + + + IFERROR(Q115*U115,"") + + + + SUM(V115:X115) + + + + + + + + + + + + + + + + + + + + + + + + IF(C115<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C115,1),"") + + + + IFERROR(IF(ISNUMBER(E116),0,1),"") + + + + IFERROR(YEAR(C116),"") + + + + IF(D116=0,F115*Variables!$B$7,"") + + + + IF(D116=0,G115*Variables!$B$7,"") + + + + IF(D116=0,H115*Variables!$B$7,"") + + + + IFERROR((F116*Variables!$B$8),"") + + + + IFERROR((G116*Variables!$B$9),"") + + + + IFERROR((H116*Variables!$B$10),"") + + + + IFERROR(SUM(I116:K116),"") + + + + + + IFERROR(I116/1000*$O$14,"") + + + + IFERROR(J116/1000*$P$14,"") + + + + IFERROR(K116/1000*$Q$14,"") + + + + + + + IFERROR(U115+Variables!$B$15,"") + + + + IFERROR(O116*U116,"") + + + + IFERROR(P116*U116,"") + + + + IFERROR(Q116*U116,"") + + + + SUM(V116:X116) + + + + + + + + + + + + + + + + + + + + + + + + IF(C116<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C116,1),"") + + + + IFERROR(IF(ISNUMBER(E117),0,1),"") + + + + IFERROR(YEAR(C117),"") + + + + IF(D117=0,F116*Variables!$B$7,"") + + + + IF(D117=0,G116*Variables!$B$7,"") + + + + IF(D117=0,H116*Variables!$B$7,"") + + + + IFERROR((F117*Variables!$B$8),"") + + + + IFERROR((G117*Variables!$B$9),"") + + + + IFERROR((H117*Variables!$B$10),"") + + + + IFERROR(SUM(I117:K117),"") + + + + + + IFERROR(I117/1000*$O$14,"") + + + + IFERROR(J117/1000*$P$14,"") + + + + IFERROR(K117/1000*$Q$14,"") + + + + + + + IFERROR(U116+Variables!$B$15,"") + + + + IFERROR(O117*U117,"") + + + + IFERROR(P117*U117,"") + + + + IFERROR(Q117*U117,"") + + + + SUM(V117:X117) + + + + + + + + + + + + + + + + + + + + + + + + IF(C117<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C117,1),"") + + + + IFERROR(IF(ISNUMBER(E118),0,1),"") + + + + IFERROR(YEAR(C118),"") + + + + IF(D118=0,F117*Variables!$B$7,"") + + + + IF(D118=0,G117*Variables!$B$7,"") + + + + IF(D118=0,H117*Variables!$B$7,"") + + + + IFERROR((F118*Variables!$B$8),"") + + + + IFERROR((G118*Variables!$B$9),"") + + + + IFERROR((H118*Variables!$B$10),"") + + + + IFERROR(SUM(I118:K118),"") + + + + + + IFERROR(I118/1000*$O$14,"") + + + + IFERROR(J118/1000*$P$14,"") + + + + IFERROR(K118/1000*$Q$14,"") + + + + + + + IFERROR(U117+Variables!$B$15,"") + + + + IFERROR(O118*U118,"") + + + + IFERROR(P118*U118,"") + + + + IFERROR(Q118*U118,"") + + + + SUM(V118:X118) + + + + + + + + + + + + + + + + + + + + + + + + IF(C118<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C118,1),"") + + + + IFERROR(IF(ISNUMBER(E119),0,1),"") + + + + IFERROR(YEAR(C119),"") + + + + IF(D119=0,F118*Variables!$B$7,"") + + + + IF(D119=0,G118*Variables!$B$7,"") + + + + IF(D119=0,H118*Variables!$B$7,"") + + + + IFERROR((F119*Variables!$B$8),"") + + + + IFERROR((G119*Variables!$B$9),"") + + + + IFERROR((H119*Variables!$B$10),"") + + + + IFERROR(SUM(I119:K119),"") + + + + + + IFERROR(I119/1000*$O$14,"") + + + + IFERROR(J119/1000*$P$14,"") + + + + IFERROR(K119/1000*$Q$14,"") + + + + + + + IFERROR(U118+Variables!$B$15,"") + + + + IFERROR(O119*U119,"") + + + + IFERROR(P119*U119,"") + + + + IFERROR(Q119*U119,"") + + + + SUM(V119:X119) + + + + + + + + + + + + + + + + + + + + + + + + IF(C119<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C119,1),"") + + + + IFERROR(IF(ISNUMBER(E120),0,1),"") + + + + IFERROR(YEAR(C120),"") + + + + IF(D120=0,F119*Variables!$B$7,"") + + + + IF(D120=0,G119*Variables!$B$7,"") + + + + IF(D120=0,H119*Variables!$B$7,"") + + + + IFERROR((F120*Variables!$B$8),"") + + + + IFERROR((G120*Variables!$B$9),"") + + + + IFERROR((H120*Variables!$B$10),"") + + + + IFERROR(SUM(I120:K120),"") + + + + + + IFERROR(I120/1000*$O$14,"") + + + + IFERROR(J120/1000*$P$14,"") + + + + IFERROR(K120/1000*$Q$14,"") + + + + + + + IFERROR(U119+Variables!$B$15,"") + + + + IFERROR(O120*U120,"") + + + + IFERROR(P120*U120,"") + + + + IFERROR(Q120*U120,"") + + + + SUM(V120:X120) + + + + + + + + + + + + + + + + + + + + + + + + IF(C120<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C120,1),"") + + + + IFERROR(IF(ISNUMBER(E121),0,1),"") + + + + IFERROR(YEAR(C121),"") + + + + IF(D121=0,F120*Variables!$B$7,"") + + + + IF(D121=0,G120*Variables!$B$7,"") + + + + IF(D121=0,H120*Variables!$B$7,"") + + + + IFERROR((F121*Variables!$B$8),"") + + + + IFERROR((G121*Variables!$B$9),"") + + + + IFERROR((H121*Variables!$B$10),"") + + + + IFERROR(SUM(I121:K121),"") + + + + + + IFERROR(I121/1000*$O$14,"") + + + + IFERROR(J121/1000*$P$14,"") + + + + IFERROR(K121/1000*$Q$14,"") + + + + + + + IFERROR(U120+Variables!$B$15,"") + + + + IFERROR(O121*U121,"") + + + + IFERROR(P121*U121,"") + + + + IFERROR(Q121*U121,"") + + + + SUM(V121:X121) + + + + + + + + + + + + + + + + + + + + + + + + IF(C121<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C121,1),"") + + + + IFERROR(IF(ISNUMBER(E122),0,1),"") + + + + IFERROR(YEAR(C122),"") + + + + IF(D122=0,F121*Variables!$B$7,"") + + + + IF(D122=0,G121*Variables!$B$7,"") + + + + IF(D122=0,H121*Variables!$B$7,"") + + + + IFERROR((F122*Variables!$B$8),"") + + + + IFERROR((G122*Variables!$B$9),"") + + + + IFERROR((H122*Variables!$B$10),"") + + + + IFERROR(SUM(I122:K122),"") + + + + + + IFERROR(I122/1000*$O$14,"") + + + + IFERROR(J122/1000*$P$14,"") + + + + IFERROR(K122/1000*$Q$14,"") + + + + + + + IFERROR(U121+Variables!$B$15,"") + + + + IFERROR(O122*U122,"") + + + + IFERROR(P122*U122,"") + + + + IFERROR(Q122*U122,"") + + + + SUM(V122:X122) + + + + + + + + + + + + + + + + + + + + + + + + IF(C122<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C122,1),"") + + + + IFERROR(IF(ISNUMBER(E123),0,1),"") + + + + IFERROR(YEAR(C123),"") + + + + IF(D123=0,F122*Variables!$B$7,"") + + + + IF(D123=0,G122*Variables!$B$7,"") + + + + IF(D123=0,H122*Variables!$B$7,"") + + + + IFERROR((F123*Variables!$B$8),"") + + + + IFERROR((G123*Variables!$B$9),"") + + + + IFERROR((H123*Variables!$B$10),"") + + + + IFERROR(SUM(I123:K123),"") + + + + + + IFERROR(I123/1000*$O$14,"") + + + + IFERROR(J123/1000*$P$14,"") + + + + IFERROR(K123/1000*$Q$14,"") + + + + + + + IFERROR(U122+Variables!$B$15,"") + + + + IFERROR(O123*U123,"") + + + + IFERROR(P123*U123,"") + + + + IFERROR(Q123*U123,"") + + + + SUM(V123:X123) + + + + + + + + + + + + + + + + + + + + + + + + IF(C123<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C123,1),"") + + + + IFERROR(IF(ISNUMBER(E124),0,1),"") + + + + IFERROR(YEAR(C124),"") + + + + IF(D124=0,F123*Variables!$B$7,"") + + + + IF(D124=0,G123*Variables!$B$7,"") + + + + IF(D124=0,H123*Variables!$B$7,"") + + + + IFERROR((F124*Variables!$B$8),"") + + + + IFERROR((G124*Variables!$B$9),"") + + + + IFERROR((H124*Variables!$B$10),"") + + + + IFERROR(SUM(I124:K124),"") + + + + + + IFERROR(I124/1000*$O$14,"") + + + + IFERROR(J124/1000*$P$14,"") + + + + IFERROR(K124/1000*$Q$14,"") + + + + + + + IFERROR(U123+Variables!$B$15,"") + + + + IFERROR(O124*U124,"") + + + + IFERROR(P124*U124,"") + + + + IFERROR(Q124*U124,"") + + + + SUM(V124:X124) + + + + + + + + + + + + + + + + + + + + + + + + IF(C124<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C124,1),"") + + + + IFERROR(IF(ISNUMBER(E125),0,1),"") + + + + IFERROR(YEAR(C125),"") + + + + IF(D125=0,F124*Variables!$B$7,"") + + + + IF(D125=0,G124*Variables!$B$7,"") + + + + IF(D125=0,H124*Variables!$B$7,"") + + + + IFERROR((F125*Variables!$B$8),"") + + + + IFERROR((G125*Variables!$B$9),"") + + + + IFERROR((H125*Variables!$B$10),"") + + + + IFERROR(SUM(I125:K125),"") + + + + + + IFERROR(I125/1000*$O$14,"") + + + + IFERROR(J125/1000*$P$14,"") + + + + IFERROR(K125/1000*$Q$14,"") + + + + + + + IFERROR(U124+Variables!$B$15,"") + + + + IFERROR(O125*U125,"") + + + + IFERROR(P125*U125,"") + + + + IFERROR(Q125*U125,"") + + + + SUM(V125:X125) + + + + + + + + + + + + + + + + + + + + + + + + IF(C125<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C125,1),"") + + + + IFERROR(IF(ISNUMBER(E126),0,1),"") + + + + IFERROR(YEAR(C126),"") + + + + IF(D126=0,F125*Variables!$B$7,"") + + + + IF(D126=0,G125*Variables!$B$7,"") + + + + IF(D126=0,H125*Variables!$B$7,"") + + + + IFERROR((F126*Variables!$B$8),"") + + + + IFERROR((G126*Variables!$B$9),"") + + + + IFERROR((H126*Variables!$B$10),"") + + + + IFERROR(SUM(I126:K126),"") + + + + + + IFERROR(I126/1000*$O$14,"") + + + + IFERROR(J126/1000*$P$14,"") + + + + IFERROR(K126/1000*$Q$14,"") + + + + + + + IFERROR(U125+Variables!$B$15,"") + + + + IFERROR(O126*U126,"") + + + + IFERROR(P126*U126,"") + + + + IFERROR(Q126*U126,"") + + + + SUM(V126:X126) + + + + + + + + + + + + + + + + + + + + + + + + IF(C126<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C126,1),"") + + + + IFERROR(IF(ISNUMBER(E127),0,1),"") + + + + IFERROR(YEAR(C127),"") + + + + IF(D127=0,F126*Variables!$B$7,"") + + + + IF(D127=0,G126*Variables!$B$7,"") + + + + IF(D127=0,H126*Variables!$B$7,"") + + + + IFERROR((F127*Variables!$B$8),"") + + + + IFERROR((G127*Variables!$B$9),"") + + + + IFERROR((H127*Variables!$B$10),"") + + + + IFERROR(SUM(I127:K127),"") + + + + + + IFERROR(I127/1000*$O$14,"") + + + + IFERROR(J127/1000*$P$14,"") + + + + IFERROR(K127/1000*$Q$14,"") + + + + + + + IFERROR(U126+Variables!$B$15,"") + + + + IFERROR(O127*U127,"") + + + + IFERROR(P127*U127,"") + + + + IFERROR(Q127*U127,"") + + + + SUM(V127:X127) + + + + + + + + + + + + + + + + + + + + + + + + IF(C127<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C127,1),"") + + + + IFERROR(IF(ISNUMBER(E128),0,1),"") + + + + IFERROR(YEAR(C128),"") + + + + IF(D128=0,F127*Variables!$B$7,"") + + + + IF(D128=0,G127*Variables!$B$7,"") + + + + IF(D128=0,H127*Variables!$B$7,"") + + + + IFERROR((F128*Variables!$B$8),"") + + + + IFERROR((G128*Variables!$B$9),"") + + + + IFERROR((H128*Variables!$B$10),"") + + + + IFERROR(SUM(I128:K128),"") + + + + + + IFERROR(I128/1000*$O$14,"") + + + + IFERROR(J128/1000*$P$14,"") + + + + IFERROR(K128/1000*$Q$14,"") + + + + + + + IFERROR(U127+Variables!$B$15,"") + + + + IFERROR(O128*U128,"") + + + + IFERROR(P128*U128,"") + + + + IFERROR(Q128*U128,"") + + + + SUM(V128:X128) + + + + + + + + + + + + + + + + + + + + + + + + IF(C128<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C128,1),"") + + + + IFERROR(IF(ISNUMBER(E129),0,1),"") + + + + IFERROR(YEAR(C129),"") + + + + IF(D129=0,F128*Variables!$B$7,"") + + + + IF(D129=0,G128*Variables!$B$7,"") + + + + IF(D129=0,H128*Variables!$B$7,"") + + + + IFERROR((F129*Variables!$B$8),"") + + + + IFERROR((G129*Variables!$B$9),"") + + + + IFERROR((H129*Variables!$B$10),"") + + + + IFERROR(SUM(I129:K129),"") + + + + + + IFERROR(I129/1000*$O$14,"") + + + + IFERROR(J129/1000*$P$14,"") + + + + IFERROR(K129/1000*$Q$14,"") + + + + + + + IFERROR(U128+Variables!$B$15,"") + + + + IFERROR(O129*U129,"") + + + + IFERROR(P129*U129,"") + + + + IFERROR(Q129*U129,"") + + + + SUM(V129:X129) + + + + + + + + + + + + + + + + + + + + + + + + IF(C129<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C129,1),"") + + + + IFERROR(IF(ISNUMBER(E130),0,1),"") + + + + IFERROR(YEAR(C130),"") + + + + IF(D130=0,F129*Variables!$B$7,"") + + + + IF(D130=0,G129*Variables!$B$7,"") + + + + IF(D130=0,H129*Variables!$B$7,"") + + + + IFERROR((F130*Variables!$B$8),"") + + + + IFERROR((G130*Variables!$B$9),"") + + + + IFERROR((H130*Variables!$B$10),"") + + + + IFERROR(SUM(I130:K130),"") + + + + + + IFERROR(I130/1000*$O$14,"") + + + + IFERROR(J130/1000*$P$14,"") + + + + IFERROR(K130/1000*$Q$14,"") + + + + + + + IFERROR(U129+Variables!$B$15,"") + + + + IFERROR(O130*U130,"") + + + + IFERROR(P130*U130,"") + + + + IFERROR(Q130*U130,"") + + + + SUM(V130:X130) + + + + + + + + + + + + + + + + + + + + + + + + IF(C130<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C130,1),"") + + + + IFERROR(IF(ISNUMBER(E131),0,1),"") + + + + IFERROR(YEAR(C131),"") + + + + IF(D131=0,F130*Variables!$B$7,"") + + + + IF(D131=0,G130*Variables!$B$7,"") + + + + IF(D131=0,H130*Variables!$B$7,"") + + + + IFERROR((F131*Variables!$B$8),"") + + + + IFERROR((G131*Variables!$B$9),"") + + + + IFERROR((H131*Variables!$B$10),"") + + + + IFERROR(SUM(I131:K131),"") + + + + + + IFERROR(I131/1000*$O$14,"") + + + + IFERROR(J131/1000*$P$14,"") + + + + IFERROR(K131/1000*$Q$14,"") + + + + + + + IFERROR(U130+Variables!$B$15,"") + + + + IFERROR(O131*U131,"") + + + + IFERROR(P131*U131,"") + + + + IFERROR(Q131*U131,"") + + + + SUM(V131:X131) + + + + + + + + + + + + + + + + + + + + + + + + IF(C131<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C131,1),"") + + + + IFERROR(IF(ISNUMBER(E132),0,1),"") + + + + IFERROR(YEAR(C132),"") + + + + IF(D132=0,F131*Variables!$B$7,"") + + + + IF(D132=0,G131*Variables!$B$7,"") + + + + IF(D132=0,H131*Variables!$B$7,"") + + + + IFERROR((F132*Variables!$B$8),"") + + + + IFERROR((G132*Variables!$B$9),"") + + + + IFERROR((H132*Variables!$B$10),"") + + + + IFERROR(SUM(I132:K132),"") + + + + + + IFERROR(I132/1000*$O$14,"") + + + + IFERROR(J132/1000*$P$14,"") + + + + IFERROR(K132/1000*$Q$14,"") + + + + + + + IFERROR(U131+Variables!$B$15,"") + + + + IFERROR(O132*U132,"") + + + + IFERROR(P132*U132,"") + + + + IFERROR(Q132*U132,"") + + + + SUM(V132:X132) + + + + + + + + + + + + + + + + + + + + + + + + IF(C132<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C132,1),"") + + + + IFERROR(IF(ISNUMBER(E133),0,1),"") + + + + IFERROR(YEAR(C133),"") + + + + IF(D133=0,F132*Variables!$B$7,"") + + + + IF(D133=0,G132*Variables!$B$7,"") + + + + IF(D133=0,H132*Variables!$B$7,"") + + + + IFERROR((F133*Variables!$B$8),"") + + + + IFERROR((G133*Variables!$B$9),"") + + + + IFERROR((H133*Variables!$B$10),"") + + + + IFERROR(SUM(I133:K133),"") + + + + + + IFERROR(I133/1000*$O$14,"") + + + + IFERROR(J133/1000*$P$14,"") + + + + IFERROR(K133/1000*$Q$14,"") + + + + + + + IFERROR(U132+Variables!$B$15,"") + + + + IFERROR(O133*U133,"") + + + + IFERROR(P133*U133,"") + + + + IFERROR(Q133*U133,"") + + + + SUM(V133:X133) + + + + + + + + + + + + + + + + + + + + + + + + IF(C133<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C133,1),"") + + + + IFERROR(IF(ISNUMBER(E134),0,1),"") + + + + IFERROR(YEAR(C134),"") + + + + IF(D134=0,F133*Variables!$B$7,"") + + + + IF(D134=0,G133*Variables!$B$7,"") + + + + IF(D134=0,H133*Variables!$B$7,"") + + + + IFERROR((F134*Variables!$B$8),"") + + + + IFERROR((G134*Variables!$B$9),"") + + + + IFERROR((H134*Variables!$B$10),"") + + + + IFERROR(SUM(I134:K134),"") + + + + + + IFERROR(I134/1000*$O$14,"") + + + + IFERROR(J134/1000*$P$14,"") + + + + IFERROR(K134/1000*$Q$14,"") + + + + + + + IFERROR(U133+Variables!$B$15,"") + + + + IFERROR(O134*U134,"") + + + + IFERROR(P134*U134,"") + + + + IFERROR(Q134*U134,"") + + + + SUM(V134:X134) + + + + + + + + + + + + + + + + + + + + + + + + IF(C134<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C134,1),"") + + + + IFERROR(IF(ISNUMBER(E135),0,1),"") + + + + IFERROR(YEAR(C135),"") + + + + IF(D135=0,F134*Variables!$B$7,"") + + + + IF(D135=0,G134*Variables!$B$7,"") + + + + IF(D135=0,H134*Variables!$B$7,"") + + + + IFERROR((F135*Variables!$B$8),"") + + + + IFERROR((G135*Variables!$B$9),"") + + + + IFERROR((H135*Variables!$B$10),"") + + + + IFERROR(SUM(I135:K135),"") + + + + + + IFERROR(I135/1000*$O$14,"") + + + + IFERROR(J135/1000*$P$14,"") + + + + IFERROR(K135/1000*$Q$14,"") + + + + + + + IFERROR(U134+Variables!$B$15,"") + + + + IFERROR(O135*U135,"") + + + + IFERROR(P135*U135,"") + + + + IFERROR(Q135*U135,"") + + + + SUM(V135:X135) + + + + + + + + + + + + + + + + + + + + + + + + IF(C135<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C135,1),"") + + + + IFERROR(IF(ISNUMBER(E136),0,1),"") + + + + IFERROR(YEAR(C136),"") + + + + IF(D136=0,F135*Variables!$B$7,"") + + + + IF(D136=0,G135*Variables!$B$7,"") + + + + IF(D136=0,H135*Variables!$B$7,"") + + + + IFERROR((F136*Variables!$B$8),"") + + + + IFERROR((G136*Variables!$B$9),"") + + + + IFERROR((H136*Variables!$B$10),"") + + + + IFERROR(SUM(I136:K136),"") + + + + + + IFERROR(I136/1000*$O$14,"") + + + + IFERROR(J136/1000*$P$14,"") + + + + IFERROR(K136/1000*$Q$14,"") + + + + + + + IFERROR(U135+Variables!$B$15,"") + + + + IFERROR(O136*U136,"") + + + + IFERROR(P136*U136,"") + + + + IFERROR(Q136*U136,"") + + + + SUM(V136:X136) + + + + + + + + + + + + + + + + + + + + + + + + IF(C136<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C136,1),"") + + + + IFERROR(IF(ISNUMBER(E137),0,1),"") + + + + IFERROR(YEAR(C137),"") + + + + IF(D137=0,F136*Variables!$B$7,"") + + + + IF(D137=0,G136*Variables!$B$7,"") + + + + IF(D137=0,H136*Variables!$B$7,"") + + + + IFERROR((F137*Variables!$B$8),"") + + + + IFERROR((G137*Variables!$B$9),"") + + + + IFERROR((H137*Variables!$B$10),"") + + + + IFERROR(SUM(I137:K137),"") + + + + + + IFERROR(I137/1000*$O$14,"") + + + + IFERROR(J137/1000*$P$14,"") + + + + IFERROR(K137/1000*$Q$14,"") + + + + + + + IFERROR(U136+Variables!$B$15,"") + + + + IFERROR(O137*U137,"") + + + + IFERROR(P137*U137,"") + + + + IFERROR(Q137*U137,"") + + + + SUM(V137:X137) + + + + + + + + + + + + + + + + + + + + + + + + IF(C137<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C137,1),"") + + + + IFERROR(IF(ISNUMBER(E138),0,1),"") + + + + IFERROR(YEAR(C138),"") + + + + IF(D138=0,F137*Variables!$B$7,"") + + + + IF(D138=0,G137*Variables!$B$7,"") + + + + IF(D138=0,H137*Variables!$B$7,"") + + + + IFERROR((F138*Variables!$B$8),"") + + + + IFERROR((G138*Variables!$B$9),"") + + + + IFERROR((H138*Variables!$B$10),"") + + + + IFERROR(SUM(I138:K138),"") + + + + + + IFERROR(I138/1000*$O$14,"") + + + + IFERROR(J138/1000*$P$14,"") + + + + IFERROR(K138/1000*$Q$14,"") + + + + + + + IFERROR(U137+Variables!$B$15,"") + + + + IFERROR(O138*U138,"") + + + + IFERROR(P138*U138,"") + + + + IFERROR(Q138*U138,"") + + + + SUM(V138:X138) + + + + + + + + + + + + + + + + + + + + + + + + IF(C138<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C138,1),"") + + + + IFERROR(IF(ISNUMBER(E139),0,1),"") + + + + IFERROR(YEAR(C139),"") + + + + IF(D139=0,F138*Variables!$B$7,"") + + + + IF(D139=0,G138*Variables!$B$7,"") + + + + IF(D139=0,H138*Variables!$B$7,"") + + + + IFERROR((F139*Variables!$B$8),"") + + + + IFERROR((G139*Variables!$B$9),"") + + + + IFERROR((H139*Variables!$B$10),"") + + + + IFERROR(SUM(I139:K139),"") + + + + + + IFERROR(I139/1000*$O$14,"") + + + + IFERROR(J139/1000*$P$14,"") + + + + IFERROR(K139/1000*$Q$14,"") + + + + + + + IFERROR(U138+Variables!$B$15,"") + + + + IFERROR(O139*U139,"") + + + + IFERROR(P139*U139,"") + + + + IFERROR(Q139*U139,"") + + + + SUM(V139:X139) + + + + + + + + + + + + + + + + + + + + + + + + IF(C139<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C139,1),"") + + + + IFERROR(IF(ISNUMBER(E140),0,1),"") + + + + IFERROR(YEAR(C140),"") + + + + IF(D140=0,F139*Variables!$B$7,"") + + + + IF(D140=0,G139*Variables!$B$7,"") + + + + IF(D140=0,H139*Variables!$B$7,"") + + + + IFERROR((F140*Variables!$B$8),"") + + + + IFERROR((G140*Variables!$B$9),"") + + + + IFERROR((H140*Variables!$B$10),"") + + + + IFERROR(SUM(I140:K140),"") + + + + + + IFERROR(I140/1000*$O$14,"") + + + + IFERROR(J140/1000*$P$14,"") + + + + IFERROR(K140/1000*$Q$14,"") + + + + + + + IFERROR(U139+Variables!$B$15,"") + + + + IFERROR(O140*U140,"") + + + + IFERROR(P140*U140,"") + + + + IFERROR(Q140*U140,"") + + + + SUM(V140:X140) + + + + + + + + + + + + + + + + + + + + + + + + IF(C140<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C140,1),"") + + + + IFERROR(IF(ISNUMBER(E141),0,1),"") + + + + IFERROR(YEAR(C141),"") + + + + IF(D141=0,F140*Variables!$B$7,"") + + + + IF(D141=0,G140*Variables!$B$7,"") + + + + IF(D141=0,H140*Variables!$B$7,"") + + + + IFERROR((F141*Variables!$B$8),"") + + + + IFERROR((G141*Variables!$B$9),"") + + + + IFERROR((H141*Variables!$B$10),"") + + + + IFERROR(SUM(I141:K141),"") + + + + + + IFERROR(I141/1000*$O$14,"") + + + + IFERROR(J141/1000*$P$14,"") + + + + IFERROR(K141/1000*$Q$14,"") + + + + + + + IFERROR(U140+Variables!$B$15,"") + + + + IFERROR(O141*U141,"") + + + + IFERROR(P141*U141,"") + + + + IFERROR(Q141*U141,"") + + + + SUM(V141:X141) + + + + + + + + + + + + + + + + + + + + + + + + IF(C141<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C141,1),"") + + + + IFERROR(IF(ISNUMBER(E142),0,1),"") + + + + IFERROR(YEAR(C142),"") + + + + IF(D142=0,F141*Variables!$B$7,"") + + + + IF(D142=0,G141*Variables!$B$7,"") + + + + IF(D142=0,H141*Variables!$B$7,"") + + + + IFERROR((F142*Variables!$B$8),"") + + + + IFERROR((G142*Variables!$B$9),"") + + + + IFERROR((H142*Variables!$B$10),"") + + + + IFERROR(SUM(I142:K142),"") + + + + + + IFERROR(I142/1000*$O$14,"") + + + + IFERROR(J142/1000*$P$14,"") + + + + IFERROR(K142/1000*$Q$14,"") + + + + + + + IFERROR(U141+Variables!$B$15,"") + + + + IFERROR(O142*U142,"") + + + + IFERROR(P142*U142,"") + + + + IFERROR(Q142*U142,"") + + + + SUM(V142:X142) + + + + + + + + + + + + + + + + + + + + + + + + IF(C142<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C142,1),"") + + + + IFERROR(IF(ISNUMBER(E143),0,1),"") + + + + IFERROR(YEAR(C143),"") + + + + IF(D143=0,F142*Variables!$B$7,"") + + + + IF(D143=0,G142*Variables!$B$7,"") + + + + IF(D143=0,H142*Variables!$B$7,"") + + + + IFERROR((F143*Variables!$B$8),"") + + + + IFERROR((G143*Variables!$B$9),"") + + + + IFERROR((H143*Variables!$B$10),"") + + + + IFERROR(SUM(I143:K143),"") + + + + + + IFERROR(I143/1000*$O$14,"") + + + + IFERROR(J143/1000*$P$14,"") + + + + IFERROR(K143/1000*$Q$14,"") + + + + + + + IFERROR(U142+Variables!$B$15,"") + + + + IFERROR(O143*U143,"") + + + + IFERROR(P143*U143,"") + + + + IFERROR(Q143*U143,"") + + + + SUM(V143:X143) + + + + + + + + + + + + + + + + + + + + + + + + IF(C143<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C143,1),"") + + + + IFERROR(IF(ISNUMBER(E144),0,1),"") + + + + IFERROR(YEAR(C144),"") + + + + IF(D144=0,F143*Variables!$B$7,"") + + + + IF(D144=0,G143*Variables!$B$7,"") + + + + IF(D144=0,H143*Variables!$B$7,"") + + + + IFERROR((F144*Variables!$B$8),"") + + + + IFERROR((G144*Variables!$B$9),"") + + + + IFERROR((H144*Variables!$B$10),"") + + + + IFERROR(SUM(I144:K144),"") + + + + + + IFERROR(I144/1000*$O$14,"") + + + + IFERROR(J144/1000*$P$14,"") + + + + IFERROR(K144/1000*$Q$14,"") + + + + + + + IFERROR(U143+Variables!$B$15,"") + + + + IFERROR(O144*U144,"") + + + + IFERROR(P144*U144,"") + + + + IFERROR(Q144*U144,"") + + + + SUM(V144:X144) + + + + + + + + + + + + + + + + + + + + + + + + IF(C144<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C144,1),"") + + + + IFERROR(IF(ISNUMBER(E145),0,1),"") + + + + IFERROR(YEAR(C145),"") + + + + IF(D145=0,F144*Variables!$B$7,"") + + + + IF(D145=0,G144*Variables!$B$7,"") + + + + IF(D145=0,H144*Variables!$B$7,"") + + + + IFERROR((F145*Variables!$B$8),"") + + + + IFERROR((G145*Variables!$B$9),"") + + + + IFERROR((H145*Variables!$B$10),"") + + + + IFERROR(SUM(I145:K145),"") + + + + + + IFERROR(I145/1000*$O$14,"") + + + + IFERROR(J145/1000*$P$14,"") + + + + IFERROR(K145/1000*$Q$14,"") + + + + + + + IFERROR(U144+Variables!$B$15,"") + + + + IFERROR(O145*U145,"") + + + + IFERROR(P145*U145,"") + + + + IFERROR(Q145*U145,"") + + + + SUM(V145:X145) + + + + + + + + + + + + + + + + + + + + + + + + IF(C145<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C145,1),"") + + + + IFERROR(IF(ISNUMBER(E146),0,1),"") + + + + IFERROR(YEAR(C146),"") + + + + IF(D146=0,F145*Variables!$B$7,"") + + + + IF(D146=0,G145*Variables!$B$7,"") + + + + IF(D146=0,H145*Variables!$B$7,"") + + + + IFERROR((F146*Variables!$B$8),"") + + + + IFERROR((G146*Variables!$B$9),"") + + + + IFERROR((H146*Variables!$B$10),"") + + + + IFERROR(SUM(I146:K146),"") + + + + + + IFERROR(I146/1000*$O$14,"") + + + + IFERROR(J146/1000*$P$14,"") + + + + IFERROR(K146/1000*$Q$14,"") + + + + + + + IFERROR(U145+Variables!$B$15,"") + + + + IFERROR(O146*U146,"") + + + + IFERROR(P146*U146,"") + + + + IFERROR(Q146*U146,"") + + + + SUM(V146:X146) + + + + + + + + + + + + + + + + + + + + + + + + IF(C146<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C146,1),"") + + + + IFERROR(IF(ISNUMBER(E147),0,1),"") + + + + IFERROR(YEAR(C147),"") + + + + IF(D147=0,F146*Variables!$B$7,"") + + + + IF(D147=0,G146*Variables!$B$7,"") + + + + IF(D147=0,H146*Variables!$B$7,"") + + + + IFERROR((F147*Variables!$B$8),"") + + + + + IFERROR((H147*Variables!$B$10),"") + + + + IFERROR(SUM(I147:K147),"") + + + + + + IFERROR(I147/1000*$O$14,"") + + + + IFERROR(J147/1000*$P$14,"") + + + + IFERROR(K147/1000*$Q$14,"") + + + + + + + IFERROR(U146+Variables!$B$15,"") + + + + IFERROR(O147*U147,"") + + + + IFERROR(P147*U147,"") + + + + IFERROR(Q147*U147,"") + + + + SUM(V147:X147) + + + + + + + + + + + + + + + + + + + + + + + + IF(C147<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C147,1),"") + + + + IFERROR(IF(ISNUMBER(E148),0,1),"") + + + + IFERROR(YEAR(C148),"") + + + + IF(D148=0,F147*Variables!$B$7,"") + + + + IF(D148=0,G147*Variables!$B$7,"") + + + + IF(D148=0,H147*Variables!$B$7,"") + + + + IFERROR((F148*Variables!$B$8),"") + + + + + IFERROR((H148*Variables!$B$10),"") + + + + IFERROR(SUM(I148:K148),"") + + + + + + IFERROR(I148/1000*$O$14,"") + + + + IFERROR(J148/1000*$P$14,"") + + + + IFERROR(K148/1000*$Q$14,"") + + + + + + + IFERROR(U147+Variables!$B$15,"") + + + + IFERROR(O148*U148,"") + + + + IFERROR(P148*U148,"") + + + + IFERROR(Q148*U148,"") + + + + SUM(V148:X148) + + + + + + + + + + + + + + + + + + + + + + + + IF(C148<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C148,1),"") + + + + IFERROR(IF(ISNUMBER(E149),0,1),"") + + + + IFERROR(YEAR(C149),"") + + + + IF(D149=0,F148*Variables!$B$7,"") + + + + IF(D149=0,G148*Variables!$B$7,"") + + + + IF(D149=0,H148*Variables!$B$7,"") + + + + IFERROR((F149*Variables!$B$8),"") + + + + + IFERROR((H149*Variables!$B$10),"") + + + + IFERROR(SUM(I149:K149),"") + + + + + + IFERROR(I149/1000*$O$14,"") + + + + IFERROR(J149/1000*$P$14,"") + + + + IFERROR(K149/1000*$Q$14,"") + + + + + + + IFERROR(U148+Variables!$B$15,"") + + + + IFERROR(O149*U149,"") + + + + IFERROR(P149*U149,"") + + + + IFERROR(Q149*U149,"") + + + + SUM(V149:X149) + + + + + + + + + + + + + + + + + + + + + + + + IF(C149<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C149,1),"") + + + + IFERROR(IF(ISNUMBER(E150),0,1),"") + + + + IFERROR(YEAR(C150),"") + + + + IF(D150=0,F149*Variables!$B$7,"") + + + + IF(D150=0,G149*Variables!$B$7,"") + + + + IF(D150=0,H149*Variables!$B$7,"") + + + + IFERROR((F150*Variables!$B$8),"") + + + + + IFERROR((H150*Variables!$B$10),"") + + + + IFERROR(SUM(I150:K150),"") + + + + + + IFERROR(I150/1000*$O$14,"") + + + + IFERROR(J150/1000*$P$14,"") + + + + IFERROR(K150/1000*$Q$14,"") + + + + + + + IFERROR(U149+Variables!$B$15,"") + + + + IFERROR(O150*U150,"") + + + + IFERROR(P150*U150,"") + + + + IFERROR(Q150*U150,"") + + + + SUM(V150:X150) + + + + + + + + + + + + + + + + + + + + + + + + IF(C150<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C150,1),"") + + + + IFERROR(IF(ISNUMBER(E151),0,1),"") + + + + IFERROR(YEAR(C151),"") + + + + IF(D151=0,F150*Variables!$B$7,"") + + + + IF(D151=0,G150*Variables!$B$7,"") + + + + IF(D151=0,H150*Variables!$B$7,"") + + + + IFERROR((F151*Variables!$B$8),"") + + + + + IFERROR((H151*Variables!$B$10),"") + + + + IFERROR(SUM(I151:K151),"") + + + + + + IFERROR(I151/1000*$O$14,"") + + + + IFERROR(J151/1000*$P$14,"") + + + + IFERROR(K151/1000*$Q$14,"") + + + + + + + IFERROR(U150+Variables!$B$15,"") + + + + IFERROR(O151*U151,"") + + + + + IFERROR(Q151*U151,"") + + + + SUM(V151:X151) + + + + + + + + + + + + + + + + + + + + + + + + IF(C151<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C151,1),"") + + + + IFERROR(IF(ISNUMBER(E152),0,1),"") + + + + IFERROR(YEAR(C152),"") + + + + IF(D152=0,F151*Variables!$B$7,"") + + + + IF(D152=0,G151*Variables!$B$7,"") + + + + IF(D152=0,H151*Variables!$B$7,"") + + + + IFERROR((F152*Variables!$B$8),"") + + + + + IFERROR((H152*Variables!$B$10),"") + + + + IFERROR(SUM(I152:K152),"") + + + + + + IFERROR(I152/1000*$O$14,"") + + + + IFERROR(J152/1000*$P$14,"") + + + + IFERROR(K152/1000*$Q$14,"") + + + + + + + IFERROR(U151+Variables!$B$15,"") + + + + IFERROR(O152*U152,"") + + + + + IFERROR(Q152*U152,"") + + + + SUM(V152:X152) + + + + + + + + + + + + + + + + + + + + + + + + IF(C152<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C152,1),"") + + + + IFERROR(IF(ISNUMBER(E153),0,1),"") + + + + IFERROR(YEAR(C153),"") + + + + IF(D153=0,F152*Variables!$B$7,"") + + + + IF(D153=0,G152*Variables!$B$7,"") + + + + IF(D153=0,H152*Variables!$B$7,"") + + + + IFERROR((F153*Variables!$B$8),"") + + + + + IFERROR((H153*Variables!$B$10),"") + + + + IFERROR(SUM(I153:K153),"") + + + + + + IFERROR(I153/1000*$O$14,"") + + + + IFERROR(J153/1000*$P$14,"") + + + + IFERROR(K153/1000*$Q$14,"") + + + + + + + IFERROR(U152+Variables!$B$15,"") + + + + IFERROR(O153*U153,"") + + + + + IFERROR(Q153*U153,"") + + + + SUM(V153:X153) + + + + + + + + + + + + + + + + + + + + + + + + IF(C153<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C153,1),"") + + + + IFERROR(IF(ISNUMBER(E154),0,1),"") + + + + IFERROR(YEAR(C154),"") + + + + IF(D154=0,F153*Variables!$B$7,"") + + + + IF(D154=0,G153*Variables!$B$7,"") + + + + IF(D154=0,H153*Variables!$B$7,"") + + + + IFERROR((F154*Variables!$B$8),"") + + + + + IFERROR((H154*Variables!$B$10),"") + + + + IFERROR(SUM(I154:K154),"") + + + + + + IFERROR(I154/1000*$O$14,"") + + + + IFERROR(J154/1000*$P$14,"") + + + + IFERROR(K154/1000*$Q$14,"") + + + + + + + IFERROR(U153+Variables!$B$15,"") + + + + IFERROR(O154*U154,"") + + + + + IFERROR(Q154*U154,"") + + + + SUM(V154:X154) + + + + + + + + + + + + + + + + + + + + + + + + IF(C154<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C154,1),"") + + + + IFERROR(IF(ISNUMBER(E155),0,1),"") + + + + IFERROR(YEAR(C155),"") + + + + IF(D155=0,F154*Variables!$B$7,"") + + + + IF(D155=0,G154*Variables!$B$7,"") + + + + IF(D155=0,H154*Variables!$B$7,"") + + + + IFERROR((F155*Variables!$B$8),"") + + + + + IFERROR((H155*Variables!$B$10),"") + + + + IFERROR(SUM(I155:K155),"") + + + + + + IFERROR(I155/1000*$O$14,"") + + + + IFERROR(J155/1000*$P$14,"") + + + + IFERROR(K155/1000*$Q$14,"") + + + + + + + IFERROR(U154+Variables!$B$15,"") + + + + IFERROR(O155*U155,"") + + + + + IFERROR(Q155*U155,"") + + + + SUM(V155:X155) + + + + + + + + + + + + + + + + + + + + + + + + IF(C155<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C155,1),"") + + + + IFERROR(IF(ISNUMBER(E156),0,1),"") + + + + IFERROR(YEAR(C156),"") + + + + IF(D156=0,F155*Variables!$B$7,"") + + + + IF(D156=0,G155*Variables!$B$7,"") + + + + IF(D156=0,H155*Variables!$B$7,"") + + + + IFERROR((F156*Variables!$B$8),"") + + + + + IFERROR((H156*Variables!$B$10),"") + + + + IFERROR(SUM(I156:K156),"") + + + + + + IFERROR(I156/1000*$O$14,"") + + + + IFERROR(J156/1000*$P$14,"") + + + + IFERROR(K156/1000*$Q$14,"") + + + + + + + IFERROR(U155+Variables!$B$15,"") + + + + IFERROR(O156*U156,"") + + + + + IFERROR(Q156*U156,"") + + + + SUM(V156:X156) + + + + + + + + + + + + + + + + + + + + + + + + IF(C156<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C156,1),"") + + + + IFERROR(IF(ISNUMBER(E157),0,1),"") + + + + IFERROR(YEAR(C157),"") + + + + + IF(D157=0,G156*Variables!$B$7,"") + + + + IF(D157=0,H156*Variables!$B$7,"") + + + + IFERROR((F157*Variables!$B$8),"") + + + + + IFERROR((H157*Variables!$B$10),"") + + + + IFERROR(SUM(I157:K157),"") + + + + + + IFERROR(I157/1000*$O$14,"") + + + + IFERROR(J157/1000*$P$14,"") + + + + IFERROR(K157/1000*$Q$14,"") + + + + + + + IFERROR(U156+Variables!$B$15,"") + + + + IFERROR(O157*U157,"") + + + + + IFERROR(Q157*U157,"") + + + + SUM(V157:X157) + + + + + + + + + + + + + + + + + + + + + + + + IF(C157<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C157,1),"") + + + + IFERROR(IF(ISNUMBER(E158),0,1),"") + + + + IFERROR(YEAR(C158),"") + + + + + IF(D158=0,G157*Variables!$B$7,"") + + + + IF(D158=0,H157*Variables!$B$7,"") + + + + IFERROR((F158*Variables!$B$8),"") + + + + + IFERROR((H158*Variables!$B$10),"") + + + + IFERROR(SUM(I158:K158),"") + + + + + + IFERROR(I158/1000*$O$14,"") + + + + IFERROR(J158/1000*$P$14,"") + + + + IFERROR(K158/1000*$Q$14,"") + + + + + + + IFERROR(U157+Variables!$B$15,"") + + + + IFERROR(O158*U158,"") + + + + + IFERROR(Q158*U158,"") + + + + SUM(V158:X158) + + + + + + + + + + + + + + + + + + + + + + + + IF(C158<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C158,1),"") + + + + IFERROR(IF(ISNUMBER(E159),0,1),"") + + + + IFERROR(YEAR(C159),"") + + + + + IF(D159=0,G158*Variables!$B$7,"") + + + + IF(D159=0,H158*Variables!$B$7,"") + + + + IFERROR((F159*Variables!$B$8),"") + + + + + IFERROR((H159*Variables!$B$10),"") + + + + IFERROR(SUM(I159:K159),"") + + + + + + IFERROR(I159/1000*$O$14,"") + + + + IFERROR(J159/1000*$P$14,"") + + + + IFERROR(K159/1000*$Q$14,"") + + + + + + + IFERROR(U158+Variables!$B$15,"") + + + + IFERROR(O159*U159,"") + + + + + IFERROR(Q159*U159,"") + + + + SUM(V159:X159) + + + + + + + + + + + + + + + + + + + + + + + + IF(C159<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C159,1),"") + + + + IFERROR(IF(ISNUMBER(E160),0,1),"") + + + + IFERROR(YEAR(C160),"") + + + + + IF(D160=0,G159*Variables!$B$7,"") + + + + IF(D160=0,H159*Variables!$B$7,"") + + + + IFERROR((F160*Variables!$B$8),"") + + + + + IFERROR((H160*Variables!$B$10),"") + + + + IFERROR(SUM(I160:K160),"") + + + + + + IFERROR(I160/1000*$O$14,"") + + + + IFERROR(J160/1000*$P$14,"") + + + + IFERROR(K160/1000*$Q$14,"") + + + + + + + IFERROR(U159+Variables!$B$15,"") + + + + IFERROR(O160*U160,"") + + + + + IFERROR(Q160*U160,"") + + + + SUM(V160:X160) + + + + + + + + + + + + + + + + + + + + + + + + IF(C160<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C160,1),"") + + + + IFERROR(IF(ISNUMBER(E161),0,1),"") + + + + IFERROR(YEAR(C161),"") + + + + + IF(D161=0,G160*Variables!$B$7,"") + + + + IF(D161=0,H160*Variables!$B$7,"") + + + + IFERROR((F161*Variables!$B$8),"") + + + + + IFERROR((H161*Variables!$B$10),"") + + + + IFERROR(SUM(I161:K161),"") + + + + + + IFERROR(I161/1000*$O$14,"") + + + + IFERROR(J161/1000*$P$14,"") + + + + IFERROR(K161/1000*$Q$14,"") + + + + + + + IFERROR(U160+Variables!$B$15,"") + + + + IFERROR(O161*U161,"") + + + + + IFERROR(Q161*U161,"") + + + + SUM(V161:X161) + + + + + + + + + + + + + + + + + + + + + + + + IF(C161<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C161,1),"") + + + + IFERROR(IF(ISNUMBER(E162),0,1),"") + + + + IFERROR(YEAR(C162),"") + + + + + IF(D162=0,G161*Variables!$B$7,"") + + + + IF(D162=0,H161*Variables!$B$7,"") + + + + IFERROR((F162*Variables!$B$8),"") + + + + + IFERROR((H162*Variables!$B$10),"") + + + + IFERROR(SUM(I162:K162),"") + + + + + + IFERROR(I162/1000*$O$14,"") + + + + IFERROR(J162/1000*$P$14,"") + + + + IFERROR(K162/1000*$Q$14,"") + + + + + + + IFERROR(U161+Variables!$B$15,"") + + + + IFERROR(O162*U162,"") + + + + + IFERROR(Q162*U162,"") + + + + SUM(V162:X162) + + + + + + + + + + + + + + + + + + + + + + + + IF(C162<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C162,1),"") + + + + IFERROR(IF(ISNUMBER(E163),0,1),"") + + + + IFERROR(YEAR(C163),"") + + + + + IF(D163=0,G162*Variables!$B$7,"") + + + + IF(D163=0,H162*Variables!$B$7,"") + + + + IFERROR((F163*Variables!$B$8),"") + + + + + IFERROR((H163*Variables!$B$10),"") + + + + IFERROR(SUM(I163:K163),"") + + + + + + IFERROR(I163/1000*$O$14,"") + + + + IFERROR(J163/1000*$P$14,"") + + + + IFERROR(K163/1000*$Q$14,"") + + + + + + + IFERROR(U162+Variables!$B$15,"") + + + + IFERROR(O163*U163,"") + + + + + IFERROR(Q163*U163,"") + + + + SUM(V163:X163) + + + + + + + + + + + + + + + + + + + + + + + + IF(C163<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C163,1),"") + + + + IFERROR(IF(ISNUMBER(E164),0,1),"") + + + + IFERROR(YEAR(C164),"") + + + + + IF(D164=0,G163*Variables!$B$7,"") + + + + IF(D164=0,H163*Variables!$B$7,"") + + + + IFERROR((F164*Variables!$B$8),"") + + + + + IFERROR((H164*Variables!$B$10),"") + + + + IFERROR(SUM(I164:K164),"") + + + + + + IFERROR(I164/1000*$O$14,"") + + + + IFERROR(J164/1000*$P$14,"") + + + + IFERROR(K164/1000*$Q$14,"") + + + + + + + IFERROR(U163+Variables!$B$15,"") + + + + IFERROR(O164*U164,"") + + + + + IFERROR(Q164*U164,"") + + + + SUM(V164:X164) + + + + + + + + + + + + + + + + + + + + + + + + IF(C164<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C164,1),"") + + + + IFERROR(IF(ISNUMBER(E165),0,1),"") + + + + IFERROR(YEAR(C165),"") + + + + + IF(D165=0,G164*Variables!$B$7,"") + + + + IF(D165=0,H164*Variables!$B$7,"") + + + + IFERROR((F165*Variables!$B$8),"") + + + + + IFERROR((H165*Variables!$B$10),"") + + + + IFERROR(SUM(I165:K165),"") + + + + + + IFERROR(I165/1000*$O$14,"") + + + + IFERROR(J165/1000*$P$14,"") + + + + IFERROR(K165/1000*$Q$14,"") + + + + + + + IFERROR(U164+Variables!$B$15,"") + + + + IFERROR(O165*U165,"") + + + + + IFERROR(Q165*U165,"") + + + + SUM(V165:X165) + + + + + + + + + + + + + + + + + + + + + + + + IF(C165<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C165,1),"") + + + + IFERROR(IF(ISNUMBER(E166),0,1),"") + + + + IFERROR(YEAR(C166),"") + + + + + IF(D166=0,G165*Variables!$B$7,"") + + + + IF(D166=0,H165*Variables!$B$7,"") + + + + IFERROR((F166*Variables!$B$8),"") + + + + + IFERROR((H166*Variables!$B$10),"") + + + + IFERROR(SUM(I166:K166),"") + + + + + + IFERROR(I166/1000*$O$14,"") + + + + IFERROR(J166/1000*$P$14,"") + + + + IFERROR(K166/1000*$Q$14,"") + + + + + + + IFERROR(U165+Variables!$B$15,"") + + + + IFERROR(O166*U166,"") + + + + + IFERROR(Q166*U166,"") + + + + SUM(V166:X166) + + + + + + + + + + + + + + + + + + + + + + + + IF(C166<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C166,1),"") + + + + IFERROR(IF(ISNUMBER(E167),0,1),"") + + + + IFERROR(YEAR(C167),"") + + + + + IF(D167=0,G166*Variables!$B$7,"") + + + + IF(D167=0,H166*Variables!$B$7,"") + + + + IFERROR((F167*Variables!$B$8),"") + + + + + IFERROR((H167*Variables!$B$10),"") + + + + IFERROR(SUM(I167:K167),"") + + + + + + IFERROR(I167/1000*$O$14,"") + + + + IFERROR(J167/1000*$P$14,"") + + + + IFERROR(K167/1000*$Q$14,"") + + + + + + + IFERROR(U166+Variables!$B$15,"") + + + + IFERROR(O167*U167,"") + + + + + IFERROR(Q167*U167,"") + + + + SUM(V167:X167) + + + + + + + + + + + + + + + + + + + + + + + + IF(C167<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C167,1),"") + + + + IFERROR(IF(ISNUMBER(E168),0,1),"") + + + + IFERROR(YEAR(C168),"") + + + + + IF(D168=0,G167*Variables!$B$7,"") + + + + IF(D168=0,H167*Variables!$B$7,"") + + + + IFERROR((F168*Variables!$B$8),"") + + + + + IFERROR((H168*Variables!$B$10),"") + + + + IFERROR(SUM(I168:K168),"") + + + + + + IFERROR(I168/1000*$O$14,"") + + + + IFERROR(J168/1000*$P$14,"") + + + + IFERROR(K168/1000*$Q$14,"") + + + + + + + IFERROR(U167+Variables!$B$15,"") + + + + IFERROR(O168*U168,"") + + + + + IFERROR(Q168*U168,"") + + + + SUM(V168:X168) + + + + + + + + + + + + + + + + + + + + + + + + IF(C168<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C168,1),"") + + + + IFERROR(IF(ISNUMBER(E169),0,1),"") + + + + IFERROR(YEAR(C169),"") + + + + + IF(D169=0,G168*Variables!$B$7,"") + + + + IF(D169=0,H168*Variables!$B$7,"") + + + + IFERROR((F169*Variables!$B$8),"") + + + + + IFERROR((H169*Variables!$B$10),"") + + + + IFERROR(SUM(I169:K169),"") + + + + + + IFERROR(I169/1000*$O$14,"") + + + + IFERROR(J169/1000*$P$14,"") + + + + IFERROR(K169/1000*$Q$14,"") + + + + + + + IFERROR(U168+Variables!$B$15,"") + + + + IFERROR(O169*U169,"") + + + + + IFERROR(Q169*U169,"") + + + + SUM(V169:X169) + + + + + + + + + + + + + + + + + + + + + + + + IF(C169<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C169,1),"") + + + + IFERROR(IF(ISNUMBER(E170),0,1),"") + + + + IFERROR(YEAR(C170),"") + + + + + IF(D170=0,G169*Variables!$B$7,"") + + + + IF(D170=0,H169*Variables!$B$7,"") + + + + IFERROR((F170*Variables!$B$8),"") + + + + + IFERROR((H170*Variables!$B$10),"") + + + + IFERROR(SUM(I170:K170),"") + + + + + + IFERROR(I170/1000*$O$14,"") + + + + IFERROR(J170/1000*$P$14,"") + + + + IFERROR(K170/1000*$Q$14,"") + + + + + + + IFERROR(U169+Variables!$B$15,"") + + + + IFERROR(O170*U170,"") + + + + + IFERROR(Q170*U170,"") + + + + SUM(V170:X170) + + + + + + + + + + + + + + + + + + + + + + + + IF(C170<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C170,1),"") + + + + IFERROR(IF(ISNUMBER(E171),0,1),"") + + + + IFERROR(YEAR(C171),"") + + + + + IF(D171=0,G170*Variables!$B$7,"") + + + + IF(D171=0,H170*Variables!$B$7,"") + + + + IFERROR((F171*Variables!$B$8),"") + + + + + IFERROR((H171*Variables!$B$10),"") + + + + IFERROR(SUM(I171:K171),"") + + + + + + IFERROR(I171/1000*$O$14,"") + + + + IFERROR(J171/1000*$P$14,"") + + + + IFERROR(K171/1000*$Q$14,"") + + + + + + + IFERROR(U170+Variables!$B$15,"") + + + + IFERROR(O171*U171,"") + + + + + IFERROR(Q171*U171,"") + + + + SUM(V171:X171) + + + + + + + + + + + + + + + + + + + + + + + + IF(C171<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C171,1),"") + + + + IFERROR(IF(ISNUMBER(E172),0,1),"") + + + + IFERROR(YEAR(C172),"") + + + + + IF(D172=0,G171*Variables!$B$7,"") + + + + IF(D172=0,H171*Variables!$B$7,"") + + + + IFERROR((F172*Variables!$B$8),"") + + + + + IFERROR((H172*Variables!$B$10),"") + + + + IFERROR(SUM(I172:K172),"") + + + + + + IFERROR(I172/1000*$O$14,"") + + + + IFERROR(J172/1000*$P$14,"") + + + + IFERROR(K172/1000*$Q$14,"") + + + + + + + IFERROR(U171+Variables!$B$15,"") + + + + IFERROR(O172*U172,"") + + + + + IFERROR(Q172*U172,"") + + + + SUM(V172:X172) + + + + + + + + + + + + + + + + + + + + + + + + IF(C172<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C172,1),"") + + + + IFERROR(IF(ISNUMBER(E173),0,1),"") + + + + IFERROR(YEAR(C173),"") + + + + + IF(D173=0,G172*Variables!$B$7,"") + + + + IF(D173=0,H172*Variables!$B$7,"") + + + + IFERROR((F173*Variables!$B$8),"") + + + + + IFERROR((H173*Variables!$B$10),"") + + + + IFERROR(SUM(I173:K173),"") + + + + + + IFERROR(I173/1000*$O$14,"") + + + + IFERROR(J173/1000*$P$14,"") + + + + IFERROR(K173/1000*$Q$14,"") + + + + + + + IFERROR(U172+Variables!$B$15,"") + + + + IFERROR(O173*U173,"") + + + + + IFERROR(Q173*U173,"") + + + + SUM(V173:X173) + + + + + + + + + + + + + + + + + + + + + + + + IF(C173<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C173,1),"") + + + + IFERROR(IF(ISNUMBER(E174),0,1),"") + + + + IFERROR(YEAR(C174),"") + + + + + IF(D174=0,G173*Variables!$B$7,"") + + + + IF(D174=0,H173*Variables!$B$7,"") + + + + IFERROR((F174*Variables!$B$8),"") + + + + + IFERROR((H174*Variables!$B$10),"") + + + + IFERROR(SUM(I174:K174),"") + + + + + + IFERROR(I174/1000*$O$14,"") + + + + IFERROR(J174/1000*$P$14,"") + + + + IFERROR(K174/1000*$Q$14,"") + + + + + + + IFERROR(U173+Variables!$B$15,"") + + + + IFERROR(O174*U174,"") + + + + + IFERROR(Q174*U174,"") + + + + SUM(V174:X174) + + + + + + + + + + + + + + + + + + + + + + + + IF(C174<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C174,1),"") + + + + IFERROR(IF(ISNUMBER(E175),0,1),"") + + + + IFERROR(YEAR(C175),"") + + + + + IF(D175=0,G174*Variables!$B$7,"") + + + + IF(D175=0,H174*Variables!$B$7,"") + + + + IFERROR((F175*Variables!$B$8),"") + + + + + IFERROR((H175*Variables!$B$10),"") + + + + IFERROR(SUM(I175:K175),"") + + + + + + IFERROR(I175/1000*$O$14,"") + + + + IFERROR(J175/1000*$P$14,"") + + + + IFERROR(K175/1000*$Q$14,"") + + + + + + + IFERROR(U174+Variables!$B$15,"") + + + + IFERROR(O175*U175,"") + + + + + IFERROR(Q175*U175,"") + + + + SUM(V175:X175) + + + + + + + + + + + + + + + + + + + + + + + + IF(C175<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C175,1),"") + + + + IFERROR(IF(ISNUMBER(E176),0,1),"") + + + + IFERROR(YEAR(C176),"") + + + + + IF(D176=0,G175*Variables!$B$7,"") + + + + IF(D176=0,H175*Variables!$B$7,"") + + + + IFERROR((F176*Variables!$B$8),"") + + + + + IFERROR((H176*Variables!$B$10),"") + + + + IFERROR(SUM(I176:K176),"") + + + + + + IFERROR(I176/1000*$O$14,"") + + + + IFERROR(J176/1000*$P$14,"") + + + + IFERROR(K176/1000*$Q$14,"") + + + + + + + IFERROR(U175+Variables!$B$15,"") + + + + IFERROR(O176*U176,"") + + + + + IFERROR(Q176*U176,"") + + + + SUM(V176:X176) + + + + + + + + + + + + + + + + + + + + + + + + IF(C176<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C176,1),"") + + + + IFERROR(IF(ISNUMBER(E177),0,1),"") + + + + IFERROR(YEAR(C177),"") + + + + + IF(D177=0,G176*Variables!$B$7,"") + + + + IF(D177=0,H176*Variables!$B$7,"") + + + + IFERROR((F177*Variables!$B$8),"") + + + + + IFERROR((H177*Variables!$B$10),"") + + + + IFERROR(SUM(I177:K177),"") + + + + + + IFERROR(I177/1000*$O$14,"") + + + + IFERROR(J177/1000*$P$14,"") + + + + IFERROR(K177/1000*$Q$14,"") + + + + + + + IFERROR(U176+Variables!$B$15,"") + + + + IFERROR(O177*U177,"") + + + + + IFERROR(Q177*U177,"") + + + + SUM(V177:X177) + + + + + + + + + + + + + + + + + + + + + + + + IF(C177<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C177,1),"") + + + + IFERROR(IF(ISNUMBER(E178),0,1),"") + + + + IFERROR(YEAR(C178),"") + + + + + IF(D178=0,G177*Variables!$B$7,"") + + + + IF(D178=0,H177*Variables!$B$7,"") + + + + IFERROR((F178*Variables!$B$8),"") + + + + + IFERROR((H178*Variables!$B$10),"") + + + + IFERROR(SUM(I178:K178),"") + + + + + + IFERROR(I178/1000*$O$14,"") + + + + IFERROR(J178/1000*$P$14,"") + + + + IFERROR(K178/1000*$Q$14,"") + + + + + + + IFERROR(U177+Variables!$B$15,"") + + + + IFERROR(O178*U178,"") + + + + + IFERROR(Q178*U178,"") + + + + SUM(V178:X178) + + + + + + + + + + + + + + + + + + + + + + + + IF(C178<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C178,1),"") + + + + IFERROR(IF(ISNUMBER(E179),0,1),"") + + + + IFERROR(YEAR(C179),"") + + + + + IF(D179=0,G178*Variables!$B$7,"") + + + + IF(D179=0,H178*Variables!$B$7,"") + + + + IFERROR((F179*Variables!$B$8),"") + + + + + IFERROR((H179*Variables!$B$10),"") + + + + IFERROR(SUM(I179:K179),"") + + + + + + IFERROR(I179/1000*$O$14,"") + + + + IFERROR(J179/1000*$P$14,"") + + + + IFERROR(K179/1000*$Q$14,"") + + + + + + + IFERROR(U178+Variables!$B$15,"") + + + + IFERROR(O179*U179,"") + + + + + IFERROR(Q179*U179,"") + + + + SUM(V179:X179) + + + + + + + + + + + + + + + + + + + + + + + + IF(C179<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C179,1),"") + + + + IFERROR(IF(ISNUMBER(E180),0,1),"") + + + + IFERROR(YEAR(C180),"") + + + + + IF(D180=0,G179*Variables!$B$7,"") + + + + IF(D180=0,H179*Variables!$B$7,"") + + + + IFERROR((F180*Variables!$B$8),"") + + + + + IFERROR((H180*Variables!$B$10),"") + + + + IFERROR(SUM(I180:K180),"") + + + + + + IFERROR(I180/1000*$O$14,"") + + + + IFERROR(J180/1000*$P$14,"") + + + + IFERROR(K180/1000*$Q$14,"") + + + + + + + IFERROR(U179+Variables!$B$15,"") + + + + IFERROR(O180*U180,"") + + + + + IFERROR(Q180*U180,"") + + + + SUM(V180:X180) + + + + + + + + + + + + + + + + + + + + + + + + IF(C180<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C180,1),"") + + + + IFERROR(IF(ISNUMBER(E181),0,1),"") + + + + IFERROR(YEAR(C181),"") + + + + + IF(D181=0,G180*Variables!$B$7,"") + + + + IF(D181=0,H180*Variables!$B$7,"") + + + + IFERROR((F181*Variables!$B$8),"") + + + + + IFERROR((H181*Variables!$B$10),"") + + + + IFERROR(SUM(I181:K181),"") + + + + + + IFERROR(I181/1000*$O$14,"") + + + + IFERROR(J181/1000*$P$14,"") + + + + IFERROR(K181/1000*$Q$14,"") + + + + + + + IFERROR(U180+Variables!$B$15,"") + + + + IFERROR(O181*U181,"") + + + + + IFERROR(Q181*U181,"") + + + + SUM(V181:X181) + + + + + + + + + + + + + + + + + + + + + + + + IF(C181<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C181,1),"") + + + + IFERROR(IF(ISNUMBER(E182),0,1),"") + + + + IFERROR(YEAR(C182),"") + + + + + IF(D182=0,G181*Variables!$B$7,"") + + + + IF(D182=0,H181*Variables!$B$7,"") + + + + IFERROR((F182*Variables!$B$8),"") + + + + + IFERROR((H182*Variables!$B$10),"") + + + + IFERROR(SUM(I182:K182),"") + + + + + + IFERROR(I182/1000*$O$14,"") + + + + IFERROR(J182/1000*$P$14,"") + + + + IFERROR(K182/1000*$Q$14,"") + + + + + + + IFERROR(U181+Variables!$B$15,"") + + + + IFERROR(O182*U182,"") + + + + + IFERROR(Q182*U182,"") + + + + SUM(V182:X182) + + + + + + + + + + + + + + + + + + + + + + + + IF(C182<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C182,1),"") + + + + IFERROR(IF(ISNUMBER(E183),0,1),"") + + + + IFERROR(YEAR(C183),"") + + + + + IF(D183=0,G182*Variables!$B$7,"") + + + + IF(D183=0,H182*Variables!$B$7,"") + + + + IFERROR((F183*Variables!$B$8),"") + + + + + IFERROR((H183*Variables!$B$10),"") + + + + IFERROR(SUM(I183:K183),"") + + + + + + IFERROR(I183/1000*$O$14,"") + + + + IFERROR(J183/1000*$P$14,"") + + + + IFERROR(K183/1000*$Q$14,"") + + + + + + + IFERROR(U182+Variables!$B$15,"") + + + + IFERROR(O183*U183,"") + + + + + IFERROR(Q183*U183,"") + + + + SUM(V183:X183) + + + + + + + + + + + + + + + + + + + + + + + + IF(C183<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C183,1),"") + + + + IFERROR(IF(ISNUMBER(E184),0,1),"") + + + + IFERROR(YEAR(C184),"") + + + + + IF(D184=0,G183*Variables!$B$7,"") + + + + IF(D184=0,H183*Variables!$B$7,"") + + + + IFERROR((F184*Variables!$B$8),"") + + + + + IFERROR((H184*Variables!$B$10),"") + + + + IFERROR(SUM(I184:K184),"") + + + + + + IFERROR(I184/1000*$O$14,"") + + + + IFERROR(J184/1000*$P$14,"") + + + + IFERROR(K184/1000*$Q$14,"") + + + + + + + IFERROR(U183+Variables!$B$15,"") + + + + IFERROR(O184*U184,"") + + + + + IFERROR(Q184*U184,"") + + + + SUM(V184:X184) + + + + + + + + + + + + + + + + + + + + + + + + IF(C184<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C184,1),"") + + + + IFERROR(IF(ISNUMBER(E185),0,1),"") + + + + IFERROR(YEAR(C185),"") + + + + + IF(D185=0,G184*Variables!$B$7,"") + + + + IF(D185=0,H184*Variables!$B$7,"") + + + + IFERROR((F185*Variables!$B$8),"") + + + + + IFERROR((H185*Variables!$B$10),"") + + + + IFERROR(SUM(I185:K185),"") + + + + + + IFERROR(I185/1000*$O$14,"") + + + + IFERROR(J185/1000*$P$14,"") + + + + IFERROR(K185/1000*$Q$14,"") + + + + + + + IFERROR(U184+Variables!$B$15,"") + + + + IFERROR(O185*U185,"") + + + + + IFERROR(Q185*U185,"") + + + + SUM(V185:X185) + + + + + + + + + + + + + + + + + + + + + + + + IF(C185<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C185,1),"") + + + + IFERROR(IF(ISNUMBER(E186),0,1),"") + + + + IFERROR(YEAR(C186),"") + + + + + IF(D186=0,G185*Variables!$B$7,"") + + + + IF(D186=0,H185*Variables!$B$7,"") + + + + IFERROR((F186*Variables!$B$8),"") + + + + + IFERROR((H186*Variables!$B$10),"") + + + + IFERROR(SUM(I186:K186),"") + + + + + + IFERROR(I186/1000*$O$14,"") + + + + IFERROR(J186/1000*$P$14,"") + + + + IFERROR(K186/1000*$Q$14,"") + + + + + + + IFERROR(U185+Variables!$B$15,"") + + + + IFERROR(O186*U186,"") + + + + + IFERROR(Q186*U186,"") + + + + SUM(V186:X186) + + + + + + + + + + + + + + + + + + + + + + + + IF(C186<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C186,1),"") + + + + IFERROR(IF(ISNUMBER(E187),0,1),"") + + + + IFERROR(YEAR(C187),"") + + + + + IF(D187=0,G186*Variables!$B$7,"") + + + + IF(D187=0,H186*Variables!$B$7,"") + + + + IFERROR((F187*Variables!$B$8),"") + + + + + IFERROR((H187*Variables!$B$10),"") + + + + IFERROR(SUM(I187:K187),"") + + + + + + IFERROR(I187/1000*$O$14,"") + + + + IFERROR(J187/1000*$P$14,"") + + + + IFERROR(K187/1000*$Q$14,"") + + + + + + + IFERROR(U186+Variables!$B$15,"") + + + + IFERROR(O187*U187,"") + + + + + IFERROR(Q187*U187,"") + + + + SUM(V187:X187) + + + + + + + + + + + + + + + + + + + + + + + + IF(C187<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C187,1),"") + + + + IFERROR(IF(ISNUMBER(E188),0,1),"") + + + + IFERROR(YEAR(C188),"") + + + + + IF(D188=0,G187*Variables!$B$7,"") + + + + IF(D188=0,H187*Variables!$B$7,"") + + + + IFERROR((F188*Variables!$B$8),"") + + + + + IFERROR((H188*Variables!$B$10),"") + + + + IFERROR(SUM(I188:K188),"") + + + + + + IFERROR(I188/1000*$O$14,"") + + + + IFERROR(J188/1000*$P$14,"") + + + + IFERROR(K188/1000*$Q$14,"") + + + + + + + IFERROR(U187+Variables!$B$15,"") + + + + IFERROR(O188*U188,"") + + + + + IFERROR(Q188*U188,"") + + + + SUM(V188:X188) + + + + + + + + + + + + + + + + + + + + + + + + IF(C188<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C188,1),"") + + + + IFERROR(IF(ISNUMBER(E189),0,1),"") + + + + IFERROR(YEAR(C189),"") + + + + + IF(D189=0,G188*Variables!$B$7,"") + + + + IF(D189=0,H188*Variables!$B$7,"") + + + + IFERROR((F189*Variables!$B$8),"") + + + + + IFERROR((H189*Variables!$B$10),"") + + + + IFERROR(SUM(I189:K189),"") + + + + + + IFERROR(I189/1000*$O$14,"") + + + + IFERROR(J189/1000*$P$14,"") + + + + IFERROR(K189/1000*$Q$14,"") + + + + + + + IFERROR(U188+Variables!$B$15,"") + + + + IFERROR(O189*U189,"") + + + + + IFERROR(Q189*U189,"") + + + + SUM(V189:X189) + + + + + + + + + + + + + + + + + + + + + + + + IF(C189<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C189,1),"") + + + + IFERROR(IF(ISNUMBER(E190),0,1),"") + + + + IFERROR(YEAR(C190),"") + + + + + IF(D190=0,G189*Variables!$B$7,"") + + + + IF(D190=0,H189*Variables!$B$7,"") + + + + IFERROR((F190*Variables!$B$8),"") + + + + + IFERROR((H190*Variables!$B$10),"") + + + + IFERROR(SUM(I190:K190),"") + + + + + + IFERROR(I190/1000*$O$14,"") + + + + IFERROR(J190/1000*$P$14,"") + + + + IFERROR(K190/1000*$Q$14,"") + + + + + + + IFERROR(U189+Variables!$B$15,"") + + + + IFERROR(O190*U190,"") + + + + + IFERROR(Q190*U190,"") + + + + SUM(V190:X190) + + + + + + + + + + + + + + + + + + + + + + + + IF(C190<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C190,1),"") + + + + IFERROR(IF(ISNUMBER(E191),0,1),"") + + + + IFERROR(YEAR(C191),"") + + + + + IF(D191=0,G190*Variables!$B$7,"") + + + + IF(D191=0,H190*Variables!$B$7,"") + + + + IFERROR((F191*Variables!$B$8),"") + + + + + IFERROR((H191*Variables!$B$10),"") + + + + IFERROR(SUM(I191:K191),"") + + + + + + IFERROR(I191/1000*$O$14,"") + + + + IFERROR(J191/1000*$P$14,"") + + + + IFERROR(K191/1000*$Q$14,"") + + + + + + + IFERROR(U190+Variables!$B$15,"") + + + + IFERROR(O191*U191,"") + + + + + IFERROR(Q191*U191,"") + + + + SUM(V191:X191) + + + + + + + + + + + + + + + + + + + + + + + + IF(C191<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C191,1),"") + + + + IFERROR(IF(ISNUMBER(E192),0,1),"") + + + + IFERROR(YEAR(C192),"") + + + + + IF(D192=0,G191*Variables!$B$7,"") + + + + IF(D192=0,H191*Variables!$B$7,"") + + + + IFERROR((F192*Variables!$B$8),"") + + + + + IFERROR((H192*Variables!$B$10),"") + + + + IFERROR(SUM(I192:K192),"") + + + + + + IFERROR(I192/1000*$O$14,"") + + + + IFERROR(J192/1000*$P$14,"") + + + + IFERROR(K192/1000*$Q$14,"") + + + + + + + IFERROR(U191+Variables!$B$15,"") + + + + IFERROR(O192*U192,"") + + + + + IFERROR(Q192*U192,"") + + + + SUM(V192:X192) + + + + + + + + + + + + + + + + + + + + + + + + IF(C192<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C192,1),"") + + + + IFERROR(IF(ISNUMBER(E193),0,1),"") + + + + IFERROR(YEAR(C193),"") + + + + + IF(D193=0,G192*Variables!$B$7,"") + + + + IF(D193=0,H192*Variables!$B$7,"") + + + + IFERROR((F193*Variables!$B$8),"") + + + + + IFERROR((H193*Variables!$B$10),"") + + + + IFERROR(SUM(I193:K193),"") + + + + + + IFERROR(I193/1000*$O$14,"") + + + + IFERROR(J193/1000*$P$14,"") + + + + IFERROR(K193/1000*$Q$14,"") + + + + + + + IFERROR(U192+Variables!$B$15,"") + + + + IFERROR(O193*U193,"") + + + + + IFERROR(Q193*U193,"") + + + + SUM(V193:X193) + + + + + + + + + + + + + + + + + + + + + + + + IF(C193<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C193,1),"") + + + + IFERROR(IF(ISNUMBER(E194),0,1),"") + + + + IFERROR(YEAR(C194),"") + + + + + IF(D194=0,G193*Variables!$B$7,"") + + + + IF(D194=0,H193*Variables!$B$7,"") + + + + IFERROR((F194*Variables!$B$8),"") + + + + + IFERROR((H194*Variables!$B$10),"") + + + + IFERROR(SUM(I194:K194),"") + + + + + + IFERROR(I194/1000*$O$14,"") + + + + IFERROR(J194/1000*$P$14,"") + + + + IFERROR(K194/1000*$Q$14,"") + + + + + + + IFERROR(U193+Variables!$B$15,"") + + + + IFERROR(O194*U194,"") + + + + + IFERROR(Q194*U194,"") + + + + SUM(V194:X194) + + + + + + + + + + + + + + + + + + + + + + + + IF(C194<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C194,1),"") + + + + IFERROR(IF(ISNUMBER(E195),0,1),"") + + + + IFERROR(YEAR(C195),"") + + + + + IF(D195=0,G194*Variables!$B$7,"") + + + + IF(D195=0,H194*Variables!$B$7,"") + + + + IFERROR((F195*Variables!$B$8),"") + + + + + IFERROR((H195*Variables!$B$10),"") + + + + IFERROR(SUM(I195:K195),"") + + + + + + IFERROR(I195/1000*$O$14,"") + + + + IFERROR(J195/1000*$P$14,"") + + + + IFERROR(K195/1000*$Q$14,"") + + + + + + + IFERROR(U194+Variables!$B$15,"") + + + + IFERROR(O195*U195,"") + + + + + IFERROR(Q195*U195,"") + + + + SUM(V195:X195) + + + + + + + + + + + + + + + + + + + + + + + + IF(C195<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C195,1),"") + + + + IFERROR(IF(ISNUMBER(E196),0,1),"") + + + + IFERROR(YEAR(C196),"") + + + + + IF(D196=0,G195*Variables!$B$7,"") + + + + IF(D196=0,H195*Variables!$B$7,"") + + + + IFERROR((F196*Variables!$B$8),"") + + + + + IFERROR((H196*Variables!$B$10),"") + + + + IFERROR(SUM(I196:K196),"") + + + + + + IFERROR(I196/1000*$O$14,"") + + + + IFERROR(J196/1000*$P$14,"") + + + + IFERROR(K196/1000*$Q$14,"") + + + + + + + IFERROR(U195+Variables!$B$15,"") + + + + IFERROR(O196*U196,"") + + + + + IFERROR(Q196*U196,"") + + + + SUM(V196:X196) + + + + + + + + + + + + + + + + + + + + + + + + IF(C196<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C196,1),"") + + + + IFERROR(IF(ISNUMBER(E197),0,1),"") + + + + IFERROR(YEAR(C197),"") + + + + + IF(D197=0,G196*Variables!$B$7,"") + + + + IF(D197=0,H196*Variables!$B$7,"") + + + + IFERROR((F197*Variables!$B$8),"") + + + + + IFERROR((H197*Variables!$B$10),"") + + + + IFERROR(SUM(I197:K197),"") + + + + + + IFERROR(I197/1000*$O$14,"") + + + + IFERROR(J197/1000*$P$14,"") + + + + IFERROR(K197/1000*$Q$14,"") + + + + + + + IFERROR(U196+Variables!$B$15,"") + + + + IFERROR(O197*U197,"") + + + + + IFERROR(Q197*U197,"") + + + + SUM(V197:X197) + + + + + + + + + + + + + + + + + + + + + + + + IF(C197<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C197,1),"") + + + + IFERROR(IF(ISNUMBER(E198),0,1),"") + + + + IFERROR(YEAR(C198),"") + + + + + IF(D198=0,G197*Variables!$B$7,"") + + + + IF(D198=0,H197*Variables!$B$7,"") + + + + IFERROR((F198*Variables!$B$8),"") + + + + + IFERROR((H198*Variables!$B$10),"") + + + + IFERROR(SUM(I198:K198),"") + + + + + + IFERROR(I198/1000*$O$14,"") + + + + IFERROR(J198/1000*$P$14,"") + + + + IFERROR(K198/1000*$Q$14,"") + + + + + + + IFERROR(U197+Variables!$B$15,"") + + + + IFERROR(O198*U198,"") + + + + + IFERROR(Q198*U198,"") + + + + SUM(V198:X198) + + + + + + + + + + + + + + + + + + + + + + + + IF(C198<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C198,1),"") + + + + IFERROR(IF(ISNUMBER(E199),0,1),"") + + + + IFERROR(YEAR(C199),"") + + + + + IF(D199=0,G198*Variables!$B$7,"") + + + + IF(D199=0,H198*Variables!$B$7,"") + + + + IFERROR((F199*Variables!$B$8),"") + + + + + IFERROR((H199*Variables!$B$10),"") + + + + IFERROR(SUM(I199:K199),"") + + + + + + IFERROR(I199/1000*$O$14,"") + + + + IFERROR(J199/1000*$P$14,"") + + + + IFERROR(K199/1000*$Q$14,"") + + + + + + + IFERROR(U198+Variables!$B$15,"") + + + + IFERROR(O199*U199,"") + + + + + IFERROR(Q199*U199,"") + + + + SUM(V199:X199) + + + + + + + + + + + + + + + + + + + + + + + + IF(C199<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C199,1),"") + + + + IFERROR(IF(ISNUMBER(E200),0,1),"") + + + + IFERROR(YEAR(C200),"") + + + + + IF(D200=0,G199*Variables!$B$7,"") + + + + IF(D200=0,H199*Variables!$B$7,"") + + + + IFERROR((F200*Variables!$B$8),"") + + + + + IFERROR((H200*Variables!$B$10),"") + + + + IFERROR(SUM(I200:K200),"") + + + + + + IFERROR(I200/1000*$O$14,"") + + + + IFERROR(J200/1000*$P$14,"") + + + + IFERROR(K200/1000*$Q$14,"") + + + + + + + IFERROR(U199+Variables!$B$15,"") + + + + IFERROR(O200*U200,"") + + + + + IFERROR(Q200*U200,"") + + + + SUM(V200:X200) + + + + + + + + + + + + + + + + + + + + + + + + IF(C200<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C200,1),"") + + + + IFERROR(IF(ISNUMBER(E201),0,1),"") + + + + IFERROR(YEAR(C201),"") + + + + + IF(D201=0,G200*Variables!$B$7,"") + + + + IF(D201=0,H200*Variables!$B$7,"") + + + + IFERROR((F201*Variables!$B$8),"") + + + + + IFERROR((H201*Variables!$B$10),"") + + + + IFERROR(SUM(I201:K201),"") + + + + + + IFERROR(I201/1000*$O$14,"") + + + + IFERROR(J201/1000*$P$14,"") + + + + IFERROR(K201/1000*$Q$14,"") + + + + + + + IFERROR(U200+Variables!$B$15,"") + + + + IFERROR(O201*U201,"") + + + + + IFERROR(Q201*U201,"") + + + + SUM(V201:X201) + + + + + + + + + + + + + + + + + + + + + + + + IF(C201<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C201,1),"") + + + + IFERROR(IF(ISNUMBER(E202),0,1),"") + + + + IFERROR(YEAR(C202),"") + + + + + IF(D202=0,G201*Variables!$B$7,"") + + + + IF(D202=0,H201*Variables!$B$7,"") + + + + IFERROR((F202*Variables!$B$8),"") + + + + + IFERROR((H202*Variables!$B$10),"") + + + + IFERROR(SUM(I202:K202),"") + + + + + + IFERROR(I202/1000*$O$14,"") + + + + IFERROR(J202/1000*$P$14,"") + + + + IFERROR(K202/1000*$Q$14,"") + + + + + + + IFERROR(U201+Variables!$B$15,"") + + + + IFERROR(O202*U202,"") + + + + + IFERROR(Q202*U202,"") + + + + SUM(V202:X202) + + + + + + + + + + + + + + + + + + + + + + + + IF(C202<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C202,1),"") + + + + IFERROR(IF(ISNUMBER(E203),0,1),"") + + + + IFERROR(YEAR(C203),"") + + + + + IF(D203=0,G202*Variables!$B$7,"") + + + + IF(D203=0,H202*Variables!$B$7,"") + + + + IFERROR((F203*Variables!$B$8),"") + + + + + IFERROR((H203*Variables!$B$10),"") + + + + IFERROR(SUM(I203:K203),"") + + + + + + IFERROR(I203/1000*$O$14,"") + + + + IFERROR(J203/1000*$P$14,"") + + + + IFERROR(K203/1000*$Q$14,"") + + + + + + + IFERROR(U202+Variables!$B$15,"") + + + + IFERROR(O203*U203,"") + + + + + IFERROR(Q203*U203,"") + + + + SUM(V203:X203) + + + + + + + + + + + + + + + + + + + + + + + + IF(C203<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C203,1),"") + + + + IFERROR(IF(ISNUMBER(E204),0,1),"") + + + + IFERROR(YEAR(C204),"") + + + + + IF(D204=0,G203*Variables!$B$7,"") + + + + IF(D204=0,H203*Variables!$B$7,"") + + + + IFERROR((F204*Variables!$B$8),"") + + + + + IFERROR((H204*Variables!$B$10),"") + + + + IFERROR(SUM(I204:K204),"") + + + + + + IFERROR(I204/1000*$O$14,"") + + + + IFERROR(J204/1000*$P$14,"") + + + + IFERROR(K204/1000*$Q$14,"") + + + + + + + IFERROR(U203+Variables!$B$15,"") + + + + IFERROR(O204*U204,"") + + + + + IFERROR(Q204*U204,"") + + + + SUM(V204:X204) + + + + + + + + + + + + + + + + + + + + + + + + IF(C204<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C204,1),"") + + + + IFERROR(IF(ISNUMBER(E205),0,1),"") + + + + IFERROR(YEAR(C205),"") + + + + + IF(D205=0,G204*Variables!$B$7,"") + + + + IF(D205=0,H204*Variables!$B$7,"") + + + + IFERROR((F205*Variables!$B$8),"") + + + + + IFERROR((H205*Variables!$B$10),"") + + + + IFERROR(SUM(I205:K205),"") + + + + + + IFERROR(I205/1000*$O$14,"") + + + + IFERROR(J205/1000*$P$14,"") + + + + IFERROR(K205/1000*$Q$14,"") + + + + + + + IFERROR(U204+Variables!$B$15,"") + + + + IFERROR(O205*U205,"") + + + + + IFERROR(Q205*U205,"") + + + + SUM(V205:X205) + + + + + + + + + + + + + + + + + + + + + + + + IF(C205<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C205,1),"") + + + + IFERROR(IF(ISNUMBER(E206),0,1),"") + + + + IFERROR(YEAR(C206),"") + + + + + IF(D206=0,G205*Variables!$B$7,"") + + + + IF(D206=0,H205*Variables!$B$7,"") + + + + IFERROR((F206*Variables!$B$8),"") + + + + + IFERROR((H206*Variables!$B$10),"") + + + + IFERROR(SUM(I206:K206),"") + + + + + + IFERROR(I206/1000*$O$14,"") + + + + IFERROR(J206/1000*$P$14,"") + + + + IFERROR(K206/1000*$Q$14,"") + + + + + + + IFERROR(U205+Variables!$B$15,"") + + + + IFERROR(O206*U206,"") + + + + + IFERROR(Q206*U206,"") + + + + SUM(V206:X206) + + + + + + + + + + + + + + + + + + + + + + + + IF(C206<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C206,1),"") + + + + IFERROR(IF(ISNUMBER(E207),0,1),"") + + + + IFERROR(YEAR(C207),"") + + + + + IF(D207=0,G206*Variables!$B$7,"") + + + + IF(D207=0,H206*Variables!$B$7,"") + + + + IFERROR((F207*Variables!$B$8),"") + + + + + IFERROR((H207*Variables!$B$10),"") + + + + IFERROR(SUM(I207:K207),"") + + + + + + IFERROR(I207/1000*$O$14,"") + + + + IFERROR(J207/1000*$P$14,"") + + + + IFERROR(K207/1000*$Q$14,"") + + + + + + + IFERROR(U206+Variables!$B$15,"") + + + + IFERROR(O207*U207,"") + + + + + IFERROR(Q207*U207,"") + + + + SUM(V207:X207) + + + + + + + + + + + + + + + + + + + + + + + + IF(C207<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C207,1),"") + + + + IFERROR(IF(ISNUMBER(E208),0,1),"") + + + + IFERROR(YEAR(C208),"") + + + + + IF(D208=0,G207*Variables!$B$7,"") + + + + IF(D208=0,H207*Variables!$B$7,"") + + + + IFERROR((F208*Variables!$B$8),"") + + + + + IFERROR((H208*Variables!$B$10),"") + + + + IFERROR(SUM(I208:K208),"") + + + + + + IFERROR(I208/1000*$O$14,"") + + + + IFERROR(J208/1000*$P$14,"") + + + + IFERROR(K208/1000*$Q$14,"") + + + + + + + IFERROR(U207+Variables!$B$15,"") + + + + IFERROR(O208*U208,"") + + + + + IFERROR(Q208*U208,"") + + + + SUM(V208:X208) + + + + + + + + + + + + + + + + + + + + + + + + IF(C208<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C208,1),"") + + + + IFERROR(IF(ISNUMBER(E209),0,1),"") + + + + IFERROR(YEAR(C209),"") + + + + + IF(D209=0,G208*Variables!$B$7,"") + + + + IF(D209=0,H208*Variables!$B$7,"") + + + + IFERROR((F209*Variables!$B$8),"") + + + + + IFERROR((H209*Variables!$B$10),"") + + + + IFERROR(SUM(I209:K209),"") + + + + + + IFERROR(I209/1000*$O$14,"") + + + + IFERROR(J209/1000*$P$14,"") + + + + IFERROR(K209/1000*$Q$14,"") + + + + + + + IFERROR(U208+Variables!$B$15,"") + + + + IFERROR(O209*U209,"") + + + + + IFERROR(Q209*U209,"") + + + + SUM(V209:X209) + + + + + + + + + + + + + + + + + + + + + + + + IF(C209<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C209,1),"") + + + + IFERROR(IF(ISNUMBER(E210),0,1),"") + + + + IFERROR(YEAR(C210),"") + + + + + IF(D210=0,G209*Variables!$B$7,"") + + + + IF(D210=0,H209*Variables!$B$7,"") + + + + IFERROR((F210*Variables!$B$8),"") + + + + + IFERROR((H210*Variables!$B$10),"") + + + + IFERROR(SUM(I210:K210),"") + + + + + + IFERROR(I210/1000*$O$14,"") + + + + IFERROR(J210/1000*$P$14,"") + + + + IFERROR(K210/1000*$Q$14,"") + + + + + + + IFERROR(U209+Variables!$B$15,"") + + + + IFERROR(O210*U210,"") + + + + + IFERROR(Q210*U210,"") + + + + SUM(V210:X210) + + + + + + + + + + + + + + + + + + + + + + + + IF(C210<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C210,1),"") + + + + IFERROR(IF(ISNUMBER(E211),0,1),"") + + + + IFERROR(YEAR(C211),"") + + + + + IF(D211=0,G210*Variables!$B$7,"") + + + + IF(D211=0,H210*Variables!$B$7,"") + + + + IFERROR((F211*Variables!$B$8),"") + + + + + IFERROR((H211*Variables!$B$10),"") + + + + IFERROR(SUM(I211:K211),"") + + + + + + IFERROR(I211/1000*$O$14,"") + + + + IFERROR(J211/1000*$P$14,"") + + + + IFERROR(K211/1000*$Q$14,"") + + + + + + + IFERROR(U210+Variables!$B$15,"") + + + + IFERROR(O211*U211,"") + + + + + IFERROR(Q211*U211,"") + + + + SUM(V211:X211) + + + + + + + + + + + + + + + + + + + + + + + + IF(C211<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C211,1),"") + + + + IFERROR(IF(ISNUMBER(E212),0,1),"") + + + + IFERROR(YEAR(C212),"") + + + + + IF(D212=0,G211*Variables!$B$7,"") + + + + IF(D212=0,H211*Variables!$B$7,"") + + + + IFERROR((F212*Variables!$B$8),"") + + + + + IFERROR((H212*Variables!$B$10),"") + + + + IFERROR(SUM(I212:K212),"") + + + + + + IFERROR(I212/1000*$O$14,"") + + + + IFERROR(J212/1000*$P$14,"") + + + + IFERROR(K212/1000*$Q$14,"") + + + + + + + IFERROR(U211+Variables!$B$15,"") + + + + IFERROR(O212*U212,"") + + + + + IFERROR(Q212*U212,"") + + + + SUM(V212:X212) + + + + + + + + + + + + + + + + + + + + + + + + IF(C212<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C212,1),"") + + + + IFERROR(IF(ISNUMBER(E213),0,1),"") + + + + IFERROR(YEAR(C213),"") + + + + + IF(D213=0,G212*Variables!$B$7,"") + + + + IF(D213=0,H212*Variables!$B$7,"") + + + + IFERROR((F213*Variables!$B$8),"") + + + + + IFERROR((H213*Variables!$B$10),"") + + + + IFERROR(SUM(I213:K213),"") + + + + + + IFERROR(I213/1000*$O$14,"") + + + + IFERROR(J213/1000*$P$14,"") + + + + + + + + IFERROR(U212+Variables!$B$15,"") + + + + IFERROR(O213*U213,"") + + + + + IFERROR(Q213*U213,"") + + + + SUM(V213:X213) + + + + + + + + + + + + + + + + + + + + + + + + IF(C213<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C213,1),"") + + + + IFERROR(IF(ISNUMBER(E214),0,1),"") + + + + IFERROR(YEAR(C214),"") + + + + + IF(D214=0,G213*Variables!$B$7,"") + + + + IF(D214=0,H213*Variables!$B$7,"") + + + + IFERROR((F214*Variables!$B$8),"") + + + + + IFERROR((H214*Variables!$B$10),"") + + + + IFERROR(SUM(I214:K214),"") + + + + + + IFERROR(I214/1000*$O$14,"") + + + + IFERROR(J214/1000*$P$14,"") + + + + + + + + IFERROR(U213+Variables!$B$15,"") + + + + + + IFERROR(Q214*U214,"") + + + + SUM(V214:X214) + + + + + + + + + + + + + + + + + + + + + + + + IF(C214<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C214,1),"") + + + + IFERROR(IF(ISNUMBER(E215),0,1),"") + + + + IFERROR(YEAR(C215),"") + + + + + IF(D215=0,G214*Variables!$B$7,"") + + + + IF(D215=0,H214*Variables!$B$7,"") + + + + IFERROR((F215*Variables!$B$8),"") + + + + + IFERROR((H215*Variables!$B$10),"") + + + + IFERROR(SUM(I215:K215),"") + + + + + + IFERROR(I215/1000*$O$14,"") + + + + IFERROR(J215/1000*$P$14,"") + + + + + + + + IFERROR(U214+Variables!$B$15,"") + + + + + + IFERROR(Q215*U215,"") + + + + SUM(V215:X215) + + + + + + + + + + + + + + + + + + + + + + + + IF(C215<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C215,1),"") + + + + IFERROR(IF(ISNUMBER(E216),0,1),"") + + + + IFERROR(YEAR(C216),"") + + + + + IF(D216=0,G215*Variables!$B$7,"") + + + + IF(D216=0,H215*Variables!$B$7,"") + + + + IFERROR((F216*Variables!$B$8),"") + + + + + IFERROR((H216*Variables!$B$10),"") + + + + IFERROR(SUM(I216:K216),"") + + + + + + IFERROR(I216/1000*$O$14,"") + + + + IFERROR(J216/1000*$P$14,"") + + + + + + + + IFERROR(U215+Variables!$B$15,"") + + + + + + IFERROR(Q216*U216,"") + + + + SUM(V216:X216) + + + + + + + + + + + + + + + + + + + + + + + + IF(C216<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C216,1),"") + + + + IFERROR(IF(ISNUMBER(E217),0,1),"") + + + + IFERROR(YEAR(C217),"") + + + + + IF(D217=0,G216*Variables!$B$7,"") + + + + IF(D217=0,H216*Variables!$B$7,"") + + + + IFERROR((F217*Variables!$B$8),"") + + + + + IFERROR((H217*Variables!$B$10),"") + + + + IFERROR(SUM(I217:K217),"") + + + + + + IFERROR(I217/1000*$O$14,"") + + + + IFERROR(J217/1000*$P$14,"") + + + + + + + + IFERROR(U216+Variables!$B$15,"") + + + + + + IFERROR(Q217*U217,"") + + + + SUM(V217:X217) + + + + + + + + + + + + + + + + + + + + + + + + IF(C217<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C217,1),"") + + + + IFERROR(IF(ISNUMBER(E218),0,1),"") + + + + IFERROR(YEAR(C218),"") + + + + + IF(D218=0,G217*Variables!$B$7,"") + + + + IF(D218=0,H217*Variables!$B$7,"") + + + + IFERROR((F218*Variables!$B$8),"") + + + + + IFERROR((H218*Variables!$B$10),"") + + + + IFERROR(SUM(I218:K218),"") + + + + + + IFERROR(I218/1000*$O$14,"") + + + + IFERROR(J218/1000*$P$14,"") + + + + + + + + IFERROR(U217+Variables!$B$15,"") + + + + + + IFERROR(Q218*U218,"") + + + + SUM(V218:X218) + + + + + + + + + + + + + + + + + + + + + + + + IF(C218<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C218,1),"") + + + + IFERROR(IF(ISNUMBER(E219),0,1),"") + + + + IFERROR(YEAR(C219),"") + + + + + IF(D219=0,G218*Variables!$B$7,"") + + + + IF(D219=0,H218*Variables!$B$7,"") + + + + IFERROR((F219*Variables!$B$8),"") + + + + + IFERROR((H219*Variables!$B$10),"") + + + + IFERROR(SUM(I219:K219),"") + + + + + + IFERROR(I219/1000*$O$14,"") + + + + IFERROR(J219/1000*$P$14,"") + + + + + + + + IFERROR(U218+Variables!$B$15,"") + + + + + + IFERROR(Q219*U219,"") + + + + SUM(V219:X219) + + + + + + + + + + + + + + + + + + + + + + + + IF(C219<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C219,1),"") + + + + IFERROR(IF(ISNUMBER(E220),0,1),"") + + + + IFERROR(YEAR(C220),"") + + + + + IF(D220=0,G219*Variables!$B$7,"") + + + + IF(D220=0,H219*Variables!$B$7,"") + + + + IFERROR((F220*Variables!$B$8),"") + + + + + IFERROR((H220*Variables!$B$10),"") + + + + IFERROR(SUM(I220:K220),"") + + + + + + IFERROR(I220/1000*$O$14,"") + + + + IFERROR(J220/1000*$P$14,"") + + + + + + + + IFERROR(U219+Variables!$B$15,"") + + + + + + IFERROR(Q220*U220,"") + + + + SUM(V220:X220) + + + + + + + + + + + + + + + + + + + + + + + + IF(C220<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C220,1),"") + + + + IFERROR(IF(ISNUMBER(E221),0,1),"") + + + + IFERROR(YEAR(C221),"") + + + + + IF(D221=0,G220*Variables!$B$7,"") + + + + IF(D221=0,H220*Variables!$B$7,"") + + + + IFERROR((F221*Variables!$B$8),"") + + + + + IFERROR((H221*Variables!$B$10),"") + + + + IFERROR(SUM(I221:K221),"") + + + + + + IFERROR(I221/1000*$O$14,"") + + + + IFERROR(J221/1000*$P$14,"") + + + + + + + + IFERROR(U220+Variables!$B$15,"") + + + + + + IFERROR(Q221*U221,"") + + + + SUM(V221:X221) + + + + + + + + + + + + + + + + + + + + + + + + IF(C221<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C221,1),"") + + + + IFERROR(IF(ISNUMBER(E222),0,1),"") + + + + IFERROR(YEAR(C222),"") + + + + + IF(D222=0,G221*Variables!$B$7,"") + + + + IF(D222=0,H221*Variables!$B$7,"") + + + + IFERROR((F222*Variables!$B$8),"") + + + + + IFERROR((H222*Variables!$B$10),"") + + + + IFERROR(SUM(I222:K222),"") + + + + + + IFERROR(I222/1000*$O$14,"") + + + + IFERROR(J222/1000*$P$14,"") + + + + + + + + IFERROR(U221+Variables!$B$15,"") + + + + + + IFERROR(Q222*U222,"") + + + + SUM(V222:X222) + + + + + + + + + + + + + + + + + + + + + + + + IF(C222<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C222,1),"") + + + + IFERROR(IF(ISNUMBER(E223),0,1),"") + + + + IFERROR(YEAR(C223),"") + + + + + IF(D223=0,G222*Variables!$B$7,"") + + + + IF(D223=0,H222*Variables!$B$7,"") + + + + IFERROR((F223*Variables!$B$8),"") + + + + + IFERROR((H223*Variables!$B$10),"") + + + + IFERROR(SUM(I223:K223),"") + + + + + + IFERROR(I223/1000*$O$14,"") + + + + IFERROR(J223/1000*$P$14,"") + + + + + + + + IFERROR(U222+Variables!$B$15,"") + + + + + + IFERROR(Q223*U223,"") + + + + SUM(V223:X223) + + + + + + + + + + + + + + + + + + + + + + + + IF(C223<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C223,1),"") + + + + IFERROR(IF(ISNUMBER(E224),0,1),"") + + + + IFERROR(YEAR(C224),"") + + + + + IF(D224=0,G223*Variables!$B$7,"") + + + + IF(D224=0,H223*Variables!$B$7,"") + + + + IFERROR((F224*Variables!$B$8),"") + + + + + IFERROR((H224*Variables!$B$10),"") + + + + IFERROR(SUM(I224:K224),"") + + + + + + IFERROR(I224/1000*$O$14,"") + + + + IFERROR(J224/1000*$P$14,"") + + + + + + + + IFERROR(U223+Variables!$B$15,"") + + + + + + IFERROR(Q224*U224,"") + + + + SUM(V224:X224) + + + + + + + + + + + + + + + + + + + + + + + + IF(C224<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C224,1),"") + + + + IFERROR(IF(ISNUMBER(E225),0,1),"") + + + + IFERROR(YEAR(C225),"") + + + + + IF(D225=0,G224*Variables!$B$7,"") + + + + IF(D225=0,H224*Variables!$B$7,"") + + + + IFERROR((F225*Variables!$B$8),"") + + + + + IFERROR((H225*Variables!$B$10),"") + + + + IFERROR(SUM(I225:K225),"") + + + + + + IFERROR(I225/1000*$O$14,"") + + + + IFERROR(J225/1000*$P$14,"") + + + + + + + + IFERROR(U224+Variables!$B$15,"") + + + + + + IFERROR(Q225*U225,"") + + + + SUM(V225:X225) + + + + + + + + + + + + + + + + + + + + + + + + IF(C225<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C225,1),"") + + + + IFERROR(IF(ISNUMBER(E226),0,1),"") + + + + IFERROR(YEAR(C226),"") + + + + + IF(D226=0,G225*Variables!$B$7,"") + + + + IF(D226=0,H225*Variables!$B$7,"") + + + + IFERROR((F226*Variables!$B$8),"") + + + + + IFERROR((H226*Variables!$B$10),"") + + + + IFERROR(SUM(I226:K226),"") + + + + + + IFERROR(I226/1000*$O$14,"") + + + + IFERROR(J226/1000*$P$14,"") + + + + + + + + IFERROR(U225+Variables!$B$15,"") + + + + + + IFERROR(Q226*U226,"") + + + + SUM(V226:X226) + + + + + + + + + + + + + + + + + + + + + + + + IF(C226<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C226,1),"") + + + + IFERROR(IF(ISNUMBER(E227),0,1),"") + + + + IFERROR(YEAR(C227),"") + + + + + IF(D227=0,G226*Variables!$B$7,"") + + + + IF(D227=0,H226*Variables!$B$7,"") + + + + + + IFERROR((H227*Variables!$B$10),"") + + + + IFERROR(SUM(I227:K227),"") + + + + + + IFERROR(I227/1000*$O$14,"") + + + + IFERROR(J227/1000*$P$14,"") + + + + + + + + IFERROR(U226+Variables!$B$15,"") + + + + + + IFERROR(Q227*U227,"") + + + + SUM(V227:X227) + + + + + + + + + + + + + + + + + + + + + + + + IF(C227<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C227,1),"") + + + + IFERROR(IF(ISNUMBER(E228),0,1),"") + + + + IFERROR(YEAR(C228),"") + + + + + IF(D228=0,G227*Variables!$B$7,"") + + + + IF(D228=0,H227*Variables!$B$7,"") + + + + + + IFERROR((H228*Variables!$B$10),"") + + + + IFERROR(SUM(I228:K228),"") + + + + + + IFERROR(I228/1000*$O$14,"") + + + + IFERROR(J228/1000*$P$14,"") + + + + + + + + IFERROR(U227+Variables!$B$15,"") + + + + + + IFERROR(Q228*U228,"") + + + + SUM(V228:X228) + + + + + + + + + + + + + + + + + + + + + + + + IF(C228<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C228,1),"") + + + + IFERROR(IF(ISNUMBER(E229),0,1),"") + + + + IFERROR(YEAR(C229),"") + + + + + IF(D229=0,G228*Variables!$B$7,"") + + + + IF(D229=0,H228*Variables!$B$7,"") + + + + + + IFERROR((H229*Variables!$B$10),"") + + + + IFERROR(SUM(I229:K229),"") + + + + + + IFERROR(I229/1000*$O$14,"") + + + + IFERROR(J229/1000*$P$14,"") + + + + + + + + IFERROR(U228+Variables!$B$15,"") + + + + + + IFERROR(Q229*U229,"") + + + + SUM(V229:X229) + + + + + + + + + + + + + + + + + + + + + + + + IF(C229<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C229,1),"") + + + + IFERROR(IF(ISNUMBER(E230),0,1),"") + + + + IFERROR(YEAR(C230),"") + + + + + IF(D230=0,G229*Variables!$B$7,"") + + + + IF(D230=0,H229*Variables!$B$7,"") + + + + + + IFERROR((H230*Variables!$B$10),"") + + + + IFERROR(SUM(I230:K230),"") + + + + + + IFERROR(I230/1000*$O$14,"") + + + + IFERROR(J230/1000*$P$14,"") + + + + + + + + IFERROR(U229+Variables!$B$15,"") + + + + + + IFERROR(Q230*U230,"") + + + + SUM(V230:X230) + + + + + + + + + + + + + + + + + + + + + + + + IF(C230<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C230,1),"") + + + + IFERROR(IF(ISNUMBER(E231),0,1),"") + + + + IFERROR(YEAR(C231),"") + + + + + IF(D231=0,G230*Variables!$B$7,"") + + + + + + + IFERROR((H231*Variables!$B$10),"") + + + + IFERROR(SUM(I231:K231),"") + + + + + + IFERROR(I231/1000*$O$14,"") + + + + IFERROR(J231/1000*$P$14,"") + + + + + + + + IFERROR(U230+Variables!$B$15,"") + + + + + + IFERROR(Q231*U231,"") + + + + SUM(V231:X231) + + + + + + + + + + + + + + + + + + + + + + + + IF(C231<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C231,1),"") + + + + IFERROR(IF(ISNUMBER(E232),0,1),"") + + + + IFERROR(YEAR(C232),"") + + + + + IF(D232=0,G231*Variables!$B$7,"") + + + + + + + IFERROR((H232*Variables!$B$10),"") + + + + IFERROR(SUM(I232:K232),"") + + + + + + IFERROR(I232/1000*$O$14,"") + + + + IFERROR(J232/1000*$P$14,"") + + + + + + + + IFERROR(U231+Variables!$B$15,"") + + + + + + IFERROR(Q232*U232,"") + + + + SUM(V232:X232) + + + + + + + + + + + + + + + + + + + + + + + + IF(C232<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C232,1),"") + + + + IFERROR(IF(ISNUMBER(E233),0,1),"") + + + + IFERROR(YEAR(C233),"") + + + + + IF(D233=0,G232*Variables!$B$7,"") + + + + + + + IFERROR((H233*Variables!$B$10),"") + + + + IFERROR(SUM(I233:K233),"") + + + + + + IFERROR(I233/1000*$O$14,"") + + + + IFERROR(J233/1000*$P$14,"") + + + + + + + + IFERROR(U232+Variables!$B$15,"") + + + + + + IFERROR(Q233*U233,"") + + + + SUM(V233:X233) + + + + + + + + + + + + + + + + + + + + + + + + IF(C233<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C233,1),"") + + + + IFERROR(IF(ISNUMBER(E234),0,1),"") + + + + IFERROR(YEAR(C234),"") + + + + + IF(D234=0,G233*Variables!$B$7,"") + + + + + + + IFERROR((H234*Variables!$B$10),"") + + + + IFERROR(SUM(I234:K234),"") + + + + + + IFERROR(I234/1000*$O$14,"") + + + + IFERROR(J234/1000*$P$14,"") + + + + + + + + IFERROR(U233+Variables!$B$15,"") + + + + + + IFERROR(Q234*U234,"") + + + + SUM(V234:X234) + + + + + + + + + + + + + + + + + + + + + + + + IF(C234<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C234,1),"") + + + + IFERROR(IF(ISNUMBER(E235),0,1),"") + + + + IFERROR(YEAR(C235),"") + + + + + IF(D235=0,G234*Variables!$B$7,"") + + + + + + + IFERROR((H235*Variables!$B$10),"") + + + + IFERROR(SUM(I235:K235),"") + + + + + + IFERROR(I235/1000*$O$14,"") + + + + IFERROR(J235/1000*$P$14,"") + + + + + + + + IFERROR(U234+Variables!$B$15,"") + + + + + + IFERROR(Q235*U235,"") + + + + SUM(V235:X235) + + + + + + + + + + + + + + + + + + + + + + + + IF(C235<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C235,1),"") + + + + IFERROR(IF(ISNUMBER(E236),0,1),"") + + + + IFERROR(YEAR(C236),"") + + + + + IF(D236=0,G235*Variables!$B$7,"") + + + + + + + IFERROR((H236*Variables!$B$10),"") + + + + IFERROR(SUM(I236:K236),"") + + + + + + IFERROR(I236/1000*$O$14,"") + + + + IFERROR(J236/1000*$P$14,"") + + + + + + + + IFERROR(U235+Variables!$B$15,"") + + + + + + IFERROR(Q236*U236,"") + + + + SUM(V236:X236) + + + + + + + + + + + + + + + + + + + + + + + + IF(C236<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C236,1),"") + + + + IFERROR(IF(ISNUMBER(E237),0,1),"") + + + + IFERROR(YEAR(C237),"") + + + + + IF(D237=0,G236*Variables!$B$7,"") + + + + + + + IFERROR((H237*Variables!$B$10),"") + + + + IFERROR(SUM(I237:K237),"") + + + + + + IFERROR(I237/1000*$O$14,"") + + + + IFERROR(J237/1000*$P$14,"") + + + + + + + + IFERROR(U236+Variables!$B$15,"") + + + + + + + SUM(V237:X237) + + + + + + + + + + + + + + + + + + + + + + + + IF(C237<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C237,1),"") + + + + IFERROR(IF(ISNUMBER(E238),0,1),"") + + + + IFERROR(YEAR(C238),"") + + + + + IF(D238=0,G237*Variables!$B$7,"") + + + + + + + IFERROR((H238*Variables!$B$10),"") + + + + IFERROR(SUM(I238:K238),"") + + + + + + IFERROR(I238/1000*$O$14,"") + + + + IFERROR(J238/1000*$P$14,"") + + + + + + + + IFERROR(U237+Variables!$B$15,"") + + + + + + + SUM(V238:X238) + + + + + + + + + + + + + + + + + + + + + + + + IF(C238<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C238,1),"") + + + + IFERROR(IF(ISNUMBER(E239),0,1),"") + + + + IFERROR(YEAR(C239),"") + + + + + IF(D239=0,G238*Variables!$B$7,"") + + + + + + + IFERROR((H239*Variables!$B$10),"") + + + + IFERROR(SUM(I239:K239),"") + + + + + + IFERROR(I239/1000*$O$14,"") + + + + IFERROR(J239/1000*$P$14,"") + + + + + + + + IFERROR(U238+Variables!$B$15,"") + + + + + + + SUM(V239:X239) + + + + + + + + + + + + + + + + + + + + + + + + IF(C239<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C239,1),"") + + + + IFERROR(IF(ISNUMBER(E240),0,1),"") + + + + IFERROR(YEAR(C240),"") + + + + + IF(D240=0,G239*Variables!$B$7,"") + + + + + + + IFERROR((H240*Variables!$B$10),"") + + + + IFERROR(SUM(I240:K240),"") + + + + + + IFERROR(I240/1000*$O$14,"") + + + + IFERROR(J240/1000*$P$14,"") + + + + + + + + IFERROR(U239+Variables!$B$15,"") + + + + + + + SUM(V240:X240) + + + + + + + + + + + + + + + + + + + + + + + + IF(C240<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C240,1),"") + + + + IFERROR(IF(ISNUMBER(E241),0,1),"") + + + + IFERROR(YEAR(C241),"") + + + + + IF(D241=0,G240*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I241:K241),"") + + + + + + IFERROR(I241/1000*$O$14,"") + + + + IFERROR(J241/1000*$P$14,"") + + + + + + + + IFERROR(U240+Variables!$B$15,"") + + + + + + + SUM(V241:X241) + + + + + + + + + + + + + + + + + + + + + + + + IF(C241<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C241,1),"") + + + + IFERROR(IF(ISNUMBER(E242),0,1),"") + + + + IFERROR(YEAR(C242),"") + + + + + IF(D242=0,G241*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I242:K242),"") + + + + + + IFERROR(I242/1000*$O$14,"") + + + + IFERROR(J242/1000*$P$14,"") + + + + + + + + IFERROR(U241+Variables!$B$15,"") + + + + + + + SUM(V242:X242) + + + + + + + + + + + + + + + + + + + + + + + + IF(C242<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C242,1),"") + + + + IFERROR(IF(ISNUMBER(E243),0,1),"") + + + + IFERROR(YEAR(C243),"") + + + + + IF(D243=0,G242*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I243:K243),"") + + + + + + IFERROR(I243/1000*$O$14,"") + + + + IFERROR(J243/1000*$P$14,"") + + + + + + + + IFERROR(U242+Variables!$B$15,"") + + + + + + + SUM(V243:X243) + + + + + + + + + + + + + + + + + + + + + + + + IF(C243<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C243,1),"") + + + + IFERROR(IF(ISNUMBER(E244),0,1),"") + + + + IFERROR(YEAR(C244),"") + + + + + IF(D244=0,G243*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I244:K244),"") + + + + + + IFERROR(I244/1000*$O$14,"") + + + + IFERROR(J244/1000*$P$14,"") + + + + + + + + IFERROR(U243+Variables!$B$15,"") + + + + + + + SUM(V244:X244) + + + + + + + + + + + + + + + + + + + + + + + + IF(C244<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C244,1),"") + + + + IFERROR(IF(ISNUMBER(E245),0,1),"") + + + + IFERROR(YEAR(C245),"") + + + + + IF(D245=0,G244*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I245:K245),"") + + + + + + IFERROR(I245/1000*$O$14,"") + + + + IFERROR(J245/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C245<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C245,1),"") + + + + IFERROR(IF(ISNUMBER(E246),0,1),"") + + + + IFERROR(YEAR(C246),"") + + + + + IF(D246=0,G245*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I246:K246),"") + + + + + + IFERROR(I246/1000*$O$14,"") + + + + IFERROR(J246/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C246<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C246,1),"") + + + + IFERROR(IF(ISNUMBER(E247),0,1),"") + + + + IFERROR(YEAR(C247),"") + + + + + IF(D247=0,G246*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I247:K247),"") + + + + + + IFERROR(I247/1000*$O$14,"") + + + + IFERROR(J247/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C247<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C247,1),"") + + + + IFERROR(IF(ISNUMBER(E248),0,1),"") + + + + IFERROR(YEAR(C248),"") + + + + + IF(D248=0,G247*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I248:K248),"") + + + + + + IFERROR(I248/1000*$O$14,"") + + + + IFERROR(J248/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C248<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C248,1),"") + + + + IFERROR(IF(ISNUMBER(E249),0,1),"") + + + + IFERROR(YEAR(C249),"") + + + + + IF(D249=0,G248*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I249:K249),"") + + + + + + IFERROR(I249/1000*$O$14,"") + + + + IFERROR(J249/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C249<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C249,1),"") + + + + IFERROR(IF(ISNUMBER(E250),0,1),"") + + + + IFERROR(YEAR(C250),"") + + + + + IF(D250=0,G249*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I250:K250),"") + + + + + + IFERROR(I250/1000*$O$14,"") + + + + IFERROR(J250/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C250<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C250,1),"") + + + + IFERROR(IF(ISNUMBER(E251),0,1),"") + + + + IFERROR(YEAR(C251),"") + + + + + IF(D251=0,G250*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I251:K251),"") + + + + + + IFERROR(I251/1000*$O$14,"") + + + + IFERROR(J251/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C251<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C251,1),"") + + + + IFERROR(IF(ISNUMBER(E252),0,1),"") + + + + IFERROR(YEAR(C252),"") + + + + + IF(D252=0,G251*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I252:K252),"") + + + + + + IFERROR(I252/1000*$O$14,"") + + + + IFERROR(J252/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C252<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C252,1),"") + + + + IFERROR(IF(ISNUMBER(E253),0,1),"") + + + + IFERROR(YEAR(C253),"") + + + + + IF(D253=0,G252*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I253:K253),"") + + + + + + IFERROR(I253/1000*$O$14,"") + + + + IFERROR(J253/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C253<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C253,1),"") + + + + IFERROR(IF(ISNUMBER(E254),0,1),"") + + + + IFERROR(YEAR(C254),"") + + + + + IF(D254=0,G253*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I254:K254),"") + + + + + + IFERROR(I254/1000*$O$14,"") + + + + IFERROR(J254/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C254<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C254,1),"") + + + + IFERROR(IF(ISNUMBER(E255),0,1),"") + + + + IFERROR(YEAR(C255),"") + + + + + IF(D255=0,G254*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I255:K255),"") + + + + + + IFERROR(I255/1000*$O$14,"") + + + + IFERROR(J255/1000*$P$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C255<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C255,1),"") + + + + IFERROR(IF(ISNUMBER(E256),0,1),"") + + + + IFERROR(YEAR(C256),"") + + + + + IF(D256=0,G255*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I256:K256),"") + + + + + + IFERROR(I256/1000*$O$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C256<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C256,1),"") + + + + IFERROR(IF(ISNUMBER(E257),0,1),"") + + + + IFERROR(YEAR(C257),"") + + + + + IF(D257=0,G256*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I257:K257),"") + + + + + + IFERROR(I257/1000*$O$14,"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C257<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C257,1),"") + + + + IFERROR(IF(ISNUMBER(E258),0,1),"") + + + + IFERROR(YEAR(C258),"") + + + + + IF(D258=0,G257*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I258:K258),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C258<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C258,1),"") + + + + IFERROR(IF(ISNUMBER(E259),0,1),"") + + + + IFERROR(YEAR(C259),"") + + + + + IF(D259=0,G258*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I259:K259),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C259<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C259,1),"") + + + + IFERROR(IF(ISNUMBER(E260),0,1),"") + + + + IFERROR(YEAR(C260),"") + + + + + IF(D260=0,G259*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I260:K260),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C260<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C260,1),"") + + + + IFERROR(IF(ISNUMBER(E261),0,1),"") + + + + IFERROR(YEAR(C261),"") + + + + + IF(D261=0,G260*Variables!$B$7,"") + + + + + + + + IFERROR(SUM(I261:K261),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C261<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C261,1),"") + + + + IFERROR(IF(ISNUMBER(E262),0,1),"") + + + + IFERROR(YEAR(C262),"") + + + + + + + + + + IFERROR(SUM(I262:K262),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C262<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C262,1),"") + + + + IFERROR(IF(ISNUMBER(E263),0,1),"") + + + + IFERROR(YEAR(C263),"") + + + + + + + + + + IFERROR(SUM(I263:K263),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C263<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C263,1),"") + + + + IFERROR(IF(ISNUMBER(E264),0,1),"") + + + + IFERROR(YEAR(C264),"") + + + + + + + + + + IFERROR(SUM(I264:K264),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C264<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C264,1),"") + + + + IFERROR(IF(ISNUMBER(E265),0,1),"") + + + + IFERROR(YEAR(C265),"") + + + + + + + + + + IFERROR(SUM(I265:K265),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C265<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C265,1),"") + + + + IFERROR(IF(ISNUMBER(E266),0,1),"") + + + + IFERROR(YEAR(C266),"") + + + + + + + + + + IFERROR(SUM(I266:K266),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C266<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C266,1),"") + + + + IFERROR(IF(ISNUMBER(E267),0,1),"") + + + + IFERROR(YEAR(C267),"") + + + + + + + + + + IFERROR(SUM(I267:K267),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C267<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C267,1),"") + + + + IFERROR(IF(ISNUMBER(E268),0,1),"") + + + + IFERROR(YEAR(C268),"") + + + + + + + + + + IFERROR(SUM(I268:K268),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C268<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C268,1),"") + + + + IFERROR(IF(ISNUMBER(E269),0,1),"") + + + + IFERROR(YEAR(C269),"") + + + + + + + + + + IFERROR(SUM(I269:K269),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C269<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C269,1),"") + + + + IFERROR(IF(ISNUMBER(E270),0,1),"") + + + + IFERROR(YEAR(C270),"") + + + + + + + + + + IFERROR(SUM(I270:K270),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C270<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C270,1),"") + + + + IFERROR(IF(ISNUMBER(E271),0,1),"") + + + + IFERROR(YEAR(C271),"") + + + + + + + + + + IFERROR(SUM(I271:K271),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C271<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C271,1),"") + + + + IFERROR(IF(ISNUMBER(E272),0,1),"") + + + + IFERROR(YEAR(C272),"") + + + + + + + + + + IFERROR(SUM(I272:K272),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C272<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C272,1),"") + + + + IFERROR(IF(ISNUMBER(E273),0,1),"") + + + + IFERROR(YEAR(C273),"") + + + + + + + + + + IFERROR(SUM(I273:K273),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C273<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C273,1),"") + + + + IFERROR(IF(ISNUMBER(E274),0,1),"") + + + + IFERROR(YEAR(C274),"") + + + + + + + + + + IFERROR(SUM(I274:K274),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C274<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C274,1),"") + + + + IFERROR(IF(ISNUMBER(E275),0,1),"") + + + + IFERROR(YEAR(C275),"") + + + + + + + + + + IFERROR(SUM(I275:K275),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C275<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C275,1),"") + + + + IFERROR(IF(ISNUMBER(E276),0,1),"") + + + + IFERROR(YEAR(C276),"") + + + + + + + + + + IFERROR(SUM(I276:K276),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C276<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C276,1),"") + + + + IFERROR(IF(ISNUMBER(E277),0,1),"") + + + + IFERROR(YEAR(C277),"") + + + + + + + + + + IFERROR(SUM(I277:K277),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C277<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C277,1),"") + + + + IFERROR(IF(ISNUMBER(E278),0,1),"") + + + + IFERROR(YEAR(C278),"") + + + + + + + + + + IFERROR(SUM(I278:K278),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C278<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C278,1),"") + + + + IFERROR(IF(ISNUMBER(E279),0,1),"") + + + + IFERROR(YEAR(C279),"") + + + + + + + + + + IFERROR(SUM(I279:K279),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C279<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C279,1),"") + + + + IFERROR(IF(ISNUMBER(E280),0,1),"") + + + + IFERROR(YEAR(C280),"") + + + + + + + + + + IFERROR(SUM(I280:K280),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C280<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C280,1),"") + + + + IFERROR(IF(ISNUMBER(E281),0,1),"") + + + + IFERROR(YEAR(C281),"") + + + + + + + + + + IFERROR(SUM(I281:K281),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C281<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C281,1),"") + + + + IFERROR(IF(ISNUMBER(E282),0,1),"") + + + + IFERROR(YEAR(C282),"") + + + + + + + + + + IFERROR(SUM(I282:K282),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C282<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C282,1),"") + + + + IFERROR(IF(ISNUMBER(E283),0,1),"") + + + + IFERROR(YEAR(C283),"") + + + + + + + + + + IFERROR(SUM(I283:K283),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C283<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C283,1),"") + + + + IFERROR(IF(ISNUMBER(E284),0,1),"") + + + + IFERROR(YEAR(C284),"") + + + + + + + + + + IFERROR(SUM(I284:K284),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C284<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C284,1),"") + + + + IFERROR(IF(ISNUMBER(E285),0,1),"") + + + + IFERROR(YEAR(C285),"") + + + + + + + + + + IFERROR(SUM(I285:K285),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C285<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C285,1),"") + + + + IFERROR(IF(ISNUMBER(E286),0,1),"") + + + + IFERROR(YEAR(C286),"") + + + + + + + + + + IFERROR(SUM(I286:K286),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C286<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C286,1),"") + + + + IFERROR(IF(ISNUMBER(E287),0,1),"") + + + + IFERROR(YEAR(C287),"") + + + + + + + + + + IFERROR(SUM(I287:K287),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C287<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C287,1),"") + + + + IFERROR(IF(ISNUMBER(E288),0,1),"") + + + + IFERROR(YEAR(C288),"") + + + + + + + + + + IFERROR(SUM(I288:K288),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C288<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C288,1),"") + + + + IFERROR(IF(ISNUMBER(E289),0,1),"") + + + + IFERROR(YEAR(C289),"") + + + + + + + + + + IFERROR(SUM(I289:K289),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C289<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C289,1),"") + + + + IFERROR(IF(ISNUMBER(E290),0,1),"") + + + + IFERROR(YEAR(C290),"") + + + + + + + + + + IFERROR(SUM(I290:K290),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C290<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C290,1),"") + + + + IFERROR(IF(ISNUMBER(E291),0,1),"") + + + + IFERROR(YEAR(C291),"") + + + + + + + + + + IFERROR(SUM(I291:K291),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C291<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C291,1),"") + + + + IFERROR(IF(ISNUMBER(E292),0,1),"") + + + + IFERROR(YEAR(C292),"") + + + + + + + + + + IFERROR(SUM(I292:K292),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C292<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C292,1),"") + + + + IFERROR(IF(ISNUMBER(E293),0,1),"") + + + + IFERROR(YEAR(C293),"") + + + + + + + + + + IFERROR(SUM(I293:K293),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C293<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C293,1),"") + + + + IFERROR(IF(ISNUMBER(E294),0,1),"") + + + + IFERROR(YEAR(C294),"") + + + + + + + + + + IFERROR(SUM(I294:K294),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C294<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C294,1),"") + + + + IFERROR(IF(ISNUMBER(E295),0,1),"") + + + + IFERROR(YEAR(C295),"") + + + + + + + + + + IFERROR(SUM(I295:K295),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C295<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C295,1),"") + + + + IFERROR(IF(ISNUMBER(E296),0,1),"") + + + + IFERROR(YEAR(C296),"") + + + + + + + + + + IFERROR(SUM(I296:K296),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C296<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C296,1),"") + + + + IFERROR(IF(ISNUMBER(E297),0,1),"") + + + + IFERROR(YEAR(C297),"") + + + + + + + + + + IFERROR(SUM(I297:K297),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C297<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C297,1),"") + + + + IFERROR(IF(ISNUMBER(E298),0,1),"") + + + + IFERROR(YEAR(C298),"") + + + + + + + + + + IFERROR(SUM(I298:K298),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C298<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C298,1),"") + + + + IFERROR(IF(ISNUMBER(E299),0,1),"") + + + + IFERROR(YEAR(C299),"") + + + + + + + + + + IFERROR(SUM(I299:K299),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C299<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C299,1),"") + + + + IFERROR(IF(ISNUMBER(E300),0,1),"") + + + + IFERROR(YEAR(C300),"") + + + + + + + + + + IFERROR(SUM(I300:K300),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C300<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C300,1),"") + + + + IFERROR(IF(ISNUMBER(E301),0,1),"") + + + + IFERROR(YEAR(C301),"") + + + + + + + + + + IFERROR(SUM(I301:K301),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C301<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C301,1),"") + + + + IFERROR(IF(ISNUMBER(E302),0,1),"") + + + + IFERROR(YEAR(C302),"") + + + + + + + + + + IFERROR(SUM(I302:K302),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C302<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C302,1),"") + + + + IFERROR(IF(ISNUMBER(E303),0,1),"") + + + + IFERROR(YEAR(C303),"") + + + + + + + + + + IFERROR(SUM(I303:K303),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C303<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C303,1),"") + + + + IFERROR(IF(ISNUMBER(E304),0,1),"") + + + + IFERROR(YEAR(C304),"") + + + + + + + + + + IFERROR(SUM(I304:K304),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C304<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C304,1),"") + + + + IFERROR(IF(ISNUMBER(E305),0,1),"") + + + + IFERROR(YEAR(C305),"") + + + + + + + + + + IFERROR(SUM(I305:K305),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C305<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C305,1),"") + + + + IFERROR(IF(ISNUMBER(E306),0,1),"") + + + + IFERROR(YEAR(C306),"") + + + + + + + + + + IFERROR(SUM(I306:K306),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C306<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C306,1),"") + + + + IFERROR(IF(ISNUMBER(E307),0,1),"") + + + + IFERROR(YEAR(C307),"") + + + + + + + + + + IFERROR(SUM(I307:K307),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C307<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C307,1),"") + + + + IFERROR(IF(ISNUMBER(E308),0,1),"") + + + + IFERROR(YEAR(C308),"") + + + + + + + + + + IFERROR(SUM(I308:K308),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C308<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C308,1),"") + + + + IFERROR(IF(ISNUMBER(E309),0,1),"") + + + + IFERROR(YEAR(C309),"") + + + + + + + + + + IFERROR(SUM(I309:K309),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C309<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C309,1),"") + + + + IFERROR(IF(ISNUMBER(E310),0,1),"") + + + + IFERROR(YEAR(C310),"") + + + + + + + + + + IFERROR(SUM(I310:K310),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C310<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C310,1),"") + + + + IFERROR(IF(ISNUMBER(E311),0,1),"") + + + + IFERROR(YEAR(C311),"") + + + + + + + + + + IFERROR(SUM(I311:K311),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C311<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C311,1),"") + + + + IFERROR(IF(ISNUMBER(E312),0,1),"") + + + + IFERROR(YEAR(C312),"") + + + + + + + + + + IFERROR(SUM(I312:K312),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C312<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C312,1),"") + + + + IFERROR(IF(ISNUMBER(E313),0,1),"") + + + + IFERROR(YEAR(C313),"") + + + + + + + + + + IFERROR(SUM(I313:K313),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C313<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C313,1),"") + + + + IFERROR(IF(ISNUMBER(E314),0,1),"") + + + + IFERROR(YEAR(C314),"") + + + + + + + + + + IFERROR(SUM(I314:K314),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C314<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C314,1),"") + + + + IFERROR(IF(ISNUMBER(E315),0,1),"") + + + + IFERROR(YEAR(C315),"") + + + + + + + + + + IFERROR(SUM(I315:K315),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C315<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C315,1),"") + + + + IFERROR(IF(ISNUMBER(E316),0,1),"") + + + + IFERROR(YEAR(C316),"") + + + + + + + + + + IFERROR(SUM(I316:K316),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C316<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C316,1),"") + + + + IFERROR(IF(ISNUMBER(E317),0,1),"") + + + + IFERROR(YEAR(C317),"") + + + + + + + + + + IFERROR(SUM(I317:K317),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C317<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C317,1),"") + + + + IFERROR(IF(ISNUMBER(E318),0,1),"") + + + + IFERROR(YEAR(C318),"") + + + + + + + + + + IFERROR(SUM(I318:K318),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C318<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C318,1),"") + + + + IFERROR(IF(ISNUMBER(E319),0,1),"") + + + + IFERROR(YEAR(C319),"") + + + + + + + + + + IFERROR(SUM(I319:K319),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C319<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C319,1),"") + + + + IFERROR(IF(ISNUMBER(E320),0,1),"") + + + + IFERROR(YEAR(C320),"") + + + + + + + + + + IFERROR(SUM(I320:K320),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C320<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C320,1),"") + + + + IFERROR(IF(ISNUMBER(E321),0,1),"") + + + + IFERROR(YEAR(C321),"") + + + + + + + + + + IFERROR(SUM(I321:K321),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C321<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C321,1),"") + + + + IFERROR(IF(ISNUMBER(E322),0,1),"") + + + + IFERROR(YEAR(C322),"") + + + + + + + + + + IFERROR(SUM(I322:K322),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C322<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C322,1),"") + + + + IFERROR(IF(ISNUMBER(E323),0,1),"") + + + + IFERROR(YEAR(C323),"") + + + + + + + + + + IFERROR(SUM(I323:K323),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C323<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C323,1),"") + + + + IFERROR(IF(ISNUMBER(E324),0,1),"") + + + + IFERROR(YEAR(C324),"") + + + + + + + + + + IFERROR(SUM(I324:K324),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C324<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C324,1),"") + + + + IFERROR(IF(ISNUMBER(E325),0,1),"") + + + + IFERROR(YEAR(C325),"") + + + + + + + + + + IFERROR(SUM(I325:K325),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C325<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C325,1),"") + + + + IFERROR(IF(ISNUMBER(E326),0,1),"") + + + + IFERROR(YEAR(C326),"") + + + + + + + + + + IFERROR(SUM(I326:K326),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C326<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C326,1),"") + + + + IFERROR(IF(ISNUMBER(E327),0,1),"") + + + + IFERROR(YEAR(C327),"") + + + + + + + + + + IFERROR(SUM(I327:K327),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C327<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C327,1),"") + + + + IFERROR(IF(ISNUMBER(E328),0,1),"") + + + + IFERROR(YEAR(C328),"") + + + + + + + + + + IFERROR(SUM(I328:K328),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C328<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C328,1),"") + + + + IFERROR(IF(ISNUMBER(E329),0,1),"") + + + + IFERROR(YEAR(C329),"") + + + + + + + + + + IFERROR(SUM(I329:K329),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C329<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C329,1),"") + + + + IFERROR(IF(ISNUMBER(E330),0,1),"") + + + + IFERROR(YEAR(C330),"") + + + + + + + + + + IFERROR(SUM(I330:K330),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C330<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C330,1),"") + + + + IFERROR(IF(ISNUMBER(E331),0,1),"") + + + + IFERROR(YEAR(C331),"") + + + + + + + + + + IFERROR(SUM(I331:K331),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C331<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C331,1),"") + + + + IFERROR(IF(ISNUMBER(E332),0,1),"") + + + + IFERROR(YEAR(C332),"") + + + + + + + + + + IFERROR(SUM(I332:K332),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C332<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C332,1),"") + + + + IFERROR(IF(ISNUMBER(E333),0,1),"") + + + + IFERROR(YEAR(C333),"") + + + + + + + + + + IFERROR(SUM(I333:K333),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C333<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C333,1),"") + + + + IFERROR(IF(ISNUMBER(E334),0,1),"") + + + + IFERROR(YEAR(C334),"") + + + + + + + + + + IFERROR(SUM(I334:K334),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C334<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C334,1),"") + + + + IFERROR(IF(ISNUMBER(E335),0,1),"") + + + + IFERROR(YEAR(C335),"") + + + + + + + + + + IFERROR(SUM(I335:K335),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C335<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C335,1),"") + + + + IFERROR(IF(ISNUMBER(E336),0,1),"") + + + + IFERROR(YEAR(C336),"") + + + + + + + + + + IFERROR(SUM(I336:K336),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C336<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C336,1),"") + + + + IFERROR(IF(ISNUMBER(E337),0,1),"") + + + + IFERROR(YEAR(C337),"") + + + + + + + + + + IFERROR(SUM(I337:K337),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C337<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C337,1),"") + + + + IFERROR(IF(ISNUMBER(E338),0,1),"") + + + + IFERROR(YEAR(C338),"") + + + + + + + + + + IFERROR(SUM(I338:K338),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C338<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C338,1),"") + + + + IFERROR(IF(ISNUMBER(E339),0,1),"") + + + + IFERROR(YEAR(C339),"") + + + + + + + + + + IFERROR(SUM(I339:K339),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C339<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C339,1),"") + + + + IFERROR(IF(ISNUMBER(E340),0,1),"") + + + + IFERROR(YEAR(C340),"") + + + + + + + + + + IFERROR(SUM(I340:K340),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C340<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C340,1),"") + + + + IFERROR(IF(ISNUMBER(E341),0,1),"") + + + + IFERROR(YEAR(C341),"") + + + + + + + + + + IFERROR(SUM(I341:K341),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C341<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C341,1),"") + + + + IFERROR(IF(ISNUMBER(E342),0,1),"") + + + + IFERROR(YEAR(C342),"") + + + + + + + + + + IFERROR(SUM(I342:K342),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C342<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C342,1),"") + + + + IFERROR(IF(ISNUMBER(E343),0,1),"") + + + + IFERROR(YEAR(C343),"") + + + + + + + + + + IFERROR(SUM(I343:K343),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C343<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C343,1),"") + + + + IFERROR(IF(ISNUMBER(E344),0,1),"") + + + + IFERROR(YEAR(C344),"") + + + + + + + + + + IFERROR(SUM(I344:K344),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C344<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C344,1),"") + + + + IFERROR(IF(ISNUMBER(E345),0,1),"") + + + + IFERROR(YEAR(C345),"") + + + + + + + + + + IFERROR(SUM(I345:K345),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C345<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C345,1),"") + + + + IFERROR(IF(ISNUMBER(E346),0,1),"") + + + + IFERROR(YEAR(C346),"") + + + + + + + + + + IFERROR(SUM(I346:K346),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C346<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C346,1),"") + + + + IFERROR(IF(ISNUMBER(E347),0,1),"") + + + + IFERROR(YEAR(C347),"") + + + + + + + + + + IFERROR(SUM(I347:K347),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C347<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C347,1),"") + + + + IFERROR(IF(ISNUMBER(E348),0,1),"") + + + + IFERROR(YEAR(C348),"") + + + + + + + + + + IFERROR(SUM(I348:K348),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C348<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C348,1),"") + + + + IFERROR(IF(ISNUMBER(E349),0,1),"") + + + + IFERROR(YEAR(C349),"") + + + + + + + + + + IFERROR(SUM(I349:K349),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C349<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C349,1),"") + + + + IFERROR(IF(ISNUMBER(E350),0,1),"") + + + + IFERROR(YEAR(C350),"") + + + + + + + + + + IFERROR(SUM(I350:K350),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C350<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C350,1),"") + + + + IFERROR(IF(ISNUMBER(E351),0,1),"") + + + + IFERROR(YEAR(C351),"") + + + + + + + + + + IFERROR(SUM(I351:K351),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C351<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C351,1),"") + + + + IFERROR(IF(ISNUMBER(E352),0,1),"") + + + + IFERROR(YEAR(C352),"") + + + + + + + + + + IFERROR(SUM(I352:K352),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C352<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C352,1),"") + + + + IFERROR(IF(ISNUMBER(E353),0,1),"") + + + + IFERROR(YEAR(C353),"") + + + + + + + + + + IFERROR(SUM(I353:K353),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C353<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C353,1),"") + + + + IFERROR(IF(ISNUMBER(E354),0,1),"") + + + + IFERROR(YEAR(C354),"") + + + + + + + + + + IFERROR(SUM(I354:K354),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C354<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C354,1),"") + + + + IFERROR(IF(ISNUMBER(E355),0,1),"") + + + + IFERROR(YEAR(C355),"") + + + + + + + + + + IFERROR(SUM(I355:K355),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C355<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C355,1),"") + + + + IFERROR(IF(ISNUMBER(E356),0,1),"") + + + + IFERROR(YEAR(C356),"") + + + + + + + + + + IFERROR(SUM(I356:K356),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C356<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C356,1),"") + + + + IFERROR(IF(ISNUMBER(E357),0,1),"") + + + + IFERROR(YEAR(C357),"") + + + + + + + + + + IFERROR(SUM(I357:K357),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C357<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C357,1),"") + + + + IFERROR(IF(ISNUMBER(E358),0,1),"") + + + + IFERROR(YEAR(C358),"") + + + + + + + + + + IFERROR(SUM(I358:K358),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C358<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C358,1),"") + + + + IFERROR(IF(ISNUMBER(E359),0,1),"") + + + + IFERROR(YEAR(C359),"") + + + + + + + + + + IFERROR(SUM(I359:K359),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C359<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C359,1),"") + + + + IFERROR(IF(ISNUMBER(E360),0,1),"") + + + + IFERROR(YEAR(C360),"") + + + + + + + + + + IFERROR(SUM(I360:K360),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C360<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C360,1),"") + + + + IFERROR(IF(ISNUMBER(E361),0,1),"") + + + + IFERROR(YEAR(C361),"") + + + + + + + + + + IFERROR(SUM(I361:K361),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C361<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C361,1),"") + + + + IFERROR(IF(ISNUMBER(E362),0,1),"") + + + + IFERROR(YEAR(C362),"") + + + + + + + + + + IFERROR(SUM(I362:K362),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C362<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C362,1),"") + + + + IFERROR(IF(ISNUMBER(E363),0,1),"") + + + + IFERROR(YEAR(C363),"") + + + + + + + + + + IFERROR(SUM(I363:K363),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C363<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C363,1),"") + + + + IFERROR(IF(ISNUMBER(E364),0,1),"") + + + + IFERROR(YEAR(C364),"") + + + + + + + + + + IFERROR(SUM(I364:K364),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C364<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C364,1),"") + + + + IFERROR(IF(ISNUMBER(E365),0,1),"") + + + + IFERROR(YEAR(C365),"") + + + + + + + + + + IFERROR(SUM(I365:K365),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C365<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C365,1),"") + + + + IFERROR(IF(ISNUMBER(E366),0,1),"") + + + + IFERROR(YEAR(C366),"") + + + + + + + + + + IFERROR(SUM(I366:K366),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C366<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C366,1),"") + + + + IFERROR(IF(ISNUMBER(E367),0,1),"") + + + + IFERROR(YEAR(C367),"") + + + + + + + + + + IFERROR(SUM(I367:K367),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C367<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C367,1),"") + + + + IFERROR(IF(ISNUMBER(E368),0,1),"") + + + + IFERROR(YEAR(C368),"") + + + + + + + + + + IFERROR(SUM(I368:K368),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C368<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C368,1),"") + + + + IFERROR(IF(ISNUMBER(E369),0,1),"") + + + + IFERROR(YEAR(C369),"") + + + + + + + + + + IFERROR(SUM(I369:K369),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C369<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C369,1),"") + + + + IFERROR(IF(ISNUMBER(E370),0,1),"") + + + + IFERROR(YEAR(C370),"") + + + + + + + + + + IFERROR(SUM(I370:K370),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C370<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C370,1),"") + + + + IFERROR(IF(ISNUMBER(E371),0,1),"") + + + + IFERROR(YEAR(C371),"") + + + + + + + + + + IFERROR(SUM(I371:K371),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C371<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C371,1),"") + + + + IFERROR(IF(ISNUMBER(E372),0,1),"") + + + + IFERROR(YEAR(C372),"") + + + + + + + + + + IFERROR(SUM(I372:K372),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C372<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C372,1),"") + + + + IFERROR(IF(ISNUMBER(E373),0,1),"") + + + + IFERROR(YEAR(C373),"") + + + + + + + + + + IFERROR(SUM(I373:K373),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C373<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C373,1),"") + + + + IFERROR(IF(ISNUMBER(E374),0,1),"") + + + + IFERROR(YEAR(C374),"") + + + + + + + + + + IFERROR(SUM(I374:K374),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C374<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C374,1),"") + + + + IFERROR(IF(ISNUMBER(E375),0,1),"") + + + + IFERROR(YEAR(C375),"") + + + + + + + + + + IFERROR(SUM(I375:K375),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C375<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C375,1),"") + + + + IFERROR(IF(ISNUMBER(E376),0,1),"") + + + + IFERROR(YEAR(C376),"") + + + + + + + + + + IFERROR(SUM(I376:K376),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C376<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C376,1),"") + + + + IFERROR(IF(ISNUMBER(E377),0,1),"") + + + + IFERROR(YEAR(C377),"") + + + + + + + + + + IFERROR(SUM(I377:K377),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C377<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C377,1),"") + + + + IFERROR(IF(ISNUMBER(E378),0,1),"") + + + + IFERROR(YEAR(C378),"") + + + + + + + + + + IFERROR(SUM(I378:K378),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C378<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C378,1),"") + + + + IFERROR(IF(ISNUMBER(E379),0,1),"") + + + + IFERROR(YEAR(C379),"") + + + + + + + + + + IFERROR(SUM(I379:K379),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C379<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C379,1),"") + + + + IFERROR(IF(ISNUMBER(E380),0,1),"") + + + + IFERROR(YEAR(C380),"") + + + + + + + + + + IFERROR(SUM(I380:K380),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C380<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C380,1),"") + + + + IFERROR(IF(ISNUMBER(E381),0,1),"") + + + + IFERROR(YEAR(C381),"") + + + + + + + + + + IFERROR(SUM(I381:K381),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C381<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C381,1),"") + + + + IFERROR(IF(ISNUMBER(E382),0,1),"") + + + + IFERROR(YEAR(C382),"") + + + + + + + + + + IFERROR(SUM(I382:K382),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C382<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C382,1),"") + + + + IFERROR(IF(ISNUMBER(E383),0,1),"") + + + + IFERROR(YEAR(C383),"") + + + + + + + + + + IFERROR(SUM(I383:K383),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C383<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C383,1),"") + + + + IFERROR(IF(ISNUMBER(E384),0,1),"") + + + + IFERROR(YEAR(C384),"") + + + + + + + + + + IFERROR(SUM(I384:K384),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C384<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C384,1),"") + + + + IFERROR(IF(ISNUMBER(E385),0,1),"") + + + + IFERROR(YEAR(C385),"") + + + + + + + + + + IFERROR(SUM(I385:K385),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C385<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C385,1),"") + + + + IFERROR(IF(ISNUMBER(E386),0,1),"") + + + + IFERROR(YEAR(C386),"") + + + + + + + + + + IFERROR(SUM(I386:K386),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C386<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C386,1),"") + + + + IFERROR(IF(ISNUMBER(E387),0,1),"") + + + + IFERROR(YEAR(C387),"") + + + + + + + + + + IFERROR(SUM(I387:K387),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C387<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C387,1),"") + + + + IFERROR(IF(ISNUMBER(E388),0,1),"") + + + + IFERROR(YEAR(C388),"") + + + + + + + + + + IFERROR(SUM(I388:K388),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C388<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C388,1),"") + + + + IFERROR(IF(ISNUMBER(E389),0,1),"") + + + + IFERROR(YEAR(C389),"") + + + + + + + + + + IFERROR(SUM(I389:K389),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C389<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C389,1),"") + + + + IFERROR(IF(ISNUMBER(E390),0,1),"") + + + + IFERROR(YEAR(C390),"") + + + + + + + + + + IFERROR(SUM(I390:K390),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C390<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C390,1),"") + + + + IFERROR(IF(ISNUMBER(E391),0,1),"") + + + + IFERROR(YEAR(C391),"") + + + + + + + + + + IFERROR(SUM(I391:K391),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C391<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C391,1),"") + + + + IFERROR(IF(ISNUMBER(E392),0,1),"") + + + + IFERROR(YEAR(C392),"") + + + + + + + + + + IFERROR(SUM(I392:K392),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C392<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C392,1),"") + + + + IFERROR(IF(ISNUMBER(E393),0,1),"") + + + + IFERROR(YEAR(C393),"") + + + + + + + + + + IFERROR(SUM(I393:K393),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C393<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C393,1),"") + + + + IFERROR(IF(ISNUMBER(E394),0,1),"") + + + + IFERROR(YEAR(C394),"") + + + + + + + + + + IFERROR(SUM(I394:K394),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C394<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C394,1),"") + + + + IFERROR(IF(ISNUMBER(E395),0,1),"") + + + + IFERROR(YEAR(C395),"") + + + + + + + + + + IFERROR(SUM(I395:K395),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C395<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C395,1),"") + + + + IFERROR(IF(ISNUMBER(E396),0,1),"") + + + + IFERROR(YEAR(C396),"") + + + + + + + + + + IFERROR(SUM(I396:K396),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C396<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C396,1),"") + + + + IFERROR(IF(ISNUMBER(E397),0,1),"") + + + + IFERROR(YEAR(C397),"") + + + + + + + + + + IFERROR(SUM(I397:K397),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C397<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C397,1),"") + + + + IFERROR(IF(ISNUMBER(E398),0,1),"") + + + + IFERROR(YEAR(C398),"") + + + + + + + + + + IFERROR(SUM(I398:K398),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C398<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C398,1),"") + + + + IFERROR(IF(ISNUMBER(E399),0,1),"") + + + + IFERROR(YEAR(C399),"") + + + + + + + + + + IFERROR(SUM(I399:K399),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C399<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C399,1),"") + + + + IFERROR(IF(ISNUMBER(E400),0,1),"") + + + + IFERROR(YEAR(C400),"") + + + + + + + + + + IFERROR(SUM(I400:K400),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C400<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C400,1),"") + + + + IFERROR(IF(ISNUMBER(E401),0,1),"") + + + + IFERROR(YEAR(C401),"") + + + + + + + + + + IFERROR(SUM(I401:K401),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C401<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C401,1),"") + + + + IFERROR(IF(ISNUMBER(E402),0,1),"") + + + + IFERROR(YEAR(C402),"") + + + + + + + + + + IFERROR(SUM(I402:K402),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C402<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C402,1),"") + + + + IFERROR(IF(ISNUMBER(E403),0,1),"") + + + + IFERROR(YEAR(C403),"") + + + + + + + + + + IFERROR(SUM(I403:K403),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C403<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C403,1),"") + + + + IFERROR(IF(ISNUMBER(E404),0,1),"") + + + + IFERROR(YEAR(C404),"") + + + + + + + + + + IFERROR(SUM(I404:K404),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C404<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C404,1),"") + + + + IFERROR(IF(ISNUMBER(E405),0,1),"") + + + + IFERROR(YEAR(C405),"") + + + + + + + + + + IFERROR(SUM(I405:K405),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C405<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C405,1),"") + + + + IFERROR(IF(ISNUMBER(E406),0,1),"") + + + + IFERROR(YEAR(C406),"") + + + + + + + + + + IFERROR(SUM(I406:K406),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C406<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C406,1),"") + + + + IFERROR(IF(ISNUMBER(E407),0,1),"") + + + + IFERROR(YEAR(C407),"") + + + + + + + + + + IFERROR(SUM(I407:K407),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C407<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C407,1),"") + + + + IFERROR(IF(ISNUMBER(E408),0,1),"") + + + + IFERROR(YEAR(C408),"") + + + + + + + + + + IFERROR(SUM(I408:K408),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C408<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C408,1),"") + + + + IFERROR(IF(ISNUMBER(E409),0,1),"") + + + + IFERROR(YEAR(C409),"") + + + + + + + + + + IFERROR(SUM(I409:K409),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C409<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C409,1),"") + + + + IFERROR(IF(ISNUMBER(E410),0,1),"") + + + + IFERROR(YEAR(C410),"") + + + + + + + + + + IFERROR(SUM(I410:K410),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C410<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C410,1),"") + + + + IFERROR(IF(ISNUMBER(E411),0,1),"") + + + + IFERROR(YEAR(C411),"") + + + + + + + + + + IFERROR(SUM(I411:K411),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C411<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C411,1),"") + + + + IFERROR(IF(ISNUMBER(E412),0,1),"") + + + + IFERROR(YEAR(C412),"") + + + + + + + + + + IFERROR(SUM(I412:K412),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C412<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C412,1),"") + + + + IFERROR(IF(ISNUMBER(E413),0,1),"") + + + + IFERROR(YEAR(C413),"") + + + + + + + + + + IFERROR(SUM(I413:K413),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C413<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C413,1),"") + + + + IFERROR(IF(ISNUMBER(E414),0,1),"") + + + + IFERROR(YEAR(C414),"") + + + + + + + + + + IFERROR(SUM(I414:K414),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C414<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C414,1),"") + + + + IFERROR(IF(ISNUMBER(E415),0,1),"") + + + + IFERROR(YEAR(C415),"") + + + + + + + + + + IFERROR(SUM(I415:K415),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C415<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C415,1),"") + + + + IFERROR(IF(ISNUMBER(E416),0,1),"") + + + + IFERROR(YEAR(C416),"") + + + + + + + + + + IFERROR(SUM(I416:K416),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C416<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C416,1),"") + + + + IFERROR(IF(ISNUMBER(E417),0,1),"") + + + + IFERROR(YEAR(C417),"") + + + + + + + + + + IFERROR(SUM(I417:K417),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C417<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C417,1),"") + + + + IFERROR(IF(ISNUMBER(E418),0,1),"") + + + + IFERROR(YEAR(C418),"") + + + + + + + + + + IFERROR(SUM(I418:K418),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C418<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C418,1),"") + + + + IFERROR(IF(ISNUMBER(E419),0,1),"") + + + + IFERROR(YEAR(C419),"") + + + + + + + + + + IFERROR(SUM(I419:K419),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C419<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C419,1),"") + + + + IFERROR(IF(ISNUMBER(E420),0,1),"") + + + + IFERROR(YEAR(C420),"") + + + + + + + + + + IFERROR(SUM(I420:K420),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C420<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C420,1),"") + + + + IFERROR(IF(ISNUMBER(E421),0,1),"") + + + + IFERROR(YEAR(C421),"") + + + + + + + + + + IFERROR(SUM(I421:K421),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C421<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C421,1),"") + + + + IFERROR(IF(ISNUMBER(E422),0,1),"") + + + + IFERROR(YEAR(C422),"") + + + + + + + + + + IFERROR(SUM(I422:K422),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C422<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C422,1),"") + + + + IFERROR(IF(ISNUMBER(E423),0,1),"") + + + + IFERROR(YEAR(C423),"") + + + + + + + + + + IFERROR(SUM(I423:K423),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C423<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C423,1),"") + + + + IFERROR(IF(ISNUMBER(E424),0,1),"") + + + + IFERROR(YEAR(C424),"") + + + + + + + + + + IFERROR(SUM(I424:K424),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C424<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C424,1),"") + + + + IFERROR(IF(ISNUMBER(E425),0,1),"") + + + + IFERROR(YEAR(C425),"") + + + + + + + + + + IFERROR(SUM(I425:K425),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C425<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C425,1),"") + + + + IFERROR(IF(ISNUMBER(E426),0,1),"") + + + + IFERROR(YEAR(C426),"") + + + + + + + + + + IFERROR(SUM(I426:K426),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C426<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C426,1),"") + + + + IFERROR(IF(ISNUMBER(E427),0,1),"") + + + + IFERROR(YEAR(C427),"") + + + + + + + + + + IFERROR(SUM(I427:K427),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C427<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C427,1),"") + + + + IFERROR(IF(ISNUMBER(E428),0,1),"") + + + + IFERROR(YEAR(C428),"") + + + + + + + + + + IFERROR(SUM(I428:K428),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C428<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C428,1),"") + + + + IFERROR(IF(ISNUMBER(E429),0,1),"") + + + + IFERROR(YEAR(C429),"") + + + + + + + + + + IFERROR(SUM(I429:K429),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C429<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C429,1),"") + + + + IFERROR(IF(ISNUMBER(E430),0,1),"") + + + + IFERROR(YEAR(C430),"") + + + + + + + + + + IFERROR(SUM(I430:K430),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C430<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C430,1),"") + + + + IFERROR(IF(ISNUMBER(E431),0,1),"") + + + + IFERROR(YEAR(C431),"") + + + + + + + + + + IFERROR(SUM(I431:K431),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C431<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C431,1),"") + + + + IFERROR(IF(ISNUMBER(E432),0,1),"") + + + + IFERROR(YEAR(C432),"") + + + + + + + + + + IFERROR(SUM(I432:K432),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C432<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C432,1),"") + + + + IFERROR(IF(ISNUMBER(E433),0,1),"") + + + + IFERROR(YEAR(C433),"") + + + + + + + + + + IFERROR(SUM(I433:K433),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C433<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C433,1),"") + + + + IFERROR(IF(ISNUMBER(E434),0,1),"") + + + + IFERROR(YEAR(C434),"") + + + + + + + + + + IFERROR(SUM(I434:K434),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C434<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C434,1),"") + + + + IFERROR(IF(ISNUMBER(E435),0,1),"") + + + + IFERROR(YEAR(C435),"") + + + + + + + + + + IFERROR(SUM(I435:K435),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C435<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C435,1),"") + + + + IFERROR(IF(ISNUMBER(E436),0,1),"") + + + + IFERROR(YEAR(C436),"") + + + + + + + + + + IFERROR(SUM(I436:K436),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C436<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C436,1),"") + + + + IFERROR(IF(ISNUMBER(E437),0,1),"") + + + + IFERROR(YEAR(C437),"") + + + + + + + + + + IFERROR(SUM(I437:K437),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C437<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C437,1),"") + + + + IFERROR(IF(ISNUMBER(E438),0,1),"") + + + + IFERROR(YEAR(C438),"") + + + + + + + + + + IFERROR(SUM(I438:K438),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C438<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C438,1),"") + + + + IFERROR(IF(ISNUMBER(E439),0,1),"") + + + + IFERROR(YEAR(C439),"") + + + + + + + + + + IFERROR(SUM(I439:K439),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C439<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C439,1),"") + + + + IFERROR(IF(ISNUMBER(E440),0,1),"") + + + + IFERROR(YEAR(C440),"") + + + + + + + + + + IFERROR(SUM(I440:K440),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C440<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C440,1),"") + + + + IFERROR(IF(ISNUMBER(E441),0,1),"") + + + + IFERROR(YEAR(C441),"") + + + + + + + + + + IFERROR(SUM(I441:K441),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C441<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C441,1),"") + + + + IFERROR(IF(ISNUMBER(E442),0,1),"") + + + + IFERROR(YEAR(C442),"") + + + + + + + + + + IFERROR(SUM(I442:K442),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C442<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C442,1),"") + + + + IFERROR(IF(ISNUMBER(E443),0,1),"") + + + + IFERROR(YEAR(C443),"") + + + + + + + + + + IFERROR(SUM(I443:K443),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C443<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C443,1),"") + + + + IFERROR(IF(ISNUMBER(E444),0,1),"") + + + + IFERROR(YEAR(C444),"") + + + + + + + + + + IFERROR(SUM(I444:K444),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C444<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C444,1),"") + + + + IFERROR(IF(ISNUMBER(E445),0,1),"") + + + + IFERROR(YEAR(C445),"") + + + + + + + + + + IFERROR(SUM(I445:K445),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C445<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C445,1),"") + + + + IFERROR(IF(ISNUMBER(E446),0,1),"") + + + + IFERROR(YEAR(C446),"") + + + + + + + + + + IFERROR(SUM(I446:K446),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C446<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C446,1),"") + + + + IFERROR(IF(ISNUMBER(E447),0,1),"") + + + + IFERROR(YEAR(C447),"") + + + + + + + + + + IFERROR(SUM(I447:K447),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C447<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C447,1),"") + + + + IFERROR(IF(ISNUMBER(E448),0,1),"") + + + + IFERROR(YEAR(C448),"") + + + + + + + + + + IFERROR(SUM(I448:K448),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C448<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C448,1),"") + + + + IFERROR(IF(ISNUMBER(E449),0,1),"") + + + + IFERROR(YEAR(C449),"") + + + + + + + + + + IFERROR(SUM(I449:K449),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C449<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C449,1),"") + + + + IFERROR(IF(ISNUMBER(E450),0,1),"") + + + + IFERROR(YEAR(C450),"") + + + + + + + + + + IFERROR(SUM(I450:K450),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C450<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C450,1),"") + + + + IFERROR(IF(ISNUMBER(E451),0,1),"") + + + + IFERROR(YEAR(C451),"") + + + + + + + + + + IFERROR(SUM(I451:K451),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C451<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C451,1),"") + + + + IFERROR(IF(ISNUMBER(E452),0,1),"") + + + + IFERROR(YEAR(C452),"") + + + + + + + + + + IFERROR(SUM(I452:K452),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C452<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C452,1),"") + + + + IFERROR(IF(ISNUMBER(E453),0,1),"") + + + + IFERROR(YEAR(C453),"") + + + + + + + + + + IFERROR(SUM(I453:K453),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C453<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C453,1),"") + + + + IFERROR(IF(ISNUMBER(E454),0,1),"") + + + + IFERROR(YEAR(C454),"") + + + + + + + + + + IFERROR(SUM(I454:K454),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C454<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C454,1),"") + + + + IFERROR(IF(ISNUMBER(E455),0,1),"") + + + + IFERROR(YEAR(C455),"") + + + + + + + + + + IFERROR(SUM(I455:K455),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C455<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C455,1),"") + + + + IFERROR(IF(ISNUMBER(E456),0,1),"") + + + + IFERROR(YEAR(C456),"") + + + + + + + + + + IFERROR(SUM(I456:K456),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C456<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C456,1),"") + + + + IFERROR(IF(ISNUMBER(E457),0,1),"") + + + + IFERROR(YEAR(C457),"") + + + + + + + + + + IFERROR(SUM(I457:K457),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C457<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C457,1),"") + + + + IFERROR(IF(ISNUMBER(E458),0,1),"") + + + + IFERROR(YEAR(C458),"") + + + + + + + + + + IFERROR(SUM(I458:K458),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C458<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C458,1),"") + + + + IFERROR(IF(ISNUMBER(E459),0,1),"") + + + + IFERROR(YEAR(C459),"") + + + + + + + + + + IFERROR(SUM(I459:K459),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C459<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C459,1),"") + + + + IFERROR(IF(ISNUMBER(E460),0,1),"") + + + + IFERROR(YEAR(C460),"") + + + + + + + + + + IFERROR(SUM(I460:K460),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C460<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C460,1),"") + + + + IFERROR(IF(ISNUMBER(E461),0,1),"") + + + + IFERROR(YEAR(C461),"") + + + + + + + + + + IFERROR(SUM(I461:K461),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C461<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C461,1),"") + + + + IFERROR(IF(ISNUMBER(E462),0,1),"") + + + + IFERROR(YEAR(C462),"") + + + + + + + + + + IFERROR(SUM(I462:K462),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C462<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C462,1),"") + + + + IFERROR(IF(ISNUMBER(E463),0,1),"") + + + + IFERROR(YEAR(C463),"") + + + + + + + + + + IFERROR(SUM(I463:K463),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C463<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C463,1),"") + + + + IFERROR(IF(ISNUMBER(E464),0,1),"") + + + + IFERROR(YEAR(C464),"") + + + + + + + + + + IFERROR(SUM(I464:K464),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C464<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C464,1),"") + + + + IFERROR(IF(ISNUMBER(E465),0,1),"") + + + + IFERROR(YEAR(C465),"") + + + + + + + + + + IFERROR(SUM(I465:K465),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C465<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C465,1),"") + + + + IFERROR(IF(ISNUMBER(E466),0,1),"") + + + + IFERROR(YEAR(C466),"") + + + + + + + + + + IFERROR(SUM(I466:K466),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C466<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C466,1),"") + + + + IFERROR(IF(ISNUMBER(E467),0,1),"") + + + + IFERROR(YEAR(C467),"") + + + + + + + + + + IFERROR(SUM(I467:K467),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C467<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C467,1),"") + + + + IFERROR(IF(ISNUMBER(E468),0,1),"") + + + + IFERROR(YEAR(C468),"") + + + + + + + + + + IFERROR(SUM(I468:K468),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C468<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C468,1),"") + + + + IFERROR(IF(ISNUMBER(E469),0,1),"") + + + + IFERROR(YEAR(C469),"") + + + + + + + + + + IFERROR(SUM(I469:K469),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C469<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C469,1),"") + + + + IFERROR(IF(ISNUMBER(E470),0,1),"") + + + + IFERROR(YEAR(C470),"") + + + + + + + + + + IFERROR(SUM(I470:K470),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C470<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C470,1),"") + + + + IFERROR(IF(ISNUMBER(E471),0,1),"") + + + + IFERROR(YEAR(C471),"") + + + + + + + + + + IFERROR(SUM(I471:K471),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C471<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C471,1),"") + + + + IFERROR(IF(ISNUMBER(E472),0,1),"") + + + + IFERROR(YEAR(C472),"") + + + + + + + + + + IFERROR(SUM(I472:K472),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C472<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C472,1),"") + + + + IFERROR(IF(ISNUMBER(E473),0,1),"") + + + + IFERROR(YEAR(C473),"") + + + + + + + + + + IFERROR(SUM(I473:K473),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C473<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C473,1),"") + + + + IFERROR(IF(ISNUMBER(E474),0,1),"") + + + + IFERROR(YEAR(C474),"") + + + + + + + + + + IFERROR(SUM(I474:K474),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C474<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C474,1),"") + + + + IFERROR(IF(ISNUMBER(E475),0,1),"") + + + + IFERROR(YEAR(C475),"") + + + + + + + + + + IFERROR(SUM(I475:K475),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C475<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C475,1),"") + + + + IFERROR(IF(ISNUMBER(E476),0,1),"") + + + + IFERROR(YEAR(C476),"") + + + + + + + + + + IFERROR(SUM(I476:K476),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C476<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C476,1),"") + + + + IFERROR(IF(ISNUMBER(E477),0,1),"") + + + + IFERROR(YEAR(C477),"") + + + + + + + + + + IFERROR(SUM(I477:K477),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IF(C477<EDATE(Variables!B$31,Variables!B$32-1),EDATE(C477,1),"") + + + + IFERROR(IF(ISNUMBER(E478),0,1),"") + + + + IFERROR(YEAR(C478),"") + + + + + + + + + + IFERROR(SUM(I478:K478),"") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $D15=1 + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + Variable + + + + + Value + + + + + + + Name + + + + + Test5 + + + + + + + Data asset valuation coefficient + + + + 10.5 + + + + + + Potential Reach In-Store M1 + + + + W41 + + + + + + + Potential Reach On-Site M1 + + + + E47 + + + + + + + Potential Reach Off-Site M1 + + + + E56 + + + + + + + Media Reach Coefficient + + + + 1.005 + + + + + + Unique Impressions In-Store + + + + V41 + + + + + + + Unique Impressions On-Site + + + + G47 + + + + + + + Unique Impressions Off-Site + + + + G56 + + + + + + + Potential Media Value In-Store + + + + 16 + + + + + + Potential Media Value On-Site + + + + 12 + + + + + + Potential Media Value Off-Site + + + + 8 + + + + + + Velocity M1 + + + + 0.075 + + + + + + Velocity Coefficient + + + + 0.005 + + + + + + Commisions to Media Agency ( Rebates ) + + + + 0.1 + + + + + + Cost of Sales + + + + 0.075 + + + + + + Cost of Campaign Management + + + + 0.05 + + + + + + Cost of Platform + + + + 0.1 + + + + + + Software Setup + + + + 20000 + + + + + + Cloud and Processing + + + + 2500 + + + + + + Cloud and Processing Coefficient + + + + 1.035 + + + + + + FTE Commercial Value + + + + 0 + + + + + + FTE Commercial Coefficient + + + + 0.5 + + + + + + + FTE Marketing Value + + + + 0 + + + + + + + FTE Marketing Coefficient + + + + 0.25 + + + + + + FTE IT Value + + + + 0 + + + + + + FTE IT Coefficient + + + + 0.5 + + + + + + Media Infrastructure Value + + + + 0 + + + + + + Media Infrastructure Coefficient + + + + 0 + + + + + + Start date + + + + + 2025-09-07 + + + + + + + End date + + + + 36 + + + + + + + + + + + + + + + In-store + + + + + Monthly Open Days + + + + + Monthly Transactions + + + + + Monthly Foot Trafic + + + + + Dwell time + + + + + Add duration + + + + + Monthly Frequency + + + + + Stores Number + + + + + If screens/no of screens + + + + + Number of screens + + + + + Stores with screens + + + + + Capture rate screen + + + + + Paid vs self screen + + + + + Stores with radio + + + + + If radio(1/0) + + + + + Capture rate radio + + + + + Paid vs self radio + + + + + Visitor vs customer coefficient + + + + + Monthly Screen payd impressions per unique reach + + + + + Monthly Radio impressions per unique reach + + + + + Monthly total impressions per unique reach + + + + + Total impressions / unique reach / visit + + + + + Monthly Potential Reach + + + + + Channel + + + + + + + Convenience + + + + 30 + + + 180000 + + + + 5 + + + 15 + + + 8 + + + 120 + + + 1 + + + 60 + + + 50 + + + 0.1 + + + 0.4 + + + 80 + + + 1 + + + Table6[[#This Row],[If radio(1/0)]]/5 + + + + 0.12 + + + 1.2 + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate screen]]*Table6[[#This Row],[ Paid vs self screen]]))*Table6[[#This Row],[If screens/no of screens]]*Table6[[#This Row],[Number of screens]],0) + + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate radio]]*Table6[[#This Row],[ Paid vs self radio]])),0) + + + + ROUNDUP(Table6[[#This Row],[Monthly Screen payd impressions per unique reach]]+Table6[[#This Row],[Monthly Radio impressions per unique reach ]],0) + + + + ROUNDUP(Table6[[#This Row],[Monthly total impressions per unique reach ]]/Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Channel ]],0) + + + + ROUNDUP((Table6[[#This Row],[Monthly Transactions]]/Table6[[#This Row],[Monthly Frequency]])*Table6[[#This Row],[Visitor vs customer coefficient]], 0) + + + + IF(Table6[[#This Row],[Stores Number]]=0,0,1) + + + + + + + Minimarket + + + + + 0 + + + + 5 + + + 15 + + + 8 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0.1 + + + 0.4 + + + 0 + + + 0 + + + Table6[[#This Row],[If radio(1/0)]]/5 + + + + 0.12 + + + 1.2 + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate screen]]*Table6[[#This Row],[ Paid vs self screen]]))*Table6[[#This Row],[If screens/no of screens]]*Table6[[#This Row],[Number of screens]],0) + + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate radio]]*Table6[[#This Row],[ Paid vs self radio]])),0) + + + + ROUNDUP(Table6[[#This Row],[Monthly Screen payd impressions per unique reach]]+Table6[[#This Row],[Monthly Radio impressions per unique reach ]],0) + + + + ROUNDUP(Table6[[#This Row],[Monthly total impressions per unique reach ]]/Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Channel ]],0) + + + + ROUNDUP((Table6[[#This Row],[Monthly Transactions]]/Table6[[#This Row],[Monthly Frequency]])*Table6[[#This Row],[Visitor vs customer coefficient]], 0) + + + + IF(Table6[[#This Row],[Stores Number]]=0,0,1) + + + + + + + Supermarket + + + + + 220000 + + + + 15 + + + 15 + + + 4 + + + 35 + + + 1 + + + 140 + + + 70 + + + 0.1 + + + 0.4 + + + 90 + + + 1 + + + Table6[[#This Row],[If radio(1/0)]]/5 + + + + 0.12 + + + 1.2 + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate screen]]*Table6[[#This Row],[ Paid vs self screen]]))*Table6[[#This Row],[If screens/no of screens]]*Table6[[#This Row],[Number of screens]],0) + + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate radio]]*Table6[[#This Row],[ Paid vs self radio]])),0) + + + + ROUNDUP(Table6[[#This Row],[Monthly Screen payd impressions per unique reach]]+Table6[[#This Row],[Monthly Radio impressions per unique reach ]],0) + + + + ROUNDUP(Table6[[#This Row],[Monthly total impressions per unique reach ]]/Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Channel ]],0) + + + + ROUNDUP((Table6[[#This Row],[Monthly Transactions]]/Table6[[#This Row],[Monthly Frequency]])*Table6[[#This Row],[Visitor vs customer coefficient]], 0) + + + + IF(Table6[[#This Row],[Stores Number]]=0,0,1) + + + + + + + Hipermarket + + + + + 300000 + + + + 30 + + + 15 + + + 2 + + + 12 + + + 1 + + + 240 + + + 90 + + + 0.1 + + + 0.4 + + + 100 + + + 1 + + + Table6[[#This Row],[If radio(1/0)]]/5 + + + + 0.12 + + + 1.2 + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate screen]]*Table6[[#This Row],[ Paid vs self screen]]))*Table6[[#This Row],[If screens/no of screens]]*Table6[[#This Row],[Number of screens]],0) + + + + ROUNDUP(((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate radio]]*Table6[[#This Row],[ Paid vs self radio]])),0) + + + + ROUNDUP(Table6[[#This Row],[Monthly Screen payd impressions per unique reach]]+Table6[[#This Row],[Monthly Radio impressions per unique reach ]],0) + + + + ROUNDUP(Table6[[#This Row],[Monthly total impressions per unique reach ]]/Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Channel ]],0) + + + + ROUNDUP((Table6[[#This Row],[Monthly Transactions]]/Table6[[#This Row],[Monthly Frequency]])*Table6[[#This Row],[Visitor vs customer coefficient]], 0) + + + + IF(Table6[[#This Row],[Stores Number]]=0,0,1) + + + + + + + Total + + + + + + + + + + SUM(H37:H40) + + + + + 4 + + + + + + + + + + + + + + ROUNDUP(V37*H37/Table6[[#This Row],[Stores Number]]+V39*H39/Table6[[#This Row],[Stores Number]]+V40*H40/Table6[[#This Row],[Stores Number]],0) + + + + SUM(W37:W40) + + + + + + + + + On-site + + + + 450000 + + + + Monthly Frequency + + + + + Bounce Rate + + + + + Monthly Potential Reach = Unique Users + + + + + Average impressions / channel / visit + + + + + Monthly Impressions per Unique Reach + + + + + Channel + + + + + + + Website + + + + 120000 + + + 2 + + + 0.45 + + + ROUNDUP(Table8[[#This Row],[Monthly Visits]]*(1-Table8[[#This Row],[ Bounce Rate]])/Table8[[#This Row],[Monthly Frequency]],0) + + + + 1 + + + Table8[[#This Row],[Average impressions / channel / visit]]*Table8[[#This Row],[Monthly Frequency]]*Table8[[#This Row],[Channel ]] + + + + IF(Table8[[#This Row],[Monthly Visits]]=0,0,1) + + + + + + + Mobile App + + + + 180000 + + + 4 + + + 0.5 + + + ROUNDUP(Table8[[#This Row],[Monthly Visits]]*(1-Table8[[#This Row],[ Bounce Rate]]),0) + + + + 3 + + + Table8[[#This Row],[Average impressions / channel / visit]]*Table8[[#This Row],[Monthly Frequency]]*Table8[[#This Row],[Channel ]] + + + + IF(Table8[[#This Row],[Monthly Visits]]=0,0,1) + + + + + + + Loyalty Program + + + + 0 + + + 4 + + + 0.35 + + + ROUNDUP(Table8[[#This Row],[Monthly Visits]]*(1-Table8[[#This Row],[ Bounce Rate]]),0) + + + + 2 + + + Table8[[#This Row],[Average impressions / channel / visit]]*Table8[[#This Row],[Monthly Frequency]]*Table8[[#This Row],[Channel ]] + + + + IF(Table8[[#This Row],[Monthly Visits]]=0,0,1) + + + + + + + Total + + + + + + + SUM(E44:E46) + + + + + SUM(G44:G46) + + + + + + + + + Off-site + + + + 150000 + + + + Monthly Frequency + + + + + Bounce Rate + + + + + Monthly Potential Reach = Unique Users + + + + + Average impressions / channel / visit + + + + + Monthly Impressions per Unique Reach + + + + + Channel + + + + + + + Facebook Business Page + + + + 120000 + + + 2 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 3 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + Instagram Bussines Page + + + + 5000000 + + + 2 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 2 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + Google Bussines Profile + + + + 85000 + + + 2 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 1 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + Email + + + + 40000 + + + 2 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 1 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + SMS + + + + 25000 + + + 2 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 1 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + WhatsApp + + + + 0 + + + 4 + + + 0.75 + + + ROUNDUP(Table12[[#This Row],[Monthly followers / customer data base]]*(1-Table12[[#This Row],[ Bounce Rate]]),0) + + + + 4 + + + Table12[[#This Row],[Monthly Frequency]]*Table12[[#This Row],[Average impressions / channel / visit]]*Table12[[#This Row],[Channel]] + + + + IF(Table12[[#This Row],[Monthly followers / customer data base]]=0,0,1) + + + + + + + Total + + + + SUM(B50:B55) + + + + + + ROUNDUP(SUM(E50:E55),0) + + + + + SUM(G50:G55) + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Share of Media Impressions over 3 years + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "",'Retail Media Investment Case'!G9) + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "",'Retail Media Investment Case'!H9) + + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "",'Retail Media Investment Case'!I9) + + + + + IF(LEN('Retail Media Investment Case'!J9)=0,"",'Retail Media Investment Case'!J9) + + + + + IF(LEN('Retail Media Investment Case'!K9)=0,"",'Retail Media Investment Case'!K9) + + + + + + + + + + In-Store + + + + SUM(IF(LEN('Retail Media Investment Case'!G9)=0,0,'2025 – Forecast {store_name}'!E5)+IF(LEN('Retail Media Investment Case'!H9)=0,0,'2026 – Forecast {store_name}'!E5)+IF(LEN('Retail Media Investment Case'!I9)=0,0,'2027 – Forecast {store_name}'!E5)+IF(LEN('Retail Media Investment Case'!J9)=0,0,'2028 – Forecast {store_name}'!E5)+IF(LEN('Retail Media Investment Case'!K9)=0,0,'2029 – Forecast {store_name}'!E5)) + + + + IF(ISNUMBER(H2),"In-Store","") + + + + IF(ISNUMBER(H2),'2025 – Forecast {store_name}'!E5,"") + + + + + In-Store + + + + '2026 – Forecast {store_name}'!E5 + + + + + In-Store + + + + '2027 – Forecast {store_name}'!E5 + + + + + In-Store + + + + '2028 – Forecast {store_name}'!E5 + + + + + In-Store + + + + '2029 – Forecast {store_name}'!$E$5 + + + + + + + On-Site + + + + SUM( + IF(LEN('Retail Media Investment Case'!G9)=0,0,'2025 – Forecast {store_name}'!E6) + + IF(LEN('Retail Media Investment Case'!H9)=0,0,'2026 – Forecast {store_name}'!E6) + + IF(LEN('Retail Media Investment Case'!I9)=0,0,'2027 – Forecast {store_name}'!E6) + + IF(LEN('Retail Media Investment Case'!J9)=0,0,'2028 – Forecast {store_name}'!E6) + + IF(LEN('Retail Media Investment Case'!K9)=0,0,'2029 – Forecast {store_name}'!E6) +) + + + + IF(ISNUMBER(H2),"On-Site","") + + + + IF(ISNUMBER(H2),'2025 – Forecast {store_name}'!E6,"") + + + + + On-Site + + + + '2026 – Forecast {store_name}'!E6 + + + + + On-Site + + + + '2027 – Forecast {store_name}'!E6 + + + + + On-Site + + + + '2028 – Forecast {store_name}'!E6 + + + + + On-Site + + + + '2029 – Forecast {store_name}'!E6 + + + + + + + Off-Site + + + + SUM( + IF(LEN('Retail Media Investment Case'!G9)=0,0,'2025 – Forecast {store_name}'!E7) + + IF(LEN('Retail Media Investment Case'!H9)=0,0,'2026 – Forecast {store_name}'!E7) + + IF(LEN('Retail Media Investment Case'!I9)=0,0,'2027 – Forecast {store_name}'!E7) + + IF(LEN('Retail Media Investment Case'!J9)=0,0,'2028 – Forecast {store_name}'!E7) + + IF(LEN('Retail Media Investment Case'!K9)=0,0,'2029 – Forecast {store_name}'!E7) +) + + + + IF(ISNUMBER(H2),"Off-Site","") + + + + IF(ISNUMBER(H2),'2025 – Forecast {store_name}'!E7,"") + + + + + Off-Site + + + + '2026 – Forecast {store_name}'!E7 + + + + + Off-Site + + + + '2027 – Forecast {store_name}'!E7 + + + + + Off-Site + + + + '2028 – Forecast {store_name}'!E7 + + + + + Off-Site + + + + '2029 – Forecast {store_name}'!E7 + + + + + + ISTEXT(U4) + + + + ISTEXT(AA4) + + + + + + + Share of Media Revenue Total Period + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "",'Retail Media Investment Case'!G9) + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "",'Retail Media Investment Case'!H9) + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "",'Retail Media Investment Case'!I9) + + + + + IF(LEN('Retail Media Investment Case'!J9)=0, "",'Retail Media Investment Case'!J9) + + + + + IF(LEN('Retail Media Investment Case'!K9)=0,"",'Retail Media Investment Case'!K9) + + + + + + + + + + In-Store + + + + SUM( + IF(LEN('Retail Media Investment Case'!G9)=0,0,SUM('2025 – Forecast {store_name}'!C10:E10)) + + IF(LEN('Retail Media Investment Case'!H9)=0,0,SUM('2026 – Forecast {store_name}'!C10:E10)) + + IF(LEN('Retail Media Investment Case'!I9)=0,0,SUM('2027 – Forecast {store_name}'!C10:E10)) + + IF(LEN('Retail Media Investment Case'!J9)=0,0,SUM('2028 – Forecast {store_name}'!C10:E10)) + + IF(LEN('Retail Media Investment Case'!K9)=0,0,SUM('2029 – Forecast {store_name}'!C10:E10)) +) + + + + + In-Store + + + + '2025 – Forecast {store_name}'!C10:E10 + + + + 0 + + + 0 + + + + In-Store + + + + '2026 – Forecast {store_name}'!C10:E10 + + + + 0 + + + 0 + + + + In-Store + + + + '2027 – Forecast {store_name}'!C10:E10 + + + + 0 + + + 0 + + + + In-Store + + + + '2028 – Forecast {store_name}'!C10:E10 + + + + 0 + + + 0 + + + + In-Store + + + + '2029 – Forecast {store_name}'!C10:E10 + + + + 0 + + + 0 + + + + + + + On-Site + + + + SUM( + IF(LEN('Retail Media Investment Case'!G9)=0,0,SUM('2025 – Forecast {store_name}'!C11:E11)) + + IF(LEN('Retail Media Investment Case'!H9)=0,0,SUM('2026 – Forecast {store_name}'!C11:E11)) + + IF(LEN('Retail Media Investment Case'!I9)=0,0,SUM('2027 – Forecast {store_name}'!C11:E11)) + + IF(LEN('Retail Media Investment Case'!J9)=0,0,SUM('2028 – Forecast {store_name}'!C11:E11)) + + IF(LEN('Retail Media Investment Case'!K9)=0,0,SUM('2029 – Forecast {store_name}'!C11:E11)) +) + + + + + On-Site + + + + '2025 – Forecast {store_name}'!C11:E11 + + + + 0 + + + 0 + + + + On-Site + + + + '2026 – Forecast {store_name}'!C11:E11 + + + + 0 + + + 0 + + + + On-Site + + + + '2027 – Forecast {store_name}'!C11:E11 + + + + 0 + + + 0 + + + + On-Site + + + + '2028 – Forecast {store_name}'!C11:E11 + + + + 0 + + + 0 + + + + On-Site + + + + '2029 – Forecast {store_name}'!C11:E11 + + + + 0 + + + 0 + + + + + + + Off-Site + + + + SUM( + IF(LEN('Retail Media Investment Case'!G9)=0,0,SUM('2025 – Forecast {store_name}'!C12:E12)) + + IF(LEN('Retail Media Investment Case'!H9)=0,0,SUM('2026 – Forecast {store_name}'!C12:E12)) + + IF(LEN('Retail Media Investment Case'!I9)=0,0,SUM('2027 – Forecast {store_name}'!C12:E12)) + + IF(LEN('Retail Media Investment Case'!J9)=0,0,SUM('2028 – Forecast {store_name}'!C12:E12)) + + IF(LEN('Retail Media Investment Case'!K9)=0,0,SUM('2029 – Forecast {store_name}'!C12:E12)) +) + + + + + Off-Site + + + + '2025 – Forecast {store_name}'!C12:E12 + + + + 0 + + + 0 + + + + Off-Site + + + + '2026 – Forecast {store_name}'!C12:E12 + + + + 0 + + + 0 + + + + Off-Site + + + + '2027 – Forecast {store_name}'!C12:E12 + + + + 0 + + + 0 + + + + Off-Site + + + + '2028 – Forecast {store_name}'!C12:E12 + + + + 0 + + + 0 + + + + Off-Site + + + + '2029 – Forecast {store_name}'!C12:E12 + + + + 0 + + + 0 + + + + + + IF('Retail Media Investment Case'!C15,'Retail Media Investment Case'!C15,"") + + + + IF(C45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(C45,1),"") + + + + IF(D45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(D45,1),"") + + + + IF(E45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(E45,1),"") + + + + IF(F45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(F45,1),"") + + + + IF(G45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(G45,1),"") + + + + IF(H45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(H45,1),"") + + + + IF(I45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(I45,1),"") + + + + IF(J45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(J45,1),"") + + + + IF(K45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(K45,1),"") + + + + IF(L45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(L45,1),"") + + + + IF(M45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(M45,1),"") + + + + IF(N45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(N45,1),"") + + + + IF(O45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(O45,1),"") + + + + IF(P45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(P45,1),"") + + + + IF(Q45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(Q45,1),"") + + + + IF(R45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(R45,1),"") + + + + IF(S45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(S45,1),"") + + + + IF(T45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(T45,1),"") + + + + IF(U45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(U45,1),"") + + + + IF(V45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(V45,1),"") + + + + IF(W45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(W45,1),"") + + + + IF(X45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(X45,1),"") + + + + IF(Y45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(Y45,1),"") + + + + IF(Z45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(Z45,1),"") + + + + IF(AA45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AA45,1),"") + + + + IF(AB45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AB45,1),"") + + + + IF(AC45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AC45,1),"") + + + + IF(AD45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AD45,1),"") + + + + IF(AE45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AE45,1),"") + + + + IF(AF45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AF45,1),"") + + + + IF(AG45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AG45,1),"") + + + + IF(AH45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AH45,1),"") + + + + IF(AI45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AI45,1),"") + + + + IF(AJ45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AJ45,1),"") + + + + IF(AK45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AK45,1),"") + + + + IF(AL45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AL45,1),"") + + + + IF(AM45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AM45,1),"") + + + + IF(AN45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AN45,1),"") + + + + IF(AO45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AO45,1),"") + + + + IF(AP45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AP45,1),"") + + + + IF(AQ45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AQ45,1),"") + + + + IF(AR45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AR45,1),"") + + + + IF(AS45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AS45,1),"") + + + + IF(AT45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AT45,1),"") + + + + IF(AU45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AU45,1),"") + + + + IF(AV45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AV45,1),"") + + + + IF(AW45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AW45,1),"") + + + + IF(AX45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AX45,1),"") + + + + IF(AY45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AY45,1),"") + + + + IF(AZ45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(AZ45,1),"") + + + + IF(BA45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BA45,1),"") + + + + IF(BB45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BB45,1),"") + + + + IF(BC45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BC45,1),"") + + + + IF(BD45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BD45,1),"") + + + + IF(BE45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BE45,1),"") + + + + IF(BF45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BF45,1),"") + + + + IF(BG45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BG45,1),"") + + + + IF(BH45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BH45,1),"") + + + + IF(BI45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BI45,1),"") + + + + IF(BJ45<EDATE(Variables!$B$31,Variables!$B$32-1),EDATE(BJ45,1),"") + + + + IF(BK45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BK45,1),"") + + + + IF(BL45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BL45,1),"") + + + + IF(BM45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BM45,1),"") + + + + IF(BN45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BN45,1),"") + + + + IF(BO45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BO45,1),"") + + + + IF(BP45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BP45,1),"") + + + + IF(BQ45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BQ45,1),"") + + + + IF(BR45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BR45,1),"") + + + + IF(BS45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BS45,1),"") + + + + IF(BT45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BT45,1),"") + + + + IF(BU45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BU45,1),"") + + + + IF(BV45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BV45,1),"") + + + + IF(BW45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BW45,1),"") + + + + IF(BX45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BX45,1),"") + + + + IF(BY45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BY45,1),"") + + + + IF(BZ45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(BZ45,1),"") + + + + IF(CA45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CA45,1),"") + + + + IF(CB45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CB45,1),"") + + + + IF(CC45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CC45,1),"") + + + + IF(CD45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CD45,1),"") + + + + IF(CE45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CE45,1),"") + + + + IF(CF45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CF45,1),"") + + + + IF(CG45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CG45,1),"") + + + + IF(CH45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CH45,1),"") + + + + IF(CI45<EDATE(Variables!$B$31,Variables!$B$32),EDATE(CI45,1),"") + + + + + + + Impressions Potential + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:I)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:J)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:K)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:L)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:M)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:N)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:O)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:P)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:Q)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:R)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:S)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:T)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:U)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:V)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:W)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:X)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:Y)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:Z)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:AZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:BZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:CA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$L:$L, 15 + COLUMNS($I:CB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + + + + Revenue Potential + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:I)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:J)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:K)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:L)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:M)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:N)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:O)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:P)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:Q)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:R)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:S)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:T)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:U)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:V)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:W)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:X)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:Y)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:Z)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:AZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:BZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$R:$R, 15 + COLUMNS($I:CN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + + + + Sales Velocity + + + + IF(LEN(C47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:I)-1, 0)) + + + + IF(LEN(D47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:J)-1, 0)) + + + + IF(LEN(E47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:K)-1, 0)) + + + + IF(LEN(F47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:L)-1, 0)) + + + + IF(LEN(G47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:M)-1, 0)) + + + + IF(LEN(H47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:N)-1, 0)) + + + + IF(LEN(I47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:O)-1, 0)) + + + + IF(LEN(J47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:P)-1, 0)) + + + + IF(LEN(K47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:Q)-1, 0)) + + + + IF(LEN(L47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:R)-1, 0)) + + + + IF(LEN(M47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:S)-1, 0)) + + + + IF(LEN(N47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:T)-1, 0)) + + + + IF(LEN(O47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:U)-1, 0)) + + + + IF(LEN(P47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:V)-1, 0)) + + + + IF(LEN(Q47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:W)-1, 0)) + + + + IF(LEN(R47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:X)-1, 0)) + + + + IF(LEN(S47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:Y)-1, 0)) + + + + IF(LEN(T47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:Z)-1, 0)) + + + + IF(LEN(U47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AA)-1, 0)) + + + + IF(LEN(V47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AB)-1, 0)) + + + + IF(LEN(W47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AC)-1, 0)) + + + + IF(LEN(X47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AD)-1, 0)) + + + + IF(LEN(Y47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AE)-1, 0)) + + + + IF(LEN(Z47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AF)-1, 0)) + + + + IF(LEN(AA47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AG)-1, 0)) + + + + IF(LEN(AB47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AH)-1, 0)) + + + + IF(LEN(AC47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AI)-1, 0)) + + + + IF(LEN(AD47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AJ)-1, 0)) + + + + IF(LEN(AE47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AK)-1, 0)) + + + + IF(LEN(AF47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AL)-1, 0)) + + + + IF(LEN(AG47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AM)-1, 0)) + + + + IF(LEN(AH47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AN)-1, 0)) + + + + IF(LEN(AI47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AO)-1, 0)) + + + + IF(LEN(AJ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AP)-1, 0)) + + + + IF(LEN(AK47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AQ)-1, 0)) + + + + IF(LEN(AL47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AR)-1, 0)) + + + + IF(LEN(AM47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AS)-1, 0)) + + + + IF(LEN(AN47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AT)-1, 0)) + + + + IF(LEN(AO47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AU)-1, 0)) + + + + IF(LEN(AP47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AV)-1, 0)) + + + + IF(LEN(AQ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AW)-1, 0)) + + + + IF(LEN(AR47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AX)-1, 0)) + + + + IF(LEN(AS47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AY)-1, 0)) + + + + IF(LEN(AT47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:AZ)-1, 0)) + + + + IF(LEN(AU47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BA)-1, 0)) + + + + IF(LEN(AV47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BB)-1, 0)) + + + + IF(LEN(AW47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BC)-1, 0)) + + + + IF(LEN(AX47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BD)-1, 0)) + + + + IF(LEN(AY47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BE)-1, 0)) + + + + IF(LEN(AZ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BF)-1, 0)) + + + + IF(LEN(BA47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BG)-1, 0)) + + + + IF(LEN(BB47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BH)-1, 0)) + + + + IF(LEN(BC47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BI)-1, 0)) + + + + IF(LEN(BD47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BJ)-1, 0)) + + + + IF(LEN(BE47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BK)-1, 0)) + + + + IF(LEN(BF47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BL)-1, 0)) + + + + IF(LEN(BG47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BM)-1, 0)) + + + + IF(LEN(BH47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BN)-1, 0)) + + + + IF(LEN(BI47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BO)-1, 0)) + + + + IF(LEN(BJ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BP)-1, 0)) + + + + IF(LEN(BK47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BQ)-1, 0)) + + + + IF(LEN(BL47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BR)-1, 0)) + + + + IF(LEN(BM47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BS)-1, 0)) + + + + IF(LEN(BN47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BT)-1, 0)) + + + + IF(LEN(BO47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BU)-1, 0)) + + + + IF(LEN(BP47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BV)-1, 0)) + + + + IF(LEN(BQ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BW)-1, 0)) + + + + IF(LEN(BR47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BX)-1, 0)) + + + + IF(LEN(BS47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BY)-1, 0)) + + + + IF(LEN(BT47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:BZ)-1, 0)) + + + + IF(LEN(BU47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CA)-1, 0)) + + + + IF(LEN(BV47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CB)-1, 0)) + + + + IF(LEN(BW47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CC)-1, 0)) + + + + IF(LEN(BX47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CD)-1, 0)) + + + + IF(LEN(BY47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CE)-1, 0)) + + + + IF(LEN(BZ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CF)-1, 0)) + + + + IF(LEN(CA47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CG)-1, 0)) + + + + IF(LEN(CB47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CH)-1, 0)) + + + + IF(LEN(CC47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CI)-1, 0)) + + + + IF(LEN(CD47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CJ)-1, 0)) + + + + IF(LEN(CE47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CK)-1, 0)) + + + + IF(LEN(CF47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CL)-1, 0)) + + + + IF(LEN(CG47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CM)-1, 0)) + + + + IF(LEN(CH47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CN)-1, 0)) + + + + IF(LEN(CI47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CO)-1, 0)) + + + + IF(LEN(CJ47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CP)-1, 0)) + + + + IF(LEN(CK47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CQ)-1, 0)) + + + + IF(LEN(CL47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CR)-1, 0)) + + + + IF(LEN(CM47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CS)-1, 0)) + + + + IF(LEN(CN47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CT)-1, 0)) + + + + IF(LEN(CO47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CU)-1, 0)) + + + + IF(LEN(CP47)=0, "", OFFSET('Retail Media Investment Case'!$U$15, COLUMNS($I:CV)-1, 0)) + + + + + + + + Actual Sales + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:I)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:J)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:K)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:L)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:M)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:N)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:O)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:P)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:Q)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:R)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:S)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:T)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:U)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:V)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:W)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:X)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:Y)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:Z)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:AZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:BZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CL)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CM)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CN)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CO)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CP)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CQ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CR)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CS)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CT)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CU)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CV)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CW)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CX)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CY)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:CZ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DA)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DB)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DC)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DD)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DE)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DF)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DG)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DH)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DI)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DJ)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + _xlfn.LET(_xlpm.v, INDEX('Retail Media Investment Case'!$Y:$Y, 15 + COLUMNS($I:DK)-1), + IF(OR(_xlpm.v="", _xlpm.v=0), "", _xlpm.v)) + + + + + + + ISNUMBER($H$2)=FALSE + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + "Retail Media Forecast " & Variables!B2 & " - 2025" + + + + + + + + + + + + + Total Retail Media Impressions + + + + + + + + + + In-Store + + + + + Digital Screens & Radio + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!I$15:I$1048576) +) + + + + + + + + + + On-Site + + + + + Website & Mobile App + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!J$15:J$1048576) +) + + + + + + + + + + Off-Site + + + + + Social Media & Direct Comms + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!K$15:K$1048576) +) + + + + + + + + + + + + + + + Forecasted Retail Media Sales + + + + + + + + + + In-Store + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!V$15:V$1048576) +) + + + + + + + + + + On-Site + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!W$15:W$1048576) +) + + + + + + + + + + Off-Site + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!X$15:X$1048576) +) + + + + + + + + + + + + Total Forecasted Media Sales + + + + + SUM(C10:E12) + + + + + + + + + Costs (€) + + + + + + + + + + Commisions to Media Agencies + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AB$15:AB$1048576) +) + + + + + + + + + Cost of Sales + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AC$15:AC$1048576) +) + + + + + + + + + Cost of Campaign Management + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AD$15:AD$1048576) +) + + + + + + + + Cost of Platform + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AE$15:AE$1048576) +) + + + + + + + + + Total Operating Cost + + + + + SUM(D16:E19) + + + + + + + + + Software Setup & Integrations + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AI$15:AI$1048576) +) + + + + + + + + + Cloud & Processing Costs + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AJ$15:AJ$1048576) +) + + + + + + + + + FTE Consumption: Commercial + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AK$15:AK$1048576) +) + + + + + + + + + FTE Consumption: Marketing + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AL$15:AL$1048576) +) + + + + + + + + + FTE Consumptions: IT & Digital + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AM$15:AM$1048576) +) + + + + + + + + + CAPEX Investments + + + + + IF(LEN('Retail Media Investment Case'!G9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!G9, + 'Retail Media Investment Case'!AN$15:AN$1048576) +) + + + + + + + + + Total Cost Including Setup & Cloud + + + + + SUM(D20:E26) + + + + + + + + + + + + + + + Operating Profit - 2025 + + + + + D13+D20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + "Retail Media Forecast " & Variables!B2 & " - 2026" + + + + + + + + + + + + + Total Retail Media Impressions + + + + + + + + + + In-Store + + + + + Digital Screens & Radio + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!I$15:I$1048576) +) + + + + + + + + + + On-Site + + + + + Website & Mobile App + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!J$15:J$1048576) +) + + + + + + + + + + Off-Site + + + + + Social Media & Direct Comms + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!K$15:K$1048576) +) + + + + + + + + + + + + + + + Forecasted Retail Media Sales + + + + + + + + + + In-Store + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!V$15:V$1048576) +) + + + + + + + + + + On-Site + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!W$15:W$1048576) +) + + + + + + + + + + Off-Site + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!X$15:X$1048576) +) + + + + + + + + + + + + Total Forecasted Media Sales + + + + + SUM(C10:E12) + + + + + + + + + Costs (€) + + + + + + + + + + Commisions to Media Agencies + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AB$15:AB$1048576) +) + + + + + + + + + Cost of Sales + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AC$15:AC$1048576) +) + + + + + + + + + Cost of Campaign Management + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AD$15:AD$1048576) +) + + + + + + + + Cost of Platform + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AE$15:AE$1048576) +) + + + + + + + + + Total Operating Cost + + + + + SUM(D16:E19) + + + + + + + + + Software Setup & Integrations + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AI$15:AI$1048576) +) + + + + + + + + + Cloud & Processing Costs + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AJ$15:AJ$1048576) +) + + + + + + + + + FTE Consumption: Commercial + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AK$15:AK$1048576) +) + + + + + + + + + FTE Consumption: Marketing + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AL$15:AL$1048576) +) + + + + + + + + + FTE Consumptions: IT & Digital + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AM$15:AM$1048576) +) + + + + + + + + + CAPEX Investments + + + + + IF(LEN('Retail Media Investment Case'!H9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!H9, + 'Retail Media Investment Case'!AN$15:AN$1048576) +) + + + + + + + + + Total Cost Including Setup & Cloud + + + + + SUM(D21:E26) + + + + + + + + + + + + + + + Operating Profit - 2026 + + + + + D13+D20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + "Retail Media Forecast " & Variables!B2 & " - 2027" + + + + + + + + + + + + + Total Retail Media Impressions + + + + + + + + + + In-Store + + + + + Digital Screens & Radio + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!I$15:I$1048576) +) + + + + + + + + + + On-Site + + + + + Website & Mobile App + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!J$15:J$1048576) +) + + + + + + + + + + Off-Site + + + + + Social Media & Direct Comms + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!K$15:K$1048576) +) + + + + + + + + + + + + + + + Forecasted Retail Media Sales + + + + + + + + + + In-Store + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!V$15:V$1048576) +) + + + + + + + + + + On-Site + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!W$15:W$1048576) +) + + + + + + + + + + Off-Site + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!X$15:X$1048576) +) + + + + + + + + + + + + Total Forecasted Media Sales + + + + + SUM(C10:E12) + + + + + + + + + Costs (€) + + + + + + + + + + Commisions to Media Agencies + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AB$15:AB$1048576) +) + + + + + + + + + Cost of Sales + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AC$15:AC$1048576) +) + + + + + + + + + Cost of Campaign Management + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AD$15:AD$1048576) +) + + + + + + + + Cost of Platform + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AE$15:AE$1048576) +) + + + + + + + + + Total Operating Cost + + + + + SUM(D16:E19) + + + + + + + + + Software Setup & Integrations + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AI$15:AI$1048576) +) + + + + + + + + + Cloud & Processing Costs + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AJ$15:AJ$1048576) +) + + + + + + + + + FTE Consumption: Commercial + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AK$15:AK$1048576) +) + + + + + + + + + FTE Consumption: Marketing + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AL$15:AL$1048576) +) + + + + + + + + + FTE Consumptions: IT & Digital + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AM$15:AM$1048576) +) + + + + + + + + + CAPEX Investments + + + + + IF(LEN('Retail Media Investment Case'!I9)=0, "", + SUMIF('Retail Media Investment Case'!E$15:E$1048576, + 'Retail Media Investment Case'!I9, + 'Retail Media Investment Case'!AN$15:AN$1048576) +) + + + + + + + + + Total Cost Including Setup & Cloud + + + + + SUM(D21:E26) + + + + + + + + + + + + + + + Operating Profit - 2026 + + + + + D13+D20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + 1 + 22678 + 28 + 12701 + + + 5 + 55824 + 42 + 16934 + + + + + + + + + + + + + + + + + + 6 + 43296 + 7 + 178215 + + + 12 + 43295 + 20 + 175382 + + + + + + + + + + + + + + + + + + 12 + 1301006 + 8 + 16821 + + + 17 + 1281442 + 21 + 0 + + + + + + + + + + + + + + + + + + 19 + 14989 + 8 + 11001 + + + 23 + 1292884 + 20 + 171622 + + + + + + + + + + + + + + + + + + 6 + 19539 + 28 + 16902 + + + 12 + 0 + 42 + 0 + + + + + + + + + + + + + + + + + + 12 + 1292881 + 28 + 7437 + + + 18 + 0 + 42 + 0 + + + + + + + + + + + + + + + + + + 19 + 8261 + 28 + 14929 + + + 23 + 1292884 + 42 + 11442 + + + + + + + + + + + + + + + + + + 0 + 442685 + 7 + 152399 + + + 4 + 851477 + 20 + 160867 + + + + + + + + + + + + + + + + + + 2 + 25977 + 51 + 83127 + + + 38 + 8659 + 75 + 129887 + + + + + + + + + + + + + + + + + + 25 + 1198 + 8 + 12461 + + + 30 + 0 + 20 + 171622 + + + + + + + + + + + + + + + + + + 25 + 8261 + 28 + 3488 + + + 30 + 11441 + 41 + 171622 + + + + + + + + + + + + + + + + + + 30 + 1294080 + 8 + 12461 + + + 36 + 0 + 20 + 171622 + + + + + + + + + + + + + + + + + + 31 + 8261 + 28 + 3487 + + + 36 + 11441 + 42 + 22883 + + + + + + + + + + + + + + + + +
+
+ + + + 18 + 52939 + 14 + 0 + + + 19 + 301 + 94 + 18143 + + + + + + + + + + + + + + + + + + 12 + 47376 + 14 + 5339 + + + 13 + 1408 + 94 + 1 + + + + + + + + + + + + + + + + + + 25 + 33649 + 14 + 0 + + + 25 + 2182091 + 94 + 36286 + + + + + + + + + + + + + + + + + + 41 + 23812 + 14 + 0 + + + 42 + 9646 + 94 + 0 + + + + + + + + + + + + + + + + + + 32 + 48228 + 14 + 0 + + + 33 + 0 + 94 + 127000 + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + Table6[[#This Row],[If radio(1/0)]]/5 + + + + + (((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate screen]]*Table6[[#This Row],[ Paid vs self screen]]))*Table6[[#This Row],[If screens/no of screens]]) + + + (((Table6[[#This Row],[Dwell time]]*(60/Table6[[#This Row],[Add duration]])*Table6[[#This Row],[Monthly Frequency]]*Table6[[#This Row],[Capture rate radio]]*Table6[[#This Row],[ Paid vs self radio]]))) + + + Table6[[#This Row],[Monthly Screen payd impressions per unique reach]]+Table6[[#This Row],[Monthly Radio impressions per unique reach ]] + + + Table6[[#This Row],[Monthly total impressions per unique reach ]]/Table6[[#This Row],[Monthly Frequency]] + + + (Table6[[#This Row],[Monthly Transactions]]/Table6[[#This Row],[Monthly Frequency]])*Table6[[#This Row],[Visitor vs customer coefficient]] + + + + + +
+
+ + + + + #REF! + + + + + Table12[[#This Row],[Monthly followers / customer data base]]*D50 + + + + + + + +
+
+ + + + + + + + Table8[[#This Row],[Monthly Visits]]*(1-Table8[[#This Row],[ Bounce Rate]]) + + + + + + + +
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$N$4:$N$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$O$4:$O$6 + + 0,,\ "Mn" + + + 332667630.5492479 + + + 88714.96528904054 + + + 18926906.97309144 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Retail Media Investment Case'!$AO$15:$AO$94 + + _("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"??_);_(@_) + + + -4862.823412500002 + + + 16319.5533018 + + + 17511.12535382831 + + + 18711.87638754436 + + + 19921.78689721372 + + + 21140.83409985134 + + + 22368.9918030915 + + + 23606.23026832387 + + + 24852.5160689292 + + + 26107.81194344264 + + + 27372.07664346633 + + + 28645.26477614685 + + + 29927.32664102668 + + + 31218.20806107211 + + + 32517.85020767294 + + + 33826.18941940247 + + + 35143.15701431854 + + + 36468.67909557908 + + + 37802.67635013728 + + + 39145.06384027341 + + + 40495.75078771223 + + + 41854.64035006539 + + + 43221.62938932958 + + + 44596.60823216186 + + + 45979.46042164339 + + + 47370.06246023304 + + + 48768.28354360181 + + + 50173.98528502815 + + + 51587.02143002304 + + + 53007.23756084216 + + + 54434.47079053032 + + + 55868.54944613134 + + + 57309.29274068303 + + + 58756.51043360426 + + + 60210.00247906697 + + + 61669.55866193181 + + + 63134.95822081148 + + + 64605.96945781024 + + + 66082.34933447264 + + + 67563.84305345823 + + + 69050.18362544099 + + + 70541.09142071687 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + 7 + + + 8 + + + 9 + + + 10 + + + 11 + + + 12 + + + 13 + + + 14 + + + 15 + + + 16 + + + 17 + + + 18 + + + 19 + + + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$Z$4:$Z$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$AA$4:$AA$6 + + 0,,\ "Mn" + + + 184679730.870653 + + + 49249.92517825949 + + + 10507232.33947751 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$Z$4:$Z$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$AG$4:$AG$6 + + 0,,\ "Mn" + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$G$25:$G$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$H$25:$H$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 515369.7077270239 + + + 103.0781210735892 + + + 14660.8110004479 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$Z$25:$Z$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$AA$25:$AA$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 790644.1676551553 + + + 158.1352454709123 + + + 22491.59105939991 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + Graphics!$B$47:$B$47 + + + + Revenue Potential + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$C$45:$BJ$45 + + + + Jan.25 + + + Feb.25 + + + Mar.25 + + + Apr.25 + + + May.25 + + + Jun.25 + + + Jul.25 + + + Aug.25 + + + Sep.25 + + + Oct.25 + + + Nov.25 + + + Dec.25 + + + Jan.26 + + + Feb.26 + + + Mar.26 + + + Apr.26 + + + May.26 + + + Jun.26 + + + Jul.26 + + + Aug.26 + + + Sep.26 + + + Oct.26 + + + Nov.26 + + + Dec.26 + + + Jan.27 + + + Feb.27 + + + Mar.27 + + + Apr.27 + + + May.27 + + + Jun.27 + + + Jul.27 + + + Aug.27 + + + Sep.27 + + + Oct.27 + + + Nov.27 + + + Dec.27 + + + Jan.28 + + + Feb.28 + + + Mar.28 + + + Apr.28 + + + May.28 + + + Jun.28 + + + + + + + Graphics!$C$47:$BJ$47 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 418066.408 + + + 420156.7400399999 + + + 422257.5237401999 + + + 424368.8113589009 + + + 426490.6554156953 + + + 428623.1086927738 + + + 430766.2242362376 + + + 432920.0553574188 + + + 435084.6556342058 + + + 437260.0789123769 + + + 439446.3793069387 + + + 441643.6112034734 + + + 443851.8292594906 + + + 446071.088405788 + + + 448301.4438478169 + + + 450542.951067056 + + + 452795.6658223912 + + + 455059.644151503 + + + 457334.9423722606 + + + 459621.6170841218 + + + 461919.7251695424 + + + 464229.32379539 + + + 466550.4704143669 + + + 468883.2227664387 + + + 471227.6388802708 + + + 473583.7770746721 + + + 475951.6959600455 + + + 478331.4544398457 + + + 480723.1117120448 + + + 483126.7272706049 + + + 485542.3609069579 + + + 487970.0727114927 + + + 490409.9230750501 + + + 492861.9726904253 + + + 495326.2825538774 + + + 497802.9139666467 + + + 500291.9285364799 + + + 502793.3881791623 + + + 505307.3551200581 + + + 507833.8918956583 + + + 510373.0613551366 + + + 512924.9266619122 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Graphics!$B$49:$B$49 + + + + Actual Sales + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$C$45:$BJ$45 + + + + Jan.25 + + + Feb.25 + + + Mar.25 + + + Apr.25 + + + May.25 + + + Jun.25 + + + Jul.25 + + + Aug.25 + + + Sep.25 + + + Oct.25 + + + Nov.25 + + + Dec.25 + + + Jan.26 + + + Feb.26 + + + Mar.26 + + + Apr.26 + + + May.26 + + + Jun.26 + + + Jul.26 + + + Aug.26 + + + Sep.26 + + + Oct.26 + + + Nov.26 + + + Dec.26 + + + Jan.27 + + + Feb.27 + + + Mar.27 + + + Apr.27 + + + May.27 + + + Jun.27 + + + Jul.27 + + + Aug.27 + + + Sep.27 + + + Oct.27 + + + Nov.27 + + + Dec.27 + + + Jan.28 + + + Feb.28 + + + Mar.28 + + + Apr.28 + + + May.28 + + + Jun.28 + + + + + + + Graphics!$C$49:$BJ$49 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 31354.9806 + + + 33612.53920319999 + + + 35891.889517917 + + + 38193.19302230109 + + + 40516.61226449106 + + + 42862.31086927739 + + + 45230.45354480496 + + + 47621.20608931608 + + + 50034.73539793369 + + + 52471.20946948523 + + + 54930.79741336736 + + + 57413.66945645155 + + + 59919.99695003125 + + + 62449.95237681035 + + + 65003.70935793347 + + + 67581.44266005843 + + + 70183.32820247067 + + + 72809.54306424051 + + + 75460.26549142302 + + + 78135.67490430074 + + + 80835.95190466993 + + + 83561.27828317025 + + + 86311.83702665791 + + + 89087.81232562338 + + + 91889.38958165285 + + + 94716.75541493447 + + + 97570.09767180937 + + + 100449.6054323676 + + + 103355.4690180897 + + + 106287.8799995331 + + + 109247.0312040656 + + + 112233.1167236434 + + + 115246.3319226368 + + + 118286.8734457021 + + + 121354.9392257 + + + 124450.7284916617 + + + 127574.4417768025 + + + 130726.2809265823 + + + 133906.4491068154 + + + 137115.1508118278 + + + 140352.5918726626 + + + 143618.9794653355 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Retail Media Investment Case'!$L$15:$L$94 + + #,##0 + + + 26853444 + + + 26987711.22 + + + 27122649.77609999 + + + 27258263.02498049 + + + 27394554.34010539 + + + 27531527.11180592 + + + 27669184.74736494 + + + 27807530.67110176 + + + 27946568.32445727 + + + 28086301.16607956 + + + 28226732.67190995 + + + 28367866.3352695 + + + 28509705.66694584 + + + 28652254.19528057 + + + 28795515.46625696 + + + 28939493.04358825 + + + 29084190.50880619 + + + 29229611.46135021 + + + 29375759.51865696 + + + 29522638.31625025 + + + 29670251.50783149 + + + 29818602.76537064 + + + 29967695.7791975 + + + 30117534.25809348 + + + 30268121.92938394 + + + 30419462.53903086 + + + 30571559.85172601 + + + 30724417.65098464 + + + 30878039.73923956 + + + 31032429.93793575 + + + 31187592.08762543 + + + 31343530.04806355 + + + 31500247.69830387 + + + 31657748.93679538 + + + 31816037.68147936 + + + 31975117.86988675 + + + 32134993.45923618 + + + 32295668.42653236 + + + 32457146.76866502 + + + 32619432.50250834 + + + 32782529.66502088 + + + 32946442.31334599 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$B$4:$B$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$C$4:$C$6 + + 0,,\ "Mn" + + + 1183874621.04792 + + + 315712.7002090275 + + + 67355771.23443852 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$Z$25:$Z$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$AG$25:$AG$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$T$25:$T$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$U$25:$U$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 1259020.821217601 + + + 251.8143746088719 + + + 35815.58253958682 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Retail Media Investment Case'!$Y$15:$Y$94 + + _("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"??_);_(@_) + + + 31354.9806 + + + 33612.53920319999 + + + 35891.889517917 + + + 38193.19302230109 + + + 40516.61226449106 + + + 42862.31086927739 + + + 45230.45354480496 + + + 47621.20608931608 + + + 50034.73539793369 + + + 52471.20946948523 + + + 54930.79741336736 + + + 57413.66945645155 + + + 59919.99695003125 + + + 62449.95237681035 + + + 65003.70935793347 + + + 67581.44266005843 + + + 70183.32820247067 + + + 72809.54306424051 + + + 75460.26549142302 + + + 78135.67490430074 + + + 80835.95190466993 + + + 83561.27828317025 + + + 86311.83702665791 + + + 89087.81232562338 + + + 91889.38958165285 + + + 94716.75541493447 + + + 97570.09767180937 + + + 100449.6054323676 + + + 103355.4690180897 + + + 106287.8799995331 + + + 109247.0312040656 + + + 112233.1167236434 + + + 115246.3319226368 + + + 118286.8734457021 + + + 121354.9392257 + + + 124450.7284916617 + + + 127574.4417768025 + + + 130726.2809265823 + + + 133906.4491068154 + + + 137115.1508118278 + + + 140352.5918726626 + + + 143618.9794653355 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Retail Media Investment Case'!$AF$15:$AF$94 + + _("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"??_);_(@_) + + + 17637.1765875 + + + 18907.0533018 + + + 20189.18785382831 + + + 21483.67107504436 + + + 22790.59439877622 + + + 24110.04986396853 + + + 25442.13011895279 + + + 26786.9284252403 + + + 28144.5386613377 + + + 29515.05532658545 + + + 30898.57354501914 + + + 32295.189069254 + + + 33704.99828439258 + + + 35128.09821195582 + + + 36564.58651383757 + + + 38014.56149628287 + + + 39478.12211388975 + + + 40955.36797363529 + + + 42446.39933892545 + + + 43951.31713366917 + + + 45470.22294637683 + + + 47003.21903428326 + + + 48550.40832749508 + + + 50111.89443316315 + + + 51687.78163967973 + + + 53278.17492090064 + + + 54883.17994039277 + + + 56502.9030557068 + + + 58137.45132267545 + + + 59786.93249973739 + + + 61451.4550522869 + + + 63131.12815704939 + + + 64826.06170648322 + + + 66536.36631320744 + + + 68262.15331445627 + + + 70003.53477655973 + + + 71760.62349945138 + + + 73533.53302120253 + + + 75322.37762258368 + + + 77127.27233165313 + + + 78948.33292837272 + + + 80785.67594925121 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$N$25:$N$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$O$25:$O$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 866517.5089281509 + + + 173.3105290406213 + + + 24649.97309019839 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$T$4:$T$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$U$4:$U$6 + + 0,,\ "Mn" + + + 353185842.0796728 + + + 94186.71022770347 + + + 20094277.18055464 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Retail Media Investment Case'!$R$15:$R$94 + + _("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"??_);_(@_) + + + 418066.408 + + + 420156.7400399999 + + + 422257.5237401999 + + + 424368.8113589009 + + + 426490.6554156953 + + + 428623.1086927738 + + + 430766.2242362376 + + + 432920.0553574188 + + + 435084.6556342058 + + + 437260.0789123769 + + + 439446.3793069387 + + + 441643.6112034734 + + + 443851.8292594906 + + + 446071.088405788 + + + 448301.4438478169 + + + 450542.951067056 + + + 452795.6658223912 + + + 455059.644151503 + + + 457334.9423722606 + + + 459621.6170841218 + + + 461919.7251695424 + + + 464229.32379539 + + + 466550.4704143669 + + + 468883.2227664387 + + + 471227.6388802708 + + + 473583.7770746721 + + + 475951.6959600455 + + + 478331.4544398457 + + + 480723.1117120448 + + + 483126.7272706049 + + + 485542.3609069579 + + + 487970.0727114927 + + + 490409.9230750501 + + + 492861.9726904253 + + + 495326.2825538774 + + + 497802.9139666467 + + + 500291.9285364799 + + + 502793.3881791623 + + + 505307.3551200581 + + + 507833.8918956583 + + + 510373.0613551366 + + + 512924.9266619122 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$B$25:$B$27 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$C$25:$C$27 + + _-[$€-2]\ * #,##0_-;\-[$€-2]\ * #,##0_-;_-[$€-2]\ * "-"??_-;_-@_- + + + 3431552.205527931 + + + 686.3382701939946 + + + 97617.95768963304 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Graphics!$G$4:$G$6 + + + + In-Store + + + On-Site + + + Off-Site + + + + + + + Graphics!$H$4:$H$6 + + 0,,\ "Mn" + + + 313341417.5483458 + + + 83561.09951402395 + + + 17827354.74131494 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..2c215ea --- /dev/null +++ b/index.html @@ -0,0 +1,1606 @@ + + + + + + Retail Media Business Case + + + + +
+
+

Retail Media Business Case

+

Complete the form below, and our retail media specialists will reach out soon.

+
+ + +
+
+ Step 1 of 6 + 16.67% +
+
+ +
+ + +
+ + +
+
+
+
+
+
+
+ + +
+ Contact + Store Details + In-Store + On-Site + Off-Site + Business Case +
+
+ +
+
+ +
+

Contact Information

+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+
+ + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..c25daf3 --- /dev/null +++ b/index.js @@ -0,0 +1,187 @@ +const fs = require('fs'); +const path = require('path'); + +// Function to update config.json with form data +async function updateConfig(formData) { + return new Promise((resolve, reject) => { + const configPath = path.join(__dirname, 'config.json'); + + // Read the existing config file + fs.readFile(configPath, 'utf8', (err, data) => { + if (err) { + reject(new Error(`Failed to read config file: ${err.message}`)); + return; + } + + try { + // Parse the existing config + const configData = JSON.parse(data); + + // Update user_data in the config with form data + configData.user_data = { + // Contact information + first_name: formData.firstName || "", + last_name: formData.lastName || "", + company_name: formData.company || "", + email: formData.email || "", + phone: formData.phone || "", + store_name: formData.storeName || "", + country: formData.country || "", + starting_date: formData.startingDate || "", + duration: parseInt(formData.duration) || 36, + + // Store information + store_types: getSelectedStoreTypes(formData), + open_days_per_month: parseInt(formData.openDays) || 0, + + // Store type specific data + convenience_store_type: { + stores_number: isStoreTypeSelected(formData, 'Convenience') ? parseInt(formData.convenience_stores) || 0 : 0, + monthly_transactions: isStoreTypeSelected(formData, 'Convenience') ? parseInt(formData.convenience_transactions) || 0 : 0, + has_digital_screens: isStoreTypeSelected(formData, 'Convenience') ? formData.convenience_screens === "Yes" : false, + screen_count: isStoreTypeSelected(formData, 'Convenience') ? parseInt(formData.convenience_screen_count) || 0 : 0, + screen_percentage: isStoreTypeSelected(formData, 'Convenience') ? parseInt(formData.convenience_screen_percentage) || 0 : 0, + has_in_store_radio: isStoreTypeSelected(formData, 'Convenience') ? formData.convenience_radio === "Yes" : false, + radio_percentage: isStoreTypeSelected(formData, 'Convenience') ? parseInt(formData.convenience_radio_percentage) || 0 : 0, + open_days_per_month: parseInt(formData.openDays) || 0 + }, + + supermarket_store_type: { + stores_number: isStoreTypeSelected(formData, 'Supermarket') ? parseInt(formData.supermarket_stores) || 0 : 0, + monthly_transactions: isStoreTypeSelected(formData, 'Supermarket') ? parseInt(formData.supermarket_transactions) || 0 : 0, + has_digital_screens: isStoreTypeSelected(formData, 'Supermarket') ? formData.supermarket_screens === "Yes" : false, + screen_count: isStoreTypeSelected(formData, 'Supermarket') ? parseInt(formData.supermarket_screen_count) || 0 : 0, + screen_percentage: isStoreTypeSelected(formData, 'Supermarket') ? parseInt(formData.supermarket_screen_percentage) || 0 : 0, + has_in_store_radio: isStoreTypeSelected(formData, 'Supermarket') ? formData.supermarket_radio === "Yes" : false, + radio_percentage: isStoreTypeSelected(formData, 'Supermarket') ? parseInt(formData.supermarket_radio_percentage) || 0 : 0, + open_days_per_month: parseInt(formData.openDays) || 0 + }, + + hypermarket_store_type: { + stores_number: isStoreTypeSelected(formData, 'Hypermarket') ? parseInt(formData.hypermarket_stores) || 0 : 0, + monthly_transactions: isStoreTypeSelected(formData, 'Hypermarket') ? parseInt(formData.hypermarket_transactions) || 0 : 0, + has_digital_screens: isStoreTypeSelected(formData, 'Hypermarket') ? formData.hypermarket_screens === "Yes" : false, + screen_count: isStoreTypeSelected(formData, 'Hypermarket') ? parseInt(formData.hypermarket_screen_count) || 0 : 0, + screen_percentage: isStoreTypeSelected(formData, 'Hypermarket') ? parseInt(formData.hypermarket_screen_percentage) || 0 : 0, + has_in_store_radio: isStoreTypeSelected(formData, 'Hypermarket') ? formData.hypermarket_radio === "Yes" : false, + radio_percentage: isStoreTypeSelected(formData, 'Hypermarket') ? parseInt(formData.hypermarket_radio_percentage) || 0 : 0, + open_days_per_month: parseInt(formData.openDays) || 0 + }, + + // On-site channels + on_site_channels: getSelectedChannels(formData, 'onSiteChannels'), + website_visitors: isChannelSelected(formData, 'onSiteChannels', 'Website') ? parseInt(formData.websiteVisitors) || 0 : 0, + app_users: isChannelSelected(formData, 'onSiteChannels', 'Mobile App') ? parseInt(formData.appUsers) || 0 : 0, + loyalty_users: isChannelSelected(formData, 'onSiteChannels', 'Loyalty Program') ? parseInt(formData.loyaltyUsers) || 0 : 0, + + // Off-site channels + off_site_channels: getSelectedChannels(formData, 'offSiteChannels'), + facebook_followers: isChannelSelected(formData, 'offSiteChannels', 'Facebook Business') ? parseInt(formData.facebookFollowers) || 0 : 0, + instagram_followers: isChannelSelected(formData, 'offSiteChannels', 'Instagram Business') ? parseInt(formData.instagramFollowers) || 0 : 0, + google_views: isChannelSelected(formData, 'offSiteChannels', 'Google Business Profile') ? parseInt(formData.googleViews) || 0 : 0, + email_subscribers: isChannelSelected(formData, 'offSiteChannels', 'Email') ? parseInt(formData.emailSubscribers) || 0 : 0, + sms_users: isChannelSelected(formData, 'offSiteChannels', 'SMS') ? parseInt(formData.smsUsers) || 0 : 0, + whatsapp_contacts: isChannelSelected(formData, 'offSiteChannels', 'WhatsApp') ? parseInt(formData.whatsappContacts) || 0 : 0, + + // Preserve existing calculation results if they exist + potential_reach_in_store: 0, + unique_impressions_in_store: 0, + potential_reach_on_site: 0, + unique_impressions_on_site: 0, + potential_reach_off_site: 0, + unique_impressions_off_site: 0 + }; + + // Write the updated config back to the file + const updatedConfig = JSON.stringify(configData, null, 2); + + fs.writeFile(configPath, updatedConfig, 'utf8', (writeErr) => { + if (writeErr) { + reject(new Error(`Failed to write to config file: ${writeErr.message}`)); + return; + } + + resolve(); + }); + } catch (parseError) { + reject(new Error(`Failed to parse config file: ${parseError.message}`)); + } + }); + }); +} + +// Helper function to check if a channel is selected +function isChannelSelected(formData, channelType, channelName) { + const selectedChannels = getSelectedChannels(formData, channelType); + return selectedChannels.includes(channelName); +} + +// Helper function to get selected channels from form data +function getSelectedChannels(formData, channelType) { + console.log(`Getting selected channels for ${channelType} from formData:`, formData[channelType]); + + let channels = []; + + if (formData[channelType]) { + if (Array.isArray(formData[channelType])) { + channels = formData[channelType]; + } else { + channels = [formData[channelType]]; + } + } + + console.log(`Selected ${channelType}:`, channels); + return channels; +} + +// Helper function to check if a store type is selected +function isStoreTypeSelected(formData, storeType) { + const selectedTypes = getSelectedStoreTypes(formData); + return selectedTypes.includes(storeType); +} + +// Helper function to get selected store types from form data +function getSelectedStoreTypes(formData) { + console.log('Getting selected store types from formData:', formData); + + // Check if storeTypes is an array or single value + let storeTypes = []; + + if (formData.storeTypes) { + if (Array.isArray(formData.storeTypes)) { + storeTypes = formData.storeTypes; + } else { + storeTypes = [formData.storeTypes]; + } + } + + console.log('Selected store types:', storeTypes); + return storeTypes; +} + +// Function to fetch config.json +async function fetchConfig() { + return new Promise((resolve, reject) => { + fs.readFile(path.join(__dirname, 'config.json'), 'utf8', (err, data) => { + if (err) { + reject(new Error(`Failed to read config file: ${err.message}`)); + return; + } + + try { + const config = JSON.parse(data); + resolve(config); + } catch (parseError) { + reject(new Error(`Failed to parse config file: ${parseError.message}`)); + } + }); + }); +} + +// For Node.js environment, export the functions +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + updateConfig, + fetchConfig + }; +} \ No newline at end of file diff --git a/llm_prompt_retail_media.md b/llm_prompt_retail_media.md new file mode 100644 index 0000000..ce69d80 --- /dev/null +++ b/llm_prompt_retail_media.md @@ -0,0 +1,92 @@ +# 🧠 LLM Prompt – Retail Media Calculation Agent + +## Purpose + +You are a smart data agent. Your job is to: + +1. **Extract input values** from the existing form ( `index.html`). +2. **Read constants and formulas** from an existing `config.json`. +3. **Normalize input**: + - For any question that asks for a percentage (e.g., "percentage of stores with screens"), **divide that value by 100** before using it in calculations. +4. **Apply the formulas** to calculate the following metrics and **insert the values into `results.json`** under the following keys: + +```json +{ + "potential_reach_in_store": , + "unique_impressions_in_store": , + "potential_reach_on_site": , + "unique_impressions_on_site": , + "potential_reach_off_site": , + "unique_impressions_off_site": +} +``` + +--- + +## 🔢 Formulas + +- **% stores with retail media** + `= min(stores_with_screens, stores_with_radio) + abs(stores_with_screens - stores_with_radio) / 2` + +- **potential_reach_in_store** + `= (transactions × % stores with retail media / frequency) × visitor_coefficient` + +- **unique_impressions_in_store** + `= ((dwell_time + 60 × ad_duration) × frequency × capture_rate_screen × paid_screen × screen_count) + ((dwell_time + 60 × ad_duration) × frequency × (radio_percentage / 0.5) × paid_radio)` + +- **potential_reach_on_site** + `= (website_visits × (1 - website_bounce_rate) / website_frequency) + (app_users × (1 - app_bounce_rate)) + (loyalty_users × (1 - loyalty_bounce_rate))` + +- **unique_impressions_on_site** + `= average_impressions_website × website_frequency × if_website + average_impressions_app × app_frequency × if_app + average_impressions_loyalty × loyalty_frequency × if_loyalty` + +- **potential_reach_off_site** + `= sum of (followers × (1 - off_site_bounce_rate))` for each channel selected + +- **unique_impressions_off_site** + `= frequency × avg_impressions × if_channel` for each selected channel (e.g., Facebook, Instagram, etc.) + +--- + +## ✅ Boolean Inputs + +Use `if_channel = 1` if selected, `0` otherwise. + +--- + +## ⚙️ Additional Behavior + +After the user clicks the **Submit** button on the form: + +- The formulas must be executed using the inputs. +- The calculated values must be generated and replaced into the `results.json`. +- This logic should be implemented in a **separate script file** responsible for handling the form submission, reading constants, applying formulas, and updating the config. + +--- + +## 📁 Output: results.json + +We maintain a JSON file named `results.json` with the following structure: + +```json +{ + "potential_reach_in_store": , + "unique_impressions_in_store": , + "potential_reach_on_site": , + "unique_impressions_on_site": , + "potential_reach_off_site": , + "unique_impressions_off_site": +} +``` + +On **each form submission**, the formulas must be: + +- **Executed using the latest input values** +- **The `results.json` file must be updated (overwritten) with the new results** + +This logic is to be implemented in **Node.js**, in a dedicated script that handles: + +- Reading user input +- Parsing `config.json` +- Performing calculations +- Writing updated values into `results.json` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6788d23 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2290 @@ +{ + "name": "retail-media-calculator", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "retail-media-calculator", + "version": "1.0.0", + "dependencies": { + "body-parser": "^1.20.2", + "exceljs": "^4.4.0", + "express": "^4.18.2", + "fs-extra": "^11.3.1", + "node-xlsx": "^0.24.0", + "python-shell": "^5.0.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-xlsx": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/node-xlsx/-/node-xlsx-0.24.0.tgz", + "integrity": "sha512-1olwK48XK9nXZsyH/FCltvGrQYvXXZuxVitxXXv2GIuRm51aBi1+5KwR4rWM4KeO61sFU+00913WLZTD+AcXEg==", + "license": "Apache-2.0", + "dependencies": { + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" + }, + "bin": { + "node-xlsx": "dist/bin/cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/node-xlsx/node_modules/xlsx": { + "version": "0.20.2", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz", + "integrity": "sha512-+nKZ39+nvK7Qq6i0PvWWRA4j/EkfWOtkP/YhMtupm+lJIiHxUrgTr1CcKv1nBk1rHtkRRQ3O2+Ih/q/sA+FXZA==", + "license": "Apache-2.0", + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/python-shell": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-5.0.0.tgz", + "integrity": "sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ebcee13 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "retail-media-calculator", + "version": "1.0.0", + "description": "Retail Media Business Case Calculation Agent", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "dependencies": { + "body-parser": "^1.20.2", + "exceljs": "^4.4.0", + "express": "^4.18.2", + "fs-extra": "^11.3.1", + "node-xlsx": "^0.24.0", + "python-shell": "^5.0.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..5bb2310 --- /dev/null +++ b/server.js @@ -0,0 +1,132 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const path = require('path'); +const { exec } = require('child_process'); +const { updateConfig } = require('./index'); + +// Create Express app +const app = express(); +const PORT = process.env.PORT || 4444; + +// Middleware +app.use(express.static(__dirname)); // Serve static files +app.use('/output', express.static(path.join(__dirname, 'output'))); // Serve files from output directory +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +// Route to serve the HTML form +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'index.html')); +}); + +// Route to serve the thank you page +app.get('/thank-you.html', (req, res) => { + res.sendFile(path.join(__dirname, 'thank-you.html')); +}); + +// Route to download the generated Excel file +app.get('/download-excel', (req, res) => { + try { + // Read the latest config to get store name and other details + const configPath = path.join(__dirname, 'config.json'); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + const storeName = config.user_data?.store_name || 'Your Store'; + + // Find the most recent Excel file in the output directory + const outputDir = path.join(__dirname, 'output'); + const files = fs.readdirSync(outputDir) + .filter(file => file.endsWith('.xlsx') && file.includes(storeName)) + .map(file => ({ + name: file, + time: fs.statSync(path.join(outputDir, file)).mtime.getTime() + })) + .sort((a, b) => b.time - a.time); // Sort by modified time, newest first + + if (files.length > 0) { + const latestFile = files[0].name; + const filePath = path.join(outputDir, latestFile); + + // Set headers for file download + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + res.setHeader('Content-Disposition', `attachment; filename="${latestFile}"`); + + // Send the file + res.sendFile(filePath); + console.log(`Excel file sent for download: ${filePath}`); + } else { + res.status(404).send('No Excel file found'); + } + } catch (error) { + console.error('Error downloading Excel file:', error); + res.status(500).send('Error downloading Excel file'); + } +}); + +// API endpoint to handle form submissions +app.post('/calculate', async (req, res) => { + try { + console.log('Received form submission'); + const formData = req.body; + console.log('Form data received:', JSON.stringify(formData, null, 2)); + + // Update config file with form data + await updateConfig(formData); + console.log('Config file updated successfully'); + + // Run Python script to create Excel file synchronously + const { execSync } = require('child_process'); + try { + console.log('Executing Python script...'); + const stdout = execSync('source venv/bin/activate && python3 create_excel_xlsxwriter.py', { + encoding: 'utf8', + shell: '/bin/bash' + }); + console.log(`Python script output: ${stdout}`); + + // Extract the filename from the Python script output + const filenameMatch = stdout.match(/Excel file created successfully: .*\/output\/(.*\.xlsx)/); + const excelFilename = filenameMatch ? filenameMatch[1] : null; + + if (excelFilename) { + // Store the filename in a session variable or pass it to the thank-you page + console.log(`Excel filename extracted: ${excelFilename}`); + } + + // Send success response after Python script completes + res.json({ + success: true, + message: 'Form data saved and Excel file created successfully', + excelFilename: excelFilename + }); + console.log('Success response sent'); + } catch (execError) { + console.error(`Error executing Python script: ${execError.message}`); + if (execError.stderr) { + console.error(`stderr: ${execError.stderr}`); + } + + // Send error response for Python script failure + res.status(500).json({ + success: false, + message: 'Error creating Excel file', + error: execError.message + }); + console.error('Error response sent for Python script failure'); + } + } catch (error) { + console.error('Error processing form data:', error); + console.error('Error stack:', error.stack); + res.status(500).json({ + success: false, + message: 'Error processing form data', + error: error.message + }); + console.error('Error response sent'); + } +}); + +// Start the server +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); \ No newline at end of file diff --git a/template/.gitkeep b/template/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx b/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bccad79195e9455a7996702b3aeb1dc0fe3a4420 GIT binary patch literal 201653 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ⅅ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≦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%0AbKz1n00*Q%+1Mm8Jt3mp;7b9-K(|C=%tBT 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�_{AHRJ>}FCQS;!EM_39yz+*n-xK%%G{BMK;7 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 SF9iJT077ll6o79;p#Kj|#$dz% literal 0 HcmV?d00001 diff --git a/thank-you.html b/thank-you.html new file mode 100644 index 0000000..7d16170 --- /dev/null +++ b/thank-you.html @@ -0,0 +1,50 @@ + + + + + + Thank You - Retail Media Business Case + + + +

+ + + \ No newline at end of file diff --git a/update_excel.py b/update_excel.py new file mode 100644 index 0000000..6e01694 --- /dev/null +++ b/update_excel.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +import json +import os +import re +import openpyxl +from openpyxl.utils import get_column_letter + +def update_excel_variables(excel_path): + """ + Update the Variables sheet in the Excel file with values from config.json + and hide forecast sheets that aren't in the calculated years array. + + This version uses openpyxl exclusively to preserve all formatting, formulas, + and Excel features that xlsxwriter cannot handle when modifying existing files. + + Args: + excel_path (str): Path to the Excel file to update + + Returns: + bool: True if successful, False otherwise + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + + try: + # Load config.json + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + + # Load Excel workbook + print(f"Opening Excel file: {excel_path}") + wb = openpyxl.load_workbook(excel_path) + + # Try to access the Variables sheet + try: + # First try by name + sheet = wb['Variables'] + except KeyError: + # If not found by name, try to access the last sheet + sheet_names = wb.sheetnames + if sheet_names: + print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}") + sheet = wb[sheet_names[-1]] + else: + print("No sheets found in the workbook") + return False + + # Map config variables to Excel cells based on the provided mapping + cell_mappings = { + 'B2': user_data.get('store_name', ''), + 'B31': user_data.get('starting_date', ''), + 'B32': user_data.get('duration', 36), + 'B37': user_data.get('open_days_per_month', 0), + + # Convenience store type + 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), + 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, + 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), + 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, + 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), + + # Minimarket store type + 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), + 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), + 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), + + # Supermarket store type + 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), + 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), + 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), + + # Hypermarket store type + 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), + 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), + 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), + + # On-site channels + 'B43': user_data.get('website_visitors', 0), + 'B44': user_data.get('app_users', 0), + 'B45': user_data.get('loyalty_users', 0), + + # Off-site channels + 'B49': user_data.get('facebook_followers', 0), + 'B50': user_data.get('instagram_followers', 0), + 'B51': user_data.get('google_views', 0), + 'B52': user_data.get('email_subscribers', 0), + 'B53': user_data.get('sms_users', 0), + 'B54': user_data.get('whatsapp_contacts', 0) + } + + # Update the cells + for cell_ref, value in cell_mappings.items(): + try: + # Force the value to be set, even if the cell is protected or has data validation + cell = sheet[cell_ref] + cell.value = value + print(f"Updated {cell_ref} with value: {value}") + except Exception as e: + print(f"Error updating cell {cell_ref}: {e}") + + # Save the workbook with variables updated + print("Saving workbook with updated variables...") + wb.save(excel_path) + + # Get the calculated years array from config + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + calculated_years = [] + + # Import datetime at the module level to avoid scope issues + import datetime + from dateutil.relativedelta import relativedelta + + # Calculate years array based on starting_date and duration + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if starting_date: + if '/' in str(starting_date): + day, month, year = map(int, str(starting_date).split('/')) + elif '.' in str(starting_date): + day, month, year = map(int, str(starting_date).split('.')) + elif '-' in str(starting_date): + # Handle ISO format (yyyy-mm-dd) + date_parts = str(starting_date).split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + elif isinstance(starting_date, datetime.datetime): + day, month, year = starting_date.day, starting_date.month, starting_date.year + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + calculated_years = sorted(list(years_set)) + print(f"Calculated years for sheet visibility: {calculated_years}") + else: + # Default to current year if no starting date + calculated_years = [datetime.datetime.now().year] + except Exception as e: + print(f"Error calculating years for sheet visibility: {e}") + calculated_years = [datetime.datetime.now().year] + + # Hide forecast sheets that aren't in the calculated years array + # No sheet renaming - just check existing sheet names + for sheet_name in wb.sheetnames: + # Check if this is a forecast sheet + # Forecast sheets have names like "2025 – Forecast" + if "Forecast" in sheet_name: + # Extract the year from the sheet name + try: + sheet_year = int(sheet_name.split()[0]) + # Hide the sheet if its year is not in the calculated years + if sheet_year not in calculated_years: + sheet = wb[sheet_name] + sheet.sheet_state = 'hidden' + print(f"Hiding sheet '{sheet_name}' as year {sheet_year} is not in calculated years {calculated_years}") + except Exception as e: + print(f"Error extracting year from sheet name '{sheet_name}': {e}") + + # Save the workbook with updated variables and hidden sheets + print("Saving workbook with all updates...") + wb.save(excel_path) + + print(f"Excel file updated successfully: {excel_path}") + return True + + except Exception as e: + print(f"Error updating Excel file: {e}") + return False + + +if __name__ == "__main__": + # For testing purposes + import sys + if len(sys.argv) > 1: + excel_path = sys.argv[1] + update_excel_variables(excel_path) + else: + print("Please provide the path to the Excel file as an argument") \ No newline at end of file diff --git a/update_excel_openpyxl.py b/update_excel_openpyxl.py new file mode 100644 index 0000000..d162b75 --- /dev/null +++ b/update_excel_openpyxl.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +import json +import os +import re +import openpyxl +from openpyxl.utils import get_column_letter +# Removed zipfile import - no longer using direct XML manipulation + +def update_excel_variables(excel_path): + """ + Update the Variables sheet in the Excel file with values from config.json + and hide forecast sheets that aren't in the calculated years array + + Args: + excel_path (str): Path to the Excel file to update + + Returns: + bool: True if successful, False otherwise + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + + try: + # Load config.json + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + + # Load Excel workbook + print(f"Opening Excel file: {excel_path}") + wb = openpyxl.load_workbook(excel_path) + + # Try to access the Variables sheet + try: + # First try by name + sheet = wb['Variables'] + except KeyError: + # If not found by name, try to access the last sheet + sheet_names = wb.sheetnames + if sheet_names: + print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}") + sheet = wb[sheet_names[-1]] + else: + print("No sheets found in the workbook") + return False + + # Map config variables to Excel cells based on the provided mapping + cell_mappings = { + 'B2': user_data.get('store_name', ''), + 'B31': user_data.get('starting_date', ''), + 'B32': user_data.get('duration', 36), + 'B37': user_data.get('open_days_per_month', 0), + + # Convenience store type + 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), + 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, + 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), + 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, + 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), + + # Minimarket store type + 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), + 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), + 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), + + # Supermarket store type + 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), + 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), + 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), + + # Hypermarket store type + 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), + 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), + 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), + + # On-site channels + 'B43': user_data.get('website_visitors', 0), + 'B44': user_data.get('app_users', 0), + 'B45': user_data.get('loyalty_users', 0), + + # Off-site channels + 'B49': user_data.get('facebook_followers', 0), + 'B50': user_data.get('instagram_followers', 0), + 'B51': user_data.get('google_views', 0), + 'B52': user_data.get('email_subscribers', 0), + 'B53': user_data.get('sms_users', 0), + 'B54': user_data.get('whatsapp_contacts', 0) + } + + # Update the cells + for cell_ref, value in cell_mappings.items(): + try: + # Force the value to be set, even if the cell is protected or has data validation + cell = sheet[cell_ref] + cell.value = value + print(f"Updated {cell_ref} with value: {value}") + except Exception as e: + print(f"Error updating cell {cell_ref}: {e}") + + # Save the workbook with variables updated + print("Saving workbook with updated variables...") + wb.save(excel_path) + + # Get the calculated years array from config + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + calculated_years = [] + + # Import datetime at the module level to avoid scope issues + import datetime + from dateutil.relativedelta import relativedelta + + # Calculate years array based on starting_date and duration + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if starting_date: + if '/' in str(starting_date): + day, month, year = map(int, str(starting_date).split('/')) + elif '.' in str(starting_date): + day, month, year = map(int, str(starting_date).split('.')) + elif '-' in str(starting_date): + # Handle ISO format (yyyy-mm-dd) + date_parts = str(starting_date).split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + elif isinstance(starting_date, datetime.datetime): + day, month, year = starting_date.day, starting_date.month, starting_date.year + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + calculated_years = sorted(list(years_set)) + print(f"Calculated years for sheet visibility: {calculated_years}") + else: + # Default to current year if no starting date + calculated_years = [datetime.datetime.now().year] + except Exception as e: + print(f"Error calculating years for sheet visibility: {e}") + calculated_years = [datetime.datetime.now().year] + + # Hide forecast sheets that aren't in the calculated years array + # No sheet renaming - just check existing sheet names + for sheet_name in wb.sheetnames: + # Check if this is a forecast sheet + # Forecast sheets have names like "2025 – Forecast" + if "Forecast" in sheet_name: + # Extract the year from the sheet name + try: + sheet_year = int(sheet_name.split()[0]) + # Hide the sheet if its year is not in the calculated years + if sheet_year not in calculated_years: + sheet = wb[sheet_name] + sheet.sheet_state = 'hidden' + print(f"Hiding sheet '{sheet_name}' as year {sheet_year} is not in calculated years {calculated_years}") + except Exception as e: + print(f"Error extracting year from sheet name '{sheet_name}': {e}") + + # Save the workbook with updated variables and hidden sheets + print("Saving workbook with all updates...") + wb.save(excel_path) + + print(f"Excel file updated successfully: {excel_path}") + return True + + except Exception as e: + print(f"Error updating Excel file: {e}") + return False + + +if __name__ == "__main__": + # For testing purposes + import sys + if len(sys.argv) > 1: + excel_path = sys.argv[1] + update_excel_variables(excel_path) + else: + print("Please provide the path to the Excel file as an argument") diff --git a/update_excel_xlsxwriter.py b/update_excel_xlsxwriter.py new file mode 100644 index 0000000..040a538 --- /dev/null +++ b/update_excel_xlsxwriter.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +import json +import os +import re +import openpyxl +from openpyxl.utils import get_column_letter + +def update_excel_variables(excel_path): + """ + Update the Variables sheet in the Excel file with values from config.json + and hide forecast sheets that aren't in the calculated years array. + + This version uses openpyxl exclusively to preserve all formatting, formulas, + and Excel features that xlsxwriter cannot handle when modifying existing files. + While this is named "xlsxwriter", it actually uses openpyxl for the best + approach to modify existing Excel files while preserving all features. + + Args: + excel_path (str): Path to the Excel file to update + + Returns: + bool: True if successful, False otherwise + """ + # Define paths + script_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(script_dir, 'config.json') + + try: + # Load config.json + with open(config_path, 'r') as f: + config = json.load(f) + user_data = config.get('user_data', {}) + + # Load Excel workbook + print(f"Opening Excel file: {excel_path}") + wb = openpyxl.load_workbook(excel_path) + + # Try to access the Variables sheet + try: + # First try by name + sheet = wb['Variables'] + except KeyError: + # If not found by name, try to access the last sheet + sheet_names = wb.sheetnames + if sheet_names: + print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}") + sheet = wb[sheet_names[-1]] + else: + print("No sheets found in the workbook") + return False + + # Map config variables to Excel cells based on the provided mapping + cell_mappings = { + 'B2': user_data.get('store_name', ''), + 'B31': user_data.get('starting_date', ''), + 'B32': user_data.get('duration', 36), + 'B37': user_data.get('open_days_per_month', 0), + + # Convenience store type + 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), + 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, + 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), + 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, + 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), + + # Minimarket store type + 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), + 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), + 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), + + # Supermarket store type + 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), + 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), + 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), + + # Hypermarket store type + 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), + 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), + # Convert boolean to 1/0 for has_digital_screens + 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, + 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), + 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), + # Convert boolean to 1/0 for has_in_store_radio + 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, + 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), + + # On-site channels + 'B43': user_data.get('website_visitors', 0), + 'B44': user_data.get('app_users', 0), + 'B45': user_data.get('loyalty_users', 0), + + # Off-site channels + 'B49': user_data.get('facebook_followers', 0), + 'B50': user_data.get('instagram_followers', 0), + 'B51': user_data.get('google_views', 0), + 'B52': user_data.get('email_subscribers', 0), + 'B53': user_data.get('sms_users', 0), + 'B54': user_data.get('whatsapp_contacts', 0) + } + + # Update the cells + for cell_ref, value in cell_mappings.items(): + try: + # Force the value to be set, even if the cell is protected or has data validation + cell = sheet[cell_ref] + cell.value = value + print(f"Updated {cell_ref} with value: {value}") + except Exception as e: + print(f"Error updating cell {cell_ref}: {e}") + + # Save the workbook with variables updated + print("Saving workbook with updated variables...") + wb.save(excel_path) + + # Get the calculated years array from config + starting_date = user_data.get('starting_date', '') + duration = user_data.get('duration', 36) + calculated_years = [] + + # Import datetime at the module level to avoid scope issues + import datetime + from dateutil.relativedelta import relativedelta + + # Calculate years array based on starting_date and duration + try: + # Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats + if starting_date: + if '/' in str(starting_date): + day, month, year = map(int, str(starting_date).split('/')) + elif '.' in str(starting_date): + day, month, year = map(int, str(starting_date).split('.')) + elif '-' in str(starting_date): + # Handle ISO format (yyyy-mm-dd) + date_parts = str(starting_date).split('-') + if len(date_parts) == 3: + year, month, day = map(int, date_parts) + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + elif isinstance(starting_date, datetime.datetime): + day, month, year = starting_date.day, starting_date.month, starting_date.year + else: + # Default to current date if format is not recognized + current_date = datetime.datetime.now() + year, month, day = current_date.year, current_date.month, current_date.day + + # Create datetime object for starting date + start_date = datetime.datetime(year, month, day) + + # Calculate end date (starting date + duration months - 1 day) + end_date = start_date + relativedelta(months=duration-1) + + # Create a set of years (to avoid duplicates) + years_set = set() + + # Add starting year + years_set.add(start_date.year) + + # Add ending year + years_set.add(end_date.year) + + # If there are years in between, add those too + for y in range(start_date.year + 1, end_date.year): + years_set.add(y) + + # Convert set to sorted list + calculated_years = sorted(list(years_set)) + print(f"Calculated years for sheet visibility: {calculated_years}") + else: + # Default to current year if no starting date + calculated_years = [datetime.datetime.now().year] + except Exception as e: + print(f"Error calculating years for sheet visibility: {e}") + calculated_years = [datetime.datetime.now().year] + + # Hide forecast sheets that aren't in the calculated years array + # No sheet renaming - just check existing sheet names + for sheet_name in wb.sheetnames: + # Check if this is a forecast sheet + # Forecast sheets have names like "2025 – Forecast" + if "Forecast" in sheet_name: + # Extract the year from the sheet name + try: + sheet_year = int(sheet_name.split()[0]) + # Hide the sheet if its year is not in the calculated years + if sheet_year not in calculated_years: + sheet_obj = wb[sheet_name] + sheet_obj.sheet_state = 'hidden' + print(f"Hiding sheet '{sheet_name}' as year {sheet_year} is not in calculated years {calculated_years}") + except Exception as e: + print(f"Error extracting year from sheet name '{sheet_name}': {e}") + + # Save the workbook with updated variables and hidden sheets + print("Saving workbook with all updates...") + wb.save(excel_path) + + print(f"Excel file updated successfully: {excel_path}") + return True + + except Exception as e: + print(f"Error updating Excel file: {e}") + return False + + +if __name__ == "__main__": + # For testing purposes + import sys + if len(sys.argv) > 1: + excel_path = sys.argv[1] + update_excel_variables(excel_path) + else: + print("Please provide the path to the Excel file as an argument") \ No newline at end of file diff --git a/venv/bin/Activate.ps1 b/venv/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 0000000..338c36b --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath /home/pixot/business_case_form/venv) +else + # use the path as-is + export VIRTUAL_ENV=/home/pixot/business_case_form/venv +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 0000000..6b4df06 --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /home/pixot/business_case_form/venv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 0000000..7bd480e --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /home/pixot/business_case_form/venv + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(venv) ' +end diff --git a/venv/bin/pip b/venv/bin/pip new file mode 100755 index 0000000..4a0a144 --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,8 @@ +#!/home/pixot/business_case_form/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3 b/venv/bin/pip3 new file mode 100755 index 0000000..4a0a144 --- /dev/null +++ b/venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/pixot/business_case_form/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3.12 b/venv/bin/pip3.12 new file mode 100755 index 0000000..4a0a144 --- /dev/null +++ b/venv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/home/pixot/business_case_form/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/venv/bin/python3.12 b/venv/bin/python3.12 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/vba_extract.py b/venv/bin/vba_extract.py new file mode 100755 index 0000000..23f0cbb --- /dev/null +++ b/venv/bin/vba_extract.py @@ -0,0 +1,79 @@ +#!/home/pixot/business_case_form/venv/bin/python3 + +############################################################################## +# +# vba_extract - A simple utility to extract a vbaProject.bin binary from an +# Excel 2007+ xlsm file for insertion into an XlsxWriter file. +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org +# + +import sys +from zipfile import BadZipFile, ZipFile + + +def extract_file(xlsm_zip, filename): + # Extract a single file from an Excel xlsm macro file. + data = xlsm_zip.read("xl/" + filename) + + # Write the data to a local file. + file = open(filename, "wb") + file.write(data) + file.close() + + +# The VBA project file and project signature file we want to extract. +vba_filename = "vbaProject.bin" +vba_signature_filename = "vbaProjectSignature.bin" + +# Get the xlsm file name from the commandline. +if len(sys.argv) > 1: + xlsm_file = sys.argv[1] +else: + print( + "\nUtility to extract a vbaProject.bin binary from an Excel 2007+ " + "xlsm macro file for insertion into an XlsxWriter file.\n" + "If the macros are digitally signed, extracts also a vbaProjectSignature.bin " + "file.\n" + "\n" + "See: https://xlsxwriter.readthedocs.io/working_with_macros.html\n" + "\n" + "Usage: vba_extract file.xlsm\n" + ) + sys.exit() + +try: + # Open the Excel xlsm file as a zip file. + xlsm_zip = ZipFile(xlsm_file, "r") + + # Read the xl/vbaProject.bin file. + extract_file(xlsm_zip, vba_filename) + print(f"Extracted: {vba_filename}") + + if "xl/" + vba_signature_filename in xlsm_zip.namelist(): + extract_file(xlsm_zip, vba_signature_filename) + print(f"Extracted: {vba_signature_filename}") + + +except IOError as e: + print(f"File error: {str(e)}") + sys.exit() + +except KeyError as e: + # Usually when there isn't a xl/vbaProject.bin member in the file. + print(f"File error: {str(e)}") + print(f"File may not be an Excel xlsm macro file: '{xlsm_file}'") + sys.exit() + +except BadZipFile as e: + # Usually if the file is an xls file and not an xlsm file. + print(f"File error: {str(e)}: '{xlsm_file}'") + print("File may not be an Excel xlsm macro file.") + sys.exit() + +except Exception as e: + # Catch any other exceptions. + print(f"File error: {str(e)}") + sys.exit() diff --git a/venv/lib/python3.12/site-packages/dateutil/__init__.py b/venv/lib/python3.12/site-packages/dateutil/__init__.py new file mode 100644 index 0000000..a2c19c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import sys + +try: + from ._version import version as __version__ +except ImportError: + __version__ = 'unknown' + +__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', + 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/venv/lib/python3.12/site-packages/dateutil/_common.py b/venv/lib/python3.12/site-packages/dateutil/_common.py new file mode 100644 index 0000000..4eb2659 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/_common.py @@ -0,0 +1,43 @@ +""" +Common code used in multiple modules. +""" + + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __hash__(self): + return hash(( + self.weekday, + self.n, + )) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +# vim:ts=4:sw=4:et diff --git a/venv/lib/python3.12/site-packages/dateutil/_version.py b/venv/lib/python3.12/site-packages/dateutil/_version.py new file mode 100644 index 0000000..ddda980 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '2.9.0.post0' +__version_tuple__ = version_tuple = (2, 9, 0) diff --git a/venv/lib/python3.12/site-packages/dateutil/easter.py b/venv/lib/python3.12/site-packages/dateutil/easter.py new file mode 100644 index 0000000..f74d1f7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/easter.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic Easter computing method for any given year, using +Western, Orthodox or Julian algorithms. +""" + +import datetime + +__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] + +EASTER_JULIAN = 1 +EASTER_ORTHODOX = 2 +EASTER_WESTERN = 3 + + +def easter(year, method=EASTER_WESTERN): + """ + This method was ported from the work done by GM Arts, + on top of the algorithm by Claus Tondering, which was + based in part on the algorithm of Ouding (1940), as + quoted in "Explanatory Supplement to the Astronomical + Almanac", P. Kenneth Seidelmann, editor. + + This algorithm implements three different Easter + calculation methods: + + 1. Original calculation in Julian calendar, valid in + dates after 326 AD + 2. Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 3. Revised method, in Gregorian calendar, valid in + years 1583 to 4099 as well + + These methods are represented by the constants: + + * ``EASTER_JULIAN = 1`` + * ``EASTER_ORTHODOX = 2`` + * ``EASTER_WESTERN = 3`` + + The default method is method 3. + + More about the algorithm may be found at: + + `GM Arts: Easter Algorithms `_ + + and + + `The Calendar FAQ: Easter `_ + + """ + + if not (1 <= method <= 3): + raise ValueError("invalid method") + + # g - Golden year - 1 + # c - Century + # h - (23 - Epact) mod 30 + # i - Number of days from March 21 to Paschal Full Moon + # j - Weekday for PFM (0=Sunday, etc) + # p - Number of days from March 21 to Sunday on or before PFM + # (-6 to 28 methods 1 & 3, to 56 for method 2) + # e - Extra days to add for method 2 (converting Julian + # date to Gregorian date) + + y = year + g = y % 19 + e = 0 + if method < 3: + # Old method + i = (19*g + 15) % 30 + j = (y + y//4 + i) % 7 + if method == 2: + # Extra dates to convert Julian to Gregorian date + e = 10 + if y > 1600: + e = e + y//100 - 16 - (y//100 - 16)//4 + else: + # New method + c = y//100 + h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 + i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) + j = (y + y//4 + i + 2 - c + c//4) % 7 + + # p can be from -6 to 56 corresponding to dates 22 March to 23 May + # (later dates apply to method 2, although 23 May never actually occurs) + p = i - j + e + d = 1 + (p + 27 + (p + 6)//40) % 31 + m = 3 + (p + 26)//30 + return datetime.date(int(y), int(m), int(d)) diff --git a/venv/lib/python3.12/site-packages/dateutil/parser/__init__.py b/venv/lib/python3.12/site-packages/dateutil/parser/__init__.py new file mode 100644 index 0000000..d174b0e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/parser/__init__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from ._parser import parse, parser, parserinfo, ParserError +from ._parser import DEFAULTPARSER, DEFAULTTZPARSER +from ._parser import UnknownTimezoneWarning + +from ._parser import __doc__ + +from .isoparser import isoparser, isoparse + +__all__ = ['parse', 'parser', 'parserinfo', + 'isoparse', 'isoparser', + 'ParserError', + 'UnknownTimezoneWarning'] + + +### +# Deprecate portions of the private interface so that downstream code that +# is improperly relying on it is given *some* notice. + + +def __deprecated_private_func(f): + from functools import wraps + import warnings + + msg = ('{name} is a private function and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=f.__name__) + + @wraps(f) + def deprecated_func(*args, **kwargs): + warnings.warn(msg, DeprecationWarning) + return f(*args, **kwargs) + + return deprecated_func + +def __deprecate_private_class(c): + import warnings + + msg = ('{name} is a private class and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=c.__name__) + + class private_class(c): + __doc__ = c.__doc__ + + def __init__(self, *args, **kwargs): + warnings.warn(msg, DeprecationWarning) + super(private_class, self).__init__(*args, **kwargs) + + private_class.__name__ = c.__name__ + + return private_class + + +from ._parser import _timelex, _resultbase +from ._parser import _tzparser, _parsetz + +_timelex = __deprecate_private_class(_timelex) +_tzparser = __deprecate_private_class(_tzparser) +_resultbase = __deprecate_private_class(_resultbase) +_parsetz = __deprecated_private_func(_parsetz) diff --git a/venv/lib/python3.12/site-packages/dateutil/parser/_parser.py b/venv/lib/python3.12/site-packages/dateutil/parser/_parser.py new file mode 100644 index 0000000..37d1663 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/parser/_parser.py @@ -0,0 +1,1613 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic date/time string parser which is able to parse +most known formats to represent a date and/or time. + +This module attempts to be forgiving with regards to unlikely input formats, +returning a datetime object even for dates which are ambiguous. If an element +of a date/time stamp is omitted, the following rules are applied: + +- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour + on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is + specified. +- If a time zone is omitted, a timezone-naive datetime is returned. + +If any other elements are missing, they are taken from the +:class:`datetime.datetime` object passed to the parameter ``default``. If this +results in a day number exceeding the valid number of days per month, the +value falls back to the end of the month. + +Additional resources about date/time string formats can be found below: + +- `A summary of the international standard date and time notation + `_ +- `W3C Date and Time Formats `_ +- `Time Formats (Planetary Rings Node) `_ +- `CPAN ParseDate module + `_ +- `Java SimpleDateFormat Class + `_ +""" +from __future__ import unicode_literals + +import datetime +import re +import string +import time +import warnings + +from calendar import monthrange +from io import StringIO + +import six +from six import integer_types, text_type + +from decimal import Decimal + +from warnings import warn + +from .. import relativedelta +from .. import tz + +__all__ = ["parse", "parserinfo", "ParserError"] + + +# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth +# making public and/or figuring out if there is something we can +# take off their plate. +class _timelex(object): + # Fractional seconds are sometimes split by a comma + _split_decimal = re.compile("([.,])") + + def __init__(self, instream): + if isinstance(instream, (bytes, bytearray)): + instream = instream.decode() + + if isinstance(instream, text_type): + instream = StringIO(instream) + elif getattr(instream, 'read', None) is None: + raise TypeError('Parser must be a string or character stream, not ' + '{itype}'.format(itype=instream.__class__.__name__)) + + self.instream = instream + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + """ + This function breaks the time string into lexical units (tokens), which + can be parsed by the parser. Lexical units are demarcated by changes in + the character set, so any continuous string of letters is considered + one unit, any continuous string of numbers is considered one unit. + + The main complication arises from the fact that dots ('.') can be used + both as separators (e.g. "Sep.20.2009") or decimal points (e.g. + "4:30:21.447"). As such, it is necessary to read the full context of + any dot-separated strings before breaking it into tokens; as such, this + function maintains a "token stack", for when the ambiguous context + demands that multiple tokens be parsed at once. + """ + if self.tokenstack: + return self.tokenstack.pop(0) + + seenletters = False + token = None + state = None + + while not self.eof: + # We only realize that we've reached the end of a token when we + # find a character that's not part of the current token - since + # that character may be part of the next token, it's stored in the + # charstack. + if self.charstack: + nextchar = self.charstack.pop(0) + else: + nextchar = self.instream.read(1) + while nextchar == '\x00': + nextchar = self.instream.read(1) + + if not nextchar: + self.eof = True + break + elif not state: + # First character of the token - determines if we're starting + # to parse a word, a number or something else. + token = nextchar + if self.isword(nextchar): + state = 'a' + elif self.isnum(nextchar): + state = '0' + elif self.isspace(nextchar): + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + # If we've already started reading a word, we keep reading + # letters until we find something that's not part of a word. + seenletters = True + if self.isword(nextchar): + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + # If we've already started reading a number, we keep reading + # numbers until we find something that doesn't fit. + if self.isnum(nextchar): + token += nextchar + elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + # If we've seen some letters and a dot separator, continue + # parsing, and the tokens will be broken up later. + seenletters = True + if nextchar == '.' or self.isword(nextchar): + token += nextchar + elif self.isnum(nextchar) and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + # If we've seen at least one dot separator, keep going, we'll + # break up the tokens later. + if nextchar == '.' or self.isnum(nextchar): + token += nextchar + elif self.isword(nextchar) and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + + if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or + token[-1] in '.,')): + l = self._split_decimal.split(token) + token = l[0] + for tok in l[1:]: + if tok: + self.tokenstack.append(tok) + + if state == '0.' and token.count('.') == 0: + token = token.replace(',', '.') + + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + + return token + + def next(self): + return self.__next__() # Python 2.x support + + @classmethod + def split(cls, s): + return list(cls(s)) + + @classmethod + def isword(cls, nextchar): + """ Whether or not the next character is part of a word """ + return nextchar.isalpha() + + @classmethod + def isnum(cls, nextchar): + """ Whether the next character is part of a number """ + return nextchar.isdigit() + + @classmethod + def isspace(cls, nextchar): + """ Whether the next character is whitespace """ + return nextchar.isspace() + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __len__(self): + return (sum(getattr(self, attr) is not None + for attr in self.__slots__)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + """ + Class which handles what inputs are accepted. Subclass this to customize + the language and acceptable values for each parameter. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. Default is ``False``. + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + Default is ``False``. + """ + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), # TODO: "Tues" + ("Wed", "Wednesday"), + ("Thu", "Thursday"), # TODO: "Thurs" + ("Fri", "Friday"), + ("Sat", "Saturday"), + ("Sun", "Sunday")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), # TODO: "Febr" + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] + UTCZONE = ["UTC", "GMT", "Z", "z"] + PERTAIN = ["of"] + TZOFFSET = {} + # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", + # "Anno Domini", "Year of Our Lord"] + + def __init__(self, dayfirst=False, yearfirst=False): + self._jump = self._convert(self.JUMP) + self._weekdays = self._convert(self.WEEKDAYS) + self._months = self._convert(self.MONTHS) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year // 100 * 100 + + def _convert(self, lst): + dct = {} + for i, v in enumerate(lst): + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + try: + return self._months[name.lower()] + 1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + def tzoffset(self, name): + if name in self._utczone: + return 0 + + return self.TZOFFSET.get(name) + + def convertyear(self, year, century_specified=False): + """ + Converts two-digit years to year within [-50, 49] + range of self._year (current local time) + """ + + # Function contract is that the year is always positive + assert year >= 0 + + if year < 100 and not century_specified: + # assume current century to start + year += self._century + + if year >= self._year + 50: # if too far in future + year -= 100 + elif year < self._year - 50: # if too far in past + year += 100 + + return year + + def validate(self, res): + # move to info + if res.year is not None: + res.year = self.convertyear(res.year, res.century_specified) + + if ((res.tzoffset == 0 and not res.tzname) or + (res.tzname == 'Z' or res.tzname == 'z')): + res.tzname = "UTC" + res.tzoffset = 0 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class _ymd(list): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.century_specified = False + self.dstridx = None + self.mstridx = None + self.ystridx = None + + @property + def has_year(self): + return self.ystridx is not None + + @property + def has_month(self): + return self.mstridx is not None + + @property + def has_day(self): + return self.dstridx is not None + + def could_be_day(self, value): + if self.has_day: + return False + elif not self.has_month: + return 1 <= value <= 31 + elif not self.has_year: + # Be permissive, assume leap year + month = self[self.mstridx] + return 1 <= value <= monthrange(2000, month)[1] + else: + month = self[self.mstridx] + year = self[self.ystridx] + return 1 <= value <= monthrange(year, month)[1] + + def append(self, val, label=None): + if hasattr(val, '__len__'): + if val.isdigit() and len(val) > 2: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + elif val > 100: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + + super(self.__class__, self).append(int(val)) + + if label == 'M': + if self.has_month: + raise ValueError('Month is already set') + self.mstridx = len(self) - 1 + elif label == 'D': + if self.has_day: + raise ValueError('Day is already set') + self.dstridx = len(self) - 1 + elif label == 'Y': + if self.has_year: + raise ValueError('Year is already set') + self.ystridx = len(self) - 1 + + def _resolve_from_stridxs(self, strids): + """ + Try to resolve the identities of year/month/day elements using + ystridx, mstridx, and dstridx, if enough of these are specified. + """ + if len(self) == 3 and len(strids) == 2: + # we can back out the remaining stridx value + missing = [x for x in range(3) if x not in strids.values()] + key = [x for x in ['y', 'm', 'd'] if x not in strids] + assert len(missing) == len(key) == 1 + key = key[0] + val = missing[0] + strids[key] = val + + assert len(self) == len(strids) # otherwise this should not be called + out = {key: self[strids[key]] for key in strids} + return (out.get('y'), out.get('m'), out.get('d')) + + def resolve_ymd(self, yearfirst, dayfirst): + len_ymd = len(self) + year, month, day = (None, None, None) + + strids = (('y', self.ystridx), + ('m', self.mstridx), + ('d', self.dstridx)) + + strids = {key: val for key, val in strids if val is not None} + if (len(self) == len(strids) > 0 or + (len(self) == 3 and len(strids) == 2)): + return self._resolve_from_stridxs(strids) + + mstridx = self.mstridx + + if len_ymd > 3: + raise ValueError("More than three YMD values") + elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): + # One member, or two members with a month string + if mstridx is not None: + month = self[mstridx] + # since mstridx is 0 or 1, self[mstridx-1] always + # looks up the other element + other = self[mstridx - 1] + else: + other = self[0] + + if len_ymd > 1 or mstridx is None: + if other > 31: + year = other + else: + day = other + + elif len_ymd == 2: + # Two members with numbers + if self[0] > 31: + # 99-01 + year, month = self + elif self[1] > 31: + # 01-99 + month, year = self + elif dayfirst and self[1] <= 12: + # 13-01 + day, month = self + else: + # 01-13 + month, day = self + + elif len_ymd == 3: + # Three members + if mstridx == 0: + if self[1] > 31: + # Apr-2003-25 + month, year, day = self + else: + month, day, year = self + elif mstridx == 1: + if self[0] > 31 or (yearfirst and self[2] <= 31): + # 99-Jan-01 + year, month, day = self + else: + # 01-Jan-01 + # Give precedence to day-first, since + # two-digit years is usually hand-written. + day, month, year = self + + elif mstridx == 2: + # WTF!? + if self[1] > 31: + # 01-99-Jan + day, year, month = self + else: + # 99-01-Jan + year, day, month = self + + else: + if (self[0] > 31 or + self.ystridx == 0 or + (yearfirst and self[1] <= 12 and self[2] <= 31)): + # 99-01-01 + if dayfirst and self[2] <= 12: + year, day, month = self + else: + year, month, day = self + elif self[0] > 12 or (dayfirst and self[1] <= 12): + # 13-01-01 + day, month, year = self + else: + # 01-13-01 + month, day, year = self + + return year, month, day + + +class parser(object): + def __init__(self, info=None): + self.info = info or parserinfo() + + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, **kwargs): + """ + Parse the date/time string into a :class:`datetime.datetime` object. + + :param timestr: + Any date/time string using the supported formats. + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a + naive :class:`datetime.datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param \\*\\*kwargs: + Keyword arguments as passed to ``_parse()``. + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string format, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date + would be created. + + :raises TypeError: + Raised for non-string or character stream input. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + + if default is None: + default = datetime.datetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) + + res, skipped_tokens = self._parse(timestr, **kwargs) + + if res is None: + raise ParserError("Unknown string format: %s", timestr) + + if len(res) == 0: + raise ParserError("String does not contain a date: %s", timestr) + + try: + ret = self._build_naive(res, default) + except ValueError as e: + six.raise_from(ParserError(str(e) + ": %s", timestr), e) + + if not ignoretz: + ret = self._build_tzaware(ret, res, tzinfos) + + if kwargs.get('fuzzy_with_tokens', False): + return ret, skipped_tokens + else: + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset", "ampm","any_unused_tokens"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, + fuzzy_with_tokens=False): + """ + Private method which performs the heavy lifting of parsing, called from + ``parse()``, which passes on its ``kwargs`` to this function. + + :param timestr: + The string to parse. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. If set to ``None``, this value is retrieved from the + current :class:`parserinfo` object (which itself defaults to + ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + If this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + """ + if fuzzy_with_tokens: + fuzzy = True + + info = self.info + + if dayfirst is None: + dayfirst = info.dayfirst + + if yearfirst is None: + yearfirst = info.yearfirst + + res = self._result() + l = _timelex.split(timestr) # Splits the timestr into tokens + + skipped_idxs = [] + + # year/month/day list + ymd = _ymd() + + len_l = len(l) + i = 0 + try: + while i < len_l: + + # Check if it's a number + value_repr = l[i] + try: + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Numeric token + i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) + + # Check weekday + elif info.weekday(l[i]) is not None: + value = info.weekday(l[i]) + res.weekday = value + + # Check month name + elif info.month(l[i]) is not None: + value = info.month(l[i]) + ymd.append(value, 'M') + + if i + 1 < len_l: + if l[i + 1] in ('-', '/'): + # Jan-01[-99] + sep = l[i + 1] + ymd.append(l[i + 2]) + + if i + 3 < len_l and l[i + 3] == sep: + # Jan-01-99 + ymd.append(l[i + 4]) + i += 2 + + i += 2 + + elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and + info.pertain(l[i + 2])): + # Jan of 01 + # In this case, 01 is clearly year + if l[i + 4].isdigit(): + # Convert it here to become unambiguous + value = int(l[i + 4]) + year = str(info.convertyear(value)) + ymd.append(year, 'Y') + else: + # Wrong guess + pass + # TODO: not hit in tests + i += 4 + + # Check am/pm + elif info.ampm(l[i]) is not None: + value = info.ampm(l[i]) + val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) + + if val_is_ampm: + res.hour = self._adjust_ampm(res.hour, value) + res.ampm = value + + elif fuzzy: + skipped_idxs.append(i) + + # Check for a timezone name + elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): + res.tzname = l[i] + res.tzoffset = info.tzoffset(res.tzname) + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i + 1 < len_l and l[i + 1] in ('+', '-'): + l[i + 1] = ('+', '-')[l[i + 1] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + # Check for a numbered timezone + elif res.hour is not None and l[i] in ('+', '-'): + signal = (-1, 1)[l[i] == '+'] + len_li = len(l[i + 1]) + + # TODO: check that l[i + 1] is integer? + if len_li == 4: + # -0300 + hour_offset = int(l[i + 1][:2]) + min_offset = int(l[i + 1][2:]) + elif i + 2 < len_l and l[i + 2] == ':': + # -03:00 + hour_offset = int(l[i + 1]) + min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? + i += 2 + elif len_li <= 2: + # -[0]3 + hour_offset = int(l[i + 1][:2]) + min_offset = 0 + else: + raise ValueError(timestr) + + res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) + + # Look for a timezone name between parenthesis + if (i + 5 < len_l and + info.jump(l[i + 2]) and l[i + 3] == '(' and + l[i + 5] == ')' and + 3 <= len(l[i + 4]) and + self._could_be_tzname(res.hour, res.tzname, + None, l[i + 4])): + # -0300 (BRST) + res.tzname = l[i + 4] + i += 4 + + i += 1 + + # Check jumps + elif not (info.jump(l[i]) or fuzzy): + raise ValueError(timestr) + + else: + skipped_idxs.append(i) + i += 1 + + # Process year/month/day + year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) + + res.century_specified = ymd.century_specified + res.year = year + res.month = month + res.day = day + + except (IndexError, ValueError): + return None, None + + if not info.validate(res): + return None, None + + if fuzzy_with_tokens: + skipped_tokens = self._recombine_skipped(l, skipped_idxs) + return res, tuple(skipped_tokens) + else: + return res, None + + def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): + # Token is a number + value_repr = tokens[idx] + try: + value = self._to_decimal(value_repr) + except Exception as e: + six.raise_from(ValueError('Unknown numeric token'), e) + + len_li = len(value_repr) + + len_l = len(tokens) + + if (len(ymd) == 3 and len_li in (2, 4) and + res.hour is None and + (idx + 1 >= len_l or + (tokens[idx + 1] != ':' and + info.hms(tokens[idx + 1]) is None))): + # 19990101T23[59] + s = tokens[idx] + res.hour = int(s[:2]) + + if len_li == 4: + res.minute = int(s[2:]) + + elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): + # YYMMDD or HHMMSS[.ss] + s = tokens[idx] + + if not ymd and '.' not in tokens[idx]: + ymd.append(s[:2]) + ymd.append(s[2:4]) + ymd.append(s[4:]) + else: + # 19990101T235959[.59] + + # TODO: Check if res attributes already set. + res.hour = int(s[:2]) + res.minute = int(s[2:4]) + res.second, res.microsecond = self._parsems(s[4:]) + + elif len_li in (8, 12, 14): + # YYYYMMDD + s = tokens[idx] + ymd.append(s[:4], 'Y') + ymd.append(s[4:6]) + ymd.append(s[6:8]) + + if len_li > 8: + res.hour = int(s[8:10]) + res.minute = int(s[10:12]) + + if len_li > 12: + res.second = int(s[12:]) + + elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: + # HH[ ]h or MM[ ]m or SS[.ss][ ]s + hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) + (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) + if hms is not None: + # TODO: checking that hour/minute/second are not + # already set? + self._assign_hms(res, value_repr, hms) + + elif idx + 2 < len_l and tokens[idx + 1] == ':': + # HH:MM[:SS[.ss]] + res.hour = int(value) + value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? + (res.minute, res.second) = self._parse_min_sec(value) + + if idx + 4 < len_l and tokens[idx + 3] == ':': + res.second, res.microsecond = self._parsems(tokens[idx + 4]) + + idx += 2 + + idx += 2 + + elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): + sep = tokens[idx + 1] + ymd.append(value_repr) + + if idx + 2 < len_l and not info.jump(tokens[idx + 2]): + if tokens[idx + 2].isdigit(): + # 01-01[-01] + ymd.append(tokens[idx + 2]) + else: + # 01-Jan[-01] + value = info.month(tokens[idx + 2]) + + if value is not None: + ymd.append(value, 'M') + else: + raise ValueError() + + if idx + 3 < len_l and tokens[idx + 3] == sep: + # We have three members + value = info.month(tokens[idx + 4]) + + if value is not None: + ymd.append(value, 'M') + else: + ymd.append(tokens[idx + 4]) + idx += 2 + + idx += 1 + idx += 1 + + elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): + if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: + # 12 am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) + idx += 1 + else: + # Year, month or day + ymd.append(value) + idx += 1 + + elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): + # 12am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) + idx += 1 + + elif ymd.could_be_day(value): + ymd.append(value) + + elif not fuzzy: + raise ValueError() + + return idx + + def _find_hms_idx(self, idx, tokens, info, allow_jump): + len_l = len(tokens) + + if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: + # There is an "h", "m", or "s" label following this token. We take + # assign the upcoming label to the current token. + # e.g. the "12" in 12h" + hms_idx = idx + 1 + + elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and + info.hms(tokens[idx+2]) is not None): + # There is a space and then an "h", "m", or "s" label. + # e.g. the "12" in "12 h" + hms_idx = idx + 2 + + elif idx > 0 and info.hms(tokens[idx-1]) is not None: + # There is a "h", "m", or "s" preceding this token. Since neither + # of the previous cases was hit, there is no label following this + # token, so we use the previous label. + # e.g. the "04" in "12h04" + hms_idx = idx-1 + + elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and + info.hms(tokens[idx-2]) is not None): + # If we are looking at the final token, we allow for a + # backward-looking check to skip over a space. + # TODO: Are we sure this is the right condition here? + hms_idx = idx - 2 + + else: + hms_idx = None + + return hms_idx + + def _assign_hms(self, res, value_repr, hms): + # See GH issue #427, fixing float rounding + value = self._to_decimal(value_repr) + + if hms == 0: + # Hour + res.hour = int(value) + if value % 1: + res.minute = int(60*(value % 1)) + + elif hms == 1: + (res.minute, res.second) = self._parse_min_sec(value) + + elif hms == 2: + (res.second, res.microsecond) = self._parsems(value_repr) + + def _could_be_tzname(self, hour, tzname, tzoffset, token): + return (hour is not None and + tzname is None and + tzoffset is None and + len(token) <= 5 and + (all(x in string.ascii_uppercase for x in token) + or token in self.info.UTCZONE)) + + def _ampm_valid(self, hour, ampm, fuzzy): + """ + For fuzzy parsing, 'a' or 'am' (both valid English words) + may erroneously trigger the AM/PM flag. Deal with that + here. + """ + val_is_ampm = True + + # If there's already an AM/PM flag, this one isn't one. + if fuzzy and ampm is not None: + val_is_ampm = False + + # If AM/PM is found and hour is not, raise a ValueError + if hour is None: + if fuzzy: + val_is_ampm = False + else: + raise ValueError('No hour specified with AM or PM flag.') + elif not 0 <= hour <= 12: + # If AM/PM is found, it's a 12 hour clock, so raise + # an error for invalid range + if fuzzy: + val_is_ampm = False + else: + raise ValueError('Invalid hour specified for 12-hour clock.') + + return val_is_ampm + + def _adjust_ampm(self, hour, ampm): + if hour < 12 and ampm == 1: + hour += 12 + elif hour == 12 and ampm == 0: + hour = 0 + return hour + + def _parse_min_sec(self, value): + # TODO: Every usage of this function sets res.second to the return + # value. Are there any cases where second will be returned as None and + # we *don't* want to set res.second = None? + minute = int(value) + second = None + + sec_remainder = value % 1 + if sec_remainder: + second = int(60 * sec_remainder) + return (minute, second) + + def _parse_hms(self, idx, tokens, info, hms_idx): + # TODO: Is this going to admit a lot of false-positives for when we + # just happen to have digits and "h", "m" or "s" characters in non-date + # text? I guess hex hashes won't have that problem, but there's plenty + # of random junk out there. + if hms_idx is None: + hms = None + new_idx = idx + elif hms_idx > idx: + hms = info.hms(tokens[hms_idx]) + new_idx = hms_idx + else: + # Looking backwards, increment one. + hms = info.hms(tokens[hms_idx]) + 1 + new_idx = idx + + return (new_idx, hms) + + # ------------------------------------------------------------------ + # Handling for individual tokens. These are kept as methods instead + # of functions for the sake of customizability via subclassing. + + def _parsems(self, value): + """Parse a I[.F] seconds value into (seconds, microseconds).""" + if "." not in value: + return int(value), 0 + else: + i, f = value.split(".") + return int(i), int(f.ljust(6, "0")[:6]) + + def _to_decimal(self, val): + try: + decimal_value = Decimal(val) + # See GH 662, edge case, infinite value should not be converted + # via `_to_decimal` + if not decimal_value.is_finite(): + raise ValueError("Converted decimal value is infinite or NaN") + except Exception as e: + msg = "Could not convert %s to decimal" % val + six.raise_from(ValueError(msg), e) + else: + return decimal_value + + # ------------------------------------------------------------------ + # Post-Parsing construction of datetime output. These are kept as + # methods instead of functions for the sake of customizability via + # subclassing. + + def _build_tzinfo(self, tzinfos, tzname, tzoffset): + if callable(tzinfos): + tzdata = tzinfos(tzname, tzoffset) + else: + tzdata = tzinfos.get(tzname) + # handle case where tzinfo is paased an options that returns None + # eg tzinfos = {'BRST' : None} + if isinstance(tzdata, datetime.tzinfo) or tzdata is None: + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(tzname, tzdata) + else: + raise TypeError("Offset must be tzinfo subclass, tz string, " + "or int offset.") + return tzinfo + + def _build_tzaware(self, naive, res, tzinfos): + if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): + tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) + aware = naive.replace(tzinfo=tzinfo) + aware = self._assign_tzname(aware, res.tzname) + + elif res.tzname and res.tzname in time.tzname: + aware = naive.replace(tzinfo=tz.tzlocal()) + + # Handle ambiguous local datetime + aware = self._assign_tzname(aware, res.tzname) + + # This is mostly relevant for winter GMT zones parsed in the UK + if (aware.tzname() != res.tzname and + res.tzname in self.info.UTCZONE): + aware = aware.replace(tzinfo=tz.UTC) + + elif res.tzoffset == 0: + aware = naive.replace(tzinfo=tz.UTC) + + elif res.tzoffset: + aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + + elif not res.tzname and not res.tzoffset: + # i.e. no timezone information was found. + aware = naive + + elif res.tzname: + # tz-like string was parsed but we don't know what to do + # with it + warnings.warn("tzname {tzname} identified but not understood. " + "Pass `tzinfos` argument in order to correctly " + "return a timezone-aware datetime. In a future " + "version, this will raise an " + "exception.".format(tzname=res.tzname), + category=UnknownTimezoneWarning) + aware = naive + + return aware + + def _build_naive(self, res, default): + repl = {} + for attr in ("year", "month", "day", "hour", + "minute", "second", "microsecond"): + value = getattr(res, attr) + if value is not None: + repl[attr] = value + + if 'day' not in repl: + # If the default day exceeds the last day of the month, fall back + # to the end of the month. + cyear = default.year if res.year is None else res.year + cmonth = default.month if res.month is None else res.month + cday = default.day if res.day is None else res.day + + if cday > monthrange(cyear, cmonth)[1]: + repl['day'] = monthrange(cyear, cmonth)[1] + + naive = default.replace(**repl) + + if res.weekday is not None and not res.day: + naive = naive + relativedelta.relativedelta(weekday=res.weekday) + + return naive + + def _assign_tzname(self, dt, tzname): + if dt.tzname() != tzname: + new_dt = tz.enfold(dt, fold=1) + if new_dt.tzname() == tzname: + return new_dt + + return dt + + def _recombine_skipped(self, tokens, skipped_idxs): + """ + >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] + >>> skipped_idxs = [0, 1, 2, 5] + >>> _recombine_skipped(tokens, skipped_idxs) + ["foo bar", "baz"] + """ + skipped_tokens = [] + for i, idx in enumerate(sorted(skipped_idxs)): + if i > 0 and idx - 1 == skipped_idxs[i - 1]: + skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] + else: + skipped_tokens.append(tokens[idx]) + + return skipped_tokens + + +DEFAULTPARSER = parser() + + +def parse(timestr, parserinfo=None, **kwargs): + """ + + Parse a string in one of the supported formats, using the + ``parserinfo`` parameters. + + :param timestr: + A string containing a date/time stamp. + + :param parserinfo: + A :class:`parserinfo` object containing parameters for the parser. + If ``None``, the default arguments to the :class:`parserinfo` + constructor are used. + + The ``**kwargs`` parameter takes the following keyword arguments: + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM and + YMD. If set to ``None``, this value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken to + be the year, otherwise the last number is taken to be the year. If + this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string formats, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date would + be created. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + if parserinfo: + return parser(parserinfo).parse(timestr, **kwargs) + else: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] + used_idxs = list() + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + offattr = "dstoffset" + res.dstabbr = "".join(l[i:j]) + + for ii in range(j): + used_idxs.append(ii) + i = j + if (i < len_l and (l[i] in ('+', '-') or l[i][0] in + "0123456789")): + if l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + used_idxs.append(i) + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + setattr(res, offattr, (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) * signal) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + setattr(res, offattr, + (int(l[i]) * 3600 + + int(l[i + 2]) * 60) * signal) + used_idxs.append(i) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2]) * 3600 * signal) + else: + return None + used_idxs.append(i) + i += 1 + if res.dstabbr: + break + else: + break + + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': + l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + not [y for x in l[i:] if x != ',' + for y in x if y not in "0123456789+-"]): + # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] + for x in (res.start, res.end): + x.month = int(l[i]) + used_idxs.append(i) + i += 2 + if l[i] == '-': + value = int(l[i + 1]) * -1 + used_idxs.append(i) + i += 1 + else: + value = int(l[i]) + used_idxs.append(i) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i]) - 1) % 7 + else: + x.day = int(l[i]) + used_idxs.append(i) + i += 2 + x.time = int(l[i]) + used_idxs.append(i) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + used_idxs.append(i) + i += 1 + else: + signal = 1 + used_idxs.append(i) + res.dstoffset = (res.stdoffset + int(l[i]) * signal) + + # This was a made-up format that is not in normal use + warn(('Parsed time zone "%s"' % tzstr) + + 'is in a non-standard dateutil-specific format, which ' + + 'is now deprecated; support for parsing this format ' + + 'will be removed in future versions. It is recommended ' + + 'that you switch to a standard format like the GNU ' + + 'TZ variable format.', tz.DeprecatedTzFormatWarning) + elif (l.count(',') == 2 and l[i:].count('/') <= 2 and + not [y for x in l[i:] if x not in (',', '/', 'J', 'M', + '.', '-', ':') + for y in x if y not in "0123456789"]): + for x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + used_idxs.append(i) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + used_idxs.append(i) + i += 1 + x.month = int(l[i]) + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.weekday = (int(l[i]) - 1) % 7 + else: + # year day (zero based) + x.yday = int(l[i]) + 1 + + used_idxs.append(i) + i += 1 + + if i < len_l and l[i] == '/': + used_idxs.append(i) + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 + used_idxs.append(i) + i += 2 + if i + 1 < len_l and l[i + 1] == ':': + used_idxs.append(i) + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2]) * 3600) + else: + return None + used_idxs.append(i) + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + unused_idxs = set(range(len_l)).difference(used_idxs) + res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) + return res + + +DEFAULTTZPARSER = _tzparser() + + +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + + +class ParserError(ValueError): + """Exception subclass used for any failure to parse a datetime string. + + This is a subclass of :py:exc:`ValueError`, and should be raised any time + earlier versions of ``dateutil`` would have raised ``ValueError``. + + .. versionadded:: 2.8.1 + """ + def __str__(self): + try: + return self.args[0] % self.args[1:] + except (TypeError, IndexError): + return super(ParserError, self).__str__() + + def __repr__(self): + args = ", ".join("'%s'" % arg for arg in self.args) + return "%s(%s)" % (self.__class__.__name__, args) + + +class UnknownTimezoneWarning(RuntimeWarning): + """Raised when the parser finds a timezone it cannot parse into a tzinfo. + + .. versionadded:: 2.7.0 + """ +# vim:ts=4:sw=4:et diff --git a/venv/lib/python3.12/site-packages/dateutil/parser/isoparser.py b/venv/lib/python3.12/site-packages/dateutil/parser/isoparser.py new file mode 100644 index 0000000..7060087 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/parser/isoparser.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +""" +This module offers a parser for ISO-8601 strings + +It is intended to support all valid date, time and datetime formats per the +ISO-8601 specification. + +..versionadded:: 2.7.0 +""" +from datetime import datetime, timedelta, time, date +import calendar +from dateutil import tz + +from functools import wraps + +import re +import six + +__all__ = ["isoparse", "isoparser"] + + +def _takes_ascii(f): + @wraps(f) + def func(self, str_in, *args, **kwargs): + # If it's a stream, read the whole thing + str_in = getattr(str_in, 'read', lambda: str_in)() + + # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII + if isinstance(str_in, six.text_type): + # ASCII is the same in UTF-8 + try: + str_in = str_in.encode('ascii') + except UnicodeEncodeError as e: + msg = 'ISO-8601 strings should contain only ASCII characters' + six.raise_from(ValueError(msg), e) + + return f(self, str_in, *args, **kwargs) + + return func + + +class isoparser(object): + def __init__(self, sep=None): + """ + :param sep: + A single character that separates date and time portions. If + ``None``, the parser will accept any single character. + For strict ISO-8601 adherence, pass ``'T'``. + """ + if sep is not None: + if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): + raise ValueError('Separator must be a single, non-numeric ' + + 'ASCII character') + + sep = sep.encode('ascii') + + self._sep = sep + + @_takes_ascii + def isoparse(self, dt_str): + """ + Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. + + An ISO-8601 datetime string consists of a date portion, followed + optionally by a time portion - the date and time portions are separated + by a single character separator, which is ``T`` in the official + standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be + combined with a time portion. + + Supported date formats are: + + Common: + + - ``YYYY`` + - ``YYYY-MM`` + - ``YYYY-MM-DD`` or ``YYYYMMDD`` + + Uncommon: + + - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) + - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day + + The ISO week and day numbering follows the same logic as + :func:`datetime.date.isocalendar`. + + Supported time formats are: + + - ``hh`` + - ``hh:mm`` or ``hhmm`` + - ``hh:mm:ss`` or ``hhmmss`` + - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) + + Midnight is a special case for `hh`, as the standard supports both + 00:00 and 24:00 as a representation. The decimal separator can be + either a dot or a comma. + + + .. caution:: + + Support for fractional components other than seconds is part of the + ISO-8601 standard, but is not currently implemented in this parser. + + Supported time zone offset formats are: + + - `Z` (UTC) + - `±HH:MM` + - `±HHMM` + - `±HH` + + Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, + with the exception of UTC, which will be represented as + :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such + as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. + + :param dt_str: + A string or stream containing only an ISO-8601 datetime string + + :return: + Returns a :class:`datetime.datetime` representing the string. + Unspecified components default to their lowest value. + + .. warning:: + + As of version 2.7.0, the strictness of the parser should not be + considered a stable part of the contract. Any valid ISO-8601 string + that parses correctly with the default settings will continue to + parse correctly in future versions, but invalid strings that + currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not + guaranteed to continue failing in future versions if they encode + a valid date. + + .. versionadded:: 2.7.0 + """ + components, pos = self._parse_isodate(dt_str) + + if len(dt_str) > pos: + if self._sep is None or dt_str[pos:pos + 1] == self._sep: + components += self._parse_isotime(dt_str[pos + 1:]) + else: + raise ValueError('String contains unknown ISO components') + + if len(components) > 3 and components[3] == 24: + components[3] = 0 + return datetime(*components) + timedelta(days=1) + + return datetime(*components) + + @_takes_ascii + def parse_isodate(self, datestr): + """ + Parse the date portion of an ISO string. + + :param datestr: + The string portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.date` object + """ + components, pos = self._parse_isodate(datestr) + if pos < len(datestr): + raise ValueError('String contains unknown ISO ' + + 'components: {!r}'.format(datestr.decode('ascii'))) + return date(*components) + + @_takes_ascii + def parse_isotime(self, timestr): + """ + Parse the time portion of an ISO string. + + :param timestr: + The time portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.time` object + """ + components = self._parse_isotime(timestr) + if components[0] == 24: + components[0] = 0 + return time(*components) + + @_takes_ascii + def parse_tzstr(self, tzstr, zero_as_utc=True): + """ + Parse a valid ISO time zone string. + + See :func:`isoparser.isoparse` for details on supported formats. + + :param tzstr: + A string representing an ISO time zone offset + + :param zero_as_utc: + Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones + + :return: + Returns :class:`dateutil.tz.tzoffset` for offsets and + :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is + specified) offsets equivalent to UTC. + """ + return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) + + # Constants + _DATE_SEP = b'-' + _TIME_SEP = b':' + _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') + + def _parse_isodate(self, dt_str): + try: + return self._parse_isodate_common(dt_str) + except ValueError: + return self._parse_isodate_uncommon(dt_str) + + def _parse_isodate_common(self, dt_str): + len_str = len(dt_str) + components = [1, 1, 1] + + if len_str < 4: + raise ValueError('ISO string too short') + + # Year + components[0] = int(dt_str[0:4]) + pos = 4 + if pos >= len_str: + return components, pos + + has_sep = dt_str[pos:pos + 1] == self._DATE_SEP + if has_sep: + pos += 1 + + # Month + if len_str - pos < 2: + raise ValueError('Invalid common month') + + components[1] = int(dt_str[pos:pos + 2]) + pos += 2 + + if pos >= len_str: + if has_sep: + return components, pos + else: + raise ValueError('Invalid ISO format') + + if has_sep: + if dt_str[pos:pos + 1] != self._DATE_SEP: + raise ValueError('Invalid separator in ISO string') + pos += 1 + + # Day + if len_str - pos < 2: + raise ValueError('Invalid common day') + components[2] = int(dt_str[pos:pos + 2]) + return components, pos + 2 + + def _parse_isodate_uncommon(self, dt_str): + if len(dt_str) < 4: + raise ValueError('ISO string too short') + + # All ISO formats start with the year + year = int(dt_str[0:4]) + + has_sep = dt_str[4:5] == self._DATE_SEP + + pos = 4 + has_sep # Skip '-' if it's there + if dt_str[pos:pos + 1] == b'W': + # YYYY-?Www-?D? + pos += 1 + weekno = int(dt_str[pos:pos + 2]) + pos += 2 + + dayno = 1 + if len(dt_str) > pos: + if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: + raise ValueError('Inconsistent use of dash separator') + + pos += has_sep + + dayno = int(dt_str[pos:pos + 1]) + pos += 1 + + base_date = self._calculate_weekdate(year, weekno, dayno) + else: + # YYYYDDD or YYYY-DDD + if len(dt_str) - pos < 3: + raise ValueError('Invalid ordinal day') + + ordinal_day = int(dt_str[pos:pos + 3]) + pos += 3 + + if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): + raise ValueError('Invalid ordinal day' + + ' {} for year {}'.format(ordinal_day, year)) + + base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) + + components = [base_date.year, base_date.month, base_date.day] + return components, pos + + def _calculate_weekdate(self, year, week, day): + """ + Calculate the day of corresponding to the ISO year-week-day calendar. + + This function is effectively the inverse of + :func:`datetime.date.isocalendar`. + + :param year: + The year in the ISO calendar + + :param week: + The week in the ISO calendar - range is [1, 53] + + :param day: + The day in the ISO calendar - range is [1 (MON), 7 (SUN)] + + :return: + Returns a :class:`datetime.date` + """ + if not 0 < week < 54: + raise ValueError('Invalid week: {}'.format(week)) + + if not 0 < day < 8: # Range is 1-7 + raise ValueError('Invalid weekday: {}'.format(day)) + + # Get week 1 for the specific year: + jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it + week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) + + # Now add the specific number of weeks and days to get what we want + week_offset = (week - 1) * 7 + (day - 1) + return week_1 + timedelta(days=week_offset) + + def _parse_isotime(self, timestr): + len_str = len(timestr) + components = [0, 0, 0, 0, None] + pos = 0 + comp = -1 + + if len_str < 2: + raise ValueError('ISO time too short') + + has_sep = False + + while pos < len_str and comp < 5: + comp += 1 + + if timestr[pos:pos + 1] in b'-+Zz': + # Detect time zone boundary + components[-1] = self._parse_tzstr(timestr[pos:]) + pos = len_str + break + + if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP: + has_sep = True + pos += 1 + elif comp == 2 and has_sep: + if timestr[pos:pos+1] != self._TIME_SEP: + raise ValueError('Inconsistent use of colon separator') + pos += 1 + + if comp < 3: + # Hour, minute, second + components[comp] = int(timestr[pos:pos + 2]) + pos += 2 + + if comp == 3: + # Fraction of a second + frac = self._FRACTION_REGEX.match(timestr[pos:]) + if not frac: + continue + + us_str = frac.group(1)[:6] # Truncate to microseconds + components[comp] = int(us_str) * 10**(6 - len(us_str)) + pos += len(frac.group()) + + if pos < len_str: + raise ValueError('Unused components in ISO string') + + if components[0] == 24: + # Standard supports 00:00 and 24:00 as representations of midnight + if any(component != 0 for component in components[1:4]): + raise ValueError('Hour may only be 24 at 24:00:00.000') + + return components + + def _parse_tzstr(self, tzstr, zero_as_utc=True): + if tzstr == b'Z' or tzstr == b'z': + return tz.UTC + + if len(tzstr) not in {3, 5, 6}: + raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') + + if tzstr[0:1] == b'-': + mult = -1 + elif tzstr[0:1] == b'+': + mult = 1 + else: + raise ValueError('Time zone offset requires sign') + + hours = int(tzstr[1:3]) + if len(tzstr) == 3: + minutes = 0 + else: + minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) + + if zero_as_utc and hours == 0 and minutes == 0: + return tz.UTC + else: + if minutes > 59: + raise ValueError('Invalid minutes in time zone offset') + + if hours > 23: + raise ValueError('Invalid hours in time zone offset') + + return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) + + +DEFAULT_ISOPARSER = isoparser() +isoparse = DEFAULT_ISOPARSER.isoparse diff --git a/venv/lib/python3.12/site-packages/dateutil/relativedelta.py b/venv/lib/python3.12/site-packages/dateutil/relativedelta.py new file mode 100644 index 0000000..cd323a5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/relativedelta.py @@ -0,0 +1,599 @@ +# -*- coding: utf-8 -*- +import datetime +import calendar + +import operator +from math import copysign + +from six import integer_types +from warnings import warn + +from ._common import weekday + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + + +class relativedelta(object): + """ + The relativedelta type is designed to be applied to an existing datetime and + can replace specific components of that datetime, or represents an interval + of time. + + It is based on the specification of the excellent work done by M.-A. Lemburg + in his + `mx.DateTime `_ extension. + However, notice that this type does *NOT* implement the same algorithm as + his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + + There are two different ways to build a relativedelta instance. The + first one is passing it two date/datetime classes:: + + relativedelta(datetime1, datetime2) + + The second one is passing it any number of the following keyword arguments:: + + relativedelta(arg1=x,arg2=y,arg3=z...) + + year, month, day, hour, minute, second, microsecond: + Absolute information (argument is singular); adding or subtracting a + relativedelta with absolute information does not perform an arithmetic + operation, but rather REPLACES the corresponding value in the + original datetime with the value(s) in relativedelta. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative (argument is plural); adding + or subtracting a relativedelta with relative information performs + the corresponding arithmetic operation on the original datetime value + with the information in the relativedelta. + + weekday: + One of the weekday instances (MO, TU, etc) available in the + relativedelta module. These instances may receive a parameter N, + specifying the Nth weekday, which could be positive or negative + (like MO(+1) or MO(-2)). Not specifying it is the same as specifying + +1. You can also use an integer, where 0=MO. This argument is always + relative e.g. if the calculated date is already Monday, using MO(1) + or MO(-1) won't change the day. To effectively make it absolute, use + it in combination with the day argument (e.g. day=1, MO(1) for first + Monday of the month). + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + + There are relative and absolute forms of the keyword + arguments. The plural is relative, and the singular is + absolute. For each argument in the order below, the absolute form + is applied first (by setting each attribute to that value) and + then the relative form (by adding the value to the attribute). + + The order of attributes considered when this relativedelta is + added to a datetime is: + + 1. Year + 2. Month + 3. Day + 4. Hours + 5. Minutes + 6. Seconds + 7. Microseconds + + Finally, weekday is applied, using the rule described above. + + For example + + >>> from datetime import datetime + >>> from dateutil.relativedelta import relativedelta, MO + >>> dt = datetime(2018, 4, 9, 13, 37, 0) + >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) + >>> dt + delta + datetime.datetime(2018, 4, 2, 14, 37) + + First, the day is set to 1 (the first of the month), then 25 hours + are added, to get to the 2nd day and 14th hour, finally the + weekday is applied, but since the 2nd is already a Monday there is + no effect. + + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + + if dt1 and dt2: + # datetime is a subclass of date. So both must be date + if not (isinstance(dt1, datetime.date) and + isinstance(dt2, datetime.date)): + raise TypeError("relativedelta only diffs datetime/date") + + # We allow two dates, or two datetimes, so we coerce them to be + # of the same type + if (isinstance(dt1, datetime.datetime) != + isinstance(dt2, datetime.datetime)): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + # Get year / month delta between the two + months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) + self._set_months(months) + + # Remove the year/month delta so the timedelta is just well-defined + # time units (seconds, days and microseconds) + dtm = self.__radd__(dt2) + + # If we've overshot our target, make an adjustment + if dt1 < dt2: + compare = operator.gt + increment = 1 + else: + compare = operator.lt + increment = -1 + + while compare(dt1, dtm): + months += increment + self._set_months(months) + dtm = self.__radd__(dt2) + + # Get the timedelta between the "months-adjusted" date and dt1 + delta = dt1 - dtm + self.seconds = delta.seconds + delta.days * 86400 + self.microseconds = delta.microseconds + else: + # Check for non-integer values in integer-only quantities + if any(x is not None and x != int(x) for x in (years, months)): + raise ValueError("Non-integer years and months are " + "ambiguous and not currently supported.") + + # Relative information + self.years = int(years) + self.months = int(months) + self.days = days + weeks * 7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + + # Absolute information + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if any(x is not None and int(x) != x + for x in (year, month, day, hour, + minute, second, microsecond)): + # For now we'll deprecate floats - later it'll be an error. + warn("Non-integer value passed as absolute information. " + + "This is not a well-defined condition and will raise " + + "errors in future versions.", DeprecationWarning) + + if isinstance(weekday, integer_types): + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334, 366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError("invalid year day (%d)" % yday) + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = _sign(self.microseconds) + div, mod = divmod(self.microseconds * s, 1000000) + self.microseconds = mod * s + self.seconds += div * s + if abs(self.seconds) > 59: + s = _sign(self.seconds) + div, mod = divmod(self.seconds * s, 60) + self.seconds = mod * s + self.minutes += div * s + if abs(self.minutes) > 59: + s = _sign(self.minutes) + div, mod = divmod(self.minutes * s, 60) + self.minutes = mod * s + self.hours += div * s + if abs(self.hours) > 23: + s = _sign(self.hours) + div, mod = divmod(self.hours * s, 24) + self.hours = mod * s + self.days += div * s + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years += div * s + if (self.hours or self.minutes or self.seconds or self.microseconds + or self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + @property + def weeks(self): + return int(self.days / 7.0) + + @weeks.setter + def weeks(self, value): + self.days = self.days - (self.weeks * 7) + value * 7 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years = div * s + else: + self.years = 0 + + def normalized(self): + """ + Return a version of this object represented entirely using integer + values for the relative attributes. + + >>> relativedelta(days=1.5, hours=2).normalized() + relativedelta(days=+1, hours=+14) + + :return: + Returns a :class:`dateutil.relativedelta.relativedelta` object. + """ + # Cascade remainders down (rounding each to roughly nearest microsecond) + days = int(self.days) + + hours_f = round(self.hours + 24 * (self.days - days), 11) + hours = int(hours_f) + + minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) + minutes = int(minutes_f) + + seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) + seconds = int(seconds_f) + + microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) + + # Constructor carries overflow back up with call to _fix() + return self.__class__(years=self.years, months=self.months, + days=days, hours=hours, minutes=minutes, + seconds=seconds, microseconds=microseconds, + leapdays=self.leapdays, year=self.year, + month=self.month, day=self.day, + weekday=self.weekday, hour=self.hour, + minute=self.minute, second=self.second, + microsecond=self.microsecond) + + def __add__(self, other): + if isinstance(other, relativedelta): + return self.__class__(years=other.years + self.years, + months=other.months + self.months, + days=other.days + self.days, + hours=other.hours + self.hours, + minutes=other.minutes + self.minutes, + seconds=other.seconds + self.seconds, + microseconds=(other.microseconds + + self.microseconds), + leapdays=other.leapdays or self.leapdays, + year=(other.year if other.year is not None + else self.year), + month=(other.month if other.month is not None + else self.month), + day=(other.day if other.day is not None + else self.day), + weekday=(other.weekday if other.weekday is not None + else self.weekday), + hour=(other.hour if other.hour is not None + else self.hour), + minute=(other.minute if other.minute is not None + else self.minute), + second=(other.second if other.second is not None + else self.second), + microsecond=(other.microsecond if other.microsecond + is not None else + self.microsecond)) + if isinstance(other, datetime.timedelta): + return self.__class__(years=self.years, + months=self.months, + days=self.days + other.days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds + other.seconds, + microseconds=self.microseconds + other.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + if not isinstance(other, datetime.date): + return NotImplemented + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth) - 1) * 7 + if nth > 0: + jumpdays += (7 - ret.weekday() + weekday) % 7 + else: + jumpdays += (ret.weekday() - weekday) % 7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented # In case the other object defines __rsub__ + return self.__class__(years=self.years - other.years, + months=self.months - other.months, + days=self.days - other.days, + hours=self.hours - other.hours, + minutes=self.minutes - other.minutes, + seconds=self.seconds - other.seconds, + microseconds=self.microseconds - other.microseconds, + leapdays=self.leapdays or other.leapdays, + year=(self.year if self.year is not None + else other.year), + month=(self.month if self.month is not None else + other.month), + day=(self.day if self.day is not None else + other.day), + weekday=(self.weekday if self.weekday is not None else + other.weekday), + hour=(self.hour if self.hour is not None else + other.hour), + minute=(self.minute if self.minute is not None else + other.minute), + second=(self.second if self.second is not None else + other.second), + microsecond=(self.microsecond if self.microsecond + is not None else + other.microsecond)) + + def __abs__(self): + return self.__class__(years=abs(self.years), + months=abs(self.months), + days=abs(self.days), + hours=abs(self.hours), + minutes=abs(self.minutes), + seconds=abs(self.seconds), + microseconds=abs(self.microseconds), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __neg__(self): + return self.__class__(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __bool__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + # Compatibility with Python 2.x + __nonzero__ = __bool__ + + def __mul__(self, other): + try: + f = float(other) + except TypeError: + return NotImplemented + + return self.__class__(years=int(self.years * f), + months=int(self.months * f), + days=int(self.days * f), + hours=int(self.hours * f), + minutes=int(self.minutes * f), + seconds=int(self.seconds * f), + microseconds=int(self.microseconds * f), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + __rmul__ = __mul__ + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.microseconds == other.microseconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __hash__(self): + return hash(( + self.weekday, + self.years, + self.months, + self.days, + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.leapdays, + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + )) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + try: + reciprocal = 1 / float(other) + except TypeError: + return NotImplemented + + return self.__mul__(reciprocal) + + __truediv__ = __div__ + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("{attr}={value:+g}".format(attr=attr, value=value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("{attr}={value}".format(attr=attr, value=repr(value))) + return "{classname}({attrs})".format(classname=self.__class__.__name__, + attrs=", ".join(l)) + + +def _sign(x): + return int(copysign(1, x)) + +# vim:ts=4:sw=4:et diff --git a/venv/lib/python3.12/site-packages/dateutil/rrule.py b/venv/lib/python3.12/site-packages/dateutil/rrule.py new file mode 100644 index 0000000..571a0d2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/rrule.py @@ -0,0 +1,1737 @@ +# -*- coding: utf-8 -*- +""" +The rrule module offers a small, complete, and very fast, implementation of +the recurrence rules documented in the +`iCalendar RFC `_, +including support for caching of results. +""" +import calendar +import datetime +import heapq +import itertools +import re +import sys +from functools import wraps +# For warning about deprecation of until and count +from warnings import warn + +from six import advance_iterator, integer_types + +from six.moves import _thread, range + +from ._common import weekday as weekdaybase + +try: + from math import gcd +except ImportError: + from fractions import gcd + +__all__ = ["rrule", "rruleset", "rrulestr", + "YEARLY", "MONTHLY", "WEEKLY", "DAILY", + "HOURLY", "MINUTELY", "SECONDLY", + "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +# Every mask is 7 days longer to handle cross-year weekly periods. +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) +M365MASK = list(M366MASK) +M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +MDAY365MASK = list(MDAY366MASK) +M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +NMDAY365MASK = list(NMDAY366MASK) +M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) +M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) +WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] +MDAY365MASK = tuple(MDAY365MASK) +M365MASK = tuple(M365MASK) + +FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'] + +(YEARLY, + MONTHLY, + WEEKLY, + DAILY, + HOURLY, + MINUTELY, + SECONDLY) = list(range(7)) + +# Imported on demand. +easter = None +parser = None + + +class weekday(weekdaybase): + """ + This version of weekday does not allow n = 0. + """ + def __init__(self, wkday, n=None): + if n == 0: + raise ValueError("Can't create weekday with n==0") + + super(weekday, self).__init__(wkday, n) + + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + + +def _invalidates_cache(f): + """ + Decorator for rruleset methods which may invalidate the + cached length. + """ + @wraps(f) + def inner_func(self, *args, **kwargs): + rv = f(self, *args, **kwargs) + self._invalidate_cache() + return rv + + return inner_func + + +class rrulebase(object): + def __init__(self, cache=False): + if cache: + self._cache = [] + self._cache_lock = _thread.allocate_lock() + self._invalidate_cache() + else: + self._cache = None + self._cache_complete = False + self._len = None + + def __iter__(self): + if self._cache_complete: + return iter(self._cache) + elif self._cache is None: + return self._iter() + else: + return self._iter_cached() + + def _invalidate_cache(self): + if self._cache is not None: + self._cache = [] + self._cache_complete = False + self._cache_gen = self._iter() + + if self._cache_lock.locked(): + self._cache_lock.release() + + self._len = None + + def _iter_cached(self): + i = 0 + gen = self._cache_gen + cache = self._cache + acquire = self._cache_lock.acquire + release = self._cache_lock.release + while gen: + if i == len(cache): + acquire() + if self._cache_complete: + break + try: + for j in range(10): + cache.append(advance_iterator(gen)) + except StopIteration: + self._cache_gen = gen = None + self._cache_complete = True + break + release() + yield cache[i] + i += 1 + while i < self._len: + yield cache[i] + i += 1 + + def __getitem__(self, item): + if self._cache_complete: + return self._cache[item] + elif isinstance(item, slice): + if item.step and item.step < 0: + return list(iter(self))[item] + else: + return list(itertools.islice(self, + item.start or 0, + item.stop or sys.maxsize, + item.step or 1)) + elif item >= 0: + gen = iter(self) + try: + for i in range(item+1): + res = advance_iterator(gen) + except StopIteration: + raise IndexError + return res + else: + return list(iter(self))[item] + + def __contains__(self, item): + if self._cache_complete: + return item in self._cache + else: + for i in self: + if i == item: + return True + elif i > item: + return False + return False + + # __len__() introduces a large performance penalty. + def count(self): + """ Returns the number of recurrences in this set. It will have go + through the whole recurrence, if this hasn't been done before. """ + if self._len is None: + for x in self: + pass + return self._len + + def before(self, dt, inc=False): + """ Returns the last recurrence before the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + last = None + if inc: + for i in gen: + if i > dt: + break + last = i + else: + for i in gen: + if i >= dt: + break + last = i + return last + + def after(self, dt, inc=False): + """ Returns the first recurrence after the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + if inc: + for i in gen: + if i >= dt: + return i + else: + for i in gen: + if i > dt: + return i + return None + + def xafter(self, dt, count=None, inc=False): + """ + Generator which yields up to `count` recurrences after the given + datetime instance, equivalent to `after`. + + :param dt: + The datetime at which to start generating recurrences. + + :param count: + The maximum number of recurrences to generate. If `None` (default), + dates are generated until the recurrence rule is exhausted. + + :param inc: + If `dt` is an instance of the rule and `inc` is `True`, it is + included in the output. + + :yields: Yields a sequence of `datetime` objects. + """ + + if self._cache_complete: + gen = self._cache + else: + gen = self + + # Select the comparison function + if inc: + comp = lambda dc, dtc: dc >= dtc + else: + comp = lambda dc, dtc: dc > dtc + + # Generate dates + n = 0 + for d in gen: + if comp(d, dt): + if count is not None: + n += 1 + if n > count: + break + + yield d + + def between(self, after, before, inc=False, count=1): + """ Returns all the occurrences of the rrule between after and before. + The inc keyword defines what happens if after and/or before are + themselves occurrences. With inc=True, they will be included in the + list, if they are found in the recurrence set. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + started = False + l = [] + if inc: + for i in gen: + if i > before: + break + elif not started: + if i >= after: + started = True + l.append(i) + else: + l.append(i) + else: + for i in gen: + if i >= before: + break + elif not started: + if i > after: + started = True + l.append(i) + else: + l.append(i) + return l + + +class rrule(rrulebase): + """ + That's the base of the rrule operation. It accepts all the keywords + defined in the RFC as its constructor parameters (except byday, + which was renamed to byweekday) and more. The constructor prototype is:: + + rrule(freq) + + Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, + or SECONDLY. + + .. note:: + Per RFC section 3.3.10, recurrence instances falling on invalid dates + and times are ignored rather than coerced: + + Recurrence rules may generate recurrence instances with an invalid + date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM + on a day where the local time is moved forward by an hour at 1:00 + AM). Such recurrence instances MUST be ignored and MUST NOT be + counted as part of the recurrence set. + + This can lead to possibly surprising behavior when, for example, the + start date occurs at the end of the month: + + >>> from dateutil.rrule import rrule, MONTHLY + >>> from datetime import datetime + >>> start_date = datetime(2014, 12, 31) + >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date)) + ... # doctest: +NORMALIZE_WHITESPACE + [datetime.datetime(2014, 12, 31, 0, 0), + datetime.datetime(2015, 1, 31, 0, 0), + datetime.datetime(2015, 3, 31, 0, 0), + datetime.datetime(2015, 5, 31, 0, 0)] + + Additionally, it supports the following keyword arguments: + + :param dtstart: + The recurrence start. Besides being the base for the recurrence, + missing parameters in the final recurrence instances will also be + extracted from this date. If not given, datetime.now() will be used + instead. + :param interval: + The interval between each freq iteration. For example, when using + YEARLY, an interval of 2 means once every two years, but with HOURLY, + it means once every two hours. The default interval is 1. + :param wkst: + The week start day. Must be one of the MO, TU, WE constants, or an + integer, specifying the first day of the week. This will affect + recurrences based on weekly periods. The default week start is got + from calendar.firstweekday(), and may be modified by + calendar.setfirstweekday(). + :param count: + If given, this determines how many occurrences will be generated. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param until: + If given, this must be a datetime instance specifying the upper-bound + limit of the recurrence. The last recurrence in the rule is the greatest + datetime that is less than or equal to the value specified in the + ``until`` parameter. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param bysetpos: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each given integer will specify an occurrence + number, corresponding to the nth occurrence of the rule inside the + frequency period. For example, a bysetpos of -1 if combined with a + MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will + result in the last work day of every month. + :param bymonth: + If given, it must be either an integer, or a sequence of integers, + meaning the months to apply the recurrence to. + :param bymonthday: + If given, it must be either an integer, or a sequence of integers, + meaning the month days to apply the recurrence to. + :param byyearday: + If given, it must be either an integer, or a sequence of integers, + meaning the year days to apply the recurrence to. + :param byeaster: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each integer will define an offset from the + Easter Sunday. Passing the offset 0 to byeaster will yield the Easter + Sunday itself. This is an extension to the RFC specification. + :param byweekno: + If given, it must be either an integer, or a sequence of integers, + meaning the week numbers to apply the recurrence to. Week numbers + have the meaning described in ISO8601, that is, the first week of + the year is that containing at least four days of the new year. + :param byweekday: + If given, it must be either an integer (0 == MO), a sequence of + integers, one of the weekday constants (MO, TU, etc), or a sequence + of these constants. When given, these variables will define the + weekdays where the recurrence will be applied. It's also possible to + use an argument n for the weekday instances, which will mean the nth + occurrence of this weekday in the period. For example, with MONTHLY, + or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the + first friday of the month where the recurrence happens. Notice that in + the RFC documentation, this is specified as BYDAY, but was renamed to + avoid the ambiguity of that keyword. + :param byhour: + If given, it must be either an integer, or a sequence of integers, + meaning the hours to apply the recurrence to. + :param byminute: + If given, it must be either an integer, or a sequence of integers, + meaning the minutes to apply the recurrence to. + :param bysecond: + If given, it must be either an integer, or a sequence of integers, + meaning the seconds to apply the recurrence to. + :param cache: + If given, it must be a boolean value specifying to enable or disable + caching of results. If you will use the same rrule instance multiple + times, enabling caching will improve the performance considerably. + """ + def __init__(self, freq, dtstart=None, + interval=1, wkst=None, count=None, until=None, bysetpos=None, + bymonth=None, bymonthday=None, byyearday=None, byeaster=None, + byweekno=None, byweekday=None, + byhour=None, byminute=None, bysecond=None, + cache=False): + super(rrule, self).__init__(cache) + global easter + if not dtstart: + if until and until.tzinfo: + dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) + else: + dtstart = datetime.datetime.now().replace(microsecond=0) + elif not isinstance(dtstart, datetime.datetime): + dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) + else: + dtstart = dtstart.replace(microsecond=0) + self._dtstart = dtstart + self._tzinfo = dtstart.tzinfo + self._freq = freq + self._interval = interval + self._count = count + + # Cache the original byxxx rules, if they are provided, as the _byxxx + # attributes do not necessarily map to the inputs, and this can be + # a problem in generating the strings. Only store things if they've + # been supplied (the string retrieval will just use .get()) + self._original_rule = {} + + if until and not isinstance(until, datetime.datetime): + until = datetime.datetime.fromordinal(until.toordinal()) + self._until = until + + if self._dtstart and self._until: + if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): + # According to RFC5545 Section 3.3.10: + # https://tools.ietf.org/html/rfc5545#section-3.3.10 + # + # > If the "DTSTART" property is specified as a date with UTC + # > time or a date with local time and time zone reference, + # > then the UNTIL rule part MUST be specified as a date with + # > UTC time. + raise ValueError( + 'RRULE UNTIL values must be specified in UTC when DTSTART ' + 'is timezone-aware' + ) + + if count is not None and until: + warn("Using both 'count' and 'until' is inconsistent with RFC 5545" + " and has been deprecated in dateutil. Future versions will " + "raise an error.", DeprecationWarning) + + if wkst is None: + self._wkst = calendar.firstweekday() + elif isinstance(wkst, integer_types): + self._wkst = wkst + else: + self._wkst = wkst.weekday + + if bysetpos is None: + self._bysetpos = None + elif isinstance(bysetpos, integer_types): + if bysetpos == 0 or not (-366 <= bysetpos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + self._bysetpos = (bysetpos,) + else: + self._bysetpos = tuple(bysetpos) + for pos in self._bysetpos: + if pos == 0 or not (-366 <= pos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + + if self._bysetpos: + self._original_rule['bysetpos'] = self._bysetpos + + if (byweekno is None and byyearday is None and bymonthday is None and + byweekday is None and byeaster is None): + if freq == YEARLY: + if bymonth is None: + bymonth = dtstart.month + self._original_rule['bymonth'] = None + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == MONTHLY: + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == WEEKLY: + byweekday = dtstart.weekday() + self._original_rule['byweekday'] = None + + # bymonth + if bymonth is None: + self._bymonth = None + else: + if isinstance(bymonth, integer_types): + bymonth = (bymonth,) + + self._bymonth = tuple(sorted(set(bymonth))) + + if 'bymonth' not in self._original_rule: + self._original_rule['bymonth'] = self._bymonth + + # byyearday + if byyearday is None: + self._byyearday = None + else: + if isinstance(byyearday, integer_types): + byyearday = (byyearday,) + + self._byyearday = tuple(sorted(set(byyearday))) + self._original_rule['byyearday'] = self._byyearday + + # byeaster + if byeaster is not None: + if not easter: + from dateutil import easter + if isinstance(byeaster, integer_types): + self._byeaster = (byeaster,) + else: + self._byeaster = tuple(sorted(byeaster)) + + self._original_rule['byeaster'] = self._byeaster + else: + self._byeaster = None + + # bymonthday + if bymonthday is None: + self._bymonthday = () + self._bynmonthday = () + else: + if isinstance(bymonthday, integer_types): + bymonthday = (bymonthday,) + + bymonthday = set(bymonthday) # Ensure it's unique + + self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0)) + self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0)) + + # Storing positive numbers first, then negative numbers + if 'bymonthday' not in self._original_rule: + self._original_rule['bymonthday'] = tuple( + itertools.chain(self._bymonthday, self._bynmonthday)) + + # byweekno + if byweekno is None: + self._byweekno = None + else: + if isinstance(byweekno, integer_types): + byweekno = (byweekno,) + + self._byweekno = tuple(sorted(set(byweekno))) + + self._original_rule['byweekno'] = self._byweekno + + # byweekday / bynweekday + if byweekday is None: + self._byweekday = None + self._bynweekday = None + else: + # If it's one of the valid non-sequence types, convert to a + # single-element sequence before the iterator that builds the + # byweekday set. + if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): + byweekday = (byweekday,) + + self._byweekday = set() + self._bynweekday = set() + for wday in byweekday: + if isinstance(wday, integer_types): + self._byweekday.add(wday) + elif not wday.n or freq > MONTHLY: + self._byweekday.add(wday.weekday) + else: + self._bynweekday.add((wday.weekday, wday.n)) + + if not self._byweekday: + self._byweekday = None + elif not self._bynweekday: + self._bynweekday = None + + if self._byweekday is not None: + self._byweekday = tuple(sorted(self._byweekday)) + orig_byweekday = [weekday(x) for x in self._byweekday] + else: + orig_byweekday = () + + if self._bynweekday is not None: + self._bynweekday = tuple(sorted(self._bynweekday)) + orig_bynweekday = [weekday(*x) for x in self._bynweekday] + else: + orig_bynweekday = () + + if 'byweekday' not in self._original_rule: + self._original_rule['byweekday'] = tuple(itertools.chain( + orig_byweekday, orig_bynweekday)) + + # byhour + if byhour is None: + if freq < HOURLY: + self._byhour = {dtstart.hour} + else: + self._byhour = None + else: + if isinstance(byhour, integer_types): + byhour = (byhour,) + + if freq == HOURLY: + self._byhour = self.__construct_byset(start=dtstart.hour, + byxxx=byhour, + base=24) + else: + self._byhour = set(byhour) + + self._byhour = tuple(sorted(self._byhour)) + self._original_rule['byhour'] = self._byhour + + # byminute + if byminute is None: + if freq < MINUTELY: + self._byminute = {dtstart.minute} + else: + self._byminute = None + else: + if isinstance(byminute, integer_types): + byminute = (byminute,) + + if freq == MINUTELY: + self._byminute = self.__construct_byset(start=dtstart.minute, + byxxx=byminute, + base=60) + else: + self._byminute = set(byminute) + + self._byminute = tuple(sorted(self._byminute)) + self._original_rule['byminute'] = self._byminute + + # bysecond + if bysecond is None: + if freq < SECONDLY: + self._bysecond = ((dtstart.second,)) + else: + self._bysecond = None + else: + if isinstance(bysecond, integer_types): + bysecond = (bysecond,) + + self._bysecond = set(bysecond) + + if freq == SECONDLY: + self._bysecond = self.__construct_byset(start=dtstart.second, + byxxx=bysecond, + base=60) + else: + self._bysecond = set(bysecond) + + self._bysecond = tuple(sorted(self._bysecond)) + self._original_rule['bysecond'] = self._bysecond + + if self._freq >= HOURLY: + self._timeset = None + else: + self._timeset = [] + for hour in self._byhour: + for minute in self._byminute: + for second in self._bysecond: + self._timeset.append( + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) + self._timeset.sort() + self._timeset = tuple(self._timeset) + + def __str__(self): + """ + Output a string that would generate this RRULE if passed to rrulestr. + This is mostly compatible with RFC5545, except for the + dateutil-specific extension BYEASTER. + """ + + output = [] + h, m, s = [None] * 3 + if self._dtstart: + output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) + h, m, s = self._dtstart.timetuple()[3:6] + + parts = ['FREQ=' + FREQNAMES[self._freq]] + if self._interval != 1: + parts.append('INTERVAL=' + str(self._interval)) + + if self._wkst: + parts.append('WKST=' + repr(weekday(self._wkst))[0:2]) + + if self._count is not None: + parts.append('COUNT=' + str(self._count)) + + if self._until: + parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) + + if self._original_rule.get('byweekday') is not None: + # The str() method on weekday objects doesn't generate + # RFC5545-compliant strings, so we should modify that. + original_rule = dict(self._original_rule) + wday_strings = [] + for wday in original_rule['byweekday']: + if wday.n: + wday_strings.append('{n:+d}{wday}'.format( + n=wday.n, + wday=repr(wday)[0:2])) + else: + wday_strings.append(repr(wday)) + + original_rule['byweekday'] = wday_strings + else: + original_rule = self._original_rule + + partfmt = '{name}={vals}' + for name, key in [('BYSETPOS', 'bysetpos'), + ('BYMONTH', 'bymonth'), + ('BYMONTHDAY', 'bymonthday'), + ('BYYEARDAY', 'byyearday'), + ('BYWEEKNO', 'byweekno'), + ('BYDAY', 'byweekday'), + ('BYHOUR', 'byhour'), + ('BYMINUTE', 'byminute'), + ('BYSECOND', 'bysecond'), + ('BYEASTER', 'byeaster')]: + value = original_rule.get(key) + if value: + parts.append(partfmt.format(name=name, vals=(','.join(str(v) + for v in value)))) + + output.append('RRULE:' + ';'.join(parts)) + return '\n'.join(output) + + def replace(self, **kwargs): + """Return new rrule with same attributes except for those attributes given new + values by whichever keyword arguments are specified.""" + new_kwargs = {"interval": self._interval, + "count": self._count, + "dtstart": self._dtstart, + "freq": self._freq, + "until": self._until, + "wkst": self._wkst, + "cache": False if self._cache is None else True } + new_kwargs.update(self._original_rule) + new_kwargs.update(kwargs) + return rrule(**new_kwargs) + + def _iter(self): + year, month, day, hour, minute, second, weekday, yearday, _ = \ + self._dtstart.timetuple() + + # Some local variables to speed things up a bit + freq = self._freq + interval = self._interval + wkst = self._wkst + until = self._until + bymonth = self._bymonth + byweekno = self._byweekno + byyearday = self._byyearday + byweekday = self._byweekday + byeaster = self._byeaster + bymonthday = self._bymonthday + bynmonthday = self._bynmonthday + bysetpos = self._bysetpos + byhour = self._byhour + byminute = self._byminute + bysecond = self._bysecond + + ii = _iterinfo(self) + ii.rebuild(year, month) + + getdayset = {YEARLY: ii.ydayset, + MONTHLY: ii.mdayset, + WEEKLY: ii.wdayset, + DAILY: ii.ddayset, + HOURLY: ii.ddayset, + MINUTELY: ii.ddayset, + SECONDLY: ii.ddayset}[freq] + + if freq < HOURLY: + timeset = self._timeset + else: + gettimeset = {HOURLY: ii.htimeset, + MINUTELY: ii.mtimeset, + SECONDLY: ii.stimeset}[freq] + if ((freq >= HOURLY and + self._byhour and hour not in self._byhour) or + (freq >= MINUTELY and + self._byminute and minute not in self._byminute) or + (freq >= SECONDLY and + self._bysecond and second not in self._bysecond)): + timeset = () + else: + timeset = gettimeset(hour, minute, second) + + total = 0 + count = self._count + while True: + # Get dayset with the right frequency + dayset, start, end = getdayset(year, month, day) + + # Do the "hard" work ;-) + filtered = False + for i in dayset[start:end]: + if ((bymonth and ii.mmask[i] not in bymonth) or + (byweekno and not ii.wnomask[i]) or + (byweekday and ii.wdaymask[i] not in byweekday) or + (ii.nwdaymask and not ii.nwdaymask[i]) or + (byeaster and not ii.eastermask[i]) or + ((bymonthday or bynmonthday) and + ii.mdaymask[i] not in bymonthday and + ii.nmdaymask[i] not in bynmonthday) or + (byyearday and + ((i < ii.yearlen and i+1 not in byyearday and + -ii.yearlen+i not in byyearday) or + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and + -ii.nextyearlen+i-ii.yearlen not in byyearday)))): + dayset[i] = None + filtered = True + + # Output results + if bysetpos and timeset: + poslist = [] + for pos in bysetpos: + if pos < 0: + daypos, timepos = divmod(pos, len(timeset)) + else: + daypos, timepos = divmod(pos-1, len(timeset)) + try: + i = [x for x in dayset[start:end] + if x is not None][daypos] + time = timeset[timepos] + except IndexError: + pass + else: + date = datetime.date.fromordinal(ii.yearordinal+i) + res = datetime.datetime.combine(date, time) + if res not in poslist: + poslist.append(res) + poslist.sort() + for res in poslist: + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + total += 1 + yield res + else: + for i in dayset[start:end]: + if i is not None: + date = datetime.date.fromordinal(ii.yearordinal + i) + for time in timeset: + res = datetime.datetime.combine(date, time) + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + + total += 1 + yield res + + # Handle frequency and interval + fixday = False + if freq == YEARLY: + year += interval + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == MONTHLY: + month += interval + if month > 12: + div, mod = divmod(month, 12) + month = mod + year += div + if month == 0: + month = 12 + year -= 1 + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == WEEKLY: + if wkst > weekday: + day += -(weekday+1+(6-wkst))+self._interval*7 + else: + day += -(weekday-wkst)+self._interval*7 + weekday = wkst + fixday = True + elif freq == DAILY: + day += interval + fixday = True + elif freq == HOURLY: + if filtered: + # Jump to one iteration before next day + hour += ((23-hour)//interval)*interval + + if byhour: + ndays, hour = self.__mod_distance(value=hour, + byxxx=self._byhour, + base=24) + else: + ndays, hour = divmod(hour+interval, 24) + + if ndays: + day += ndays + fixday = True + + timeset = gettimeset(hour, minute, second) + elif freq == MINUTELY: + if filtered: + # Jump to one iteration before next day + minute += ((1439-(hour*60+minute))//interval)*interval + + valid = False + rep_rate = (24*60) + for j in range(rep_rate // gcd(interval, rep_rate)): + if byminute: + nhours, minute = \ + self.__mod_distance(value=minute, + byxxx=self._byminute, + base=60) + else: + nhours, minute = divmod(minute+interval, 60) + + div, hour = divmod(hour+nhours, 24) + if div: + day += div + fixday = True + filtered = False + + if not byhour or hour in byhour: + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval and ' + + 'byhour resulting in empty rule.') + + timeset = gettimeset(hour, minute, second) + elif freq == SECONDLY: + if filtered: + # Jump to one iteration before next day + second += (((86399 - (hour * 3600 + minute * 60 + second)) + // interval) * interval) + + rep_rate = (24 * 3600) + valid = False + for j in range(0, rep_rate // gcd(interval, rep_rate)): + if bysecond: + nminutes, second = \ + self.__mod_distance(value=second, + byxxx=self._bysecond, + base=60) + else: + nminutes, second = divmod(second+interval, 60) + + div, minute = divmod(minute+nminutes, 60) + if div: + hour += div + div, hour = divmod(hour, 24) + if div: + day += div + fixday = True + + if ((not byhour or hour in byhour) and + (not byminute or minute in byminute) and + (not bysecond or second in bysecond)): + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval, ' + + 'byhour and byminute resulting in empty' + + ' rule.') + + timeset = gettimeset(hour, minute, second) + + if fixday and day > 28: + daysinmonth = calendar.monthrange(year, month)[1] + if day > daysinmonth: + while day > daysinmonth: + day -= daysinmonth + month += 1 + if month == 13: + month = 1 + year += 1 + if year > datetime.MAXYEAR: + self._len = total + return + daysinmonth = calendar.monthrange(year, month)[1] + ii.rebuild(year, month) + + def __construct_byset(self, start, byxxx, base): + """ + If a `BYXXX` sequence is passed to the constructor at the same level as + `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some + specifications which cannot be reached given some starting conditions. + + This occurs whenever the interval is not coprime with the base of a + given unit and the difference between the starting position and the + ending position is not coprime with the greatest common denominator + between the interval and the base. For example, with a FREQ of hourly + starting at 17:00 and an interval of 4, the only valid values for + BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not + coprime. + + :param start: + Specifies the starting position. + :param byxxx: + An iterable containing the list of allowed values. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + This does not preserve the type of the iterable, returning a set, since + the values should be unique and the order is irrelevant, this will + speed up later lookups. + + In the event of an empty set, raises a :exception:`ValueError`, as this + results in an empty rrule. + """ + + cset = set() + + # Support a single byxxx value. + if isinstance(byxxx, integer_types): + byxxx = (byxxx, ) + + for num in byxxx: + i_gcd = gcd(self._interval, base) + # Use divmod rather than % because we need to wrap negative nums. + if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: + cset.add(num) + + if len(cset) == 0: + raise ValueError("Invalid rrule byxxx generates an empty set.") + + return cset + + def __mod_distance(self, value, byxxx, base): + """ + Calculates the next value in a sequence where the `FREQ` parameter is + specified along with a `BYXXX` parameter at the same "level" + (e.g. `HOURLY` specified with `BYHOUR`). + + :param value: + The old value of the component. + :param byxxx: + The `BYXXX` set, which should have been generated by + `rrule._construct_byset`, or something else which checks that a + valid rule is present. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + If a valid value is not found after `base` iterations (the maximum + number before the sequence would start to repeat), this raises a + :exception:`ValueError`, as no valid values were found. + + This returns a tuple of `divmod(n*interval, base)`, where `n` is the + smallest number of `interval` repetitions until the next specified + value in `byxxx` is found. + """ + accumulator = 0 + for ii in range(1, base + 1): + # Using divmod() over % to account for negative intervals + div, value = divmod(value + self._interval, base) + accumulator += div + if value in byxxx: + return (accumulator, value) + + +class _iterinfo(object): + __slots__ = ["rrule", "lastyear", "lastmonth", + "yearlen", "nextyearlen", "yearordinal", "yearweekday", + "mmask", "mrange", "mdaymask", "nmdaymask", + "wdaymask", "wnomask", "nwdaymask", "eastermask"] + + def __init__(self, rrule): + for attr in self.__slots__: + setattr(self, attr, None) + self.rrule = rrule + + def rebuild(self, year, month): + # Every mask is 7 days longer to handle cross-year weekly periods. + rr = self.rrule + if year != self.lastyear: + self.yearlen = 365 + calendar.isleap(year) + self.nextyearlen = 365 + calendar.isleap(year + 1) + firstyday = datetime.date(year, 1, 1) + self.yearordinal = firstyday.toordinal() + self.yearweekday = firstyday.weekday() + + wday = datetime.date(year, 1, 1).weekday() + if self.yearlen == 365: + self.mmask = M365MASK + self.mdaymask = MDAY365MASK + self.nmdaymask = NMDAY365MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M365RANGE + else: + self.mmask = M366MASK + self.mdaymask = MDAY366MASK + self.nmdaymask = NMDAY366MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M366RANGE + + if not rr._byweekno: + self.wnomask = None + else: + self.wnomask = [0]*(self.yearlen+7) + # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 + if no1wkst >= 4: + no1wkst = 0 + # Number of days in the year, plus the days we got + # from last year. + wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 + else: + # Number of days in the year, minus the days we + # left in last year. + wyearlen = self.yearlen-no1wkst + div, mod = divmod(wyearlen, 7) + numweeks = div+mod//4 + for n in rr._byweekno: + if n < 0: + n += numweeks+1 + if not (0 < n <= numweeks): + continue + if n > 1: + i = no1wkst+(n-1)*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + else: + i = no1wkst + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if 1 in rr._byweekno: + # Check week number 1 of next year as well + # TODO: Check -numweeks for next year. + i = no1wkst+numweeks*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + if i < self.yearlen: + # If week starts in next year, we + # don't care about it. + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if no1wkst: + # Check last week number of last year as + # well. If no1wkst is 0, either the year + # started on week start, or week number 1 + # got days from last year, so there are no + # days from last year's last week number in + # this year. + if -1 not in rr._byweekno: + lyearweekday = datetime.date(year-1, 1, 1).weekday() + lno1wkst = (7-lyearweekday+rr._wkst) % 7 + lyearlen = 365+calendar.isleap(year-1) + if lno1wkst >= 4: + lno1wkst = 0 + lnumweeks = 52+(lyearlen + + (lyearweekday-rr._wkst) % 7) % 7//4 + else: + lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 + else: + lnumweeks = -1 + if lnumweeks in rr._byweekno: + for i in range(no1wkst): + self.wnomask[i] = 1 + + if (rr._bynweekday and (month != self.lastmonth or + year != self.lastyear)): + ranges = [] + if rr._freq == YEARLY: + if rr._bymonth: + for month in rr._bymonth: + ranges.append(self.mrange[month-1:month+1]) + else: + ranges = [(0, self.yearlen)] + elif rr._freq == MONTHLY: + ranges = [self.mrange[month-1:month+1]] + if ranges: + # Weekly frequency won't get here, so we may not + # care about cross-year weekly periods. + self.nwdaymask = [0]*self.yearlen + for first, last in ranges: + last -= 1 + for wday, n in rr._bynweekday: + if n < 0: + i = last+(n+1)*7 + i -= (self.wdaymask[i]-wday) % 7 + else: + i = first+(n-1)*7 + i += (7-self.wdaymask[i]+wday) % 7 + if first <= i <= last: + self.nwdaymask[i] = 1 + + if rr._byeaster: + self.eastermask = [0]*(self.yearlen+7) + eyday = easter.easter(year).toordinal()-self.yearordinal + for offset in rr._byeaster: + self.eastermask[eyday+offset] = 1 + + self.lastyear = year + self.lastmonth = month + + def ydayset(self, year, month, day): + return list(range(self.yearlen)), 0, self.yearlen + + def mdayset(self, year, month, day): + dset = [None]*self.yearlen + start, end = self.mrange[month-1:month+1] + for i in range(start, end): + dset[i] = i + return dset, start, end + + def wdayset(self, year, month, day): + # We need to handle cross-year weeks here. + dset = [None]*(self.yearlen+7) + i = datetime.date(year, month, day).toordinal()-self.yearordinal + start = i + for j in range(7): + dset[i] = i + i += 1 + # if (not (0 <= i < self.yearlen) or + # self.wdaymask[i] == self.rrule._wkst): + # This will cross the year boundary, if necessary. + if self.wdaymask[i] == self.rrule._wkst: + break + return dset, start, i + + def ddayset(self, year, month, day): + dset = [None] * self.yearlen + i = datetime.date(year, month, day).toordinal() - self.yearordinal + dset[i] = i + return dset, i, i + 1 + + def htimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for minute in rr._byminute: + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def mtimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def stimeset(self, hour, minute, second): + return (datetime.time(hour, minute, second, + tzinfo=self.rrule._tzinfo),) + + +class rruleset(rrulebase): + """ The rruleset type allows more complex recurrence setups, mixing + multiple rules, dates, exclusion rules, and exclusion dates. The type + constructor takes the following keyword arguments: + + :param cache: If True, caching of results will be enabled, improving + performance of multiple queries considerably. """ + + class _genitem(object): + def __init__(self, genlist, gen): + try: + self.dt = advance_iterator(gen) + genlist.append(self) + except StopIteration: + pass + self.genlist = genlist + self.gen = gen + + def __next__(self): + try: + self.dt = advance_iterator(self.gen) + except StopIteration: + if self.genlist[0] is self: + heapq.heappop(self.genlist) + else: + self.genlist.remove(self) + heapq.heapify(self.genlist) + + next = __next__ + + def __lt__(self, other): + return self.dt < other.dt + + def __gt__(self, other): + return self.dt > other.dt + + def __eq__(self, other): + return self.dt == other.dt + + def __ne__(self, other): + return self.dt != other.dt + + def __init__(self, cache=False): + super(rruleset, self).__init__(cache) + self._rrule = [] + self._rdate = [] + self._exrule = [] + self._exdate = [] + + @_invalidates_cache + def rrule(self, rrule): + """ Include the given :py:class:`rrule` instance in the recurrence set + generation. """ + self._rrule.append(rrule) + + @_invalidates_cache + def rdate(self, rdate): + """ Include the given :py:class:`datetime` instance in the recurrence + set generation. """ + self._rdate.append(rdate) + + @_invalidates_cache + def exrule(self, exrule): + """ Include the given rrule instance in the recurrence set exclusion + list. Dates which are part of the given recurrence rules will not + be generated, even if some inclusive rrule or rdate matches them. + """ + self._exrule.append(exrule) + + @_invalidates_cache + def exdate(self, exdate): + """ Include the given datetime instance in the recurrence set + exclusion list. Dates included that way will not be generated, + even if some inclusive rrule or rdate matches them. """ + self._exdate.append(exdate) + + def _iter(self): + rlist = [] + self._rdate.sort() + self._genitem(rlist, iter(self._rdate)) + for gen in [iter(x) for x in self._rrule]: + self._genitem(rlist, gen) + exlist = [] + self._exdate.sort() + self._genitem(exlist, iter(self._exdate)) + for gen in [iter(x) for x in self._exrule]: + self._genitem(exlist, gen) + lastdt = None + total = 0 + heapq.heapify(rlist) + heapq.heapify(exlist) + while rlist: + ritem = rlist[0] + if not lastdt or lastdt != ritem.dt: + while exlist and exlist[0] < ritem: + exitem = exlist[0] + advance_iterator(exitem) + if exlist and exlist[0] is exitem: + heapq.heapreplace(exlist, exitem) + if not exlist or ritem != exlist[0]: + total += 1 + yield ritem.dt + lastdt = ritem.dt + advance_iterator(ritem) + if rlist and rlist[0] is ritem: + heapq.heapreplace(rlist, ritem) + self._len = total + + + + +class _rrulestr(object): + """ Parses a string representation of a recurrence rule or set of + recurrence rules. + + :param s: + Required, a string defining one or more recurrence rules. + + :param dtstart: + If given, used as the default recurrence start if not specified in the + rule string. + + :param cache: + If set ``True`` caching of results will be enabled, improving + performance of multiple queries considerably. + + :param unfold: + If set ``True`` indicates that a rule string is split over more + than one line and should be joined before processing. + + :param forceset: + If set ``True`` forces a :class:`dateutil.rrule.rruleset` to + be returned. + + :param compatible: + If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime.datetime` object is returned. + + :param tzids: + If given, a callable or mapping used to retrieve a + :class:`datetime.tzinfo` from a string representation. + Defaults to :func:`dateutil.tz.gettz`. + + :param tzinfos: + Additional time zone names / aliases which may be present in a string + representation. See :func:`dateutil.parser.parse` for more + information. + + :return: + Returns a :class:`dateutil.rrule.rruleset` or + :class:`dateutil.rrule.rrule` + """ + + _freq_map = {"YEARLY": YEARLY, + "MONTHLY": MONTHLY, + "WEEKLY": WEEKLY, + "DAILY": DAILY, + "HOURLY": HOURLY, + "MINUTELY": MINUTELY, + "SECONDLY": SECONDLY} + + _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, + "FR": 4, "SA": 5, "SU": 6} + + def _handle_int(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = int(value) + + def _handle_int_list(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = [int(x) for x in value.split(',')] + + _handle_INTERVAL = _handle_int + _handle_COUNT = _handle_int + _handle_BYSETPOS = _handle_int_list + _handle_BYMONTH = _handle_int_list + _handle_BYMONTHDAY = _handle_int_list + _handle_BYYEARDAY = _handle_int_list + _handle_BYEASTER = _handle_int_list + _handle_BYWEEKNO = _handle_int_list + _handle_BYHOUR = _handle_int_list + _handle_BYMINUTE = _handle_int_list + _handle_BYSECOND = _handle_int_list + + def _handle_FREQ(self, rrkwargs, name, value, **kwargs): + rrkwargs["freq"] = self._freq_map[value] + + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): + global parser + if not parser: + from dateutil import parser + try: + rrkwargs["until"] = parser.parse(value, + ignoretz=kwargs.get("ignoretz"), + tzinfos=kwargs.get("tzinfos")) + except ValueError: + raise ValueError("invalid until date") + + def _handle_WKST(self, rrkwargs, name, value, **kwargs): + rrkwargs["wkst"] = self._weekday_map[value] + + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): + """ + Two ways to specify this: +1MO or MO(+1) + """ + l = [] + for wday in value.split(','): + if '(' in wday: + # If it's of the form TH(+1), etc. + splt = wday.split('(') + w = splt[0] + n = int(splt[1][:-1]) + elif len(wday): + # If it's of the form +1MO + for i in range(len(wday)): + if wday[i] not in '+-0123456789': + break + n = wday[:i] or None + w = wday[i:] + if n: + n = int(n) + else: + raise ValueError("Invalid (empty) BYDAY specification.") + + l.append(weekdays[self._weekday_map[w]](n)) + rrkwargs["byweekday"] = l + + _handle_BYDAY = _handle_BYWEEKDAY + + def _parse_rfc_rrule(self, line, + dtstart=None, + cache=False, + ignoretz=False, + tzinfos=None): + if line.find(':') != -1: + name, value = line.split(':') + if name != "RRULE": + raise ValueError("unknown parameter name") + else: + value = line + rrkwargs = {} + for pair in value.split(';'): + name, value = pair.split('=') + name = name.upper() + value = value.upper() + try: + getattr(self, "_handle_"+name)(rrkwargs, name, value, + ignoretz=ignoretz, + tzinfos=tzinfos) + except AttributeError: + raise ValueError("unknown parameter '%s'" % name) + except (KeyError, ValueError): + raise ValueError("invalid '%s': %s" % (name, value)) + return rrule(dtstart=dtstart, cache=cache, **rrkwargs) + + def _parse_date_value(self, date_value, parms, rule_tzids, + ignoretz, tzids, tzinfos): + global parser + if not parser: + from dateutil import parser + + datevals = [] + value_found = False + TZID = None + + for parm in parms: + if parm.startswith("TZID="): + try: + tzkey = rule_tzids[parm.split('TZID=')[-1]] + except KeyError: + continue + if tzids is None: + from . import tz + tzlookup = tz.gettz + elif callable(tzids): + tzlookup = tzids + else: + tzlookup = getattr(tzids, 'get', None) + if tzlookup is None: + msg = ('tzids must be a callable, mapping, or None, ' + 'not %s' % tzids) + raise ValueError(msg) + + TZID = tzlookup(tzkey) + continue + + # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found + # only once. + if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}: + raise ValueError("unsupported parm: " + parm) + else: + if value_found: + msg = ("Duplicate value parameter found in: " + parm) + raise ValueError(msg) + value_found = True + + for datestr in date_value.split(','): + date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos) + if TZID is not None: + if date.tzinfo is None: + date = date.replace(tzinfo=TZID) + else: + raise ValueError('DTSTART/EXDATE specifies multiple timezone') + datevals.append(date) + + return datevals + + def _parse_rfc(self, s, + dtstart=None, + cache=False, + unfold=False, + forceset=False, + compatible=False, + ignoretz=False, + tzids=None, + tzinfos=None): + global parser + if compatible: + forceset = True + unfold = True + + TZID_NAMES = dict(map( + lambda x: (x.upper(), x), + re.findall('TZID=(?P[^:]+):', s) + )) + s = s.upper() + if not s.strip(): + raise ValueError("empty string") + if unfold: + lines = s.splitlines() + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + else: + lines = s.split() + if (not forceset and len(lines) == 1 and (s.find(':') == -1 or + s.startswith('RRULE:'))): + return self._parse_rfc_rrule(lines[0], cache=cache, + dtstart=dtstart, ignoretz=ignoretz, + tzinfos=tzinfos) + else: + rrulevals = [] + rdatevals = [] + exrulevals = [] + exdatevals = [] + for line in lines: + if not line: + continue + if line.find(':') == -1: + name = "RRULE" + value = line + else: + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0] + parms = parms[1:] + if name == "RRULE": + for parm in parms: + raise ValueError("unsupported RRULE parm: "+parm) + rrulevals.append(value) + elif name == "RDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + rdatevals.append(value) + elif name == "EXRULE": + for parm in parms: + raise ValueError("unsupported EXRULE parm: "+parm) + exrulevals.append(value) + elif name == "EXDATE": + exdatevals.extend( + self._parse_date_value(value, parms, + TZID_NAMES, ignoretz, + tzids, tzinfos) + ) + elif name == "DTSTART": + dtvals = self._parse_date_value(value, parms, TZID_NAMES, + ignoretz, tzids, tzinfos) + if len(dtvals) != 1: + raise ValueError("Multiple DTSTART values specified:" + + value) + dtstart = dtvals[0] + else: + raise ValueError("unsupported property: "+name) + if (forceset or len(rrulevals) > 1 or rdatevals + or exrulevals or exdatevals): + if not parser and (rdatevals or exdatevals): + from dateutil import parser + rset = rruleset(cache=cache) + for value in rrulevals: + rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in rdatevals: + for datestr in value.split(','): + rset.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exrulevals: + rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exdatevals: + rset.exdate(value) + if compatible and dtstart: + rset.rdate(dtstart) + return rset + else: + return self._parse_rfc_rrule(rrulevals[0], + dtstart=dtstart, + cache=cache, + ignoretz=ignoretz, + tzinfos=tzinfos) + + def __call__(self, s, **kwargs): + return self._parse_rfc(s, **kwargs) + + +rrulestr = _rrulestr() + +# vim:ts=4:sw=4:et diff --git a/venv/lib/python3.12/site-packages/dateutil/tz/__init__.py b/venv/lib/python3.12/site-packages/dateutil/tz/__init__.py new file mode 100644 index 0000000..af1352c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tz/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from .tz import * +from .tz import __doc__ + +__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", + "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", + "enfold", "datetime_ambiguous", "datetime_exists", + "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] + + +class DeprecatedTzFormatWarning(Warning): + """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/venv/lib/python3.12/site-packages/dateutil/tz/_common.py b/venv/lib/python3.12/site-packages/dateutil/tz/_common.py new file mode 100644 index 0000000..e6ac118 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tz/_common.py @@ -0,0 +1,419 @@ +from six import PY2 + +from functools import wraps + +from datetime import datetime, timedelta, tzinfo + + +ZERO = timedelta(0) + +__all__ = ['tzname_in_python2', 'enfold'] + + +def tzname_in_python2(namefunc): + """Change unicode output into bytestrings in Python 2 + + tzname() API changed in Python 3. It used to return bytes, but was changed + to unicode strings + """ + if PY2: + @wraps(namefunc) + def adjust_encoding(*args, **kwargs): + name = namefunc(*args, **kwargs) + if name is not None: + name = name.encode() + + return name + + return adjust_encoding + else: + return namefunc + + +# The following is adapted from Alexander Belopolsky's tz library +# https://github.com/abalkin/tz +if hasattr(datetime, 'fold'): + # This is the pre-python 3.6 fold situation + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + return dt.replace(fold=fold) + +else: + class _DatetimeWithFold(datetime): + """ + This is a class designed to provide a PEP 495-compliant interface for + Python versions before 3.6. It is used only for dates in a fold, so + the ``fold`` attribute is fixed at ``1``. + + .. versionadded:: 2.6.0 + """ + __slots__ = () + + def replace(self, *args, **kwargs): + """ + Return a datetime with the same attributes, except for those + attributes given new values by whichever keyword arguments are + specified. Note that tzinfo=None can be specified to create a naive + datetime from an aware datetime with no conversion of date and time + data. + + This is reimplemented in ``_DatetimeWithFold`` because pypy3 will + return a ``datetime.datetime`` even if ``fold`` is unchanged. + """ + argnames = ( + 'year', 'month', 'day', 'hour', 'minute', 'second', + 'microsecond', 'tzinfo' + ) + + for arg, argname in zip(args, argnames): + if argname in kwargs: + raise TypeError('Duplicate argument: {}'.format(argname)) + + kwargs[argname] = arg + + for argname in argnames: + if argname not in kwargs: + kwargs[argname] = getattr(self, argname) + + dt_class = self.__class__ if kwargs.get('fold', 1) else datetime + + return dt_class(**kwargs) + + @property + def fold(self): + return 1 + + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + if getattr(dt, 'fold', 0) == fold: + return dt + + args = dt.timetuple()[:6] + args += (dt.microsecond, dt.tzinfo) + + if fold: + return _DatetimeWithFold(*args) + else: + return datetime(*args) + + +def _validate_fromutc_inputs(f): + """ + The CPython version of ``fromutc`` checks that the input is a ``datetime`` + object and that ``self`` is attached as its ``tzinfo``. + """ + @wraps(f) + def fromutc(self, dt): + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + return f(self, dt) + + return fromutc + + +class _tzinfo(tzinfo): + """ + Base class for all ``dateutil`` ``tzinfo`` objects. + """ + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + + dt = dt.replace(tzinfo=self) + + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) + + return same_dt and not same_offset + + def _fold_status(self, dt_utc, dt_wall): + """ + Determine the fold status of a "wall" datetime, given a representation + of the same datetime as a (naive) UTC datetime. This is calculated based + on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all + datetimes, and that this offset is the actual number of hours separating + ``dt_utc`` and ``dt_wall``. + + :param dt_utc: + Representation of the datetime as UTC + + :param dt_wall: + Representation of the datetime as "wall time". This parameter must + either have a `fold` attribute or have a fold-naive + :class:`datetime.tzinfo` attached, otherwise the calculation may + fail. + """ + if self.is_ambiguous(dt_wall): + delta_wall = dt_wall - dt_utc + _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) + else: + _fold = 0 + + return _fold + + def _fold(self, dt): + return getattr(dt, 'fold', 0) + + def _fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + + # Re-implement the algorithm from Python's datetime.py + dtoff = dt.utcoffset() + if dtoff is None: + raise ValueError("fromutc() requires a non-None utcoffset() " + "result") + + # The original datetime.py code assumes that `dst()` defaults to + # zero during ambiguous times. PEP 495 inverts this presumption, so + # for pre-PEP 495 versions of python, we need to tweak the algorithm. + dtdst = dt.dst() + if dtdst is None: + raise ValueError("fromutc() requires a non-None dst() result") + delta = dtoff - dtdst + + dt += delta + # Set fold=1 so we can default to being in the fold for + # ambiguous dates. + dtdst = enfold(dt, fold=1).dst() + if dtdst is None: + raise ValueError("fromutc(): dt.dst gave inconsistent " + "results; cannot convert") + return dt + dtdst + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + dt_wall = self._fromutc(dt) + + # Calculate the fold status given the two datetimes. + _fold = self._fold_status(dt, dt_wall) + + # Set the default fold value for ambiguous dates + return enfold(dt_wall, fold=_fold) + + +class tzrangebase(_tzinfo): + """ + This is an abstract base class for time zones represented by an annual + transition into and out of DST. Child classes should implement the following + methods: + + * ``__init__(self, *args, **kwargs)`` + * ``transitions(self, year)`` - this is expected to return a tuple of + datetimes representing the DST on and off transitions in standard + time. + + A fully initialized ``tzrangebase`` subclass should also provide the + following attributes: + * ``hasdst``: Boolean whether or not the zone uses DST. + * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects + representing the respective UTC offsets. + * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short + abbreviations in DST and STD, respectively. + * ``_hasdst``: Whether or not the zone has DST. + + .. versionadded:: 2.6.0 + """ + def __init__(self): + raise NotImplementedError('tzrangebase is an abstract base class') + + def utcoffset(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_base_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + if self._isdst(dt): + return self._dst_abbr + else: + return self._std_abbr + + def fromutc(self, dt): + """ Given a datetime in UTC, return local time """ + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # Get transitions - if there are none, fixed offset + transitions = self.transitions(dt.year) + if transitions is None: + return dt + self.utcoffset(dt) + + # Get the transition times in UTC + dston, dstoff = transitions + + dston -= self._std_offset + dstoff -= self._std_offset + + utc_transitions = (dston, dstoff) + dt_utc = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt_utc, utc_transitions) + + if isdst: + dt_wall = dt + self._dst_offset + else: + dt_wall = dt + self._std_offset + + _fold = int(not isdst and self.is_ambiguous(dt_wall)) + + return enfold(dt_wall, fold=_fold) + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if not self.hasdst: + return False + + start, end = self.transitions(dt.year) + + dt = dt.replace(tzinfo=None) + return (end <= dt < end + self._dst_base_offset) + + def _isdst(self, dt): + if not self.hasdst: + return False + elif dt is None: + return None + + transitions = self.transitions(dt.year) + + if transitions is None: + return False + + dt = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt, transitions) + + # Handle ambiguous dates + if not isdst and self.is_ambiguous(dt): + return not self._fold(dt) + else: + return isdst + + def _naive_isdst(self, dt, transitions): + dston, dstoff = transitions + + dt = dt.replace(tzinfo=None) + + if dston < dstoff: + isdst = dston <= dt < dstoff + else: + isdst = not dstoff <= dt < dston + + return isdst + + @property + def _dst_base_offset(self): + return self._dst_offset - self._std_offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(...)" % self.__class__.__name__ + + __reduce__ = object.__reduce__ diff --git a/venv/lib/python3.12/site-packages/dateutil/tz/_factories.py b/venv/lib/python3.12/site-packages/dateutil/tz/_factories.py new file mode 100644 index 0000000..f8a6589 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tz/_factories.py @@ -0,0 +1,80 @@ +from datetime import timedelta +import weakref +from collections import OrderedDict + +from six.moves import _thread + + +class _TzSingleton(type): + def __init__(cls, *args, **kwargs): + cls.__instance = None + super(_TzSingleton, cls).__init__(*args, **kwargs) + + def __call__(cls): + if cls.__instance is None: + cls.__instance = super(_TzSingleton, cls).__call__() + return cls.__instance + + +class _TzFactory(type): + def instance(cls, *args, **kwargs): + """Alternate constructor that returns a fresh instance""" + return type.__call__(cls, *args, **kwargs) + + +class _TzOffsetFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls._cache_lock = _thread.allocate_lock() + + def __call__(cls, name, offset): + if isinstance(offset, timedelta): + key = (name, offset.total_seconds()) + else: + key = (name, offset) + + instance = cls.__instances.get(key, None) + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(name, offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls._cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + + +class _TzStrFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls.__cache_lock = _thread.allocate_lock() + + def __call__(cls, s, posix_offset=False): + key = (s, posix_offset) + instance = cls.__instances.get(key, None) + + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(s, posix_offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls.__cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + diff --git a/venv/lib/python3.12/site-packages/dateutil/tz/tz.py b/venv/lib/python3.12/site-packages/dateutil/tz/tz.py new file mode 100644 index 0000000..6175914 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tz/tz.py @@ -0,0 +1,1849 @@ +# -*- coding: utf-8 -*- +""" +This module offers timezone implementations subclassing the abstract +:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format +files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, +etc), TZ environment string (in all known formats), given ranges (with help +from relative deltas), local machine timezone, fixed offset timezone, and UTC +timezone. +""" +import datetime +import struct +import time +import sys +import os +import bisect +import weakref +from collections import OrderedDict + +import six +from six import string_types +from six.moves import _thread +from ._common import tzname_in_python2, _tzinfo +from ._common import tzrangebase, enfold +from ._common import _validate_fromutc_inputs + +from ._factories import _TzSingleton, _TzOffsetFactory +from ._factories import _TzStrFactory +try: + from .win import tzwin, tzwinlocal +except ImportError: + tzwin = tzwinlocal = None + +# For warning about rounding tzinfo +from warnings import warn + +ZERO = datetime.timedelta(0) +EPOCH = datetime.datetime(1970, 1, 1, 0, 0) +EPOCHORDINAL = EPOCH.toordinal() + + +@six.add_metaclass(_TzSingleton) +class tzutc(datetime.tzinfo): + """ + This is a tzinfo object that represents the UTC time zone. + + **Examples:** + + .. doctest:: + + >>> from datetime import * + >>> from dateutil.tz import * + + >>> datetime.now() + datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + + >>> datetime.now(tzutc()) + datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + + >>> datetime.now(tzutc()).tzname() + 'UTC' + + .. versionchanged:: 2.7.0 + ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will + always return the same object. + + .. doctest:: + + >>> from dateutil.tz import tzutc, UTC + >>> tzutc() is tzutc() + True + >>> tzutc() is UTC + True + """ + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return "UTC" + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Fast track version of fromutc() returns the original ``dt`` object for + any valid :py:class:`datetime.datetime` object. + """ + return dt + + def __eq__(self, other): + if not isinstance(other, (tzutc, tzoffset)): + return NotImplemented + + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +#: Convenience constant providing a :class:`tzutc()` instance +#: +#: .. versionadded:: 2.7.0 +UTC = tzutc() + + +@six.add_metaclass(_TzOffsetFactory) +class tzoffset(datetime.tzinfo): + """ + A simple class for representing a fixed offset from UTC. + + :param name: + The timezone name, to be returned when ``tzname()`` is called. + :param offset: + The time zone offset in seconds, or (since version 2.6.0, represented + as a :py:class:`datetime.timedelta` object). + """ + def __init__(self, name, offset): + self._name = name + + try: + # Allow a timedelta + offset = offset.total_seconds() + except (TypeError, AttributeError): + pass + + self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._name + + @_validate_fromutc_inputs + def fromutc(self, dt): + return dt + self._offset + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + def __eq__(self, other): + if not isinstance(other, tzoffset): + return NotImplemented + + return self._offset == other._offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + int(self._offset.total_seconds())) + + __reduce__ = object.__reduce__ + + +class tzlocal(_tzinfo): + """ + A :class:`tzinfo` subclass built around the ``time`` timezone functions. + """ + def __init__(self): + super(tzlocal, self).__init__() + + self._std_offset = datetime.timedelta(seconds=-time.timezone) + if time.daylight: + self._dst_offset = datetime.timedelta(seconds=-time.altzone) + else: + self._dst_offset = self._std_offset + + self._dst_saved = self._dst_offset - self._std_offset + self._hasdst = bool(self._dst_saved) + self._tznames = tuple(time.tzname) + + def utcoffset(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset - self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._tznames[self._isdst(dt)] + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + naive_dst = self._naive_is_dst(dt) + return (not naive_dst and + (naive_dst != self._naive_is_dst(dt - self._dst_saved))) + + def _naive_is_dst(self, dt): + timestamp = _datetime_to_timestamp(dt) + return time.localtime(timestamp + time.timezone).tm_isdst + + def _isdst(self, dt, fold_naive=True): + # We can't use mktime here. It is unstable when deciding if + # the hour near to a change is DST or not. + # + # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, + # dt.minute, dt.second, dt.weekday(), 0, -1)) + # return time.localtime(timestamp).tm_isdst + # + # The code above yields the following result: + # + # >>> import tz, datetime + # >>> t = tz.tzlocal() + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # + # Here is a more stable implementation: + # + if not self._hasdst: + return False + + # Check for ambiguous times: + dstval = self._naive_is_dst(dt) + fold = getattr(dt, 'fold', None) + + if self.is_ambiguous(dt): + if fold is not None: + return not self._fold(dt) + else: + return True + + return dstval + + def __eq__(self, other): + if isinstance(other, tzlocal): + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + elif isinstance(other, tzutc): + return (not self._hasdst and + self._tznames[0] in {'UTC', 'GMT'} and + self._std_offset == ZERO) + elif isinstance(other, tzoffset): + return (not self._hasdst and + self._tznames[0] == other._name and + self._std_offset == other._offset) + else: + return NotImplemented + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +class _ttinfo(object): + __slots__ = ["offset", "delta", "isdst", "abbr", + "isstd", "isgmt", "dstoffset"] + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def __repr__(self): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + + def __eq__(self, other): + if not isinstance(other, _ttinfo): + return NotImplemented + + return (self.offset == other.offset and + self.delta == other.delta and + self.isdst == other.isdst and + self.abbr == other.abbr and + self.isstd == other.isstd and + self.isgmt == other.isgmt and + self.dstoffset == other.dstoffset) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __getstate__(self): + state = {} + for name in self.__slots__: + state[name] = getattr(self, name, None) + return state + + def __setstate__(self, state): + for name in self.__slots__: + if name in state: + setattr(self, name, state[name]) + + +class _tzfile(object): + """ + Lightweight class for holding the relevant transition and time zone + information read from binary tzfiles. + """ + attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', + 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] + + def __init__(self, **kwargs): + for attr in self.attrs: + setattr(self, attr, kwargs.get(attr, None)) + + +class tzfile(_tzinfo): + """ + This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)`` + format timezone files to extract current and historical zone information. + + :param fileobj: + This can be an opened file stream or a file name that the time zone + information can be read from. + + :param filename: + This is an optional parameter specifying the source of the time zone + information in the event that ``fileobj`` is a file object. If omitted + and ``fileobj`` is a file stream, this parameter will be set either to + ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. + + See `Sources for Time Zone and Daylight Saving Time Data + `_ for more information. + Time zone files can be compiled from the `IANA Time Zone database files + `_ with the `zic time zone compiler + `_ + + .. note:: + + Only construct a ``tzfile`` directly if you have a specific timezone + file on disk that you want to read into a Python ``tzinfo`` object. + If you want to get a ``tzfile`` representing a specific IANA zone, + (e.g. ``'America/New_York'``), you should call + :func:`dateutil.tz.gettz` with the zone identifier. + + + **Examples:** + + Using the US Eastern time zone as an example, we can see that a ``tzfile`` + provides time zone information for the standard Daylight Saving offsets: + + .. testsetup:: tzfile + + from dateutil.tz import gettz + from datetime import datetime + + .. doctest:: tzfile + + >>> NYC = gettz('America/New_York') + >>> NYC + tzfile('/usr/share/zoneinfo/America/New_York') + + >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST + 2016-01-03 00:00:00-05:00 + + >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT + 2016-07-07 00:00:00-04:00 + + + The ``tzfile`` structure contains a fully history of the time zone, + so historical dates will also have the right offsets. For example, before + the adoption of the UTC standards, New York used local solar mean time: + + .. doctest:: tzfile + + >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT + 1901-04-12 00:00:00-04:56 + + And during World War II, New York was on "Eastern War Time", which was a + state of permanent daylight saving time: + + .. doctest:: tzfile + + >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT + 1944-02-07 00:00:00-04:00 + + """ + + def __init__(self, fileobj, filename=None): + super(tzfile, self).__init__() + + file_opened_here = False + if isinstance(fileobj, string_types): + self._filename = fileobj + fileobj = open(fileobj, 'rb') + file_opened_here = True + elif filename is not None: + self._filename = filename + elif hasattr(fileobj, "name"): + self._filename = fileobj.name + else: + self._filename = repr(fileobj) + + if fileobj is not None: + if not file_opened_here: + fileobj = _nullcontext(fileobj) + + with fileobj as file_stream: + tzobj = self._read_tzfile(file_stream) + + self._set_tzdata(tzobj) + + def _set_tzdata(self, tzobj): + """ Set the time zone data of this object from a _tzfile object """ + # Copy the relevant attributes over as private attributes + for attr in _tzfile.attrs: + setattr(self, '_' + attr, getattr(tzobj, attr)) + + def _read_tzfile(self, fileobj): + out = _tzfile() + + # From tzfile(5): + # + # The time zone information files used by tzset(3) + # begin with the magic characters "TZif" to identify + # them as time zone information files, followed by + # sixteen bytes reserved for future use, followed by + # six four-byte values of type long, written in a + # ``standard'' byte order (the high-order byte + # of the value is written first). + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") + + fileobj.read(16) + + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, + + # The number of standard/wall indicators stored in the file. + ttisstdcnt, + + # The number of leap seconds for which data is + # stored in the file. + leapcnt, + + # The number of "transition times" for which data + # is stored in the file. + timecnt, + + # The number of "local time types" for which data + # is stored in the file (must not be zero). + typecnt, + + # The number of characters of "time zone + # abbreviation strings" stored in the file. + charcnt, + + ) = struct.unpack(">6l", fileobj.read(24)) + + # The above header is followed by tzh_timecnt four-byte + # values of type long, sorted in ascending order. + # These values are written in ``standard'' byte order. + # Each is used as a transition time (as returned by + # time(2)) at which the rules for computing local time + # change. + + if timecnt: + out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, + fileobj.read(timecnt*4))) + else: + out.trans_list_utc = [] + + # Next come tzh_timecnt one-byte values of type unsigned + # char; each one tells which of the different types of + # ``local time'' types described in the file is associated + # with the same-indexed transition time. These values + # serve as indices into an array of ttinfo structures that + # appears next in the file. + + if timecnt: + out.trans_idx = struct.unpack(">%dB" % timecnt, + fileobj.read(timecnt)) + else: + out.trans_idx = [] + + # Each ttinfo structure is written as a four-byte value + # for tt_gmtoff of type long, in a standard byte + # order, followed by a one-byte value for tt_isdst + # and a one-byte value for tt_abbrind. In each + # structure, tt_gmtoff gives the number of + # seconds to be added to UTC, tt_isdst tells whether + # tm_isdst should be set by localtime(3), and + # tt_abbrind serves as an index into the array of + # time zone abbreviation characters that follow the + # ttinfo structure(s) in the file. + + ttinfo = [] + + for i in range(typecnt): + ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) + + abbr = fileobj.read(charcnt).decode() + + # Then there are tzh_leapcnt pairs of four-byte + # values, written in standard byte order; the + # first value of each pair gives the time (as + # returned by time(2)) at which a leap second + # occurs; the second gives the total number of + # leap seconds to be applied after the given time. + # The pairs of values are sorted in ascending order + # by time. + + # Not used, for now (but seek for correct file position) + if leapcnt: + fileobj.seek(leapcnt * 8, os.SEEK_CUR) + + # Then there are tzh_ttisstdcnt standard/wall + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as standard + # time or wall clock time, and are used when + # a time zone file is used in handling POSIX-style + # time zone environment variables. + + if ttisstdcnt: + isstd = struct.unpack(">%db" % ttisstdcnt, + fileobj.read(ttisstdcnt)) + + # Finally, there are tzh_ttisgmtcnt UTC/local + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as UTC or + # local time, and are used when a time zone file + # is used in handling POSIX-style time zone envi- + # ronment variables. + + if ttisgmtcnt: + isgmt = struct.unpack(">%db" % ttisgmtcnt, + fileobj.read(ttisgmtcnt)) + + # Build ttinfo list + out.ttinfo_list = [] + for i in range(typecnt): + gmtoff, isdst, abbrind = ttinfo[i] + gmtoff = _get_supported_offset(gmtoff) + tti = _ttinfo() + tti.offset = gmtoff + tti.dstoffset = datetime.timedelta(0) + tti.delta = datetime.timedelta(seconds=gmtoff) + tti.isdst = isdst + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] + tti.isstd = (ttisstdcnt > i and isstd[i] != 0) + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) + out.ttinfo_list.append(tti) + + # Replace ttinfo indexes for ttinfo objects. + out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] + + # Set standard, dst, and before ttinfos. before will be + # used when a given time is before any transitions, + # and will be set to the first non-dst ttinfo, or to + # the first dst, if all of them are dst. + out.ttinfo_std = None + out.ttinfo_dst = None + out.ttinfo_before = None + if out.ttinfo_list: + if not out.trans_list_utc: + out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] + else: + for i in range(timecnt-1, -1, -1): + tti = out.trans_idx[i] + if not out.ttinfo_std and not tti.isdst: + out.ttinfo_std = tti + elif not out.ttinfo_dst and tti.isdst: + out.ttinfo_dst = tti + + if out.ttinfo_std and out.ttinfo_dst: + break + else: + if out.ttinfo_dst and not out.ttinfo_std: + out.ttinfo_std = out.ttinfo_dst + + for tti in out.ttinfo_list: + if not tti.isdst: + out.ttinfo_before = tti + break + else: + out.ttinfo_before = out.ttinfo_list[0] + + # Now fix transition times to become relative to wall time. + # + # I'm not sure about this. In my tests, the tz source file + # is setup to wall time, and in the binary file isstd and + # isgmt are off, so it should be in wall time. OTOH, it's + # always in gmt time. Let me know if you have comments + # about this. + lastdst = None + lastoffset = None + lastdstoffset = None + lastbaseoffset = None + out.trans_list = [] + + for i, tti in enumerate(out.trans_idx): + offset = tti.offset + dstoffset = 0 + + if lastdst is not None: + if tti.isdst: + if not lastdst: + dstoffset = offset - lastoffset + + if not dstoffset and lastdstoffset: + dstoffset = lastdstoffset + + tti.dstoffset = datetime.timedelta(seconds=dstoffset) + lastdstoffset = dstoffset + + # If a time zone changes its base offset during a DST transition, + # then you need to adjust by the previous base offset to get the + # transition time in local time. Otherwise you use the current + # base offset. Ideally, I would have some mathematical proof of + # why this is true, but I haven't really thought about it enough. + baseoffset = offset - dstoffset + adjustment = baseoffset + if (lastbaseoffset is not None and baseoffset != lastbaseoffset + and tti.isdst != lastdst): + # The base DST has changed + adjustment = lastbaseoffset + + lastdst = tti.isdst + lastoffset = offset + lastbaseoffset = baseoffset + + out.trans_list.append(out.trans_list_utc[i] + adjustment) + + out.trans_idx = tuple(out.trans_idx) + out.trans_list = tuple(out.trans_list) + out.trans_list_utc = tuple(out.trans_list_utc) + + return out + + def _find_last_transition(self, dt, in_utc=False): + # If there's no list, there are no transitions to find + if not self._trans_list: + return None + + timestamp = _datetime_to_timestamp(dt) + + # Find where the timestamp fits in the transition list - if the + # timestamp is a transition time, it's part of the "after" period. + trans_list = self._trans_list_utc if in_utc else self._trans_list + idx = bisect.bisect_right(trans_list, timestamp) + + # We want to know when the previous transition was, so subtract off 1 + return idx - 1 + + def _get_ttinfo(self, idx): + # For no list or after the last transition, default to _ttinfo_std + if idx is None or (idx + 1) >= len(self._trans_list): + return self._ttinfo_std + + # If there is a list and the time is before it, return _ttinfo_before + if idx < 0: + return self._ttinfo_before + + return self._trans_idx[idx] + + def _find_ttinfo(self, dt): + idx = self._resolve_ambiguous_time(dt) + + return self._get_ttinfo(idx) + + def fromutc(self, dt): + """ + The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. + + :param dt: + A :py:class:`datetime.datetime` object. + + :raises TypeError: + Raised if ``dt`` is not a :py:class:`datetime.datetime` object. + + :raises ValueError: + Raised if this is called with a ``dt`` which does not have this + ``tzinfo`` attached. + + :return: + Returns a :py:class:`datetime.datetime` object representing the + wall time in ``self``'s time zone. + """ + # These isinstance checks are in datetime.tzinfo, so we'll preserve + # them, even if we don't care about duck typing. + if not isinstance(dt, datetime.datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # First treat UTC as wall time and get the transition we're in. + idx = self._find_last_transition(dt, in_utc=True) + tti = self._get_ttinfo(idx) + + dt_out = dt + datetime.timedelta(seconds=tti.offset) + + fold = self.is_ambiguous(dt_out, idx=idx) + + return enfold(dt_out, fold=int(fold)) + + def is_ambiguous(self, dt, idx=None): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if idx is None: + idx = self._find_last_transition(dt) + + # Calculate the difference in offsets from current to previous + timestamp = _datetime_to_timestamp(dt) + tti = self._get_ttinfo(idx) + + if idx is None or idx <= 0: + return False + + od = self._get_ttinfo(idx - 1).offset - tti.offset + tt = self._trans_list[idx] # Transition time + + return timestamp < tt + od + + def _resolve_ambiguous_time(self, dt): + idx = self._find_last_transition(dt) + + # If we have no transitions, return the index + _fold = self._fold(dt) + if idx is None or idx == 0: + return idx + + # If it's ambiguous and we're in a fold, shift to a different index. + idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) + + return idx - idx_offset + + def utcoffset(self, dt): + if dt is None: + return None + + if not self._ttinfo_std: + return ZERO + + return self._find_ttinfo(dt).delta + + def dst(self, dt): + if dt is None: + return None + + if not self._ttinfo_dst: + return ZERO + + tti = self._find_ttinfo(dt) + + if not tti.isdst: + return ZERO + + # The documentation says that utcoffset()-dst() must + # be constant for every dt. + return tti.dstoffset + + @tzname_in_python2 + def tzname(self, dt): + if not self._ttinfo_std or dt is None: + return None + return self._find_ttinfo(dt).abbr + + def __eq__(self, other): + if not isinstance(other, tzfile): + return NotImplemented + return (self._trans_list == other._trans_list and + self._trans_idx == other._trans_idx and + self._ttinfo_list == other._ttinfo_list) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) + + def __reduce__(self): + return self.__reduce_ex__(None) + + def __reduce_ex__(self, protocol): + return (self.__class__, (None, self._filename), self.__dict__) + + +class tzrange(tzrangebase): + """ + The ``tzrange`` object is a time zone specified by a set of offsets and + abbreviations, equivalent to the way the ``TZ`` variable can be specified + in POSIX-like systems, but using Python delta objects to specify DST + start, end and offsets. + + :param stdabbr: + The abbreviation for standard time (e.g. ``'EST'``). + + :param stdoffset: + An integer or :class:`datetime.timedelta` object or equivalent + specifying the base offset from UTC. + + If unspecified, +00:00 is used. + + :param dstabbr: + The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). + + If specified, with no other DST information, DST is assumed to occur + and the default behavior or ``dstoffset``, ``start`` and ``end`` is + used. If unspecified and no other DST information is specified, it + is assumed that this zone has no DST. + + If this is unspecified and other DST information is *is* specified, + DST occurs in the zone but the time zone abbreviation is left + unchanged. + + :param dstoffset: + A an integer or :class:`datetime.timedelta` object or equivalent + specifying the UTC offset during DST. If unspecified and any other DST + information is specified, it is assumed to be the STD offset +1 hour. + + :param start: + A :class:`relativedelta.relativedelta` object or equivalent specifying + the time and time of year that daylight savings time starts. To + specify, for example, that DST starts at 2AM on the 2nd Sunday in + March, pass: + + ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` + + If unspecified and any other DST information is specified, the default + value is 2 AM on the first Sunday in April. + + :param end: + A :class:`relativedelta.relativedelta` object or equivalent + representing the time and time of year that daylight savings time + ends, with the same specification method as in ``start``. One note is + that this should point to the first time in the *standard* zone, so if + a transition occurs at 2AM in the DST zone and the clocks are set back + 1 hour to 1AM, set the ``hours`` parameter to +1. + + + **Examples:** + + .. testsetup:: tzrange + + from dateutil.tz import tzrange, tzstr + + .. doctest:: tzrange + + >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") + True + + >>> from dateutil.relativedelta import * + >>> range1 = tzrange("EST", -18000, "EDT") + >>> range2 = tzrange("EST", -18000, "EDT", -14400, + ... relativedelta(hours=+2, month=4, day=1, + ... weekday=SU(+1)), + ... relativedelta(hours=+1, month=10, day=31, + ... weekday=SU(-1))) + >>> tzstr('EST5EDT') == range1 == range2 + True + + """ + def __init__(self, stdabbr, stdoffset=None, + dstabbr=None, dstoffset=None, + start=None, end=None): + + global relativedelta + from dateutil import relativedelta + + self._std_abbr = stdabbr + self._dst_abbr = dstabbr + + try: + stdoffset = stdoffset.total_seconds() + except (TypeError, AttributeError): + pass + + try: + dstoffset = dstoffset.total_seconds() + except (TypeError, AttributeError): + pass + + if stdoffset is not None: + self._std_offset = datetime.timedelta(seconds=stdoffset) + else: + self._std_offset = ZERO + + if dstoffset is not None: + self._dst_offset = datetime.timedelta(seconds=dstoffset) + elif dstabbr and stdoffset is not None: + self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) + else: + self._dst_offset = ZERO + + if dstabbr and start is None: + self._start_delta = relativedelta.relativedelta( + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) + else: + self._start_delta = start + + if dstabbr and end is None: + self._end_delta = relativedelta.relativedelta( + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) + else: + self._end_delta = end + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = bool(self._start_delta) + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + if not self.hasdst: + return None + + base_year = datetime.datetime(year, 1, 1) + + start = base_year + self._start_delta + end = base_year + self._end_delta + + return (start, end) + + def __eq__(self, other): + if not isinstance(other, tzrange): + return NotImplemented + + return (self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr and + self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._start_delta == other._start_delta and + self._end_delta == other._end_delta) + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +@six.add_metaclass(_TzStrFactory) +class tzstr(tzrange): + """ + ``tzstr`` objects are time zone objects specified by a time-zone string as + it would be passed to a ``TZ`` variable on POSIX-style systems (see + the `GNU C Library: TZ Variable`_ for more details). + + There is one notable exception, which is that POSIX-style time zones use an + inverted offset format, so normally ``GMT+3`` would be parsed as an offset + 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an + offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX + behavior, pass a ``True`` value to ``posix_offset``. + + The :class:`tzrange` object provides the same functionality, but is + specified using :class:`relativedelta.relativedelta` objects. rather than + strings. + + :param s: + A time zone string in ``TZ`` variable format. This can be a + :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: + :class:`unicode`) or a stream emitting unicode characters + (e.g. :class:`StringIO`). + + :param posix_offset: + Optional. If set to ``True``, interpret strings such as ``GMT+3`` or + ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the + POSIX standard. + + .. caution:: + + Prior to version 2.7.0, this function also supported time zones + in the format: + + * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` + * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` + + This format is non-standard and has been deprecated; this function + will raise a :class:`DeprecatedTZFormatWarning` until + support is removed in a future version. + + .. _`GNU C Library: TZ Variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + """ + def __init__(self, s, posix_offset=False): + global parser + from dateutil.parser import _parser as parser + + self._s = s + + res = parser._parsetz(s) + if res is None or res.any_unused_tokens: + raise ValueError("unknown string format") + + # Here we break the compatibility with the TZ variable handling. + # GMT-3 actually *means* the timezone -3. + if res.stdabbr in ("GMT", "UTC") and not posix_offset: + res.stdoffset *= -1 + + # We must initialize it first, since _delta() needs + # _std_offset and _dst_offset set. Use False in start/end + # to avoid building it two times. + tzrange.__init__(self, res.stdabbr, res.stdoffset, + res.dstabbr, res.dstoffset, + start=False, end=False) + + if not res.dstabbr: + self._start_delta = None + self._end_delta = None + else: + self._start_delta = self._delta(res.start) + if self._start_delta: + self._end_delta = self._delta(res.end, isend=1) + + self.hasdst = bool(self._start_delta) + + def _delta(self, x, isend=0): + from dateutil import relativedelta + kwargs = {} + if x.month is not None: + kwargs["month"] = x.month + if x.weekday is not None: + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) + if x.week > 0: + kwargs["day"] = 1 + else: + kwargs["day"] = 31 + elif x.day: + kwargs["day"] = x.day + elif x.yday is not None: + kwargs["yearday"] = x.yday + elif x.jyday is not None: + kwargs["nlyearday"] = x.jyday + if not kwargs: + # Default is to start on first sunday of april, and end + # on last sunday of october. + if not isend: + kwargs["month"] = 4 + kwargs["day"] = 1 + kwargs["weekday"] = relativedelta.SU(+1) + else: + kwargs["month"] = 10 + kwargs["day"] = 31 + kwargs["weekday"] = relativedelta.SU(-1) + if x.time is not None: + kwargs["seconds"] = x.time + else: + # Default is 2AM. + kwargs["seconds"] = 7200 + if isend: + # Convert to standard time, to follow the documented way + # of working with the extra hour. See the documentation + # of the tzinfo class. + delta = self._dst_offset - self._std_offset + kwargs["seconds"] -= delta.seconds + delta.days * 86400 + return relativedelta.relativedelta(**kwargs) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +class _tzicalvtzcomp(object): + def __init__(self, tzoffsetfrom, tzoffsetto, isdst, + tzname=None, rrule=None): + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) + self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom + self.isdst = isdst + self.tzname = tzname + self.rrule = rrule + + +class _tzicalvtz(_tzinfo): + def __init__(self, tzid, comps=[]): + super(_tzicalvtz, self).__init__() + + self._tzid = tzid + self._comps = comps + self._cachedate = [] + self._cachecomp = [] + self._cache_lock = _thread.allocate_lock() + + def _find_comp(self, dt): + if len(self._comps) == 1: + return self._comps[0] + + dt = dt.replace(tzinfo=None) + + try: + with self._cache_lock: + return self._cachecomp[self._cachedate.index( + (dt, self._fold(dt)))] + except ValueError: + pass + + lastcompdt = None + lastcomp = None + + for comp in self._comps: + compdt = self._find_compdt(comp, dt) + + if compdt and (not lastcompdt or lastcompdt < compdt): + lastcompdt = compdt + lastcomp = comp + + if not lastcomp: + # RFC says nothing about what to do when a given + # time is before the first onset date. We'll look for the + # first standard component, or the first component, if + # none is found. + for comp in self._comps: + if not comp.isdst: + lastcomp = comp + break + else: + lastcomp = comp[0] + + with self._cache_lock: + self._cachedate.insert(0, (dt, self._fold(dt))) + self._cachecomp.insert(0, lastcomp) + + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() + + return lastcomp + + def _find_compdt(self, comp, dt): + if comp.tzoffsetdiff < ZERO and self._fold(dt): + dt -= comp.tzoffsetdiff + + compdt = comp.rrule.before(dt, inc=True) + + return compdt + + def utcoffset(self, dt): + if dt is None: + return None + + return self._find_comp(dt).tzoffsetto + + def dst(self, dt): + comp = self._find_comp(dt) + if comp.isdst: + return comp.tzoffsetdiff + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._find_comp(dt).tzname + + def __repr__(self): + return "" % repr(self._tzid) + + __reduce__ = object.__reduce__ + + +class tzical(object): + """ + This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure + as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. + + :param `fileobj`: + A file or stream in iCalendar format, which should be UTF-8 encoded + with CRLF endings. + + .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 + """ + def __init__(self, fileobj): + global rrule + from dateutil import rrule + + if isinstance(fileobj, string_types): + self._s = fileobj + # ical should be encoded in UTF-8 with CRLF + fileobj = open(fileobj, 'r') + else: + self._s = getattr(fileobj, 'name', repr(fileobj)) + fileobj = _nullcontext(fileobj) + + self._vtz = {} + + with fileobj as fobj: + self._parse_rfc(fobj.read()) + + def keys(self): + """ + Retrieves the available time zones as a list. + """ + return list(self._vtz.keys()) + + def get(self, tzid=None): + """ + Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. + + :param tzid: + If there is exactly one time zone available, omitting ``tzid`` + or passing :py:const:`None` value returns it. Otherwise a valid + key (which can be retrieved from :func:`keys`) is required. + + :raises ValueError: + Raised if ``tzid`` is not specified but there are either more + or fewer than 1 zone defined. + + :returns: + Returns either a :py:class:`datetime.tzinfo` object representing + the relevant time zone or :py:const:`None` if the ``tzid`` was + not found. + """ + if tzid is None: + if len(self._vtz) == 0: + raise ValueError("no timezones defined") + elif len(self._vtz) > 1: + raise ValueError("more than one timezone available") + tzid = next(iter(self._vtz)) + + return self._vtz.get(tzid) + + def _parse_offset(self, s): + s = s.strip() + if not s: + raise ValueError("empty offset") + if s[0] in ('+', '-'): + signal = (-1, +1)[s[0] == '+'] + s = s[1:] + else: + signal = +1 + if len(s) == 4: + return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal + elif len(s) == 6: + return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal + else: + raise ValueError("invalid offset: " + s) + + def _parse_rfc(self, s): + lines = s.splitlines() + if not lines: + raise ValueError("empty string") + + # Unfold + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + + tzid = None + comps = [] + invtz = False + comptype = None + for line in lines: + if not line: + continue + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0].upper() + parms = parms[1:] + if invtz: + if name == "BEGIN": + if value in ("STANDARD", "DAYLIGHT"): + # Process component + pass + else: + raise ValueError("unknown component: "+value) + comptype = value + founddtstart = False + tzoffsetfrom = None + tzoffsetto = None + rrulelines = [] + tzname = None + elif name == "END": + if value == "VTIMEZONE": + if comptype: + raise ValueError("component not closed: "+comptype) + if not tzid: + raise ValueError("mandatory TZID not found") + if not comps: + raise ValueError( + "at least one component is needed") + # Process vtimezone + self._vtz[tzid] = _tzicalvtz(tzid, comps) + invtz = False + elif value == comptype: + if not founddtstart: + raise ValueError("mandatory DTSTART not found") + if tzoffsetfrom is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + if tzoffsetto is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + # Process component + rr = None + if rrulelines: + rr = rrule.rrulestr("\n".join(rrulelines), + compatible=True, + ignoretz=True, + cache=True) + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, + (comptype == "DAYLIGHT"), + tzname, rr) + comps.append(comp) + comptype = None + else: + raise ValueError("invalid component end: "+value) + elif comptype: + if name == "DTSTART": + # DTSTART in VTIMEZONE takes a subset of valid RRULE + # values under RFC 5545. + for parm in parms: + if parm != 'VALUE=DATE-TIME': + msg = ('Unsupported DTSTART param in ' + + 'VTIMEZONE: ' + parm) + raise ValueError(msg) + rrulelines.append(line) + founddtstart = True + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): + rrulelines.append(line) + elif name == "TZOFFSETFROM": + if parms: + raise ValueError( + "unsupported %s parm: %s " % (name, parms[0])) + tzoffsetfrom = self._parse_offset(value) + elif name == "TZOFFSETTO": + if parms: + raise ValueError( + "unsupported TZOFFSETTO parm: "+parms[0]) + tzoffsetto = self._parse_offset(value) + elif name == "TZNAME": + if parms: + raise ValueError( + "unsupported TZNAME parm: "+parms[0]) + tzname = value + elif name == "COMMENT": + pass + else: + raise ValueError("unsupported property: "+name) + else: + if name == "TZID": + if parms: + raise ValueError( + "unsupported TZID parm: "+parms[0]) + tzid = value + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): + pass + else: + raise ValueError("unsupported property: "+name) + elif name == "BEGIN" and value == "VTIMEZONE": + tzid = None + comps = [] + invtz = True + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +if sys.platform != "win32": + TZFILES = ["/etc/localtime", "localtime"] + TZPATHS = ["/usr/share/zoneinfo", + "/usr/lib/zoneinfo", + "/usr/share/lib/zoneinfo", + "/etc/zoneinfo"] +else: + TZFILES = [] + TZPATHS = [] + + +def __get_gettz(): + tzlocal_classes = (tzlocal,) + if tzwinlocal is not None: + tzlocal_classes += (tzwinlocal,) + + class GettzFunc(object): + """ + Retrieve a time zone object from a string representation + + This function is intended to retrieve the :py:class:`tzinfo` subclass + that best represents the time zone that would be used if a POSIX + `TZ variable`_ were set to the same value. + + If no argument or an empty string is passed to ``gettz``, local time + is returned: + + .. code-block:: python3 + + >>> gettz() + tzfile('/etc/localtime') + + This function is also the preferred way to map IANA tz database keys + to :class:`tzfile` objects: + + .. code-block:: python3 + + >>> gettz('Pacific/Kiritimati') + tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') + + On Windows, the standard is extended to include the Windows-specific + zone names provided by the operating system: + + .. code-block:: python3 + + >>> gettz('Egypt Standard Time') + tzwin('Egypt Standard Time') + + Passing a GNU ``TZ`` style string time zone specification returns a + :class:`tzstr` object: + + .. code-block:: python3 + + >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + + :param name: + A time zone name (IANA, or, on Windows, Windows keys), location of + a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone + specifier. An empty string, no argument or ``None`` is interpreted + as local time. + + :return: + Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` + subclasses. + + .. versionchanged:: 2.7.0 + + After version 2.7.0, any two calls to ``gettz`` using the same + input strings will return the same object: + + .. code-block:: python3 + + >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') + True + + In addition to improving performance, this ensures that + `"same zone" semantics`_ are used for datetimes in the same zone. + + + .. _`TZ variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + + .. _`"same zone" semantics`: + https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html + """ + def __init__(self): + + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache_size = 8 + self.__strong_cache = OrderedDict() + self._cache_lock = _thread.allocate_lock() + + def __call__(self, name=None): + with self._cache_lock: + rv = self.__instances.get(name, None) + + if rv is None: + rv = self.nocache(name=name) + if not (name is None + or isinstance(rv, tzlocal_classes) + or rv is None): + # tzlocal is slightly more complicated than the other + # time zone providers because it depends on environment + # at construction time, so don't cache that. + # + # We also cannot store weak references to None, so we + # will also not store that. + self.__instances[name] = rv + else: + # No need for strong caching, return immediately + return rv + + self.__strong_cache[name] = self.__strong_cache.pop(name, rv) + + if len(self.__strong_cache) > self.__strong_cache_size: + self.__strong_cache.popitem(last=False) + + return rv + + def set_cache_size(self, size): + with self._cache_lock: + self.__strong_cache_size = size + while len(self.__strong_cache) > size: + self.__strong_cache.popitem(last=False) + + def cache_clear(self): + with self._cache_lock: + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache.clear() + + @staticmethod + def nocache(name=None): + """A non-cached version of gettz""" + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass + if name is None or name in ("", ":"): + for filepath in TZFILES: + if not os.path.isabs(filepath): + filename = filepath + for path in TZPATHS: + filepath = os.path.join(path, filename) + if os.path.isfile(filepath): + break + else: + continue + if os.path.isfile(filepath): + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = tzlocal() + else: + try: + if name.startswith(":"): + name = name[1:] + except TypeError as e: + if isinstance(name, bytes): + new_msg = "gettz argument should be str, not bytes" + six.raise_from(TypeError(new_msg), e) + else: + raise + if os.path.isabs(name): + if os.path.isfile(name): + tz = tzfile(name) + else: + tz = None + else: + for path in TZPATHS: + filepath = os.path.join(path, name) + if not os.path.isfile(filepath): + filepath = filepath.replace(' ', '_') + if not os.path.isfile(filepath): + continue + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = None + if tzwin is not None: + try: + tz = tzwin(name) + except (WindowsError, UnicodeEncodeError): + # UnicodeEncodeError is for Python 2.7 compat + tz = None + + if not tz: + from dateutil.zoneinfo import get_zonefile_instance + tz = get_zonefile_instance().get(name) + + if not tz: + for c in name: + # name is not a tzstr unless it has at least + # one offset. For short values of "name", an + # explicit for loop seems to be the fastest way + # To determine if a string contains a digit + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): + tz = UTC + elif name in time.tzname: + tz = tzlocal() + return tz + + return GettzFunc() + + +gettz = __get_gettz() +del __get_gettz + + +def datetime_exists(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + would fall in a gap. + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" exists in + ``tz``. + + .. versionadded:: 2.7.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + tz = dt.tzinfo + + dt = dt.replace(tzinfo=None) + + # This is essentially a test of whether or not the datetime can survive + # a round trip to UTC. + dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) + dt_rt = dt_rt.replace(tzinfo=None) + + return dt == dt_rt + + +def datetime_ambiguous(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + is ambiguous (i.e if there are two times differentiated only by their DST + status). + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" is ambiguous in + ``tz``. + + .. versionadded:: 2.6.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + + tz = dt.tzinfo + + # If a time zone defines its own "is_ambiguous" function, we'll use that. + is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) + if is_ambiguous_fn is not None: + try: + return tz.is_ambiguous(dt) + except Exception: + pass + + # If it doesn't come out and tell us it's ambiguous, we'll just check if + # the fold attribute has any effect on this particular date and time. + dt = dt.replace(tzinfo=tz) + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dst = wall_0.dst() == wall_1.dst() + + return not (same_offset and same_dst) + + +def resolve_imaginary(dt): + """ + Given a datetime that may be imaginary, return an existing datetime. + + This function assumes that an imaginary datetime represents what the + wall time would be in a zone had the offset transition not occurred, so + it will always fall forward by the transition's change in offset. + + .. doctest:: + + >>> from dateutil import tz + >>> from datetime import datetime + >>> NYC = tz.gettz('America/New_York') + >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) + 2017-03-12 03:30:00-04:00 + + >>> KIR = tz.gettz('Pacific/Kiritimati') + >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) + 1995-01-02 12:30:00+14:00 + + As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, + existing datetime, so a round-trip to and from UTC is sufficient to get + an extant datetime, however, this generally "falls back" to an earlier time + rather than falling forward to the STD side (though no guarantees are made + about this behavior). + + :param dt: + A :class:`datetime.datetime` which may or may not exist. + + :return: + Returns an existing :class:`datetime.datetime`. If ``dt`` was not + imaginary, the datetime returned is guaranteed to be the same object + passed to the function. + + .. versionadded:: 2.7.0 + """ + if dt.tzinfo is not None and not datetime_exists(dt): + + curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() + old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() + + dt += curr_offset - old_offset + + return dt + + +def _datetime_to_timestamp(dt): + """ + Convert a :class:`datetime.datetime` object to an epoch timestamp in + seconds since January 1, 1970, ignoring the time zone. + """ + return (dt.replace(tzinfo=None) - EPOCH).total_seconds() + + +if sys.version_info >= (3, 6): + def _get_supported_offset(second_offset): + return second_offset +else: + def _get_supported_offset(second_offset): + # For python pre-3.6, round to full-minutes if that's not the case. + # Python's datetime doesn't accept sub-minute timezones. Check + # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 + # for some information. + old_offset = second_offset + calculated_offset = 60 * ((second_offset + 30) // 60) + return calculated_offset + + +try: + # Python 3.7 feature + from contextlib import nullcontext as _nullcontext +except ImportError: + class _nullcontext(object): + """ + Class for wrapping contexts so that they are passed through in a + with statement. + """ + def __init__(self, context): + self.context = context + + def __enter__(self): + return self.context + + def __exit__(*args, **kwargs): + pass + +# vim:ts=4:sw=4:et diff --git a/venv/lib/python3.12/site-packages/dateutil/tz/win.py b/venv/lib/python3.12/site-packages/dateutil/tz/win.py new file mode 100644 index 0000000..cde07ba --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tz/win.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +This module provides an interface to the native time zone data on Windows, +including :py:class:`datetime.tzinfo` implementations. + +Attempting to import this module on a non-Windows platform will raise an +:py:obj:`ImportError`. +""" +# This code was originally contributed by Jeffrey Harris. +import datetime +import struct + +from six.moves import winreg +from six import text_type + +try: + import ctypes + from ctypes import wintypes +except ValueError: + # ValueError is raised on non-Windows systems for some horrible reason. + raise ImportError("Running tzwin on non-Windows system") + +from ._common import tzrangebase + +__all__ = ["tzwin", "tzwinlocal", "tzres"] + +ONEWEEK = datetime.timedelta(7) + +TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" +TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" +TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + + +def _settzkeyname(): + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + winreg.OpenKey(handle, TZKEYNAMENT).Close() + TZKEYNAME = TZKEYNAMENT + except WindowsError: + TZKEYNAME = TZKEYNAME9X + handle.Close() + return TZKEYNAME + + +TZKEYNAME = _settzkeyname() + + +class tzres(object): + """ + Class for accessing ``tzres.dll``, which contains timezone name related + resources. + + .. versionadded:: 2.5.0 + """ + p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char + + def __init__(self, tzres_loc='tzres.dll'): + # Load the user32 DLL so we can load strings from tzres + user32 = ctypes.WinDLL('user32') + + # Specify the LoadStringW function + user32.LoadStringW.argtypes = (wintypes.HINSTANCE, + wintypes.UINT, + wintypes.LPWSTR, + ctypes.c_int) + + self.LoadStringW = user32.LoadStringW + self._tzres = ctypes.WinDLL(tzres_loc) + self.tzres_loc = tzres_loc + + def load_name(self, offset): + """ + Load a timezone name from a DLL offset (integer). + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.load_name(112)) + 'Eastern Standard Time' + + :param offset: + A positive integer value referring to a string from the tzres dll. + + .. note:: + + Offsets found in the registry are generally of the form + ``@tzres.dll,-114``. The offset in this case is 114, not -114. + + """ + resource = self.p_wchar() + lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) + nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) + return resource[:nchar] + + def name_from_string(self, tzname_str): + """ + Parse strings as returned from the Windows registry into the time zone + name as defined in the registry. + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.name_from_string('@tzres.dll,-251')) + 'Dateline Daylight Time' + >>> print(tzr.name_from_string('Eastern Standard Time')) + 'Eastern Standard Time' + + :param tzname_str: + A timezone name string as returned from a Windows registry key. + + :return: + Returns the localized timezone string from tzres.dll if the string + is of the form `@tzres.dll,-offset`, else returns the input string. + """ + if not tzname_str.startswith('@'): + return tzname_str + + name_splt = tzname_str.split(',-') + try: + offset = int(name_splt[1]) + except: + raise ValueError("Malformed timezone string.") + + return self.load_name(offset) + + +class tzwinbase(tzrangebase): + """tzinfo class based on win32's timezones available in the registry.""" + def __init__(self): + raise NotImplementedError('tzwinbase is an abstract base class') + + def __eq__(self, other): + # Compare on all relevant dimensions, including name. + if not isinstance(other, tzwinbase): + return NotImplemented + + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._stddayofweek == other._stddayofweek and + self._dstdayofweek == other._dstdayofweek and + self._stdweeknumber == other._stdweeknumber and + self._dstweeknumber == other._dstweeknumber and + self._stdhour == other._stdhour and + self._dsthour == other._dsthour and + self._stdminute == other._stdminute and + self._dstminute == other._dstminute and + self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr) + + @staticmethod + def list(): + """Return a list of all time zones known to the system.""" + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZKEYNAME) as tzkey: + result = [winreg.EnumKey(tzkey, i) + for i in range(winreg.QueryInfoKey(tzkey)[0])] + return result + + def display(self): + """ + Return the display name of the time zone. + """ + return self._display + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + + if not self.hasdst: + return None + + dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, + self._dsthour, self._dstminute, + self._dstweeknumber) + + dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, + self._stdhour, self._stdminute, + self._stdweeknumber) + + # Ambiguous dates default to the STD side + dstoff -= self._dst_base_offset + + return dston, dstoff + + def _get_hasdst(self): + return self._dstmonth != 0 + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +class tzwin(tzwinbase): + """ + Time zone object created from the zone info in the Windows registry + + These are similar to :py:class:`dateutil.tz.tzrange` objects in that + the time zone data is provided in the format of a single offset rule + for either 0 or 2 time zone transitions per year. + + :param: name + The name of a Windows time zone key, e.g. "Eastern Standard Time". + The full list of keys can be retrieved with :func:`tzwin.list`. + """ + + def __init__(self, name): + self._name = name + + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + keydict = valuestodict(tzkey) + + self._std_abbr = keydict["Std"] + self._dst_abbr = keydict["Dlt"] + + self._display = keydict["Display"] + + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=3l16h", keydict["TZI"]) + stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 + dstoffset = stdoffset-tup[2] # + DaylightBias * -1 + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + (self._stdmonth, + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[4:9] + + (self._dstmonth, + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[12:17] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwin(%s)" % repr(self._name) + + def __reduce__(self): + return (self.__class__, (self._name,)) + + +class tzwinlocal(tzwinbase): + """ + Class representing the local time zone information in the Windows registry + + While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time` + module) to retrieve time zone information, ``tzwinlocal`` retrieves the + rules directly from the Windows registry and creates an object like + :class:`dateutil.tz.tzwin`. + + Because Windows does not have an equivalent of :func:`time.tzset`, on + Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the + time zone settings *at the time that the process was started*, meaning + changes to the machine's time zone settings during the run of a program + on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`. + Because ``tzwinlocal`` reads the registry directly, it is unaffected by + this issue. + """ + def __init__(self): + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: + keydict = valuestodict(tzlocalkey) + + self._std_abbr = keydict["StandardName"] + self._dst_abbr = keydict["DaylightName"] + + try: + tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, + sn=self._std_abbr) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + _keydict = valuestodict(tzkey) + self._display = _keydict["Display"] + except OSError: + self._display = None + + stdoffset = -keydict["Bias"]-keydict["StandardBias"] + dstoffset = stdoffset-keydict["DaylightBias"] + + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # For reasons unclear, in this particular key, the day of week has been + # moved to the END of the SYSTEMTIME structure. + tup = struct.unpack("=8h", keydict["StandardStart"]) + + (self._stdmonth, + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[1:5] + + self._stddayofweek = tup[7] + + tup = struct.unpack("=8h", keydict["DaylightStart"]) + + (self._dstmonth, + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[1:5] + + self._dstdayofweek = tup[7] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwinlocal()" + + def __str__(self): + # str will return the standard name, not the daylight name. + return "tzwinlocal(%s)" % repr(self._std_abbr) + + def __reduce__(self): + return (self.__class__, ()) + + +def picknthweekday(year, month, dayofweek, hour, minute, whichweek): + """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ + first = datetime.datetime(year, month, 1, hour, minute) + + # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), + # Because 7 % 7 = 0 + weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) + wd = weekdayone + ((whichweek - 1) * ONEWEEK) + if (wd.month != month): + wd -= ONEWEEK + + return wd + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dout = {} + size = winreg.QueryInfoKey(key)[1] + tz_res = None + + for i in range(size): + key_name, value, dtype = winreg.EnumValue(key, i) + if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: + # If it's a DWORD (32-bit integer), it's stored as unsigned - convert + # that to a proper signed integer + if value & (1 << 31): + value = value - (1 << 32) + elif dtype == winreg.REG_SZ: + # If it's a reference to the tzres DLL, load the actual string + if value.startswith('@tzres'): + tz_res = tz_res or tzres() + value = tz_res.name_from_string(value) + + value = value.rstrip('\x00') # Remove trailing nulls + + dout[key_name] = value + + return dout diff --git a/venv/lib/python3.12/site-packages/dateutil/tzwin.py b/venv/lib/python3.12/site-packages/dateutil/tzwin.py new file mode 100644 index 0000000..cebc673 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/tzwin.py @@ -0,0 +1,2 @@ +# tzwin has moved to dateutil.tz.win +from .tz.win import * diff --git a/venv/lib/python3.12/site-packages/dateutil/utils.py b/venv/lib/python3.12/site-packages/dateutil/utils.py new file mode 100644 index 0000000..dd2d245 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/utils.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +This module offers general convenience and utility functions for dealing with +datetimes. + +.. versionadded:: 2.7.0 +""" +from __future__ import unicode_literals + +from datetime import datetime, time + + +def today(tzinfo=None): + """ + Returns a :py:class:`datetime` representing the current day at midnight + + :param tzinfo: + The time zone to attach (also used to determine the current day). + + :return: + A :py:class:`datetime.datetime` object representing the current day + at midnight. + """ + + dt = datetime.now(tzinfo) + return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) + + +def default_tzinfo(dt, tzinfo): + """ + Sets the ``tzinfo`` parameter on naive datetimes only + + This is useful for example when you are provided a datetime that may have + either an implicit or explicit time zone, such as when parsing a time zone + string. + + .. doctest:: + + >>> from dateutil.tz import tzoffset + >>> from dateutil.parser import parse + >>> from dateutil.utils import default_tzinfo + >>> dflt_tz = tzoffset("EST", -18000) + >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) + 2014-01-01 12:30:00+00:00 + >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) + 2014-01-01 12:30:00-05:00 + + :param dt: + The datetime on which to replace the time zone + + :param tzinfo: + The :py:class:`datetime.tzinfo` subclass instance to assign to + ``dt`` if (and only if) it is naive. + + :return: + Returns an aware :py:class:`datetime.datetime`. + """ + if dt.tzinfo is not None: + return dt + else: + return dt.replace(tzinfo=tzinfo) + + +def within_delta(dt1, dt2, delta): + """ + Useful for comparing two datetimes that may have a negligible difference + to be considered equal. + """ + delta = abs(delta) + difference = dt1 - dt2 + return -delta <= difference <= delta diff --git a/venv/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py b/venv/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py new file mode 100644 index 0000000..34f11ad --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import warnings +import json + +from tarfile import TarFile +from pkgutil import get_data +from io import BytesIO + +from dateutil.tz import tzfile as _tzfile + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +ZONEFILENAME = "dateutil-zoneinfo.tar.gz" +METADATA_FN = 'METADATA' + + +class tzfile(_tzfile): + def __reduce__(self): + return (gettz, (self._filename,)) + + +def getzoneinfofile_stream(): + try: + return BytesIO(get_data(__name__, ZONEFILENAME)) + except IOError as e: # TODO switch to FileNotFoundError? + warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) + return None + + +class ZoneInfoFile(object): + def __init__(self, zonefile_stream=None): + if zonefile_stream is not None: + with TarFile.open(fileobj=zonefile_stream) as tf: + self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) + for zf in tf.getmembers() + if zf.isfile() and zf.name != METADATA_FN} + # deal with links: They'll point to their parent object. Less + # waste of memory + links = {zl.name: self.zones[zl.linkname] + for zl in tf.getmembers() if + zl.islnk() or zl.issym()} + self.zones.update(links) + try: + metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) + metadata_str = metadata_json.read().decode('UTF-8') + self.metadata = json.loads(metadata_str) + except KeyError: + # no metadata in tar file + self.metadata = None + else: + self.zones = {} + self.metadata = None + + def get(self, name, default=None): + """ + Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method + for retrieving zones from the zone dictionary. + + :param name: + The name of the zone to retrieve. (Generally IANA zone names) + + :param default: + The value to return in the event of a missing key. + + .. versionadded:: 2.6.0 + + """ + return self.zones.get(name, default) + + +# The current API has gettz as a module function, although in fact it taps into +# a stateful class. So as a workaround for now, without changing the API, we +# will create a new "global" class instance the first time a user requests a +# timezone. Ugly, but adheres to the api. +# +# TODO: Remove after deprecation period. +_CLASS_ZONE_INSTANCE = [] + + +def get_zonefile_instance(new_instance=False): + """ + This is a convenience function which provides a :class:`ZoneInfoFile` + instance using the data provided by the ``dateutil`` package. By default, it + caches a single instance of the ZoneInfoFile object and returns that. + + :param new_instance: + If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and + used as the cached instance for the next call. Otherwise, new instances + are created only as necessary. + + :return: + Returns a :class:`ZoneInfoFile` object. + + .. versionadded:: 2.6 + """ + if new_instance: + zif = None + else: + zif = getattr(get_zonefile_instance, '_cached_instance', None) + + if zif is None: + zif = ZoneInfoFile(getzoneinfofile_stream()) + + get_zonefile_instance._cached_instance = zif + + return zif + + +def gettz(name): + """ + This retrieves a time zone from the local zoneinfo tarball that is packaged + with dateutil. + + :param name: + An IANA-style time zone name, as found in the zoneinfo file. + + :return: + Returns a :class:`dateutil.tz.tzfile` time zone object. + + .. warning:: + It is generally inadvisable to use this function, and it is only + provided for API compatibility with earlier versions. This is *not* + equivalent to ``dateutil.tz.gettz()``, which selects an appropriate + time zone based on the inputs, favoring system zoneinfo. This is ONLY + for accessing the dateutil-specific zoneinfo (which may be out of + date compared to the system zoneinfo). + + .. deprecated:: 2.6 + If you need to use a specific zoneinfofile over the system zoneinfo, + instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call + :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. + + Use :func:`get_zonefile_instance` to retrieve an instance of the + dateutil-provided zoneinfo. + """ + warnings.warn("zoneinfo.gettz() will be removed in future versions, " + "to use the dateutil-provided zoneinfo files, instantiate a " + "ZoneInfoFile object and use ZoneInfoFile.zones.get() " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].zones.get(name) + + +def gettz_db_metadata(): + """ Get the zonefile metadata + + See `zonefile_metadata`_ + + :returns: + A dictionary with the database metadata + + .. deprecated:: 2.6 + See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, + query the attribute ``zoneinfo.ZoneInfoFile.metadata``. + """ + warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " + "versions, to use the dateutil-provided zoneinfo files, " + "ZoneInfoFile object and query the 'metadata' attribute " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].metadata diff --git a/venv/lib/python3.12/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/venv/lib/python3.12/site-packages/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..1461f8c862df8c3eae75e0ea376788b5b2602269 GIT binary patch literal 156400 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

%(title)s

+ +''' + +DOC_HEADER_EXTERNALCSS = '''\ + + + + + %(title)s + + + + +

%(title)s

+ +''' + +DOC_FOOTER = '''\ + + +''' + + +class HtmlFormatter(Formatter): + r""" + Format tokens as HTML 4 ```` tags. By default, the content is enclosed + in a ``
`` tag, itself wrapped in a ``
`` tag (but see the `nowrap` option). + The ``
``'s CSS class can be set by the `cssclass` option. + + If the `linenos` option is set to ``"table"``, the ``
`` is
+    additionally wrapped inside a ``

+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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py b/venv/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py new file mode 100644 index 0000000..684c658 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py @@ -0,0 +1,75 @@ +import logging +import os +import tempfile +import shutil +import json +from subprocess import check_call, check_output +from tarfile import TarFile + +from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME + + +def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ``ftp.iana.org/tz``. + + """ + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + try: + with TarFile.open(filename) as tf: + for name in zonegroups: + tf.extract(name, tmpdir) + filepaths = [os.path.join(tmpdir, n) for n in zonegroups] + + _run_zic(zonedir, filepaths) + + # write metadata file + with open(os.path.join(zonedir, METADATA_FN), 'w') as f: + json.dump(metadata, f, indent=4, sort_keys=True) + target = os.path.join(moduledir, ZONEFILENAME) + with TarFile.open(target, "w:%s" % format) as tf: + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + finally: + shutil.rmtree(tmpdir) + + +def _run_zic(zonedir, filepaths): + """Calls the ``zic`` compiler in a compatible way to get a "fat" binary. + + Recent versions of ``zic`` default to ``-b slim``, while older versions + don't even have the ``-b`` option (but default to "fat" binaries). The + current version of dateutil does not support Version 2+ TZif files, which + causes problems when used in conjunction with "slim" binaries, so this + function is used to ensure that we always get a "fat" binary. + """ + + try: + help_text = check_output(["zic", "--help"]) + except OSError as e: + _print_on_nosuchfile(e) + raise + + if b"-b " in help_text: + bloat_args = ["-b", "fat"] + else: + bloat_args = [] + + check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths) + + +def _print_on_nosuchfile(e): + """Print helpful troubleshooting message + + e is an exception raised by subprocess.check_call() + + """ + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/AUTHORS.txt b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/AUTHORS.txt new file mode 100644 index 0000000..ae94c4e --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/AUTHORS.txt @@ -0,0 +1,5 @@ +The authors in alphabetical order + +* Charlie Clark +* Daniel Hillier +* Elias Rabel diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.python b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.python new file mode 100644 index 0000000..3740f80 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.python @@ -0,0 +1,298 @@ +et_xml is licensed under the MIT license; see the file LICENCE for details. + +et_xml includes code from the Python standard library, which is licensed under +the Python license, a permissive open source license. The copyright and license +is included below for compliance with Python's terms. + +This module includes corrections and new features as follows: +- Correct handling of attributes namespaces when a default namespace + has been registered. +- Records the namespaces for an Element during parsing and utilises them to + allow inspection of namespaces at specific elements in the xml tree and + during serialisation. + +Misc: +- Includes the test_xml_etree with small modifications for testing the + modifications in this package. + +---------------------------------------------------------------------- + +Copyright (c) 2001-present Python Software Foundation; All Rights Reserved + +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001-2024 Python Software Foundation; All Rights Reserved" +are retained in Python alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.rst b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.rst new file mode 100644 index 0000000..82213c5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/LICENCE.rst @@ -0,0 +1,23 @@ +This software is under the MIT Licence +====================================== + +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/METADATA new file mode 100644 index 0000000..3eee724 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/METADATA @@ -0,0 +1,51 @@ +Metadata-Version: 2.1 +Name: et_xmlfile +Version: 2.0.0 +Summary: An implementation of lxml.xmlfile for the standard library +Home-page: https://foss.heptapod.net/openpyxl/et_xmlfile +Author: See AUTHORS.txt +Author-email: charlie.clark@clark-consulting.eu +License: MIT +Project-URL: Documentation, https://openpyxl.pages.heptapod.net/et_xmlfile/ +Project-URL: Source, https://foss.heptapod.net/openpyxl/et_xmlfile +Project-URL: Tracker, https://foss.heptapod.net/openpyxl/et_xmfile/-/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.8 +License-File: LICENCE.python +License-File: LICENCE.rst +License-File: AUTHORS.txt + +.. image:: https://foss.heptapod.net/openpyxl/et_xmlfile/badges/branch/default/coverage.svg + :target: https://coveralls.io/bitbucket/openpyxl/et_xmlfile?branch=default + :alt: coverage status + +et_xmfile +========= + +XML can use lots of memory, and et_xmlfile is a low memory library for creating large XML files +And, although the standard library already includes an incremental parser, `iterparse` it has no equivalent when writing XML. Once an element has been added to the tree, it is written to +the file or stream and the memory is then cleared. + +This module is based upon the `xmlfile module from lxml `_ with the aim of allowing code to be developed that will work with both libraries. +It was developed initially for the openpyxl project, but is now a standalone module. + +The code was written by Elias Rabel as part of the `Python Düsseldorf `_ openpyxl sprint in September 2014. + +Proper support for incremental writing was provided by Daniel Hillier in 2024 + +Note on performance +------------------- + +The code was not developed with performance in mind, but turned out to be faster than the existing SAX-based implementation but is generally slower than lxml's xmlfile. +There is one area where an optimisation for lxml may negatively affect the performance of et_xmfile and that is when using the `.element()` method on the xmlfile context manager. It is, therefore, recommended simply to create Elements write these directly, as in the sample code. diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/RECORD new file mode 100644 index 0000000..036a078 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/RECORD @@ -0,0 +1,14 @@ +et_xmlfile-2.0.0.dist-info/AUTHORS.txt,sha256=fwOAKepUY2Bd0ieNMACZo4G86ekN2oPMqyBCNGtsgQc,82 +et_xmlfile-2.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +et_xmlfile-2.0.0.dist-info/LICENCE.python,sha256=TM2q68D0S4NyDsA5m7erMprc4GfdYvc8VTWi3AViirI,14688 +et_xmlfile-2.0.0.dist-info/LICENCE.rst,sha256=DIS7QvXTZ-Xr-fwt3jWxYUHfXuD9wYklCFi8bFVg9p4,1131 +et_xmlfile-2.0.0.dist-info/METADATA,sha256=DpfX6pCe0PvgPYi8i29YZ3zuGwe9M1PONhzSQFkVIE4,2711 +et_xmlfile-2.0.0.dist-info/RECORD,, +et_xmlfile-2.0.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91 +et_xmlfile-2.0.0.dist-info/top_level.txt,sha256=34-74d5NNARgTsPxCMta5o28XpBNmSN0iCZhtmx2Fk8,11 +et_xmlfile/__init__.py,sha256=AQ4_2cNUEyUHlHo-Y3Gd6-8S_6eyKd55jYO4eh23UHw,228 +et_xmlfile/__pycache__/__init__.cpython-312.pyc,, +et_xmlfile/__pycache__/incremental_tree.cpython-312.pyc,, +et_xmlfile/__pycache__/xmlfile.cpython-312.pyc,, +et_xmlfile/incremental_tree.py,sha256=lX4VStfzUNK0jtrVsvshPENu7E_zQirglkyRtzGDwEg,34534 +et_xmlfile/xmlfile.py,sha256=6QdxBq2P0Cf35R-oyXjLl5wOItfJJ4Yy6AlIF9RX7Bg,4886 diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/WHEEL new file mode 100644 index 0000000..71360e0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (72.2.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/top_level.txt new file mode 100644 index 0000000..f573c27 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile-2.0.0.dist-info/top_level.txt @@ -0,0 +1 @@ +et_xmlfile diff --git a/venv/lib/python3.12/site-packages/et_xmlfile/__init__.py b/venv/lib/python3.12/site-packages/et_xmlfile/__init__.py new file mode 100644 index 0000000..776a146 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile/__init__.py @@ -0,0 +1,8 @@ +from .xmlfile import xmlfile + +# constants +__version__ = '2.0.0' +__author__ = 'See AUTHORS.txt' +__license__ = 'MIT' +__author_email__ = 'charlie.clark@clark-consulting.eu' +__url__ = 'https://foss.heptapod.net/openpyxl/et_xmlfile' diff --git a/venv/lib/python3.12/site-packages/et_xmlfile/incremental_tree.py b/venv/lib/python3.12/site-packages/et_xmlfile/incremental_tree.py new file mode 100644 index 0000000..b735c1b --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile/incremental_tree.py @@ -0,0 +1,917 @@ +# Code modified from cPython's Lib/xml/etree/ElementTree.py +# The write() code is modified to allow specifying a particular namespace +# uri -> prefix mapping. +# +# --------------------------------------------------------------------- +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +# +# ElementTree +# Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2008 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- +import contextlib +import io + +import xml.etree.ElementTree as ET + + +def current_global_nsmap(): + return { + prefix: uri for uri, prefix in ET._namespace_map.items() + } + + +class IncrementalTree(ET.ElementTree): + + def write( + self, + file_or_filename, + encoding=None, + xml_declaration=None, + default_namespace=None, + method=None, + *, + short_empty_elements=True, + nsmap=None, + root_ns_only=False, + minimal_ns_only=False, + ): + """Write element tree to a file as XML. + + Arguments: + *file_or_filename* -- file name or a file object opened for writing + + *encoding* -- the output encoding (default: US-ASCII) + + *xml_declaration* -- bool indicating if an XML declaration should be + added to the output. If None, an XML declaration + is added if encoding IS NOT either of: + US-ASCII, UTF-8, or Unicode + + *default_namespace* -- sets the default XML namespace (for "xmlns"). + Takes precedence over any default namespace + provided in nsmap or + xml.etree.ElementTree.register_namespace(). + + *method* -- either "xml" (default), "html, "text", or "c14n" + + *short_empty_elements* -- controls the formatting of elements + that contain no content. If True (default) + they are emitted as a single self-closed + tag, otherwise they are emitted as a pair + of start/end tags + + *nsmap* -- a mapping of namespace prefixes to URIs. These take + precedence over any mappings registered using + xml.etree.ElementTree.register_namespace(). The + default_namespace argument, if supplied, takes precedence + over any default namespace supplied in nsmap. All supplied + namespaces will be declared on the root element, even if + unused in the document. + + *root_ns_only* -- bool indicating namespace declrations should only + be written on the root element. This requires two + passes of the xml tree adding additional time to + the writing process. This is primarily meant to + mimic xml.etree.ElementTree's behaviour. + + *minimal_ns_only* -- bool indicating only namespaces that were used + to qualify elements or attributes should be + declared. All namespace declarations will be + written on the root element regardless of the + value of the root_ns_only arg. Requires two + passes of the xml tree adding additional time to + the writing process. + + """ + if not method: + method = "xml" + elif method not in ("text", "xml", "html"): + raise ValueError("unknown method %r" % method) + if not encoding: + encoding = "us-ascii" + + with _get_writer(file_or_filename, encoding) as (write, declared_encoding): + if method == "xml" and ( + xml_declaration + or ( + xml_declaration is None + and encoding.lower() != "unicode" + and declared_encoding.lower() not in ("utf-8", "us-ascii") + ) + ): + write("\n" % (declared_encoding,)) + if method == "text": + ET._serialize_text(write, self._root) + else: + if method == "xml": + is_html = False + else: + is_html = True + if nsmap: + if None in nsmap: + raise ValueError( + 'Found None as default nsmap prefix in nsmap. ' + 'Use "" as the default namespace prefix.' + ) + new_nsmap = nsmap.copy() + else: + new_nsmap = {} + if default_namespace: + new_nsmap[""] = default_namespace + if root_ns_only or minimal_ns_only: + # _namespaces returns a mapping of only the namespaces that + # were used. + new_nsmap = _namespaces( + self._root, + default_namespace, + new_nsmap, + ) + if not minimal_ns_only: + if nsmap: + # We want all namespaces defined in the provided + # nsmap to be declared regardless of whether + # they've been used. + new_nsmap.update(nsmap) + if default_namespace: + new_nsmap[""] = default_namespace + global_nsmap = { + prefix: uri for uri, prefix in ET._namespace_map.items() + } + if None in global_nsmap: + raise ValueError( + 'Found None as default nsmap prefix in nsmap registered with ' + 'register_namespace. Use "" for the default namespace prefix.' + ) + nsmap_scope = {} + _serialize_ns_xml( + write, + self._root, + nsmap_scope, + global_nsmap, + is_html=is_html, + is_root=True, + short_empty_elements=short_empty_elements, + new_nsmap=new_nsmap, + ) + + +def _make_new_ns_prefix( + nsmap_scope, + global_prefixes, + local_nsmap=None, + default_namespace=None, +): + i = len(nsmap_scope) + if default_namespace is not None and "" not in nsmap_scope: + # Keep the same numbering scheme as python which assumes the default + # namespace is present if supplied. + i += 1 + + while True: + prefix = f"ns{i}" + if ( + prefix not in nsmap_scope + and prefix not in global_prefixes + and ( + not local_nsmap or prefix not in local_nsmap + ) + ): + return prefix + i += 1 + + +def _get_or_create_prefix( + uri, + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + for_default_namespace_attr_prefix=False, +): + """Find a prefix that doesn't conflict with the ns scope or create a new prefix + + This function mutates nsmap_scope, global_nsmap, new_namespace_prefixes and + uri_to_prefix. It is intended to keep state in _serialize_ns_xml consistent + while deduplicating the house keeping code or updating these dictionaries. + """ + # Check if we can reuse an existing (global) prefix within the current + # namespace scope. There maybe many prefixes pointing to a single URI by + # this point and we need to select a prefix that is not in use in the + # current scope. + for global_prefix, global_uri in global_nsmap.items(): + if uri == global_uri and global_prefix not in nsmap_scope: + prefix = global_prefix + break + else: # no break + # We couldn't find a suitable existing prefix for this namespace scope, + # let's create a new one. + prefix = _make_new_ns_prefix(nsmap_scope, global_prefixes=global_nsmap) + global_nsmap[prefix] = uri + nsmap_scope[prefix] = uri + if not for_default_namespace_attr_prefix: + # Don't override the actual default namespace prefix + uri_to_prefix[uri] = prefix + if prefix != "xml": + new_namespace_prefixes.add(prefix) + return prefix + + +def _find_default_namespace_attr_prefix( + default_namespace, + nsmap, + local_nsmap, + global_prefixes, + provided_default_namespace=None, +): + # Search the provided nsmap for any prefixes for this uri that aren't the + # default namespace "" + for prefix, uri in nsmap.items(): + if uri == default_namespace and prefix != "": + return prefix + + for prefix, uri in local_nsmap.items(): + if uri == default_namespace and prefix != "": + return prefix + + # _namespace_map is a 1:1 mapping of uri -> prefix + prefix = ET._namespace_map.get(default_namespace) + if prefix and prefix not in nsmap: + return prefix + + return _make_new_ns_prefix( + nsmap, + global_prefixes, + local_nsmap, + provided_default_namespace, + ) + + +def process_attribs( + elem, + is_nsmap_scope_changed, + default_ns_attr_prefix, + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, +): + item_parts = [] + for k, v in elem.items(): + if isinstance(k, ET.QName): + k = k.text + try: + if k[:1] == "{": + uri_and_name = k[1:].rsplit("}", 1) + try: + prefix = uri_to_prefix[uri_and_name[0]] + except KeyError: + if not is_nsmap_scope_changed: + # We're about to mutate the these dicts so + # let's copy them first. We don't have to + # recompute other mappings as we're looking up + # or creating a new prefix + nsmap_scope = nsmap_scope.copy() + uri_to_prefix = uri_to_prefix.copy() + is_nsmap_scope_changed = True + prefix = _get_or_create_prefix( + uri_and_name[0], + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + ) + + if not prefix: + if default_ns_attr_prefix: + prefix = default_ns_attr_prefix + else: + for prefix, known_uri in nsmap_scope.items(): + if known_uri == uri_and_name[0] and prefix != "": + default_ns_attr_prefix = prefix + break + else: # no break + if not is_nsmap_scope_changed: + # We're about to mutate the these dicts so + # let's copy them first. We don't have to + # recompute other mappings as we're looking up + # or creating a new prefix + nsmap_scope = nsmap_scope.copy() + uri_to_prefix = uri_to_prefix.copy() + is_nsmap_scope_changed = True + prefix = _get_or_create_prefix( + uri_and_name[0], + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + for_default_namespace_attr_prefix=True, + ) + default_ns_attr_prefix = prefix + k = f"{prefix}:{uri_and_name[1]}" + except TypeError: + ET._raise_serialization_error(k) + + if isinstance(v, ET.QName): + if v.text[:1] != "{": + v = v.text + else: + uri_and_name = v.text[1:].rsplit("}", 1) + try: + prefix = uri_to_prefix[uri_and_name[0]] + except KeyError: + if not is_nsmap_scope_changed: + # We're about to mutate the these dicts so + # let's copy them first. We don't have to + # recompute other mappings as we're looking up + # or creating a new prefix + nsmap_scope = nsmap_scope.copy() + uri_to_prefix = uri_to_prefix.copy() + is_nsmap_scope_changed = True + prefix = _get_or_create_prefix( + uri_and_name[0], + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + ) + v = f"{prefix}:{uri_and_name[1]}" + item_parts.append((k, v)) + return item_parts, default_ns_attr_prefix, nsmap_scope + + +def write_elem_start( + write, + elem, + nsmap_scope, + global_nsmap, + short_empty_elements, + is_html, + is_root=False, + uri_to_prefix=None, + default_ns_attr_prefix=None, + new_nsmap=None, + **kwargs, +): + """Write the opening tag (including self closing) and element text. + + Refer to _serialize_ns_xml for description of arguments. + + nsmap_scope should be an empty dictionary on first call. All nsmap prefixes + must be strings with the default namespace prefix represented by "". + + eg. + - (returns tag = 'foo') + - text (returns tag = 'foo') + - (returns tag = None) + + Returns: + tag: + The tag name to be closed or None if no closing required. + nsmap_scope: + The current nsmap after any prefix to uri additions from this + element. This is the input dict if unmodified or an updated copy. + default_ns_attr_prefix: + The prefix for the default namespace to use with attrs. + uri_to_prefix: + The current uri to prefix map after any uri to prefix additions + from this element. This is the input dict if unmodified or an + updated copy. + next_remains_root: + A bool indicating if the child element(s) should be treated as + their own roots. + """ + tag = elem.tag + text = elem.text + + if tag is ET.Comment: + write("" % text) + tag = None + next_remains_root = False + elif tag is ET.ProcessingInstruction: + write("" % text) + tag = None + next_remains_root = False + else: + if new_nsmap: + is_nsmap_scope_changed = True + nsmap_scope = nsmap_scope.copy() + nsmap_scope.update(new_nsmap) + new_namespace_prefixes = set(new_nsmap.keys()) + new_namespace_prefixes.discard("xml") + # We need to recompute the uri to prefixes + uri_to_prefix = None + default_ns_attr_prefix = None + else: + is_nsmap_scope_changed = False + new_namespace_prefixes = set() + + if uri_to_prefix is None: + if None in nsmap_scope: + raise ValueError( + 'Found None as a namespace prefix. Use "" as the default namespace prefix.' + ) + uri_to_prefix = {uri: prefix for prefix, uri in nsmap_scope.items()} + if "" in nsmap_scope: + # There may be multiple prefixes for the default namespace but + # we want to make sure we preferentially use "" (for elements) + uri_to_prefix[nsmap_scope[""]] = "" + + if tag is None: + # tag supression where tag is set to None + # Don't change is_root so namespaces can be passed down + next_remains_root = is_root + if text: + write(ET._escape_cdata(text)) + else: + next_remains_root = False + if isinstance(tag, ET.QName): + tag = tag.text + try: + # These splits / fully qualified tag creationg are the + # bottleneck in this implementation vs the python + # implementation. + # The following split takes ~42ns with no uri and ~85ns if a + # prefix is present. If the uri was present, we then need to + # look up a prefix (~14ns) and create the fully qualified + # string (~41ns). This gives a total of ~140ns where a uri is + # present. + # Python's implementation needs to preprocess the tree to + # create a dict of qname -> tag by traversing the tree which + # takes a bit of extra time but it quickly makes that back by + # only having to do a dictionary look up (~14ns) for each tag / + # attrname vs our splitting (~140ns). + # So here we have the flexibility of being able to redefine the + # uri a prefix points to midway through serialisation at the + # expense of performance (~10% slower for a 1mb file on my + # machine). + if tag[:1] == "{": + uri_and_name = tag[1:].rsplit("}", 1) + try: + prefix = uri_to_prefix[uri_and_name[0]] + except KeyError: + if not is_nsmap_scope_changed: + # We're about to mutate the these dicts so let's + # copy them first. We don't have to recompute other + # mappings as we're looking up or creating a new + # prefix + nsmap_scope = nsmap_scope.copy() + uri_to_prefix = uri_to_prefix.copy() + is_nsmap_scope_changed = True + prefix = _get_or_create_prefix( + uri_and_name[0], + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + ) + if prefix: + tag = f"{prefix}:{uri_and_name[1]}" + else: + tag = uri_and_name[1] + elif "" in nsmap_scope: + raise ValueError( + "cannot use non-qualified names with default_namespace option" + ) + except TypeError: + ET._raise_serialization_error(tag) + + write("<" + tag) + + if elem.attrib: + item_parts, default_ns_attr_prefix, nsmap_scope = process_attribs( + elem, + is_nsmap_scope_changed, + default_ns_attr_prefix, + nsmap_scope, + global_nsmap, + new_namespace_prefixes, + uri_to_prefix, + ) + else: + item_parts = [] + if new_namespace_prefixes: + ns_attrs = [] + for k in sorted(new_namespace_prefixes): + v = nsmap_scope[k] + if k: + k = "xmlns:" + k + else: + k = "xmlns" + ns_attrs.append((k, v)) + if is_html: + write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in ns_attrs])) + else: + write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in ns_attrs])) + if item_parts: + if is_html: + write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in item_parts])) + else: + write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in item_parts])) + if is_html: + write(">") + ltag = tag.lower() + if text: + if ltag == "script" or ltag == "style": + write(text) + else: + write(ET._escape_cdata(text)) + if ltag in ET.HTML_EMPTY: + tag = None + elif text or len(elem) or not short_empty_elements: + write(">") + if text: + write(ET._escape_cdata(text)) + else: + tag = None + write(" />") + return ( + tag, + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + next_remains_root, + ) + + +def _serialize_ns_xml( + write, + elem, + nsmap_scope, + global_nsmap, + short_empty_elements, + is_html, + is_root=False, + uri_to_prefix=None, + default_ns_attr_prefix=None, + new_nsmap=None, + **kwargs, +): + """Serialize an element or tree using 'write' for output. + + Args: + write: + A function to write the xml to its destination. + elem: + The element to serialize. + nsmap_scope: + The current prefix to uri mapping for this element. This should be + an empty dictionary for the root element. Additional namespaces are + progressively added using the new_nsmap arg. + global_nsmap: + A dict copy of the globally registered _namespace_map in uri to + prefix form + short_empty_elements: + Controls the formatting of elements that contain no content. If True + (default) they are emitted as a single self-closed tag, otherwise + they are emitted as a pair of start/end tags. + is_html: + Set to True to serialize as HTML otherwise XML. + is_root: + Boolean indicating if this is a root element. + uri_to_prefix: + Current state of the mapping of uri to prefix. + default_ns_attr_prefix: + new_nsmap: + New prefix -> uri mapping to be applied to this element. + """ + ( + tag, + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + next_remains_root, + ) = write_elem_start( + write, + elem, + nsmap_scope, + global_nsmap, + short_empty_elements, + is_html, + is_root, + uri_to_prefix, + default_ns_attr_prefix, + new_nsmap=new_nsmap, + ) + for e in elem: + _serialize_ns_xml( + write, + e, + nsmap_scope, + global_nsmap, + short_empty_elements, + is_html, + next_remains_root, + uri_to_prefix, + default_ns_attr_prefix, + new_nsmap=None, + ) + if tag: + write(f"") + if elem.tail: + write(ET._escape_cdata(elem.tail)) + + +def _qnames_iter(elem): + """Iterate through all the qualified names in elem""" + seen_el_qnames = set() + seen_other_qnames = set() + for this_elem in elem.iter(): + tag = this_elem.tag + if isinstance(tag, str): + if tag not in seen_el_qnames: + seen_el_qnames.add(tag) + yield tag, True + elif isinstance(tag, ET.QName): + tag = tag.text + if tag not in seen_el_qnames: + seen_el_qnames.add(tag) + yield tag, True + elif ( + tag is not None + and tag is not ET.ProcessingInstruction + and tag is not ET.Comment + ): + ET._raise_serialization_error(tag) + + for key, value in this_elem.items(): + if isinstance(key, ET.QName): + key = key.text + if key not in seen_other_qnames: + seen_other_qnames.add(key) + yield key, False + + if isinstance(value, ET.QName): + if value.text not in seen_other_qnames: + seen_other_qnames.add(value.text) + yield value.text, False + + text = this_elem.text + if isinstance(text, ET.QName): + if text.text not in seen_other_qnames: + seen_other_qnames.add(text.text) + yield text.text, False + + +def _namespaces( + elem, + default_namespace=None, + nsmap=None, +): + """Find all namespaces used in the document and return a prefix to uri map""" + if nsmap is None: + nsmap = {} + + out_nsmap = {} + + seen_uri_to_prefix = {} + # Multiple prefixes may be present for a single uri. This will select the + # last prefix found in nsmap for a given uri. + local_prefix_map = {uri: prefix for prefix, uri in nsmap.items()} + if default_namespace is not None: + local_prefix_map[default_namespace] = "" + elif "" in nsmap: + # but we make sure the default prefix always take precedence + local_prefix_map[nsmap[""]] = "" + + global_prefixes = set(ET._namespace_map.values()) + has_unqual_el = False + default_namespace_attr_prefix = None + for qname, is_el in _qnames_iter(elem): + try: + if qname[:1] == "{": + uri_and_name = qname[1:].rsplit("}", 1) + + prefix = seen_uri_to_prefix.get(uri_and_name[0]) + if prefix is None: + prefix = local_prefix_map.get(uri_and_name[0]) + if prefix is None or prefix in out_nsmap: + prefix = ET._namespace_map.get(uri_and_name[0]) + if prefix is None or prefix in out_nsmap: + prefix = _make_new_ns_prefix( + out_nsmap, + global_prefixes, + nsmap, + default_namespace, + ) + if prefix or is_el: + out_nsmap[prefix] = uri_and_name[0] + seen_uri_to_prefix[uri_and_name[0]] = prefix + + if not is_el and not prefix and not default_namespace_attr_prefix: + # Find the alternative prefix to use with non-element + # names + default_namespace_attr_prefix = _find_default_namespace_attr_prefix( + uri_and_name[0], + out_nsmap, + nsmap, + global_prefixes, + default_namespace, + ) + out_nsmap[default_namespace_attr_prefix] = uri_and_name[0] + # Don't add this uri to prefix mapping as it might override + # the uri -> "" default mapping. We'll fix this up at the + # end of the fn. + # local_prefix_map[uri_and_name[0]] = default_namespace_attr_prefix + else: + if is_el: + has_unqual_el = True + except TypeError: + ET._raise_serialization_error(qname) + + if "" in out_nsmap and has_unqual_el: + # FIXME: can this be handled in XML 1.0? + raise ValueError( + "cannot use non-qualified names with default_namespace option" + ) + + # The xml prefix doesn't need to be declared but may have been used to + # prefix names. Let's remove it if it has been used + out_nsmap.pop("xml", None) + return out_nsmap + + +def tostring( + element, + encoding=None, + method=None, + *, + xml_declaration=None, + default_namespace=None, + short_empty_elements=True, + nsmap=None, + root_ns_only=False, + minimal_ns_only=False, + tree_cls=IncrementalTree, +): + """Generate string representation of XML element. + + All subelements are included. If encoding is "unicode", a string + is returned. Otherwise a bytestring is returned. + + *element* is an Element instance, *encoding* is an optional output + encoding defaulting to US-ASCII, *method* is an optional output which can + be one of "xml" (default), "html", "text" or "c14n", *default_namespace* + sets the default XML namespace (for "xmlns"). + + Returns an (optionally) encoded string containing the XML data. + + """ + stream = io.StringIO() if encoding == "unicode" else io.BytesIO() + tree_cls(element).write( + stream, + encoding, + xml_declaration=xml_declaration, + default_namespace=default_namespace, + method=method, + short_empty_elements=short_empty_elements, + nsmap=nsmap, + root_ns_only=root_ns_only, + minimal_ns_only=minimal_ns_only, + ) + return stream.getvalue() + + +def tostringlist( + element, + encoding=None, + method=None, + *, + xml_declaration=None, + default_namespace=None, + short_empty_elements=True, + nsmap=None, + root_ns_only=False, + minimal_ns_only=False, + tree_cls=IncrementalTree, +): + lst = [] + stream = ET._ListDataStream(lst) + tree_cls(element).write( + stream, + encoding, + xml_declaration=xml_declaration, + default_namespace=default_namespace, + method=method, + short_empty_elements=short_empty_elements, + nsmap=nsmap, + root_ns_only=root_ns_only, + minimal_ns_only=minimal_ns_only, + ) + return lst + + +def compat_tostring( + element, + encoding=None, + method=None, + *, + xml_declaration=None, + default_namespace=None, + short_empty_elements=True, + nsmap=None, + root_ns_only=True, + minimal_ns_only=False, + tree_cls=IncrementalTree, +): + """tostring with options that produce the same results as xml.etree.ElementTree.tostring + + root_ns_only=True is a bit slower than False as it needs to traverse the + tree one more time to collect all the namespaces. + """ + return tostring( + element, + encoding=encoding, + method=method, + xml_declaration=xml_declaration, + default_namespace=default_namespace, + short_empty_elements=short_empty_elements, + nsmap=nsmap, + root_ns_only=root_ns_only, + minimal_ns_only=minimal_ns_only, + tree_cls=tree_cls, + ) + + +# -------------------------------------------------------------------- +# serialization support + +@contextlib.contextmanager +def _get_writer(file_or_filename, encoding): + # Copied from Python 3.12 + # returns text write method and release all resources after using + try: + write = file_or_filename.write + except AttributeError: + # file_or_filename is a file name + if encoding.lower() == "unicode": + encoding = "utf-8" + with open(file_or_filename, "w", encoding=encoding, + errors="xmlcharrefreplace") as file: + yield file.write, encoding + else: + # file_or_filename is a file-like object + # encoding determines if it is a text or binary writer + if encoding.lower() == "unicode": + # use a text writer as is + yield write, getattr(file_or_filename, "encoding", None) or "utf-8" + else: + # wrap a binary writer with TextIOWrapper + with contextlib.ExitStack() as stack: + if isinstance(file_or_filename, io.BufferedIOBase): + file = file_or_filename + elif isinstance(file_or_filename, io.RawIOBase): + file = io.BufferedWriter(file_or_filename) + # Keep the original file open when the BufferedWriter is + # destroyed + stack.callback(file.detach) + else: + # This is to handle passed objects that aren't in the + # IOBase hierarchy, but just have a write method + file = io.BufferedIOBase() + file.writable = lambda: True + file.write = write + try: + # TextIOWrapper uses this methods to determine + # if BOM (for UTF-16, etc) should be added + file.seekable = file_or_filename.seekable + file.tell = file_or_filename.tell + except AttributeError: + pass + file = io.TextIOWrapper(file, + encoding=encoding, + errors="xmlcharrefreplace", + newline="\n") + # Keep the original file open when the TextIOWrapper is + # destroyed + stack.callback(file.detach) + yield file.write, encoding diff --git a/venv/lib/python3.12/site-packages/et_xmlfile/xmlfile.py b/venv/lib/python3.12/site-packages/et_xmlfile/xmlfile.py new file mode 100644 index 0000000..9b8ce82 --- /dev/null +++ b/venv/lib/python3.12/site-packages/et_xmlfile/xmlfile.py @@ -0,0 +1,158 @@ +from __future__ import absolute_import +# Copyright (c) 2010-2015 openpyxl + +"""Implements the lxml.etree.xmlfile API using the standard library xml.etree""" + + +from contextlib import contextmanager + +from xml.etree.ElementTree import ( + Element, + _escape_cdata, +) + +from . import incremental_tree + + +class LxmlSyntaxError(Exception): + pass + + +class _IncrementalFileWriter(object): + """Replacement for _IncrementalFileWriter of lxml""" + def __init__(self, output_file): + self._element_stack = [] + self._file = output_file + self._have_root = False + self.global_nsmap = incremental_tree.current_global_nsmap() + self.is_html = False + + @contextmanager + def element(self, tag, attrib=None, nsmap=None, **_extra): + """Create a new xml element using a context manager.""" + if nsmap and None in nsmap: + # Normalise None prefix (lxml's default namespace prefix) -> "", as + # required for incremental_tree + if "" in nsmap and nsmap[""] != nsmap[None]: + raise ValueError( + 'Found None and "" as default nsmap prefixes with different URIs' + ) + nsmap = nsmap.copy() + nsmap[""] = nsmap.pop(None) + + # __enter__ part + self._have_root = True + if attrib is None: + attrib = {} + elem = Element(tag, attrib=attrib, **_extra) + elem.text = '' + elem.tail = '' + if self._element_stack: + is_root = False + ( + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + ) = self._element_stack[-1] + else: + is_root = True + nsmap_scope = {} + default_ns_attr_prefix = None + uri_to_prefix = {} + ( + tag, + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + next_remains_root, + ) = incremental_tree.write_elem_start( + self._file, + elem, + nsmap_scope=nsmap_scope, + global_nsmap=self.global_nsmap, + short_empty_elements=False, + is_html=self.is_html, + is_root=is_root, + uri_to_prefix=uri_to_prefix, + default_ns_attr_prefix=default_ns_attr_prefix, + new_nsmap=nsmap, + ) + self._element_stack.append( + ( + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + ) + ) + yield + + # __exit__ part + self._element_stack.pop() + self._file(f"") + if elem.tail: + self._file(_escape_cdata(elem.tail)) + + def write(self, arg): + """Write a string or subelement.""" + + if isinstance(arg, str): + # it is not allowed to write a string outside of an element + if not self._element_stack: + raise LxmlSyntaxError() + self._file(_escape_cdata(arg)) + + else: + if not self._element_stack and self._have_root: + raise LxmlSyntaxError() + + if self._element_stack: + is_root = False + ( + nsmap_scope, + default_ns_attr_prefix, + uri_to_prefix, + ) = self._element_stack[-1] + else: + is_root = True + nsmap_scope = {} + default_ns_attr_prefix = None + uri_to_prefix = {} + incremental_tree._serialize_ns_xml( + self._file, + arg, + nsmap_scope=nsmap_scope, + global_nsmap=self.global_nsmap, + short_empty_elements=True, + is_html=self.is_html, + is_root=is_root, + uri_to_prefix=uri_to_prefix, + default_ns_attr_prefix=default_ns_attr_prefix, + ) + + def __enter__(self): + pass + + def __exit__(self, type, value, traceback): + # without root the xml document is incomplete + if not self._have_root: + raise LxmlSyntaxError() + + +class xmlfile(object): + """Context manager that can replace lxml.etree.xmlfile.""" + def __init__(self, output_file, buffered=False, encoding="utf-8", close=False): + self._file = output_file + self._close = close + self.encoding = encoding + self.writer_cm = None + + def __enter__(self): + self.writer_cm = incremental_tree._get_writer(self._file, encoding=self.encoding) + writer, declared_encoding = self.writer_cm.__enter__() + return _IncrementalFileWriter(writer) + + def __exit__(self, type, value, traceback): + if self.writer_cm: + self.writer_cm.__exit__(type, value, traceback) + if self._close: + self._file.close() diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/LICENCE.rst b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/LICENCE.rst new file mode 100644 index 0000000..82213c5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/LICENCE.rst @@ -0,0 +1,23 @@ +This software is under the MIT Licence +====================================== + +Copyright (c) 2010 openpyxl + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/METADATA b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/METADATA new file mode 100644 index 0000000..bac5c46 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/METADATA @@ -0,0 +1,86 @@ +Metadata-Version: 2.1 +Name: openpyxl +Version: 3.1.5 +Summary: A Python library to read/write Excel 2010 xlsx/xlsm files +Home-page: https://openpyxl.readthedocs.io +Author: See AUTHORS +Author-email: charlie.clark@clark-consulting.eu +License: MIT +Project-URL: Documentation, https://openpyxl.readthedocs.io/en/stable/ +Project-URL: Source, https://foss.heptapod.net/openpyxl/openpyxl +Project-URL: Tracker, https://foss.heptapod.net/openpyxl/openpyxl/-/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Requires-Python: >=3.8 +License-File: LICENCE.rst +Requires-Dist: et-xmlfile + +.. image:: https://coveralls.io/repos/bitbucket/openpyxl/openpyxl/badge.svg?branch=default + :target: https://coveralls.io/bitbucket/openpyxl/openpyxl?branch=default + :alt: coverage status + +Introduction +------------ + +openpyxl is a Python library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files. + +It was born from lack of existing library to read/write natively from Python +the Office Open XML format. + +All kudos to the PHPExcel team as openpyxl was initially based on PHPExcel. + + +Security +-------- + +By default openpyxl does not guard against quadratic blowup or billion laughs +xml attacks. To guard against these attacks install defusedxml. + +Mailing List +------------ + +The user list can be found on http://groups.google.com/group/openpyxl-users + + +Sample code:: + + from openpyxl import Workbook + wb = Workbook() + + # grab the active worksheet + ws = wb.active + + # Data can be assigned directly to cells + ws['A1'] = 42 + + # Rows can also be appended + ws.append([1, 2, 3]) + + # Python types will automatically be converted + import datetime + ws['A2'] = datetime.datetime.now() + + # Save the file + wb.save("sample.xlsx") + + +Documentation +------------- + +The documentation is at: https://openpyxl.readthedocs.io + +* installation methods +* code examples +* instructions for contributing + +Release notes: https://openpyxl.readthedocs.io/en/stable/changes.html diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/RECORD b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/RECORD new file mode 100644 index 0000000..1dd09fc --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/RECORD @@ -0,0 +1,387 @@ +openpyxl-3.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +openpyxl-3.1.5.dist-info/LICENCE.rst,sha256=DIS7QvXTZ-Xr-fwt3jWxYUHfXuD9wYklCFi8bFVg9p4,1131 +openpyxl-3.1.5.dist-info/METADATA,sha256=I_gMqYMN2JQ12hcQ8m3tqPgeVAkofnRUAhDHJiekrZY,2510 +openpyxl-3.1.5.dist-info/RECORD,, +openpyxl-3.1.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openpyxl-3.1.5.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110 +openpyxl-3.1.5.dist-info/top_level.txt,sha256=mKJO5QFAsUEDtJ_c97F-IbmVtHYEDymqD7d5X0ULkVs,9 +openpyxl/__init__.py,sha256=s2sXcp8ThXXHswNSh-UuQi5BHsoasuczUyjNNz0Vupc,603 +openpyxl/__pycache__/__init__.cpython-312.pyc,, +openpyxl/__pycache__/_constants.cpython-312.pyc,, +openpyxl/_constants.py,sha256=rhOeQ6wNH6jw73G4I242VtbmyM8fvdNVwOsOjJlJ6TU,306 +openpyxl/cell/__init__.py,sha256=OXNzFFR9dlxUXiuWXyKSVQRJiQhZFel-_RQS3mHNnrQ,122 +openpyxl/cell/__pycache__/__init__.cpython-312.pyc,, +openpyxl/cell/__pycache__/_writer.cpython-312.pyc,, +openpyxl/cell/__pycache__/cell.cpython-312.pyc,, +openpyxl/cell/__pycache__/read_only.cpython-312.pyc,, +openpyxl/cell/__pycache__/rich_text.cpython-312.pyc,, +openpyxl/cell/__pycache__/text.cpython-312.pyc,, +openpyxl/cell/_writer.py,sha256=3I6WLKEJGuFe8rOjxdAVuDT4sZYjcYo57-6velGepdQ,4015 +openpyxl/cell/cell.py,sha256=hVJsMC9kJAxxb_CspJlBrwDt2qzfccO6YDfPHK3BBCQ,8922 +openpyxl/cell/read_only.py,sha256=ApXkofmUK5QISsuTgZvmZKsU8PufSQtqe2xmYWTgLnc,3097 +openpyxl/cell/rich_text.py,sha256=uAZmGB7bYDUnanHI0vJmKbfSF8riuIYS5CwlVU_3_fM,5628 +openpyxl/cell/text.py,sha256=acU6BZQNSmVx4bBXPgFavoxmfoPbVYrm_ztp1bGeOmc,4367 +openpyxl/chart/_3d.py,sha256=Sdm0TNpXHXNoOLUwiOSccv7yFwrel_-rjQhkrDqAAF4,3104 +openpyxl/chart/__init__.py,sha256=ag4YCN1B3JH0lkS7tiiZCohVAA51x_pejGdAMuxaI1Y,564 +openpyxl/chart/__pycache__/_3d.cpython-312.pyc,, +openpyxl/chart/__pycache__/__init__.cpython-312.pyc,, +openpyxl/chart/__pycache__/_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/area_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/axis.cpython-312.pyc,, +openpyxl/chart/__pycache__/bar_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/bubble_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/chartspace.cpython-312.pyc,, +openpyxl/chart/__pycache__/data_source.cpython-312.pyc,, +openpyxl/chart/__pycache__/descriptors.cpython-312.pyc,, +openpyxl/chart/__pycache__/error_bar.cpython-312.pyc,, +openpyxl/chart/__pycache__/label.cpython-312.pyc,, +openpyxl/chart/__pycache__/layout.cpython-312.pyc,, +openpyxl/chart/__pycache__/legend.cpython-312.pyc,, +openpyxl/chart/__pycache__/line_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/marker.cpython-312.pyc,, +openpyxl/chart/__pycache__/picture.cpython-312.pyc,, +openpyxl/chart/__pycache__/pie_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/pivot.cpython-312.pyc,, +openpyxl/chart/__pycache__/plotarea.cpython-312.pyc,, +openpyxl/chart/__pycache__/print_settings.cpython-312.pyc,, +openpyxl/chart/__pycache__/radar_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/reader.cpython-312.pyc,, +openpyxl/chart/__pycache__/reference.cpython-312.pyc,, +openpyxl/chart/__pycache__/scatter_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/series.cpython-312.pyc,, +openpyxl/chart/__pycache__/series_factory.cpython-312.pyc,, +openpyxl/chart/__pycache__/shapes.cpython-312.pyc,, +openpyxl/chart/__pycache__/stock_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/surface_chart.cpython-312.pyc,, +openpyxl/chart/__pycache__/text.cpython-312.pyc,, +openpyxl/chart/__pycache__/title.cpython-312.pyc,, +openpyxl/chart/__pycache__/trendline.cpython-312.pyc,, +openpyxl/chart/__pycache__/updown_bars.cpython-312.pyc,, +openpyxl/chart/_chart.py,sha256=j5xn6mQYmZ4E7y2V1Xvx1jwhX2_O68Mp-8zeXRteS7E,5746 +openpyxl/chart/area_chart.py,sha256=uROD3fdus6yD1TGu87j4z7KtOEH7tI-3Z5NFK73wwgw,2890 +openpyxl/chart/axis.py,sha256=yommy5q2mQWKmmLRouWBpimiBZDBM1K-UKAIwCwKDNc,12580 +openpyxl/chart/bar_chart.py,sha256=_TQHleMT3gSa6B1BkKD_FkLFcv8LRaoiHbpy2yflLO4,4142 +openpyxl/chart/bubble_chart.py,sha256=KL7VZYFyLDpA8MC-IFtRAUIN262xK6MzjU41DrSVgpY,2004 +openpyxl/chart/chartspace.py,sha256=PuPGBsVbpK5JagbB7SWgp4JwdQtTrZzIm8mf3kfGAuY,6069 +openpyxl/chart/data_source.py,sha256=GAuWoCOJ4k7RZNJZkZck0zt_-D5UfDEwqwQ3ND4-s34,5782 +openpyxl/chart/descriptors.py,sha256=uj-qptwKOBeg7U5xBN4QJQ2OwQvFQ7o4n5eMXXIWS7M,736 +openpyxl/chart/error_bar.py,sha256=GS_L7PiyKNnJVHvQqG2hLxEW237igLLCatCNC-xGMxk,1832 +openpyxl/chart/label.py,sha256=IjvI-CZjTY8ydoUzUOihcbxoRWiSpFb_ipD6C2I8Pu4,4133 +openpyxl/chart/layout.py,sha256=QHakp_CIcoNuvjyZMsQ2p_qP44DIQs4aquy7yln94JM,2040 +openpyxl/chart/legend.py,sha256=iPMycOhYDAVYd05OU_QDB-GSavdw_1L9CMuJIETOoGI,2040 +openpyxl/chart/line_chart.py,sha256=6tAyDCzFiuiBFuUDTWhQepH8xVCx2s57lH951cEcwn0,3951 +openpyxl/chart/marker.py,sha256=kfybMkshK3qefOUW7OX-Os0vfl5OCXfg8MytwHC2i-w,2600 +openpyxl/chart/picture.py,sha256=Q4eBNQMKQDHR91RnPc7tM-YZVdcnWncedUlfagj67gk,1156 +openpyxl/chart/pie_chart.py,sha256=UOvkjrBpNd_rT-rvKcpPeVd9dK-ELdMIaHjAUEr6oN8,4793 +openpyxl/chart/pivot.py,sha256=9kVDmnxnR0uQRQ-Wbl6qw8eew9LGhqomaDBaXqQGZY4,1741 +openpyxl/chart/plotarea.py,sha256=em7yorXFz9SmJruqOR4Pn-2oEj0Su4rnzyNc5e0IZ_U,5805 +openpyxl/chart/print_settings.py,sha256=UwB6Kn6xkLRBejXScl-utF8dkNhV7Lm3Lfk7ACpbRgs,1454 +openpyxl/chart/radar_chart.py,sha256=93I1Y1dmXZ6Y0F1VKXz9I3x1ufgwygBOdbPZumR5n3s,1521 +openpyxl/chart/reader.py,sha256=oQD-29oxSLW2yzXdyXNhzQYNXgM64Y3kVSOIkrPZCuU,802 +openpyxl/chart/reference.py,sha256=N3T4qYMH9BVrtbDRiKIZz-qGvPAdfquWTGL0XKxD9G8,3098 +openpyxl/chart/scatter_chart.py,sha256=JMU32jjxTj7txPJ2TebBHPS5UcMsRHVqLz_psnN2YZs,1563 +openpyxl/chart/series.py,sha256=k8eR8cviH9EPllRjjr_2a-lH5S3_HWBTLyE7XKghzWc,5896 +openpyxl/chart/series_factory.py,sha256=ey1zgNwM1g4bQwB9lLhM6E-ctLIM2kLWM3X7CPw8SDs,1368 +openpyxl/chart/shapes.py,sha256=JkgMy3DUWDKLV6JZHKb_pUBvWpzTAQ3biUMr-1fJWZU,2815 +openpyxl/chart/stock_chart.py,sha256=YJ7eElBX5omHziKo41ygTA7F_NEkyIlFUfdDJXZuKhM,1604 +openpyxl/chart/surface_chart.py,sha256=_-yGEX-Ou2NJVmJCA_K_bSLyzk-RvbPupyQLmjfCWj0,2914 +openpyxl/chart/text.py,sha256=voJCf4PK5olmX0g_5u9aQo8B5LpCUlOeq4j4pnOy_A0,1847 +openpyxl/chart/title.py,sha256=L-7KxwcpMb2aZk4ikgMsIgFPVtBafIppx9ykd5FPJ4w,1952 +openpyxl/chart/trendline.py,sha256=9pWSJa9Adwtd6v_i7dPT7qNKzhOrSMWZ4QuAOntZWVg,3045 +openpyxl/chart/updown_bars.py,sha256=QA4lyEMtMVvZCrYUpHZYMVS1xsnaN4_T5UBi6E7ilQ0,897 +openpyxl/chartsheet/__init__.py,sha256=3Ony1WNbxxWuddTW-peuUPvO3xqIWFWe3Da2OUzsVnI,71 +openpyxl/chartsheet/__pycache__/__init__.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/chartsheet.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/custom.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/properties.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/protection.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/publish.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/relation.cpython-312.pyc,, +openpyxl/chartsheet/__pycache__/views.cpython-312.pyc,, +openpyxl/chartsheet/chartsheet.py,sha256=GTXNfQPYBaS4B7XB4f7gDkAo2kCjtZqidl6iDxp-JQ8,3911 +openpyxl/chartsheet/custom.py,sha256=qVgeCzT7t1tN_pDwaLqtR3ubuPDLeTR5KKlcxwnTWa8,1691 +openpyxl/chartsheet/properties.py,sha256=dR1nrp22FsPkyDrwQaZV7t-p-Z2Jc88Y2IhIGbBvFhk,679 +openpyxl/chartsheet/protection.py,sha256=eJixEBmdoTDO2_0h6g51sdSdfSdCaP8UUNsbEqHds6U,1265 +openpyxl/chartsheet/publish.py,sha256=PrwqsUKn2SK67ZM3NEGT9FH4nOKC1cOxxm3322hHawQ,1587 +openpyxl/chartsheet/relation.py,sha256=ZAAfEZb639ve0k6ByRwmHdjBrjqVC0bHOLgIcBwRx6o,2731 +openpyxl/chartsheet/views.py,sha256=My3Au-DEAcC4lwBARhrCcwsN7Lp9H6cFQT-SiAcJlko,1341 +openpyxl/comments/__init__.py,sha256=k_QJ-OPRme8HgAYQlyxbbRhmS1n2FyowqIeekBW-7vw,67 +openpyxl/comments/__pycache__/__init__.cpython-312.pyc,, +openpyxl/comments/__pycache__/author.cpython-312.pyc,, +openpyxl/comments/__pycache__/comment_sheet.cpython-312.pyc,, +openpyxl/comments/__pycache__/comments.cpython-312.pyc,, +openpyxl/comments/__pycache__/shape_writer.cpython-312.pyc,, +openpyxl/comments/author.py,sha256=PZB_fjQqiEm8BdHDblbfzB0gzkFvECWq5i1jSHeJZco,388 +openpyxl/comments/comment_sheet.py,sha256=Uv2RPpIxrikDPHBr5Yj1dDkusZB97yVE-NQTM0-EnBk,5753 +openpyxl/comments/comments.py,sha256=CxurAWM7WbCdbeya-DQklbiWSFaxhtrUNBZEzulTyxc,1466 +openpyxl/comments/shape_writer.py,sha256=Ls1d0SscfxGM9H2spjxMNHeJSaZJuLawlXs4t4qH7v4,3809 +openpyxl/compat/__init__.py,sha256=fltF__CdGK97l2V3MtIDxbwgV_p1AZvLdyqcEtXKsqs,1592 +openpyxl/compat/__pycache__/__init__.cpython-312.pyc,, +openpyxl/compat/__pycache__/abc.cpython-312.pyc,, +openpyxl/compat/__pycache__/numbers.cpython-312.pyc,, +openpyxl/compat/__pycache__/product.cpython-312.pyc,, +openpyxl/compat/__pycache__/singleton.cpython-312.pyc,, +openpyxl/compat/__pycache__/strings.cpython-312.pyc,, +openpyxl/compat/abc.py,sha256=Y-L6pozzgjr81OfXsjDkGDeKEq6BOfMr6nvrFps_o6Q,155 +openpyxl/compat/numbers.py,sha256=2dckE0PHT7eB89Sc2BdlWOH4ZLXWt3_eo73-CzRujUY,1617 +openpyxl/compat/product.py,sha256=-bDgNMHGDgbahgw0jqale8TeIARLw7HO0soQAL9b_4k,264 +openpyxl/compat/singleton.py,sha256=R1HiH7XpjaW4kr3GILWMc4hRGZkXyc0yK7T1jcg_QWg,1023 +openpyxl/compat/strings.py,sha256=D_TWf8QnMH6WMx6xuCDfXl0boc1k9q7j8hGalVQ2RUk,604 +openpyxl/descriptors/__init__.py,sha256=eISTR0Sa1ZKKNQPxMZtqlE39JugYzkjxiZf7u9fttiw,1952 +openpyxl/descriptors/__pycache__/__init__.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/base.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/container.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/excel.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/namespace.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/nested.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/sequence.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/serialisable.cpython-312.pyc,, +openpyxl/descriptors/__pycache__/slots.cpython-312.pyc,, +openpyxl/descriptors/base.py,sha256=-CuNfswEGazgOoX3GuM2Bs2zkBImT992TvR2R1xsnXM,7135 +openpyxl/descriptors/container.py,sha256=IcO91M02hR0vXZtWGurz0IH1Vi2PoEECP1PEbz62FJQ,889 +openpyxl/descriptors/excel.py,sha256=d6a6mtoZ-33jwMGlgvNTL54cqLANKyhMihG6887j8r0,2412 +openpyxl/descriptors/namespace.py,sha256=LjI4e9R09NSbClr_ewv0YmHgWY8RO5xq1s-SpAvz2wo,313 +openpyxl/descriptors/nested.py,sha256=5LSsf2uvTKsrGEEQF1KVXMLHZFoRgmLfL_lzW0lWQjI,2603 +openpyxl/descriptors/sequence.py,sha256=OqF34K_nUC46XD5B_6xzGHeEICz_82hkFkNFXpBkSSE,3490 +openpyxl/descriptors/serialisable.py,sha256=U_7wMEGQRIOiimUUL4AbdOiWMc_aLyKeaRnj_Z7dVO8,7361 +openpyxl/descriptors/slots.py,sha256=xNj5vLWWoounpYqbP2JDnnhlTiTLRn-uTfQxncpFfn0,824 +openpyxl/drawing/__init__.py,sha256=xlXVaT3Fs9ltvbbRIGTSRow9kw9nhLY3Zj1Mm6vXRHE,66 +openpyxl/drawing/__pycache__/__init__.cpython-312.pyc,, +openpyxl/drawing/__pycache__/colors.cpython-312.pyc,, +openpyxl/drawing/__pycache__/connector.cpython-312.pyc,, +openpyxl/drawing/__pycache__/drawing.cpython-312.pyc,, +openpyxl/drawing/__pycache__/effect.cpython-312.pyc,, +openpyxl/drawing/__pycache__/fill.cpython-312.pyc,, +openpyxl/drawing/__pycache__/geometry.cpython-312.pyc,, +openpyxl/drawing/__pycache__/graphic.cpython-312.pyc,, +openpyxl/drawing/__pycache__/image.cpython-312.pyc,, +openpyxl/drawing/__pycache__/line.cpython-312.pyc,, +openpyxl/drawing/__pycache__/picture.cpython-312.pyc,, +openpyxl/drawing/__pycache__/properties.cpython-312.pyc,, +openpyxl/drawing/__pycache__/relation.cpython-312.pyc,, +openpyxl/drawing/__pycache__/spreadsheet_drawing.cpython-312.pyc,, +openpyxl/drawing/__pycache__/text.cpython-312.pyc,, +openpyxl/drawing/__pycache__/xdr.cpython-312.pyc,, +openpyxl/drawing/colors.py,sha256=d92d6LQv2xi4xVt0F6bEJz-kpe4ahghNsOIY0_cxgQI,15251 +openpyxl/drawing/connector.py,sha256=4be6kFwDmixqYX6ko22JE3cqJ9xUM7lRonSer1BDVgY,3863 +openpyxl/drawing/drawing.py,sha256=Wbv24TZbNaPngDR3adOj6jUBg-iyMYyfvgEPg-5IPu8,2339 +openpyxl/drawing/effect.py,sha256=vZ5r9k3JfyaAoBggFzN9wyvsEDnMnAmkQZsdVQN1-wo,9435 +openpyxl/drawing/fill.py,sha256=Z_kAY5bncgu1WkZNvgjiX5ucrYI6GLXyUi6H3_mne2k,13092 +openpyxl/drawing/geometry.py,sha256=0UM5hMHYy_R3C-lHt5x3NECDn7O1tfbKu5BweLwdLlg,17523 +openpyxl/drawing/graphic.py,sha256=013KhmTqp1PFKht9lRRA6SHjznxq9EL4u_ybA88OuCk,4811 +openpyxl/drawing/image.py,sha256=ROO0YJjzH9eqjPUKU5bMtt4bXnHFK9uofDa2__R3G2k,1455 +openpyxl/drawing/line.py,sha256=CRxV0NUpce4RfXPDllodcneoHk8vr2Ind8HaWnUv2HE,3904 +openpyxl/drawing/picture.py,sha256=tDYob2x4encQ9rUWOe29PqtiRSDEj746j-SvQ7rVV10,4205 +openpyxl/drawing/properties.py,sha256=TyLOF3ehp38XJvuupNZdsOqZ0HNXkVPBDYwU5O1GhBM,4948 +openpyxl/drawing/relation.py,sha256=InbM75ymWUjICXhjyCcYqp1FWcfCFp9q9vecYLptzk4,344 +openpyxl/drawing/spreadsheet_drawing.py,sha256=CUWSpIYWOHUEp-USOAGVNlLfXBQObcGdg_RZ_bggPYM,10721 +openpyxl/drawing/text.py,sha256=6_ShIu9FLG7MJvMLs_G_tTatTaBqxpaX5KMKxSfTY7Y,22421 +openpyxl/drawing/xdr.py,sha256=XE2yRzlCqoJBWg3TPRxelzZ4GmBV9dDFTtiJgJZku-U,626 +openpyxl/formatting/__init__.py,sha256=vpkL3EimMa-moJjcWk4l3bIWdJ3c7a8pKOfGlnPte9c,59 +openpyxl/formatting/__pycache__/__init__.cpython-312.pyc,, +openpyxl/formatting/__pycache__/formatting.cpython-312.pyc,, +openpyxl/formatting/__pycache__/rule.cpython-312.pyc,, +openpyxl/formatting/formatting.py,sha256=AdXlrhic4CPvyJ300oFJPJUH-2vS0VNOLiNudt3U26c,2701 +openpyxl/formatting/rule.py,sha256=96Fc5-hSByCrvkC1O0agEoZyL9G_AdeulrjRXnf_rZ8,9288 +openpyxl/formula/__init__.py,sha256=AgvEdunVryhzwecuFVO2EezdJT3h5gCXpw2j3f5VUWA,69 +openpyxl/formula/__pycache__/__init__.cpython-312.pyc,, +openpyxl/formula/__pycache__/tokenizer.cpython-312.pyc,, +openpyxl/formula/__pycache__/translate.cpython-312.pyc,, +openpyxl/formula/tokenizer.py,sha256=o1jDAOl79YiCWr-2LmSICyAbhm2hdb-37jriasmv4dc,15088 +openpyxl/formula/translate.py,sha256=Zs9adqfZTAuo8J_QNbqK3vjQDlSFhWc0vWc6TCMDYrI,6653 +openpyxl/packaging/__init__.py,sha256=KcNtO2zoYizOgG-iZzayZffSL1WeZR98i1Q8QYTRhfI,90 +openpyxl/packaging/__pycache__/__init__.cpython-312.pyc,, +openpyxl/packaging/__pycache__/core.cpython-312.pyc,, +openpyxl/packaging/__pycache__/custom.cpython-312.pyc,, +openpyxl/packaging/__pycache__/extended.cpython-312.pyc,, +openpyxl/packaging/__pycache__/interface.cpython-312.pyc,, +openpyxl/packaging/__pycache__/manifest.cpython-312.pyc,, +openpyxl/packaging/__pycache__/relationship.cpython-312.pyc,, +openpyxl/packaging/__pycache__/workbook.cpython-312.pyc,, +openpyxl/packaging/core.py,sha256=OSbSFGZrKYcZszcHe3LhQEyiAf2Wylwxm4_6N8WO-Yo,4061 +openpyxl/packaging/custom.py,sha256=uCEl7IwITFX2pOxiAITnvNbfsav80uHB0wXUFvjIRUQ,6738 +openpyxl/packaging/extended.py,sha256=JFksxDd67rA57n-vxg48tbeZh2g2LEOb0fgJLeqbTWM,4810 +openpyxl/packaging/interface.py,sha256=vlGVt4YvyUR4UX9Tr9xmkn1G8s_ynYVtAx4okJ6-g_8,920 +openpyxl/packaging/manifest.py,sha256=y5zoDQnhJ1aW_HPLItY_WE94fSLS4jxvfIqn_J2zJ6Q,5366 +openpyxl/packaging/relationship.py,sha256=jLhvFvDVZBRTZTXokRrrsEiLI9CmFlulhGzA_OYKM0Q,3974 +openpyxl/packaging/workbook.py,sha256=s4jl4gqqMkaUHmMAR52dc9ZoNTieuXcq1OG3cgNDYjw,6495 +openpyxl/pivot/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35 +openpyxl/pivot/__pycache__/__init__.cpython-312.pyc,, +openpyxl/pivot/__pycache__/cache.cpython-312.pyc,, +openpyxl/pivot/__pycache__/fields.cpython-312.pyc,, +openpyxl/pivot/__pycache__/record.cpython-312.pyc,, +openpyxl/pivot/__pycache__/table.cpython-312.pyc,, +openpyxl/pivot/cache.py,sha256=kKQMEcoYb9scl_CNNWfmNOTewD5S3hpBGwViMtDCyx0,27840 +openpyxl/pivot/fields.py,sha256=0CQLdTOBhYAa9gfEZb_bvkgCx8feASYp64dqFskDkqU,7057 +openpyxl/pivot/record.py,sha256=c45ft1YsPAVRneMVh_WvUQ1nZt9RJQ_josRuolKx3qE,2671 +openpyxl/pivot/table.py,sha256=riKBeb1aICXWipnhpSaSx9iqP-AkfcyOSm3Dfl407dA,40756 +openpyxl/reader/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35 +openpyxl/reader/__pycache__/__init__.cpython-312.pyc,, +openpyxl/reader/__pycache__/drawings.cpython-312.pyc,, +openpyxl/reader/__pycache__/excel.cpython-312.pyc,, +openpyxl/reader/__pycache__/strings.cpython-312.pyc,, +openpyxl/reader/__pycache__/workbook.cpython-312.pyc,, +openpyxl/reader/drawings.py,sha256=iZPok8Dc_mZMyRPk_EfDXDQvZdwfHwbYjvxfK2cXtag,2209 +openpyxl/reader/excel.py,sha256=kgStQtO1j0vV56GWaXxo3GA2EXuouGtnFrRVMocq8EY,12357 +openpyxl/reader/strings.py,sha256=oG2Mq6eBD0-ElFOxPdHTBUmshUxTNrK1sns1UJRaVis,1113 +openpyxl/reader/workbook.py,sha256=4w0LRV7qNNGHDnYd19zUgWnJOEX8tHjm3vlkxwllzv4,4352 +openpyxl/styles/__init__.py,sha256=2QNNdlz4CjhnkBQVNhZ-12Yz73_uHIinqRKWo_TjNwg,363 +openpyxl/styles/__pycache__/__init__.cpython-312.pyc,, +openpyxl/styles/__pycache__/alignment.cpython-312.pyc,, +openpyxl/styles/__pycache__/borders.cpython-312.pyc,, +openpyxl/styles/__pycache__/builtins.cpython-312.pyc,, +openpyxl/styles/__pycache__/cell_style.cpython-312.pyc,, +openpyxl/styles/__pycache__/colors.cpython-312.pyc,, +openpyxl/styles/__pycache__/differential.cpython-312.pyc,, +openpyxl/styles/__pycache__/fills.cpython-312.pyc,, +openpyxl/styles/__pycache__/fonts.cpython-312.pyc,, +openpyxl/styles/__pycache__/named_styles.cpython-312.pyc,, +openpyxl/styles/__pycache__/numbers.cpython-312.pyc,, +openpyxl/styles/__pycache__/protection.cpython-312.pyc,, +openpyxl/styles/__pycache__/proxy.cpython-312.pyc,, +openpyxl/styles/__pycache__/styleable.cpython-312.pyc,, +openpyxl/styles/__pycache__/stylesheet.cpython-312.pyc,, +openpyxl/styles/__pycache__/table.cpython-312.pyc,, +openpyxl/styles/alignment.py,sha256=wQOEtmYhPJFtnuBq0juMe5EsCp9DNSVS1ieBhlAnwWE,2198 +openpyxl/styles/borders.py,sha256=BLUTOyBbxWQzv8Kuh1u4sWfJiIPJc8QExb7nGwdSmXc,3302 +openpyxl/styles/builtins.py,sha256=cMtJverVSjdIdCckP6L-AlI0OLMRPgbQwaJWUkldA0U,31182 +openpyxl/styles/cell_style.py,sha256=8Ol5F6ktKeSqhDVF-10w5eIh7W-jkzijpPPHqqv1qDs,5414 +openpyxl/styles/colors.py,sha256=Ss3QqNS5YISVkJxlNfd4q_YSrFKdKjATWLDSu2rPMBc,4612 +openpyxl/styles/differential.py,sha256=dqEGny_ou1jC3tegBal1w_UbONyQEJXvGPURs8xWwfU,2267 +openpyxl/styles/fills.py,sha256=LmR4H00GzKDWyYjzDEayzKZN28S_muD65DvAFWlbaCI,6380 +openpyxl/styles/fonts.py,sha256=nkeiJUgKYnWaETvn51sOo9zQXJiOEJKHDTqvxt0JiBc,3516 +openpyxl/styles/named_styles.py,sha256=nfL1KPpd6b0Y0qBrGJQ15EUOebfeO1eZBQhPVpcZW-o,7254 +openpyxl/styles/numbers.py,sha256=6kK7mdBD-0xs7bjYDFNGsUAvoFvRu5wSMjOF9J5j-Go,5097 +openpyxl/styles/protection.py,sha256=BUHgARq7SjOVfW_ST53hKCUofVBEWXn3Lnn_c5n4i_I,394 +openpyxl/styles/proxy.py,sha256=ajsvzRp_MOeV_rZSEfVoti6-3tW8aowo5_Hjwp2AlfA,1432 +openpyxl/styles/styleable.py,sha256=Yl_-oPljEuFzg9tXKSSCuvWRL4L0HC5bHMFJVhex6Oc,4499 +openpyxl/styles/stylesheet.py,sha256=7kZpzyavLrOJcdZqZzl3WZTyM60CqWP8i_OQ0J_1xy0,8790 +openpyxl/styles/table.py,sha256=VexRqPPQmjRzWe1rVTOgyOQgvlCBuEYTif5MEV_0qsk,2801 +openpyxl/utils/__init__.py,sha256=wCMNXgIoA4aF4tpSuSzxm1k3SmJJGOEjtdbqdJZZG7I,324 +openpyxl/utils/__pycache__/__init__.cpython-312.pyc,, +openpyxl/utils/__pycache__/bound_dictionary.cpython-312.pyc,, +openpyxl/utils/__pycache__/cell.cpython-312.pyc,, +openpyxl/utils/__pycache__/dataframe.cpython-312.pyc,, +openpyxl/utils/__pycache__/datetime.cpython-312.pyc,, +openpyxl/utils/__pycache__/escape.cpython-312.pyc,, +openpyxl/utils/__pycache__/exceptions.cpython-312.pyc,, +openpyxl/utils/__pycache__/formulas.cpython-312.pyc,, +openpyxl/utils/__pycache__/indexed_list.cpython-312.pyc,, +openpyxl/utils/__pycache__/inference.cpython-312.pyc,, +openpyxl/utils/__pycache__/protection.cpython-312.pyc,, +openpyxl/utils/__pycache__/units.cpython-312.pyc,, +openpyxl/utils/bound_dictionary.py,sha256=zfzflQom1FqfEw8uexBqI8eExCeAWELzSk4TqqpD-w8,717 +openpyxl/utils/cell.py,sha256=P7og4c4JcSN__amIsubIMgSMlQ4SrAA5eZ0cjkoXlaQ,6967 +openpyxl/utils/dataframe.py,sha256=d3SPeb4p9YKFwlFTUWhdVUYYyMLNrd9atC6iSf2QB6w,2957 +openpyxl/utils/datetime.py,sha256=xQ8zHJFb-n4nlN6fA_fFZKHlHeNOB7El48p9-YOPvGE,4529 +openpyxl/utils/escape.py,sha256=4dgcSlSdPNk0vkJNHRUK9poEe8pn4sBIQ5Rjz-7H1Uk,790 +openpyxl/utils/exceptions.py,sha256=WT40gTyd9YUhg1MeqZNzHp9qJnL5eXzbCEb_VtHp3Kk,889 +openpyxl/utils/formulas.py,sha256=-I0zyvicBZMaAH1XzsmmEEzE4GB-NA605aArWVt9ik4,4248 +openpyxl/utils/indexed_list.py,sha256=hBsQP9gunTit7iKdMGw_tM3y5uIpXDjUx7jswbQF6Dc,1257 +openpyxl/utils/inference.py,sha256=dM1FBW_Rx_xE7P8vGo6WNhbBe-2eqpGuJj4eqdS7UjE,1583 +openpyxl/utils/protection.py,sha256=opm7GVM2ePQvpNzKT-W56u-0yP8liS9WJkxpzpG_tE0,830 +openpyxl/utils/units.py,sha256=eGpGrdzyoKlqLs99eALNC5c1gSLXRo4GdUNAqdB4wzg,2642 +openpyxl/workbook/__init__.py,sha256=yKMikN8VqoVZJcoZSVW3p9Smt88ibeqNq9NHhGBJqEM,68 +openpyxl/workbook/__pycache__/__init__.cpython-312.pyc,, +openpyxl/workbook/__pycache__/_writer.cpython-312.pyc,, +openpyxl/workbook/__pycache__/child.cpython-312.pyc,, +openpyxl/workbook/__pycache__/defined_name.cpython-312.pyc,, +openpyxl/workbook/__pycache__/external_reference.cpython-312.pyc,, +openpyxl/workbook/__pycache__/function_group.cpython-312.pyc,, +openpyxl/workbook/__pycache__/properties.cpython-312.pyc,, +openpyxl/workbook/__pycache__/protection.cpython-312.pyc,, +openpyxl/workbook/__pycache__/smart_tags.cpython-312.pyc,, +openpyxl/workbook/__pycache__/views.cpython-312.pyc,, +openpyxl/workbook/__pycache__/web.cpython-312.pyc,, +openpyxl/workbook/__pycache__/workbook.cpython-312.pyc,, +openpyxl/workbook/_writer.py,sha256=pB4s05erNEBJFT_w5LT-2DlxqXkZLOutXWVgewRLVds,6506 +openpyxl/workbook/child.py,sha256=r_5V9DNkGSYZhzi62P10ZnsO5iT518YopcTdmSvtAUc,4052 +openpyxl/workbook/defined_name.py,sha256=EAF1WvGYU4WG7dusDi29yBAr15BhkYtkF_GrFym1DDY,5394 +openpyxl/workbook/external_link/__init__.py,sha256=YOkLI226nyopB6moShzGIfBRckdQgPiFXjVZwXW-DpE,71 +openpyxl/workbook/external_link/__pycache__/__init__.cpython-312.pyc,, +openpyxl/workbook/external_link/__pycache__/external.cpython-312.pyc,, +openpyxl/workbook/external_link/external.py,sha256=LXXuej0-d0iRnwlJ-13S81kbuDxvhAWo3qfnxpsClvM,4509 +openpyxl/workbook/external_reference.py,sha256=9bKX9_QgNJxv7fEUd0G-ocXyZajMAsDzG11d0miguxY,348 +openpyxl/workbook/function_group.py,sha256=x5QfUpFdsjtbFbAJzZof7SrZ376nufNY92mpCcaSPiQ,803 +openpyxl/workbook/properties.py,sha256=vMUriu67iQU11xIos37ayv73gjq1kdHgI27ncJ3Vk24,5261 +openpyxl/workbook/protection.py,sha256=LhiyuoOchdrun9xMwq_pxGzbkysziThfKivk0dHHOLw,6008 +openpyxl/workbook/smart_tags.py,sha256=xHHXCrUPnHeRoM_eakrCOz-eCpct3Y7xKHShr9wGv7s,1181 +openpyxl/workbook/views.py,sha256=uwQqZCrRavAoBDLZIBtgz7riOEhEaHplybV4cX_TMgY,5214 +openpyxl/workbook/web.py,sha256=87B5mEZ6vfHTwywcGtcYL6u7D3RyJVDCJxV0nHZeS-w,2642 +openpyxl/workbook/workbook.py,sha256=oaErvSH1qUphUAPOZTCHj2UHyKeDqsj2DycKCDcgo7M,13232 +openpyxl/worksheet/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35 +openpyxl/worksheet/__pycache__/__init__.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/_read_only.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/_reader.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/_write_only.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/_writer.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/cell_range.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/cell_watch.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/controls.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/copier.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/custom.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/datavalidation.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/dimensions.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/drawing.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/errors.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/filters.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/formula.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/header_footer.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/hyperlink.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/merge.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/ole.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/page.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/pagebreak.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/picture.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/print_settings.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/properties.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/protection.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/related.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/scenario.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/smart_tag.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/table.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/views.cpython-312.pyc,, +openpyxl/worksheet/__pycache__/worksheet.cpython-312.pyc,, +openpyxl/worksheet/_read_only.py,sha256=6Kd4Q-73UoJDY66skRJy_ks-wCHNttlGhsDxvB99PuY,5709 +openpyxl/worksheet/_reader.py,sha256=vp_D7w4DiADMdyNrYpQglrCVvVLT9_DsSZikOd--n2c,16375 +openpyxl/worksheet/_write_only.py,sha256=yqW-DtBDDYTwGCBHRVIwkheSB7SSLO3xlw-RsXtPorE,4232 +openpyxl/worksheet/_writer.py,sha256=bDtw6BV5tdztARQEkQPprExRr8hZVFkj0DyolqxVu2k,10283 +openpyxl/worksheet/cell_range.py,sha256=YP8AUnqUFP5wOV_avMDFRSZ0Qi2p78RWFuwyyCua7m8,15013 +openpyxl/worksheet/cell_watch.py,sha256=LdxGcTmXbZ4sxm6inasFgZPld1ijdL5_ODSUvvz13DU,608 +openpyxl/worksheet/controls.py,sha256=FPLg4N94T-IL27NLg8Le_U4WYDT_6Aa25LDG_kiEDVA,2735 +openpyxl/worksheet/copier.py,sha256=0Di1qSks0g7Jtgmpc_M20O-KPCW81Yr2myC5j458nyU,2319 +openpyxl/worksheet/custom.py,sha256=CRlQ98GwqqKmEDkv8gPUCa0ApNM2Vz-BLs_-RMu3jLA,639 +openpyxl/worksheet/datavalidation.py,sha256=m-O7NOoTDr_bAfxB9xEeY5QttFiuPtzs-IFAlF0j4FE,6131 +openpyxl/worksheet/dimensions.py,sha256=HzM77FrYixiQDCugRT-C9ZpKq7GNFaGchxT73U4cisY,9102 +openpyxl/worksheet/drawing.py,sha256=2nfrLyTX0kAizPIINF12KwDW9mRnaq8hs-NrSBcWpmE,275 +openpyxl/worksheet/errors.py,sha256=KkFC4bnckvCp74XsVXA7JUCi4MIimEFu3uAddcQpjo0,2435 +openpyxl/worksheet/filters.py,sha256=8eUj2LuP8Qbz1R1gkK1c6W_UKS8-__6XlFMVkunIua0,13854 +openpyxl/worksheet/formula.py,sha256=5yuul6s1l-K_78KXHC6HrF_pLhxypoldh5jMg7zmlyY,1045 +openpyxl/worksheet/header_footer.py,sha256=91F6NUDUEwrhgeWrxG9XtDPyPD03XAtGU_ONBpkAfUc,7886 +openpyxl/worksheet/hyperlink.py,sha256=sXzPkkjl9BWNzCxwwEEaSS53J37jIXPmnnED-j8MIBo,1103 +openpyxl/worksheet/merge.py,sha256=gNOIH6EJ8wVcJpibAv4CMc7UpD7_DrGvgaCSvG2im5A,4125 +openpyxl/worksheet/ole.py,sha256=khVvqMt4GPc9Yr6whLDfkUo51euyLXfJe1p4zFee4no,3530 +openpyxl/worksheet/page.py,sha256=4jeSRcDE0S2RPzIAmA3Bh-uXRyq0hnbO5h5pJdGHbbQ,4901 +openpyxl/worksheet/pagebreak.py,sha256=XXFIMOY4VdPQCd86nGPghA6hOfLGK5G_KFuvjBNPRsw,1811 +openpyxl/worksheet/picture.py,sha256=72TctCxzk2JU8uFfjiEbTBufEe5eQxIieSPBRhU6m1Q,185 +openpyxl/worksheet/print_settings.py,sha256=k_g4fkrs9bfz-S-RIKIBGqzVgubufMdryWQ3ejXQoRI,5215 +openpyxl/worksheet/properties.py,sha256=9iXTOVC8B9C-2pp_iU5l0r5Fjf3Uzv0SIOUKRrZ2hw4,3087 +openpyxl/worksheet/protection.py,sha256=vj5M6WWC5xKiHeWS_tJqXxrlOJHJ7GpW2JdPw7r9jjE,3758 +openpyxl/worksheet/related.py,sha256=ZLDpgcrW6DWl8vvh2sSVB_r1JyG8bC8EicCBKjfssTs,335 +openpyxl/worksheet/scenario.py,sha256=VlJW4pi1OTy1cJ9m7ZxazIy8PSlo17BGpnUYixmNotQ,2401 +openpyxl/worksheet/smart_tag.py,sha256=nLbt04IqeJllk7TmNS1eTNdb7On5jMf3llfyy3otDSk,1608 +openpyxl/worksheet/table.py,sha256=gjt-jNP8dhVy8w5g-gMJpfHO-eV1EoxJy91yi-5HG64,11671 +openpyxl/worksheet/views.py,sha256=DkZcptwpbpklHILSlvK-a2LmJ7BWb1wbDcz2JVl7404,4974 +openpyxl/worksheet/worksheet.py,sha256=4JM5qjoJumtcqftHFkimtFEQrz7E2DBmXnkVo7R3WX8,27572 +openpyxl/writer/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35 +openpyxl/writer/__pycache__/__init__.cpython-312.pyc,, +openpyxl/writer/__pycache__/excel.cpython-312.pyc,, +openpyxl/writer/__pycache__/theme.cpython-312.pyc,, +openpyxl/writer/excel.py,sha256=6ioXn3hSHHIUnkW2wCyBgPA4CncO6FXL5yGSAzsqp6Y,9572 +openpyxl/writer/theme.py,sha256=5Hhq-0uP55sf_Zhw7i3M9azCfCjALQxoo7CV_9QPmTA,10320 +openpyxl/xml/__init__.py,sha256=A5Kj0GWk5XI-zJxbAL5vIppV_AgEHLRveGu8RK5c7U0,1016 +openpyxl/xml/__pycache__/__init__.cpython-312.pyc,, +openpyxl/xml/__pycache__/constants.cpython-312.pyc,, +openpyxl/xml/__pycache__/functions.cpython-312.pyc,, +openpyxl/xml/constants.py,sha256=HDNnhcj-WO9ayO4Mqwca3Au0ZTNfsDqWDtleREs_Wto,4833 +openpyxl/xml/functions.py,sha256=jBtfa8__w4gBlEPGHLGCAtJiaNKPyihTLsfmigyq2_Q,2025 diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/REQUESTED b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/WHEEL b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/WHEEL new file mode 100644 index 0000000..832be11 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.43.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/top_level.txt new file mode 100644 index 0000000..794cc3d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/top_level.txt @@ -0,0 +1 @@ +openpyxl diff --git a/venv/lib/python3.12/site-packages/openpyxl/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/__init__.py new file mode 100644 index 0000000..14e8432 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2010-2024 openpyxl + +DEBUG = False + +from openpyxl.compat.numbers import NUMPY +from openpyxl.xml import DEFUSEDXML, LXML +from openpyxl.workbook import Workbook +from openpyxl.reader.excel import load_workbook as open +from openpyxl.reader.excel import load_workbook +import openpyxl._constants as constants + +# Expose constants especially the version number + +__author__ = constants.__author__ +__author_email__ = constants.__author_email__ +__license__ = constants.__license__ +__maintainer_email__ = constants.__maintainer_email__ +__url__ = constants.__url__ +__version__ = constants.__version__ diff --git a/venv/lib/python3.12/site-packages/openpyxl/_constants.py b/venv/lib/python3.12/site-packages/openpyxl/_constants.py new file mode 100644 index 0000000..e7ff6b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/_constants.py @@ -0,0 +1,13 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Package metadata +""" + +__author__ = "See AUTHORS" +__author_email__ = "charlie.clark@clark-consulting.eu" +__license__ = "MIT" +__maintainer_email__ = "openpyxl-users@googlegroups.com" +__url__ = "https://openpyxl.readthedocs.io" +__version__ = "3.1.5" +__python__ = "3.8" diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py new file mode 100644 index 0000000..0c1ca3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + +from .cell import Cell, WriteOnlyCell, MergedCell +from .read_only import ReadOnlyCell diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py b/venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py new file mode 100644 index 0000000..4a27d68 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/_writer.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS +from openpyxl import LXML +from openpyxl.utils.datetime import to_excel, to_ISO8601 +from datetime import timedelta + +from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula +from openpyxl.cell.rich_text import CellRichText + +def _set_attributes(cell, styled=None): + """ + Set coordinate and datatype + """ + coordinate = cell.coordinate + attrs = {'r': coordinate} + if styled: + attrs['s'] = f"{cell.style_id}" + + if cell.data_type == "s": + attrs['t'] = "inlineStr" + elif cell.data_type != 'f': + attrs['t'] = cell.data_type + + value = cell._value + + if cell.data_type == "d": + if hasattr(value, "tzinfo") and value.tzinfo is not None: + raise TypeError("Excel does not support timezones in datetimes. " + "The tzinfo in the datetime/time object must be set to None.") + + if cell.parent.parent.iso_dates and not isinstance(value, timedelta): + value = to_ISO8601(value) + else: + attrs['t'] = "n" + value = to_excel(value, cell.parent.parent.epoch) + + if cell.hyperlink: + cell.parent._hyperlinks.append(cell.hyperlink) + + return value, attrs + + +def etree_write_cell(xf, worksheet, cell, styled=None): + + value, attributes = _set_attributes(cell, styled) + + el = Element("c", attributes) + if value is None or value == "": + xf.write(el) + return + + if cell.data_type == 'f': + attrib = {} + + if isinstance(value, ArrayFormula): + attrib = dict(value) + value = value.text + + elif isinstance(value, DataTableFormula): + attrib = dict(value) + value = None + + formula = SubElement(el, 'f', attrib) + if value is not None and not attrib.get('t') == "dataTable": + formula.text = value[1:] + value = None + + if cell.data_type == 's': + if isinstance(value, CellRichText): + el.append(value.to_tree()) + else: + inline_string = Element("is") + text = Element('t') + text.text = value + whitespace(text) + inline_string.append(text) + el.append(inline_string) + + else: + cell_content = SubElement(el, 'v') + if value is not None: + cell_content.text = safe_string(value) + + xf.write(el) + + +def lxml_write_cell(xf, worksheet, cell, styled=False): + value, attributes = _set_attributes(cell, styled) + + if value == '' or value is None: + with xf.element("c", attributes): + return + + with xf.element('c', attributes): + if cell.data_type == 'f': + attrib = {} + + if isinstance(value, ArrayFormula): + attrib = dict(value) + value = value.text + + elif isinstance(value, DataTableFormula): + attrib = dict(value) + value = None + + with xf.element('f', attrib): + if value is not None and not attrib.get('t') == "dataTable": + xf.write(value[1:]) + value = None + + if cell.data_type == 's': + if isinstance(value, CellRichText): + el = value.to_tree() + xf.write(el) + else: + with xf.element("is"): + if isinstance(value, str): + attrs = {} + if value != value.strip(): + attrs["{%s}space" % XML_NS] = "preserve" + el = Element("t", attrs) # lxml can't handle xml-ns + el.text = value + xf.write(el) + + else: + with xf.element("v"): + if value is not None: + xf.write(safe_string(value)) + + +if LXML: + write_cell = lxml_write_cell +else: + write_cell = etree_write_cell diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/cell.py b/venv/lib/python3.12/site-packages/openpyxl/cell/cell.py new file mode 100644 index 0000000..d29be28 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/cell.py @@ -0,0 +1,332 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Manage individual cells in a spreadsheet. + +The Cell class is required to know its value and type, display options, +and any other features of an Excel cell. Utilities for referencing +cells using Excel's 'A1' column/row nomenclature are also provided. + +""" + +__docformat__ = "restructuredtext en" + +# Python stdlib imports +from copy import copy +import datetime +import re + + +from openpyxl.compat import ( + NUMERIC_TYPES, +) + +from openpyxl.utils.exceptions import IllegalCharacterError + +from openpyxl.utils import get_column_letter +from openpyxl.styles import numbers, is_date_format +from openpyxl.styles.styleable import StyleableObject +from openpyxl.worksheet.hyperlink import Hyperlink +from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula +from openpyxl.cell.rich_text import CellRichText + +# constants + +TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta) +TIME_FORMATS = { + datetime.datetime:numbers.FORMAT_DATE_DATETIME, + datetime.date:numbers.FORMAT_DATE_YYYYMMDD2, + datetime.time:numbers.FORMAT_DATE_TIME6, + datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA, + } + +STRING_TYPES = (str, bytes, CellRichText) +KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None)) + +ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]') +ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', + '#N/A') + +TYPE_STRING = 's' +TYPE_FORMULA = 'f' +TYPE_NUMERIC = 'n' +TYPE_BOOL = 'b' +TYPE_NULL = 'n' +TYPE_INLINE = 'inlineStr' +TYPE_ERROR = 'e' +TYPE_FORMULA_CACHE_STRING = 'str' + +VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL, + TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING) + + +_TYPES = {int:'n', float:'n', str:'s', bool:'b'} + + +def get_type(t, value): + if isinstance(value, NUMERIC_TYPES): + dt = 'n' + elif isinstance(value, STRING_TYPES): + dt = 's' + elif isinstance(value, TIME_TYPES): + dt = 'd' + elif isinstance(value, (DataTableFormula, ArrayFormula)): + dt = 'f' + else: + return + _TYPES[t] = dt + return dt + + +def get_time_format(t): + value = TIME_FORMATS.get(t) + if value: + return value + for base in t.mro()[1:]: + value = TIME_FORMATS.get(base) + if value: + TIME_FORMATS[t] = value + return value + raise ValueError("Could not get time format for {0!r}".format(value)) + + +class Cell(StyleableObject): + """Describes cell associated properties. + + Properties of interest include style, type, value, and address. + + """ + __slots__ = ( + 'row', + 'column', + '_value', + 'data_type', + 'parent', + '_hyperlink', + '_comment', + ) + + def __init__(self, worksheet, row=None, column=None, value=None, style_array=None): + super().__init__(worksheet, style_array) + self.row = row + """Row number of this cell (1-based)""" + self.column = column + """Column number of this cell (1-based)""" + # _value is the stored value, while value is the displayed value + self._value = None + self._hyperlink = None + self.data_type = 'n' + if value is not None: + self.value = value + self._comment = None + + + @property + def coordinate(self): + """This cell's coordinate (ex. 'A5')""" + col = get_column_letter(self.column) + return f"{col}{self.row}" + + + @property + def col_idx(self): + """The numerical index of the column""" + return self.column + + + @property + def column_letter(self): + return get_column_letter(self.column) + + + @property + def encoding(self): + return self.parent.encoding + + @property + def base_date(self): + return self.parent.parent.epoch + + + def __repr__(self): + return "".format(self.parent.title, self.coordinate) + + def check_string(self, value): + """Check string coding, length, and line break character""" + if value is None: + return + # convert to str string + if not isinstance(value, str): + value = str(value, self.encoding) + value = str(value) + # string must never be longer than 32,767 characters + # truncate if necessary + value = value[:32767] + if next(ILLEGAL_CHARACTERS_RE.finditer(value), None): + raise IllegalCharacterError(f"{value} cannot be used in worksheets.") + return value + + def check_error(self, value): + """Tries to convert Error" else N/A""" + try: + return str(value) + except UnicodeDecodeError: + return u'#N/A' + + + def _bind_value(self, value): + """Given a value, infer the correct data type""" + + self.data_type = "n" + t = type(value) + try: + dt = _TYPES[t] + except KeyError: + dt = get_type(t, value) + + if dt is None and value is not None: + raise ValueError("Cannot convert {0!r} to Excel".format(value)) + + if dt: + self.data_type = dt + + if dt == 'd': + if not is_date_format(self.number_format): + self.number_format = get_time_format(t) + + elif dt == "s" and not isinstance(value, CellRichText): + value = self.check_string(value) + if len(value) > 1 and value.startswith("="): + self.data_type = 'f' + elif value in ERROR_CODES: + self.data_type = 'e' + + self._value = value + + + @property + def value(self): + """Get or set the value held in the cell. + + :type: depends on the value (string, float, int or + :class:`datetime.datetime`) + """ + return self._value + + @value.setter + def value(self, value): + """Set the value and infer type and display options.""" + self._bind_value(value) + + @property + def internal_value(self): + """Always returns the value for excel.""" + return self._value + + @property + def hyperlink(self): + """Return the hyperlink target or an empty string""" + return self._hyperlink + + + @hyperlink.setter + def hyperlink(self, val): + """Set value and display for hyperlinks in a cell. + Automatically sets the `value` of the cell with link text, + but you can modify it afterwards by setting the `value` + property, and the hyperlink will remain. + Hyperlink is removed if set to ``None``.""" + if val is None: + self._hyperlink = None + else: + if not isinstance(val, Hyperlink): + val = Hyperlink(ref="", target=val) + val.ref = self.coordinate + self._hyperlink = val + if self._value is None: + self.value = val.target or val.location + + + @property + def is_date(self): + """True if the value is formatted as a date + + :type: bool + """ + return self.data_type == 'd' or ( + self.data_type == 'n' and is_date_format(self.number_format) + ) + + + def offset(self, row=0, column=0): + """Returns a cell location relative to this cell. + + :param row: number of rows to offset + :type row: int + + :param column: number of columns to offset + :type column: int + + :rtype: :class:`openpyxl.cell.Cell` + """ + offset_column = self.col_idx + column + offset_row = self.row + row + return self.parent.cell(column=offset_column, row=offset_row) + + + @property + def comment(self): + """ Returns the comment associated with this cell + + :type: :class:`openpyxl.comments.Comment` + """ + return self._comment + + + @comment.setter + def comment(self, value): + """ + Assign a comment to a cell + """ + + if value is not None: + if value.parent: + value = copy(value) + value.bind(self) + elif value is None and self._comment: + self._comment.unbind() + self._comment = value + + +class MergedCell(StyleableObject): + + """ + Describes the properties of a cell in a merged cell and helps to + display the borders of the merged cell. + + The value of a MergedCell is always None. + """ + + __slots__ = ('row', 'column') + + _value = None + data_type = "n" + comment = None + hyperlink = None + + + def __init__(self, worksheet, row=None, column=None): + super().__init__(worksheet) + self.row = row + self.column = column + + + def __repr__(self): + return "".format(self.parent.title, self.coordinate) + + coordinate = Cell.coordinate + _comment = comment + value = _value + + +def WriteOnlyCell(ws=None, value=None): + return Cell(worksheet=ws, column=1, row=1, value=value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py b/venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py new file mode 100644 index 0000000..2eec09e --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/read_only.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.cell import Cell +from openpyxl.utils import get_column_letter +from openpyxl.utils.datetime import from_excel +from openpyxl.styles import is_date_format +from openpyxl.styles.numbers import BUILTIN_FORMATS, BUILTIN_FORMATS_MAX_SIZE + + +class ReadOnlyCell: + + __slots__ = ('parent', 'row', 'column', '_value', 'data_type', '_style_id') + + def __init__(self, sheet, row, column, value, data_type='n', style_id=0): + self.parent = sheet + self._value = None + self.row = row + self.column = column + self.data_type = data_type + self.value = value + self._style_id = style_id + + + def __eq__(self, other): + for a in self.__slots__: + if getattr(self, a) != getattr(other, a): + return + return True + + def __ne__(self, other): + return not self.__eq__(other) + + + def __repr__(self): + return "".format(self.parent.title, self.coordinate) + + + @property + def coordinate(self): + column = get_column_letter(self.column) + return "{1}{0}".format(self.row, column) + + + @property + def coordinate(self): + return Cell.coordinate.__get__(self) + + + @property + def column_letter(self): + return Cell.column_letter.__get__(self) + + + @property + def style_array(self): + return self.parent.parent._cell_styles[self._style_id] + + + @property + def has_style(self): + return self._style_id != 0 + + + @property + def number_format(self): + _id = self.style_array.numFmtId + if _id < BUILTIN_FORMATS_MAX_SIZE: + return BUILTIN_FORMATS.get(_id, "General") + else: + return self.parent.parent._number_formats[ + _id - BUILTIN_FORMATS_MAX_SIZE] + + @property + def font(self): + _id = self.style_array.fontId + return self.parent.parent._fonts[_id] + + @property + def fill(self): + _id = self.style_array.fillId + return self.parent.parent._fills[_id] + + @property + def border(self): + _id = self.style_array.borderId + return self.parent.parent._borders[_id] + + @property + def alignment(self): + _id = self.style_array.alignmentId + return self.parent.parent._alignments[_id] + + @property + def protection(self): + _id = self.style_array.protectionId + return self.parent.parent._protections[_id] + + + @property + def is_date(self): + return Cell.is_date.__get__(self) + + + @property + def internal_value(self): + return self._value + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + if self._value is not None: + raise AttributeError("Cell is read only") + self._value = value + + +class EmptyCell: + + __slots__ = () + + value = None + is_date = False + font = None + border = None + fill = None + number_format = None + alignment = None + data_type = 'n' + + + def __repr__(self): + return "" + +EMPTY_CELL = EmptyCell() diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py b/venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py new file mode 100644 index 0000000..373e263 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/rich_text.py @@ -0,0 +1,202 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +RichText definition +""" +from copy import copy +from openpyxl.compat import NUMERIC_TYPES +from openpyxl.cell.text import InlineFont, Text +from openpyxl.descriptors import ( + Strict, + String, + Typed +) + +from openpyxl.xml.functions import Element, whitespace + +class TextBlock(Strict): + """ Represents text string in a specific format + + This class is used as part of constructing a rich text strings. + """ + font = Typed(expected_type=InlineFont) + text = String() + + def __init__(self, font, text): + self.font = font + self.text = text + + + def __eq__(self, other): + return self.text == other.text and self.font == other.font + + + def __str__(self): + """Just retun the text""" + return self.text + + + def __repr__(self): + font = self.font != InlineFont() and self.font or "default" + return f"{self.__class__.__name__} text={self.text}, font={font}" + + + def to_tree(self): + el = Element("r") + el.append(self.font.to_tree(tagname="rPr")) + t = Element("t") + t.text = self.text + whitespace(t) + el.append(t) + return el + +# +# Rich Text class. +# This class behaves just like a list whose members are either simple strings, or TextBlock() instances. +# In addition, it can be initialized in several ways: +# t = CellRFichText([...]) # initialize with a list. +# t = CellRFichText((...)) # initialize with a tuple. +# t = CellRichText(node) # where node is an Element() from either lxml or xml.etree (has a 'tag' element) +class CellRichText(list): + """Represents a rich text string. + + Initialize with a list made of pure strings or :class:`TextBlock` elements + Can index object to access or modify individual rich text elements + it also supports the + and += operators between rich text strings + There are no user methods for this class + + operations which modify the string will generally call an optimization pass afterwards, + that merges text blocks with identical formats, consecutive pure text strings, + and remove empty strings and empty text blocks + """ + + def __init__(self, *args): + if len(args) == 1: + args = args[0] + if isinstance(args, (list, tuple)): + CellRichText._check_rich_text(args) + else: + CellRichText._check_element(args) + args = [args] + else: + CellRichText._check_rich_text(args) + super().__init__(args) + + + @classmethod + def _check_element(cls, value): + if not isinstance(value, (str, TextBlock, NUMERIC_TYPES)): + raise TypeError(f"Illegal CellRichText element {value}") + + + @classmethod + def _check_rich_text(cls, rich_text): + for t in rich_text: + CellRichText._check_element(t) + + @classmethod + def from_tree(cls, node): + text = Text.from_tree(node) + if text.t: + return (text.t.replace('x005F_', ''),) + s = [] + for r in text.r: + t = "" + if r.t: + t = r.t.replace('x005F_', '') + if r.rPr: + s.append(TextBlock(r.rPr, t)) + else: + s.append(t) + return cls(s) + + # Merge TextBlocks with identical formatting + # remove empty elements + def _opt(self): + last_t = None + l = CellRichText(tuple()) + for t in self: + if isinstance(t, str): + if not t: + continue + elif not t.text: + continue + if type(last_t) == type(t): + if isinstance(t, str): + last_t += t + continue + elif last_t.font == t.font: + last_t.text += t.text + continue + if last_t: + l.append(last_t) + last_t = t + if last_t: + # Add remaining TextBlock at end of rich text + l.append(last_t) + super().__setitem__(slice(None), l) + return self + + + def __iadd__(self, arg): + # copy used here to create new TextBlock() so we don't modify the right hand side in _opt() + CellRichText._check_rich_text(arg) + super().__iadd__([copy(e) for e in list(arg)]) + return self._opt() + + + def __add__(self, arg): + return CellRichText([copy(e) for e in list(self) + list(arg)])._opt() + + + def __setitem__(self, indx, val): + CellRichText._check_element(val) + super().__setitem__(indx, val) + self._opt() + + + def append(self, arg): + CellRichText._check_element(arg) + super().append(arg) + + + def extend(self, arg): + CellRichText._check_rich_text(arg) + super().extend(arg) + + + def __repr__(self): + return "CellRichText([{}])".format(', '.join((repr(s) for s in self))) + + + def __str__(self): + return ''.join([str(s) for s in self]) + + + def as_list(self): + """ + Returns a list of the strings contained. + The main reason for this is to make editing easier. + """ + return [str(s) for s in self] + + + def to_tree(self): + """ + Return the full XML representation + """ + container = Element("is") + for obj in self: + if isinstance(obj, TextBlock): + container.append(obj.to_tree()) + + else: + el = Element("r") + t = Element("t") + t.text = obj + whitespace(t) + el.append(t) + container.append(el) + + return container + diff --git a/venv/lib/python3.12/site-packages/openpyxl/cell/text.py b/venv/lib/python3.12/site-packages/openpyxl/cell/text.py new file mode 100644 index 0000000..54923dd --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/cell/text.py @@ -0,0 +1,184 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Richtext definition +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Integer, + Set, + NoneSet, + Bool, + String, + Sequence, +) +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedString, + NestedText, +) +from openpyxl.styles.fonts import Font + + +class PhoneticProperties(Serialisable): + + tagname = "phoneticPr" + + fontId = Integer() + type = NoneSet(values=(['halfwidthKatakana', 'fullwidthKatakana', + 'Hiragana', 'noConversion'])) + alignment = NoneSet(values=(['noControl', 'left', 'center', 'distributed'])) + + def __init__(self, + fontId=None, + type=None, + alignment=None, + ): + self.fontId = fontId + self.type = type + self.alignment = alignment + + +class PhoneticText(Serialisable): + + tagname = "rPh" + + sb = Integer() + eb = Integer() + t = NestedText(expected_type=str) + text = Alias('t') + + def __init__(self, + sb=None, + eb=None, + t=None, + ): + self.sb = sb + self.eb = eb + self.t = t + + +class InlineFont(Font): + + """ + Font for inline text because, yes what you need are different objects with the same elements but different constraints. + """ + + tagname = "RPrElt" + + rFont = NestedString(allow_none=True) + charset = Font.charset + family = Font.family + b =Font.b + i = Font.i + strike = Font.strike + outline = Font.outline + shadow = Font.shadow + condense = Font.condense + extend = Font.extend + color = Font.color + sz = Font.sz + u = Font.u + vertAlign = Font.vertAlign + scheme = Font.scheme + + __elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike', + 'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 'u', + 'vertAlign', 'scheme') + + def __init__(self, + rFont=None, + charset=None, + family=None, + b=None, + i=None, + strike=None, + outline=None, + shadow=None, + condense=None, + extend=None, + color=None, + sz=None, + u=None, + vertAlign=None, + scheme=None, + ): + self.rFont = rFont + self.charset = charset + self.family = family + self.b = b + self.i = i + self.strike = strike + self.outline = outline + self.shadow = shadow + self.condense = condense + self.extend = extend + self.color = color + self.sz = sz + self.u = u + self.vertAlign = vertAlign + self.scheme = scheme + + +class RichText(Serialisable): + + tagname = "RElt" + + rPr = Typed(expected_type=InlineFont, allow_none=True) + font = Alias("rPr") + t = NestedText(expected_type=str, allow_none=True) + text = Alias("t") + + __elements__ = ('rPr', 't') + + def __init__(self, + rPr=None, + t=None, + ): + self.rPr = rPr + self.t = t + + +class Text(Serialisable): + + tagname = "text" + + t = NestedText(allow_none=True, expected_type=str) + plain = Alias("t") + r = Sequence(expected_type=RichText, allow_none=True) + formatted = Alias("r") + rPh = Sequence(expected_type=PhoneticText, allow_none=True) + phonetic = Alias("rPh") + phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True) + PhoneticProperties = Alias("phoneticPr") + + __elements__ = ('t', 'r', 'rPh', 'phoneticPr') + + def __init__(self, + t=None, + r=(), + rPh=(), + phoneticPr=None, + ): + self.t = t + self.r = r + self.rPh = rPh + self.phoneticPr = phoneticPr + + + @property + def content(self): + """ + Text stripped of all formatting + """ + snippets = [] + if self.plain is not None: + snippets.append(self.plain) + for block in self.formatted: + if block.t is not None: + snippets.append(block.t) + return u"".join(snippets) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py b/venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py new file mode 100644 index 0000000..1651a99 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py @@ -0,0 +1,105 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import Typed, Alias +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedMinMax, +) +from openpyxl.descriptors.excel import ExtensionList +from .marker import PictureOptions +from .shapes import GraphicalProperties + + +class View3D(Serialisable): + + tagname = "view3D" + + rotX = NestedMinMax(min=-90, max=90, allow_none=True) + x_rotation = Alias('rotX') + hPercent = NestedMinMax(min=5, max=500, allow_none=True) + height_percent = Alias('hPercent') + rotY = NestedInteger(min=-90, max=90, allow_none=True) + y_rotation = Alias('rotY') + depthPercent = NestedInteger(allow_none=True) + rAngAx = NestedBool(allow_none=True) + right_angle_axes = Alias('rAngAx') + perspective = NestedInteger(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx', + 'perspective',) + + def __init__(self, + rotX=15, + hPercent=None, + rotY=20, + depthPercent=None, + rAngAx=True, + perspective=None, + extLst=None, + ): + self.rotX = rotX + self.hPercent = hPercent + self.rotY = rotY + self.depthPercent = depthPercent + self.rAngAx = rAngAx + self.perspective = perspective + + +class Surface(Serialisable): + + tagname = "surface" + + thickness = NestedInteger(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('thickness', 'spPr', 'pictureOptions',) + + def __init__(self, + thickness=None, + spPr=None, + pictureOptions=None, + extLst=None, + ): + self.thickness = thickness + self.spPr = spPr + self.pictureOptions = pictureOptions + + +class _3DBase(Serialisable): + + """ + Base class for 3D charts + """ + + tagname = "ChartBase" + + view3D = Typed(expected_type=View3D, allow_none=True) + floor = Typed(expected_type=Surface, allow_none=True) + sideWall = Typed(expected_type=Surface, allow_none=True) + backWall = Typed(expected_type=Surface, allow_none=True) + + def __init__(self, + view3D=None, + floor=None, + sideWall=None, + backWall=None, + ): + if view3D is None: + view3D = View3D() + self.view3D = view3D + if floor is None: + floor = Surface() + self.floor = floor + if sideWall is None: + sideWall = Surface() + self.sideWall = sideWall + if backWall is None: + backWall = Surface() + self.backWall = backWall + super(_3DBase, self).__init__() diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py new file mode 100644 index 0000000..ecc4d8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2010-2024 openpyxl + +from .area_chart import AreaChart, AreaChart3D +from .bar_chart import BarChart, BarChart3D +from .bubble_chart import BubbleChart +from .line_chart import LineChart, LineChart3D +from .pie_chart import ( + PieChart, + PieChart3D, + DoughnutChart, + ProjectedPieChart +) +from .radar_chart import RadarChart +from .scatter_chart import ScatterChart +from .stock_chart import StockChart +from .surface_chart import SurfaceChart, SurfaceChart3D + +from .series_factory import SeriesFactory as Series +from .reference import Reference diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py new file mode 100644 index 0000000..6a61354 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py @@ -0,0 +1,199 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import OrderedDict +from operator import attrgetter + +from openpyxl.descriptors import ( + Typed, + Integer, + Alias, + MinMax, + Bool, + Set, +) +from openpyxl.descriptors.sequence import ValueSequence +from openpyxl.descriptors.serialisable import Serialisable + +from ._3d import _3DBase +from .data_source import AxDataSource, NumRef +from .layout import Layout +from .legend import Legend +from .reference import Reference +from .series_factory import SeriesFactory +from .series import attribute_mapping +from .shapes import GraphicalProperties +from .title import TitleDescriptor + +class AxId(Serialisable): + + val = Integer() + + def __init__(self, val): + self.val = val + + +def PlotArea(): + from .chartspace import PlotArea + return PlotArea() + + +class ChartBase(Serialisable): + + """ + Base class for all charts + """ + + legend = Typed(expected_type=Legend, allow_none=True) + layout = Typed(expected_type=Layout, allow_none=True) + roundedCorners = Bool(allow_none=True) + axId = ValueSequence(expected_type=int) + visible_cells_only = Bool(allow_none=True) + display_blanks = Set(values=['span', 'gap', 'zero']) + graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) + + _series_type = "" + ser = () + series = Alias('ser') + title = TitleDescriptor() + anchor = "E15" # default anchor position + width = 15 # in cm, approx 5 rows + height = 7.5 # in cm, approx 14 rows + _id = 1 + _path = "/xl/charts/chart{0}.xml" + style = MinMax(allow_none=True, min=1, max=48) + mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) # mapped to chartspace + + __elements__ = () + + + def __init__(self, axId=(), **kw): + self._charts = [self] + self.title = None + self.layout = None + self.roundedCorners = None + self.legend = Legend() + self.graphical_properties = None + self.style = None + self.plot_area = PlotArea() + self.axId = axId + self.display_blanks = 'gap' + self.pivotSource = None + self.pivotFormats = () + self.visible_cells_only = True + self.idx_base = 0 + self.graphical_properties = None + super().__init__() + + + def __hash__(self): + """ + Just need to check for identity + """ + return id(self) + + def __iadd__(self, other): + """ + Combine the chart with another one + """ + if not isinstance(other, ChartBase): + raise TypeError("Only other charts can be added") + self._charts.append(other) + return self + + + def to_tree(self, namespace=None, tagname=None, idx=None): + self.axId = [id for id in self._axes] + if self.ser is not None: + for s in self.ser: + s.__elements__ = attribute_mapping[self._series_type] + return super().to_tree(tagname, idx) + + + def _reindex(self): + """ + Normalise and rebase series: sort by order and then rebase order + + """ + # sort data series in order and rebase + ds = sorted(self.series, key=attrgetter("order")) + for idx, s in enumerate(ds): + s.order = idx + self.series = ds + + + def _write(self): + from .chartspace import ChartSpace, ChartContainer + self.plot_area.layout = self.layout + + idx_base = self.idx_base + for chart in self._charts: + if chart not in self.plot_area._charts: + chart.idx_base = idx_base + idx_base += len(chart.series) + self.plot_area._charts = self._charts + + container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title) + if isinstance(chart, _3DBase): + container.view3D = chart.view3D + container.floor = chart.floor + container.sideWall = chart.sideWall + container.backWall = chart.backWall + container.plotVisOnly = self.visible_cells_only + container.dispBlanksAs = self.display_blanks + container.pivotFmts = self.pivotFormats + cs = ChartSpace(chart=container) + cs.style = self.style + cs.roundedCorners = self.roundedCorners + cs.pivotSource = self.pivotSource + cs.spPr = self.graphical_properties + return cs.to_tree() + + + @property + def _axes(self): + x = getattr(self, "x_axis", None) + y = getattr(self, "y_axis", None) + z = getattr(self, "z_axis", None) + return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis]) + + + def set_categories(self, labels): + """ + Set the categories / x-axis values + """ + if not isinstance(labels, Reference): + labels = Reference(range_string=labels) + for s in self.ser: + s.cat = AxDataSource(numRef=NumRef(f=labels)) + + + def add_data(self, data, from_rows=False, titles_from_data=False): + """ + Add a range of data in a single pass. + The default is to treat each column as a data series. + """ + if not isinstance(data, Reference): + data = Reference(range_string=data) + + if from_rows: + values = data.rows + + else: + values = data.cols + + for ref in values: + series = SeriesFactory(ref, title_from_data=titles_from_data) + self.series.append(series) + + + def append(self, value): + """Append a data series to the chart""" + l = self.series[:] + l.append(value) + self.series = l + + + @property + def path(self): + return self._path.format(self._id) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py new file mode 100644 index 0000000..d3d9808 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py @@ -0,0 +1,106 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Set, + Bool, + Integer, + Sequence, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedMinMax, + NestedSet, + NestedBool, +) + +from ._chart import ChartBase +from .descriptors import NestedGapAmount +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines +from .label import DataLabelList +from .series import Series + + +class _AreaChartBase(ChartBase): + + grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked'])) + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + dropLines = Typed(expected_type=ChartLines, allow_none=True) + + _series_type = "area" + + __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines') + + def __init__(self, + grouping="standard", + varyColors=None, + ser=(), + dLbls=None, + dropLines=None, + ): + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + super().__init__() + + +class AreaChart(_AreaChartBase): + + tagname = "areaChart" + + grouping = _AreaChartBase.grouping + varyColors = _AreaChartBase.varyColors + ser = _AreaChartBase.ser + dLbls = _AreaChartBase.dLbls + dropLines = _AreaChartBase.dropLines + + # chart properties actually used by containing classes + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _AreaChartBase.__elements__ + ('axId',) + + def __init__(self, + axId=None, + extLst=None, + **kw + ): + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) + + +class AreaChart3D(AreaChart): + + tagname = "area3DChart" + + grouping = _AreaChartBase.grouping + varyColors = _AreaChartBase.varyColors + ser = _AreaChartBase.ser + dLbls = _AreaChartBase.dLbls + dropLines = _AreaChartBase.dropLines + + gapDepth = NestedGapAmount() + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis, allow_none=True) + + __elements__ = AreaChart.__elements__ + ('gapDepth', ) + + def __init__(self, gapDepth=None, **kw): + self.gapDepth = gapDepth + super(AreaChart3D, self).__init__(**kw) + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/axis.py b/venv/lib/python3.12/site-packages/openpyxl/chart/axis.py new file mode 100644 index 0000000..7e99416 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/axis.py @@ -0,0 +1,401 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + NoneSet, + Bool, + Integer, + MinMax, + NoneSet, + Set, + String, + Alias, +) + +from openpyxl.descriptors.excel import ( + ExtensionList, + Percentage, + _explicit_none, +) +from openpyxl.descriptors.nested import ( + NestedValue, + NestedSet, + NestedBool, + NestedNoneSet, + NestedFloat, + NestedInteger, + NestedMinMax, +) +from openpyxl.xml.constants import CHART_NS + +from .descriptors import NumberFormatDescriptor +from .layout import Layout +from .text import Text, RichText +from .shapes import GraphicalProperties +from .title import Title, TitleDescriptor + + +class ChartLines(Serialisable): + + tagname = "chartLines" + + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + + def __init__(self, spPr=None): + self.spPr = spPr + + +class Scaling(Serialisable): + + tagname = "scaling" + + logBase = NestedFloat(allow_none=True) + orientation = NestedSet(values=(['maxMin', 'minMax'])) + max = NestedFloat(allow_none=True) + min = NestedFloat(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('logBase', 'orientation', 'max', 'min',) + + def __init__(self, + logBase=None, + orientation="minMax", + max=None, + min=None, + extLst=None, + ): + self.logBase = logBase + self.orientation = orientation + self.max = max + self.min = min + + +class _BaseAxis(Serialisable): + + axId = NestedInteger(expected_type=int) + scaling = Typed(expected_type=Scaling) + delete = NestedBool(allow_none=True) + axPos = NestedSet(values=(['b', 'l', 'r', 't'])) + majorGridlines = Typed(expected_type=ChartLines, allow_none=True) + minorGridlines = Typed(expected_type=ChartLines, allow_none=True) + title = TitleDescriptor() + numFmt = NumberFormatDescriptor() + number_format = Alias("numFmt") + majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none) + minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none) + tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo'])) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + crossAx = NestedInteger(expected_type=int) # references other axis + crosses = NestedNoneSet(values=(['autoZero', 'max', 'min'])) + crossesAt = NestedFloat(allow_none=True) + + # crosses & crossesAt are mutually exclusive + + __elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines', + 'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark', + 'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt') + + def __init__(self, + axId=None, + scaling=None, + delete=None, + axPos='l', + majorGridlines=None, + minorGridlines=None, + title=None, + numFmt=None, + majorTickMark=None, + minorTickMark=None, + tickLblPos=None, + spPr=None, + txPr= None, + crossAx=None, + crosses=None, + crossesAt=None, + ): + self.axId = axId + if scaling is None: + scaling = Scaling() + self.scaling = scaling + self.delete = delete + self.axPos = axPos + self.majorGridlines = majorGridlines + self.minorGridlines = minorGridlines + self.title = title + self.numFmt = numFmt + self.majorTickMark = majorTickMark + self.minorTickMark = minorTickMark + self.tickLblPos = tickLblPos + self.spPr = spPr + self.txPr = txPr + self.crossAx = crossAx + self.crosses = crosses + self.crossesAt = crossesAt + + +class DisplayUnitsLabel(Serialisable): + + tagname = "dispUnitsLbl" + + layout = Typed(expected_type=Layout, allow_none=True) + tx = Typed(expected_type=Text, allow_none=True) + text = Alias("tx") + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textPropertes = Alias("txPr") + + __elements__ = ('layout', 'tx', 'spPr', 'txPr') + + def __init__(self, + layout=None, + tx=None, + spPr=None, + txPr=None, + ): + self.layout = layout + self.tx = tx + self.spPr = spPr + self.txPr = txPr + + +class DisplayUnitsLabelList(Serialisable): + + tagname = "dispUnits" + + custUnit = NestedFloat(allow_none=True) + builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands', + 'tenThousands', 'hundredThousands', 'millions', 'tenMillions', + 'hundredMillions', 'billions', 'trillions'])) + dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',) + + def __init__(self, + custUnit=None, + builtInUnit=None, + dispUnitsLbl=None, + extLst=None, + ): + self.custUnit = custUnit + self.builtInUnit = builtInUnit + self.dispUnitsLbl = dispUnitsLbl + + +class NumericAxis(_BaseAxis): + + tagname = "valAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + crossBetween = NestedNoneSet(values=(['between', 'midCat'])) + majorUnit = NestedFloat(allow_none=True) + minorUnit = NestedFloat(allow_none=True) + dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit', + 'minorUnit', 'dispUnits',) + + + def __init__(self, + crossBetween=None, + majorUnit=None, + minorUnit=None, + dispUnits=None, + extLst=None, + **kw + ): + self.crossBetween = crossBetween + self.majorUnit = majorUnit + self.minorUnit = minorUnit + self.dispUnits = dispUnits + kw.setdefault('majorGridlines', ChartLines()) + kw.setdefault('axId', 100) + kw.setdefault('crossAx', 10) + super().__init__(**kw) + + + @classmethod + def from_tree(cls, node): + """ + Special case value axes with no gridlines + """ + self = super().from_tree(node) + gridlines = node.find("{%s}majorGridlines" % CHART_NS) + if gridlines is None: + self.majorGridlines = None + return self + + + +class TextAxis(_BaseAxis): + + tagname = "catAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + auto = NestedBool(allow_none=True) + lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r'])) + lblOffset = NestedMinMax(min=0, max=1000) + tickLblSkip = NestedInteger(allow_none=True) + tickMarkSkip = NestedInteger(allow_none=True) + noMultiLvlLbl = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset', + 'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl') + + def __init__(self, + auto=None, + lblAlgn=None, + lblOffset=100, + tickLblSkip=None, + tickMarkSkip=None, + noMultiLvlLbl=None, + extLst=None, + **kw + ): + self.auto = auto + self.lblAlgn = lblAlgn + self.lblOffset = lblOffset + self.tickLblSkip = tickLblSkip + self.tickMarkSkip = tickMarkSkip + self.noMultiLvlLbl = noMultiLvlLbl + kw.setdefault('axId', 10) + kw.setdefault('crossAx', 100) + super().__init__(**kw) + + +class DateAxis(TextAxis): + + tagname = "dateAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + auto = NestedBool(allow_none=True) + lblOffset = NestedInteger(allow_none=True) + baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + majorUnit = NestedFloat(allow_none=True) + majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + minorUnit = NestedFloat(allow_none=True) + minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years'])) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset', + 'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit', + 'minorTimeUnit') + + def __init__(self, + auto=None, + lblOffset=None, + baseTimeUnit=None, + majorUnit=None, + majorTimeUnit=None, + minorUnit=None, + minorTimeUnit=None, + extLst=None, + **kw + ): + self.auto = auto + self.lblOffset = lblOffset + self.baseTimeUnit = baseTimeUnit + self.majorUnit = majorUnit + self.majorTimeUnit = majorTimeUnit + self.minorUnit = minorUnit + self.minorTimeUnit = minorTimeUnit + kw.setdefault('axId', 500) + kw.setdefault('lblOffset', lblOffset) + super().__init__(**kw) + + +class SeriesAxis(_BaseAxis): + + tagname = "serAx" + + axId = _BaseAxis.axId + scaling = _BaseAxis.scaling + delete = _BaseAxis.delete + axPos = _BaseAxis.axPos + majorGridlines = _BaseAxis.majorGridlines + minorGridlines = _BaseAxis.minorGridlines + title = _BaseAxis.title + numFmt = _BaseAxis.numFmt + majorTickMark = _BaseAxis.majorTickMark + minorTickMark = _BaseAxis.minorTickMark + tickLblPos = _BaseAxis.tickLblPos + spPr = _BaseAxis.spPr + txPr = _BaseAxis.txPr + crossAx = _BaseAxis.crossAx + crosses = _BaseAxis.crosses + crossesAt = _BaseAxis.crossesAt + + tickLblSkip = NestedInteger(allow_none=True) + tickMarkSkip = NestedInteger(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip') + + def __init__(self, + tickLblSkip=None, + tickMarkSkip=None, + extLst=None, + **kw + ): + self.tickLblSkip = tickLblSkip + self.tickMarkSkip = tickMarkSkip + kw.setdefault('axId', 1000) + kw.setdefault('crossAx', 10) + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py new file mode 100644 index 0000000..fa08e07 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + Sequence, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedBool, + NestedInteger, + NestedMinMax, +) + +from .descriptors import ( + NestedGapAmount, + NestedOverlap, +) +from ._chart import ChartBase +from ._3d import _3DBase +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines +from .shapes import GraphicalProperties +from .series import Series +from .legend import Legend +from .label import DataLabelList + + +class _BarChartBase(ChartBase): + + barDir = NestedSet(values=(['bar', 'col'])) + type = Alias("barDir") + grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard', + 'stacked'])) + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + + __elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls') + + _series_type = "bar" + + def __init__(self, + barDir="col", + grouping="clustered", + varyColors=None, + ser=(), + dLbls=None, + **kw + ): + self.barDir = barDir + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + super().__init__(**kw) + + +class BarChart(_BarChartBase): + + tagname = "barChart" + + barDir = _BarChartBase.barDir + grouping = _BarChartBase.grouping + varyColors = _BarChartBase.varyColors + ser = _BarChartBase.ser + dLbls = _BarChartBase.dLbls + + gapWidth = NestedGapAmount() + overlap = NestedOverlap() + serLines = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + # chart properties actually used by containing classes + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId') + + def __init__(self, + gapWidth=150, + overlap=None, + serLines=None, + extLst=None, + **kw + ): + self.gapWidth = gapWidth + self.overlap = overlap + self.serLines = serLines + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.legend = Legend() + super().__init__(**kw) + + +class BarChart3D(_BarChartBase, _3DBase): + + tagname = "bar3DChart" + + barDir = _BarChartBase.barDir + grouping = _BarChartBase.grouping + varyColors = _BarChartBase.varyColors + ser = _BarChartBase.ser + dLbls = _BarChartBase.dLbls + + view3D = _3DBase.view3D + floor = _3DBase.floor + sideWall = _3DBase.sideWall + backWall = _3DBase.backWall + + gapWidth = NestedGapAmount() + gapDepth = NestedGapAmount() + shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax'])) + serLines = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis, allow_none=True) + + __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId') + + def __init__(self, + gapWidth=150, + gapDepth=150, + shape=None, + serLines=None, + extLst=None, + **kw + ): + self.gapWidth = gapWidth + self.gapDepth = gapDepth + self.shape = shape + self.serLines = serLines + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + + super(BarChart3D, self).__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py new file mode 100644 index 0000000..3fca043 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py @@ -0,0 +1,67 @@ +#Autogenerated schema +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Set, + MinMax, + Bool, + Integer, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedMinMax, + NestedBool, +) + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis +from .series import XYSeries +from .label import DataLabelList + + +class BubbleChart(ChartBase): + + tagname = "bubbleChart" + + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=XYSeries, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + bubble3D = NestedBool(allow_none=True) + bubbleScale = NestedMinMax(min=0, max=300, allow_none=True) + showNegBubbles = NestedBool(allow_none=True) + sizeRepresents = NestedNoneSet(values=(['area', 'w'])) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=NumericAxis) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "bubble" + + __elements__ = ('varyColors', 'ser', 'dLbls', 'bubble3D', 'bubbleScale', + 'showNegBubbles', 'sizeRepresents', 'axId') + + def __init__(self, + varyColors=None, + ser=(), + dLbls=None, + bubble3D=None, + bubbleScale=None, + showNegBubbles=None, + sizeRepresents=None, + extLst=None, + **kw + ): + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.bubble3D = bubble3D + self.bubbleScale = bubbleScale + self.showNegBubbles = showNegBubbles + self.sizeRepresents = sizeRepresents + self.x_axis = NumericAxis(axId=10, crossAx=20) + self.y_axis = NumericAxis(axId=20, crossAx=10) + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py b/venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py new file mode 100644 index 0000000..cba213c --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py @@ -0,0 +1,195 @@ + +# Copyright (c) 2010-2024 openpyxl + +""" +Enclosing chart object. The various chart types are actually child objects. +Will probably need to call this indirectly +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Alias, +) +from openpyxl.descriptors.excel import ( + ExtensionList, + Relation +) +from openpyxl.descriptors.nested import ( + NestedBool, + NestedNoneSet, + NestedString, + NestedMinMax, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.xml.constants import CHART_NS + +from openpyxl.drawing.colors import ColorMapping +from .text import RichText +from .shapes import GraphicalProperties +from .legend import Legend +from ._3d import _3DBase +from .plotarea import PlotArea +from .title import Title +from .pivot import ( + PivotFormat, + PivotSource, +) +from .print_settings import PrintSettings + + +class ChartContainer(Serialisable): + + tagname = "chart" + + title = Typed(expected_type=Title, allow_none=True) + autoTitleDeleted = NestedBool(allow_none=True) + pivotFmts = NestedSequence(expected_type=PivotFormat) + view3D = _3DBase.view3D + floor = _3DBase.floor + sideWall = _3DBase.sideWall + backWall = _3DBase.backWall + plotArea = Typed(expected_type=PlotArea, ) + legend = Typed(expected_type=Legend, allow_none=True) + plotVisOnly = NestedBool() + dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero'])) + showDLblsOverMax = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D', + 'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly', + 'dispBlanksAs', 'showDLblsOverMax') + + def __init__(self, + title=None, + autoTitleDeleted=None, + pivotFmts=(), + view3D=None, + floor=None, + sideWall=None, + backWall=None, + plotArea=None, + legend=None, + plotVisOnly=True, + dispBlanksAs="gap", + showDLblsOverMax=None, + extLst=None, + ): + self.title = title + self.autoTitleDeleted = autoTitleDeleted + self.pivotFmts = pivotFmts + self.view3D = view3D + self.floor = floor + self.sideWall = sideWall + self.backWall = backWall + if plotArea is None: + plotArea = PlotArea() + self.plotArea = plotArea + self.legend = legend + self.plotVisOnly = plotVisOnly + self.dispBlanksAs = dispBlanksAs + self.showDLblsOverMax = showDLblsOverMax + + +class Protection(Serialisable): + + tagname = "protection" + + chartObject = NestedBool(allow_none=True) + data = NestedBool(allow_none=True) + formatting = NestedBool(allow_none=True) + selection = NestedBool(allow_none=True) + userInterface = NestedBool(allow_none=True) + + __elements__ = ("chartObject", "data", "formatting", "selection", "userInterface") + + def __init__(self, + chartObject=None, + data=None, + formatting=None, + selection=None, + userInterface=None, + ): + self.chartObject = chartObject + self.data = data + self.formatting = formatting + self.selection = selection + self.userInterface = userInterface + + +class ExternalData(Serialisable): + + tagname = "externalData" + + autoUpdate = NestedBool(allow_none=True) + id = String() # Needs namespace + + def __init__(self, + autoUpdate=None, + id=None + ): + self.autoUpdate = autoUpdate + self.id = id + + +class ChartSpace(Serialisable): + + tagname = "chartSpace" + + date1904 = NestedBool(allow_none=True) + lang = NestedString(allow_none=True) + roundedCorners = NestedBool(allow_none=True) + style = NestedMinMax(allow_none=True, min=1, max=48) + clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True) + pivotSource = Typed(expected_type=PivotSource, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + chart = Typed(expected_type=ChartContainer) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphical_properties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias("txPr") + externalData = Typed(expected_type=ExternalData, allow_none=True) + printSettings = Typed(expected_type=PrintSettings, allow_none=True) + userShapes = Relation() + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('date1904', 'lang', 'roundedCorners', 'style', + 'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr', + 'externalData', 'printSettings', 'userShapes') + + def __init__(self, + date1904=None, + lang=None, + roundedCorners=None, + style=None, + clrMapOvr=None, + pivotSource=None, + protection=None, + chart=None, + spPr=None, + txPr=None, + externalData=None, + printSettings=None, + userShapes=None, + extLst=None, + ): + self.date1904 = date1904 + self.lang = lang + self.roundedCorners = roundedCorners + self.style = style + self.clrMapOvr = clrMapOvr + self.pivotSource = pivotSource + self.protection = protection + self.chart = chart + self.spPr = spPr + self.txPr = txPr + self.externalData = externalData + self.printSettings = printSettings + self.userShapes = userShapes + + + def to_tree(self, tagname=None, idx=None, namespace=None): + tree = super().to_tree() + tree.set("xmlns", CHART_NS) + return tree diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py b/venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py new file mode 100644 index 0000000..c38eafb --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py @@ -0,0 +1,246 @@ +""" +Collection of utility primitives for charts. +""" + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Bool, + Typed, + Alias, + String, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedString, + NestedText, + NestedInteger, +) + + +class NumFmt(Serialisable): + + formatCode = String() + sourceLinked = Bool() + + def __init__(self, + formatCode=None, + sourceLinked=False + ): + self.formatCode = formatCode + self.sourceLinked = sourceLinked + + +class NumberValueDescriptor(NestedText): + """ + Data should be numerical but isn't always :-/ + """ + + allow_none = True + + def __set__(self, instance, value): + if value == "#N/A": + self.expected_type = str + else: + self.expected_type = float + super().__set__(instance, value) + + +class NumVal(Serialisable): + + idx = Integer() + formatCode = NestedText(allow_none=True, expected_type=str) + v = NumberValueDescriptor() + + def __init__(self, + idx=None, + formatCode=None, + v=None, + ): + self.idx = idx + self.formatCode = formatCode + self.v = v + + +class NumData(Serialisable): + + formatCode = NestedText(expected_type=str, allow_none=True) + ptCount = NestedInteger(allow_none=True) + pt = Sequence(expected_type=NumVal) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('formatCode', 'ptCount', 'pt') + + def __init__(self, + formatCode=None, + ptCount=None, + pt=(), + extLst=None, + ): + self.formatCode = formatCode + self.ptCount = ptCount + self.pt = pt + + +class NumRef(Serialisable): + + f = NestedText(expected_type=str) + ref = Alias('f') + numCache = Typed(expected_type=NumData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('f', 'numCache') + + def __init__(self, + f=None, + numCache=None, + extLst=None, + ): + self.f = f + self.numCache = numCache + + +class StrVal(Serialisable): + + tagname = "strVal" + + idx = Integer() + v = NestedText(expected_type=str) + + def __init__(self, + idx=0, + v=None, + ): + self.idx = idx + self.v = v + + +class StrData(Serialisable): + + tagname = "strData" + + ptCount = NestedInteger(allow_none=True) + pt = Sequence(expected_type=StrVal) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('ptCount', 'pt') + + def __init__(self, + ptCount=None, + pt=(), + extLst=None, + ): + self.ptCount = ptCount + self.pt = pt + + +class StrRef(Serialisable): + + tagname = "strRef" + + f = NestedText(expected_type=str, allow_none=True) + strCache = Typed(expected_type=StrData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('f', 'strCache') + + def __init__(self, + f=None, + strCache=None, + extLst=None, + ): + self.f = f + self.strCache = strCache + + +class NumDataSource(Serialisable): + + numRef = Typed(expected_type=NumRef, allow_none=True) + numLit = Typed(expected_type=NumData, allow_none=True) + + + def __init__(self, + numRef=None, + numLit=None, + ): + self.numRef = numRef + self.numLit = numLit + + +class Level(Serialisable): + + tagname = "lvl" + + pt = Sequence(expected_type=StrVal) + + __elements__ = ('pt',) + + def __init__(self, + pt=(), + ): + self.pt = pt + + +class MultiLevelStrData(Serialisable): + + tagname = "multiLvlStrData" + + ptCount = Integer(allow_none=True) + lvl = Sequence(expected_type=Level) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('ptCount', 'lvl',) + + def __init__(self, + ptCount=None, + lvl=(), + extLst=None, + ): + self.ptCount = ptCount + self.lvl = lvl + + +class MultiLevelStrRef(Serialisable): + + tagname = "multiLvlStrRef" + + f = NestedText(expected_type=str) + multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('multiLvlStrCache', 'f') + + def __init__(self, + f=None, + multiLvlStrCache=None, + extLst=None, + ): + self.f = f + self.multiLvlStrCache = multiLvlStrCache + + +class AxDataSource(Serialisable): + + tagname = "cat" + + numRef = Typed(expected_type=NumRef, allow_none=True) + numLit = Typed(expected_type=NumData, allow_none=True) + strRef = Typed(expected_type=StrRef, allow_none=True) + strLit = Typed(expected_type=StrData, allow_none=True) + multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True) + + def __init__(self, + numRef=None, + numLit=None, + strRef=None, + strLit=None, + multiLvlStrRef=None, + ): + if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]): + raise TypeError("A data source must be provided") + self.numRef = numRef + self.numLit = numLit + self.strRef = strRef + self.strLit = strLit + self.multiLvlStrRef = multiLvlStrRef diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py b/venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py new file mode 100644 index 0000000..6bc9434 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + + + +from openpyxl.descriptors.nested import ( + NestedMinMax + ) + +from openpyxl.descriptors import Typed + +from .data_source import NumFmt + +""" +Utility descriptors for the chart module. +For convenience but also clarity. +""" + +class NestedGapAmount(NestedMinMax): + + allow_none = True + min = 0 + max = 500 + + +class NestedOverlap(NestedMinMax): + + allow_none = True + min = -100 + max = 100 + + +class NumberFormatDescriptor(Typed): + """ + Allow direct assignment of format code + """ + + expected_type = NumFmt + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = NumFmt(value) + super().__set__(instance, value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py b/venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py new file mode 100644 index 0000000..6ae2445 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Set, + Alias +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedBool, + NestedFloat, +) + +from .data_source import NumDataSource +from .shapes import GraphicalProperties + + +class ErrorBars(Serialisable): + + tagname = "errBars" + + errDir = NestedNoneSet(values=(['x', 'y'])) + direction = Alias("errDir") + errBarType = NestedSet(values=(['both', 'minus', 'plus'])) + style = Alias("errBarType") + errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr'])) + size = Alias("errValType") + noEndCap = NestedBool(nested=True, allow_none=True) + plus = Typed(expected_type=NumDataSource, allow_none=True) + minus = Typed(expected_type=NumDataSource, allow_none=True) + val = NestedFloat(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr') + + + def __init__(self, + errDir=None, + errBarType="both", + errValType="fixedVal", + noEndCap=None, + plus=None, + minus=None, + val=None, + spPr=None, + extLst=None, + ): + self.errDir = errDir + self.errBarType = errBarType + self.errValType = errValType + self.noEndCap = noEndCap + self.plus = plus + self.minus = minus + self.val = val + self.spPr = spPr diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/label.py b/venv/lib/python3.12/site-packages/openpyxl/chart/label.py new file mode 100644 index 0000000..d6eacb1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/label.py @@ -0,0 +1,127 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Alias, + Typed +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedBool, + NestedString, + NestedInteger, + ) + +from .shapes import GraphicalProperties +from .text import RichText + + +class _DataLabelBase(Serialisable): + + numFmt = NestedString(allow_none=True, attribute="formatCode") + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd', + 'l', 'outEnd', 'r', 't']) + position = Alias('dLblPos') + showLegendKey = NestedBool(allow_none=True) + showVal = NestedBool(allow_none=True) + showCatName = NestedBool(allow_none=True) + showSerName = NestedBool(allow_none=True) + showPercent = NestedBool(allow_none=True) + showBubbleSize = NestedBool(allow_none=True) + showLeaderLines = NestedBool(allow_none=True) + separator = NestedString(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey", + "showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize", + "showLeaderLines", "separator") + + def __init__(self, + numFmt=None, + spPr=None, + txPr=None, + dLblPos=None, + showLegendKey=None, + showVal=None, + showCatName=None, + showSerName=None, + showPercent=None, + showBubbleSize=None, + showLeaderLines=None, + separator=None, + extLst=None, + ): + self.numFmt = numFmt + self.spPr = spPr + self.txPr = txPr + self.dLblPos = dLblPos + self.showLegendKey = showLegendKey + self.showVal = showVal + self.showCatName = showCatName + self.showSerName = showSerName + self.showPercent = showPercent + self.showBubbleSize = showBubbleSize + self.showLeaderLines = showLeaderLines + self.separator = separator + + +class DataLabel(_DataLabelBase): + + tagname = "dLbl" + + idx = NestedInteger() + + numFmt = _DataLabelBase.numFmt + spPr = _DataLabelBase.spPr + txPr = _DataLabelBase.txPr + dLblPos = _DataLabelBase.dLblPos + showLegendKey = _DataLabelBase.showLegendKey + showVal = _DataLabelBase.showVal + showCatName = _DataLabelBase.showCatName + showSerName = _DataLabelBase.showSerName + showPercent = _DataLabelBase.showPercent + showBubbleSize = _DataLabelBase.showBubbleSize + showLeaderLines = _DataLabelBase.showLeaderLines + separator = _DataLabelBase.separator + extLst = _DataLabelBase.extLst + + __elements__ = ("idx",) + _DataLabelBase.__elements__ + + def __init__(self, idx=0, **kw ): + self.idx = idx + super().__init__(**kw) + + +class DataLabelList(_DataLabelBase): + + tagname = "dLbls" + + dLbl = Sequence(expected_type=DataLabel, allow_none=True) + + delete = NestedBool(allow_none=True) + numFmt = _DataLabelBase.numFmt + spPr = _DataLabelBase.spPr + txPr = _DataLabelBase.txPr + dLblPos = _DataLabelBase.dLblPos + showLegendKey = _DataLabelBase.showLegendKey + showVal = _DataLabelBase.showVal + showCatName = _DataLabelBase.showCatName + showSerName = _DataLabelBase.showSerName + showPercent = _DataLabelBase.showPercent + showBubbleSize = _DataLabelBase.showBubbleSize + showLeaderLines = _DataLabelBase.showLeaderLines + separator = _DataLabelBase.separator + extLst = _DataLabelBase.extLst + + __elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__ + + def __init__(self, dLbl=(), delete=None, **kw): + self.dLbl = dLbl + self.delete = delete + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/layout.py b/venv/lib/python3.12/site-packages/openpyxl/chart/layout.py new file mode 100644 index 0000000..f2f6553 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/layout.py @@ -0,0 +1,74 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + NoneSet, + Float, + Typed, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedSet, + NestedMinMax, +) + +class ManualLayout(Serialisable): + + tagname = "manualLayout" + + layoutTarget = NestedNoneSet(values=(['inner', 'outer'])) + xMode = NestedNoneSet(values=(['edge', 'factor'])) + yMode = NestedNoneSet(values=(['edge', 'factor'])) + wMode = NestedSet(values=(['edge', 'factor'])) + hMode = NestedSet(values=(['edge', 'factor'])) + x = NestedMinMax(min=-1, max=1, allow_none=True) + y = NestedMinMax(min=-1, max=1, allow_none=True) + w = NestedMinMax(min=0, max=1, allow_none=True) + width = Alias('w') + h = NestedMinMax(min=0, max=1, allow_none=True) + height = Alias('h') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x', + 'y', 'w', 'h') + + def __init__(self, + layoutTarget=None, + xMode=None, + yMode=None, + wMode="factor", + hMode="factor", + x=None, + y=None, + w=None, + h=None, + extLst=None, + ): + self.layoutTarget = layoutTarget + self.xMode = xMode + self.yMode = yMode + self.wMode = wMode + self.hMode = hMode + self.x = x + self.y = y + self.w = w + self.h = h + + +class Layout(Serialisable): + + tagname = "layout" + + manualLayout = Typed(expected_type=ManualLayout, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('manualLayout',) + + def __init__(self, + manualLayout=None, + extLst=None, + ): + self.manualLayout = manualLayout diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/legend.py b/venv/lib/python3.12/site-packages/openpyxl/chart/legend.py new file mode 100644 index 0000000..1f7c802 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/legend.py @@ -0,0 +1,75 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedSet, + NestedInteger +) + +from .layout import Layout +from .shapes import GraphicalProperties +from .text import RichText + + +class LegendEntry(Serialisable): + + tagname = "legendEntry" + + idx = NestedInteger() + delete = NestedBool() + txPr = Typed(expected_type=RichText, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'delete', 'txPr') + + def __init__(self, + idx=0, + delete=False, + txPr=None, + extLst=None, + ): + self.idx = idx + self.delete = delete + self.txPr = txPr + + +class Legend(Serialisable): + + tagname = "legend" + + legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't'])) + position = Alias('legendPos') + legendEntry = Sequence(expected_type=LegendEntry) + layout = Typed(expected_type=Layout, allow_none=True) + overlay = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias('txPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',) + + def __init__(self, + legendPos="r", + legendEntry=(), + layout=None, + overlay=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.legendPos = legendPos + self.legendEntry = legendEntry + self.layout = layout + self.overlay = overlay + self.spPr = spPr + self.txPr = txPr diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py new file mode 100644 index 0000000..0aa3ad5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py @@ -0,0 +1,129 @@ +#Autogenerated schema +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, + ) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedSet, + NestedBool, +) + +from ._chart import ChartBase +from .updown_bars import UpDownBars +from .descriptors import NestedGapAmount +from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines, _BaseAxis +from .label import DataLabelList +from .series import Series + + +class _LineChartBase(ChartBase): + + grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked'])) + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + dropLines = Typed(expected_type=ChartLines, allow_none=True) + + _series_type = "line" + + __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines') + + def __init__(self, + grouping="standard", + varyColors=None, + ser=(), + dLbls=None, + dropLines=None, + **kw + ): + self.grouping = grouping + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + super().__init__(**kw) + + +class LineChart(_LineChartBase): + + tagname = "lineChart" + + grouping = _LineChartBase.grouping + varyColors = _LineChartBase.varyColors + ser = _LineChartBase.ser + dLbls = _LineChartBase.dLbls + dropLines =_LineChartBase.dropLines + + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + marker = NestedBool(allow_none=True) + smooth = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=_BaseAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = _LineChartBase.__elements__ + ('hiLowLines', 'upDownBars', 'marker', 'smooth', 'axId') + + def __init__(self, + hiLowLines=None, + upDownBars=None, + marker=None, + smooth=None, + extLst=None, + **kw + ): + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.marker = marker + self.smooth = smooth + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + + super().__init__(**kw) + + +class LineChart3D(_LineChartBase): + + tagname = "line3DChart" + + grouping = _LineChartBase.grouping + varyColors = _LineChartBase.varyColors + ser = _LineChartBase.ser + dLbls = _LineChartBase.dLbls + dropLines =_LineChartBase.dropLines + + gapDepth = NestedGapAmount() + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + marker = NestedBool(allow_none=True) + smooth = NestedBool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis) + + __elements__ = _LineChartBase.__elements__ + ('gapDepth', 'hiLowLines', + 'upDownBars', 'marker', 'smooth', 'axId') + + def __init__(self, + gapDepth=None, + hiLowLines=None, + upDownBars=None, + marker=None, + smooth=None, + **kw + ): + self.gapDepth = gapDepth + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.marker = marker + self.smooth = smooth + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + super(LineChart3D, self).__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/marker.py b/venv/lib/python3.12/site-packages/openpyxl/chart/marker.py new file mode 100644 index 0000000..61e2641 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/marker.py @@ -0,0 +1,90 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) + +from openpyxl.descriptors.excel import( + ExtensionList, + _explicit_none, +) + +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedMinMax, + NestedNoneSet, +) + +from .layout import Layout +from .picture import PictureOptions +from .shapes import * +from .text import * +from .error_bar import * + + +class Marker(Serialisable): + + tagname = "marker" + + symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture', + 'plus', 'square', 'star', 'triangle', 'x', 'auto']), + to_tree=_explicit_none) + size = NestedMinMax(min=2, max=72, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('symbol', 'size', 'spPr') + + def __init__(self, + symbol=None, + size=None, + spPr=None, + extLst=None, + ): + self.symbol = symbol + self.size = size + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + + +class DataPoint(Serialisable): + + tagname = "dPt" + + idx = NestedInteger() + invertIfNegative = NestedBool(allow_none=True) + marker = Typed(expected_type=Marker, allow_none=True) + bubble3D = NestedBool(allow_none=True) + explosion = NestedInteger(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D', + 'explosion', 'spPr', 'pictureOptions') + + def __init__(self, + idx=None, + invertIfNegative=None, + marker=None, + bubble3D=None, + explosion=None, + spPr=None, + pictureOptions=None, + extLst=None, + ): + self.idx = idx + self.invertIfNegative = invertIfNegative + self.marker = marker + self.bubble3D = bubble3D + self.explosion = explosion + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.pictureOptions = pictureOptions diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/picture.py b/venv/lib/python3.12/site-packages/openpyxl/chart/picture.py new file mode 100644 index 0000000..8c917d8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/picture.py @@ -0,0 +1,35 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.descriptors.nested import ( + NestedBool, + NestedFloat, + NestedMinMax, + NestedNoneSet, +) + +class PictureOptions(Serialisable): + + tagname = "pictureOptions" + + applyToFront = NestedBool(allow_none=True, nested=True) + applyToSides = NestedBool(allow_none=True, nested=True) + applyToEnd = NestedBool(allow_none=True, nested=True) + pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True) + pictureStackUnit = NestedFloat(allow_none=True, nested=True) + + __elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit') + + def __init__(self, + applyToFront=None, + applyToSides=None, + applyToEnd=None, + pictureFormat=None, + pictureStackUnit=None, + ): + self.applyToFront = applyToFront + self.applyToSides = applyToSides + self.applyToEnd = applyToEnd + self.pictureFormat = pictureFormat + self.pictureStackUnit = pictureStackUnit diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py new file mode 100644 index 0000000..6bb67e1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py @@ -0,0 +1,177 @@ +#Autogenerated schema +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + MinMax, + Integer, + NoneSet, + Float, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList, Percentage +from openpyxl.descriptors.nested import ( + NestedBool, + NestedMinMax, + NestedInteger, + NestedFloat, + NestedNoneSet, + NestedSet, +) +from openpyxl.descriptors.sequence import ValueSequence + +from ._chart import ChartBase +from .axis import ChartLines +from .descriptors import NestedGapAmount +from .series import Series +from .label import DataLabelList + + +class _PieChartBase(ChartBase): + + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + + _series_type = "pie" + + __elements__ = ('varyColors', 'ser', 'dLbls') + + def __init__(self, + varyColors=True, + ser=(), + dLbls=None, + ): + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + super().__init__() + + + +class PieChart(_PieChartBase): + + tagname = "pieChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + firstSliceAng = NestedMinMax(min=0, max=360) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', ) + + def __init__(self, + firstSliceAng=0, + extLst=None, + **kw + ): + self.firstSliceAng = firstSliceAng + super().__init__(**kw) + + +class PieChart3D(_PieChartBase): + + tagname = "pie3DChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + + +class DoughnutChart(_PieChartBase): + + tagname = "doughnutChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + firstSliceAng = NestedMinMax(min=0, max=360) + holeSize = NestedMinMax(min=1, max=90, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', 'holeSize') + + def __init__(self, + firstSliceAng=0, + holeSize=10, + extLst=None, + **kw + ): + self.firstSliceAng = firstSliceAng + self.holeSize = holeSize + super().__init__(**kw) + + +class CustomSplit(Serialisable): + + tagname = "custSplit" + + secondPiePt = ValueSequence(expected_type=int) + + __elements__ = ('secondPiePt',) + + def __init__(self, + secondPiePt=(), + ): + self.secondPiePt = secondPiePt + + +class ProjectedPieChart(_PieChartBase): + + """ + From the spec 21.2.2.126 + + This element contains the pie of pie or bar of pie series on this + chart. Only the first series shall be displayed. The splitType element + shall determine whether the splitPos and custSplit elements apply. + """ + + tagname = "ofPieChart" + + varyColors = _PieChartBase.varyColors + ser = _PieChartBase.ser + dLbls = _PieChartBase.dLbls + + ofPieType = NestedSet(values=(['pie', 'bar'])) + type = Alias('ofPieType') + gapWidth = NestedGapAmount() + splitType = NestedNoneSet(values=(['auto', 'cust', 'percent', 'pos', 'val'])) + splitPos = NestedFloat(allow_none=True) + custSplit = Typed(expected_type=CustomSplit, allow_none=True) + secondPieSize = NestedMinMax(min=5, max=200, allow_none=True) + serLines = Typed(expected_type=ChartLines, allow_none=True) + join_lines = Alias('serLines') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = _PieChartBase.__elements__ + ('ofPieType', 'gapWidth', + 'splitType', 'splitPos', 'custSplit', 'secondPieSize', 'serLines') + + def __init__(self, + ofPieType="pie", + gapWidth=None, + splitType="auto", + splitPos=None, + custSplit=None, + secondPieSize=75, + serLines=None, + extLst=None, + **kw + ): + self.ofPieType = ofPieType + self.gapWidth = gapWidth + self.splitType = splitType + self.splitPos = splitPos + self.custSplit = custSplit + self.secondPieSize = secondPieSize + if serLines is None: + self.serLines = ChartLines() + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py b/venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py new file mode 100644 index 0000000..937fd29 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py @@ -0,0 +1,65 @@ + +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, +) +from openpyxl.descriptors.nested import NestedInteger, NestedText +from openpyxl.descriptors.excel import ExtensionList + +from .label import DataLabel +from .marker import Marker +from .shapes import GraphicalProperties +from .text import RichText + + +class PivotSource(Serialisable): + + tagname = "pivotSource" + + name = NestedText(expected_type=str) + fmtId = NestedInteger(expected_type=int) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('name', 'fmtId') + + def __init__(self, + name=None, + fmtId=None, + extLst=None, + ): + self.name = name + self.fmtId = fmtId + + +class PivotFormat(Serialisable): + + tagname = "pivotFmt" + + idx = NestedInteger(nested=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + TextBody = Alias("txPr") + marker = Typed(expected_type=Marker, allow_none=True) + dLbl = Typed(expected_type=DataLabel, allow_none=True) + DataLabel = Alias("dLbl") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl') + + def __init__(self, + idx=0, + spPr=None, + txPr=None, + marker=None, + dLbl=None, + extLst=None, + ): + self.idx = idx + self.spPr = spPr + self.txPr = txPr + self.marker = marker + self.dLbl = dLbl diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py b/venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py new file mode 100644 index 0000000..268bfbc --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py @@ -0,0 +1,162 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) +from openpyxl.descriptors.excel import ( + ExtensionList, +) +from openpyxl.descriptors.sequence import ( + MultiSequence, + MultiSequencePart, +) +from openpyxl.descriptors.nested import ( + NestedBool, +) + +from ._3d import _3DBase +from .area_chart import AreaChart, AreaChart3D +from .bar_chart import BarChart, BarChart3D +from .bubble_chart import BubbleChart +from .line_chart import LineChart, LineChart3D +from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart +from .radar_chart import RadarChart +from .scatter_chart import ScatterChart +from .stock_chart import StockChart +from .surface_chart import SurfaceChart, SurfaceChart3D +from .layout import Layout +from .shapes import GraphicalProperties +from .text import RichText + +from .axis import ( + NumericAxis, + TextAxis, + SeriesAxis, + DateAxis, +) + + +class DataTable(Serialisable): + + tagname = "dTable" + + showHorzBorder = NestedBool(allow_none=True) + showVertBorder = NestedBool(allow_none=True) + showOutline = NestedBool(allow_none=True) + showKeys = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline', + 'showKeys', 'spPr', 'txPr') + + def __init__(self, + showHorzBorder=None, + showVertBorder=None, + showOutline=None, + showKeys=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.showHorzBorder = showHorzBorder + self.showVertBorder = showVertBorder + self.showOutline = showOutline + self.showKeys = showKeys + self.spPr = spPr + self.txPr = txPr + + +class PlotArea(Serialisable): + + tagname = "plotArea" + + layout = Typed(expected_type=Layout, allow_none=True) + dTable = Typed(expected_type=DataTable, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + # at least one chart + _charts = MultiSequence() + areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts") + area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts") + lineChart = MultiSequencePart(expected_type=LineChart, store="_charts") + line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts") + stockChart = MultiSequencePart(expected_type=StockChart, store="_charts") + radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts") + scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts") + pieChart = MultiSequencePart(expected_type=PieChart, store="_charts") + pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts") + doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts") + barChart = MultiSequencePart(expected_type=BarChart, store="_charts") + bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts") + ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts") + surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts") + surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts") + bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts") + + # axes + _axes = MultiSequence() + valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes") + catAx = MultiSequencePart(expected_type=TextAxis, store="_axes") + dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes") + serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes") + + __elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr') + + def __init__(self, + layout=None, + dTable=None, + spPr=None, + _charts=(), + _axes=(), + extLst=None, + ): + self.layout = layout + self.dTable = dTable + self.spPr = spPr + self._charts = _charts + self._axes = _axes + + + def to_tree(self, tagname=None, idx=None, namespace=None): + axIds = {ax.axId for ax in self._axes} + for chart in self._charts: + for id, axis in chart._axes.items(): + if id not in axIds: + setattr(self, axis.tagname, axis) + axIds.add(id) + + return super().to_tree(tagname) + + + @classmethod + def from_tree(cls, node): + self = super().from_tree(node) + axes = dict((axis.axId, axis) for axis in self._axes) + for chart in self._charts: + if isinstance(chart, (ScatterChart, BubbleChart)): + x, y = (axes[axId] for axId in chart.axId) + chart.x_axis = x + chart.y_axis = y + continue + + for axId in chart.axId: + axis = axes.get(axId) + if axis is None and isinstance(chart, _3DBase): + # Series Axis can be optional + chart.z_axis = None + continue + if axis.tagname in ("catAx", "dateAx"): + chart.x_axis = axis + elif axis.tagname == "valAx": + chart.y_axis = axis + elif axis.tagname == "serAx": + chart.z_axis = axis + + return self diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py b/venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py new file mode 100644 index 0000000..6513731 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py @@ -0,0 +1,57 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Float, + Typed, + Alias, +) + +from openpyxl.worksheet.page import PrintPageSetup +from openpyxl.worksheet.header_footer import HeaderFooter + + +class PageMargins(Serialisable): + """ + Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/ + """ + tagname = "pageMargins" + + l = Float() + left = Alias('l') + r = Float() + right = Alias('r') + t = Float() + top = Alias('t') + b = Float() + bottom = Alias('b') + header = Float() + footer = Float() + + def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5): + self.l = l + self.r = r + self.t = t + self.b = b + self.header = header + self.footer = footer + + +class PrintSettings(Serialisable): + + tagname = "printSettings" + + headerFooter = Typed(expected_type=HeaderFooter, allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + + __elements__ = ("headerFooter", "pageMargins", "pageMargins") + + def __init__(self, + headerFooter=None, + pageMargins=None, + pageSetup=None, + ): + self.headerFooter = headerFooter + self.pageMargins = pageMargins + self.pageSetup = pageSetup diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py new file mode 100644 index 0000000..fa3aa0d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py @@ -0,0 +1,55 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Typed, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedSet +) + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis +from .series import Series +from .label import DataLabelList + + +class RadarChart(ChartBase): + + tagname = "radarChart" + + radarStyle = NestedSet(values=(['standard', 'marker', 'filled'])) + type = Alias("radarStyle") + varyColors = NestedBool(nested=True, allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + _series_type = "radar" + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + __elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId') + + def __init__(self, + radarStyle="standard", + varyColors=None, + ser=(), + dLbls=None, + extLst=None, + **kw + ): + self.radarStyle = radarStyle + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/reader.py b/venv/lib/python3.12/site-packages/openpyxl/chart/reader.py new file mode 100644 index 0000000..0ef719f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/reader.py @@ -0,0 +1,32 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Read a chart +""" + +def read_chart(chartspace): + cs = chartspace + plot = cs.chart.plotArea + + chart = plot._charts[0] + chart._charts = plot._charts + + chart.title = cs.chart.title + chart.display_blanks = cs.chart.dispBlanksAs + chart.visible_cells_only = cs.chart.plotVisOnly + chart.layout = plot.layout + chart.legend = cs.chart.legend + + # 3d attributes + chart.floor = cs.chart.floor + chart.sideWall = cs.chart.sideWall + chart.backWall = cs.chart.backWall + chart.pivotSource = cs.pivotSource + chart.pivotFormats = cs.chart.pivotFmts + chart.idx_base = min((s.idx for s in chart.series), default=0) + chart._reindex() + + # Border, fill, etc. + chart.graphical_properties = cs.graphical_properties + + return chart diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/reference.py b/venv/lib/python3.12/site-packages/openpyxl/chart/reference.py new file mode 100644 index 0000000..dc10279 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/reference.py @@ -0,0 +1,124 @@ +# Copyright (c) 2010-2024 openpyxl + +from itertools import chain + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + MinMax, + Typed, + String, + Strict, +) +from openpyxl.worksheet.worksheet import Worksheet +from openpyxl.utils import ( + get_column_letter, + range_to_tuple, + quote_sheetname +) + + +class DummyWorksheet: + + + def __init__(self, title): + self.title = title + + +class Reference(Strict): + + """ + Normalise cell range references + """ + + min_row = MinMax(min=1, max=1000000, expected_type=int) + max_row = MinMax(min=1, max=1000000, expected_type=int) + min_col = MinMax(min=1, max=16384, expected_type=int) + max_col = MinMax(min=1, max=16384, expected_type=int) + range_string = String(allow_none=True) + + def __init__(self, + worksheet=None, + min_col=None, + min_row=None, + max_col=None, + max_row=None, + range_string=None + ): + if range_string is not None: + sheetname, boundaries = range_to_tuple(range_string) + min_col, min_row, max_col, max_row = boundaries + worksheet = DummyWorksheet(sheetname) + + self.worksheet = worksheet + self.min_col = min_col + self.min_row = min_row + if max_col is None: + max_col = min_col + self.max_col = max_col + if max_row is None: + max_row = min_row + self.max_row = max_row + + + def __repr__(self): + return str(self) + + + def __str__(self): + fmt = u"{0}!${1}${2}:${3}${4}" + if (self.min_col == self.max_col + and self.min_row == self.max_row): + fmt = u"{0}!${1}${2}" + return fmt.format(self.sheetname, + get_column_letter(self.min_col), self.min_row, + get_column_letter(self.max_col), self.max_row + ) + + + __str__ = __str__ + + + + def __len__(self): + if self.min_row == self.max_row: + return 1 + self.max_col - self.min_col + return 1 + self.max_row - self.min_row + + + def __eq__(self, other): + return str(self) == str(other) + + + @property + def rows(self): + """ + Return all rows in the range + """ + for row in range(self.min_row, self.max_row+1): + yield Reference(self.worksheet, self.min_col, row, self.max_col, row) + + + @property + def cols(self): + """ + Return all columns in the range + """ + for col in range(self.min_col, self.max_col+1): + yield Reference(self.worksheet, col, self.min_row, col, self.max_row) + + + def pop(self): + """ + Return and remove the first cell + """ + cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row) + if self.min_row == self.max_row: + self.min_col += 1 + else: + self.min_row += 1 + return cell + + + @property + def sheetname(self): + return quote_sheetname(self.worksheet.title) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py new file mode 100644 index 0000000..2699239 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py @@ -0,0 +1,53 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedBool, +) + +from ._chart import ChartBase +from .axis import NumericAxis, TextAxis +from .series import XYSeries +from .label import DataLabelList + + +class ScatterChart(ChartBase): + + tagname = "scatterChart" + + scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker'])) + varyColors = NestedBool(allow_none=True) + ser = Sequence(expected_type=XYSeries, allow_none=True) + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias("dLbls") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=(NumericAxis, TextAxis)) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "scatter" + + __elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',) + + def __init__(self, + scatterStyle=None, + varyColors=None, + ser=(), + dLbls=None, + extLst=None, + **kw + ): + self.scatterStyle = scatterStyle + self.varyColors = varyColors + self.ser = ser + self.dLbls = dLbls + self.x_axis = NumericAxis(axId=10, crossAx=20) + self.y_axis = NumericAxis(axId=20, crossAx=10) + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/series.py b/venv/lib/python3.12/site-packages/openpyxl/chart/series.py new file mode 100644 index 0000000..f1403a6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/series.py @@ -0,0 +1,197 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Integer, + Bool, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, + NestedNoneSet, + NestedText, +) + +from .shapes import GraphicalProperties +from .data_source import ( + AxDataSource, + NumDataSource, + NumRef, + StrRef, +) +from .error_bar import ErrorBars +from .label import DataLabelList +from .marker import DataPoint, PictureOptions, Marker +from .trendline import Trendline + +attribute_mapping = { + 'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars', + 'trendline', 'cat', 'val',), + 'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt', + 'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'), + 'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls', + 'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'), + 'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline', + 'errBars', 'cat', 'val', 'smooth'), + 'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'), + 'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'), + 'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline', + 'errBars', 'xVal', 'yVal', 'smooth'), + 'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'), + } + + +class SeriesLabel(Serialisable): + + tagname = "tx" + + strRef = Typed(expected_type=StrRef, allow_none=True) + v = NestedText(expected_type=str, allow_none=True) + value = Alias('v') + + __elements__ = ('strRef', 'v') + + def __init__(self, + strRef=None, + v=None): + self.strRef = strRef + self.v = v + + +class Series(Serialisable): + + """ + Generic series object. Should not be instantiated directly. + User the chart.Series factory instead. + """ + + tagname = "ser" + + idx = NestedInteger() + order = NestedInteger() + tx = Typed(expected_type=SeriesLabel, allow_none=True) + title = Alias('tx') + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + + # area chart + pictureOptions = Typed(expected_type=PictureOptions, allow_none=True) + dPt = Sequence(expected_type=DataPoint, allow_none=True) + data_points = Alias("dPt") + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + labels = Alias("dLbls") + trendline = Typed(expected_type=Trendline, allow_none=True) + errBars = Typed(expected_type=ErrorBars, allow_none=True) + cat = Typed(expected_type=AxDataSource, allow_none=True) + identifiers = Alias("cat") + val = Typed(expected_type=NumDataSource, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + #bar chart + invertIfNegative = NestedBool(allow_none=True) + shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax'])) + + #bubble chart + xVal = Typed(expected_type=AxDataSource, allow_none=True) + yVal = Typed(expected_type=NumDataSource, allow_none=True) + bubbleSize = Typed(expected_type=NumDataSource, allow_none=True) + zVal = Alias("bubbleSize") + bubble3D = NestedBool(allow_none=True) + + #line chart + marker = Typed(expected_type=Marker, allow_none=True) + smooth = NestedBool(allow_none=True) + + #pie chart + explosion = NestedInteger(allow_none=True) + + __elements__ = () + + + def __init__(self, + idx=0, + order=0, + tx=None, + spPr=None, + pictureOptions=None, + dPt=(), + dLbls=None, + trendline=None, + errBars=None, + cat=None, + val=None, + invertIfNegative=None, + shape=None, + xVal=None, + yVal=None, + bubbleSize=None, + bubble3D=None, + marker=None, + smooth=None, + explosion=None, + extLst=None, + ): + self.idx = idx + self.order = order + self.tx = tx + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.pictureOptions = pictureOptions + self.dPt = dPt + self.dLbls = dLbls + self.trendline = trendline + self.errBars = errBars + self.cat = cat + self.val = val + self.invertIfNegative = invertIfNegative + self.shape = shape + self.xVal = xVal + self.yVal = yVal + self.bubbleSize = bubbleSize + self.bubble3D = bubble3D + if marker is None: + marker = Marker() + self.marker = marker + self.smooth = smooth + self.explosion = explosion + + + def to_tree(self, tagname=None, idx=None): + """The index can need rebasing""" + if idx is not None: + if self.order == self.idx: + self.order = idx # rebase the order if the index has been rebased + self.idx = idx + return super().to_tree(tagname) + + +class XYSeries(Series): + + """Dedicated series for charts that have x and y series""" + + idx = Series.idx + order = Series.order + tx = Series.tx + spPr = Series.spPr + + dPt = Series.dPt + dLbls = Series.dLbls + trendline = Series.trendline + errBars = Series.errBars + xVal = Series.xVal + yVal = Series.yVal + + invertIfNegative = Series.invertIfNegative + + bubbleSize = Series.bubbleSize + bubble3D = Series.bubble3D + + marker = Series.marker + smooth = Series.smooth diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py b/venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py new file mode 100644 index 0000000..90b368d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py @@ -0,0 +1,41 @@ +# Copyright (c) 2010-2024 openpyxl + +from .data_source import NumDataSource, NumRef, AxDataSource +from .reference import Reference +from .series import Series, XYSeries, SeriesLabel, StrRef +from openpyxl.utils import rows_from_range, quote_sheetname + + +def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False): + """ + Convenience Factory for creating chart data series. + """ + + if not isinstance(values, Reference): + values = Reference(range_string=values) + + if title_from_data: + cell = values.pop() + title = u"{0}!{1}".format(values.sheetname, cell) + title = SeriesLabel(strRef=StrRef(title)) + elif title is not None: + title = SeriesLabel(v=title) + + source = NumDataSource(numRef=NumRef(f=values)) + if xvalues is not None: + if not isinstance(xvalues, Reference): + xvalues = Reference(range_string=xvalues) + series = XYSeries() + series.yVal = source + series.xVal = AxDataSource(numRef=NumRef(f=xvalues)) + if zvalues is not None: + if not isinstance(zvalues, Reference): + zvalues = Reference(range_string=zvalues) + series.zVal = NumDataSource(NumRef(f=zvalues)) + else: + series = Series() + series.val = source + + if title is not None: + series.title = title + return series diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py b/venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py new file mode 100644 index 0000000..7736c1a --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py @@ -0,0 +1,89 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias +) +from openpyxl.descriptors.nested import ( + EmptyTag +) +from openpyxl.drawing.colors import ColorChoiceDescriptor +from openpyxl.drawing.fill import * +from openpyxl.drawing.line import LineProperties +from openpyxl.drawing.geometry import ( + Shape3D, + Scene3D, + Transform2D, + CustomGeometry2D, + PresetGeometry2D, +) + + +class GraphicalProperties(Serialisable): + + """ + Somewhat vaguely 21.2.2.197 says this: + + This element specifies the formatting for the parent chart element. The + custGeom, prstGeom, scene3d, and xfrm elements are not supported. The + bwMode attribute is not supported. + + This doesn't leave much. And the element is used in different places. + """ + + tagname = "spPr" + + bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray', + 'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden'] + ) + ) + + xfrm = Typed(expected_type=Transform2D, allow_none=True) + transform = Alias('xfrm') + custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or + prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True) + + # fills one of + noFill = EmptyTag(namespace=DRAWING_NS) + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + + ln = Typed(expected_type=LineProperties, allow_none=True) + line = Alias('ln') + scene3d = Typed(expected_type=Scene3D, allow_none=True) + sp3d = Typed(expected_type=Shape3D, allow_none=True) + shape3D = Alias('sp3d') + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill', + 'ln', 'scene3d', 'sp3d') + + def __init__(self, + bwMode=None, + xfrm=None, + noFill=None, + solidFill=None, + gradFill=None, + pattFill=None, + ln=None, + scene3d=None, + custGeom=None, + prstGeom=None, + sp3d=None, + extLst=None, + ): + self.bwMode = bwMode + self.xfrm = xfrm + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.pattFill = pattFill + if ln is None: + ln = LineProperties() + self.ln = ln + self.custGeom = custGeom + self.prstGeom = prstGeom + self.scene3d = scene3d + self.sp3d = sp3d diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py new file mode 100644 index 0000000..119c790 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList + +from ._chart import ChartBase +from .axis import TextAxis, NumericAxis, ChartLines +from .updown_bars import UpDownBars +from .label import DataLabelList +from .series import Series + + +class StockChart(ChartBase): + + tagname = "stockChart" + + ser = Sequence(expected_type=Series) #min 3, max4 + dLbls = Typed(expected_type=DataLabelList, allow_none=True) + dataLabels = Alias('dLbls') + dropLines = Typed(expected_type=ChartLines, allow_none=True) + hiLowLines = Typed(expected_type=ChartLines, allow_none=True) + upDownBars = Typed(expected_type=UpDownBars, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + + _series_type = "line" + + __elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars', + 'axId') + + def __init__(self, + ser=(), + dLbls=None, + dropLines=None, + hiLowLines=None, + upDownBars=None, + extLst=None, + **kw + ): + self.ser = ser + self.dLbls = dLbls + self.dropLines = dropLines + self.hiLowLines = hiLowLines + self.upDownBars = upDownBars + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + super().__init__(**kw) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py b/venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py new file mode 100644 index 0000000..5f388e1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py @@ -0,0 +1,119 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Bool, + Alias, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, +) + +from ._chart import ChartBase +from ._3d import _3DBase +from .axis import TextAxis, NumericAxis, SeriesAxis +from .shapes import GraphicalProperties +from .series import Series + + +class BandFormat(Serialisable): + + tagname = "bandFmt" + + idx = NestedInteger() + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + + __elements__ = ('idx', 'spPr') + + def __init__(self, + idx=0, + spPr=None, + ): + self.idx = idx + self.spPr = spPr + + +class BandFormatList(Serialisable): + + tagname = "bandFmts" + + bandFmt = Sequence(expected_type=BandFormat, allow_none=True) + + __elements__ = ('bandFmt',) + + def __init__(self, + bandFmt=(), + ): + self.bandFmt = bandFmt + + +class _SurfaceChartBase(ChartBase): + + wireframe = NestedBool(allow_none=True) + ser = Sequence(expected_type=Series, allow_none=True) + bandFmts = Typed(expected_type=BandFormatList, allow_none=True) + + _series_type = "surface" + + __elements__ = ('wireframe', 'ser', 'bandFmts') + + def __init__(self, + wireframe=None, + ser=(), + bandFmts=None, + **kw + ): + self.wireframe = wireframe + self.ser = ser + self.bandFmts = bandFmts + super().__init__(**kw) + + +class SurfaceChart3D(_SurfaceChartBase, _3DBase): + + tagname = "surface3DChart" + + wireframe = _SurfaceChartBase.wireframe + ser = _SurfaceChartBase.ser + bandFmts = _SurfaceChartBase.bandFmts + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + x_axis = Typed(expected_type=TextAxis) + y_axis = Typed(expected_type=NumericAxis) + z_axis = Typed(expected_type=SeriesAxis) + + __elements__ = _SurfaceChartBase.__elements__ + ('axId',) + + def __init__(self, **kw): + self.x_axis = TextAxis() + self.y_axis = NumericAxis() + self.z_axis = SeriesAxis() + super(SurfaceChart3D, self).__init__(**kw) + + +class SurfaceChart(SurfaceChart3D): + + tagname = "surfaceChart" + + wireframe = _SurfaceChartBase.wireframe + ser = _SurfaceChartBase.ser + bandFmts = _SurfaceChartBase.bandFmts + + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = SurfaceChart3D.__elements__ + + def __init__(self, **kw): + super().__init__(**kw) + self.y_axis.delete = True + self.view3D.x_rotation = 90 + self.view3D.y_rotation = 0 + self.view3D.perspective = False + self.view3D.right_angle_axes = False diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/text.py b/venv/lib/python3.12/site-packages/openpyxl/chart/text.py new file mode 100644 index 0000000..bd034c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/text.py @@ -0,0 +1,78 @@ +# Copyright (c) 2010-2024 openpyxl +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, + Sequence, +) + + +from openpyxl.drawing.text import ( + RichTextProperties, + ListStyle, + Paragraph, +) + +from .data_source import StrRef + + +class RichText(Serialisable): + + """ + From the specification: 21.2.2.216 + + This element specifies text formatting. The lstStyle element is not supported. + """ + + tagname = "rich" + + bodyPr = Typed(expected_type=RichTextProperties) + properties = Alias("bodyPr") + lstStyle = Typed(expected_type=ListStyle, allow_none=True) + p = Sequence(expected_type=Paragraph) + paragraphs = Alias('p') + + __elements__ = ("bodyPr", "lstStyle", "p") + + def __init__(self, + bodyPr=None, + lstStyle=None, + p=None, + ): + if bodyPr is None: + bodyPr = RichTextProperties() + self.bodyPr = bodyPr + self.lstStyle = lstStyle + if p is None: + p = [Paragraph()] + self.p = p + + +class Text(Serialisable): + + """ + The value can be either a cell reference or a text element + If both are present then the reference will be used. + """ + + tagname = "tx" + + strRef = Typed(expected_type=StrRef, allow_none=True) + rich = Typed(expected_type=RichText, allow_none=True) + + __elements__ = ("strRef", "rich") + + def __init__(self, + strRef=None, + rich=None + ): + self.strRef = strRef + if rich is None: + rich = RichText() + self.rich = rich + + + def to_tree(self, tagname=None, idx=None, namespace=None): + if self.strRef and self.rich: + self.rich = None # can only have one + return super().to_tree(tagname, idx, namespace) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/title.py b/venv/lib/python3.12/site-packages/openpyxl/chart/title.py new file mode 100644 index 0000000..10f79d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/title.py @@ -0,0 +1,76 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Alias, +) + +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import NestedBool + +from .text import Text, RichText +from .layout import Layout +from .shapes import GraphicalProperties + +from openpyxl.drawing.text import ( + Paragraph, + RegularTextRun, + LineBreak, + ParagraphProperties, + CharacterProperties, +) + + +class Title(Serialisable): + tagname = "title" + + tx = Typed(expected_type=Text, allow_none=True) + text = Alias('tx') + layout = Typed(expected_type=Layout, allow_none=True) + overlay = NestedBool(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + txPr = Typed(expected_type=RichText, allow_none=True) + body = Alias('txPr') + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr') + + def __init__(self, + tx=None, + layout=None, + overlay=None, + spPr=None, + txPr=None, + extLst=None, + ): + if tx is None: + tx = Text() + self.tx = tx + self.layout = layout + self.overlay = overlay + self.spPr = spPr + self.txPr = txPr + + + +def title_maker(text): + title = Title() + paraprops = ParagraphProperties() + paraprops.defRPr = CharacterProperties() + paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")] + + title.tx.rich.paragraphs = paras + return title + + +class TitleDescriptor(Typed): + + expected_type = Title + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = title_maker(value) + super().__set__(instance, value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py b/venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py new file mode 100644 index 0000000..bf6d236 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py @@ -0,0 +1,98 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Alias +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedBool, + NestedInteger, + NestedFloat, + NestedSet +) + +from .data_source import NumFmt +from .shapes import GraphicalProperties +from .text import RichText, Text +from .layout import Layout + + +class TrendlineLabel(Serialisable): + + tagname = "trendlineLbl" + + layout = Typed(expected_type=Layout, allow_none=True) + tx = Typed(expected_type=Text, allow_none=True) + numFmt = Typed(expected_type=NumFmt, allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias("spPr") + txPr = Typed(expected_type=RichText, allow_none=True) + textProperties = Alias("txPr") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr') + + def __init__(self, + layout=None, + tx=None, + numFmt=None, + spPr=None, + txPr=None, + extLst=None, + ): + self.layout = layout + self.tx = tx + self.numFmt = numFmt + self.spPr = spPr + self.txPr = txPr + + +class Trendline(Serialisable): + + tagname = "trendline" + + name = String(allow_none=True) + spPr = Typed(expected_type=GraphicalProperties, allow_none=True) + graphicalProperties = Alias('spPr') + trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power'])) + order = NestedInteger(allow_none=True) + period = NestedInteger(allow_none=True) + forward = NestedFloat(allow_none=True) + backward = NestedFloat(allow_none=True) + intercept = NestedFloat(allow_none=True) + dispRSqr = NestedBool(allow_none=True) + dispEq = NestedBool(allow_none=True) + trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('spPr', 'trendlineType', 'order', 'period', 'forward', + 'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl') + + def __init__(self, + name=None, + spPr=None, + trendlineType='linear', + order=None, + period=None, + forward=None, + backward=None, + intercept=None, + dispRSqr=None, + dispEq=None, + trendlineLbl=None, + extLst=None, + ): + self.name = name + self.spPr = spPr + self.trendlineType = trendlineType + self.order = order + self.period = period + self.forward = forward + self.backward = backward + self.intercept = intercept + self.dispRSqr = dispRSqr + self.dispEq = dispEq + self.trendlineLbl = trendlineLbl diff --git a/venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py b/venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py new file mode 100644 index 0000000..6de7ab8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py @@ -0,0 +1,31 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import Typed +from openpyxl.descriptors.excel import ExtensionList + +from .shapes import GraphicalProperties +from .axis import ChartLines +from .descriptors import NestedGapAmount + + +class UpDownBars(Serialisable): + + tagname = "upbars" + + gapWidth = NestedGapAmount() + upBars = Typed(expected_type=ChartLines, allow_none=True) + downBars = Typed(expected_type=ChartLines, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('gapWidth', 'upBars', 'downBars') + + def __init__(self, + gapWidth=150, + upBars=None, + downBars=None, + extLst=None, + ): + self.gapWidth = gapWidth + self.upBars = upBars + self.downBars = downBars diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py new file mode 100644 index 0000000..1726676 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .chartsheet import Chartsheet diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py new file mode 100644 index 0000000..21adbb4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/chartsheet.py @@ -0,0 +1,107 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors import Typed, Set, Alias +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.drawing.spreadsheet_drawing import ( + AbsoluteAnchor, + SpreadsheetDrawing, +) +from openpyxl.worksheet.page import ( + PageMargins, + PrintPageSetup +) +from openpyxl.worksheet.drawing import Drawing +from openpyxl.worksheet.header_footer import HeaderFooter +from openpyxl.workbook.child import _WorkbookChild +from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS + +from .relation import DrawingHF, SheetBackgroundPicture +from .properties import ChartsheetProperties +from .protection import ChartsheetProtection +from .views import ChartsheetViewList +from .custom import CustomChartsheetViews +from .publish import WebPublishItems + + +class Chartsheet(_WorkbookChild, Serialisable): + + tagname = "chartsheet" + _default_title = "Chart" + _rel_type = "chartsheet" + _path = "/xl/chartsheets/sheet{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" + + sheetPr = Typed(expected_type=ChartsheetProperties, allow_none=True) + sheetViews = Typed(expected_type=ChartsheetViewList) + sheetProtection = Typed(expected_type=ChartsheetProtection, allow_none=True) + customSheetViews = Typed(expected_type=CustomChartsheetViews, allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + drawing = Typed(expected_type=Drawing, allow_none=True) + drawingHF = Typed(expected_type=DrawingHF, allow_none=True) + picture = Typed(expected_type=SheetBackgroundPicture, allow_none=True) + webPublishItems = Typed(expected_type=WebPublishItems, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + sheet_state = Set(values=('visible', 'hidden', 'veryHidden')) + headerFooter = Typed(expected_type=HeaderFooter) + HeaderFooter = Alias('headerFooter') + + __elements__ = ( + 'sheetPr', 'sheetViews', 'sheetProtection', 'customSheetViews', + 'pageMargins', 'pageSetup', 'headerFooter', 'drawing', 'drawingHF', + 'picture', 'webPublishItems') + + __attrs__ = () + + def __init__(self, + sheetPr=None, + sheetViews=None, + sheetProtection=None, + customSheetViews=None, + pageMargins=None, + pageSetup=None, + headerFooter=None, + drawing=None, + drawingHF=None, + picture=None, + webPublishItems=None, + extLst=None, + parent=None, + title="", + sheet_state='visible', + ): + super().__init__(parent, title) + self._charts = [] + self.sheetPr = sheetPr + if sheetViews is None: + sheetViews = ChartsheetViewList() + self.sheetViews = sheetViews + self.sheetProtection = sheetProtection + self.customSheetViews = customSheetViews + self.pageMargins = pageMargins + self.pageSetup = pageSetup + if headerFooter is not None: + self.headerFooter = headerFooter + self.drawing = Drawing("rId1") + self.drawingHF = drawingHF + self.picture = picture + self.webPublishItems = webPublishItems + self.sheet_state = sheet_state + + + def add_chart(self, chart): + chart.anchor = AbsoluteAnchor() + self._charts.append(chart) + + + def to_tree(self): + self._drawing = SpreadsheetDrawing() + self._drawing.charts = self._charts + tree = super().to_tree() + if not self.headerFooter: + el = tree.find('headerFooter') + tree.remove(el) + tree.set("xmlns", SHEET_MAIN_NS) + return tree diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py new file mode 100644 index 0000000..01fcd25 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/custom.py @@ -0,0 +1,61 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.worksheet.header_footer import HeaderFooter + +from openpyxl.descriptors import ( + Bool, + Integer, + Set, + Typed, + Sequence +) +from openpyxl.descriptors.excel import Guid +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.worksheet.page import ( + PageMargins, + PrintPageSetup +) + + +class CustomChartsheetView(Serialisable): + tagname = "customSheetView" + + guid = Guid() + scale = Integer() + state = Set(values=(['visible', 'hidden', 'veryHidden'])) + zoomToFit = Bool(allow_none=True) + pageMargins = Typed(expected_type=PageMargins, allow_none=True) + pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True) + headerFooter = Typed(expected_type=HeaderFooter, allow_none=True) + + __elements__ = ('pageMargins', 'pageSetup', 'headerFooter') + + def __init__(self, + guid=None, + scale=None, + state='visible', + zoomToFit=None, + pageMargins=None, + pageSetup=None, + headerFooter=None, + ): + self.guid = guid + self.scale = scale + self.state = state + self.zoomToFit = zoomToFit + self.pageMargins = pageMargins + self.pageSetup = pageSetup + self.headerFooter = headerFooter + + +class CustomChartsheetViews(Serialisable): + tagname = "customSheetViews" + + customSheetView = Sequence(expected_type=CustomChartsheetView, allow_none=True) + + __elements__ = ('customSheetView',) + + def __init__(self, + customSheetView=None, + ): + self.customSheetView = customSheetView diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py new file mode 100644 index 0000000..bff6b3b --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/properties.py @@ -0,0 +1,28 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + String, + Typed +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.styles import Color + + +class ChartsheetProperties(Serialisable): + tagname = "sheetPr" + + published = Bool(allow_none=True) + codeName = String(allow_none=True) + tabColor = Typed(expected_type=Color, allow_none=True) + + __elements__ = ('tabColor',) + + def __init__(self, + published=None, + codeName=None, + tabColor=None, + ): + self.published = published + self.codeName = codeName + self.tabColor = tabColor diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py new file mode 100644 index 0000000..f76a306 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/protection.py @@ -0,0 +1,41 @@ +import hashlib + +from openpyxl.descriptors import (Bool, Integer, String) +from openpyxl.descriptors.excel import Base64Binary +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.worksheet.protection import ( + hash_password, + _Protected +) + + +class ChartsheetProtection(Serialisable, _Protected): + tagname = "sheetProtection" + + algorithmName = String(allow_none=True) + hashValue = Base64Binary(allow_none=True) + saltValue = Base64Binary(allow_none=True) + spinCount = Integer(allow_none=True) + content = Bool(allow_none=True) + objects = Bool(allow_none=True) + + __attrs__ = ("content", "objects", "password", "hashValue", "spinCount", "saltValue", "algorithmName") + + def __init__(self, + content=None, + objects=None, + hashValue=None, + spinCount=None, + saltValue=None, + algorithmName=None, + password=None, + ): + self.content = content + self.objects = objects + self.hashValue = hashValue + self.spinCount = spinCount + self.saltValue = saltValue + self.algorithmName = algorithmName + if password is not None: + self.password = password diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py new file mode 100644 index 0000000..4f5714e --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/publish.py @@ -0,0 +1,58 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + Integer, + String, + Set, + Sequence +) +from openpyxl.descriptors.serialisable import Serialisable + + +class WebPublishItem(Serialisable): + tagname = "webPublishItem" + + id = Integer() + divId = String() + sourceType = Set(values=(['sheet', 'printArea', 'autoFilter', 'range', 'chart', 'pivotTable', 'query', 'label'])) + sourceRef = String() + sourceObject = String(allow_none=True) + destinationFile = String() + title = String(allow_none=True) + autoRepublish = Bool(allow_none=True) + + def __init__(self, + id=None, + divId=None, + sourceType=None, + sourceRef=None, + sourceObject=None, + destinationFile=None, + title=None, + autoRepublish=None, + ): + self.id = id + self.divId = divId + self.sourceType = sourceType + self.sourceRef = sourceRef + self.sourceObject = sourceObject + self.destinationFile = destinationFile + self.title = title + self.autoRepublish = autoRepublish + + +class WebPublishItems(Serialisable): + tagname = "WebPublishItems" + + count = Integer(allow_none=True) + webPublishItem = Sequence(expected_type=WebPublishItem, ) + + __elements__ = ('webPublishItem',) + + def __init__(self, + count=None, + webPublishItem=None, + ): + self.count = len(webPublishItem) + self.webPublishItem = webPublishItem diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py new file mode 100644 index 0000000..47f5f3d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/relation.py @@ -0,0 +1,97 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Integer, + Alias +) +from openpyxl.descriptors.excel import Relation +from openpyxl.descriptors.serialisable import Serialisable + + +class SheetBackgroundPicture(Serialisable): + tagname = "picture" + id = Relation() + + def __init__(self, id): + self.id = id + + +class DrawingHF(Serialisable): + id = Relation() + lho = Integer(allow_none=True) + leftHeaderOddPages = Alias('lho') + lhe = Integer(allow_none=True) + leftHeaderEvenPages = Alias('lhe') + lhf = Integer(allow_none=True) + leftHeaderFirstPage = Alias('lhf') + cho = Integer(allow_none=True) + centerHeaderOddPages = Alias('cho') + che = Integer(allow_none=True) + centerHeaderEvenPages = Alias('che') + chf = Integer(allow_none=True) + centerHeaderFirstPage = Alias('chf') + rho = Integer(allow_none=True) + rightHeaderOddPages = Alias('rho') + rhe = Integer(allow_none=True) + rightHeaderEvenPages = Alias('rhe') + rhf = Integer(allow_none=True) + rightHeaderFirstPage = Alias('rhf') + lfo = Integer(allow_none=True) + leftFooterOddPages = Alias('lfo') + lfe = Integer(allow_none=True) + leftFooterEvenPages = Alias('lfe') + lff = Integer(allow_none=True) + leftFooterFirstPage = Alias('lff') + cfo = Integer(allow_none=True) + centerFooterOddPages = Alias('cfo') + cfe = Integer(allow_none=True) + centerFooterEvenPages = Alias('cfe') + cff = Integer(allow_none=True) + centerFooterFirstPage = Alias('cff') + rfo = Integer(allow_none=True) + rightFooterOddPages = Alias('rfo') + rfe = Integer(allow_none=True) + rightFooterEvenPages = Alias('rfe') + rff = Integer(allow_none=True) + rightFooterFirstPage = Alias('rff') + + def __init__(self, + id=None, + lho=None, + lhe=None, + lhf=None, + cho=None, + che=None, + chf=None, + rho=None, + rhe=None, + rhf=None, + lfo=None, + lfe=None, + lff=None, + cfo=None, + cfe=None, + cff=None, + rfo=None, + rfe=None, + rff=None, + ): + self.id = id + self.lho = lho + self.lhe = lhe + self.lhf = lhf + self.cho = cho + self.che = che + self.chf = chf + self.rho = rho + self.rhe = rhe + self.rhf = rhf + self.lfo = lfo + self.lfe = lfe + self.lff = lff + self.cfo = cfo + self.cfe = cfe + self.cff = cff + self.rfo = rfo + self.rfe = rfe + self.rff = rff diff --git a/venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py new file mode 100644 index 0000000..5928922 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/chartsheet/views.py @@ -0,0 +1,51 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Bool, + Integer, + Typed, + Sequence +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable + + +class ChartsheetView(Serialisable): + tagname = "sheetView" + + tabSelected = Bool(allow_none=True) + zoomScale = Integer(allow_none=True) + workbookViewId = Integer() + zoomToFit = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + tabSelected=None, + zoomScale=None, + workbookViewId=0, + zoomToFit=True, + extLst=None, + ): + self.tabSelected = tabSelected + self.zoomScale = zoomScale + self.workbookViewId = workbookViewId + self.zoomToFit = zoomToFit + + +class ChartsheetViewList(Serialisable): + tagname = "sheetViews" + + sheetView = Sequence(expected_type=ChartsheetView, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('sheetView',) + + def __init__(self, + sheetView=None, + extLst=None, + ): + if sheetView is None: + sheetView = [ChartsheetView()] + self.sheetView = sheetView diff --git a/venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py new file mode 100644 index 0000000..288bdf1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .comments import Comment diff --git a/venv/lib/python3.12/site-packages/openpyxl/comments/author.py b/venv/lib/python3.12/site-packages/openpyxl/comments/author.py new file mode 100644 index 0000000..9155fa5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/comments/author.py @@ -0,0 +1,21 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Sequence, + Alias +) + + +class AuthorList(Serialisable): + + tagname = "authors" + + author = Sequence(expected_type=str) + authors = Alias("author") + + def __init__(self, + author=(), + ): + self.author = author diff --git a/venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py b/venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py new file mode 100644 index 0000000..67dccc5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py @@ -0,0 +1,211 @@ +# Copyright (c) 2010-2024 openpyxl + +## Incomplete! +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Set, + String, + Bool, +) +from openpyxl.descriptors.excel import Guid, ExtensionList +from openpyxl.descriptors.sequence import NestedSequence + +from openpyxl.utils.indexed_list import IndexedList +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.cell.text import Text +from .author import AuthorList +from .comments import Comment +from .shape_writer import ShapeWriter + + +class Properties(Serialisable): + + locked = Bool(allow_none=True) + defaultSize = Bool(allow_none=True) + _print = Bool(allow_none=True) + disabled = Bool(allow_none=True) + uiObject = Bool(allow_none=True) + autoFill = Bool(allow_none=True) + autoLine = Bool(allow_none=True) + altText = String(allow_none=True) + textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed'])) + textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed'])) + lockText = Bool(allow_none=True) + justLastX = Bool(allow_none=True) + autoScale = Bool(allow_none=True) + rowHidden = Bool(allow_none=True) + colHidden = Bool(allow_none=True) + # anchor = Typed(expected_type=ObjectAnchor, ) + + __elements__ = ('anchor',) + + def __init__(self, + locked=None, + defaultSize=None, + _print=None, + disabled=None, + uiObject=None, + autoFill=None, + autoLine=None, + altText=None, + textHAlign=None, + textVAlign=None, + lockText=None, + justLastX=None, + autoScale=None, + rowHidden=None, + colHidden=None, + anchor=None, + ): + self.locked = locked + self.defaultSize = defaultSize + self._print = _print + self.disabled = disabled + self.uiObject = uiObject + self.autoFill = autoFill + self.autoLine = autoLine + self.altText = altText + self.textHAlign = textHAlign + self.textVAlign = textVAlign + self.lockText = lockText + self.justLastX = justLastX + self.autoScale = autoScale + self.rowHidden = rowHidden + self.colHidden = colHidden + self.anchor = anchor + + +class CommentRecord(Serialisable): + + tagname = "comment" + + ref = String() + authorId = Integer() + guid = Guid(allow_none=True) + shapeId = Integer(allow_none=True) + text = Typed(expected_type=Text) + commentPr = Typed(expected_type=Properties, allow_none=True) + author = String(allow_none=True) + + __elements__ = ('text', 'commentPr') + __attrs__ = ('ref', 'authorId', 'guid', 'shapeId') + + def __init__(self, + ref="", + authorId=0, + guid=None, + shapeId=0, + text=None, + commentPr=None, + author=None, + height=79, + width=144 + ): + self.ref = ref + self.authorId = authorId + self.guid = guid + self.shapeId = shapeId + if text is None: + text = Text() + self.text = text + self.commentPr = commentPr + self.author = author + self.height = height + self.width = width + + + @classmethod + def from_cell(cls, cell): + """ + Class method to convert cell comment + """ + comment = cell._comment + ref = cell.coordinate + self = cls(ref=ref, author=comment.author) + self.text.t = comment.content + self.height = comment.height + self.width = comment.width + return self + + + @property + def content(self): + """ + Remove all inline formatting and stuff + """ + return self.text.content + + +class CommentSheet(Serialisable): + + tagname = "comments" + + authors = Typed(expected_type=AuthorList) + commentList = NestedSequence(expected_type=CommentRecord, count=0) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + _id = None + _path = "/xl/comments/comment{0}.xml" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" + _rel_type = "comments" + _rel_id = None + + __elements__ = ('authors', 'commentList') + + def __init__(self, + authors=None, + commentList=None, + extLst=None, + ): + self.authors = authors + self.commentList = commentList + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def comments(self): + """ + Return a dictionary of comments keyed by coord + """ + authors = self.authors.author + + for c in self.commentList: + yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width) + + + @classmethod + def from_comments(cls, comments): + """ + Create a comment sheet from a list of comments for a particular worksheet + """ + authors = IndexedList() + + # dedupe authors and get indexes + for comment in comments: + comment.authorId = authors.add(comment.author) + + return cls(authors=AuthorList(authors), commentList=comments) + + + def write_shapes(self, vml=None): + """ + Create the VML for comments + """ + sw = ShapeWriter(self.comments) + return sw.write(vml) + + + @property + def path(self): + """ + Return path within the archive + """ + return self._path.format(self._id) diff --git a/venv/lib/python3.12/site-packages/openpyxl/comments/comments.py b/venv/lib/python3.12/site-packages/openpyxl/comments/comments.py new file mode 100644 index 0000000..192bbc4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/comments/comments.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + + +class Comment: + + _parent = None + + def __init__(self, text, author, height=79, width=144): + self.content = text + self.author = author + self.height = height + self.width = width + + + @property + def parent(self): + return self._parent + + + def __eq__(self, other): + return ( + self.content == other.content + and self.author == other.author + ) + + def __repr__(self): + return "Comment: {0} by {1}".format(self.content, self.author) + + + def __copy__(self): + """Create a detached copy of this comment.""" + clone = self.__class__(self.content, self.author, self.height, self.width) + return clone + + + def bind(self, cell): + """ + Bind comment to a particular cell + """ + if cell is not None and self._parent is not None and self._parent != cell: + fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell" + raise AttributeError(fmt.format(cell.coordinate, cell.parent.title)) + self._parent = cell + + + def unbind(self): + """ + Unbind a comment from a cell + """ + self._parent = None + + + @property + def text(self): + """ + Any comment text stripped of all formatting. + """ + return self.content + + @text.setter + def text(self, value): + self.content = value diff --git a/venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py b/venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py new file mode 100644 index 0000000..cebfbc3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py @@ -0,0 +1,112 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.functions import ( + Element, + SubElement, + tostring, +) + +from openpyxl.utils import coordinate_to_tuple + +vmlns = "urn:schemas-microsoft-com:vml" +officens = "urn:schemas-microsoft-com:office:office" +excelns = "urn:schemas-microsoft-com:office:excel" + + +class ShapeWriter: + """ + Create VML for comments + """ + + vml = None + vml_path = None + + + def __init__(self, comments): + self.comments = comments + + + def add_comment_shapetype(self, root): + shape_layout = SubElement(root, "{%s}shapelayout" % officens, + {"{%s}ext" % vmlns: "edit"}) + SubElement(shape_layout, + "{%s}idmap" % officens, + {"{%s}ext" % vmlns: "edit", "data": "1"}) + shape_type = SubElement(root, + "{%s}shapetype" % vmlns, + {"id": "_x0000_t202", + "coordsize": "21600,21600", + "{%s}spt" % officens: "202", + "path": "m,l,21600r21600,l21600,xe"}) + SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"}) + SubElement(shape_type, + "{%s}path" % vmlns, + {"gradientshapeok": "t", + "{%s}connecttype" % officens: "rect"}) + + + def add_comment_shape(self, root, idx, coord, height, width): + row, col = coordinate_to_tuple(coord) + row -= 1 + col -= 1 + shape = _shape_factory(row, col, height, width) + + shape.set('id', "_x0000_s%04d" % idx) + root.append(shape) + + + def write(self, root): + + if not hasattr(root, "findall"): + root = Element("xml") + + # Remove any existing comment shapes + comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns) + for c in comments: + root.remove(c) + + # check whether comments shape type already exists + shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns) + if shape_types is None: + self.add_comment_shapetype(root) + + for idx, (coord, comment) in enumerate(self.comments, 1026): + self.add_comment_shape(root, idx, coord, comment.height, comment.width) + + return tostring(root) + + +def _shape_factory(row, column, height, width): + style = ("position:absolute; " + "margin-left:59.25pt;" + "margin-top:1.5pt;" + "width:{width}px;" + "height:{height}px;" + "z-index:1;" + "visibility:hidden").format(height=height, + width=width) + attrs = { + "type": "#_x0000_t202", + "style": style, + "fillcolor": "#ffffe1", + "{%s}insetmode" % officens: "auto" + } + shape = Element("{%s}shape" % vmlns, attrs) + + SubElement(shape, "{%s}fill" % vmlns, + {"color2": "#ffffe1"}) + SubElement(shape, "{%s}shadow" % vmlns, + {"color": "black", "obscured": "t"}) + SubElement(shape, "{%s}path" % vmlns, + {"{%s}connecttype" % officens: "none"}) + textbox = SubElement(shape, "{%s}textbox" % vmlns, + {"style": "mso-direction-alt:auto"}) + SubElement(textbox, "div", {"style": "text-align:left"}) + client_data = SubElement(shape, "{%s}ClientData" % excelns, + {"ObjectType": "Note"}) + SubElement(client_data, "{%s}MoveWithCells" % excelns) + SubElement(client_data, "{%s}SizeWithCells" % excelns) + SubElement(client_data, "{%s}AutoFill" % excelns).text = "False" + SubElement(client_data, "{%s}Row" % excelns).text = str(row) + SubElement(client_data, "{%s}Column" % excelns).text = str(column) + return shape diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py new file mode 100644 index 0000000..dac0909 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/__init__.py @@ -0,0 +1,54 @@ +# Copyright (c) 2010-2024 openpyxl + +from .numbers import NUMERIC_TYPES +from .strings import safe_string + +import warnings +from functools import wraps +import inspect + + +class DummyCode: + + pass + + +# from https://github.com/tantale/deprecated/blob/master/deprecated/__init__.py +# with an enhancement to update docstrings of deprecated functions +string_types = (type(b''), type(u'')) +def deprecated(reason): + + if isinstance(reason, string_types): + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "Call to deprecated class {name} ({reason})." + else: + fmt1 = "Call to deprecated function {name} ({reason})." + + @wraps(func1) + def new_func1(*args, **kwargs): + #warnings.simplefilter('default', DeprecationWarning) + warnings.warn( + fmt1.format(name=func1.__name__, reason=reason), + category=DeprecationWarning, + stacklevel=2 + ) + return func1(*args, **kwargs) + + # Enhance docstring with a deprecation note + deprecationNote = "\n\n.. note::\n Deprecated: " + reason + if new_func1.__doc__: + new_func1.__doc__ += deprecationNote + else: + new_func1.__doc__ = deprecationNote + return new_func1 + + return decorator + + elif inspect.isclass(reason) or inspect.isfunction(reason): + raise TypeError("Reason for deprecation must be supplied") + + else: + raise TypeError(repr(type(reason))) diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/abc.py b/venv/lib/python3.12/site-packages/openpyxl/compat/abc.py new file mode 100644 index 0000000..36a47f3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/abc.py @@ -0,0 +1,8 @@ +# Copyright (c) 2010-2024 openpyxl + + +try: + from abc import ABC +except ImportError: + from abc import ABCMeta + ABC = ABCMeta('ABC', (object, ), {}) diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py b/venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py new file mode 100644 index 0000000..7d58345 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/numbers.py @@ -0,0 +1,43 @@ +# Copyright (c) 2010-2024 openpyxl + +from decimal import Decimal + +NUMERIC_TYPES = (int, float, Decimal) + + +try: + import numpy + NUMPY = True +except ImportError: + NUMPY = False + + +if NUMPY: + NUMERIC_TYPES = NUMERIC_TYPES + (numpy.short, + numpy.ushort, + numpy.intc, + numpy.uintc, + numpy.int_, + numpy.uint, + numpy.longlong, + numpy.ulonglong, + numpy.half, + numpy.float16, + numpy.single, + numpy.double, + numpy.longdouble, + numpy.int8, + numpy.int16, + numpy.int32, + numpy.int64, + numpy.uint8, + numpy.uint16, + numpy.uint32, + numpy.uint64, + numpy.intp, + numpy.uintp, + numpy.float32, + numpy.float64, + numpy.bool_, + numpy.floating, + numpy.integer) diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/product.py b/venv/lib/python3.12/site-packages/openpyxl/compat/product.py new file mode 100644 index 0000000..68fdae9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/product.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +math.prod equivalent for < Python 3.8 +""" + +import functools +import operator + +def product(sequence): + return functools.reduce(operator.mul, sequence) + + +try: + from math import prod +except ImportError: + prod = product diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py b/venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py new file mode 100644 index 0000000..1fe6a90 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/singleton.py @@ -0,0 +1,40 @@ +# Copyright (c) 2010-2024 openpyxl + +import weakref + + +class Singleton(type): + """ + Singleton metaclass + Based on Python Cookbook 3rd Edition Recipe 9.13 + Only one instance of a class can exist. Does not work with __slots__ + """ + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__instance = None + + def __call__(self, *args, **kw): + if self.__instance is None: + self.__instance = super().__call__(*args, **kw) + return self.__instance + + +class Cached(type): + """ + Caching metaclass + Child classes will only create new instances of themselves if + one doesn't already exist. Does not work with __slots__ + """ + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__cache = weakref.WeakValueDictionary() + + def __call__(self, *args): + if args in self.__cache: + return self.__cache[args] + + obj = super().__call__(*args) + self.__cache[args] = obj + return obj diff --git a/venv/lib/python3.12/site-packages/openpyxl/compat/strings.py b/venv/lib/python3.12/site-packages/openpyxl/compat/strings.py new file mode 100644 index 0000000..2cc9d60 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/compat/strings.py @@ -0,0 +1,25 @@ +# Copyright (c) 2010-2024 openpyxl + +from datetime import datetime +from math import isnan, isinf +import sys + +VER = sys.version_info + +from .numbers import NUMERIC_TYPES + + +def safe_string(value): + """Safely and consistently format numeric values""" + if isinstance(value, NUMERIC_TYPES): + if isnan(value) or isinf(value): + value = "" + else: + value = "%.16g" % value + elif value is None: + value = "none" + elif isinstance(value, datetime): + value = value.isoformat() + elif not isinstance(value, str): + value = str(value) + return value diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py new file mode 100644 index 0000000..df86a3c --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/__init__.py @@ -0,0 +1,58 @@ +# Copyright (c) 2010-2024 openpyxl + +from .base import * +from .sequence import Sequence + + +class MetaStrict(type): + + def __new__(cls, clsname, bases, methods): + for k, v in methods.items(): + if isinstance(v, Descriptor): + v.name = k + return type.__new__(cls, clsname, bases, methods) + + +class Strict(metaclass=MetaStrict): + + pass + + +class MetaSerialisable(type): + + def __new__(cls, clsname, bases, methods): + attrs = [] + nested = [] + elements = [] + namespaced = [] + for k, v in methods.items(): + if isinstance(v, Descriptor): + ns= getattr(v, 'namespace', None) + if ns: + namespaced.append((k, "{%s}%s" % (ns, k))) + if getattr(v, 'nested', False): + nested.append(k) + elements.append(k) + elif isinstance(v, Sequence): + elements.append(k) + elif isinstance(v, Typed): + if hasattr(v.expected_type, 'to_tree'): + elements.append(k) + elif isinstance(v.expected_type, tuple): + if any((hasattr(el, "to_tree") for el in v.expected_type)): + # don't bind elements as attrs + continue + else: + attrs.append(k) + else: + if not isinstance(v, Alias): + attrs.append(k) + + if methods.get('__attrs__') is None: + methods['__attrs__'] = tuple(attrs) + methods['__namespaced__'] = tuple(namespaced) + if methods.get('__nested__') is None: + methods['__nested__'] = tuple(sorted(nested)) + if methods.get('__elements__') is None: + methods['__elements__'] = tuple(sorted(elements)) + return MetaStrict.__new__(cls, clsname, bases, methods) diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py new file mode 100644 index 0000000..f1e86ed --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/base.py @@ -0,0 +1,272 @@ +# Copyright (c) 2010-2024 openpyxl + + +""" +Based on Python Cookbook 3rd Edition, 8.13 +http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130 +""" + +import datetime +import re + +from openpyxl import DEBUG +from openpyxl.utils.datetime import from_ISO8601 + +from .namespace import namespaced + +class Descriptor: + + def __init__(self, name=None, **kw): + self.name = name + for k, v in kw.items(): + setattr(self, k, v) + + def __set__(self, instance, value): + instance.__dict__[self.name] = value + + +class Typed(Descriptor): + """Values must of a particular type""" + + expected_type = type(None) + allow_none = False + nested = False + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.__doc__ = f"Values must be of type {self.expected_type}" + + def __set__(self, instance, value): + if not isinstance(value, self.expected_type): + if (not self.allow_none + or (self.allow_none and value is not None)): + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}" + if DEBUG: + msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}" + raise TypeError(msg) + super().__set__(instance, value) + + def __repr__(self): + return self.__doc__ + + +def _convert(expected_type, value): + """ + Check value is of or can be converted to expected type. + """ + if not isinstance(value, expected_type): + try: + value = expected_type(value) + except: + raise TypeError('expected ' + str(expected_type)) + return value + + +class Convertible(Typed): + """Values must be convertible to a particular type""" + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + super().__set__(instance, value) + + +class Max(Convertible): + """Values must be less than a `max` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'max' not in kw and not hasattr(self, 'max'): + raise TypeError('missing max value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value > self.max: + raise ValueError('Max value is {0}'.format(self.max)) + super().__set__(instance, value) + + +class Min(Convertible): + """Values must be greater than a `min` value""" + + expected_type = float + allow_none = False + + def __init__(self, **kw): + if 'min' not in kw and not hasattr(self, 'min'): + raise TypeError('missing min value') + super().__init__(**kw) + + def __set__(self, instance, value): + if ((self.allow_none and value is not None) + or not self.allow_none): + value = _convert(self.expected_type, value) + if value < self.min: + raise ValueError('Min value is {0}'.format(self.min)) + super().__set__(instance, value) + + +class MinMax(Min, Max): + """Values must be greater than `min` value and less than a `max` one""" + pass + + +class Set(Descriptor): + """Value can only be from a set of know values""" + + def __init__(self, name=None, **kw): + if not 'values' in kw: + raise TypeError("missing set of values") + kw['values'] = set(kw['values']) + super().__init__(name, **kw) + self.__doc__ = "Value must be one of {0}".format(self.values) + + def __set__(self, instance, value): + if value not in self.values: + raise ValueError(self.__doc__) + super().__set__(instance, value) + + +class NoneSet(Set): + + """'none' will be treated as None""" + + def __init__(self, name=None, **kw): + super().__init__(name, **kw) + self.values.add(None) + + def __set__(self, instance, value): + if value == 'none': + value = None + super().__set__(instance, value) + + +class Integer(Convertible): + + expected_type = int + + +class Float(Convertible): + + expected_type = float + + +class Bool(Convertible): + + expected_type = bool + + def __set__(self, instance, value): + if isinstance(value, str): + if value in ('false', 'f', '0'): + value = False + super().__set__(instance, value) + + +class String(Typed): + + expected_type = str + + +class Text(String, Convertible): + + pass + + +class ASCII(Typed): + + expected_type = bytes + + +class Tuple(Typed): + + expected_type = tuple + + +class Length(Descriptor): + + def __init__(self, name=None, **kw): + if "length" not in kw: + raise TypeError("value length must be supplied") + super().__init__(**kw) + + + def __set__(self, instance, value): + if len(value) != self.length: + raise ValueError("Value must be length {0}".format(self.length)) + super().__set__(instance, value) + + +class Default(Typed): + """ + When called returns an instance of the expected type. + Additional default values can be passed in to the descriptor + """ + + def __init__(self, name=None, **kw): + if "defaults" not in kw: + kw['defaults'] = {} + super().__init__(**kw) + + def __call__(self): + return self.expected_type() + + +class Alias(Descriptor): + """ + Aliases can be used when either the desired attribute name is not allowed + or confusing in Python (eg. "type") or a more descriptive name is desired + (eg. "underline" for "u") + """ + + def __init__(self, alias): + self.alias = alias + + def __set__(self, instance, value): + setattr(instance, self.alias, value) + + def __get__(self, instance, cls): + return getattr(instance, self.alias) + + +class MatchPattern(Descriptor): + """Values must match a regex pattern """ + allow_none = False + + def __init__(self, name=None, **kw): + if 'pattern' not in kw and not hasattr(self, 'pattern'): + raise TypeError('missing pattern value') + + super().__init__(name, **kw) + self.test_pattern = re.compile(self.pattern, re.VERBOSE) + + + def __set__(self, instance, value): + + if value is None and not self.allow_none: + raise ValueError("Value must not be none") + + if ((self.allow_none and value is not None) + or not self.allow_none): + if not self.test_pattern.match(value): + raise ValueError('Value does not match pattern {0}'.format(self.pattern)) + + super().__set__(instance, value) + + +class DateTime(Typed): + + expected_type = datetime.datetime + + def __set__(self, instance, value): + if value is not None and isinstance(value, str): + try: + value = from_ISO8601(value) + except ValueError: + raise ValueError("Value must be ISO datetime format") + super().__set__(instance, value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py new file mode 100644 index 0000000..4b1839f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/container.py @@ -0,0 +1,41 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Utility list for top level containers that contain one type of element + +Provides the necessary API to read and write XML +""" + +from openpyxl.xml.functions import Element + + +class ElementList(list): + + + @property + def tagname(self): + raise NotImplementedError + + + @property + def expected_type(self): + raise NotImplementedError + + + @classmethod + def from_tree(cls, tree): + l = [cls.expected_type.from_tree(el) for el in tree] + return cls(l) + + + def to_tree(self): + container = Element(self.tagname) + for el in self: + container.append(el.to_tree()) + return container + + + def append(self, value): + if not isinstance(value, self.expected_type): + raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided") + super().append(value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py new file mode 100644 index 0000000..d8aa202 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/excel.py @@ -0,0 +1,112 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Excel specific descriptors +""" + +from openpyxl.xml.constants import REL_NS +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element + +from . import ( + MatchPattern, + MinMax, + Integer, + String, + Sequence, +) +from .serialisable import Serialisable + + +class HexBinary(MatchPattern): + + pattern = "[0-9a-fA-F]+$" + + +class UniversalMeasure(MatchPattern): + + pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)" + + +class TextPoint(MinMax): + """ + Size in hundredths of points. + In theory other units of measurement can be used but these are unbounded + """ + expected_type = int + + min = -400000 + max = 400000 + + +Coordinate = Integer + + +class Percentage(MinMax): + + pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict + min = -1000000 + max = 1000000 + + def __set__(self, instance, value): + if isinstance(value, str) and "%" in value: + value = value.replace("%", "") + value = int(float(value) * 1000) + super().__set__(instance, value) + + +class Extension(Serialisable): + + uri = String() + + def __init__(self, + uri=None, + ): + self.uri = uri + + +class ExtensionList(Serialisable): + + ext = Sequence(expected_type=Extension) + + def __init__(self, + ext=(), + ): + self.ext = ext + + +class Relation(String): + + namespace = REL_NS + allow_none = True + + +class Base64Binary(MatchPattern): + # http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary + pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$" + + +class Guid(MatchPattern): + # https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx + pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}" + + +class CellRange(MatchPattern): + + pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$" + allow_none = True + + def __set__(self, instance, value): + + if value is not None: + value = value.upper() + super().__set__(instance, value) + + +def _explicit_none(tagname, value, namespace=None): + """ + Override serialisation because explicit none required + """ + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname, val=safe_string(value)) diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py new file mode 100644 index 0000000..93cc9e4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/namespace.py @@ -0,0 +1,12 @@ +# Copyright (c) 2010-2024 openpyxl + + +def namespaced(obj, tagname, namespace=None): + """ + Utility to create a namespaced tag for an object + """ + + namespace = getattr(obj, "namespace", None) or namespace + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return tagname diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py new file mode 100644 index 0000000..bda63a2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/nested.py @@ -0,0 +1,129 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Generic serialisable classes +""" +from .base import ( + Convertible, + Bool, + Descriptor, + NoneSet, + MinMax, + Set, + Float, + Integer, + String, + ) +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, localname, whitespace + + +class Nested(Descriptor): + + nested = True + attribute = "val" + + def __set__(self, instance, value): + if hasattr(value, "tag"): + tag = localname(value) + if tag != self.name: + raise ValueError("Tag does not match attribute") + + value = self.from_tree(value) + super().__set__(instance, value) + + + def from_tree(self, node): + return node.get(self.attribute) + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + value = safe_string(value) + return Element(tagname, {self.attribute:value}) + + +class NestedValue(Nested, Convertible): + """ + Nested tag storing the value on the 'val' attribute + """ + pass + + +class NestedText(NestedValue): + """ + Represents any nested tag with the value as the contents of the tag + """ + + + def from_tree(self, node): + return node.text + + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if value is not None: + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + el = Element(tagname) + el.text = safe_string(value) + whitespace(el) + return el + + +class NestedFloat(NestedValue, Float): + + pass + + +class NestedInteger(NestedValue, Integer): + + pass + + +class NestedString(NestedValue, String): + + pass + + +class NestedBool(NestedValue, Bool): + + + def from_tree(self, node): + return node.get("val", True) + + +class NestedNoneSet(Nested, NoneSet): + + pass + + +class NestedSet(Nested, Set): + + pass + + +class NestedMinMax(Nested, MinMax): + + pass + + +class EmptyTag(Nested, Bool): + + """ + Boolean if a tag exists or not. + """ + + def from_tree(self, node): + return True + + + def to_tree(self, tagname=None, value=None, namespace=None): + if value: + namespace = getattr(self, "namespace", namespace) + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + return Element(tagname) diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py new file mode 100644 index 0000000..d77116b --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/sequence.py @@ -0,0 +1,136 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element +from openpyxl.utils.indexed_list import IndexedList + +from .base import Descriptor, Alias, _convert +from .namespace import namespaced + + +class Sequence(Descriptor): + """ + A sequence (list or tuple) that may only contain objects of the declared + type + """ + + expected_type = type(None) + seq_types = (list, tuple) + idx_base = 0 + unique = False + container = list + + + def __set__(self, instance, seq): + if not isinstance(seq, self.seq_types): + raise TypeError("Value must be a sequence") + seq = self.container(_convert(self.expected_type, value) for value in seq) + if self.unique: + seq = IndexedList(seq) + + super().__set__(instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for idx, v in enumerate(obj, self.idx_base): + if hasattr(v, "to_tree"): + el = v.to_tree(tagname, idx) + else: + tagname = namespaced(obj, tagname, namespace) + el = Element(tagname) + el.text = safe_string(v) + yield el + + +class UniqueSequence(Sequence): + """ + Use a set to keep values unique + """ + seq_types = (list, tuple, set) + container = set + + +class ValueSequence(Sequence): + """ + A sequence of primitive types that are stored as a single attribute. + "val" is the default attribute + """ + + attribute = "val" + + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + for v in obj: + yield Element(tagname, {self.attribute:safe_string(v)}) + + + def from_tree(self, node): + + return node.get(self.attribute) + + +class NestedSequence(Sequence): + """ + Wrap a sequence in an containing object + """ + + count = False + + def to_tree(self, tagname, obj, namespace=None): + tagname = namespaced(self, tagname, namespace) + container = Element(tagname) + if self.count: + container.set('count', str(len(obj))) + for v in obj: + container.append(v.to_tree()) + return container + + + def from_tree(self, node): + return [self.expected_type.from_tree(el) for el in node] + + +class MultiSequence(Sequence): + """ + Sequences can contain objects with different tags + """ + + def __set__(self, instance, seq): + if not isinstance(seq, (tuple, list)): + raise ValueError("Value must be a sequence") + seq = list(seq) + Descriptor.__set__(self, instance, seq) + + + def to_tree(self, tagname, obj, namespace=None): + """ + Convert the sequence represented by the descriptor to an XML element + """ + for v in obj: + el = v.to_tree(namespace=namespace) + yield el + + +class MultiSequencePart(Alias): + """ + Allow a multisequence to be built up from parts + + Excluded from the instance __elements__ or __attrs__ as is effectively an Alias + """ + + def __init__(self, expected_type, store): + self.expected_type = expected_type + self.store = store + + + def __set__(self, instance, value): + value = _convert(self.expected_type, value) + instance.__dict__[self.store].append(value) + + + def __get__(self, instance, cls): + return self diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py new file mode 100644 index 0000000..1bc9ef0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/serialisable.py @@ -0,0 +1,240 @@ +# Copyright (c) 2010-2024 openpyxl + +from copy import copy +from keyword import kwlist +KEYWORDS = frozenset(kwlist) + +from . import Descriptor +from . import MetaSerialisable +from .sequence import ( + Sequence, + NestedSequence, + MultiSequencePart, +) +from .namespace import namespaced + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import ( + Element, + localname, +) + +seq_types = (list, tuple) + +class Serialisable(metaclass=MetaSerialisable): + """ + Objects can serialise to XML their attributes and child objects. + The following class attributes are created by the metaclass at runtime: + __attrs__ = attributes + __nested__ = single-valued child treated as an attribute + __elements__ = child elements + """ + + __attrs__ = None + __nested__ = None + __elements__ = None + __namespaced__ = None + + idx_base = 0 + + @property + def tagname(self): + raise(NotImplementedError) + + namespace = None + + @classmethod + def from_tree(cls, node): + """ + Create object from XML + """ + # strip known namespaces from attributes + attrib = dict(node.attrib) + for key, ns in cls.__namespaced__: + if ns in attrib: + attrib[key] = attrib[ns] + del attrib[ns] + + # strip attributes with unknown namespaces + for key in list(attrib): + if key.startswith('{'): + del attrib[key] + elif key in KEYWORDS: + attrib["_" + key] = attrib[key] + del attrib[key] + elif "-" in key: + n = key.replace("-", "_") + attrib[n] = attrib[key] + del attrib[key] + + if node.text and "attr_text" in cls.__attrs__: + attrib["attr_text"] = node.text + + for el in node: + tag = localname(el) + if tag in KEYWORDS: + tag = "_" + tag + desc = getattr(cls, tag, None) + if desc is None or isinstance(desc, property): + continue + + if hasattr(desc, 'from_tree'): + #descriptor manages conversion + obj = desc.from_tree(el) + else: + if hasattr(desc.expected_type, "from_tree"): + #complex type + obj = desc.expected_type.from_tree(el) + else: + #primitive + obj = el.text + + if isinstance(desc, NestedSequence): + attrib[tag] = obj + elif isinstance(desc, Sequence): + attrib.setdefault(tag, []) + attrib[tag].append(obj) + elif isinstance(desc, MultiSequencePart): + attrib.setdefault(desc.store, []) + attrib[desc.store].append(obj) + else: + attrib[tag] = obj + + return cls(**attrib) + + + def to_tree(self, tagname=None, idx=None, namespace=None): + + if tagname is None: + tagname = self.tagname + + # keywords have to be masked + if tagname.startswith("_"): + tagname = tagname[1:] + + tagname = namespaced(self, tagname, namespace) + namespace = getattr(self, "namespace", namespace) + + attrs = dict(self) + for key, ns in self.__namespaced__: + if key in attrs: + attrs[ns] = attrs[key] + del attrs[key] + + el = Element(tagname, attrs) + if "attr_text" in self.__attrs__: + el.text = safe_string(getattr(self, "attr_text")) + + for child_tag in self.__elements__: + desc = getattr(self.__class__, child_tag, None) + obj = getattr(self, child_tag) + if hasattr(desc, "namespace") and hasattr(obj, 'namespace'): + obj.namespace = desc.namespace + + if isinstance(obj, seq_types): + if isinstance(desc, NestedSequence): + # wrap sequence in container + if not obj: + continue + nodes = [desc.to_tree(child_tag, obj, namespace)] + elif isinstance(desc, Sequence): + # sequence + desc.idx_base = self.idx_base + nodes = (desc.to_tree(child_tag, obj, namespace)) + else: # property + nodes = (v.to_tree(child_tag, namespace) for v in obj) + for node in nodes: + el.append(node) + else: + if child_tag in self.__nested__: + node = desc.to_tree(child_tag, obj, namespace) + elif obj is None: + continue + else: + node = obj.to_tree(child_tag) + if node is not None: + el.append(node) + return el + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if attr.startswith("_"): + attr = attr[1:] + elif attr != "attr_text" and "_" in attr: + desc = getattr(self.__class__, attr) + if getattr(desc, "hyphenated", False): + attr = attr.replace("_", "-") + if attr != "attr_text" and value is not None: + yield attr, safe_string(value) + + + def __eq__(self, other): + if not self.__class__ == other.__class__: + return False + elif not dict(self) == dict(other): + return False + for el in self.__elements__: + if getattr(self, el) != getattr(other, el): + return False + return True + + + def __ne__(self, other): + return not self == other + + + def __repr__(self): + s = u"<{0}.{1} object>\nParameters:".format( + self.__module__, + self.__class__.__name__ + ) + args = [] + for k in self.__attrs__ + self.__elements__: + v = getattr(self, k) + if isinstance(v, Descriptor): + v = None + args.append(u"{0}={1}".format(k, repr(v))) + args = u", ".join(args) + + return u"\n".join([s, args]) + + + def __hash__(self): + fields = [] + for attr in self.__attrs__ + self.__elements__: + val = getattr(self, attr) + if isinstance(val, list): + val = tuple(val) + fields.append(val) + + return hash(tuple(fields)) + + + def __add__(self, other): + if type(self) != type(other): + raise TypeError("Cannot combine instances of different types") + vals = {} + for attr in self.__attrs__: + vals[attr] = getattr(self, attr) or getattr(other, attr) + for el in self.__elements__: + a = getattr(self, el) + b = getattr(other, el) + if a and b: + vals[el] = a + b + else: + vals[el] = a or b + return self.__class__(**vals) + + + def __copy__(self): + # serialise to xml and back to avoid shallow copies + xml = self.to_tree(tagname="dummy") + cp = self.__class__.from_tree(xml) + # copy any non-persisted attributed + for k in self.__dict__: + if k not in self.__attrs__ + self.__elements__: + v = copy(getattr(self, k)) + setattr(cp, k, v) + return cp diff --git a/venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py b/venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py new file mode 100644 index 0000000..cadc1ef --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/descriptors/slots.py @@ -0,0 +1,18 @@ +# Metaclass for mixing slots and descriptors +# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383 + +class AutoSlotProperties(type): + + def __new__(mcl, classname, bases, dictionary): + slots = list(dictionary.get("__slots__", [])) + for getter_name in [key for key in dictionary if key.startswith("get_")]: + name = getter_name + slots.append("__" + name) + getter = dictionary.pop(getter_name) + setter = dictionary.get(setter_name, None) + if (setter is not None + and isinstance(setter, collections.Callable)): + del dictionary[setter_name] + dictionary[name] = property(getter. setter) + dictionary["__slots__"] = tuple(slots) + return super().__new__(mcl, classname, bases, dictionary) diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py new file mode 100644 index 0000000..02f0587 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .drawing import Drawing diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py new file mode 100644 index 0000000..19fa5e8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/colors.py @@ -0,0 +1,435 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Integer, + Set, + MinMax, +) +from openpyxl.descriptors.excel import Percentage +from openpyxl.descriptors.nested import ( + NestedNoneSet, + NestedValue, + NestedInteger, + EmptyTag, +) + +from openpyxl.styles.colors import RGB +from openpyxl.xml.constants import DRAWING_NS + +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +PRESET_COLORS = [ + 'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine', + 'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue', + 'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse', + 'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan', + 'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey', + 'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange', + 'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue', + 'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet', + 'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen', + 'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed', + 'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey', + 'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray', + 'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen', + 'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray', + 'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed', + 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen', + 'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan', + 'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen', + 'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue', + 'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow', + 'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey', + 'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue', + 'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime', + 'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue', + 'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue', + 'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine', + 'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen', + 'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise', + 'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin', + 'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange', + 'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise', + 'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum', + 'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown', + 'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver', + 'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen', + 'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', + 'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen' + ] + + +SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3', + 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1', + 'dk2', 'lt2' + ] + + +class Transform(Serialisable): + + pass + + +class SystemColor(Serialisable): + + tagname = "sysClr" + namespace = DRAWING_NS + + # color transform options + tint = NestedInteger(allow_none=True) + shade = NestedInteger(allow_none=True) + comp = Typed(expected_type=Transform, allow_none=True) + inv = Typed(expected_type=Transform, allow_none=True) + gray = Typed(expected_type=Transform, allow_none=True) + alpha = NestedInteger(allow_none=True) + alphaOff = NestedInteger(allow_none=True) + alphaMod = NestedInteger(allow_none=True) + hue = NestedInteger(allow_none=True) + hueOff = NestedInteger(allow_none=True) + hueMod = NestedInteger(allow_none=True) + sat = NestedInteger(allow_none=True) + satOff = NestedInteger(allow_none=True) + satMod = NestedInteger(allow_none=True) + lum = NestedInteger(allow_none=True) + lumOff = NestedInteger(allow_none=True) + lumMod = NestedInteger(allow_none=True) + red = NestedInteger(allow_none=True) + redOff = NestedInteger(allow_none=True) + redMod = NestedInteger(allow_none=True) + green = NestedInteger(allow_none=True) + greenOff = NestedInteger(allow_none=True) + greenMod = NestedInteger(allow_none=True) + blue = NestedInteger(allow_none=True) + blueOff = NestedInteger(allow_none=True) + blueMod = NestedInteger(allow_none=True) + gamma = Typed(expected_type=Transform, allow_none=True) + invGamma = Typed(expected_type=Transform, allow_none=True) + + val = Set(values=( ['scrollBar', 'background', 'activeCaption', + 'inactiveCaption', 'menu', 'window', 'windowFrame', 'menuText', + 'windowText', 'captionText', 'activeBorder', 'inactiveBorder', + 'appWorkspace', 'highlight', 'highlightText', 'btnFace', 'btnShadow', + 'grayText', 'btnText', 'inactiveCaptionText', 'btnHighlight', + '3dDkShadow', '3dLight', 'infoText', 'infoBk', 'hotLight', + 'gradientActiveCaption', 'gradientInactiveCaption', 'menuHighlight', + 'menuBar'] ) + ) + lastClr = RGB(allow_none=True) + + __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', "alpha", + "alphaOff", "alphaMod", "hue", "hueOff", "hueMod", "hueOff", "sat", + "satOff", "satMod", "lum", "lumOff", "lumMod", "red", "redOff", "redMod", + "green", "greenOff", "greenMod", "blue", "blueOff", "blueMod", "gamma", + "invGamma") + + def __init__(self, + val="windowText", + lastClr=None, + tint=None, + shade=None, + comp=None, + inv=None, + gray=None, + alpha=None, + alphaOff=None, + alphaMod=None, + hue=None, + hueOff=None, + hueMod=None, + sat=None, + satOff=None, + satMod=None, + lum=None, + lumOff=None, + lumMod=None, + red=None, + redOff=None, + redMod=None, + green=None, + greenOff=None, + greenMod=None, + blue=None, + blueOff=None, + blueMod=None, + gamma=None, + invGamma=None + ): + self.val = val + self.lastClr = lastClr + self.tint = tint + self.shade = shade + self.comp = comp + self.inv = inv + self.gray = gray + self.alpha = alpha + self.alphaOff = alphaOff + self.alphaMod = alphaMod + self.hue = hue + self.hueOff = hueOff + self.hueMod = hueMod + self.sat = sat + self.satOff = satOff + self.satMod = satMod + self.lum = lum + self.lumOff = lumOff + self.lumMod = lumMod + self.red = red + self.redOff = redOff + self.redMod = redMod + self.green = green + self.greenOff = greenOff + self.greenMod = greenMod + self.blue = blue + self.blueOff = blueOff + self.blueMod = blueMod + self.gamma = gamma + self.invGamma = invGamma + + +class HSLColor(Serialisable): + + tagname = "hslClr" + + hue = Integer() + sat = MinMax(min=0, max=100) + lum = MinMax(min=0, max=100) + + #TODO add color transform options + + def __init__(self, + hue=None, + sat=None, + lum=None, + ): + self.hue = hue + self.sat = sat + self.lum = lum + + + +class RGBPercent(Serialisable): + + tagname = "rgbClr" + + r = MinMax(min=0, max=100) + g = MinMax(min=0, max=100) + b = MinMax(min=0, max=100) + + #TODO add color transform options + + def __init__(self, + r=None, + g=None, + b=None, + ): + self.r = r + self.g = g + self.b = b + + +class SchemeColor(Serialisable): + + tagname = "schemeClr" + namespace = DRAWING_NS + + tint = NestedInteger(allow_none=True) + shade = NestedInteger(allow_none=True) + comp = EmptyTag(allow_none=True) + inv = NestedInteger(allow_none=True) + gray = NestedInteger(allow_none=True) + alpha = NestedInteger(allow_none=True) + alphaOff = NestedInteger(allow_none=True) + alphaMod = NestedInteger(allow_none=True) + hue = NestedInteger(allow_none=True) + hueOff = NestedInteger(allow_none=True) + hueMod = NestedInteger(allow_none=True) + sat = NestedInteger(allow_none=True) + satOff = NestedInteger(allow_none=True) + satMod = NestedInteger(allow_none=True) + lum = NestedInteger(allow_none=True) + lumOff = NestedInteger(allow_none=True) + lumMod = NestedInteger(allow_none=True) + red = NestedInteger(allow_none=True) + redOff = NestedInteger(allow_none=True) + redMod = NestedInteger(allow_none=True) + green = NestedInteger(allow_none=True) + greenOff = NestedInteger(allow_none=True) + greenMod = NestedInteger(allow_none=True) + blue = NestedInteger(allow_none=True) + blueOff = NestedInteger(allow_none=True) + blueMod = NestedInteger(allow_none=True) + gamma = EmptyTag(allow_none=True) + invGamma = EmptyTag(allow_none=True) + val = Set(values=(['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', + 'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', + 'dk1', 'lt1', 'dk2', 'lt2'])) + + __elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', 'alpha', + 'alphaOff', 'alphaMod', 'hue', 'hueOff', 'hueMod', 'sat', 'satOff', + 'satMod', 'lum', 'lumMod', 'lumOff', 'red', 'redOff', 'redMod', 'green', + 'greenOff', 'greenMod', 'blue', 'blueOff', 'blueMod', 'gamma', + 'invGamma') + + def __init__(self, + tint=None, + shade=None, + comp=None, + inv=None, + gray=None, + alpha=None, + alphaOff=None, + alphaMod=None, + hue=None, + hueOff=None, + hueMod=None, + sat=None, + satOff=None, + satMod=None, + lum=None, + lumOff=None, + lumMod=None, + red=None, + redOff=None, + redMod=None, + green=None, + greenOff=None, + greenMod=None, + blue=None, + blueOff=None, + blueMod=None, + gamma=None, + invGamma=None, + val=None, + ): + self.tint = tint + self.shade = shade + self.comp = comp + self.inv = inv + self.gray = gray + self.alpha = alpha + self.alphaOff = alphaOff + self.alphaMod = alphaMod + self.hue = hue + self.hueOff = hueOff + self.hueMod = hueMod + self.sat = sat + self.satOff = satOff + self.satMod = satMod + self.lum = lum + self.lumOff = lumOff + self.lumMod = lumMod + self.red = red + self.redOff = redOff + self.redMod = redMod + self.green = green + self.greenOff = greenOff + self.greenMod = greenMod + self.blue = blue + self.blueOff = blueOff + self.blueMod = blueMod + self.gamma = gamma + self.invGamma = invGamma + self.val = val + +class ColorChoice(Serialisable): + + tagname = "colorChoice" + namespace = DRAWING_NS + + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + +_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3', + 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink') + + +class ColorMapping(Serialisable): + + tagname = "clrMapOvr" + + bg1 = Set(values=_COLOR_SET) + tx1 = Set(values=_COLOR_SET) + bg2 = Set(values=_COLOR_SET) + tx2 = Set(values=_COLOR_SET) + accent1 = Set(values=_COLOR_SET) + accent2 = Set(values=_COLOR_SET) + accent3 = Set(values=_COLOR_SET) + accent4 = Set(values=_COLOR_SET) + accent5 = Set(values=_COLOR_SET) + accent6 = Set(values=_COLOR_SET) + hlink = Set(values=_COLOR_SET) + folHlink = Set(values=_COLOR_SET) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + bg1="lt1", + tx1="dk1", + bg2="lt2", + tx2="dk2", + accent1="accent1", + accent2="accent2", + accent3="accent3", + accent4="accent4", + accent5="accent5", + accent6="accent6", + hlink="hlink", + folHlink="folHlink", + extLst=None, + ): + self.bg1 = bg1 + self.tx1 = tx1 + self.bg2 = bg2 + self.tx2 = tx2 + self.accent1 = accent1 + self.accent2 = accent2 + self.accent3 = accent3 + self.accent4 = accent4 + self.accent5 = accent5 + self.accent6 = accent6 + self.hlink = hlink + self.folHlink = folHlink + self.extLst = extLst + + +class ColorChoiceDescriptor(Typed): + """ + Objects can choose from 7 different kinds of color system. + Assume RGBHex if a string is passed in. + """ + + expected_type = ColorChoice + allow_none = True + + def __set__(self, instance, value): + if isinstance(value, str): + value = ColorChoice(srgbClr=value) + else: + if hasattr(self, "namespace") and value is not None: + value.namespace = self.namespace + super().__set__(instance, value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py new file mode 100644 index 0000000..d25bcf7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/connector.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.chart.shapes import GraphicalProperties +from openpyxl.chart.text import RichText + +from .properties import ( + NonVisualDrawingProps, + NonVisualDrawingShapeProps, +) +from .geometry import ShapeStyle + +class Connection(Serialisable): + + id = Integer() + idx = Integer() + + def __init__(self, + id=None, + idx=None, + ): + self.id = id + self.idx = idx + + +class ConnectorLocking(Serialisable): + + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + extLst=None, + ): + self.extLst = extLst + + +class NonVisualConnectorProperties(Serialisable): + + cxnSpLocks = Typed(expected_type=ConnectorLocking, allow_none=True) + stCxn = Typed(expected_type=Connection, allow_none=True) + endCxn = Typed(expected_type=Connection, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + cxnSpLocks=None, + stCxn=None, + endCxn=None, + extLst=None, + ): + self.cxnSpLocks = cxnSpLocks + self.stCxn = stCxn + self.endCxn = endCxn + self.extLst = extLst + + +class ConnectorNonVisual(Serialisable): + + cNvPr = Typed(expected_type=NonVisualDrawingProps, ) + cNvCxnSpPr = Typed(expected_type=NonVisualConnectorProperties, ) + + __elements__ = ("cNvPr", "cNvCxnSpPr",) + + def __init__(self, + cNvPr=None, + cNvCxnSpPr=None, + ): + self.cNvPr = cNvPr + self.cNvCxnSpPr = cNvCxnSpPr + + +class ConnectorShape(Serialisable): + + tagname = "cxnSp" + + nvCxnSpPr = Typed(expected_type=ConnectorNonVisual) + spPr = Typed(expected_type=GraphicalProperties) + style = Typed(expected_type=ShapeStyle, allow_none=True) + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + + def __init__(self, + nvCxnSpPr=None, + spPr=None, + style=None, + macro=None, + fPublished=None, + ): + self.nvCxnSpPr = nvCxnSpPr + self.spPr = spPr + self.style = style + self.macro = macro + self.fPublished = fPublished + + +class ShapeMeta(Serialisable): + + tagname = "nvSpPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvSpPr = Typed(expected_type=NonVisualDrawingShapeProps) + + def __init__(self, cNvPr=None, cNvSpPr=None): + self.cNvPr = cNvPr + self.cNvSpPr = cNvSpPr + + +class Shape(Serialisable): + + macro = String(allow_none=True) + textlink = String(allow_none=True) + fPublished = Bool(allow_none=True) + fLocksText = Bool(allow_none=True) + nvSpPr = Typed(expected_type=ShapeMeta, allow_none=True) + meta = Alias("nvSpPr") + spPr = Typed(expected_type=GraphicalProperties) + graphicalProperties = Alias("spPr") + style = Typed(expected_type=ShapeStyle, allow_none=True) + txBody = Typed(expected_type=RichText, allow_none=True) + + def __init__(self, + macro=None, + textlink=None, + fPublished=None, + fLocksText=None, + nvSpPr=None, + spPr=None, + style=None, + txBody=None, + ): + self.macro = macro + self.textlink = textlink + self.fPublished = fPublished + self.fLocksText = fLocksText + self.nvSpPr = nvSpPr + self.spPr = spPr + self.style = style + self.txBody = txBody diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py new file mode 100644 index 0000000..45acdfe --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/drawing.py @@ -0,0 +1,92 @@ + +# Copyright (c) 2010-2024 openpyxl + +import math + +from openpyxl.utils.units import pixels_to_EMU + + +class Drawing: + """ a drawing object - eg container for shapes or charts + we assume user specifies dimensions in pixels; units are + converted to EMU in the drawing part + """ + + count = 0 + + def __init__(self): + + self.name = '' + self.description = '' + self.coordinates = ((1, 2), (16, 8)) + self.left = 0 + self.top = 0 + self._width = 21 # default in px + self._height = 192 #default in px + self.resize_proportional = False + self.rotation = 0 + self.anchortype = "absolute" + self.anchorcol = 0 # left cell + self.anchorrow = 0 # top row + + + @property + def width(self): + return self._width + + + @width.setter + def width(self, w): + if self.resize_proportional and w: + ratio = self._height / self._width + self._height = round(ratio * w) + self._width = w + + + @property + def height(self): + return self._height + + + @height.setter + def height(self, h): + if self.resize_proportional and h: + ratio = self._width / self._height + self._width = round(ratio * h) + self._height = h + + + def set_dimension(self, w=0, h=0): + + xratio = w / self._width + yratio = h / self._height + + if self.resize_proportional and w and h: + if (xratio * self._height) < h: + self._height = math.ceil(xratio * self._height) + self._width = w + else: + self._width = math.ceil(yratio * self._width) + self._height = h + + + @property + def anchor(self): + from .spreadsheet_drawing import ( + OneCellAnchor, + TwoCellAnchor, + AbsoluteAnchor) + if self.anchortype == "absolute": + anchor = AbsoluteAnchor() + anchor.pos.x = pixels_to_EMU(self.left) + anchor.pos.y = pixels_to_EMU(self.top) + + elif self.anchortype == "oneCell": + anchor = OneCellAnchor() + anchor._from.col = self.anchorcol + anchor._from.row = self.anchorrow + + anchor.ext.width = pixels_to_EMU(self._width) + anchor.ext.height = pixels_to_EMU(self._height) + + return anchor diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py new file mode 100644 index 0000000..9edae34 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/effect.py @@ -0,0 +1,407 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Set, + Bool, + Integer, + Float, +) + +from .colors import ColorChoice + + +class TintEffect(Serialisable): + + tagname = "tint" + + hue = Integer() + amt = Integer() + + def __init__(self, + hue=0, + amt=0, + ): + self.hue = hue + self.amt = amt + + +class LuminanceEffect(Serialisable): + + tagname = "lum" + + bright = Integer() #Pct ? + contrast = Integer() #Pct# + + def __init__(self, + bright=0, + contrast=0, + ): + self.bright = bright + self.contrast = contrast + + +class HSLEffect(Serialisable): + + hue = Integer() + sat = Integer() + lum = Integer() + + def __init__(self, + hue=None, + sat=None, + lum=None, + ): + self.hue = hue + self.sat = sat + self.lum = lum + + +class GrayscaleEffect(Serialisable): + + tagname = "grayscl" + + +class FillOverlayEffect(Serialisable): + + blend = Set(values=(['over', 'mult', 'screen', 'darken', 'lighten'])) + + def __init__(self, + blend=None, + ): + self.blend = blend + + +class DuotoneEffect(Serialisable): + + pass + +class ColorReplaceEffect(Serialisable): + + pass + +class Color(Serialisable): + + pass + +class ColorChangeEffect(Serialisable): + + useA = Bool(allow_none=True) + clrFrom = Typed(expected_type=Color, ) + clrTo = Typed(expected_type=Color, ) + + def __init__(self, + useA=None, + clrFrom=None, + clrTo=None, + ): + self.useA = useA + self.clrFrom = clrFrom + self.clrTo = clrTo + + +class BlurEffect(Serialisable): + + rad = Float() + grow = Bool(allow_none=True) + + def __init__(self, + rad=None, + grow=None, + ): + self.rad = rad + self.grow = grow + + +class BiLevelEffect(Serialisable): + + thresh = Integer() + + def __init__(self, + thresh=None, + ): + self.thresh = thresh + + +class AlphaReplaceEffect(Serialisable): + + a = Integer() + + def __init__(self, + a=None, + ): + self.a = a + + +class AlphaModulateFixedEffect(Serialisable): + + amt = Integer() + + def __init__(self, + amt=None, + ): + self.amt = amt + + +class EffectContainer(Serialisable): + + type = Set(values=(['sib', 'tree'])) + name = String(allow_none=True) + + def __init__(self, + type=None, + name=None, + ): + self.type = type + self.name = name + + +class AlphaModulateEffect(Serialisable): + + cont = Typed(expected_type=EffectContainer, ) + + def __init__(self, + cont=None, + ): + self.cont = cont + + +class AlphaInverseEffect(Serialisable): + + pass + +class AlphaFloorEffect(Serialisable): + + pass + +class AlphaCeilingEffect(Serialisable): + + pass + +class AlphaBiLevelEffect(Serialisable): + + thresh = Integer() + + def __init__(self, + thresh=None, + ): + self.thresh = thresh + + +class GlowEffect(ColorChoice): + + rad = Float() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + rad=None, + **kw + ): + self.rad = rad + super().__init__(**kw) + + +class InnerShadowEffect(ColorChoice): + + blurRad = Float() + dist = Float() + dir = Integer() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + blurRad=None, + dist=None, + dir=None, + **kw + ): + self.blurRad = blurRad + self.dist = dist + self.dir = dir + super().__init__(**kw) + + +class OuterShadow(ColorChoice): + + tagname = "outerShdw" + + blurRad = Float(allow_none=True) + dist = Float(allow_none=True) + dir = Integer(allow_none=True) + sx = Integer(allow_none=True) + sy = Integer(allow_none=True) + kx = Integer(allow_none=True) + ky = Integer(allow_none=True) + algn = Set(values=['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']) + rotWithShape = Bool(allow_none=True) + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + blurRad=None, + dist=None, + dir=None, + sx=None, + sy=None, + kx=None, + ky=None, + algn=None, + rotWithShape=None, + **kw + ): + self.blurRad = blurRad + self.dist = dist + self.dir = dir + self.sx = sx + self.sy = sy + self.kx = kx + self.ky = ky + self.algn = algn + self.rotWithShape = rotWithShape + super().__init__(**kw) + + +class PresetShadowEffect(ColorChoice): + + prst = Set(values=(['shdw1', 'shdw2', 'shdw3', 'shdw4', 'shdw5', 'shdw6', + 'shdw7', 'shdw8', 'shdw9', 'shdw10', 'shdw11', 'shdw12', 'shdw13', + 'shdw14', 'shdw15', 'shdw16', 'shdw17', 'shdw18', 'shdw19', 'shdw20'])) + dist = Float() + dir = Integer() + # uses element group EG_ColorChoice + scrgbClr = ColorChoice.scrgbClr + srgbClr = ColorChoice.srgbClr + hslClr = ColorChoice.hslClr + sysClr = ColorChoice.sysClr + schemeClr = ColorChoice.schemeClr + prstClr = ColorChoice.prstClr + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + prst=None, + dist=None, + dir=None, + **kw + ): + self.prst = prst + self.dist = dist + self.dir = dir + super().__init__(**kw) + + +class ReflectionEffect(Serialisable): + + blurRad = Float() + stA = Integer() + stPos = Integer() + endA = Integer() + endPos = Integer() + dist = Float() + dir = Integer() + fadeDir = Integer() + sx = Integer() + sy = Integer() + kx = Integer() + ky = Integer() + algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])) + rotWithShape = Bool(allow_none=True) + + def __init__(self, + blurRad=None, + stA=None, + stPos=None, + endA=None, + endPos=None, + dist=None, + dir=None, + fadeDir=None, + sx=None, + sy=None, + kx=None, + ky=None, + algn=None, + rotWithShape=None, + ): + self.blurRad = blurRad + self.stA = stA + self.stPos = stPos + self.endA = endA + self.endPos = endPos + self.dist = dist + self.dir = dir + self.fadeDir = fadeDir + self.sx = sx + self.sy = sy + self.kx = kx + self.ky = ky + self.algn = algn + self.rotWithShape = rotWithShape + + +class SoftEdgesEffect(Serialisable): + + rad = Float() + + def __init__(self, + rad=None, + ): + self.rad = rad + + +class EffectList(Serialisable): + + blur = Typed(expected_type=BlurEffect, allow_none=True) + fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True) + glow = Typed(expected_type=GlowEffect, allow_none=True) + innerShdw = Typed(expected_type=InnerShadowEffect, allow_none=True) + outerShdw = Typed(expected_type=OuterShadow, allow_none=True) + prstShdw = Typed(expected_type=PresetShadowEffect, allow_none=True) + reflection = Typed(expected_type=ReflectionEffect, allow_none=True) + softEdge = Typed(expected_type=SoftEdgesEffect, allow_none=True) + + __elements__ = ('blur', 'fillOverlay', 'glow', 'innerShdw', 'outerShdw', + 'prstShdw', 'reflection', 'softEdge') + + def __init__(self, + blur=None, + fillOverlay=None, + glow=None, + innerShdw=None, + outerShdw=None, + prstShdw=None, + reflection=None, + softEdge=None, + ): + self.blur = blur + self.fillOverlay = fillOverlay + self.glow = glow + self.innerShdw = innerShdw + self.outerShdw = outerShdw + self.prstShdw = prstShdw + self.reflection = reflection + self.softEdge = softEdge diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py new file mode 100644 index 0000000..580e0db --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/fill.py @@ -0,0 +1,425 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Bool, + Integer, + Set, + NoneSet, + Typed, + MinMax, +) +from openpyxl.descriptors.excel import ( + Relation, + Percentage, +) +from openpyxl.descriptors.nested import NestedNoneSet, NestedValue +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.xml.constants import DRAWING_NS + +from .colors import ( + ColorChoice, + HSLColor, + SystemColor, + SchemeColor, + PRESET_COLORS, + RGBPercent, +) + +from .effect import ( + AlphaBiLevelEffect, + AlphaCeilingEffect, + AlphaFloorEffect, + AlphaInverseEffect, + AlphaModulateEffect, + AlphaModulateFixedEffect, + AlphaReplaceEffect, + BiLevelEffect, + BlurEffect, + ColorChangeEffect, + ColorReplaceEffect, + DuotoneEffect, + FillOverlayEffect, + GrayscaleEffect, + HSLEffect, + LuminanceEffect, + TintEffect, +) + +""" +Fill elements from drawing main schema +""" + +class PatternFillProperties(Serialisable): + + tagname = "pattFill" + namespace = DRAWING_NS + + prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30', + 'pct40', 'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 'horz', + 'vert', 'ltHorz', 'ltVert', 'dkHorz', 'dkVert', 'narHorz', 'narVert', + 'dashHorz', 'dashVert', 'cross', 'dnDiag', 'upDiag', 'ltDnDiag', + 'ltUpDiag', 'dkDnDiag', 'dkUpDiag', 'wdDnDiag', 'wdUpDiag', 'dashDnDiag', + 'dashUpDiag', 'diagCross', 'smCheck', 'lgCheck', 'smGrid', 'lgGrid', + 'dotGrid', 'smConfetti', 'lgConfetti', 'horzBrick', 'diagBrick', + 'solidDmnd', 'openDmnd', 'dotDmnd', 'plaid', 'sphere', 'weave', 'divot', + 'shingle', 'wave', 'trellis', 'zigZag'])) + preset = Alias("prst") + fgClr = Typed(expected_type=ColorChoice, allow_none=True) + foreground = Alias("fgClr") + bgClr = Typed(expected_type=ColorChoice, allow_none=True) + background = Alias("bgClr") + + __elements__ = ("fgClr", "bgClr") + + def __init__(self, + prst=None, + fgClr=None, + bgClr=None, + ): + self.prst = prst + self.fgClr = fgClr + self.bgClr = bgClr + + +class RelativeRect(Serialisable): + + tagname = "rect" + namespace = DRAWING_NS + + l = Percentage(allow_none=True) + left = Alias('l') + t = Percentage(allow_none=True) + top = Alias('t') + r = Percentage(allow_none=True) + right = Alias('r') + b = Percentage(allow_none=True) + bottom = Alias('b') + + def __init__(self, + l=None, + t=None, + r=None, + b=None, + ): + self.l = l + self.t = t + self.r = r + self.b = b + + +class StretchInfoProperties(Serialisable): + + tagname = "stretch" + namespace = DRAWING_NS + + fillRect = Typed(expected_type=RelativeRect, allow_none=True) + + def __init__(self, + fillRect=RelativeRect(), + ): + self.fillRect = fillRect + + +class GradientStop(Serialisable): + + tagname = "gs" + namespace = DRAWING_NS + + pos = MinMax(min=0, max=100000, allow_none=True) + # Color Choice Group + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + pos=None, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + if pos is None: + pos = 0 + self.pos = pos + + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + + +class LinearShadeProperties(Serialisable): + + tagname = "lin" + namespace = DRAWING_NS + + ang = Integer() + scaled = Bool(allow_none=True) + + def __init__(self, + ang=None, + scaled=None, + ): + self.ang = ang + self.scaled = scaled + + +class PathShadeProperties(Serialisable): + + tagname = "path" + namespace = DRAWING_NS + + path = Set(values=(['shape', 'circle', 'rect'])) + fillToRect = Typed(expected_type=RelativeRect, allow_none=True) + + def __init__(self, + path=None, + fillToRect=None, + ): + self.path = path + self.fillToRect = fillToRect + + +class GradientFillProperties(Serialisable): + + tagname = "gradFill" + namespace = DRAWING_NS + + flip = NoneSet(values=(['x', 'y', 'xy'])) + rotWithShape = Bool(allow_none=True) + + gsLst = NestedSequence(expected_type=GradientStop, count=False) + stop_list = Alias("gsLst") + + lin = Typed(expected_type=LinearShadeProperties, allow_none=True) + linear = Alias("lin") + path = Typed(expected_type=PathShadeProperties, allow_none=True) + + tileRect = Typed(expected_type=RelativeRect, allow_none=True) + + __elements__ = ('gsLst', 'lin', 'path', 'tileRect') + + def __init__(self, + flip=None, + rotWithShape=None, + gsLst=(), + lin=None, + path=None, + tileRect=None, + ): + self.flip = flip + self.rotWithShape = rotWithShape + self.gsLst = gsLst + self.lin = lin + self.path = path + self.tileRect = tileRect + + +class SolidColorFillProperties(Serialisable): + + tagname = "solidFill" + + # uses element group EG_ColorChoice + scrgbClr = Typed(expected_type=RGBPercent, allow_none=True) + RGBPercent = Alias('scrgbClr') + srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform + RGB = Alias('srgbClr') + hslClr = Typed(expected_type=HSLColor, allow_none=True) + sysClr = Typed(expected_type=SystemColor, allow_none=True) + schemeClr = Typed(expected_type=SchemeColor, allow_none=True) + prstClr = NestedNoneSet(values=PRESET_COLORS) + + __elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr') + + def __init__(self, + scrgbClr=None, + srgbClr=None, + hslClr=None, + sysClr=None, + schemeClr=None, + prstClr=None, + ): + self.scrgbClr = scrgbClr + self.srgbClr = srgbClr + self.hslClr = hslClr + self.sysClr = sysClr + self.schemeClr = schemeClr + self.prstClr = prstClr + + +class Blip(Serialisable): + + tagname = "blip" + namespace = DRAWING_NS + + # Using attribute groupAG_Blob + cstate = NoneSet(values=(['email', 'screen', 'print', 'hqprint'])) + embed = Relation() # rId + link = Relation() # hyperlink + noGrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + # some elements are choice + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + alphaBiLevel = Typed(expected_type=AlphaBiLevelEffect, allow_none=True) + alphaCeiling = Typed(expected_type=AlphaCeilingEffect, allow_none=True) + alphaFloor = Typed(expected_type=AlphaFloorEffect, allow_none=True) + alphaInv = Typed(expected_type=AlphaInverseEffect, allow_none=True) + alphaMod = Typed(expected_type=AlphaModulateEffect, allow_none=True) + alphaModFix = Typed(expected_type=AlphaModulateFixedEffect, allow_none=True) + alphaRepl = Typed(expected_type=AlphaReplaceEffect, allow_none=True) + biLevel = Typed(expected_type=BiLevelEffect, allow_none=True) + blur = Typed(expected_type=BlurEffect, allow_none=True) + clrChange = Typed(expected_type=ColorChangeEffect, allow_none=True) + clrRepl = Typed(expected_type=ColorReplaceEffect, allow_none=True) + duotone = Typed(expected_type=DuotoneEffect, allow_none=True) + fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True) + grayscl = Typed(expected_type=GrayscaleEffect, allow_none=True) + hsl = Typed(expected_type=HSLEffect, allow_none=True) + lum = Typed(expected_type=LuminanceEffect, allow_none=True) + tint = Typed(expected_type=TintEffect, allow_none=True) + + __elements__ = ('alphaBiLevel', 'alphaCeiling', 'alphaFloor', 'alphaInv', + 'alphaMod', 'alphaModFix', 'alphaRepl', 'biLevel', 'blur', 'clrChange', + 'clrRepl', 'duotone', 'fillOverlay', 'grayscl', 'hsl', 'lum', 'tint') + + def __init__(self, + cstate=None, + embed=None, + link=None, + noGrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeArrowheads=None, + noChangeShapeType=None, + extLst=None, + alphaBiLevel=None, + alphaCeiling=None, + alphaFloor=None, + alphaInv=None, + alphaMod=None, + alphaModFix=None, + alphaRepl=None, + biLevel=None, + blur=None, + clrChange=None, + clrRepl=None, + duotone=None, + fillOverlay=None, + grayscl=None, + hsl=None, + lum=None, + tint=None, + ): + self.cstate = cstate + self.embed = embed + self.link = link + self.noGrp = noGrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeArrowheads = noChangeArrowheads + self.noChangeShapeType = noChangeShapeType + self.extLst = extLst + self.alphaBiLevel = alphaBiLevel + self.alphaCeiling = alphaCeiling + self.alphaFloor = alphaFloor + self.alphaInv = alphaInv + self.alphaMod = alphaMod + self.alphaModFix = alphaModFix + self.alphaRepl = alphaRepl + self.biLevel = biLevel + self.blur = blur + self.clrChange = clrChange + self.clrRepl = clrRepl + self.duotone = duotone + self.fillOverlay = fillOverlay + self.grayscl = grayscl + self.hsl = hsl + self.lum = lum + self.tint = tint + + +class TileInfoProperties(Serialisable): + + tx = Integer(allow_none=True) + ty = Integer(allow_none=True) + sx = Integer(allow_none=True) + sy = Integer(allow_none=True) + flip = NoneSet(values=(['x', 'y', 'xy'])) + algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])) + + def __init__(self, + tx=None, + ty=None, + sx=None, + sy=None, + flip=None, + algn=None, + ): + self.tx = tx + self.ty = ty + self.sx = sx + self.sy = sy + self.flip = flip + self.algn = algn + + +class BlipFillProperties(Serialisable): + + tagname = "blipFill" + + dpi = Integer(allow_none=True) + rotWithShape = Bool(allow_none=True) + + blip = Typed(expected_type=Blip, allow_none=True) + srcRect = Typed(expected_type=RelativeRect, allow_none=True) + tile = Typed(expected_type=TileInfoProperties, allow_none=True) + stretch = Typed(expected_type=StretchInfoProperties, allow_none=True) + + __elements__ = ("blip", "srcRect", "tile", "stretch") + + def __init__(self, + dpi=None, + rotWithShape=None, + blip=None, + tile=None, + stretch=StretchInfoProperties(), + srcRect=None, + ): + self.dpi = dpi + self.rotWithShape = rotWithShape + self.blip = blip + self.tile = tile + self.stretch = stretch + self.srcRect = srcRect diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py new file mode 100644 index 0000000..2cc7ca6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/geometry.py @@ -0,0 +1,584 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Integer, + Bool, + MinMax, + Set, + NoneSet, + String, + Alias, +) +from openpyxl.descriptors.excel import Coordinate, Percentage +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from .line import LineProperties + +from openpyxl.styles.colors import Color +from openpyxl.xml.constants import DRAWING_NS + + +class Point2D(Serialisable): + + tagname = "off" + namespace = DRAWING_NS + + x = Coordinate() + y = Coordinate() + + def __init__(self, + x=None, + y=None, + ): + self.x = x + self.y = y + + +class PositiveSize2D(Serialisable): + + tagname = "ext" + namespace = DRAWING_NS + + """ + Dimensions in EMUs + """ + + cx = Integer() + width = Alias('cx') + cy = Integer() + height = Alias('cy') + + def __init__(self, + cx=None, + cy=None, + ): + self.cx = cx + self.cy = cy + + +class Transform2D(Serialisable): + + tagname = "xfrm" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + flipH = Bool(allow_none=True) + flipV = Bool(allow_none=True) + off = Typed(expected_type=Point2D, allow_none=True) + ext = Typed(expected_type=PositiveSize2D, allow_none=True) + chOff = Typed(expected_type=Point2D, allow_none=True) + chExt = Typed(expected_type=PositiveSize2D, allow_none=True) + + __elements__ = ('off', 'ext', 'chOff', 'chExt') + + def __init__(self, + rot=None, + flipH=None, + flipV=None, + off=None, + ext=None, + chOff=None, + chExt=None, + ): + self.rot = rot + self.flipH = flipH + self.flipV = flipV + self.off = off + self.ext = ext + self.chOff = chOff + self.chExt = chExt + + +class GroupTransform2D(Serialisable): + + tagname = "xfrm" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + flipH = Bool(allow_none=True) + flipV = Bool(allow_none=True) + off = Typed(expected_type=Point2D, allow_none=True) + ext = Typed(expected_type=PositiveSize2D, allow_none=True) + chOff = Typed(expected_type=Point2D, allow_none=True) + chExt = Typed(expected_type=PositiveSize2D, allow_none=True) + + __elements__ = ("off", "ext", "chOff", "chExt") + + def __init__(self, + rot=0, + flipH=None, + flipV=None, + off=None, + ext=None, + chOff=None, + chExt=None, + ): + self.rot = rot + self.flipH = flipH + self.flipV = flipV + self.off = off + self.ext = ext + self.chOff = chOff + self.chExt = chExt + + +class SphereCoords(Serialisable): + + tagname = "sphereCoords" # usually + + lat = Integer() + lon = Integer() + rev = Integer() + + def __init__(self, + lat=None, + lon=None, + rev=None, + ): + self.lat = lat + self.lon = lon + self.rev = rev + + +class Camera(Serialisable): + + tagname = "camera" + + prst = Set(values=[ + 'legacyObliqueTopLeft', 'legacyObliqueTop', 'legacyObliqueTopRight', 'legacyObliqueLeft', + 'legacyObliqueFront', 'legacyObliqueRight', 'legacyObliqueBottomLeft', + 'legacyObliqueBottom', 'legacyObliqueBottomRight', 'legacyPerspectiveTopLeft', + 'legacyPerspectiveTop', 'legacyPerspectiveTopRight', 'legacyPerspectiveLeft', + 'legacyPerspectiveFront', 'legacyPerspectiveRight', 'legacyPerspectiveBottomLeft', + 'legacyPerspectiveBottom', 'legacyPerspectiveBottomRight', 'orthographicFront', + 'isometricTopUp', 'isometricTopDown', 'isometricBottomUp', 'isometricBottomDown', + 'isometricLeftUp', 'isometricLeftDown', 'isometricRightUp', 'isometricRightDown', + 'isometricOffAxis1Left', 'isometricOffAxis1Right', 'isometricOffAxis1Top', + 'isometricOffAxis2Left', 'isometricOffAxis2Right', 'isometricOffAxis2Top', + 'isometricOffAxis3Left', 'isometricOffAxis3Right', 'isometricOffAxis3Bottom', + 'isometricOffAxis4Left', 'isometricOffAxis4Right', 'isometricOffAxis4Bottom', + 'obliqueTopLeft', 'obliqueTop', 'obliqueTopRight', 'obliqueLeft', 'obliqueRight', + 'obliqueBottomLeft', 'obliqueBottom', 'obliqueBottomRight', 'perspectiveFront', + 'perspectiveLeft', 'perspectiveRight', 'perspectiveAbove', 'perspectiveBelow', + 'perspectiveAboveLeftFacing', 'perspectiveAboveRightFacing', + 'perspectiveContrastingLeftFacing', 'perspectiveContrastingRightFacing', + 'perspectiveHeroicLeftFacing', 'perspectiveHeroicRightFacing', + 'perspectiveHeroicExtremeLeftFacing', 'perspectiveHeroicExtremeRightFacing', + 'perspectiveRelaxed', 'perspectiveRelaxedModerately']) + fov = Integer(allow_none=True) + zoom = Typed(expected_type=Percentage, allow_none=True) + rot = Typed(expected_type=SphereCoords, allow_none=True) + + + def __init__(self, + prst=None, + fov=None, + zoom=None, + rot=None, + ): + self.prst = prst + self.fov = fov + self.zoom = zoom + self.rot = rot + + +class LightRig(Serialisable): + + tagname = "lightRig" + + rig = Set(values=['legacyFlat1', 'legacyFlat2', 'legacyFlat3', 'legacyFlat4', 'legacyNormal1', + 'legacyNormal2', 'legacyNormal3', 'legacyNormal4', 'legacyHarsh1', + 'legacyHarsh2', 'legacyHarsh3', 'legacyHarsh4', 'threePt', 'balanced', + 'soft', 'harsh', 'flood', 'contrasting', 'morning', 'sunrise', 'sunset', + 'chilly', 'freezing', 'flat', 'twoPt', 'glow', 'brightRoom'] + ) + dir = Set(values=(['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br'])) + rot = Typed(expected_type=SphereCoords, allow_none=True) + + def __init__(self, + rig=None, + dir=None, + rot=None, + ): + self.rig = rig + self.dir = dir + self.rot = rot + + +class Vector3D(Serialisable): + + tagname = "vector" + + dx = Integer() # can be in or universl measure :-/ + dy = Integer() + dz = Integer() + + def __init__(self, + dx=None, + dy=None, + dz=None, + ): + self.dx = dx + self.dy = dy + self.dz = dz + + +class Point3D(Serialisable): + + tagname = "anchor" + + x = Integer() + y = Integer() + z = Integer() + + def __init__(self, + x=None, + y=None, + z=None, + ): + self.x = x + self.y = y + self.z = z + + +class Backdrop(Serialisable): + + anchor = Typed(expected_type=Point3D, ) + norm = Typed(expected_type=Vector3D, ) + up = Typed(expected_type=Vector3D, ) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + anchor=None, + norm=None, + up=None, + extLst=None, + ): + self.anchor = anchor + self.norm = norm + self.up = up + self.extLst = extLst + + +class Scene3D(Serialisable): + + camera = Typed(expected_type=Camera, ) + lightRig = Typed(expected_type=LightRig, ) + backdrop = Typed(expected_type=Backdrop, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + camera=None, + lightRig=None, + backdrop=None, + extLst=None, + ): + self.camera = camera + self.lightRig = lightRig + self.backdrop = backdrop + self.extLst = extLst + + +class Bevel(Serialisable): + + tagname = "bevel" + + w = Integer() + h = Integer() + prst = NoneSet(values= + ['relaxedInset', 'circle', 'slope', 'cross', 'angle', + 'softRound', 'convex', 'coolSlant', 'divot', 'riblet', + 'hardEdge', 'artDeco'] + ) + + def __init__(self, + w=None, + h=None, + prst=None, + ): + self.w = w + self.h = h + self.prst = prst + + +class Shape3D(Serialisable): + + namespace = DRAWING_NS + + z = Typed(expected_type=Coordinate, allow_none=True) + extrusionH = Integer(allow_none=True) + contourW = Integer(allow_none=True) + prstMaterial = NoneSet(values=[ + 'legacyMatte','legacyPlastic', 'legacyMetal', 'legacyWireframe', 'matte', 'plastic', + 'metal', 'warmMatte', 'translucentPowder', 'powder', 'dkEdge', + 'softEdge', 'clear', 'flat', 'softmetal'] + ) + bevelT = Typed(expected_type=Bevel, allow_none=True) + bevelB = Typed(expected_type=Bevel, allow_none=True) + extrusionClr = Typed(expected_type=Color, allow_none=True) + contourClr = Typed(expected_type=Color, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + z=None, + extrusionH=None, + contourW=None, + prstMaterial=None, + bevelT=None, + bevelB=None, + extrusionClr=None, + contourClr=None, + extLst=None, + ): + self.z = z + self.extrusionH = extrusionH + self.contourW = contourW + self.prstMaterial = prstMaterial + self.bevelT = bevelT + self.bevelB = bevelB + self.extrusionClr = extrusionClr + self.contourClr = contourClr + self.extLst = extLst + + +class Path2D(Serialisable): + + w = Float() + h = Float() + fill = NoneSet(values=(['norm', 'lighten', 'lightenLess', 'darken', 'darkenLess'])) + stroke = Bool(allow_none=True) + extrusionOk = Bool(allow_none=True) + + def __init__(self, + w=None, + h=None, + fill=None, + stroke=None, + extrusionOk=None, + ): + self.w = w + self.h = h + self.fill = fill + self.stroke = stroke + self.extrusionOk = extrusionOk + + +class Path2DList(Serialisable): + + path = Typed(expected_type=Path2D, allow_none=True) + + def __init__(self, + path=None, + ): + self.path = path + + +class GeomRect(Serialisable): + + l = Coordinate() + t = Coordinate() + r = Coordinate() + b = Coordinate() + + def __init__(self, + l=None, + t=None, + r=None, + b=None, + ): + self.l = l + self.t = t + self.r = r + self.b = b + + +class AdjPoint2D(Serialisable): + + x = Coordinate() + y = Coordinate() + + def __init__(self, + x=None, + y=None, + ): + self.x = x + self.y = y + + +class ConnectionSite(Serialisable): + + ang = MinMax(min=0, max=360) # guess work, can also be a name + pos = Typed(expected_type=AdjPoint2D, ) + + def __init__(self, + ang=None, + pos=None, + ): + self.ang = ang + self.pos = pos + + +class ConnectionSiteList(Serialisable): + + cxn = Typed(expected_type=ConnectionSite, allow_none=True) + + def __init__(self, + cxn=None, + ): + self.cxn = cxn + + +class AdjustHandleList(Serialisable): + + pass + +class GeomGuide(Serialisable): + + name = String() + fmla = String() + + def __init__(self, + name=None, + fmla=None, + ): + self.name = name + self.fmla = fmla + + +class GeomGuideList(Serialisable): + + gd = Typed(expected_type=GeomGuide, allow_none=True) + + def __init__(self, + gd=None, + ): + self.gd = gd + + +class CustomGeometry2D(Serialisable): + + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + gdLst = Typed(expected_type=GeomGuideList, allow_none=True) + ahLst = Typed(expected_type=AdjustHandleList, allow_none=True) + cxnLst = Typed(expected_type=ConnectionSiteList, allow_none=True) + #rect = Typed(expected_type=GeomRect, allow_none=True) + pathLst = Typed(expected_type=Path2DList, ) + + def __init__(self, + avLst=None, + gdLst=None, + ahLst=None, + cxnLst=None, + rect=None, + pathLst=None, + ): + self.avLst = avLst + self.gdLst = gdLst + self.ahLst = ahLst + self.cxnLst = cxnLst + self.rect = None + self.pathLst = pathLst + + +class PresetGeometry2D(Serialisable): + + namespace = DRAWING_NS + + prst = Set(values=( + ['line', 'lineInv', 'triangle', 'rtTriangle', 'rect', + 'diamond', 'parallelogram', 'trapezoid', 'nonIsoscelesTrapezoid', + 'pentagon', 'hexagon', 'heptagon', 'octagon', 'decagon', 'dodecagon', + 'star4', 'star5', 'star6', 'star7', 'star8', 'star10', 'star12', + 'star16', 'star24', 'star32', 'roundRect', 'round1Rect', + 'round2SameRect', 'round2DiagRect', 'snipRoundRect', 'snip1Rect', + 'snip2SameRect', 'snip2DiagRect', 'plaque', 'ellipse', 'teardrop', + 'homePlate', 'chevron', 'pieWedge', 'pie', 'blockArc', 'donut', + 'noSmoking', 'rightArrow', 'leftArrow', 'upArrow', 'downArrow', + 'stripedRightArrow', 'notchedRightArrow', 'bentUpArrow', + 'leftRightArrow', 'upDownArrow', 'leftUpArrow', 'leftRightUpArrow', + 'quadArrow', 'leftArrowCallout', 'rightArrowCallout', 'upArrowCallout', + 'downArrowCallout', 'leftRightArrowCallout', 'upDownArrowCallout', + 'quadArrowCallout', 'bentArrow', 'uturnArrow', 'circularArrow', + 'leftCircularArrow', 'leftRightCircularArrow', 'curvedRightArrow', + 'curvedLeftArrow', 'curvedUpArrow', 'curvedDownArrow', 'swooshArrow', + 'cube', 'can', 'lightningBolt', 'heart', 'sun', 'moon', 'smileyFace', + 'irregularSeal1', 'irregularSeal2', 'foldedCorner', 'bevel', 'frame', + 'halfFrame', 'corner', 'diagStripe', 'chord', 'arc', 'leftBracket', + 'rightBracket', 'leftBrace', 'rightBrace', 'bracketPair', 'bracePair', + 'straightConnector1', 'bentConnector2', 'bentConnector3', + 'bentConnector4', 'bentConnector5', 'curvedConnector2', + 'curvedConnector3', 'curvedConnector4', 'curvedConnector5', 'callout1', + 'callout2', 'callout3', 'accentCallout1', 'accentCallout2', + 'accentCallout3', 'borderCallout1', 'borderCallout2', 'borderCallout3', + 'accentBorderCallout1', 'accentBorderCallout2', 'accentBorderCallout3', + 'wedgeRectCallout', 'wedgeRoundRectCallout', 'wedgeEllipseCallout', + 'cloudCallout', 'cloud', 'ribbon', 'ribbon2', 'ellipseRibbon', + 'ellipseRibbon2', 'leftRightRibbon', 'verticalScroll', + 'horizontalScroll', 'wave', 'doubleWave', 'plus', 'flowChartProcess', + 'flowChartDecision', 'flowChartInputOutput', + 'flowChartPredefinedProcess', 'flowChartInternalStorage', + 'flowChartDocument', 'flowChartMultidocument', 'flowChartTerminator', + 'flowChartPreparation', 'flowChartManualInput', + 'flowChartManualOperation', 'flowChartConnector', 'flowChartPunchedCard', + 'flowChartPunchedTape', 'flowChartSummingJunction', 'flowChartOr', + 'flowChartCollate', 'flowChartSort', 'flowChartExtract', + 'flowChartMerge', 'flowChartOfflineStorage', 'flowChartOnlineStorage', + 'flowChartMagneticTape', 'flowChartMagneticDisk', + 'flowChartMagneticDrum', 'flowChartDisplay', 'flowChartDelay', + 'flowChartAlternateProcess', 'flowChartOffpageConnector', + 'actionButtonBlank', 'actionButtonHome', 'actionButtonHelp', + 'actionButtonInformation', 'actionButtonForwardNext', + 'actionButtonBackPrevious', 'actionButtonEnd', 'actionButtonBeginning', + 'actionButtonReturn', 'actionButtonDocument', 'actionButtonSound', + 'actionButtonMovie', 'gear6', 'gear9', 'funnel', 'mathPlus', 'mathMinus', + 'mathMultiply', 'mathDivide', 'mathEqual', 'mathNotEqual', 'cornerTabs', + 'squareTabs', 'plaqueTabs', 'chartX', 'chartStar', 'chartPlus'])) + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + + def __init__(self, + prst=None, + avLst=None, + ): + self.prst = prst + self.avLst = avLst + + +class FontReference(Serialisable): + + idx = NoneSet(values=(['major', 'minor'])) + + def __init__(self, + idx=None, + ): + self.idx = idx + + +class StyleMatrixReference(Serialisable): + + idx = Integer() + + def __init__(self, + idx=None, + ): + self.idx = idx + + +class ShapeStyle(Serialisable): + + lnRef = Typed(expected_type=StyleMatrixReference, ) + fillRef = Typed(expected_type=StyleMatrixReference, ) + effectRef = Typed(expected_type=StyleMatrixReference, ) + fontRef = Typed(expected_type=FontReference, ) + + def __init__(self, + lnRef=None, + fillRef=None, + effectRef=None, + fontRef=None, + ): + self.lnRef = lnRef + self.fillRef = fillRef + self.effectRef = effectRef + self.fontRef = fontRef diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py new file mode 100644 index 0000000..2c34087 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/graphic.py @@ -0,0 +1,177 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import CHART_NS, DRAWING_NS +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from .effect import ( + EffectList, + EffectContainer, +) +from .fill import ( + Blip, + GradientFillProperties, + BlipFillProperties, +) +from .picture import PictureFrame +from .properties import ( + NonVisualDrawingProps, + NonVisualGroupShape, + GroupShapeProperties, +) +from .relation import ChartRelation +from .xdr import XDRTransform2D + + +class GraphicFrameLocking(Serialisable): + + noGrp = Bool(allow_none=True) + noDrilldown = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + noGrp=None, + noDrilldown=None, + noSelect=None, + noChangeAspect=None, + noMove=None, + noResize=None, + extLst=None, + ): + self.noGrp = noGrp + self.noDrilldown = noDrilldown + self.noSelect = noSelect + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.extLst = extLst + + +class NonVisualGraphicFrameProperties(Serialisable): + + tagname = "cNvGraphicFramePr" + + graphicFrameLocks = Typed(expected_type=GraphicFrameLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + graphicFrameLocks=None, + extLst=None, + ): + self.graphicFrameLocks = graphicFrameLocks + self.extLst = extLst + + +class NonVisualGraphicFrame(Serialisable): + + tagname = "nvGraphicFramePr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrameProperties) + + __elements__ = ('cNvPr', 'cNvGraphicFramePr') + + def __init__(self, + cNvPr=None, + cNvGraphicFramePr=None, + ): + if cNvPr is None: + cNvPr = NonVisualDrawingProps(id=0, name="Chart 0") + self.cNvPr = cNvPr + if cNvGraphicFramePr is None: + cNvGraphicFramePr = NonVisualGraphicFrameProperties() + self.cNvGraphicFramePr = cNvGraphicFramePr + + +class GraphicData(Serialisable): + + tagname = "graphicData" + namespace = DRAWING_NS + + uri = String() + chart = Typed(expected_type=ChartRelation, allow_none=True) + + + def __init__(self, + uri=CHART_NS, + chart=None, + ): + self.uri = uri + self.chart = chart + + +class GraphicObject(Serialisable): + + tagname = "graphic" + namespace = DRAWING_NS + + graphicData = Typed(expected_type=GraphicData) + + def __init__(self, + graphicData=None, + ): + if graphicData is None: + graphicData = GraphicData() + self.graphicData = graphicData + + +class GraphicFrame(Serialisable): + + tagname = "graphicFrame" + + nvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrame) + xfrm = Typed(expected_type=XDRTransform2D) + graphic = Typed(expected_type=GraphicObject) + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + + __elements__ = ('nvGraphicFramePr', 'xfrm', 'graphic', 'macro', 'fPublished') + + def __init__(self, + nvGraphicFramePr=None, + xfrm=None, + graphic=None, + macro=None, + fPublished=None, + ): + if nvGraphicFramePr is None: + nvGraphicFramePr = NonVisualGraphicFrame() + self.nvGraphicFramePr = nvGraphicFramePr + if xfrm is None: + xfrm = XDRTransform2D() + self.xfrm = xfrm + if graphic is None: + graphic = GraphicObject() + self.graphic = graphic + self.macro = macro + self.fPublished = fPublished + + +class GroupShape(Serialisable): + + nvGrpSpPr = Typed(expected_type=NonVisualGroupShape) + nonVisualProperties = Alias("nvGrpSpPr") + grpSpPr = Typed(expected_type=GroupShapeProperties) + visualProperties = Alias("grpSpPr") + pic = Typed(expected_type=PictureFrame, allow_none=True) + + __elements__ = ["nvGrpSpPr", "grpSpPr", "pic"] + + def __init__(self, + nvGrpSpPr=None, + grpSpPr=None, + pic=None, + ): + self.nvGrpSpPr = nvGrpSpPr + self.grpSpPr = grpSpPr + self.pic = pic diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/image.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/image.py new file mode 100644 index 0000000..9d0446f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/image.py @@ -0,0 +1,65 @@ +# Copyright (c) 2010-2024 openpyxl + +from io import BytesIO + +try: + from PIL import Image as PILImage +except ImportError: + PILImage = False + + +def _import_image(img): + if not PILImage: + raise ImportError('You must install Pillow to fetch image objects') + + if not isinstance(img, PILImage.Image): + img = PILImage.open(img) + + return img + + +class Image: + """Image in a spreadsheet""" + + _id = 1 + _path = "/xl/media/image{0}.{1}" + anchor = "A1" + + def __init__(self, img): + + self.ref = img + mark_to_close = isinstance(img, str) + image = _import_image(img) + self.width, self.height = image.size + + try: + self.format = image.format.lower() + except AttributeError: + self.format = "png" + if mark_to_close: + # PIL instances created for metadata should be closed. + image.close() + + + def _data(self): + """ + Return image data, convert to supported types if necessary + """ + img = _import_image(self.ref) + # don't convert these file formats + if self.format in ['gif', 'jpeg', 'png']: + img.fp.seek(0) + fp = img.fp + else: + fp = BytesIO() + img.save(fp, format="png") + fp.seek(0) + + data = fp.read() + fp.close() + return data + + + @property + def path(self): + return self._path.format(self._id, self.format) diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/line.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/line.py new file mode 100644 index 0000000..43388e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/line.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + MinMax, + NoneSet, + Alias, + Sequence +) + +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedNoneSet, + EmptyTag, +) +from openpyxl.xml.constants import DRAWING_NS + +from .colors import ColorChoiceDescriptor +from .fill import GradientFillProperties, PatternFillProperties +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +""" +Line elements from drawing main schema +""" + + +class LineEndProperties(Serialisable): + + tagname = "end" + namespace = DRAWING_NS + + type = NoneSet(values=(['none', 'triangle', 'stealth', 'diamond', 'oval', 'arrow'])) + w = NoneSet(values=(['sm', 'med', 'lg'])) + len = NoneSet(values=(['sm', 'med', 'lg'])) + + def __init__(self, + type=None, + w=None, + len=None, + ): + self.type = type + self.w = w + self.len = len + + +class DashStop(Serialisable): + + tagname = "ds" + namespace = DRAWING_NS + + d = Integer() + length = Alias('d') + sp = Integer() + space = Alias('sp') + + def __init__(self, + d=0, + sp=0, + ): + self.d = d + self.sp = sp + + +class DashStopList(Serialisable): + + ds = Sequence(expected_type=DashStop, allow_none=True) + + def __init__(self, + ds=None, + ): + self.ds = ds + + +class LineProperties(Serialisable): + + tagname = "ln" + namespace = DRAWING_NS + + w = MinMax(min=0, max=20116800, allow_none=True) # EMU + width = Alias('w') + cap = NoneSet(values=(['rnd', 'sq', 'flat'])) + cmpd = NoneSet(values=(['sng', 'dbl', 'thickThin', 'thinThick', 'tri'])) + algn = NoneSet(values=(['ctr', 'in'])) + + noFill = EmptyTag() + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + + prstDash = NestedNoneSet(values=(['solid', 'dot', 'dash', 'lgDash', 'dashDot', + 'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot', 'sysDashDot', + 'sysDashDotDot']), namespace=namespace) + dashStyle = Alias('prstDash') + + custDash = Typed(expected_type=DashStop, allow_none=True) + + round = EmptyTag() + bevel = EmptyTag() + miter = NestedInteger(allow_none=True, attribute="lim") + + headEnd = Typed(expected_type=LineEndProperties, allow_none=True) + tailEnd = Typed(expected_type=LineEndProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ('noFill', 'solidFill', 'gradFill', 'pattFill', + 'prstDash', 'custDash', 'round', 'bevel', 'miter', 'headEnd', 'tailEnd') + + def __init__(self, + w=None, + cap=None, + cmpd=None, + algn=None, + noFill=None, + solidFill=None, + gradFill=None, + pattFill=None, + prstDash=None, + custDash=None, + round=None, + bevel=None, + miter=None, + headEnd=None, + tailEnd=None, + extLst=None, + ): + self.w = w + self.cap = cap + self.cmpd = cmpd + self.algn = algn + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.pattFill = pattFill + if prstDash is None: + prstDash = "solid" + self.prstDash = prstDash + self.custDash = custDash + self.round = round + self.bevel = bevel + self.miter = miter + self.headEnd = headEnd + self.tailEnd = tailEnd diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py new file mode 100644 index 0000000..9a83fac --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/picture.py @@ -0,0 +1,144 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import DRAWING_NS + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + String, + Alias, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from openpyxl.chart.shapes import GraphicalProperties + +from .fill import BlipFillProperties +from .properties import NonVisualDrawingProps +from .geometry import ShapeStyle + + +class PictureLocking(Serialisable): + + tagname = "picLocks" + namespace = DRAWING_NS + + # Using attribute group AG_Locking + noCrop = Bool(allow_none=True) + noGrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + noCrop=None, + noGrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeArrowheads=None, + noChangeShapeType=None, + extLst=None, + ): + self.noCrop = noCrop + self.noGrp = noGrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeArrowheads = noChangeArrowheads + self.noChangeShapeType = noChangeShapeType + + +class NonVisualPictureProperties(Serialisable): + + tagname = "cNvPicPr" + + preferRelativeResize = Bool(allow_none=True) + picLocks = Typed(expected_type=PictureLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("picLocks",) + + def __init__(self, + preferRelativeResize=None, + picLocks=None, + extLst=None, + ): + self.preferRelativeResize = preferRelativeResize + self.picLocks = picLocks + + +class PictureNonVisual(Serialisable): + + tagname = "nvPicPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps, ) + cNvPicPr = Typed(expected_type=NonVisualPictureProperties, ) + + __elements__ = ("cNvPr", "cNvPicPr") + + def __init__(self, + cNvPr=None, + cNvPicPr=None, + ): + if cNvPr is None: + cNvPr = NonVisualDrawingProps(id=0, name="Image 1", descr="Name of file") + self.cNvPr = cNvPr + if cNvPicPr is None: + cNvPicPr = NonVisualPictureProperties() + self.cNvPicPr = cNvPicPr + + + + +class PictureFrame(Serialisable): + + tagname = "pic" + + macro = String(allow_none=True) + fPublished = Bool(allow_none=True) + nvPicPr = Typed(expected_type=PictureNonVisual, ) + blipFill = Typed(expected_type=BlipFillProperties, ) + spPr = Typed(expected_type=GraphicalProperties, ) + graphicalProperties = Alias('spPr') + style = Typed(expected_type=ShapeStyle, allow_none=True) + + __elements__ = ("nvPicPr", "blipFill", "spPr", "style") + + def __init__(self, + macro=None, + fPublished=None, + nvPicPr=None, + blipFill=None, + spPr=None, + style=None, + ): + self.macro = macro + self.fPublished = fPublished + if nvPicPr is None: + nvPicPr = PictureNonVisual() + self.nvPicPr = nvPicPr + if blipFill is None: + blipFill = BlipFillProperties() + self.blipFill = blipFill + if spPr is None: + spPr = GraphicalProperties() + self.spPr = spPr + self.style = style diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py new file mode 100644 index 0000000..77b0072 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/properties.py @@ -0,0 +1,174 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import DRAWING_NS +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Integer, + Set, + String, + Alias, + NoneSet, +) +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList + +from .geometry import GroupTransform2D, Scene3D +from .text import Hyperlink + + +class GroupShapeProperties(Serialisable): + + tagname = "grpSpPr" + + bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray', + 'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden'])) + xfrm = Typed(expected_type=GroupTransform2D, allow_none=True) + scene3d = Typed(expected_type=Scene3D, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + def __init__(self, + bwMode=None, + xfrm=None, + scene3d=None, + extLst=None, + ): + self.bwMode = bwMode + self.xfrm = xfrm + self.scene3d = scene3d + self.extLst = extLst + + +class GroupLocking(Serialisable): + + tagname = "grpSpLocks" + namespace = DRAWING_NS + + noGrp = Bool(allow_none=True) + noUngrp = Bool(allow_none=True) + noSelect = Bool(allow_none=True) + noRot = Bool(allow_none=True) + noChangeAspect = Bool(allow_none=True) + noMove = Bool(allow_none=True) + noResize = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noEditPoints = Bool(allow_none=True) + noAdjustHandles = Bool(allow_none=True) + noChangeArrowheads = Bool(allow_none=True) + noChangeShapeType = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + noGrp=None, + noUngrp=None, + noSelect=None, + noRot=None, + noChangeAspect=None, + noChangeArrowheads=None, + noMove=None, + noResize=None, + noEditPoints=None, + noAdjustHandles=None, + noChangeShapeType=None, + extLst=None, + ): + self.noGrp = noGrp + self.noUngrp = noUngrp + self.noSelect = noSelect + self.noRot = noRot + self.noChangeAspect = noChangeAspect + self.noChangeArrowheads = noChangeArrowheads + self.noMove = noMove + self.noResize = noResize + self.noEditPoints = noEditPoints + self.noAdjustHandles = noAdjustHandles + self.noChangeShapeType = noChangeShapeType + + +class NonVisualGroupDrawingShapeProps(Serialisable): + + tagname = "cNvGrpSpPr" + + grpSpLocks = Typed(expected_type=GroupLocking, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("grpSpLocks",) + + def __init__(self, + grpSpLocks=None, + extLst=None, + ): + self.grpSpLocks = grpSpLocks + + +class NonVisualDrawingShapeProps(Serialisable): + + tagname = "cNvSpPr" + + spLocks = Typed(expected_type=GroupLocking, allow_none=True) + txBax = Bool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("spLocks", "txBax") + + def __init__(self, + spLocks=None, + txBox=None, + extLst=None, + ): + self.spLocks = spLocks + self.txBox = txBox + + +class NonVisualDrawingProps(Serialisable): + + tagname = "cNvPr" + + id = Integer() + name = String() + descr = String(allow_none=True) + hidden = Bool(allow_none=True) + title = String(allow_none=True) + hlinkClick = Typed(expected_type=Hyperlink, allow_none=True) + hlinkHover = Typed(expected_type=Hyperlink, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ["hlinkClick", "hlinkHover"] + + def __init__(self, + id=None, + name=None, + descr=None, + hidden=None, + title=None, + hlinkClick=None, + hlinkHover=None, + extLst=None, + ): + self.id = id + self.name = name + self.descr = descr + self.hidden = hidden + self.title = title + self.hlinkClick = hlinkClick + self.hlinkHover = hlinkHover + self.extLst = extLst + +class NonVisualGroupShape(Serialisable): + + tagname = "nvGrpSpPr" + + cNvPr = Typed(expected_type=NonVisualDrawingProps) + cNvGrpSpPr = Typed(expected_type=NonVisualGroupDrawingShapeProps) + + __elements__ = ("cNvPr", "cNvGrpSpPr") + + def __init__(self, + cNvPr=None, + cNvGrpSpPr=None, + ): + self.cNvPr = cNvPr + self.cNvGrpSpPr = cNvGrpSpPr + diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py new file mode 100644 index 0000000..0163293 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/relation.py @@ -0,0 +1,17 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.xml.constants import CHART_NS + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.excel import Relation + + +class ChartRelation(Serialisable): + + tagname = "chart" + namespace = CHART_NS + + id = Relation() + + def __init__(self, id): + self.id = id diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py new file mode 100644 index 0000000..4f378ca --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/spreadsheet_drawing.py @@ -0,0 +1,382 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + NoneSet, + Integer, + Sequence, + Alias, +) +from openpyxl.descriptors.nested import ( + NestedText, + NestedNoneSet, +) +from openpyxl.descriptors.excel import Relation + +from openpyxl.packaging.relationship import ( + Relationship, + RelationshipList, +) +from openpyxl.utils import coordinate_to_tuple +from openpyxl.utils.units import ( + cm_to_EMU, + pixels_to_EMU, +) +from openpyxl.drawing.image import Image + +from openpyxl.xml.constants import SHEET_DRAWING_NS + +from openpyxl.chart._chart import ChartBase +from .xdr import ( + XDRPoint2D, + XDRPositiveSize2D, +) +from .fill import Blip +from .connector import Shape +from .graphic import ( + GroupShape, + GraphicFrame, + ) +from .geometry import PresetGeometry2D +from .picture import PictureFrame +from .relation import ChartRelation + + +class AnchorClientData(Serialisable): + + fLocksWithSheet = Bool(allow_none=True) + fPrintsWithSheet = Bool(allow_none=True) + + def __init__(self, + fLocksWithSheet=None, + fPrintsWithSheet=None, + ): + self.fLocksWithSheet = fLocksWithSheet + self.fPrintsWithSheet = fPrintsWithSheet + + +class AnchorMarker(Serialisable): + + tagname = "marker" + + col = NestedText(expected_type=int) + colOff = NestedText(expected_type=int) + row = NestedText(expected_type=int) + rowOff = NestedText(expected_type=int) + + def __init__(self, + col=0, + colOff=0, + row=0, + rowOff=0, + ): + self.col = col + self.colOff = colOff + self.row = row + self.rowOff = rowOff + + +class _AnchorBase(Serialisable): + + #one of + sp = Typed(expected_type=Shape, allow_none=True) + shape = Alias("sp") + grpSp = Typed(expected_type=GroupShape, allow_none=True) + groupShape = Alias("grpSp") + graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True) + cxnSp = Typed(expected_type=Shape, allow_none=True) + connectionShape = Alias("cxnSp") + pic = Typed(expected_type=PictureFrame, allow_none=True) + contentPart = Relation() + + clientData = Typed(expected_type=AnchorClientData) + + __elements__ = ('sp', 'grpSp', 'graphicFrame', + 'cxnSp', 'pic', 'contentPart', 'clientData') + + def __init__(self, + clientData=None, + sp=None, + grpSp=None, + graphicFrame=None, + cxnSp=None, + pic=None, + contentPart=None + ): + if clientData is None: + clientData = AnchorClientData() + self.clientData = clientData + self.sp = sp + self.grpSp = grpSp + self.graphicFrame = graphicFrame + self.cxnSp = cxnSp + self.pic = pic + self.contentPart = contentPart + + +class AbsoluteAnchor(_AnchorBase): + + tagname = "absoluteAnchor" + + pos = Typed(expected_type=XDRPoint2D) + ext = Typed(expected_type=XDRPositiveSize2D) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('pos', 'ext') + _AnchorBase.__elements__ + + def __init__(self, + pos=None, + ext=None, + **kw + ): + if pos is None: + pos = XDRPoint2D(0, 0) + self.pos = pos + if ext is None: + ext = XDRPositiveSize2D(0, 0) + self.ext = ext + super().__init__(**kw) + + +class OneCellAnchor(_AnchorBase): + + tagname = "oneCellAnchor" + + _from = Typed(expected_type=AnchorMarker) + ext = Typed(expected_type=XDRPositiveSize2D) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('_from', 'ext') + _AnchorBase.__elements__ + + + def __init__(self, + _from=None, + ext=None, + **kw + ): + if _from is None: + _from = AnchorMarker() + self._from = _from + if ext is None: + ext = XDRPositiveSize2D(0, 0) + self.ext = ext + super().__init__(**kw) + + +class TwoCellAnchor(_AnchorBase): + + tagname = "twoCellAnchor" + + editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute'])) + _from = Typed(expected_type=AnchorMarker) + to = Typed(expected_type=AnchorMarker) + + sp = _AnchorBase.sp + grpSp = _AnchorBase.grpSp + graphicFrame = _AnchorBase.graphicFrame + cxnSp = _AnchorBase.cxnSp + pic = _AnchorBase.pic + contentPart = _AnchorBase.contentPart + clientData = _AnchorBase.clientData + + __elements__ = ('_from', 'to') + _AnchorBase.__elements__ + + def __init__(self, + editAs=None, + _from=None, + to=None, + **kw + ): + self.editAs = editAs + if _from is None: + _from = AnchorMarker() + self._from = _from + if to is None: + to = AnchorMarker() + self.to = to + super().__init__(**kw) + + +def _check_anchor(obj): + """ + Check whether an object has an existing Anchor object + If not create a OneCellAnchor using the provided coordinate + """ + anchor = obj.anchor + if not isinstance(anchor, _AnchorBase): + row, col = coordinate_to_tuple(anchor.upper()) + anchor = OneCellAnchor() + anchor._from.row = row -1 + anchor._from.col = col -1 + if isinstance(obj, ChartBase): + anchor.ext.width = cm_to_EMU(obj.width) + anchor.ext.height = cm_to_EMU(obj.height) + elif isinstance(obj, Image): + anchor.ext.width = pixels_to_EMU(obj.width) + anchor.ext.height = pixels_to_EMU(obj.height) + return anchor + + +class SpreadsheetDrawing(Serialisable): + + tagname = "wsDr" + mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml" + _rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + _path = PartName="/xl/drawings/drawing{0}.xml" + _id = None + + twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True) + oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True) + absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True) + + __elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor") + + def __init__(self, + twoCellAnchor=(), + oneCellAnchor=(), + absoluteAnchor=(), + ): + self.twoCellAnchor = twoCellAnchor + self.oneCellAnchor = oneCellAnchor + self.absoluteAnchor = absoluteAnchor + self.charts = [] + self.images = [] + self._rels = [] + + + def __hash__(self): + """ + Just need to check for identity + """ + return id(self) + + + def __bool__(self): + return bool(self.charts) or bool(self.images) + + + + def _write(self): + """ + create required structure and the serialise + """ + anchors = [] + for idx, obj in enumerate(self.charts + self.images, 1): + anchor = _check_anchor(obj) + if isinstance(obj, ChartBase): + rel = Relationship(type="chart", Target=obj.path) + anchor.graphicFrame = self._chart_frame(idx) + elif isinstance(obj, Image): + rel = Relationship(type="image", Target=obj.path) + child = anchor.pic or anchor.groupShape and anchor.groupShape.pic + if not child: + anchor.pic = self._picture_frame(idx) + else: + child.blipFill.blip.embed = "rId{0}".format(idx) + + anchors.append(anchor) + self._rels.append(rel) + + for a in anchors: + if isinstance(a, OneCellAnchor): + self.oneCellAnchor.append(a) + elif isinstance(a, TwoCellAnchor): + self.twoCellAnchor.append(a) + else: + self.absoluteAnchor.append(a) + + tree = self.to_tree() + tree.set('xmlns', SHEET_DRAWING_NS) + return tree + + + def _chart_frame(self, idx): + chart_rel = ChartRelation(f"rId{idx}") + frame = GraphicFrame() + nv = frame.nvGraphicFramePr.cNvPr + nv.id = idx + nv.name = "Chart {0}".format(idx) + frame.graphic.graphicData.chart = chart_rel + return frame + + + def _picture_frame(self, idx): + pic = PictureFrame() + pic.nvPicPr.cNvPr.descr = "Picture" + pic.nvPicPr.cNvPr.id = idx + pic.nvPicPr.cNvPr.name = "Image {0}".format(idx) + + pic.blipFill.blip = Blip() + pic.blipFill.blip.embed = "rId{0}".format(idx) + pic.blipFill.blip.cstate = "print" + + pic.spPr.prstGeom = PresetGeometry2D(prst="rect") + pic.spPr.ln = None + return pic + + + def _write_rels(self): + rels = RelationshipList() + for r in self._rels: + rels.append(r) + return rels.to_tree() + + + @property + def path(self): + return self._path.format(self._id) + + + @property + def _chart_rels(self): + """ + Get relationship information for each chart and bind anchor to it + """ + rels = [] + anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor + for anchor in anchors: + if anchor.graphicFrame is not None: + graphic = anchor.graphicFrame.graphic + rel = graphic.graphicData.chart + if rel is not None: + rel.anchor = anchor + rel.anchor.graphicFrame = None + rels.append(rel) + return rels + + + @property + def _blip_rels(self): + """ + Get relationship information for each blip and bind anchor to it + + Images that are not part of the XLSX package will be ignored. + """ + rels = [] + anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor + + for anchor in anchors: + child = anchor.pic or anchor.groupShape and anchor.groupShape.pic + if child and child.blipFill: + rel = child.blipFill.blip + if rel is not None and rel.embed: + rel.anchor = anchor + rels.append(rel) + + return rels diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/text.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/text.py new file mode 100644 index 0000000..5bdc771 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/text.py @@ -0,0 +1,717 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + Set, + NoneSet, + Sequence, + String, + Bool, + MinMax, + Integer +) +from openpyxl.descriptors.excel import ( + HexBinary, + Coordinate, + Relation, +) +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedText, + NestedValue, + EmptyTag +) +from openpyxl.xml.constants import DRAWING_NS + + +from .colors import ColorChoiceDescriptor +from .effect import ( + EffectList, + EffectContainer, +) +from .fill import( + GradientFillProperties, + BlipFillProperties, + PatternFillProperties, + Blip +) +from .geometry import ( + LineProperties, + Color, + Scene3D +) + +from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList +from openpyxl.descriptors.nested import NestedBool + + +class EmbeddedWAVAudioFile(Serialisable): + + name = String(allow_none=True) + + def __init__(self, + name=None, + ): + self.name = name + + +class Hyperlink(Serialisable): + + tagname = "hlinkClick" + namespace = DRAWING_NS + + invalidUrl = String(allow_none=True) + action = String(allow_none=True) + tgtFrame = String(allow_none=True) + tooltip = String(allow_none=True) + history = Bool(allow_none=True) + highlightClick = Bool(allow_none=True) + endSnd = Bool(allow_none=True) + snd = Typed(expected_type=EmbeddedWAVAudioFile, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + id = Relation(allow_none=True) + + __elements__ = ('snd',) + + def __init__(self, + invalidUrl=None, + action=None, + tgtFrame=None, + tooltip=None, + history=None, + highlightClick=None, + endSnd=None, + snd=None, + extLst=None, + id=None, + ): + self.invalidUrl = invalidUrl + self.action = action + self.tgtFrame = tgtFrame + self.tooltip = tooltip + self.history = history + self.highlightClick = highlightClick + self.endSnd = endSnd + self.snd = snd + self.id = id + + +class Font(Serialisable): + + tagname = "latin" + namespace = DRAWING_NS + + typeface = String() + panose = HexBinary(allow_none=True) + pitchFamily = MinMax(min=0, max=52, allow_none=True) + charset = Integer(allow_none=True) + + def __init__(self, + typeface=None, + panose=None, + pitchFamily=None, + charset=None, + ): + self.typeface = typeface + self.panose = panose + self.pitchFamily = pitchFamily + self.charset = charset + + +class CharacterProperties(Serialisable): + + tagname = "defRPr" + namespace = DRAWING_NS + + kumimoji = Bool(allow_none=True) + lang = String(allow_none=True) + altLang = String(allow_none=True) + sz = MinMax(allow_none=True, min=100, max=400000) # 100ths of a point + b = Bool(allow_none=True) + i = Bool(allow_none=True) + u = NoneSet(values=(['words', 'sng', 'dbl', 'heavy', 'dotted', + 'dottedHeavy', 'dash', 'dashHeavy', 'dashLong', 'dashLongHeavy', + 'dotDash', 'dotDashHeavy', 'dotDotDash', 'dotDotDashHeavy', 'wavy', + 'wavyHeavy', 'wavyDbl'])) + strike = NoneSet(values=(['noStrike', 'sngStrike', 'dblStrike'])) + kern = Integer(allow_none=True) + cap = NoneSet(values=(['small', 'all'])) + spc = Integer(allow_none=True) + normalizeH = Bool(allow_none=True) + baseline = Integer(allow_none=True) + noProof = Bool(allow_none=True) + dirty = Bool(allow_none=True) + err = Bool(allow_none=True) + smtClean = Bool(allow_none=True) + smtId = Integer(allow_none=True) + bmk = String(allow_none=True) + ln = Typed(expected_type=LineProperties, allow_none=True) + highlight = Typed(expected_type=Color, allow_none=True) + latin = Typed(expected_type=Font, allow_none=True) + ea = Typed(expected_type=Font, allow_none=True) + cs = Typed(expected_type=Font, allow_none=True) + sym = Typed(expected_type=Font, allow_none=True) + hlinkClick = Typed(expected_type=Hyperlink, allow_none=True) + hlinkMouseOver = Typed(expected_type=Hyperlink, allow_none=True) + rtl = NestedBool(allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + # uses element group EG_FillProperties + noFill = EmptyTag(namespace=DRAWING_NS) + solidFill = ColorChoiceDescriptor() + gradFill = Typed(expected_type=GradientFillProperties, allow_none=True) + blipFill = Typed(expected_type=BlipFillProperties, allow_none=True) + pattFill = Typed(expected_type=PatternFillProperties, allow_none=True) + grpFill = EmptyTag(namespace=DRAWING_NS) + # uses element group EG_EffectProperties + effectLst = Typed(expected_type=EffectList, allow_none=True) + effectDag = Typed(expected_type=EffectContainer, allow_none=True) + # uses element group EG_TextUnderlineLine + uLnTx = EmptyTag() + uLn = Typed(expected_type=LineProperties, allow_none=True) + # uses element group EG_TextUnderlineFill + uFillTx = EmptyTag() + uFill = EmptyTag() + + __elements__ = ('ln', 'noFill', 'solidFill', 'gradFill', 'blipFill', + 'pattFill', 'grpFill', 'effectLst', 'effectDag', 'highlight','uLnTx', + 'uLn', 'uFillTx', 'uFill', 'latin', 'ea', 'cs', 'sym', 'hlinkClick', + 'hlinkMouseOver', 'rtl', ) + + def __init__(self, + kumimoji=None, + lang=None, + altLang=None, + sz=None, + b=None, + i=None, + u=None, + strike=None, + kern=None, + cap=None, + spc=None, + normalizeH=None, + baseline=None, + noProof=None, + dirty=None, + err=None, + smtClean=None, + smtId=None, + bmk=None, + ln=None, + highlight=None, + latin=None, + ea=None, + cs=None, + sym=None, + hlinkClick=None, + hlinkMouseOver=None, + rtl=None, + extLst=None, + noFill=None, + solidFill=None, + gradFill=None, + blipFill=None, + pattFill=None, + grpFill=None, + effectLst=None, + effectDag=None, + uLnTx=None, + uLn=None, + uFillTx=None, + uFill=None, + ): + self.kumimoji = kumimoji + self.lang = lang + self.altLang = altLang + self.sz = sz + self.b = b + self.i = i + self.u = u + self.strike = strike + self.kern = kern + self.cap = cap + self.spc = spc + self.normalizeH = normalizeH + self.baseline = baseline + self.noProof = noProof + self.dirty = dirty + self.err = err + self.smtClean = smtClean + self.smtId = smtId + self.bmk = bmk + self.ln = ln + self.highlight = highlight + self.latin = latin + self.ea = ea + self.cs = cs + self.sym = sym + self.hlinkClick = hlinkClick + self.hlinkMouseOver = hlinkMouseOver + self.rtl = rtl + self.noFill = noFill + self.solidFill = solidFill + self.gradFill = gradFill + self.blipFill = blipFill + self.pattFill = pattFill + self.grpFill = grpFill + self.effectLst = effectLst + self.effectDag = effectDag + self.uLnTx = uLnTx + self.uLn = uLn + self.uFillTx = uFillTx + self.uFill = uFill + + +class TabStop(Serialisable): + + pos = Typed(expected_type=Coordinate, allow_none=True) + algn = Typed(expected_type=Set(values=(['l', 'ctr', 'r', 'dec']))) + + def __init__(self, + pos=None, + algn=None, + ): + self.pos = pos + self.algn = algn + + +class TabStopList(Serialisable): + + tab = Typed(expected_type=TabStop, allow_none=True) + + def __init__(self, + tab=None, + ): + self.tab = tab + + +class Spacing(Serialisable): + + spcPct = NestedInteger(allow_none=True) + spcPts = NestedInteger(allow_none=True) + + __elements__ = ('spcPct', 'spcPts') + + def __init__(self, + spcPct=None, + spcPts=None, + ): + self.spcPct = spcPct + self.spcPts = spcPts + + +class AutonumberBullet(Serialisable): + + type = Set(values=(['alphaLcParenBoth', 'alphaUcParenBoth', + 'alphaLcParenR', 'alphaUcParenR', 'alphaLcPeriod', 'alphaUcPeriod', + 'arabicParenBoth', 'arabicParenR', 'arabicPeriod', 'arabicPlain', + 'romanLcParenBoth', 'romanUcParenBoth', 'romanLcParenR', 'romanUcParenR', + 'romanLcPeriod', 'romanUcPeriod', 'circleNumDbPlain', + 'circleNumWdBlackPlain', 'circleNumWdWhitePlain', 'arabicDbPeriod', + 'arabicDbPlain', 'ea1ChsPeriod', 'ea1ChsPlain', 'ea1ChtPeriod', + 'ea1ChtPlain', 'ea1JpnChsDbPeriod', 'ea1JpnKorPlain', 'ea1JpnKorPeriod', + 'arabic1Minus', 'arabic2Minus', 'hebrew2Minus', 'thaiAlphaPeriod', + 'thaiAlphaParenR', 'thaiAlphaParenBoth', 'thaiNumPeriod', + 'thaiNumParenR', 'thaiNumParenBoth', 'hindiAlphaPeriod', + 'hindiNumPeriod', 'hindiNumParenR', 'hindiAlpha1Period'])) + startAt = Integer() + + def __init__(self, + type=None, + startAt=None, + ): + self.type = type + self.startAt = startAt + + +class ParagraphProperties(Serialisable): + + tagname = "pPr" + namespace = DRAWING_NS + + marL = Integer(allow_none=True) + marR = Integer(allow_none=True) + lvl = Integer(allow_none=True) + indent = Integer(allow_none=True) + algn = NoneSet(values=(['l', 'ctr', 'r', 'just', 'justLow', 'dist', 'thaiDist'])) + defTabSz = Integer(allow_none=True) + rtl = Bool(allow_none=True) + eaLnBrk = Bool(allow_none=True) + fontAlgn = NoneSet(values=(['auto', 't', 'ctr', 'base', 'b'])) + latinLnBrk = Bool(allow_none=True) + hangingPunct = Bool(allow_none=True) + + # uses element group EG_TextBulletColor + # uses element group EG_TextBulletSize + # uses element group EG_TextBulletTypeface + # uses element group EG_TextBullet + lnSpc = Typed(expected_type=Spacing, allow_none=True) + spcBef = Typed(expected_type=Spacing, allow_none=True) + spcAft = Typed(expected_type=Spacing, allow_none=True) + tabLst = Typed(expected_type=TabStopList, allow_none=True) + defRPr = Typed(expected_type=CharacterProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + buClrTx = EmptyTag() + buClr = Typed(expected_type=Color, allow_none=True) + buSzTx = EmptyTag() + buSzPct = NestedInteger(allow_none=True) + buSzPts = NestedInteger(allow_none=True) + buFontTx = EmptyTag() + buFont = Typed(expected_type=Font, allow_none=True) + buNone = EmptyTag() + buAutoNum = EmptyTag() + buChar = NestedValue(expected_type=str, attribute="char", allow_none=True) + buBlip = NestedValue(expected_type=Blip, attribute="blip", allow_none=True) + + __elements__ = ('lnSpc', 'spcBef', 'spcAft', 'tabLst', 'defRPr', + 'buClrTx', 'buClr', 'buSzTx', 'buSzPct', 'buSzPts', 'buFontTx', 'buFont', + 'buNone', 'buAutoNum', 'buChar', 'buBlip') + + def __init__(self, + marL=None, + marR=None, + lvl=None, + indent=None, + algn=None, + defTabSz=None, + rtl=None, + eaLnBrk=None, + fontAlgn=None, + latinLnBrk=None, + hangingPunct=None, + lnSpc=None, + spcBef=None, + spcAft=None, + tabLst=None, + defRPr=None, + extLst=None, + buClrTx=None, + buClr=None, + buSzTx=None, + buSzPct=None, + buSzPts=None, + buFontTx=None, + buFont=None, + buNone=None, + buAutoNum=None, + buChar=None, + buBlip=None, + ): + self.marL = marL + self.marR = marR + self.lvl = lvl + self.indent = indent + self.algn = algn + self.defTabSz = defTabSz + self.rtl = rtl + self.eaLnBrk = eaLnBrk + self.fontAlgn = fontAlgn + self.latinLnBrk = latinLnBrk + self.hangingPunct = hangingPunct + self.lnSpc = lnSpc + self.spcBef = spcBef + self.spcAft = spcAft + self.tabLst = tabLst + self.defRPr = defRPr + self.buClrTx = buClrTx + self.buClr = buClr + self.buSzTx = buSzTx + self.buSzPct = buSzPct + self.buSzPts = buSzPts + self.buFontTx = buFontTx + self.buFont = buFont + self.buNone = buNone + self.buAutoNum = buAutoNum + self.buChar = buChar + self.buBlip = buBlip + self.defRPr = defRPr + + +class ListStyle(Serialisable): + + tagname = "lstStyle" + namespace = DRAWING_NS + + defPPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl1pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl2pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl3pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl4pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl5pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl6pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl7pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl8pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + lvl9pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + + __elements__ = ("defPPr", "lvl1pPr", "lvl2pPr", "lvl3pPr", "lvl4pPr", + "lvl5pPr", "lvl6pPr", "lvl7pPr", "lvl8pPr", "lvl9pPr") + + def __init__(self, + defPPr=None, + lvl1pPr=None, + lvl2pPr=None, + lvl3pPr=None, + lvl4pPr=None, + lvl5pPr=None, + lvl6pPr=None, + lvl7pPr=None, + lvl8pPr=None, + lvl9pPr=None, + extLst=None, + ): + self.defPPr = defPPr + self.lvl1pPr = lvl1pPr + self.lvl2pPr = lvl2pPr + self.lvl3pPr = lvl3pPr + self.lvl4pPr = lvl4pPr + self.lvl5pPr = lvl5pPr + self.lvl6pPr = lvl6pPr + self.lvl7pPr = lvl7pPr + self.lvl8pPr = lvl8pPr + self.lvl9pPr = lvl9pPr + + +class RegularTextRun(Serialisable): + + tagname = "r" + namespace = DRAWING_NS + + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + properties = Alias("rPr") + t = NestedText(expected_type=str) + value = Alias("t") + + __elements__ = ('rPr', 't') + + def __init__(self, + rPr=None, + t="", + ): + self.rPr = rPr + self.t = t + + +class LineBreak(Serialisable): + + tagname = "br" + namespace = DRAWING_NS + + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + + __elements__ = ('rPr',) + + def __init__(self, + rPr=None, + ): + self.rPr = rPr + + +class TextField(Serialisable): + + id = String() + type = String(allow_none=True) + rPr = Typed(expected_type=CharacterProperties, allow_none=True) + pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + t = String(allow_none=True) + + __elements__ = ('rPr', 'pPr') + + def __init__(self, + id=None, + type=None, + rPr=None, + pPr=None, + t=None, + ): + self.id = id + self.type = type + self.rPr = rPr + self.pPr = pPr + self.t = t + + +class Paragraph(Serialisable): + + tagname = "p" + namespace = DRAWING_NS + + # uses element group EG_TextRun + pPr = Typed(expected_type=ParagraphProperties, allow_none=True) + properties = Alias("pPr") + endParaRPr = Typed(expected_type=CharacterProperties, allow_none=True) + r = Sequence(expected_type=RegularTextRun) + text = Alias('r') + br = Typed(expected_type=LineBreak, allow_none=True) + fld = Typed(expected_type=TextField, allow_none=True) + + __elements__ = ('pPr', 'r', 'br', 'fld', 'endParaRPr') + + def __init__(self, + pPr=None, + endParaRPr=None, + r=None, + br=None, + fld=None, + ): + self.pPr = pPr + self.endParaRPr = endParaRPr + if r is None: + r = [RegularTextRun()] + self.r = r + self.br = br + self.fld = fld + + +class GeomGuide(Serialisable): + + name = String(()) + fmla = String(()) + + def __init__(self, + name=None, + fmla=None, + ): + self.name = name + self.fmla = fmla + + +class GeomGuideList(Serialisable): + + gd = Sequence(expected_type=GeomGuide, allow_none=True) + + def __init__(self, + gd=None, + ): + self.gd = gd + + +class PresetTextShape(Serialisable): + + prst = Typed(expected_type=Set(values=( + ['textNoShape', 'textPlain','textStop', 'textTriangle', 'textTriangleInverted', 'textChevron', + 'textChevronInverted', 'textRingInside', 'textRingOutside', 'textArchUp', + 'textArchDown', 'textCircle', 'textButton', 'textArchUpPour', + 'textArchDownPour', 'textCirclePour', 'textButtonPour', 'textCurveUp', + 'textCurveDown', 'textCanUp', 'textCanDown', 'textWave1', 'textWave2', + 'textDoubleWave1', 'textWave4', 'textInflate', 'textDeflate', + 'textInflateBottom', 'textDeflateBottom', 'textInflateTop', + 'textDeflateTop', 'textDeflateInflate', 'textDeflateInflateDeflate', + 'textFadeRight', 'textFadeLeft', 'textFadeUp', 'textFadeDown', + 'textSlantUp', 'textSlantDown', 'textCascadeUp', 'textCascadeDown' + ] + ))) + avLst = Typed(expected_type=GeomGuideList, allow_none=True) + + def __init__(self, + prst=None, + avLst=None, + ): + self.prst = prst + self.avLst = avLst + + +class TextNormalAutofit(Serialisable): + + fontScale = Integer() + lnSpcReduction = Integer() + + def __init__(self, + fontScale=None, + lnSpcReduction=None, + ): + self.fontScale = fontScale + self.lnSpcReduction = lnSpcReduction + + +class RichTextProperties(Serialisable): + + tagname = "bodyPr" + namespace = DRAWING_NS + + rot = Integer(allow_none=True) + spcFirstLastPara = Bool(allow_none=True) + vertOverflow = NoneSet(values=(['overflow', 'ellipsis', 'clip'])) + horzOverflow = NoneSet(values=(['overflow', 'clip'])) + vert = NoneSet(values=(['horz', 'vert', 'vert270', 'wordArtVert', + 'eaVert', 'mongolianVert', 'wordArtVertRtl'])) + wrap = NoneSet(values=(['none', 'square'])) + lIns = Integer(allow_none=True) + tIns = Integer(allow_none=True) + rIns = Integer(allow_none=True) + bIns = Integer(allow_none=True) + numCol = Integer(allow_none=True) + spcCol = Integer(allow_none=True) + rtlCol = Bool(allow_none=True) + fromWordArt = Bool(allow_none=True) + anchor = NoneSet(values=(['t', 'ctr', 'b', 'just', 'dist'])) + anchorCtr = Bool(allow_none=True) + forceAA = Bool(allow_none=True) + upright = Bool(allow_none=True) + compatLnSpc = Bool(allow_none=True) + prstTxWarp = Typed(expected_type=PresetTextShape, allow_none=True) + scene3d = Typed(expected_type=Scene3D, allow_none=True) + extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True) + noAutofit = EmptyTag() + normAutofit = EmptyTag() + spAutoFit = EmptyTag() + flatTx = NestedInteger(attribute="z", allow_none=True) + + __elements__ = ('prstTxWarp', 'scene3d', 'noAutofit', 'normAutofit', 'spAutoFit') + + def __init__(self, + rot=None, + spcFirstLastPara=None, + vertOverflow=None, + horzOverflow=None, + vert=None, + wrap=None, + lIns=None, + tIns=None, + rIns=None, + bIns=None, + numCol=None, + spcCol=None, + rtlCol=None, + fromWordArt=None, + anchor=None, + anchorCtr=None, + forceAA=None, + upright=None, + compatLnSpc=None, + prstTxWarp=None, + scene3d=None, + extLst=None, + noAutofit=None, + normAutofit=None, + spAutoFit=None, + flatTx=None, + ): + self.rot = rot + self.spcFirstLastPara = spcFirstLastPara + self.vertOverflow = vertOverflow + self.horzOverflow = horzOverflow + self.vert = vert + self.wrap = wrap + self.lIns = lIns + self.tIns = tIns + self.rIns = rIns + self.bIns = bIns + self.numCol = numCol + self.spcCol = spcCol + self.rtlCol = rtlCol + self.fromWordArt = fromWordArt + self.anchor = anchor + self.anchorCtr = anchorCtr + self.forceAA = forceAA + self.upright = upright + self.compatLnSpc = compatLnSpc + self.prstTxWarp = prstTxWarp + self.scene3d = scene3d + self.noAutofit = noAutofit + self.normAutofit = normAutofit + self.spAutoFit = spAutoFit + self.flatTx = flatTx diff --git a/venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py b/venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py new file mode 100644 index 0000000..335480c --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/drawing/xdr.py @@ -0,0 +1,33 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +Spreadsheet Drawing has some copies of Drawing ML elements +""" + +from .geometry import Point2D, PositiveSize2D, Transform2D + + +class XDRPoint2D(Point2D): + + namespace = None + x = Point2D.x + y = Point2D.y + + +class XDRPositiveSize2D(PositiveSize2D): + + namespace = None + cx = PositiveSize2D.cx + cy = PositiveSize2D.cy + + +class XDRTransform2D(Transform2D): + + namespace = None + rot = Transform2D.rot + flipH = Transform2D.flipH + flipV = Transform2D.flipV + off = Transform2D.off + ext = Transform2D.ext + chOff = Transform2D.chOff + chExt = Transform2D.chExt diff --git a/venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py new file mode 100644 index 0000000..bedc2bc --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formatting/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .rule import Rule diff --git a/venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py b/venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py new file mode 100644 index 0000000..bf622bf --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formatting/formatting.py @@ -0,0 +1,114 @@ +# Copyright (c) 2010-2024 openpyxl + +from collections import OrderedDict + +from openpyxl.descriptors import ( + Bool, + Sequence, + Alias, + Convertible, +) +from openpyxl.descriptors.serialisable import Serialisable + +from .rule import Rule + +from openpyxl.worksheet.cell_range import MultiCellRange + +class ConditionalFormatting(Serialisable): + + tagname = "conditionalFormatting" + + sqref = Convertible(expected_type=MultiCellRange) + cells = Alias("sqref") + pivot = Bool(allow_none=True) + cfRule = Sequence(expected_type=Rule) + rules = Alias("cfRule") + + + def __init__(self, sqref=(), pivot=None, cfRule=(), extLst=None): + self.sqref = sqref + self.pivot = pivot + self.cfRule = cfRule + + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.sqref == other.sqref + + + def __hash__(self): + return hash(self.sqref) + + + def __repr__(self): + return "<{cls} {cells}>".format(cls=self.__class__.__name__, cells=self.sqref) + + + def __contains__(self, coord): + """ + Check whether a certain cell is affected by the formatting + """ + return coord in self.sqref + + +class ConditionalFormattingList: + """Conditional formatting rules.""" + + + def __init__(self): + self._cf_rules = OrderedDict() + self.max_priority = 0 + + + def add(self, range_string, cfRule): + """Add a rule such as ColorScaleRule, FormulaRule or CellIsRule + + The priority will be added automatically. + """ + cf = range_string + if isinstance(range_string, str): + cf = ConditionalFormatting(range_string) + if not isinstance(cfRule, Rule): + raise ValueError("Only instances of openpyxl.formatting.rule.Rule may be added") + rule = cfRule + self.max_priority += 1 + if not rule.priority: + rule.priority = self.max_priority + + self._cf_rules.setdefault(cf, []).append(rule) + + + def __bool__(self): + return bool(self._cf_rules) + + + def __len__(self): + return len(self._cf_rules) + + + def __iter__(self): + for cf, rules in self._cf_rules.items(): + cf.rules = rules + yield cf + + + def __getitem__(self, key): + """ + Get the rules for a cell range + """ + if isinstance(key, str): + key = ConditionalFormatting(sqref=key) + return self._cf_rules[key] + + + def __delitem__(self, key): + key = ConditionalFormatting(sqref=key) + del self._cf_rules[key] + + + def __setitem__(self, key, rule): + """ + Add a rule for a cell range + """ + self.add(key, rule) diff --git a/venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py b/venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py new file mode 100644 index 0000000..c4ba7f8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formatting/rule.py @@ -0,0 +1,291 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + String, + Sequence, + Bool, + NoneSet, + Set, + Integer, + Float, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.styles.colors import Color, ColorDescriptor +from openpyxl.styles.differential import DifferentialStyle + +from openpyxl.utils.cell import COORD_RE + + +class ValueDescriptor(Float): + """ + Expected type depends upon type attribute of parent :-( + + Most values should be numeric BUT they can also be cell references + """ + + def __set__(self, instance, value): + ref = None + if value is not None and isinstance(value, str): + ref = COORD_RE.match(value) + if instance.type == "formula" or ref: + self.expected_type = str + else: + self.expected_type = float + super().__set__(instance, value) + + +class FormatObject(Serialisable): + + tagname = "cfvo" + + type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 'percentile'])) + val = ValueDescriptor(allow_none=True) + gte = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + type, + val=None, + gte=None, + extLst=None, + ): + self.type = type + self.val = val + self.gte = gte + + +class RuleType(Serialisable): + + cfvo = Sequence(expected_type=FormatObject) + + +class IconSet(RuleType): + + tagname = "iconSet" + + iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags', + '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2', + '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights', + '5Arrows', '5ArrowsGray', '5Rating', '5Quarters'])) + showValue = Bool(allow_none=True) + percent = Bool(allow_none=True) + reverse = Bool(allow_none=True) + + __elements__ = ("cfvo",) + + def __init__(self, + iconSet=None, + showValue=None, + percent=None, + reverse=None, + cfvo=None, + ): + self.iconSet = iconSet + self.showValue = showValue + self.percent = percent + self.reverse = reverse + self.cfvo = cfvo + + +class DataBar(RuleType): + + tagname = "dataBar" + + minLength = Integer(allow_none=True) + maxLength = Integer(allow_none=True) + showValue = Bool(allow_none=True) + color = ColorDescriptor() + + __elements__ = ('cfvo', 'color') + + def __init__(self, + minLength=None, + maxLength=None, + showValue=None, + cfvo=None, + color=None, + ): + self.minLength = minLength + self.maxLength = maxLength + self.showValue = showValue + self.cfvo = cfvo + self.color = color + + +class ColorScale(RuleType): + + tagname = "colorScale" + + color = Sequence(expected_type=Color) + + __elements__ = ('cfvo', 'color') + + def __init__(self, + cfvo=None, + color=None, + ): + self.cfvo = cfvo + self.color = color + + +class Rule(Serialisable): + + tagname = "cfRule" + + type = Set(values=(['expression', 'cellIs', 'colorScale', 'dataBar', + 'iconSet', 'top10', 'uniqueValues', 'duplicateValues', 'containsText', + 'notContainsText', 'beginsWith', 'endsWith', 'containsBlanks', + 'notContainsBlanks', 'containsErrors', 'notContainsErrors', 'timePeriod', + 'aboveAverage'])) + dxfId = Integer(allow_none=True) + priority = Integer() + stopIfTrue = Bool(allow_none=True) + aboveAverage = Bool(allow_none=True) + percent = Bool(allow_none=True) + bottom = Bool(allow_none=True) + operator = NoneSet(values=(['lessThan', 'lessThanOrEqual', 'equal', + 'notEqual', 'greaterThanOrEqual', 'greaterThan', 'between', 'notBetween', + 'containsText', 'notContains', 'beginsWith', 'endsWith'])) + text = String(allow_none=True) + timePeriod = NoneSet(values=(['today', 'yesterday', 'tomorrow', 'last7Days', + 'thisMonth', 'lastMonth', 'nextMonth', 'thisWeek', 'lastWeek', + 'nextWeek'])) + rank = Integer(allow_none=True) + stdDev = Integer(allow_none=True) + equalAverage = Bool(allow_none=True) + formula = Sequence(expected_type=str) + colorScale = Typed(expected_type=ColorScale, allow_none=True) + dataBar = Typed(expected_type=DataBar, allow_none=True) + iconSet = Typed(expected_type=IconSet, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + dxf = Typed(expected_type=DifferentialStyle, allow_none=True) + + __elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula') + __attrs__ = ('type', 'rank', 'priority', 'equalAverage', 'operator', + 'aboveAverage', 'dxfId', 'stdDev', 'stopIfTrue', 'timePeriod', 'text', + 'percent', 'bottom') + + + def __init__(self, + type, + dxfId=None, + priority=0, + stopIfTrue=None, + aboveAverage=None, + percent=None, + bottom=None, + operator=None, + text=None, + timePeriod=None, + rank=None, + stdDev=None, + equalAverage=None, + formula=(), + colorScale=None, + dataBar=None, + iconSet=None, + extLst=None, + dxf=None, + ): + self.type = type + self.dxfId = dxfId + self.priority = priority + self.stopIfTrue = stopIfTrue + self.aboveAverage = aboveAverage + self.percent = percent + self.bottom = bottom + self.operator = operator + self.text = text + self.timePeriod = timePeriod + self.rank = rank + self.stdDev = stdDev + self.equalAverage = equalAverage + self.formula = formula + self.colorScale = colorScale + self.dataBar = dataBar + self.iconSet = iconSet + self.dxf = dxf + + +def ColorScaleRule(start_type=None, + start_value=None, + start_color=None, + mid_type=None, + mid_value=None, + mid_color=None, + end_type=None, + end_value=None, + end_color=None): + + """Backwards compatibility""" + formats = [] + if start_type is not None: + formats.append(FormatObject(type=start_type, val=start_value)) + if mid_type is not None: + formats.append(FormatObject(type=mid_type, val=mid_value)) + if end_type is not None: + formats.append(FormatObject(type=end_type, val=end_value)) + colors = [] + for v in (start_color, mid_color, end_color): + if v is not None: + if not isinstance(v, Color): + v = Color(v) + colors.append(v) + cs = ColorScale(cfvo=formats, color=colors) + rule = Rule(type="colorScale", colorScale=cs) + return rule + + +def FormulaRule(formula=None, stopIfTrue=None, font=None, border=None, + fill=None): + """ + Conditional formatting with custom differential style + """ + rule = Rule(type="expression", formula=formula, stopIfTrue=stopIfTrue) + rule.dxf = DifferentialStyle(font=font, border=border, fill=fill) + return rule + + +def CellIsRule(operator=None, formula=None, stopIfTrue=None, font=None, border=None, fill=None): + """ + Conditional formatting rule based on cell contents. + """ + # Excel doesn't use >, >=, etc, but allow for ease of python development + expand = {">": "greaterThan", ">=": "greaterThanOrEqual", "<": "lessThan", "<=": "lessThanOrEqual", + "=": "equal", "==": "equal", "!=": "notEqual"} + + operator = expand.get(operator, operator) + + rule = Rule(type='cellIs', operator=operator, formula=formula, stopIfTrue=stopIfTrue) + rule.dxf = DifferentialStyle(font=font, border=border, fill=fill) + + return rule + + +def IconSetRule(icon_style=None, type=None, values=None, showValue=None, percent=None, reverse=None): + """ + Convenience function for creating icon set rules + """ + cfvo = [] + for val in values: + cfvo.append(FormatObject(type, val)) + icon_set = IconSet(iconSet=icon_style, cfvo=cfvo, showValue=showValue, + percent=percent, reverse=reverse) + rule = Rule(type='iconSet', iconSet=icon_set) + + return rule + + +def DataBarRule(start_type=None, start_value=None, end_type=None, + end_value=None, color=None, showValue=None, minLength=None, maxLength=None): + start = FormatObject(start_type, start_value) + end = FormatObject(end_type, end_value) + data_bar = DataBar(cfvo=[start, end], color=color, showValue=showValue, + minLength=minLength, maxLength=maxLength) + rule = Rule(type='dataBar', dataBar=data_bar) + + return rule diff --git a/venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py new file mode 100644 index 0000000..a98a0c4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formula/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2010-2024 openpyxl + +from .tokenizer import Tokenizer diff --git a/venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py b/venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py new file mode 100644 index 0000000..9bf2624 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formula/tokenizer.py @@ -0,0 +1,446 @@ +""" +This module contains a tokenizer for Excel formulae. + +The tokenizer is based on the Javascript tokenizer found at +http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html written by Eric +Bachtal +""" + +import re + + +class TokenizerError(Exception): + """Base class for all Tokenizer errors.""" + + +class Tokenizer: + + """ + A tokenizer for Excel worksheet formulae. + + Converts a str string representing an Excel formula (in A1 notation) + into a sequence of `Token` objects. + + `formula`: The str string to tokenize + + Tokenizer defines a method `._parse()` to parse the formula into tokens, + which can then be accessed through the `.items` attribute. + + """ + + SN_RE = re.compile("^[1-9](\\.[0-9]+)?[Ee]$") # Scientific notation + WSPACE_RE = re.compile(r"[ \n]+") + STRING_REGEXES = { + # Inside a string, all characters are treated as literals, except for + # the quote character used to start the string. That character, when + # doubled is treated as a single character in the string. If an + # unmatched quote appears, the string is terminated. + '"': re.compile('"(?:[^"]*"")*[^"]*"(?!")'), + "'": re.compile("'(?:[^']*'')*[^']*'(?!')"), + } + ERROR_CODES = ("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", + "#NUM!", "#N/A", "#GETTING_DATA") + TOKEN_ENDERS = ',;}) +-*/^&=><%' # Each of these characters, marks the + # end of an operand token + + def __init__(self, formula): + self.formula = formula + self.items = [] + self.token_stack = [] # Used to keep track of arrays, functions, and + # parentheses + self.offset = 0 # How many chars have we read + self.token = [] # Used to build up token values char by char + self._parse() + + def _parse(self): + """Populate self.items with the tokens from the formula.""" + if self.offset: + return # Already parsed! + if not self.formula: + return + elif self.formula[0] == '=': + self.offset += 1 + else: + self.items.append(Token(self.formula, Token.LITERAL)) + return + consumers = ( + ('"\'', self._parse_string), + ('[', self._parse_brackets), + ('#', self._parse_error), + (' ', self._parse_whitespace), + ('\n', self._parse_whitespace), + ('+-*/^&=><%', self._parse_operator), + ('{(', self._parse_opener), + (')}', self._parse_closer), + (';,', self._parse_separator), + ) + dispatcher = {} # maps chars to the specific parsing function + for chars, consumer in consumers: + dispatcher.update(dict.fromkeys(chars, consumer)) + while self.offset < len(self.formula): + if self.check_scientific_notation(): # May consume one character + continue + curr_char = self.formula[self.offset] + if curr_char in self.TOKEN_ENDERS: + self.save_token() + if curr_char in dispatcher: + self.offset += dispatcher[curr_char]() + else: + # TODO: this can probably be sped up using a regex to get to + # the next interesting character + self.token.append(curr_char) + self.offset += 1 + self.save_token() + + def _parse_string(self): + """ + Parse a "-delimited string or '-delimited link. + + The offset must be pointing to either a single quote ("'") or double + quote ('"') character. The strings are parsed according to Excel + rules where to escape the delimiter you just double it up. E.g., + "abc""def" in Excel is parsed as 'abc"def' in Python. + + Returns the number of characters matched. (Does not update + self.offset) + + """ + self.assert_empty_token(can_follow=':') + delim = self.formula[self.offset] + assert delim in ('"', "'") + regex = self.STRING_REGEXES[delim] + match = regex.match(self.formula[self.offset:]) + if match is None: + subtype = "string" if delim == '"' else 'link' + raise TokenizerError(f"Reached end of formula while parsing {subtype} in {self.formula}") + match = match.group(0) + if delim == '"': + self.items.append(Token.make_operand(match)) + else: + self.token.append(match) + return len(match) + + def _parse_brackets(self): + """ + Consume all the text between square brackets []. + + Returns the number of characters matched. (Does not update + self.offset) + + """ + assert self.formula[self.offset] == '[' + lefts = [(t.start(), 1) for t in + re.finditer(r"\[", self.formula[self.offset:])] + rights = [(t.start(), -1) for t in + re.finditer(r"\]", self.formula[self.offset:])] + + open_count = 0 + for idx, open_close in sorted(lefts + rights): + open_count += open_close + if open_count == 0: + outer_right = idx + 1 + self.token.append( + self.formula[self.offset:self.offset + outer_right]) + return outer_right + + raise TokenizerError(f"Encountered unmatched '[' in {self.formula}") + + def _parse_error(self): + """ + Consume the text following a '#' as an error. + + Looks for a match in self.ERROR_CODES and returns the number of + characters matched. (Does not update self.offset) + + """ + self.assert_empty_token(can_follow='!') + assert self.formula[self.offset] == '#' + subformula = self.formula[self.offset:] + for err in self.ERROR_CODES: + if subformula.startswith(err): + self.items.append(Token.make_operand(''.join(self.token) + err)) + del self.token[:] + return len(err) + raise TokenizerError(f"Invalid error code at position {self.offset} in '{self.formula}'") + + def _parse_whitespace(self): + """ + Consume a string of consecutive spaces. + + Returns the number of spaces found. (Does not update self.offset). + + """ + assert self.formula[self.offset] in (' ', '\n') + self.items.append(Token(self.formula[self.offset], Token.WSPACE)) + return self.WSPACE_RE.match(self.formula[self.offset:]).end() + + def _parse_operator(self): + """ + Consume the characters constituting an operator. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + if self.formula[self.offset:self.offset + 2] in ('>=', '<=', '<>'): + self.items.append(Token( + self.formula[self.offset:self.offset + 2], + Token.OP_IN + )) + return 2 + curr_char = self.formula[self.offset] # guaranteed to be 1 char + assert curr_char in '%*/^&=><+-' + if curr_char == '%': + token = Token('%', Token.OP_POST) + elif curr_char in "*/^&=><": + token = Token(curr_char, Token.OP_IN) + # From here on, curr_char is guaranteed to be in '+-' + elif not self.items: + token = Token(curr_char, Token.OP_PRE) + else: + prev = next((i for i in reversed(self.items) + if i.type != Token.WSPACE), None) + is_infix = prev and ( + prev.subtype == Token.CLOSE + or prev.type == Token.OP_POST + or prev.type == Token.OPERAND + ) + if is_infix: + token = Token(curr_char, Token.OP_IN) + else: + token = Token(curr_char, Token.OP_PRE) + self.items.append(token) + return 1 + + def _parse_opener(self): + """ + Consumes a ( or { character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + assert self.formula[self.offset] in ('(', '{') + if self.formula[self.offset] == '{': + self.assert_empty_token() + token = Token.make_subexp("{") + elif self.token: + token_value = "".join(self.token) + '(' + del self.token[:] + token = Token.make_subexp(token_value) + else: + token = Token.make_subexp("(") + self.items.append(token) + self.token_stack.append(token) + return 1 + + def _parse_closer(self): + """ + Consumes a } or ) character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + assert self.formula[self.offset] in (')', '}') + token = self.token_stack.pop().get_closer() + if token.value != self.formula[self.offset]: + raise TokenizerError( + "Mismatched ( and { pair in '%s'" % self.formula) + self.items.append(token) + return 1 + + def _parse_separator(self): + """ + Consumes a ; or , character. + + Returns the number of characters consumed. (Does not update + self.offset) + + """ + curr_char = self.formula[self.offset] + assert curr_char in (';', ',') + if curr_char == ';': + token = Token.make_separator(";") + else: + try: + top_type = self.token_stack[-1].type + except IndexError: + token = Token(",", Token.OP_IN) # Range Union operator + else: + if top_type == Token.PAREN: + token = Token(",", Token.OP_IN) # Range Union operator + else: + token = Token.make_separator(",") + self.items.append(token) + return 1 + + def check_scientific_notation(self): + """ + Consumes a + or - character if part of a number in sci. notation. + + Returns True if the character was consumed and self.offset was + updated, False otherwise. + + """ + curr_char = self.formula[self.offset] + if (curr_char in '+-' + and len(self.token) >= 1 + and self.SN_RE.match("".join(self.token))): + self.token.append(curr_char) + self.offset += 1 + return True + return False + + def assert_empty_token(self, can_follow=()): + """ + Ensure that there's no token currently being parsed. + + Or if there is a token being parsed, it must end with a character in + can_follow. + + If there are unconsumed token contents, it means we hit an unexpected + token transition. In this case, we raise a TokenizerError + + """ + if self.token and self.token[-1] not in can_follow: + raise TokenizerError(f"Unexpected character at position {self.offset} in '{self.formula}'") + + def save_token(self): + """If there's a token being parsed, add it to the item list.""" + if self.token: + self.items.append(Token.make_operand("".join(self.token))) + del self.token[:] + + def render(self): + """Convert the parsed tokens back to a string.""" + if not self.items: + return "" + elif self.items[0].type == Token.LITERAL: + return self.items[0].value + return "=" + "".join(token.value for token in self.items) + + +class Token: + + """ + A token in an Excel formula. + + Tokens have three attributes: + + * `value`: The string value parsed that led to this token + * `type`: A string identifying the type of token + * `subtype`: A string identifying subtype of the token (optional, and + defaults to "") + + """ + + __slots__ = ['value', 'type', 'subtype'] + + LITERAL = "LITERAL" + OPERAND = "OPERAND" + FUNC = "FUNC" + ARRAY = "ARRAY" + PAREN = "PAREN" + SEP = "SEP" + OP_PRE = "OPERATOR-PREFIX" + OP_IN = "OPERATOR-INFIX" + OP_POST = "OPERATOR-POSTFIX" + WSPACE = "WHITE-SPACE" + + def __init__(self, value, type_, subtype=""): + self.value = value + self.type = type_ + self.subtype = subtype + + # Literal operands: + # + # Literal operands are always of type 'OPERAND' and can be of subtype + # 'TEXT' (for text strings), 'NUMBER' (for all numeric types), 'LOGICAL' + # (for TRUE and FALSE), 'ERROR' (for literal error values), or 'RANGE' + # (for all range references). + + TEXT = 'TEXT' + NUMBER = 'NUMBER' + LOGICAL = 'LOGICAL' + ERROR = 'ERROR' + RANGE = 'RANGE' + + def __repr__(self): + return u"{0} {1} {2}:".format(self.type, self.subtype, self.value) + + @classmethod + def make_operand(cls, value): + """Create an operand token.""" + if value.startswith('"'): + subtype = cls.TEXT + elif value.startswith('#'): + subtype = cls.ERROR + elif value in ('TRUE', 'FALSE'): + subtype = cls.LOGICAL + else: + try: + float(value) + subtype = cls.NUMBER + except ValueError: + subtype = cls.RANGE + return cls(value, cls.OPERAND, subtype) + + + # Subexpresssions + # + # There are 3 types of `Subexpressions`: functions, array literals, and + # parentheticals. Subexpressions have 'OPEN' and 'CLOSE' tokens. 'OPEN' + # is used when parsing the initial expression token (i.e., '(' or '{') + # and 'CLOSE' is used when parsing the closing expression token ('}' or + # ')'). + + OPEN = "OPEN" + CLOSE = "CLOSE" + + @classmethod + def make_subexp(cls, value, func=False): + """ + Create a subexpression token. + + `value`: The value of the token + `func`: If True, force the token to be of type FUNC + + """ + assert value[-1] in ('{', '}', '(', ')') + if func: + assert re.match('.+\\(|\\)', value) + type_ = Token.FUNC + elif value in '{}': + type_ = Token.ARRAY + elif value in '()': + type_ = Token.PAREN + else: + type_ = Token.FUNC + subtype = cls.CLOSE if value in ')}' else cls.OPEN + return cls(value, type_, subtype) + + def get_closer(self): + """Return a closing token that matches this token's type.""" + assert self.type in (self.FUNC, self.ARRAY, self.PAREN) + assert self.subtype == self.OPEN + value = "}" if self.type == self.ARRAY else ")" + return self.make_subexp(value, func=self.type == self.FUNC) + + # Separator tokens + # + # Argument separators always have type 'SEP' and can have one of two + # subtypes: 'ARG', 'ROW'. 'ARG' is used for the ',' token, when used to + # delimit either function arguments or array elements. 'ROW' is used for + # the ';' token, which is always used to delimit rows in an array + # literal. + + ARG = "ARG" + ROW = "ROW" + + @classmethod + def make_separator(cls, value): + """Create a separator token""" + assert value in (',', ';') + subtype = cls.ARG if value == ',' else cls.ROW + return cls(value, cls.SEP, subtype) diff --git a/venv/lib/python3.12/site-packages/openpyxl/formula/translate.py b/venv/lib/python3.12/site-packages/openpyxl/formula/translate.py new file mode 100644 index 0000000..a7e90ec --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/formula/translate.py @@ -0,0 +1,166 @@ +""" +This module contains code to translate formulae across cells in a worksheet. + +The idea is that if A1 has formula "=B1+C1", then translating it to cell A2 +results in formula "=B2+C2". The algorithm relies on the formula tokenizer +to identify the parts of the formula that need to change. + +""" + +import re +from .tokenizer import Tokenizer, Token +from openpyxl.utils import ( + coordinate_to_tuple, + column_index_from_string, + get_column_letter +) + +class TranslatorError(Exception): + """ + Raised when a formula can't be translated across cells. + + This error arises when a formula's references would be translated outside + the worksheet's bounds on the top or left. Excel represents these + situations with a #REF! literal error. E.g., if the formula at B2 is + '=A1', attempting to translate the formula to B1 raises TranslatorError, + since there's no cell above A1. Similarly, translating the same formula + from B2 to A2 raises TranslatorError, since there's no cell to the left of + A1. + + """ + + +class Translator: + + """ + Modifies a formula so that it can be translated from one cell to another. + + `formula`: The str string to translate. Must include the leading '=' + character. + `origin`: The cell address (in A1 notation) where this formula was + defined (excluding the worksheet name). + + """ + + def __init__(self, formula, origin): + # Excel errors out when a workbook has formulae in R1C1 notation, + # regardless of the calcPr:refMode setting, so I'm assuming the + # formulae stored in the workbook must be in A1 notation. + self.row, self.col = coordinate_to_tuple(origin) + self.tokenizer = Tokenizer(formula) + + def get_tokens(self): + "Returns a list with the tokens comprising the formula." + return self.tokenizer.items + + ROW_RANGE_RE = re.compile(r"(\$?[1-9][0-9]{0,6}):(\$?[1-9][0-9]{0,6})$") + COL_RANGE_RE = re.compile(r"(\$?[A-Za-z]{1,3}):(\$?[A-Za-z]{1,3})$") + CELL_REF_RE = re.compile(r"(\$?[A-Za-z]{1,3})(\$?[1-9][0-9]{0,6})$") + + @staticmethod + def translate_row(row_str, rdelta): + """ + Translate a range row-snippet by the given number of rows. + """ + if row_str.startswith('$'): + return row_str + else: + new_row = int(row_str) + rdelta + if new_row <= 0: + raise TranslatorError("Formula out of range") + return str(new_row) + + @staticmethod + def translate_col(col_str, cdelta): + """ + Translate a range col-snippet by the given number of columns + """ + if col_str.startswith('$'): + return col_str + else: + try: + return get_column_letter( + column_index_from_string(col_str) + cdelta) + except ValueError: + raise TranslatorError("Formula out of range") + + @staticmethod + def strip_ws_name(range_str): + "Splits out the worksheet reference, if any, from a range reference." + # This code assumes that named ranges cannot contain any exclamation + # marks. Excel refuses to create these (even using VBA), and + # complains of a corrupt workbook when there are names with + # exclamation marks. The ECMA spec only states that named ranges will + # be of `ST_Xstring` type, which in theory allows '!' (char code + # 0x21) per http://www.w3.org/TR/xml/#charsets + if '!' in range_str: + sheet, range_str = range_str.rsplit('!', 1) + return sheet + "!", range_str + return "", range_str + + @classmethod + def translate_range(cls, range_str, rdelta, cdelta): + """ + Translate an A1-style range reference to the destination cell. + + `rdelta`: the row offset to add to the range + `cdelta`: the column offset to add to the range + `range_str`: an A1-style reference to a range. Potentially includes + the worksheet reference. Could also be a named range. + + """ + ws_part, range_str = cls.strip_ws_name(range_str) + match = cls.ROW_RANGE_RE.match(range_str) # e.g. `3:4` + if match is not None: + return (ws_part + cls.translate_row(match.group(1), rdelta) + ":" + + cls.translate_row(match.group(2), rdelta)) + match = cls.COL_RANGE_RE.match(range_str) # e.g. `A:BC` + if match is not None: + return (ws_part + cls.translate_col(match.group(1), cdelta) + ':' + + cls.translate_col(match.group(2), cdelta)) + if ':' in range_str: # e.g. `A1:B5` + # The check is necessarily general because range references can + # have one or both endpoints specified by named ranges. I.e., + # `named_range:C2`, `C2:named_range`, and `name1:name2` are all + # valid references. Further, Excel allows chaining multiple + # colons together (with unclear meaning) + return ws_part + ":".join( + cls.translate_range(piece, rdelta, cdelta) + for piece in range_str.split(':')) + match = cls.CELL_REF_RE.match(range_str) + if match is None: # Must be a named range + return range_str + return (ws_part + cls.translate_col(match.group(1), cdelta) + + cls.translate_row(match.group(2), rdelta)) + + def translate_formula(self, dest=None, row_delta=0, col_delta=0): + """ + Convert the formula into A1 notation, or as row and column coordinates + + The formula is converted into A1 assuming it is assigned to the cell + whose address is `dest` (no worksheet name). + + """ + tokens = self.get_tokens() + if not tokens: + return "" + elif tokens[0].type == Token.LITERAL: + return tokens[0].value + out = ['='] + # per the spec: + # A compliant producer or consumer considers a defined name in the + # range A1-XFD1048576 to be an error. All other names outside this + # range can be defined as names and overrides a cell reference if an + # ambiguity exists. (I.18.2.5) + if dest: + row, col = coordinate_to_tuple(dest) + row_delta = row - self.row + col_delta = col - self.col + for token in tokens: + if (token.type == Token.OPERAND + and token.subtype == Token.RANGE): + out.append(self.translate_range(token.value, row_delta, + col_delta)) + else: + out.append(token.value) + return "".join(out) diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py new file mode 100644 index 0000000..c3085ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py @@ -0,0 +1,3 @@ +""" +Stuff related to Office OpenXML packaging: relationships, archive, content types. +""" diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/core.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/core.py new file mode 100644 index 0000000..4515373 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/core.py @@ -0,0 +1,115 @@ +# Copyright (c) 2010-2024 openpyxl + +import datetime + +from openpyxl.descriptors import ( + DateTime, + Alias, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.nested import NestedText +from openpyxl.xml.functions import ( + Element, + QName, +) +from openpyxl.xml.constants import ( + COREPROPS_NS, + DCORE_NS, + XSI_NS, + DCTERMS_NS, +) + + +class NestedDateTime(DateTime, NestedText): + + expected_type = datetime.datetime + + def to_tree(self, tagname=None, value=None, namespace=None): + namespace = getattr(self, "namespace", namespace) + if namespace is not None: + tagname = "{%s}%s" % (namespace, tagname) + el = Element(tagname) + if value is not None: + value = value.replace(tzinfo=None) + el.text = value.isoformat(timespec="seconds") + 'Z' + return el + + +class QualifiedDateTime(NestedDateTime): + + """In certain situations Excel will complain if the additional type + attribute isn't set""" + + def to_tree(self, tagname=None, value=None, namespace=None): + el = super().to_tree(tagname, value, namespace) + el.set("{%s}type" % XSI_NS, QName(DCTERMS_NS, "W3CDTF")) + return el + + +class DocumentProperties(Serialisable): + """High-level properties of the document. + Defined in ECMA-376 Par2 Annex D + """ + + tagname = "coreProperties" + namespace = COREPROPS_NS + + category = NestedText(expected_type=str, allow_none=True) + contentStatus = NestedText(expected_type=str, allow_none=True) + keywords = NestedText(expected_type=str, allow_none=True) + lastModifiedBy = NestedText(expected_type=str, allow_none=True) + lastPrinted = NestedDateTime(allow_none=True) + revision = NestedText(expected_type=str, allow_none=True) + version = NestedText(expected_type=str, allow_none=True) + last_modified_by = Alias("lastModifiedBy") + + # Dublin Core Properties + subject = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + title = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + creator = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + description = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + identifier = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + language = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS) + # Dublin Core Terms + created = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC + modified = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC + + __elements__ = ("creator", "title", "description", "subject","identifier", + "language", "created", "modified", "lastModifiedBy", "category", + "contentStatus", "version", "revision", "keywords", "lastPrinted", + ) + + + def __init__(self, + category=None, + contentStatus=None, + keywords=None, + lastModifiedBy=None, + lastPrinted=None, + revision=None, + version=None, + created=None, + creator="openpyxl", + description=None, + identifier=None, + language=None, + modified=None, + subject=None, + title=None, + ): + now = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None) + self.contentStatus = contentStatus + self.lastPrinted = lastPrinted + self.revision = revision + self.version = version + self.creator = creator + self.lastModifiedBy = lastModifiedBy + self.modified = modified or now + self.created = created or now + self.title = title + self.subject = subject + self.description = description + self.identifier = identifier + self.language = language + self.keywords = keywords + self.category = category diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py new file mode 100644 index 0000000..7e253d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py @@ -0,0 +1,289 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Implementation of custom properties see § 22.3 in the specification""" + + +from warnings import warn + +from openpyxl.descriptors import Strict +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.sequence import Sequence +from openpyxl.descriptors import ( + Alias, + String, + Integer, + Float, + DateTime, + Bool, +) +from openpyxl.descriptors.nested import ( + NestedText, +) + +from openpyxl.xml.constants import ( + CUSTPROPS_NS, + VTYPES_NS, + CPROPS_FMTID, +) + +from .core import NestedDateTime + + +class NestedBoolText(Bool, NestedText): + """ + Descriptor for handling nested elements with the value stored in the text part + """ + + pass + + +class _CustomDocumentProperty(Serialisable): + + """ + Low-level representation of a Custom Document Property. + Not used directly + Must always contain a child element, even if this is empty + """ + + tagname = "property" + _typ = None + + name = String(allow_none=True) + lpwstr = NestedText(expected_type=str, allow_none=True, namespace=VTYPES_NS) + i4 = NestedText(expected_type=int, allow_none=True, namespace=VTYPES_NS) + r8 = NestedText(expected_type=float, allow_none=True, namespace=VTYPES_NS) + filetime = NestedDateTime(allow_none=True, namespace=VTYPES_NS) + bool = NestedBoolText(expected_type=bool, allow_none=True, namespace=VTYPES_NS) + linkTarget = String(expected_type=str, allow_none=True) + fmtid = String() + pid = Integer() + + def __init__(self, + name=None, + pid=0, + fmtid=CPROPS_FMTID, + linkTarget=None, + **kw): + self.fmtid = fmtid + self.pid = pid + self.name = name + self._typ = None + self.linkTarget = linkTarget + + for k, v in kw.items(): + setattr(self, k, v) + setattr(self, "_typ", k) # ugh! + for e in self.__elements__: + if e not in kw: + setattr(self, e, None) + + + @property + def type(self): + if self._typ is not None: + return self._typ + for a in self.__elements__: + if getattr(self, a) is not None: + return a + if self.linkTarget is not None: + return "linkTarget" + + + def to_tree(self, tagname=None, idx=None, namespace=None): + child = getattr(self, self._typ, None) + if child is None: + setattr(self, self._typ, "") + + return super().to_tree(tagname=None, idx=None, namespace=None) + + +class _CustomDocumentPropertyList(Serialisable): + + """ + Parses and seriliases property lists but is not used directly + """ + + tagname = "Properties" + + property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS) + customProps = Alias("property") + + + def __init__(self, property=()): + self.property = property + + + def __len__(self): + return len(self.property) + + + def to_tree(self, tagname=None, idx=None, namespace=None): + for idx, p in enumerate(self.property, 2): + p.pid = idx + tree = super().to_tree(tagname, idx, namespace) + tree.set("xmlns", CUSTPROPS_NS) + + return tree + + +class _TypedProperty(Strict): + + name = String() + + def __init__(self, + name, + value): + self.name = name + self.value = value + + + def __eq__(self, other): + return self.name == other.name and self.value == other.value + + + def __repr__(self): + return f"{self.__class__.__name__}, name={self.name}, value={self.value}" + + +class IntProperty(_TypedProperty): + + value = Integer() + + +class FloatProperty(_TypedProperty): + + value = Float() + + +class StringProperty(_TypedProperty): + + value = String(allow_none=True) + + +class DateTimeProperty(_TypedProperty): + + value = DateTime() + + +class BoolProperty(_TypedProperty): + + value = Bool() + + +class LinkProperty(_TypedProperty): + + value = String() + + +# from Python +CLASS_MAPPING = { + StringProperty: "lpwstr", + IntProperty: "i4", + FloatProperty: "r8", + DateTimeProperty: "filetime", + BoolProperty: "bool", + LinkProperty: "linkTarget" +} + +XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()} + + +class CustomPropertyList(Strict): + + + props = Sequence(expected_type=_TypedProperty) + + def __init__(self): + self.props = [] + + + @classmethod + def from_tree(cls, tree): + """ + Create list from OOXML element + """ + prop_list = _CustomDocumentPropertyList.from_tree(tree) + props = [] + + for prop in prop_list.property: + attr = prop.type + + typ = XML_MAPPING.get(attr, None) + if not typ: + warn(f"Unknown type for {prop.name}") + continue + value = getattr(prop, attr) + link = prop.linkTarget + if link is not None: + typ = LinkProperty + value = prop.linkTarget + + new_prop = typ(name=prop.name, value=value) + props.append(new_prop) + + new_prop_list = cls() + new_prop_list.props = props + return new_prop_list + + + def append(self, prop): + if prop.name in self.names: + raise ValueError(f"Property with name {prop.name} already exists") + + self.props.append(prop) + + + def to_tree(self): + props = [] + + for p in self.props: + attr = CLASS_MAPPING.get(p.__class__, None) + if not attr: + raise TypeError("Unknown adapter for {p}") + np = _CustomDocumentProperty(name=p.name, **{attr:p.value}) + if isinstance(p, LinkProperty): + np._typ = "lpwstr" + #np.lpwstr = "" + props.append(np) + + prop_list = _CustomDocumentPropertyList(property=props) + return prop_list.to_tree() + + + def __len__(self): + return len(self.props) + + + @property + def names(self): + """List of property names""" + return [p.name for p in self.props] + + + def __getitem__(self, name): + """ + Get property by name + """ + for p in self.props: + if p.name == name: + return p + raise KeyError(f"Property with name {name} not found") + + + def __delitem__(self, name): + """ + Delete a propery by name + """ + for idx, p in enumerate(self.props): + if p.name == name: + self.props.pop(idx) + return + raise KeyError(f"Property with name {name} not found") + + + def __repr__(self): + return f"{self.__class__.__name__} containing {self.props}" + + + def __iter__(self): + return iter(self.props) diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py new file mode 100644 index 0000000..fbd794a --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py @@ -0,0 +1,137 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, +) +from openpyxl.descriptors.nested import ( + NestedText, +) + +from openpyxl.xml.constants import XPROPS_NS +from openpyxl import __version__ + + +class DigSigBlob(Serialisable): + + __elements__ = __attrs__ = () + + +class VectorLpstr(Serialisable): + + __elements__ = __attrs__ = () + + +class VectorVariant(Serialisable): + + __elements__ = __attrs__ = () + + +class ExtendedProperties(Serialisable): + + """ + See 22.2 + + Most of this is irrelevant but Excel is very picky about the version number + + It uses XX.YYYY (Version.Build) and expects everyone else to + + We provide Major.Minor and the full version in the application name + """ + + tagname = "Properties" + + Template = NestedText(expected_type=str, allow_none=True) + Manager = NestedText(expected_type=str, allow_none=True) + Company = NestedText(expected_type=str, allow_none=True) + Pages = NestedText(expected_type=int, allow_none=True) + Words = NestedText(expected_type=int,allow_none=True) + Characters = NestedText(expected_type=int, allow_none=True) + PresentationFormat = NestedText(expected_type=str, allow_none=True) + Lines = NestedText(expected_type=int, allow_none=True) + Paragraphs = NestedText(expected_type=int, allow_none=True) + Slides = NestedText(expected_type=int, allow_none=True) + Notes = NestedText(expected_type=int, allow_none=True) + TotalTime = NestedText(expected_type=int, allow_none=True) + HiddenSlides = NestedText(expected_type=int, allow_none=True) + MMClips = NestedText(expected_type=int, allow_none=True) + ScaleCrop = NestedText(expected_type=bool, allow_none=True) + HeadingPairs = Typed(expected_type=VectorVariant, allow_none=True) + TitlesOfParts = Typed(expected_type=VectorLpstr, allow_none=True) + LinksUpToDate = NestedText(expected_type=bool, allow_none=True) + CharactersWithSpaces = NestedText(expected_type=int, allow_none=True) + SharedDoc = NestedText(expected_type=bool, allow_none=True) + HyperlinkBase = NestedText(expected_type=str, allow_none=True) + HLinks = Typed(expected_type=VectorVariant, allow_none=True) + HyperlinksChanged = NestedText(expected_type=bool, allow_none=True) + DigSig = Typed(expected_type=DigSigBlob, allow_none=True) + Application = NestedText(expected_type=str, allow_none=True) + AppVersion = NestedText(expected_type=str, allow_none=True) + DocSecurity = NestedText(expected_type=int, allow_none=True) + + __elements__ = ('Application', 'AppVersion', 'DocSecurity', 'ScaleCrop', + 'LinksUpToDate', 'SharedDoc', 'HyperlinksChanged') + + def __init__(self, + Template=None, + Manager=None, + Company=None, + Pages=None, + Words=None, + Characters=None, + PresentationFormat=None, + Lines=None, + Paragraphs=None, + Slides=None, + Notes=None, + TotalTime=None, + HiddenSlides=None, + MMClips=None, + ScaleCrop=None, + HeadingPairs=None, + TitlesOfParts=None, + LinksUpToDate=None, + CharactersWithSpaces=None, + SharedDoc=None, + HyperlinkBase=None, + HLinks=None, + HyperlinksChanged=None, + DigSig=None, + Application=None, + AppVersion=None, + DocSecurity=None, + ): + self.Template = Template + self.Manager = Manager + self.Company = Company + self.Pages = Pages + self.Words = Words + self.Characters = Characters + self.PresentationFormat = PresentationFormat + self.Lines = Lines + self.Paragraphs = Paragraphs + self.Slides = Slides + self.Notes = Notes + self.TotalTime = TotalTime + self.HiddenSlides = HiddenSlides + self.MMClips = MMClips + self.ScaleCrop = ScaleCrop + self.HeadingPairs = None + self.TitlesOfParts = None + self.LinksUpToDate = LinksUpToDate + self.CharactersWithSpaces = CharactersWithSpaces + self.SharedDoc = SharedDoc + self.HyperlinkBase = HyperlinkBase + self.HLinks = None + self.HyperlinksChanged = HyperlinksChanged + self.DigSig = None + self.Application = f"Microsoft Excel Compatible / Openpyxl {__version__}" + self.AppVersion = ".".join(__version__.split(".")[:-1]) + self.DocSecurity = DocSecurity + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", XPROPS_NS) + return tree diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py new file mode 100644 index 0000000..cacc046 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py @@ -0,0 +1,56 @@ +# Copyright (c) 2010-2024 openpyxl + +from abc import abstractproperty +from openpyxl.compat.abc import ABC + + +class ISerialisableFile(ABC): + + """ + Interface for Serialisable classes that represent files in the archive + """ + + + @abstractproperty + def id(self): + """ + Object id making it unique + """ + pass + + + @abstractproperty + def _path(self): + """ + File path in the archive + """ + pass + + + @abstractproperty + def _namespace(self): + """ + Qualified namespace when serialised + """ + pass + + + @abstractproperty + def _type(self): + """ + The content type for the manifest + """ + + + @abstractproperty + def _rel_type(self): + """ + The content type for relationships + """ + + + @abstractproperty + def _rel_id(self): + """ + Links object with parent + """ diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py new file mode 100644 index 0000000..41da07f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py @@ -0,0 +1,194 @@ +# Copyright (c) 2010-2024 openpyxl + +""" +File manifest +""" +from mimetypes import MimeTypes +import os.path + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import String, Sequence +from openpyxl.xml.functions import fromstring +from openpyxl.xml.constants import ( + ARC_CONTENT_TYPES, + ARC_THEME, + ARC_STYLE, + THEME_TYPE, + STYLES_TYPE, + CONTYPES_NS, + ACTIVEX, + CTRL, + VBA, +) +from openpyxl.xml.functions import tostring + +# initialise mime-types +mimetypes = MimeTypes() +mimetypes.add_type('application/xml', ".xml") +mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels") +mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin") +mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml") +mimetypes.add_type("image/x-emf", ".emf") + + +class FileExtension(Serialisable): + + tagname = "Default" + + Extension = String() + ContentType = String() + + def __init__(self, Extension, ContentType): + self.Extension = Extension + self.ContentType = ContentType + + +class Override(Serialisable): + + tagname = "Override" + + PartName = String() + ContentType = String() + + def __init__(self, PartName, ContentType): + self.PartName = PartName + self.ContentType = ContentType + + +DEFAULT_TYPES = [ + FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"), + FileExtension("xml", "application/xml"), +] + +DEFAULT_OVERRIDE = [ + Override("/" + ARC_STYLE, STYLES_TYPE), # Styles + Override("/" + ARC_THEME, THEME_TYPE), # Theme + Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"), + Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml") +] + + +class Manifest(Serialisable): + + tagname = "Types" + + Default = Sequence(expected_type=FileExtension, unique=True) + Override = Sequence(expected_type=Override, unique=True) + path = "[Content_Types].xml" + + __elements__ = ("Default", "Override") + + def __init__(self, + Default=(), + Override=(), + ): + if not Default: + Default = DEFAULT_TYPES + self.Default = Default + if not Override: + Override = DEFAULT_OVERRIDE + self.Override = Override + + + @property + def filenames(self): + return [part.PartName for part in self.Override] + + + @property + def extensions(self): + """ + Map content types to file extensions + Skip parts without extensions + """ + exts = {os.path.splitext(part.PartName)[-1] for part in self.Override} + return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext] + + + def to_tree(self): + """ + Custom serialisation method to allow setting a default namespace + """ + defaults = [t.Extension for t in self.Default] + for ext, mime in self.extensions: + if ext not in defaults: + mime = FileExtension(ext, mime) + self.Default.append(mime) + tree = super().to_tree() + tree.set("xmlns", CONTYPES_NS) + return tree + + + def __contains__(self, content_type): + """ + Check whether a particular content type is contained + """ + for t in self.Override: + if t.ContentType == content_type: + return True + + + def find(self, content_type): + """ + Find specific content-type + """ + try: + return next(self.findall(content_type)) + except StopIteration: + return + + + def findall(self, content_type): + """ + Find all elements of a specific content-type + """ + for t in self.Override: + if t.ContentType == content_type: + yield t + + + def append(self, obj): + """ + Add content object to the package manifest + # needs a contract... + """ + ct = Override(PartName=obj.path, ContentType=obj.mime_type) + self.Override.append(ct) + + + def _write(self, archive, workbook): + """ + Write manifest to the archive + """ + self.append(workbook) + self._write_vba(workbook) + self._register_mimetypes(filenames=archive.namelist()) + archive.writestr(self.path, tostring(self.to_tree())) + + + def _register_mimetypes(self, filenames): + """ + Make sure that the mime type for all file extensions is registered + """ + for fn in filenames: + ext = os.path.splitext(fn)[-1] + if not ext: + continue + mime = mimetypes.types_map[True][ext] + fe = FileExtension(ext[1:], mime) + self.Default.append(fe) + + + def _write_vba(self, workbook): + """ + Add content types from cached workbook when keeping VBA + """ + if workbook.vba_archive: + node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES)) + mf = Manifest.from_tree(node) + filenames = self.filenames + for override in mf.Override: + if override.PartName not in (ACTIVEX, CTRL, VBA): + continue + if override.PartName not in filenames: + self.Override.append(override) diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py new file mode 100644 index 0000000..4318282 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py @@ -0,0 +1,158 @@ +# Copyright (c) 2010-2024 openpyxl + +import posixpath +from warnings import warn + +from openpyxl.descriptors import ( + String, + Alias, + Sequence, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.container import ElementList + +from openpyxl.xml.constants import REL_NS, PKG_REL_NS +from openpyxl.xml.functions import ( + Element, + fromstring, +) + + +class Relationship(Serialisable): + """Represents many kinds of relationships.""" + + tagname = "Relationship" + + Type = String() + Target = String() + target = Alias("Target") + TargetMode = String(allow_none=True) + Id = String(allow_none=True) + id = Alias("Id") + + + def __init__(self, + Id=None, + Type=None, + type=None, + Target=None, + TargetMode=None + ): + """ + `type` can be used as a shorthand with the default relationships namespace + otherwise the `Type` must be a fully qualified URL + """ + if type is not None: + Type = "{0}/{1}".format(REL_NS, type) + self.Type = Type + self.Target = Target + self.TargetMode = TargetMode + self.Id = Id + + +class RelationshipList(ElementList): + + tagname = "Relationships" + expected_type = Relationship + + + def append(self, value): + super().append(value) + if not value.Id: + value.Id = f"rId{len(self)}" + + + def find(self, content_type): + """ + Find relationships by content-type + NB. these content-types namespaced objects and different to the MIME-types + in the package manifest :-( + """ + for r in self: + if r.Type == content_type: + yield r + + + def get(self, key): + for r in self: + if r.Id == key: + return r + raise KeyError("Unknown relationship: {0}".format(key)) + + + def to_dict(self): + """Return a dictionary of relations keyed by id""" + return {r.id:r for r in self} + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", PKG_REL_NS) + return tree + + +def get_rels_path(path): + """ + Convert relative path to absolutes that can be loaded from a zip + archive. + The path to be passed in is that of containing object (workbook, + worksheet, etc.) + """ + folder, obj = posixpath.split(path) + filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj)) + return filename + + +def get_dependents(archive, filename): + """ + Normalise dependency file paths to absolute ones + + Relative paths are relative to parent object + """ + src = archive.read(filename) + node = fromstring(src) + try: + rels = RelationshipList.from_tree(node) + except TypeError: + msg = "{0} contains invalid dependency definitions".format(filename) + warn(msg) + rels = RelationshipList() + folder = posixpath.dirname(filename) + parent = posixpath.split(folder)[0] + for r in rels: + if r.TargetMode == "External": + continue + elif r.target.startswith("/"): + r.target = r.target[1:] + else: + pth = posixpath.join(parent, r.target) + r.target = posixpath.normpath(pth) + return rels + + +def get_rel(archive, deps, id=None, cls=None): + """ + Get related object based on id or rel_type + """ + if not any([id, cls]): + raise ValueError("Either the id or the content type are required") + if id is not None: + rel = deps.get(id) + else: + try: + rel = next(deps.find(cls.rel_type)) + except StopIteration: # no known dependency + return + + path = rel.target + src = archive.read(path) + tree = fromstring(src) + obj = cls.from_tree(tree) + + rels_path = get_rels_path(path) + try: + obj.deps = get_dependents(archive, rels_path) + except KeyError: + obj.deps = [] + + return obj diff --git a/venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py b/venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py new file mode 100644 index 0000000..a6413cd --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py @@ -0,0 +1,185 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Alias, + Typed, + String, + Integer, + Bool, + NoneSet, +) +from openpyxl.descriptors.excel import ExtensionList, Relation +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.nested import NestedString + +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.workbook.defined_name import DefinedNameList +from openpyxl.workbook.external_reference import ExternalReference +from openpyxl.workbook.function_group import FunctionGroupList +from openpyxl.workbook.properties import WorkbookProperties, CalcProperties, FileVersion +from openpyxl.workbook.protection import WorkbookProtection, FileSharing +from openpyxl.workbook.smart_tags import SmartTagList, SmartTagProperties +from openpyxl.workbook.views import CustomWorkbookView, BookView +from openpyxl.workbook.web import WebPublishing, WebPublishObjectList + + +class FileRecoveryProperties(Serialisable): + + tagname = "fileRecoveryPr" + + autoRecover = Bool(allow_none=True) + crashSave = Bool(allow_none=True) + dataExtractLoad = Bool(allow_none=True) + repairLoad = Bool(allow_none=True) + + def __init__(self, + autoRecover=None, + crashSave=None, + dataExtractLoad=None, + repairLoad=None, + ): + self.autoRecover = autoRecover + self.crashSave = crashSave + self.dataExtractLoad = dataExtractLoad + self.repairLoad = repairLoad + + +class ChildSheet(Serialisable): + """ + Represents a reference to a worksheet or chartsheet in workbook.xml + + It contains the title, order and state but only an indirect reference to + the objects themselves. + """ + + tagname = "sheet" + + name = String() + sheetId = Integer() + state = NoneSet(values=(['visible', 'hidden', 'veryHidden'])) + id = Relation() + + def __init__(self, + name=None, + sheetId=None, + state="visible", + id=None, + ): + self.name = name + self.sheetId = sheetId + self.state = state + self.id = id + + +class PivotCache(Serialisable): + + tagname = "pivotCache" + + cacheId = Integer() + id = Relation() + + def __init__(self, + cacheId=None, + id=None + ): + self.cacheId = cacheId + self.id = id + + +class WorkbookPackage(Serialisable): + + """ + Represent the workbook file in the archive + """ + + tagname = "workbook" + + conformance = NoneSet(values=['strict', 'transitional']) + fileVersion = Typed(expected_type=FileVersion, allow_none=True) + fileSharing = Typed(expected_type=FileSharing, allow_none=True) + workbookPr = Typed(expected_type=WorkbookProperties, allow_none=True) + properties = Alias("workbookPr") + workbookProtection = Typed(expected_type=WorkbookProtection, allow_none=True) + bookViews = NestedSequence(expected_type=BookView) + sheets = NestedSequence(expected_type=ChildSheet) + functionGroups = Typed(expected_type=FunctionGroupList, allow_none=True) + externalReferences = NestedSequence(expected_type=ExternalReference) + definedNames = Typed(expected_type=DefinedNameList, allow_none=True) + calcPr = Typed(expected_type=CalcProperties, allow_none=True) + oleSize = NestedString(allow_none=True, attribute="ref") + customWorkbookViews = NestedSequence(expected_type=CustomWorkbookView) + pivotCaches = NestedSequence(expected_type=PivotCache, allow_none=True) + smartTagPr = Typed(expected_type=SmartTagProperties, allow_none=True) + smartTagTypes = Typed(expected_type=SmartTagList, allow_none=True) + webPublishing = Typed(expected_type=WebPublishing, allow_none=True) + fileRecoveryPr = Typed(expected_type=FileRecoveryProperties, allow_none=True) + webPublishObjects = Typed(expected_type=WebPublishObjectList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + Ignorable = NestedString(namespace="http://schemas.openxmlformats.org/markup-compatibility/2006", allow_none=True) + + __elements__ = ('fileVersion', 'fileSharing', 'workbookPr', + 'workbookProtection', 'bookViews', 'sheets', 'functionGroups', + 'externalReferences', 'definedNames', 'calcPr', 'oleSize', + 'customWorkbookViews', 'pivotCaches', 'smartTagPr', 'smartTagTypes', + 'webPublishing', 'fileRecoveryPr', 'webPublishObjects') + + def __init__(self, + conformance=None, + fileVersion=None, + fileSharing=None, + workbookPr=None, + workbookProtection=None, + bookViews=(), + sheets=(), + functionGroups=None, + externalReferences=(), + definedNames=None, + calcPr=None, + oleSize=None, + customWorkbookViews=(), + pivotCaches=(), + smartTagPr=None, + smartTagTypes=None, + webPublishing=None, + fileRecoveryPr=None, + webPublishObjects=None, + extLst=None, + Ignorable=None, + ): + self.conformance = conformance + self.fileVersion = fileVersion + self.fileSharing = fileSharing + if workbookPr is None: + workbookPr = WorkbookProperties() + self.workbookPr = workbookPr + self.workbookProtection = workbookProtection + self.bookViews = bookViews + self.sheets = sheets + self.functionGroups = functionGroups + self.externalReferences = externalReferences + self.definedNames = definedNames + self.calcPr = calcPr + self.oleSize = oleSize + self.customWorkbookViews = customWorkbookViews + self.pivotCaches = pivotCaches + self.smartTagPr = smartTagPr + self.smartTagTypes = smartTagTypes + self.webPublishing = webPublishing + self.fileRecoveryPr = fileRecoveryPr + self.webPublishObjects = webPublishObjects + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def active(self): + for view in self.bookViews: + if view.activeTab is not None: + return view.activeTab + return 0 diff --git a/venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py new file mode 100644 index 0000000..ab6cdea --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/pivot/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py b/venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py new file mode 100644 index 0000000..7ae2b4d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/pivot/cache.py @@ -0,0 +1,965 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Bool, + Float, + Set, + NoneSet, + String, + Integer, + DateTime, + Sequence, +) + +from openpyxl.descriptors.excel import ( + HexBinary, + ExtensionList, + Relation, +) +from openpyxl.descriptors.nested import NestedInteger +from openpyxl.descriptors.sequence import ( + NestedSequence, + MultiSequence, + MultiSequencePart, +) +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring +from openpyxl.packaging.relationship import ( + RelationshipList, + Relationship, + get_rels_path +) + +from .table import ( + PivotArea, + Reference, +) +from .fields import ( + Boolean, + Error, + Missing, + Number, + Text, + TupleList, + DateTimeField, +) + +class MeasureDimensionMap(Serialisable): + + tagname = "map" + + measureGroup = Integer(allow_none=True) + dimension = Integer(allow_none=True) + + def __init__(self, + measureGroup=None, + dimension=None, + ): + self.measureGroup = measureGroup + self.dimension = dimension + + +class MeasureGroup(Serialisable): + + tagname = "measureGroup" + + name = String() + caption = String() + + def __init__(self, + name=None, + caption=None, + ): + self.name = name + self.caption = caption + + +class PivotDimension(Serialisable): + + tagname = "dimension" + + measure = Bool() + name = String() + uniqueName = String() + caption = String() + + def __init__(self, + measure=None, + name=None, + uniqueName=None, + caption=None, + ): + self.measure = measure + self.name = name + self.uniqueName = uniqueName + self.caption = caption + + +class CalculatedMember(Serialisable): + + tagname = "calculatedMember" + + name = String() + mdx = String() + memberName = String(allow_none=True) + hierarchy = String(allow_none=True) + parent = String(allow_none=True) + solveOrder = Integer(allow_none=True) + set = Bool() + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + name=None, + mdx=None, + memberName=None, + hierarchy=None, + parent=None, + solveOrder=None, + set=None, + extLst=None, + ): + self.name = name + self.mdx = mdx + self.memberName = memberName + self.hierarchy = hierarchy + self.parent = parent + self.solveOrder = solveOrder + self.set = set + #self.extLst = extLst + + +class CalculatedItem(Serialisable): + + tagname = "calculatedItem" + + field = Integer(allow_none=True) + formula = String() + pivotArea = Typed(expected_type=PivotArea, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotArea', 'extLst') + + def __init__(self, + field=None, + formula=None, + pivotArea=None, + extLst=None, + ): + self.field = field + self.formula = formula + self.pivotArea = pivotArea + self.extLst = extLst + + +class ServerFormat(Serialisable): + + tagname = "serverFormat" + + culture = String(allow_none=True) + format = String(allow_none=True) + + def __init__(self, + culture=None, + format=None, + ): + self.culture = culture + self.format = format + + +class Query(Serialisable): + + tagname = "query" + + mdx = String() + tpls = Typed(expected_type=TupleList, allow_none=True) + + __elements__ = ('tpls',) + + def __init__(self, + mdx=None, + tpls=None, + ): + self.mdx = mdx + self.tpls = tpls + + +class OLAPSet(Serialisable): + + tagname = "set" + + count = Integer() + maxRank = Integer() + setDefinition = String() + sortType = NoneSet(values=(['ascending', 'descending', 'ascendingAlpha', + 'descendingAlpha', 'ascendingNatural', 'descendingNatural'])) + queryFailed = Bool() + tpls = Typed(expected_type=TupleList, allow_none=True) + sortByTuple = Typed(expected_type=TupleList, allow_none=True) + + __elements__ = ('tpls', 'sortByTuple') + + def __init__(self, + count=None, + maxRank=None, + setDefinition=None, + sortType=None, + queryFailed=None, + tpls=None, + sortByTuple=None, + ): + self.count = count + self.maxRank = maxRank + self.setDefinition = setDefinition + self.sortType = sortType + self.queryFailed = queryFailed + self.tpls = tpls + self.sortByTuple = sortByTuple + + +class PCDSDTCEntries(Serialisable): + # Implements CT_PCDSDTCEntries + + tagname = "entries" + + count = Integer(allow_none=True) + # elements are choice + m = Typed(expected_type=Missing, allow_none=True) + n = Typed(expected_type=Number, allow_none=True) + e = Typed(expected_type=Error, allow_none=True) + s = Typed(expected_type=Text, allow_none=True) + + __elements__ = ('m', 'n', 'e', 's') + + def __init__(self, + count=None, + m=None, + n=None, + e=None, + s=None, + ): + self.count = count + self.m = m + self.n = n + self.e = e + self.s = s + + +class TupleCache(Serialisable): + + tagname = "tupleCache" + + entries = Typed(expected_type=PCDSDTCEntries, allow_none=True) + sets = NestedSequence(expected_type=OLAPSet, count=True) + queryCache = NestedSequence(expected_type=Query, count=True) + serverFormats = NestedSequence(expected_type=ServerFormat, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('entries', 'sets', 'queryCache', 'serverFormats', 'extLst') + + def __init__(self, + entries=None, + sets=(), + queryCache=(), + serverFormats=(), + extLst=None, + ): + self.entries = entries + self.sets = sets + self.queryCache = queryCache + self.serverFormats = serverFormats + self.extLst = extLst + + +class OLAPKPI(Serialisable): + + tagname = "kpi" + + uniqueName = String() + caption = String(allow_none=True) + displayFolder = String(allow_none=True) + measureGroup = String(allow_none=True) + parent = String(allow_none=True) + value = String() + goal = String(allow_none=True) + status = String(allow_none=True) + trend = String(allow_none=True) + weight = String(allow_none=True) + time = String(allow_none=True) + + def __init__(self, + uniqueName=None, + caption=None, + displayFolder=None, + measureGroup=None, + parent=None, + value=None, + goal=None, + status=None, + trend=None, + weight=None, + time=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.displayFolder = displayFolder + self.measureGroup = measureGroup + self.parent = parent + self.value = value + self.goal = goal + self.status = status + self.trend = trend + self.weight = weight + self.time = time + + +class GroupMember(Serialisable): + + tagname = "groupMember" + + uniqueName = String() + group = Bool() + + def __init__(self, + uniqueName=None, + group=None, + ): + self.uniqueName = uniqueName + self.group = group + + +class LevelGroup(Serialisable): + + tagname = "group" + + name = String() + uniqueName = String() + caption = String() + uniqueParent = String() + id = Integer() + groupMembers = NestedSequence(expected_type=GroupMember, count=True) + + __elements__ = ('groupMembers',) + + def __init__(self, + name=None, + uniqueName=None, + caption=None, + uniqueParent=None, + id=None, + groupMembers=(), + ): + self.name = name + self.uniqueName = uniqueName + self.caption = caption + self.uniqueParent = uniqueParent + self.id = id + self.groupMembers = groupMembers + + +class GroupLevel(Serialisable): + + tagname = "groupLevel" + + uniqueName = String() + caption = String() + user = Bool() + customRollUp = Bool() + groups = NestedSequence(expected_type=LevelGroup, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('groups', 'extLst') + + def __init__(self, + uniqueName=None, + caption=None, + user=None, + customRollUp=None, + groups=(), + extLst=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.user = user + self.customRollUp = customRollUp + self.groups = groups + self.extLst = extLst + + +class FieldUsage(Serialisable): + + tagname = "fieldUsage" + + x = Integer() + + def __init__(self, + x=None, + ): + self.x = x + + +class CacheHierarchy(Serialisable): + + tagname = "cacheHierarchy" + + uniqueName = String() + caption = String(allow_none=True) + measure = Bool() + set = Bool() + parentSet = Integer(allow_none=True) + iconSet = Integer() + attribute = Bool() + time = Bool() + keyAttribute = Bool() + defaultMemberUniqueName = String(allow_none=True) + allUniqueName = String(allow_none=True) + allCaption = String(allow_none=True) + dimensionUniqueName = String(allow_none=True) + displayFolder = String(allow_none=True) + measureGroup = String(allow_none=True) + measures = Bool() + count = Integer() + oneField = Bool() + memberValueDatatype = Integer(allow_none=True) + unbalanced = Bool(allow_none=True) + unbalancedGroup = Bool(allow_none=True) + hidden = Bool() + fieldsUsage = NestedSequence(expected_type=FieldUsage, count=True) + groupLevels = NestedSequence(expected_type=GroupLevel, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('fieldsUsage', 'groupLevels') + + def __init__(self, + uniqueName="", + caption=None, + measure=None, + set=None, + parentSet=None, + iconSet=0, + attribute=None, + time=None, + keyAttribute=None, + defaultMemberUniqueName=None, + allUniqueName=None, + allCaption=None, + dimensionUniqueName=None, + displayFolder=None, + measureGroup=None, + measures=None, + count=None, + oneField=None, + memberValueDatatype=None, + unbalanced=None, + unbalancedGroup=None, + hidden=None, + fieldsUsage=(), + groupLevels=(), + extLst=None, + ): + self.uniqueName = uniqueName + self.caption = caption + self.measure = measure + self.set = set + self.parentSet = parentSet + self.iconSet = iconSet + self.attribute = attribute + self.time = time + self.keyAttribute = keyAttribute + self.defaultMemberUniqueName = defaultMemberUniqueName + self.allUniqueName = allUniqueName + self.allCaption = allCaption + self.dimensionUniqueName = dimensionUniqueName + self.displayFolder = displayFolder + self.measureGroup = measureGroup + self.measures = measures + self.count = count + self.oneField = oneField + self.memberValueDatatype = memberValueDatatype + self.unbalanced = unbalanced + self.unbalancedGroup = unbalancedGroup + self.hidden = hidden + self.fieldsUsage = fieldsUsage + self.groupLevels = groupLevels + self.extLst = extLst + + +class GroupItems(Serialisable): + + tagname = "groupItems" + + m = Sequence(expected_type=Missing) + n = Sequence(expected_type=Number) + b = Sequence(expected_type=Boolean) + e = Sequence(expected_type=Error) + s = Sequence(expected_type=Text) + d = Sequence(expected_type=DateTimeField,) + + __elements__ = ('m', 'n', 'b', 'e', 's', 'd') + __attrs__ = ("count", ) + + def __init__(self, + count=None, + m=(), + n=(), + b=(), + e=(), + s=(), + d=(), + ): + self.m = m + self.n = n + self.b = b + self.e = e + self.s = s + self.d = d + + + @property + def count(self): + return len(self.m + self.n + self.b + self.e + self.s + self.d) + + +class RangePr(Serialisable): + + tagname = "rangePr" + + autoStart = Bool(allow_none=True) + autoEnd = Bool(allow_none=True) + groupBy = NoneSet(values=(['range', 'seconds', 'minutes', 'hours', 'days', + 'months', 'quarters', 'years'])) + startNum = Float(allow_none=True) + endNum = Float(allow_none=True) + startDate = DateTime(allow_none=True) + endDate = DateTime(allow_none=True) + groupInterval = Float(allow_none=True) + + def __init__(self, + autoStart=True, + autoEnd=True, + groupBy="range", + startNum=None, + endNum=None, + startDate=None, + endDate=None, + groupInterval=1, + ): + self.autoStart = autoStart + self.autoEnd = autoEnd + self.groupBy = groupBy + self.startNum = startNum + self.endNum = endNum + self.startDate = startDate + self.endDate = endDate + self.groupInterval = groupInterval + + +class FieldGroup(Serialisable): + + tagname = "fieldGroup" + + par = Integer(allow_none=True) + base = Integer(allow_none=True) + rangePr = Typed(expected_type=RangePr, allow_none=True) + discretePr = NestedSequence(expected_type=NestedInteger, count=True) + groupItems = Typed(expected_type=GroupItems, allow_none=True) + + __elements__ = ('rangePr', 'discretePr', 'groupItems') + + def __init__(self, + par=None, + base=None, + rangePr=None, + discretePr=(), + groupItems=None, + ): + self.par = par + self.base = base + self.rangePr = rangePr + self.discretePr = discretePr + self.groupItems = groupItems + + +class SharedItems(Serialisable): + + tagname = "sharedItems" + + _fields = MultiSequence() + m = MultiSequencePart(expected_type=Missing, store="_fields") + n = MultiSequencePart(expected_type=Number, store="_fields") + b = MultiSequencePart(expected_type=Boolean, store="_fields") + e = MultiSequencePart(expected_type=Error, store="_fields") + s = MultiSequencePart(expected_type=Text, store="_fields") + d = MultiSequencePart(expected_type=DateTimeField, store="_fields") + # attributes are optional and must be derived from associated cache records + containsSemiMixedTypes = Bool(allow_none=True) + containsNonDate = Bool(allow_none=True) + containsDate = Bool(allow_none=True) + containsString = Bool(allow_none=True) + containsBlank = Bool(allow_none=True) + containsMixedTypes = Bool(allow_none=True) + containsNumber = Bool(allow_none=True) + containsInteger = Bool(allow_none=True) + minValue = Float(allow_none=True) + maxValue = Float(allow_none=True) + minDate = DateTime(allow_none=True) + maxDate = DateTime(allow_none=True) + longText = Bool(allow_none=True) + + __attrs__ = ('count', 'containsBlank', 'containsDate', 'containsInteger', + 'containsMixedTypes', 'containsNonDate', 'containsNumber', + 'containsSemiMixedTypes', 'containsString', 'minValue', 'maxValue', + 'minDate', 'maxDate', 'longText') + + def __init__(self, + _fields=(), + containsSemiMixedTypes=None, + containsNonDate=None, + containsDate=None, + containsString=None, + containsBlank=None, + containsMixedTypes=None, + containsNumber=None, + containsInteger=None, + minValue=None, + maxValue=None, + minDate=None, + maxDate=None, + count=None, + longText=None, + ): + self._fields = _fields + self.containsBlank = containsBlank + self.containsDate = containsDate + self.containsNonDate = containsNonDate + self.containsString = containsString + self.containsMixedTypes = containsMixedTypes + self.containsSemiMixedTypes = containsSemiMixedTypes + self.containsNumber = containsNumber + self.containsInteger = containsInteger + self.minValue = minValue + self.maxValue = maxValue + self.minDate = minDate + self.maxDate = maxDate + self.longText = longText + + + @property + def count(self): + return len(self._fields) + + +class CacheField(Serialisable): + + tagname = "cacheField" + + sharedItems = Typed(expected_type=SharedItems, allow_none=True) + fieldGroup = Typed(expected_type=FieldGroup, allow_none=True) + mpMap = NestedInteger(allow_none=True, attribute="v") + extLst = Typed(expected_type=ExtensionList, allow_none=True) + name = String() + caption = String(allow_none=True) + propertyName = String(allow_none=True) + serverField = Bool(allow_none=True) + uniqueList = Bool(allow_none=True) + numFmtId = Integer(allow_none=True) + formula = String(allow_none=True) + sqlType = Integer(allow_none=True) + hierarchy = Integer(allow_none=True) + level = Integer(allow_none=True) + databaseField = Bool(allow_none=True) + mappingCount = Integer(allow_none=True) + memberPropertyField = Bool(allow_none=True) + + __elements__ = ('sharedItems', 'fieldGroup', 'mpMap') + + def __init__(self, + sharedItems=None, + fieldGroup=None, + mpMap=None, + extLst=None, + name=None, + caption=None, + propertyName=None, + serverField=None, + uniqueList=True, + numFmtId=None, + formula=None, + sqlType=0, + hierarchy=0, + level=0, + databaseField=True, + mappingCount=None, + memberPropertyField=None, + ): + self.sharedItems = sharedItems + self.fieldGroup = fieldGroup + self.mpMap = mpMap + self.extLst = extLst + self.name = name + self.caption = caption + self.propertyName = propertyName + self.serverField = serverField + self.uniqueList = uniqueList + self.numFmtId = numFmtId + self.formula = formula + self.sqlType = sqlType + self.hierarchy = hierarchy + self.level = level + self.databaseField = databaseField + self.mappingCount = mappingCount + self.memberPropertyField = memberPropertyField + + +class RangeSet(Serialisable): + + tagname = "rangeSet" + + i1 = Integer(allow_none=True) + i2 = Integer(allow_none=True) + i3 = Integer(allow_none=True) + i4 = Integer(allow_none=True) + ref = String() + name = String(allow_none=True) + sheet = String(allow_none=True) + + def __init__(self, + i1=None, + i2=None, + i3=None, + i4=None, + ref=None, + name=None, + sheet=None, + ): + self.i1 = i1 + self.i2 = i2 + self.i3 = i3 + self.i4 = i4 + self.ref = ref + self.name = name + self.sheet = sheet + + +class PageItem(Serialisable): + + tagname = "pageItem" + + name = String() + + def __init__(self, + name=None, + ): + self.name = name + + +class Consolidation(Serialisable): + + tagname = "consolidation" + + autoPage = Bool(allow_none=True) + pages = NestedSequence(expected_type=PageItem, count=True) + rangeSets = NestedSequence(expected_type=RangeSet, count=True) + + __elements__ = ('pages', 'rangeSets') + + def __init__(self, + autoPage=None, + pages=(), + rangeSets=(), + ): + self.autoPage = autoPage + self.pages = pages + self.rangeSets = rangeSets + + +class WorksheetSource(Serialisable): + + tagname = "worksheetSource" + + ref = String(allow_none=True) + name = String(allow_none=True) + sheet = String(allow_none=True) + + def __init__(self, + ref=None, + name=None, + sheet=None, + ): + self.ref = ref + self.name = name + self.sheet = sheet + + +class CacheSource(Serialisable): + + tagname = "cacheSource" + + type = Set(values=(['worksheet', 'external', 'consolidation', 'scenario'])) + connectionId = Integer(allow_none=True) + # some elements are choice + worksheetSource = Typed(expected_type=WorksheetSource, allow_none=True) + consolidation = Typed(expected_type=Consolidation, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('worksheetSource', 'consolidation',) + + def __init__(self, + type=None, + connectionId=None, + worksheetSource=None, + consolidation=None, + extLst=None, + ): + self.type = type + self.connectionId = connectionId + self.worksheetSource = worksheetSource + self.consolidation = consolidation + + +class CacheDefinition(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" + _id = 1 + _path = "/xl/pivotCache/pivotCacheDefinition{0}.xml" + records = None + + tagname = "pivotCacheDefinition" + + invalid = Bool(allow_none=True) + saveData = Bool(allow_none=True) + refreshOnLoad = Bool(allow_none=True) + optimizeMemory = Bool(allow_none=True) + enableRefresh = Bool(allow_none=True) + refreshedBy = String(allow_none=True) + refreshedDate = Float(allow_none=True) + refreshedDateIso = DateTime(allow_none=True) + backgroundQuery = Bool(allow_none=True) + missingItemsLimit = Integer(allow_none=True) + createdVersion = Integer(allow_none=True) + refreshedVersion = Integer(allow_none=True) + minRefreshableVersion = Integer(allow_none=True) + recordCount = Integer(allow_none=True) + upgradeOnRefresh = Bool(allow_none=True) + supportSubquery = Bool(allow_none=True) + supportAdvancedDrill = Bool(allow_none=True) + cacheSource = Typed(expected_type=CacheSource) + cacheFields = NestedSequence(expected_type=CacheField, count=True) + cacheHierarchies = NestedSequence(expected_type=CacheHierarchy, allow_none=True) + kpis = NestedSequence(expected_type=OLAPKPI, count=True) + tupleCache = Typed(expected_type=TupleCache, allow_none=True) + calculatedItems = NestedSequence(expected_type=CalculatedItem, count=True) + calculatedMembers = NestedSequence(expected_type=CalculatedMember, count=True) + dimensions = NestedSequence(expected_type=PivotDimension, allow_none=True) + measureGroups = NestedSequence(expected_type=MeasureGroup, count=True) + maps = NestedSequence(expected_type=MeasureDimensionMap, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + id = Relation() + + __elements__ = ('cacheSource', 'cacheFields', 'cacheHierarchies', 'kpis', + 'tupleCache', 'calculatedItems', 'calculatedMembers', 'dimensions', + 'measureGroups', 'maps',) + + def __init__(self, + invalid=None, + saveData=None, + refreshOnLoad=None, + optimizeMemory=None, + enableRefresh=None, + refreshedBy=None, + refreshedDate=None, + refreshedDateIso=None, + backgroundQuery=None, + missingItemsLimit=None, + createdVersion=None, + refreshedVersion=None, + minRefreshableVersion=None, + recordCount=None, + upgradeOnRefresh=None, + tupleCache=None, + supportSubquery=None, + supportAdvancedDrill=None, + cacheSource=None, + cacheFields=(), + cacheHierarchies=(), + kpis=(), + calculatedItems=(), + calculatedMembers=(), + dimensions=(), + measureGroups=(), + maps=(), + extLst=None, + id = None, + ): + self.invalid = invalid + self.saveData = saveData + self.refreshOnLoad = refreshOnLoad + self.optimizeMemory = optimizeMemory + self.enableRefresh = enableRefresh + self.refreshedBy = refreshedBy + self.refreshedDate = refreshedDate + self.refreshedDateIso = refreshedDateIso + self.backgroundQuery = backgroundQuery + self.missingItemsLimit = missingItemsLimit + self.createdVersion = createdVersion + self.refreshedVersion = refreshedVersion + self.minRefreshableVersion = minRefreshableVersion + self.recordCount = recordCount + self.upgradeOnRefresh = upgradeOnRefresh + self.supportSubquery = supportSubquery + self.supportAdvancedDrill = supportAdvancedDrill + self.cacheSource = cacheSource + self.cacheFields = cacheFields + self.cacheHierarchies = cacheHierarchies + self.kpis = kpis + self.tupleCache = tupleCache + self.calculatedItems = calculatedItems + self.calculatedMembers = calculatedMembers + self.dimensions = dimensions + self.measureGroups = measureGroups + self.maps = maps + self.id = id + + + def to_tree(self): + node = super().to_tree() + node.set("xmlns", SHEET_MAIN_NS) + return node + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Add to zipfile and update manifest + """ + self._write_rels(archive, manifest) + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + """ + Write the relevant child objects and add links + """ + if self.records is None: + return + + rels = RelationshipList() + r = Relationship(Type=self.records.rel_type, Target=self.records.path) + rels.append(r) + self.id = r.id + self.records._id = self._id + self.records._write(archive, manifest) + + path = get_rels_path(self.path) + xml = tostring(rels.to_tree()) + archive.writestr(path[1:], xml) diff --git a/venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py b/venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py new file mode 100644 index 0000000..cd6bcb2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/pivot/fields.py @@ -0,0 +1,326 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + DateTime, + Bool, + Float, + String, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import HexBinary + +class Index(Serialisable): + + tagname = "x" + + v = Integer(allow_none=True) + + def __init__(self, + v=0, + ): + self.v = v + + +class Tuple(Serialisable): + + tagname = "tpl" + + fld = Integer(allow_none=True) + hier = Integer(allow_none=True) + item = Integer() + + def __init__(self, + fld=None, + hier=None, + item=None, + ): + self.fld = fld + self.hier = hier + self.item = item + + +class TupleList(Serialisable): + + tagname = "tpls" + + c = Integer(allow_none=True) + tpl = Typed(expected_type=Tuple, ) + + __elements__ = ('tpl',) + + def __init__(self, + c=None, + tpl=None, + ): + self.c = c + self.tpl = tpl + + +class Missing(Serialisable): + + tagname = "m" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Number(Serialisable): + + tagname = "n" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + v = Float() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Error(Serialisable): + + tagname = "e" + + tpls = Typed(expected_type=TupleList, allow_none=True) + x = Sequence(expected_type=Index) + v = String() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=None, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class Boolean(Serialisable): + + tagname = "b" + + x = Sequence(expected_type=Index) + v = Bool() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + ): + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + + +class Text(Serialisable): + + tagname = "s" + + tpls = Sequence(expected_type=TupleList) + x = Sequence(expected_type=Index) + v = String() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + _in = Integer(allow_none=True) + bc = HexBinary(allow_none=True) + fc = HexBinary(allow_none=True) + i = Bool(allow_none=True) + un = Bool(allow_none=True) + st = Bool(allow_none=True) + b = Bool(allow_none=True) + + __elements__ = ('tpls', 'x') + + def __init__(self, + tpls=(), + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + _in=None, + bc=None, + fc=None, + i=None, + un=None, + st=None, + b=None, + ): + self.tpls = tpls + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp + self._in = _in + self.bc = bc + self.fc = fc + self.i = i + self.un = un + self.st = st + self.b = b + + +class DateTimeField(Serialisable): + + tagname = "d" + + x = Sequence(expected_type=Index) + v = DateTime() + u = Bool(allow_none=True) + f = Bool(allow_none=True) + c = String(allow_none=True) + cp = Integer(allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + x=(), + v=None, + u=None, + f=None, + c=None, + cp=None, + ): + self.x = x + self.v = v + self.u = u + self.f = f + self.c = c + self.cp = cp diff --git a/venv/lib/python3.12/site-packages/openpyxl/pivot/record.py b/venv/lib/python3.12/site-packages/openpyxl/pivot/record.py new file mode 100644 index 0000000..4260377 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/pivot/record.py @@ -0,0 +1,111 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + Sequence, +) +from openpyxl.descriptors.sequence import ( + MultiSequence, + MultiSequencePart, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.nested import ( + NestedInteger, + NestedBool, +) + +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring + +from .fields import ( + Boolean, + Error, + Missing, + Number, + Text, + TupleList, + DateTimeField, + Index, +) + + +class Record(Serialisable): + + tagname = "r" + + _fields = MultiSequence() + m = MultiSequencePart(expected_type=Missing, store="_fields") + n = MultiSequencePart(expected_type=Number, store="_fields") + b = MultiSequencePart(expected_type=Boolean, store="_fields") + e = MultiSequencePart(expected_type=Error, store="_fields") + s = MultiSequencePart(expected_type=Text, store="_fields") + d = MultiSequencePart(expected_type=DateTimeField, store="_fields") + x = MultiSequencePart(expected_type=Index, store="_fields") + + + def __init__(self, + _fields=(), + m=None, + n=None, + b=None, + e=None, + s=None, + d=None, + x=None, + ): + self._fields = _fields + + +class RecordList(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords" + _id = 1 + _path = "/xl/pivotCache/pivotCacheRecords{0}.xml" + + tagname ="pivotCacheRecords" + + r = Sequence(expected_type=Record, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('r', ) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + r=(), + extLst=None, + ): + self.r = r + self.extLst = extLst + + + @property + def count(self): + return len(self.r) + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Write to zipfile and update manifest + """ + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + pass diff --git a/venv/lib/python3.12/site-packages/openpyxl/pivot/table.py b/venv/lib/python3.12/site-packages/openpyxl/pivot/table.py new file mode 100644 index 0000000..cc3548b --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/pivot/table.py @@ -0,0 +1,1261 @@ +# Copyright (c) 2010-2024 openpyxl + + +from collections import defaultdict +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Integer, + NoneSet, + Set, + Bool, + String, + Bool, + Sequence, +) + +from openpyxl.descriptors.excel import ExtensionList, Relation +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.xml.functions import tostring +from openpyxl.packaging.relationship import ( + RelationshipList, + Relationship, + get_rels_path +) +from .fields import Index + +from openpyxl.worksheet.filters import ( + AutoFilter, +) + + +class HierarchyUsage(Serialisable): + + tagname = "hierarchyUsage" + + hierarchyUsage = Integer() + + def __init__(self, + hierarchyUsage=None, + ): + self.hierarchyUsage = hierarchyUsage + + +class ColHierarchiesUsage(Serialisable): + + tagname = "colHierarchiesUsage" + + colHierarchyUsage = Sequence(expected_type=HierarchyUsage, ) + + __elements__ = ('colHierarchyUsage',) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + colHierarchyUsage=(), + ): + self.colHierarchyUsage = colHierarchyUsage + + + @property + def count(self): + return len(self.colHierarchyUsage) + + +class RowHierarchiesUsage(Serialisable): + + tagname = "rowHierarchiesUsage" + + rowHierarchyUsage = Sequence(expected_type=HierarchyUsage, ) + + __elements__ = ('rowHierarchyUsage',) + __attrs__ = ('count', ) + + def __init__(self, + count=None, + rowHierarchyUsage=(), + ): + self.rowHierarchyUsage = rowHierarchyUsage + + @property + def count(self): + return len(self.rowHierarchyUsage) + + +class PivotFilter(Serialisable): + + tagname = "filter" + + fld = Integer() + mpFld = Integer(allow_none=True) + type = Set(values=(['unknown', 'count', 'percent', 'sum', 'captionEqual', + 'captionNotEqual', 'captionBeginsWith', 'captionNotBeginsWith', + 'captionEndsWith', 'captionNotEndsWith', 'captionContains', + 'captionNotContains', 'captionGreaterThan', 'captionGreaterThanOrEqual', + 'captionLessThan', 'captionLessThanOrEqual', 'captionBetween', + 'captionNotBetween', 'valueEqual', 'valueNotEqual', 'valueGreaterThan', + 'valueGreaterThanOrEqual', 'valueLessThan', 'valueLessThanOrEqual', + 'valueBetween', 'valueNotBetween', 'dateEqual', 'dateNotEqual', + 'dateOlderThan', 'dateOlderThanOrEqual', 'dateNewerThan', + 'dateNewerThanOrEqual', 'dateBetween', 'dateNotBetween', 'tomorrow', + 'today', 'yesterday', 'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth', + 'thisMonth', 'lastMonth', 'nextQuarter', 'thisQuarter', 'lastQuarter', + 'nextYear', 'thisYear', 'lastYear', 'yearToDate', 'Q1', 'Q2', 'Q3', 'Q4', + 'M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', + 'M12'])) + evalOrder = Integer(allow_none=True) + id = Integer() + iMeasureHier = Integer(allow_none=True) + iMeasureFld = Integer(allow_none=True) + name = String(allow_none=True) + description = String(allow_none=True) + stringValue1 = String(allow_none=True) + stringValue2 = String(allow_none=True) + autoFilter = Typed(expected_type=AutoFilter, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('autoFilter',) + + def __init__(self, + fld=None, + mpFld=None, + type=None, + evalOrder=None, + id=None, + iMeasureHier=None, + iMeasureFld=None, + name=None, + description=None, + stringValue1=None, + stringValue2=None, + autoFilter=None, + extLst=None, + ): + self.fld = fld + self.mpFld = mpFld + self.type = type + self.evalOrder = evalOrder + self.id = id + self.iMeasureHier = iMeasureHier + self.iMeasureFld = iMeasureFld + self.name = name + self.description = description + self.stringValue1 = stringValue1 + self.stringValue2 = stringValue2 + self.autoFilter = autoFilter + + +class PivotFilters(Serialisable): + + count = Integer() + filter = Typed(expected_type=PivotFilter, allow_none=True) + + __elements__ = ('filter',) + + def __init__(self, + count=None, + filter=None, + ): + self.filter = filter + + +class PivotTableStyle(Serialisable): + + tagname = "pivotTableStyleInfo" + + name = String(allow_none=True) + showRowHeaders = Bool() + showColHeaders = Bool() + showRowStripes = Bool() + showColStripes = Bool() + showLastColumn = Bool() + + def __init__(self, + name=None, + showRowHeaders=None, + showColHeaders=None, + showRowStripes=None, + showColStripes=None, + showLastColumn=None, + ): + self.name = name + self.showRowHeaders = showRowHeaders + self.showColHeaders = showColHeaders + self.showRowStripes = showRowStripes + self.showColStripes = showColStripes + self.showLastColumn = showLastColumn + + +class MemberList(Serialisable): + + tagname = "members" + + level = Integer(allow_none=True) + member = NestedSequence(expected_type=String, attribute="name") + + __elements__ = ('member',) + + def __init__(self, + count=None, + level=None, + member=(), + ): + self.level = level + self.member = member + + @property + def count(self): + return len(self.member) + + +class MemberProperty(Serialisable): + + tagname = "mps" + + name = String(allow_none=True) + showCell = Bool(allow_none=True) + showTip = Bool(allow_none=True) + showAsCaption = Bool(allow_none=True) + nameLen = Integer(allow_none=True) + pPos = Integer(allow_none=True) + pLen = Integer(allow_none=True) + level = Integer(allow_none=True) + field = Integer() + + def __init__(self, + name=None, + showCell=None, + showTip=None, + showAsCaption=None, + nameLen=None, + pPos=None, + pLen=None, + level=None, + field=None, + ): + self.name = name + self.showCell = showCell + self.showTip = showTip + self.showAsCaption = showAsCaption + self.nameLen = nameLen + self.pPos = pPos + self.pLen = pLen + self.level = level + self.field = field + + +class PivotHierarchy(Serialisable): + + tagname = "pivotHierarchy" + + outline = Bool() + multipleItemSelectionAllowed = Bool() + subtotalTop = Bool() + showInFieldList = Bool() + dragToRow = Bool() + dragToCol = Bool() + dragToPage = Bool() + dragToData = Bool() + dragOff = Bool() + includeNewItemsInFilter = Bool() + caption = String(allow_none=True) + mps = NestedSequence(expected_type=MemberProperty, count=True) + members = Typed(expected_type=MemberList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('mps', 'members',) + + def __init__(self, + outline=None, + multipleItemSelectionAllowed=None, + subtotalTop=None, + showInFieldList=None, + dragToRow=None, + dragToCol=None, + dragToPage=None, + dragToData=None, + dragOff=None, + includeNewItemsInFilter=None, + caption=None, + mps=(), + members=None, + extLst=None, + ): + self.outline = outline + self.multipleItemSelectionAllowed = multipleItemSelectionAllowed + self.subtotalTop = subtotalTop + self.showInFieldList = showInFieldList + self.dragToRow = dragToRow + self.dragToCol = dragToCol + self.dragToPage = dragToPage + self.dragToData = dragToData + self.dragOff = dragOff + self.includeNewItemsInFilter = includeNewItemsInFilter + self.caption = caption + self.mps = mps + self.members = members + self.extLst = extLst + + +class Reference(Serialisable): + + tagname = "reference" + + field = Integer(allow_none=True) + selected = Bool(allow_none=True) + byPosition = Bool(allow_none=True) + relative = Bool(allow_none=True) + defaultSubtotal = Bool(allow_none=True) + sumSubtotal = Bool(allow_none=True) + countASubtotal = Bool(allow_none=True) + avgSubtotal = Bool(allow_none=True) + maxSubtotal = Bool(allow_none=True) + minSubtotal = Bool(allow_none=True) + productSubtotal = Bool(allow_none=True) + countSubtotal = Bool(allow_none=True) + stdDevSubtotal = Bool(allow_none=True) + stdDevPSubtotal = Bool(allow_none=True) + varSubtotal = Bool(allow_none=True) + varPSubtotal = Bool(allow_none=True) + x = Sequence(expected_type=Index) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('x',) + + def __init__(self, + field=None, + count=None, + selected=None, + byPosition=None, + relative=None, + defaultSubtotal=None, + sumSubtotal=None, + countASubtotal=None, + avgSubtotal=None, + maxSubtotal=None, + minSubtotal=None, + productSubtotal=None, + countSubtotal=None, + stdDevSubtotal=None, + stdDevPSubtotal=None, + varSubtotal=None, + varPSubtotal=None, + x=(), + extLst=None, + ): + self.field = field + self.selected = selected + self.byPosition = byPosition + self.relative = relative + self.defaultSubtotal = defaultSubtotal + self.sumSubtotal = sumSubtotal + self.countASubtotal = countASubtotal + self.avgSubtotal = avgSubtotal + self.maxSubtotal = maxSubtotal + self.minSubtotal = minSubtotal + self.productSubtotal = productSubtotal + self.countSubtotal = countSubtotal + self.stdDevSubtotal = stdDevSubtotal + self.stdDevPSubtotal = stdDevPSubtotal + self.varSubtotal = varSubtotal + self.varPSubtotal = varPSubtotal + self.x = x + + + @property + def count(self): + return len(self.field) + + +class PivotArea(Serialisable): + + tagname = "pivotArea" + + references = NestedSequence(expected_type=Reference, count=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + field = Integer(allow_none=True) + type = NoneSet(values=(['normal', 'data', 'all', 'origin', 'button', + 'topEnd', 'topRight'])) + dataOnly = Bool(allow_none=True) + labelOnly = Bool(allow_none=True) + grandRow = Bool(allow_none=True) + grandCol = Bool(allow_none=True) + cacheIndex = Bool(allow_none=True) + outline = Bool(allow_none=True) + offset = String(allow_none=True) + collapsedLevelsAreSubtotals = Bool(allow_none=True) + axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues'])) + fieldPosition = Integer(allow_none=True) + + __elements__ = ('references',) + + def __init__(self, + references=(), + extLst=None, + field=None, + type="normal", + dataOnly=True, + labelOnly=None, + grandRow=None, + grandCol=None, + cacheIndex=None, + outline=True, + offset=None, + collapsedLevelsAreSubtotals=None, + axis=None, + fieldPosition=None, + ): + self.references = references + self.extLst = extLst + self.field = field + self.type = type + self.dataOnly = dataOnly + self.labelOnly = labelOnly + self.grandRow = grandRow + self.grandCol = grandCol + self.cacheIndex = cacheIndex + self.outline = outline + self.offset = offset + self.collapsedLevelsAreSubtotals = collapsedLevelsAreSubtotals + self.axis = axis + self.fieldPosition = fieldPosition + + +class ChartFormat(Serialisable): + + tagname = "chartFormat" + + chart = Integer() + format = Integer() + series = Bool() + pivotArea = Typed(expected_type=PivotArea, ) + + __elements__ = ('pivotArea',) + + def __init__(self, + chart=None, + format=None, + series=None, + pivotArea=None, + ): + self.chart = chart + self.format = format + self.series = series + self.pivotArea = pivotArea + + +class ConditionalFormat(Serialisable): + + tagname = "conditionalFormat" + + scope = Set(values=(['selection', 'data', 'field'])) + type = NoneSet(values=(['all', 'row', 'column'])) + priority = Integer() + pivotAreas = NestedSequence(expected_type=PivotArea) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotAreas',) + + def __init__(self, + scope="selection", + type=None, + priority=None, + pivotAreas=(), + extLst=None, + ): + self.scope = scope + self.type = type + self.priority = priority + self.pivotAreas = pivotAreas + self.extLst = extLst + + +class ConditionalFormatList(Serialisable): + + tagname = "conditionalFormats" + + conditionalFormat = Sequence(expected_type=ConditionalFormat) + + __attrs__ = ("count",) + + def __init__(self, conditionalFormat=(), count=None): + self.conditionalFormat = conditionalFormat + + + def by_priority(self): + """ + Return a dictionary of format objects keyed by (field id and format property). + This can be used to map the formats to field but also to dedupe to match + worksheet definitions which are grouped by cell range + """ + + fmts = {} + for fmt in self.conditionalFormat: + for area in fmt.pivotAreas: + for ref in area.references: + for field in ref.x: + key = (field.v, fmt.priority) + fmts[key] = fmt + + return fmts + + + def _dedupe(self): + """ + Group formats by field index and priority. + Sorted to match sorting and grouping for corresponding worksheet formats + + The implemtenters notes contain significant deviance from the OOXML + specification, in particular how conditional formats in tables relate to + those defined in corresponding worksheets and how to determine which + format applies to which fields. + + There are some magical interdependencies: + + * Every pivot table fmt must have a worksheet cxf with the same priority. + + * In the reference part the field 4294967294 refers to a data field, the + spec says -2 + + * Data fields are referenced by the 0-index reference.x.v value + + Things are made more complicated by the fact that field items behave + diffently if the parent is a reference or shared item: "In Office if the + parent is the reference element, then restrictions of this value are + defined by reference@field. If the parent is the tables element, then + this value specifies the index into the table tag position in @url." + Yeah, right! + """ + fmts = self.by_priority() + # sort by priority in order, keeping the highest numerical priority, least when + # actually applied + # this is not documented but it's what Excel is happy with + fmts = {field:fmt for (field, priority), fmt in sorted(fmts.items(), reverse=True)} + #fmts = {field:fmt for (field, priority), fmt in fmts.items()} + if fmts: + self.conditionalFormat = list(fmts.values()) + + + @property + def count(self): + return len(self.conditionalFormat) + + + def to_tree(self, tagname=None): + self._dedupe() + return super().to_tree(tagname) + + +class Format(Serialisable): + + tagname = "format" + + action = NoneSet(values=(['blank', 'formatting', 'drill', 'formula'])) + dxfId = Integer(allow_none=True) + pivotArea = Typed(expected_type=PivotArea, ) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('pivotArea',) + + def __init__(self, + action="formatting", + dxfId=None, + pivotArea=None, + extLst=None, + ): + self.action = action + self.dxfId = dxfId + self.pivotArea = pivotArea + self.extLst = extLst + + +class DataField(Serialisable): + + tagname = "dataField" + + name = String(allow_none=True) + fld = Integer() + subtotal = Set(values=(['average', 'count', 'countNums', 'max', 'min', + 'product', 'stdDev', 'stdDevp', 'sum', 'var', 'varp'])) + showDataAs = Set(values=(['normal', 'difference', 'percent', + 'percentDiff', 'runTotal', 'percentOfRow', 'percentOfCol', + 'percentOfTotal', 'index'])) + baseField = Integer() + baseItem = Integer() + numFmtId = Integer(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + + def __init__(self, + name=None, + fld=None, + subtotal="sum", + showDataAs="normal", + baseField=-1, + baseItem=1048832, + numFmtId=None, + extLst=None, + ): + self.name = name + self.fld = fld + self.subtotal = subtotal + self.showDataAs = showDataAs + self.baseField = baseField + self.baseItem = baseItem + self.numFmtId = numFmtId + self.extLst = extLst + + +class PageField(Serialisable): + + tagname = "pageField" + + fld = Integer() + item = Integer(allow_none=True) + hier = Integer(allow_none=True) + name = String(allow_none=True) + cap = String(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + def __init__(self, + fld=None, + item=None, + hier=None, + name=None, + cap=None, + extLst=None, + ): + self.fld = fld + self.item = item + self.hier = hier + self.name = name + self.cap = cap + self.extLst = extLst + + +class RowColItem(Serialisable): + + tagname = "i" + + t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min', + 'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand', + 'blank'])) + r = Integer() + i = Integer() + x = Sequence(expected_type=Index, attribute="v") + + __elements__ = ('x',) + + def __init__(self, + t="data", + r=0, + i=0, + x=(), + ): + self.t = t + self.r = r + self.i = i + self.x = x + + +class RowColField(Serialisable): + + tagname = "field" + + x = Integer() + + def __init__(self, + x=None, + ): + self.x = x + + +class AutoSortScope(Serialisable): + + pivotArea = Typed(expected_type=PivotArea, ) + + __elements__ = ('pivotArea',) + + def __init__(self, + pivotArea=None, + ): + self.pivotArea = pivotArea + + +class FieldItem(Serialisable): + + tagname = "item" + + n = String(allow_none=True) + t = Set(values=(['data', 'default', 'sum', 'countA', 'avg', 'max', 'min', + 'product', 'count', 'stdDev', 'stdDevP', 'var', 'varP', 'grand', + 'blank'])) + h = Bool(allow_none=True) + s = Bool(allow_none=True) + sd = Bool(allow_none=True) + f = Bool(allow_none=True) + m = Bool(allow_none=True) + c = Bool(allow_none=True) + x = Integer(allow_none=True) + d = Bool(allow_none=True) + e = Bool(allow_none=True) + + def __init__(self, + n=None, + t="data", + h=None, + s=None, + sd=True, + f=None, + m=None, + c=None, + x=None, + d=None, + e=None, + ): + self.n = n + self.t = t + self.h = h + self.s = s + self.sd = sd + self.f = f + self.m = m + self.c = c + self.x = x + self.d = d + self.e = e + + +class PivotField(Serialisable): + + tagname = "pivotField" + + items = NestedSequence(expected_type=FieldItem, count=True) + autoSortScope = Typed(expected_type=AutoSortScope, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + name = String(allow_none=True) + axis = NoneSet(values=(['axisRow', 'axisCol', 'axisPage', 'axisValues'])) + dataField = Bool(allow_none=True) + subtotalCaption = String(allow_none=True) + showDropDowns = Bool(allow_none=True) + hiddenLevel = Bool(allow_none=True) + uniqueMemberProperty = String(allow_none=True) + compact = Bool(allow_none=True) + allDrilled = Bool(allow_none=True) + numFmtId = Integer(allow_none=True) + outline = Bool(allow_none=True) + subtotalTop = Bool(allow_none=True) + dragToRow = Bool(allow_none=True) + dragToCol = Bool(allow_none=True) + multipleItemSelectionAllowed = Bool(allow_none=True) + dragToPage = Bool(allow_none=True) + dragToData = Bool(allow_none=True) + dragOff = Bool(allow_none=True) + showAll = Bool(allow_none=True) + insertBlankRow = Bool(allow_none=True) + serverField = Bool(allow_none=True) + insertPageBreak = Bool(allow_none=True) + autoShow = Bool(allow_none=True) + topAutoShow = Bool(allow_none=True) + hideNewItems = Bool(allow_none=True) + measureFilter = Bool(allow_none=True) + includeNewItemsInFilter = Bool(allow_none=True) + itemPageCount = Integer(allow_none=True) + sortType = Set(values=(['manual', 'ascending', 'descending'])) + dataSourceSort = Bool(allow_none=True) + nonAutoSortDefault = Bool(allow_none=True) + rankBy = Integer(allow_none=True) + defaultSubtotal = Bool(allow_none=True) + sumSubtotal = Bool(allow_none=True) + countASubtotal = Bool(allow_none=True) + avgSubtotal = Bool(allow_none=True) + maxSubtotal = Bool(allow_none=True) + minSubtotal = Bool(allow_none=True) + productSubtotal = Bool(allow_none=True) + countSubtotal = Bool(allow_none=True) + stdDevSubtotal = Bool(allow_none=True) + stdDevPSubtotal = Bool(allow_none=True) + varSubtotal = Bool(allow_none=True) + varPSubtotal = Bool(allow_none=True) + showPropCell = Bool(allow_none=True) + showPropTip = Bool(allow_none=True) + showPropAsCaption = Bool(allow_none=True) + defaultAttributeDrillState = Bool(allow_none=True) + + __elements__ = ('items', 'autoSortScope',) + + def __init__(self, + items=(), + autoSortScope=None, + name=None, + axis=None, + dataField=None, + subtotalCaption=None, + showDropDowns=True, + hiddenLevel=None, + uniqueMemberProperty=None, + compact=True, + allDrilled=None, + numFmtId=None, + outline=True, + subtotalTop=True, + dragToRow=True, + dragToCol=True, + multipleItemSelectionAllowed=None, + dragToPage=True, + dragToData=True, + dragOff=True, + showAll=True, + insertBlankRow=None, + serverField=None, + insertPageBreak=None, + autoShow=None, + topAutoShow=True, + hideNewItems=None, + measureFilter=None, + includeNewItemsInFilter=None, + itemPageCount=10, + sortType="manual", + dataSourceSort=None, + nonAutoSortDefault=None, + rankBy=None, + defaultSubtotal=True, + sumSubtotal=None, + countASubtotal=None, + avgSubtotal=None, + maxSubtotal=None, + minSubtotal=None, + productSubtotal=None, + countSubtotal=None, + stdDevSubtotal=None, + stdDevPSubtotal=None, + varSubtotal=None, + varPSubtotal=None, + showPropCell=None, + showPropTip=None, + showPropAsCaption=None, + defaultAttributeDrillState=None, + extLst=None, + ): + self.items = items + self.autoSortScope = autoSortScope + self.name = name + self.axis = axis + self.dataField = dataField + self.subtotalCaption = subtotalCaption + self.showDropDowns = showDropDowns + self.hiddenLevel = hiddenLevel + self.uniqueMemberProperty = uniqueMemberProperty + self.compact = compact + self.allDrilled = allDrilled + self.numFmtId = numFmtId + self.outline = outline + self.subtotalTop = subtotalTop + self.dragToRow = dragToRow + self.dragToCol = dragToCol + self.multipleItemSelectionAllowed = multipleItemSelectionAllowed + self.dragToPage = dragToPage + self.dragToData = dragToData + self.dragOff = dragOff + self.showAll = showAll + self.insertBlankRow = insertBlankRow + self.serverField = serverField + self.insertPageBreak = insertPageBreak + self.autoShow = autoShow + self.topAutoShow = topAutoShow + self.hideNewItems = hideNewItems + self.measureFilter = measureFilter + self.includeNewItemsInFilter = includeNewItemsInFilter + self.itemPageCount = itemPageCount + self.sortType = sortType + self.dataSourceSort = dataSourceSort + self.nonAutoSortDefault = nonAutoSortDefault + self.rankBy = rankBy + self.defaultSubtotal = defaultSubtotal + self.sumSubtotal = sumSubtotal + self.countASubtotal = countASubtotal + self.avgSubtotal = avgSubtotal + self.maxSubtotal = maxSubtotal + self.minSubtotal = minSubtotal + self.productSubtotal = productSubtotal + self.countSubtotal = countSubtotal + self.stdDevSubtotal = stdDevSubtotal + self.stdDevPSubtotal = stdDevPSubtotal + self.varSubtotal = varSubtotal + self.varPSubtotal = varPSubtotal + self.showPropCell = showPropCell + self.showPropTip = showPropTip + self.showPropAsCaption = showPropAsCaption + self.defaultAttributeDrillState = defaultAttributeDrillState + + +class Location(Serialisable): + + tagname = "location" + + ref = String() + firstHeaderRow = Integer() + firstDataRow = Integer() + firstDataCol = Integer() + rowPageCount = Integer(allow_none=True) + colPageCount = Integer(allow_none=True) + + def __init__(self, + ref=None, + firstHeaderRow=None, + firstDataRow=None, + firstDataCol=None, + rowPageCount=None, + colPageCount=None, + ): + self.ref = ref + self.firstHeaderRow = firstHeaderRow + self.firstDataRow = firstDataRow + self.firstDataCol = firstDataCol + self.rowPageCount = rowPageCount + self.colPageCount = colPageCount + + +class TableDefinition(Serialisable): + + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" + rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" + _id = 1 + _path = "/xl/pivotTables/pivotTable{0}.xml" + + tagname = "pivotTableDefinition" + cache = None + + name = String() + cacheId = Integer() + dataOnRows = Bool() + dataPosition = Integer(allow_none=True) + dataCaption = String() + grandTotalCaption = String(allow_none=True) + errorCaption = String(allow_none=True) + showError = Bool() + missingCaption = String(allow_none=True) + showMissing = Bool() + pageStyle = String(allow_none=True) + pivotTableStyle = String(allow_none=True) + vacatedStyle = String(allow_none=True) + tag = String(allow_none=True) + updatedVersion = Integer() + minRefreshableVersion = Integer() + asteriskTotals = Bool() + showItems = Bool() + editData = Bool() + disableFieldList = Bool() + showCalcMbrs = Bool() + visualTotals = Bool() + showMultipleLabel = Bool() + showDataDropDown = Bool() + showDrill = Bool() + printDrill = Bool() + showMemberPropertyTips = Bool() + showDataTips = Bool() + enableWizard = Bool() + enableDrill = Bool() + enableFieldProperties = Bool() + preserveFormatting = Bool() + useAutoFormatting = Bool() + pageWrap = Integer() + pageOverThenDown = Bool() + subtotalHiddenItems = Bool() + rowGrandTotals = Bool() + colGrandTotals = Bool() + fieldPrintTitles = Bool() + itemPrintTitles = Bool() + mergeItem = Bool() + showDropZones = Bool() + createdVersion = Integer() + indent = Integer() + showEmptyRow = Bool() + showEmptyCol = Bool() + showHeaders = Bool() + compact = Bool() + outline = Bool() + outlineData = Bool() + compactData = Bool() + published = Bool() + gridDropZones = Bool() + immersive = Bool() + multipleFieldFilters = Bool() + chartFormat = Integer() + rowHeaderCaption = String(allow_none=True) + colHeaderCaption = String(allow_none=True) + fieldListSortAscending = Bool() + mdxSubqueries = Bool() + customListSort = Bool(allow_none=True) + autoFormatId = Integer(allow_none=True) + applyNumberFormats = Bool() + applyBorderFormats = Bool() + applyFontFormats = Bool() + applyPatternFormats = Bool() + applyAlignmentFormats = Bool() + applyWidthHeightFormats = Bool() + location = Typed(expected_type=Location, ) + pivotFields = NestedSequence(expected_type=PivotField, count=True) + rowFields = NestedSequence(expected_type=RowColField, count=True) + rowItems = NestedSequence(expected_type=RowColItem, count=True) + colFields = NestedSequence(expected_type=RowColField, count=True) + colItems = NestedSequence(expected_type=RowColItem, count=True) + pageFields = NestedSequence(expected_type=PageField, count=True) + dataFields = NestedSequence(expected_type=DataField, count=True) + formats = NestedSequence(expected_type=Format, count=True) + conditionalFormats = Typed(expected_type=ConditionalFormatList, allow_none=True) + chartFormats = NestedSequence(expected_type=ChartFormat, count=True) + pivotHierarchies = NestedSequence(expected_type=PivotHierarchy, count=True) + pivotTableStyleInfo = Typed(expected_type=PivotTableStyle, allow_none=True) + filters = NestedSequence(expected_type=PivotFilter, count=True) + rowHierarchiesUsage = Typed(expected_type=RowHierarchiesUsage, allow_none=True) + colHierarchiesUsage = Typed(expected_type=ColHierarchiesUsage, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + id = Relation() + + __elements__ = ('location', 'pivotFields', 'rowFields', 'rowItems', + 'colFields', 'colItems', 'pageFields', 'dataFields', 'formats', + 'conditionalFormats', 'chartFormats', 'pivotHierarchies', + 'pivotTableStyleInfo', 'filters', 'rowHierarchiesUsage', + 'colHierarchiesUsage',) + + def __init__(self, + name=None, + cacheId=None, + dataOnRows=False, + dataPosition=None, + dataCaption=None, + grandTotalCaption=None, + errorCaption=None, + showError=False, + missingCaption=None, + showMissing=True, + pageStyle=None, + pivotTableStyle=None, + vacatedStyle=None, + tag=None, + updatedVersion=0, + minRefreshableVersion=0, + asteriskTotals=False, + showItems=True, + editData=False, + disableFieldList=False, + showCalcMbrs=True, + visualTotals=True, + showMultipleLabel=True, + showDataDropDown=True, + showDrill=True, + printDrill=False, + showMemberPropertyTips=True, + showDataTips=True, + enableWizard=True, + enableDrill=True, + enableFieldProperties=True, + preserveFormatting=True, + useAutoFormatting=False, + pageWrap=0, + pageOverThenDown=False, + subtotalHiddenItems=False, + rowGrandTotals=True, + colGrandTotals=True, + fieldPrintTitles=False, + itemPrintTitles=False, + mergeItem=False, + showDropZones=True, + createdVersion=0, + indent=1, + showEmptyRow=False, + showEmptyCol=False, + showHeaders=True, + compact=True, + outline=False, + outlineData=False, + compactData=True, + published=False, + gridDropZones=False, + immersive=True, + multipleFieldFilters=None, + chartFormat=0, + rowHeaderCaption=None, + colHeaderCaption=None, + fieldListSortAscending=None, + mdxSubqueries=None, + customListSort=None, + autoFormatId=None, + applyNumberFormats=False, + applyBorderFormats=False, + applyFontFormats=False, + applyPatternFormats=False, + applyAlignmentFormats=False, + applyWidthHeightFormats=False, + location=None, + pivotFields=(), + rowFields=(), + rowItems=(), + colFields=(), + colItems=(), + pageFields=(), + dataFields=(), + formats=(), + conditionalFormats=None, + chartFormats=(), + pivotHierarchies=(), + pivotTableStyleInfo=None, + filters=(), + rowHierarchiesUsage=None, + colHierarchiesUsage=None, + extLst=None, + id=None, + ): + self.name = name + self.cacheId = cacheId + self.dataOnRows = dataOnRows + self.dataPosition = dataPosition + self.dataCaption = dataCaption + self.grandTotalCaption = grandTotalCaption + self.errorCaption = errorCaption + self.showError = showError + self.missingCaption = missingCaption + self.showMissing = showMissing + self.pageStyle = pageStyle + self.pivotTableStyle = pivotTableStyle + self.vacatedStyle = vacatedStyle + self.tag = tag + self.updatedVersion = updatedVersion + self.minRefreshableVersion = minRefreshableVersion + self.asteriskTotals = asteriskTotals + self.showItems = showItems + self.editData = editData + self.disableFieldList = disableFieldList + self.showCalcMbrs = showCalcMbrs + self.visualTotals = visualTotals + self.showMultipleLabel = showMultipleLabel + self.showDataDropDown = showDataDropDown + self.showDrill = showDrill + self.printDrill = printDrill + self.showMemberPropertyTips = showMemberPropertyTips + self.showDataTips = showDataTips + self.enableWizard = enableWizard + self.enableDrill = enableDrill + self.enableFieldProperties = enableFieldProperties + self.preserveFormatting = preserveFormatting + self.useAutoFormatting = useAutoFormatting + self.pageWrap = pageWrap + self.pageOverThenDown = pageOverThenDown + self.subtotalHiddenItems = subtotalHiddenItems + self.rowGrandTotals = rowGrandTotals + self.colGrandTotals = colGrandTotals + self.fieldPrintTitles = fieldPrintTitles + self.itemPrintTitles = itemPrintTitles + self.mergeItem = mergeItem + self.showDropZones = showDropZones + self.createdVersion = createdVersion + self.indent = indent + self.showEmptyRow = showEmptyRow + self.showEmptyCol = showEmptyCol + self.showHeaders = showHeaders + self.compact = compact + self.outline = outline + self.outlineData = outlineData + self.compactData = compactData + self.published = published + self.gridDropZones = gridDropZones + self.immersive = immersive + self.multipleFieldFilters = multipleFieldFilters + self.chartFormat = chartFormat + self.rowHeaderCaption = rowHeaderCaption + self.colHeaderCaption = colHeaderCaption + self.fieldListSortAscending = fieldListSortAscending + self.mdxSubqueries = mdxSubqueries + self.customListSort = customListSort + self.autoFormatId = autoFormatId + self.applyNumberFormats = applyNumberFormats + self.applyBorderFormats = applyBorderFormats + self.applyFontFormats = applyFontFormats + self.applyPatternFormats = applyPatternFormats + self.applyAlignmentFormats = applyAlignmentFormats + self.applyWidthHeightFormats = applyWidthHeightFormats + self.location = location + self.pivotFields = pivotFields + self.rowFields = rowFields + self.rowItems = rowItems + self.colFields = colFields + self.colItems = colItems + self.pageFields = pageFields + self.dataFields = dataFields + self.formats = formats + self.conditionalFormats = conditionalFormats + self.conditionalFormats = None + self.chartFormats = chartFormats + self.pivotHierarchies = pivotHierarchies + self.pivotTableStyleInfo = pivotTableStyleInfo + self.filters = filters + self.rowHierarchiesUsage = rowHierarchiesUsage + self.colHierarchiesUsage = colHierarchiesUsage + self.extLst = extLst + self.id = id + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + + @property + def path(self): + return self._path.format(self._id) + + + def _write(self, archive, manifest): + """ + Add to zipfile and update manifest + """ + self._write_rels(archive, manifest) + xml = tostring(self.to_tree()) + archive.writestr(self.path[1:], xml) + manifest.append(self) + + + def _write_rels(self, archive, manifest): + """ + Write the relevant child objects and add links + """ + if self.cache is None: + return + + rels = RelationshipList() + r = Relationship(Type=self.cache.rel_type, Target=self.cache.path) + rels.append(r) + self.id = r.id + if self.cache.path[1:] not in archive.namelist(): + self.cache._write(archive, manifest) + + path = get_rels_path(self.path) + xml = tostring(rels.to_tree()) + archive.writestr(path[1:], xml) + + + def formatted_fields(self): + """Map fields to associated conditional formats by priority""" + if not self.conditionalFormats: + return {} + fields = defaultdict(list) + for idx, prio in self.conditionalFormats.by_priority(): + name = self.dataFields[idx].name + fields[name].append(prio) + return fields + + + @property + def summary(self): + """ + Provide a simplified summary of the table + """ + + return f"{self.name} {dict(self.location)}" diff --git a/venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py new file mode 100644 index 0000000..ab6cdea --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/reader/__init__.py @@ -0,0 +1 @@ +# Copyright (c) 2010-2024 openpyxl diff --git a/venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py b/venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py new file mode 100644 index 0000000..caaa857 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/reader/drawings.py @@ -0,0 +1,71 @@ + +# Copyright (c) 2010-2024 openpyxl + + +from io import BytesIO +from warnings import warn + +from openpyxl.xml.functions import fromstring +from openpyxl.xml.constants import IMAGE_NS +from openpyxl.packaging.relationship import ( + get_rel, + get_rels_path, + get_dependents, +) +from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing +from openpyxl.drawing.image import Image, PILImage +from openpyxl.chart.chartspace import ChartSpace +from openpyxl.chart.reader import read_chart + + +def find_images(archive, path): + """ + Given the path to a drawing file extract charts and images + + Ignore errors due to unsupported parts of DrawingML + """ + + src = archive.read(path) + tree = fromstring(src) + try: + drawing = SpreadsheetDrawing.from_tree(tree) + except TypeError: + warn("DrawingML support is incomplete and limited to charts and images only. Shapes and drawings will be lost.") + return [], [] + + rels_path = get_rels_path(path) + deps = [] + if rels_path in archive.namelist(): + deps = get_dependents(archive, rels_path) + + charts = [] + for rel in drawing._chart_rels: + try: + cs = get_rel(archive, deps, rel.id, ChartSpace) + except TypeError as e: + warn(f"Unable to read chart {rel.id} from {path} {e}") + continue + chart = read_chart(cs) + chart.anchor = rel.anchor + charts.append(chart) + + images = [] + if not PILImage: # Pillow not installed, drop images + return charts, images + + for rel in drawing._blip_rels: + dep = deps.get(rel.embed) + if dep.Type == IMAGE_NS: + try: + image = Image(BytesIO(archive.read(dep.target))) + except OSError: + msg = "The image {0} will be removed because it cannot be read".format(dep.target) + warn(msg) + continue + if image.format.upper() == "WMF": # cannot save + msg = "{0} image format is not supported so the image is being dropped".format(image.format) + warn(msg) + continue + image.anchor = rel.anchor + images.append(image) + return charts, images diff --git a/venv/lib/python3.12/site-packages/openpyxl/reader/excel.py b/venv/lib/python3.12/site-packages/openpyxl/reader/excel.py new file mode 100644 index 0000000..dfd8eea --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/reader/excel.py @@ -0,0 +1,349 @@ +# Copyright (c) 2010-2024 openpyxl + + +"""Read an xlsx file into Python""" + +# Python stdlib imports +from zipfile import ZipFile, ZIP_DEFLATED +from io import BytesIO +import os.path +import warnings + +from openpyxl.pivot.table import TableDefinition + +# Allow blanket setting of KEEP_VBA for testing +try: + from ..tests import KEEP_VBA +except ImportError: + KEEP_VBA = False + +# package imports +from openpyxl.utils.exceptions import InvalidFileException +from openpyxl.xml.constants import ( + ARC_CORE, + ARC_CUSTOM, + ARC_CONTENT_TYPES, + ARC_WORKBOOK, + ARC_THEME, + COMMENTS_NS, + SHARED_STRINGS, + XLTM, + XLTX, + XLSM, + XLSX, +) +from openpyxl.cell import MergedCell +from openpyxl.comments.comment_sheet import CommentSheet + +from .strings import read_string_table, read_rich_text +from .workbook import WorkbookParser +from openpyxl.styles.stylesheet import apply_stylesheet + +from openpyxl.packaging.core import DocumentProperties +from openpyxl.packaging.custom import CustomPropertyList +from openpyxl.packaging.manifest import Manifest, Override + +from openpyxl.packaging.relationship import ( + RelationshipList, + get_dependents, + get_rels_path, +) + +from openpyxl.worksheet._read_only import ReadOnlyWorksheet +from openpyxl.worksheet._reader import WorksheetReader +from openpyxl.chartsheet import Chartsheet +from openpyxl.worksheet.table import Table +from openpyxl.drawing.spreadsheet_drawing import SpreadsheetDrawing + +from openpyxl.xml.functions import fromstring + +from .drawings import find_images + + +SUPPORTED_FORMATS = ('.xlsx', '.xlsm', '.xltx', '.xltm') + + +def _validate_archive(filename): + """ + Does a first check whether filename is a string or a file-like + object. If it is a string representing a filename, a check is done + for supported formats by checking the given file-extension. If the + file-extension is not in SUPPORTED_FORMATS an InvalidFileException + will raised. Otherwise the filename (resp. file-like object) will + forwarded to zipfile.ZipFile returning a ZipFile-Instance. + """ + is_file_like = hasattr(filename, 'read') + if not is_file_like: + file_format = os.path.splitext(filename)[-1].lower() + if file_format not in SUPPORTED_FORMATS: + if file_format == '.xls': + msg = ('openpyxl does not support the old .xls file format, ' + 'please use xlrd to read this file, or convert it to ' + 'the more recent .xlsx file format.') + elif file_format == '.xlsb': + msg = ('openpyxl does not support binary format .xlsb, ' + 'please convert this file to .xlsx format if you want ' + 'to open it with openpyxl') + else: + msg = ('openpyxl does not support %s file format, ' + 'please check you can open ' + 'it with Excel first. ' + 'Supported formats are: %s') % (file_format, + ','.join(SUPPORTED_FORMATS)) + raise InvalidFileException(msg) + + archive = ZipFile(filename, 'r') + return archive + + +def _find_workbook_part(package): + workbook_types = [XLTM, XLTX, XLSM, XLSX] + for ct in workbook_types: + part = package.find(ct) + if part: + return part + + # some applications reassign the default for application/xml + defaults = {p.ContentType for p in package.Default} + workbook_type = defaults & set(workbook_types) + if workbook_type: + return Override("/" + ARC_WORKBOOK, workbook_type.pop()) + + raise IOError("File contains no valid workbook part") + + +class ExcelReader: + + """ + Read an Excel package and dispatch the contents to the relevant modules + """ + + def __init__(self, fn, read_only=False, keep_vba=KEEP_VBA, + data_only=False, keep_links=True, rich_text=False): + self.archive = _validate_archive(fn) + self.valid_files = self.archive.namelist() + self.read_only = read_only + self.keep_vba = keep_vba + self.data_only = data_only + self.keep_links = keep_links + self.rich_text = rich_text + self.shared_strings = [] + + + def read_manifest(self): + src = self.archive.read(ARC_CONTENT_TYPES) + root = fromstring(src) + self.package = Manifest.from_tree(root) + + + def read_strings(self): + ct = self.package.find(SHARED_STRINGS) + reader = read_string_table + if self.rich_text: + reader = read_rich_text + if ct is not None: + strings_path = ct.PartName[1:] + with self.archive.open(strings_path,) as src: + self.shared_strings = reader(src) + + + def read_workbook(self): + wb_part = _find_workbook_part(self.package) + self.parser = WorkbookParser(self.archive, wb_part.PartName[1:], keep_links=self.keep_links) + self.parser.parse() + wb = self.parser.wb + wb._sheets = [] + wb._data_only = self.data_only + wb._read_only = self.read_only + wb.template = wb_part.ContentType in (XLTX, XLTM) + + # If are going to preserve the vba then attach a copy of the archive to the + # workbook so that is available for the save. + if self.keep_vba: + wb.vba_archive = ZipFile(BytesIO(), 'a', ZIP_DEFLATED) + for name in self.valid_files: + wb.vba_archive.writestr(name, self.archive.read(name)) + + if self.read_only: + wb._archive = self.archive + + self.wb = wb + + + def read_properties(self): + if ARC_CORE in self.valid_files: + src = fromstring(self.archive.read(ARC_CORE)) + self.wb.properties = DocumentProperties.from_tree(src) + + + def read_custom(self): + if ARC_CUSTOM in self.valid_files: + src = fromstring(self.archive.read(ARC_CUSTOM)) + self.wb.custom_doc_props = CustomPropertyList.from_tree(src) + + + def read_theme(self): + if ARC_THEME in self.valid_files: + self.wb.loaded_theme = self.archive.read(ARC_THEME) + + + def read_chartsheet(self, sheet, rel): + sheet_path = rel.target + rels_path = get_rels_path(sheet_path) + rels = [] + if rels_path in self.valid_files: + rels = get_dependents(self.archive, rels_path) + + with self.archive.open(sheet_path, "r") as src: + xml = src.read() + node = fromstring(xml) + cs = Chartsheet.from_tree(node) + cs._parent = self.wb + cs.title = sheet.name + self.wb._add_sheet(cs) + + drawings = rels.find(SpreadsheetDrawing._rel_type) + for rel in drawings: + charts, images = find_images(self.archive, rel.target) + for c in charts: + cs.add_chart(c) + + + def read_worksheets(self): + comment_warning = """Cell '{0}':{1} is part of a merged range but has a comment which will be removed because merged cells cannot contain any data.""" + for sheet, rel in self.parser.find_sheets(): + if rel.target not in self.valid_files: + continue + + if "chartsheet" in rel.Type: + self.read_chartsheet(sheet, rel) + continue + + rels_path = get_rels_path(rel.target) + rels = RelationshipList() + if rels_path in self.valid_files: + rels = get_dependents(self.archive, rels_path) + + if self.read_only: + ws = ReadOnlyWorksheet(self.wb, sheet.name, rel.target, self.shared_strings) + ws.sheet_state = sheet.state + self.wb._sheets.append(ws) + continue + else: + fh = self.archive.open(rel.target) + ws = self.wb.create_sheet(sheet.name) + ws._rels = rels + ws_parser = WorksheetReader(ws, fh, self.shared_strings, self.data_only, self.rich_text) + ws_parser.bind_all() + fh.close() + + # assign any comments to cells + for r in rels.find(COMMENTS_NS): + src = self.archive.read(r.target) + comment_sheet = CommentSheet.from_tree(fromstring(src)) + for ref, comment in comment_sheet.comments: + try: + ws[ref].comment = comment + except AttributeError: + c = ws[ref] + if isinstance(c, MergedCell): + warnings.warn(comment_warning.format(ws.title, c.coordinate)) + continue + + # preserve link to VML file if VBA + if self.wb.vba_archive and ws.legacy_drawing: + ws.legacy_drawing = rels.get(ws.legacy_drawing).target + else: + ws.legacy_drawing = None + + for t in ws_parser.tables: + src = self.archive.read(t) + xml = fromstring(src) + table = Table.from_tree(xml) + ws.add_table(table) + + drawings = rels.find(SpreadsheetDrawing._rel_type) + for rel in drawings: + charts, images = find_images(self.archive, rel.target) + for c in charts: + ws.add_chart(c, c.anchor) + for im in images: + ws.add_image(im, im.anchor) + + pivot_rel = rels.find(TableDefinition.rel_type) + pivot_caches = self.parser.pivot_caches + for r in pivot_rel: + pivot_path = r.Target + src = self.archive.read(pivot_path) + tree = fromstring(src) + pivot = TableDefinition.from_tree(tree) + pivot.cache = pivot_caches[pivot.cacheId] + ws.add_pivot(pivot) + + ws.sheet_state = sheet.state + + + def read(self): + action = "read manifest" + try: + self.read_manifest() + action = "read strings" + self.read_strings() + action = "read workbook" + self.read_workbook() + action = "read properties" + self.read_properties() + action = "read custom properties" + self.read_custom() + action = "read theme" + self.read_theme() + action = "read stylesheet" + apply_stylesheet(self.archive, self.wb) + action = "read worksheets" + self.read_worksheets() + action = "assign names" + self.parser.assign_names() + if not self.read_only: + self.archive.close() + except ValueError as e: + raise ValueError( + f"Unable to read workbook: could not {action} from {self.archive.filename}.\n" + "This is most probably because the workbook source files contain some invalid XML.\n" + "Please see the exception for more details." + ) from e + + +def load_workbook(filename, read_only=False, keep_vba=KEEP_VBA, + data_only=False, keep_links=True, rich_text=False): + """Open the given filename and return the workbook + + :param filename: the path to open or a file-like object + :type filename: string or a file-like object open in binary mode c.f., :class:`zipfile.ZipFile` + + :param read_only: optimised for reading, content cannot be edited + :type read_only: bool + + :param keep_vba: preserve vba content (this does NOT mean you can use it) + :type keep_vba: bool + + :param data_only: controls whether cells with formulae have either the formula (default) or the value stored the last time Excel read the sheet + :type data_only: bool + + :param keep_links: whether links to external workbooks should be preserved. The default is True + :type keep_links: bool + + :param rich_text: if set to True openpyxl will preserve any rich text formatting in cells. The default is False + :type rich_text: bool + + :rtype: :class:`openpyxl.workbook.Workbook` + + .. note:: + + When using lazy load, all worksheets will be :class:`openpyxl.worksheet.iter_worksheet.IterableWorksheet` + and the returned workbook will be read-only. + + """ + reader = ExcelReader(filename, read_only, keep_vba, + data_only, keep_links, rich_text) + reader.read() + return reader.wb diff --git a/venv/lib/python3.12/site-packages/openpyxl/reader/strings.py b/venv/lib/python3.12/site-packages/openpyxl/reader/strings.py new file mode 100644 index 0000000..5168f20 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/reader/strings.py @@ -0,0 +1,44 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.cell.text import Text + +from openpyxl.xml.functions import iterparse +from openpyxl.xml.constants import SHEET_MAIN_NS +from openpyxl.cell.rich_text import CellRichText + + +def read_string_table(xml_source): + """Read in all shared strings in the table""" + + strings = [] + STRING_TAG = '{%s}si' % SHEET_MAIN_NS + + for _, node in iterparse(xml_source): + if node.tag == STRING_TAG: + text = Text.from_tree(node).content + text = text.replace('x005F_', '') + node.clear() + + strings.append(text) + + return strings + + +def read_rich_text(xml_source): + """Read in all shared strings in the table""" + + strings = [] + STRING_TAG = '{%s}si' % SHEET_MAIN_NS + + for _, node in iterparse(xml_source): + if node.tag == STRING_TAG: + text = CellRichText.from_tree(node) + if len(text) == 0: + text = '' + elif len(text) == 1 and isinstance(text[0], str): + text = text[0] + node.clear() + + strings.append(text) + + return strings diff --git a/venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py b/venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py new file mode 100644 index 0000000..2afbfdd --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/reader/workbook.py @@ -0,0 +1,133 @@ +# Copyright (c) 2010-2024 openpyxl + +from warnings import warn + +from openpyxl.xml.functions import fromstring + +from openpyxl.packaging.relationship import ( + get_dependents, + get_rels_path, + get_rel, +) +from openpyxl.packaging.workbook import WorkbookPackage +from openpyxl.workbook import Workbook +from openpyxl.workbook.defined_name import DefinedNameList +from openpyxl.workbook.external_link.external import read_external_link +from openpyxl.pivot.cache import CacheDefinition +from openpyxl.pivot.record import RecordList +from openpyxl.worksheet.print_settings import PrintTitles, PrintArea + +from openpyxl.utils.datetime import CALENDAR_MAC_1904 + + +class WorkbookParser: + + _rels = None + + def __init__(self, archive, workbook_part_name, keep_links=True): + self.archive = archive + self.workbook_part_name = workbook_part_name + self.defined_names = DefinedNameList() + self.wb = Workbook() + self.keep_links = keep_links + self.sheets = [] + + + @property + def rels(self): + if self._rels is None: + self._rels = get_dependents(self.archive, get_rels_path(self.workbook_part_name)).to_dict() + return self._rels + + + def parse(self): + src = self.archive.read(self.workbook_part_name) + node = fromstring(src) + package = WorkbookPackage.from_tree(node) + if package.properties.date1904: + self.wb.epoch = CALENDAR_MAC_1904 + + self.wb.code_name = package.properties.codeName + self.wb.active = package.active + self.wb.views = package.bookViews + self.sheets = package.sheets + self.wb.calculation = package.calcPr + self.caches = package.pivotCaches + + # external links contain cached worksheets and can be very big + if not self.keep_links: + package.externalReferences = [] + + for ext_ref in package.externalReferences: + rel = self.rels.get(ext_ref.id) + self.wb._external_links.append( + read_external_link(self.archive, rel.Target) + ) + + if package.definedNames: + self.defined_names = package.definedNames + + self.wb.security = package.workbookProtection + + + def find_sheets(self): + """ + Find all sheets in the workbook and return the link to the source file. + + Older XLSM files sometimes contain invalid sheet elements. + Warn user when these are removed. + """ + + for sheet in self.sheets: + if not sheet.id: + msg = f"File contains an invalid specification for {0}. This will be removed".format(sheet.name) + warn(msg) + continue + yield sheet, self.rels[sheet.id] + + + def assign_names(self): + """ + Bind defined names and other definitions to worksheets or the workbook + """ + + for idx, names in self.defined_names.by_sheet().items(): + if idx == "global": + self.wb.defined_names = names + continue + + try: + sheet = self.wb._sheets[idx] + except IndexError: + warn(f"Defined names for sheet index {idx} cannot be located") + continue + + for name, defn in names.items(): + reserved = defn.is_reserved + if reserved is None: + sheet.defined_names[name] = defn + + elif reserved == "Print_Titles": + titles = PrintTitles.from_string(defn.value) + sheet._print_rows = titles.rows + sheet._print_cols = titles.cols + elif reserved == "Print_Area": + try: + sheet._print_area = PrintArea.from_string(defn.value) + except TypeError: + warn(f"Print area cannot be set to Defined name: {defn.value}.") + continue + + @property + def pivot_caches(self): + """ + Get PivotCache objects + """ + d = {} + for c in self.caches: + cache = get_rel(self.archive, self.rels, id=c.id, cls=CacheDefinition) + if cache.deps: + records = get_rel(self.archive, cache.deps, cache.id, RecordList) + cache.records = records + d[c.cacheId] = cache + return d diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py b/venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py new file mode 100644 index 0000000..ea20d0d --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2010-2024 openpyxl + + +from .alignment import Alignment +from .borders import Border, Side +from .colors import Color +from .fills import PatternFill, GradientFill, Fill +from .fonts import Font, DEFAULT_FONT +from .numbers import NumberFormatDescriptor, is_date_format, is_builtin +from .protection import Protection +from .named_styles import NamedStyle diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py b/venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py new file mode 100644 index 0000000..a727f67 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py @@ -0,0 +1,62 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string + +from openpyxl.descriptors import Bool, MinMax, Min, Alias, NoneSet +from openpyxl.descriptors.serialisable import Serialisable + + +horizontal_alignments = ( + "general", "left", "center", "right", "fill", "justify", "centerContinuous", + "distributed", ) +vertical_aligments = ( + "top", "center", "bottom", "justify", "distributed", +) + +class Alignment(Serialisable): + """Alignment options for use in styles.""" + + tagname = "alignment" + + horizontal = NoneSet(values=horizontal_alignments) + vertical = NoneSet(values=vertical_aligments) + textRotation = NoneSet(values=range(181)) + textRotation.values.add(255) + text_rotation = Alias('textRotation') + wrapText = Bool(allow_none=True) + wrap_text = Alias('wrapText') + shrinkToFit = Bool(allow_none=True) + shrink_to_fit = Alias('shrinkToFit') + indent = MinMax(min=0, max=255) + relativeIndent = MinMax(min=-255, max=255) + justifyLastLine = Bool(allow_none=True) + readingOrder = Min(min=0) + + def __init__(self, horizontal=None, vertical=None, + textRotation=0, wrapText=None, shrinkToFit=None, indent=0, relativeIndent=0, + justifyLastLine=None, readingOrder=0, text_rotation=None, + wrap_text=None, shrink_to_fit=None, mergeCell=None): + self.horizontal = horizontal + self.vertical = vertical + self.indent = indent + self.relativeIndent = relativeIndent + self.justifyLastLine = justifyLastLine + self.readingOrder = readingOrder + if text_rotation is not None: + textRotation = text_rotation + if textRotation is not None: + self.textRotation = int(textRotation) + if wrap_text is not None: + wrapText = wrap_text + self.wrapText = wrapText + if shrink_to_fit is not None: + shrinkToFit = shrink_to_fit + self.shrinkToFit = shrinkToFit + # mergeCell is vestigial + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value is not None and value != 0: + yield attr, safe_string(value) diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/borders.py b/venv/lib/python3.12/site-packages/openpyxl/styles/borders.py new file mode 100644 index 0000000..f9fce81 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/borders.py @@ -0,0 +1,103 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string +from openpyxl.descriptors import ( + NoneSet, + Typed, + Bool, + Alias, + Sequence, + Integer, +) +from openpyxl.descriptors.serialisable import Serialisable + +from .colors import ColorDescriptor + + +BORDER_NONE = None +BORDER_DASHDOT = 'dashDot' +BORDER_DASHDOTDOT = 'dashDotDot' +BORDER_DASHED = 'dashed' +BORDER_DOTTED = 'dotted' +BORDER_DOUBLE = 'double' +BORDER_HAIR = 'hair' +BORDER_MEDIUM = 'medium' +BORDER_MEDIUMDASHDOT = 'mediumDashDot' +BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot' +BORDER_MEDIUMDASHED = 'mediumDashed' +BORDER_SLANTDASHDOT = 'slantDashDot' +BORDER_THICK = 'thick' +BORDER_THIN = 'thin' + + +class Side(Serialisable): + + """Border options for use in styles. + Caution: if you do not specify a border_style, other attributes will + have no effect !""" + + + color = ColorDescriptor(allow_none=True) + style = NoneSet(values=('dashDot','dashDotDot', 'dashed','dotted', + 'double','hair', 'medium', 'mediumDashDot', 'mediumDashDotDot', + 'mediumDashed', 'slantDashDot', 'thick', 'thin') + ) + border_style = Alias('style') + + def __init__(self, style=None, color=None, border_style=None): + if border_style is not None: + style = border_style + self.style = style + self.color = color + + +class Border(Serialisable): + """Border positioning for use in styles.""" + + tagname = "border" + + __elements__ = ('start', 'end', 'left', 'right', 'top', 'bottom', + 'diagonal', 'vertical', 'horizontal') + + # child elements + start = Typed(expected_type=Side, allow_none=True) + end = Typed(expected_type=Side, allow_none=True) + left = Typed(expected_type=Side, allow_none=True) + right = Typed(expected_type=Side, allow_none=True) + top = Typed(expected_type=Side, allow_none=True) + bottom = Typed(expected_type=Side, allow_none=True) + diagonal = Typed(expected_type=Side, allow_none=True) + vertical = Typed(expected_type=Side, allow_none=True) + horizontal = Typed(expected_type=Side, allow_none=True) + # attributes + outline = Bool() + diagonalUp = Bool() + diagonalDown = Bool() + + def __init__(self, left=None, right=None, top=None, + bottom=None, diagonal=None, diagonal_direction=None, + vertical=None, horizontal=None, diagonalUp=False, diagonalDown=False, + outline=True, start=None, end=None): + self.left = left + self.right = right + self.top = top + self.bottom = bottom + self.diagonal = diagonal + self.vertical = vertical + self.horizontal = horizontal + self.diagonal_direction = diagonal_direction + self.diagonalUp = diagonalUp + self.diagonalDown = diagonalDown + self.outline = outline + self.start = start + self.end = end + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value and attr != "outline": + yield attr, safe_string(value) + elif attr == "outline" and not value: + yield attr, safe_string(value) + +DEFAULT_BORDER = Border(left=Side(), right=Side(), top=Side(), bottom=Side(), diagonal=Side()) diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py b/venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py new file mode 100644 index 0000000..7095eb3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py @@ -0,0 +1,1397 @@ +# Copyright (c) 2010-2024 openpyxl + +# Builtins styles as defined in Part 4 Annex G.2 + +from .named_styles import NamedStyle +from openpyxl.xml.functions import fromstring + + +normal = """ + + + + + + + + + + + + + + + + + + + + +""" + +comma = """ + + + _-* #,##0.00\\ _$_-;\\-* #,##0.00\\ _$_-;_-* "-"??\\ _$_-;_-@_- + + + + + + + + + + + + + + + + + + +""" + +comma_0 = """ + + + _-* #,##0\\ _$_-;\\-* #,##0\\ _$_-;_-* "-"\\ _$_-;_-@_- + + + + + + + + + + + + + + + + + + +""" + +currency = """ + + + _-* #,##0.00\\ "$"_-;\\-* #,##0.00\\ "$"_-;_-* "-"??\\ "$"_-;_-@_- + + + + + + + + + + + + + + + + + + +""" + +currency_0 = """ + + + _-* #,##0\\ "$"_-;\\-* #,##0\\ "$"_-;_-* "-"\\ "$"_-;_-@_- + + + + + + + + + + + + + + + + + + +""" + +percent = """ + + + 0% + + + + + + + + + + + + + + + + + + +""" + +hyperlink = """ + + + + + + + + + + + + + + + + + + + + """ + +followed_hyperlink = """ + + + + + + + + + + + + + + + + + + + + """ + +title = """ + + + + + + + + + + + + + + + + + + + + + +""" + +headline_1 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +headline_2 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +headline_3 = """ + + + + + + + + + + + + + + + + + + + + + + + + +""" + +headline_4 = """ + + + + + + + + + + + + + + + + + + + + + +""" + +good = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +bad = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +neutral = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +input = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +output = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +calculation = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +linked_cell = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +check_cell = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +warning = """ + + + + + + + + + + + + + + + + + + + + +""" + +note = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +explanatory = """ + + + + + + + + + + + + + + + + + + + + + +""" + +total = """ + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_1 = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_1_20 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_1_40 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_1_60 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_2 = """ + + + + + + + + + + + + + + + + + + + + + """ + +accent_2_20 = """ + + + + + + + + + + + + + + + + + + + + + + + """ + +accent_2_40 = """ + + + + + + + + + + + + + + + + + + + + + + + """ + +accent_2_60 = """ + + + + + + + + + + + + + + + + + + + + + + + """ + +accent_3 = """ + + + + + + + + + + + + + + + + + + + + + + """ + +accent_3_20 = """ + + + + + + + + + + + + + + + + + + + + + + + """ + +accent_3_40 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" +accent_3_60 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" +accent_4 = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_4_20 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_4_40 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_4_60 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_5 = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_5_20 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_5_40 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_5_60 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_6 = """ + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_6_20 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_6_40 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +accent_6_60 = """ + + + + + + + + + + + + + + + + + + + + + + + +""" + +pandas_highlight = """ + +""" + +styles = dict( + [ + ('Normal', NamedStyle.from_tree(fromstring(normal))), + ('Comma', NamedStyle.from_tree(fromstring(comma))), + ('Currency', NamedStyle.from_tree(fromstring(currency))), + ('Percent', NamedStyle.from_tree(fromstring(percent))), + ('Comma [0]', NamedStyle.from_tree(fromstring(comma_0))), + ('Currency [0]', NamedStyle.from_tree(fromstring(currency_0))), + ('Hyperlink', NamedStyle.from_tree(fromstring(hyperlink))), + ('Followed Hyperlink', NamedStyle.from_tree(fromstring(followed_hyperlink))), + ('Note', NamedStyle.from_tree(fromstring(note))), + ('Warning Text', NamedStyle.from_tree(fromstring(warning))), + ('Title', NamedStyle.from_tree(fromstring(title))), + ('Headline 1', NamedStyle.from_tree(fromstring(headline_1))), + ('Headline 2', NamedStyle.from_tree(fromstring(headline_2))), + ('Headline 3', NamedStyle.from_tree(fromstring(headline_3))), + ('Headline 4', NamedStyle.from_tree(fromstring(headline_4))), + ('Input', NamedStyle.from_tree(fromstring(input))), + ('Output', NamedStyle.from_tree(fromstring(output))), + ('Calculation',NamedStyle.from_tree(fromstring(calculation))), + ('Check Cell', NamedStyle.from_tree(fromstring(check_cell))), + ('Linked Cell', NamedStyle.from_tree(fromstring(linked_cell))), + ('Total', NamedStyle.from_tree(fromstring(total))), + ('Good', NamedStyle.from_tree(fromstring(good))), + ('Bad', NamedStyle.from_tree(fromstring(bad))), + ('Neutral', NamedStyle.from_tree(fromstring(neutral))), + ('Accent1', NamedStyle.from_tree(fromstring(accent_1))), + ('20 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_20))), + ('40 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_40))), + ('60 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_60))), + ('Accent2', NamedStyle.from_tree(fromstring(accent_2))), + ('20 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_20))), + ('40 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_40))), + ('60 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_60))), + ('Accent3', NamedStyle.from_tree(fromstring(accent_3))), + ('20 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_20))), + ('40 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_40))), + ('60 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_60))), + ('Accent4', NamedStyle.from_tree(fromstring(accent_4))), + ('20 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_20))), + ('40 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_40))), + ('60 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_60))), + ('Accent5', NamedStyle.from_tree(fromstring(accent_5))), + ('20 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_20))), + ('40 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_40))), + ('60 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_60))), + ('Accent6', NamedStyle.from_tree(fromstring(accent_6))), + ('20 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_20))), + ('40 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_40))), + ('60 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_60))), + ('Explanatory Text', NamedStyle.from_tree(fromstring(explanatory))), + ('Pandas', NamedStyle.from_tree(fromstring(pandas_highlight))) + ] +) diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py b/venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py new file mode 100644 index 0000000..51091aa --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py @@ -0,0 +1,206 @@ +# Copyright (c) 2010-2024 openpyxl + +from array import array + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, + Float, + Bool, + Integer, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.utils.indexed_list import IndexedList + + +from .alignment import Alignment +from .protection import Protection + + +class ArrayDescriptor: + + def __init__(self, key): + self.key = key + + def __get__(self, instance, cls): + return instance[self.key] + + def __set__(self, instance, value): + instance[self.key] = value + + +class StyleArray(array): + """ + Simplified named tuple with an array + """ + + __slots__ = () + tagname = 'xf' + + fontId = ArrayDescriptor(0) + fillId = ArrayDescriptor(1) + borderId = ArrayDescriptor(2) + numFmtId = ArrayDescriptor(3) + protectionId = ArrayDescriptor(4) + alignmentId = ArrayDescriptor(5) + pivotButton = ArrayDescriptor(6) + quotePrefix = ArrayDescriptor(7) + xfId = ArrayDescriptor(8) + + + def __new__(cls, args=[0]*9): + return array.__new__(cls, 'i', args) + + + def __hash__(self): + return hash(tuple(self)) + + + def __copy__(self): + return StyleArray((self)) + + + def __deepcopy__(self, memo): + return StyleArray((self)) + + +class CellStyle(Serialisable): + + tagname = "xf" + + numFmtId = Integer() + fontId = Integer() + fillId = Integer() + borderId = Integer() + xfId = Integer(allow_none=True) + quotePrefix = Bool(allow_none=True) + pivotButton = Bool(allow_none=True) + applyNumberFormat = Bool(allow_none=True) + applyFont = Bool(allow_none=True) + applyFill = Bool(allow_none=True) + applyBorder = Bool(allow_none=True) + applyAlignment = Bool(allow_none=True) + applyProtection = Bool(allow_none=True) + alignment = Typed(expected_type=Alignment, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('alignment', 'protection') + __attrs__ = ("numFmtId", "fontId", "fillId", "borderId", + "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId") + + def __init__(self, + numFmtId=0, + fontId=0, + fillId=0, + borderId=0, + xfId=None, + quotePrefix=None, + pivotButton=None, + applyNumberFormat=None, + applyFont=None, + applyFill=None, + applyBorder=None, + applyAlignment=None, + applyProtection=None, + alignment=None, + protection=None, + extLst=None, + ): + self.numFmtId = numFmtId + self.fontId = fontId + self.fillId = fillId + self.borderId = borderId + self.xfId = xfId + self.quotePrefix = quotePrefix + self.pivotButton = pivotButton + self.applyNumberFormat = applyNumberFormat + self.applyFont = applyFont + self.applyFill = applyFill + self.applyBorder = applyBorder + self.alignment = alignment + self.protection = protection + + + def to_array(self): + """ + Convert to StyleArray + """ + style = StyleArray() + for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton", + "quotePrefix", "xfId"): + v = getattr(self, k, 0) + if v is not None: + setattr(style, k, v) + return style + + + @classmethod + def from_array(cls, style): + """ + Convert from StyleArray + """ + return cls(numFmtId=style.numFmtId, fontId=style.fontId, + fillId=style.fillId, borderId=style.borderId, xfId=style.xfId, + quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,) + + + @property + def applyProtection(self): + return self.protection is not None or None + + + @property + def applyAlignment(self): + return self.alignment is not None or None + + +class CellStyleList(Serialisable): + + tagname = "cellXfs" + + __attrs__ = ("count",) + + count = Integer(allow_none=True) + xf = Sequence(expected_type=CellStyle) + alignment = Sequence(expected_type=Alignment) + protection = Sequence(expected_type=Protection) + + __elements__ = ('xf',) + + def __init__(self, + count=None, + xf=(), + ): + self.xf = xf + + + @property + def count(self): + return len(self.xf) + + + def __getitem__(self, idx): + try: + return self.xf[idx] + except IndexError: + print((f"{idx} is out of range")) + return self.xf[idx] + + + def _to_array(self): + """ + Extract protection and alignments, convert to style array + """ + self.prots = IndexedList([Protection()]) + self.alignments = IndexedList([Alignment()]) + styles = [] # allow duplicates + for xf in self.xf: + style = xf.to_array() + if xf.alignment is not None: + style.alignmentId = self.alignments.add(xf.alignment) + if xf.protection is not None: + style.protectionId = self.prots.add(xf.protection) + styles.append(style) + return IndexedList(styles) diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/colors.py b/venv/lib/python3.12/site-packages/openpyxl/styles/colors.py new file mode 100644 index 0000000..6fa7476 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/colors.py @@ -0,0 +1,172 @@ +# Copyright (c) 2010-2024 openpyxl + +import re +from openpyxl.compat import safe_string +from openpyxl.descriptors import ( + String, + Bool, + MinMax, + Integer, + Typed, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.serialisable import Serialisable + +# Default Color Index as per 18.8.27 of ECMA Part 4 +COLOR_INDEX = ( + '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', #0-4 + '00FFFF00', '00FF00FF', '0000FFFF', '00000000', '00FFFFFF', #5-9 + '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', #10-14 + '0000FFFF', '00800000', '00008000', '00000080', '00808000', #15-19 + '00800080', '00008080', '00C0C0C0', '00808080', '009999FF', #20-24 + '00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', #25-29 + '000066CC', '00CCCCFF', '00000080', '00FF00FF', '00FFFF00', #30-34 + '0000FFFF', '00800080', '00800000', '00008080', '000000FF', #35-39 + '0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', #40-44 + '00FF99CC', '00CC99FF', '00FFCC99', '003366FF', '0033CCCC', #45-49 + '0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', #50-54 + '00969696', '00003366', '00339966', '00003300', '00333300', #55-59 + '00993300', '00993366', '00333399', '00333333', #60-63 +) +# indices 64 and 65 are reserved for the system foreground and background colours respectively + +# Will remove these definitions in a future release +BLACK = COLOR_INDEX[0] +WHITE = COLOR_INDEX[1] +#RED = COLOR_INDEX[2] +#DARKRED = COLOR_INDEX[8] +BLUE = COLOR_INDEX[4] +#DARKBLUE = COLOR_INDEX[12] +#GREEN = COLOR_INDEX[3] +#DARKGREEN = COLOR_INDEX[9] +#YELLOW = COLOR_INDEX[5] +#DARKYELLOW = COLOR_INDEX[19] + + +aRGB_REGEX = re.compile("^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$") + + +class RGB(Typed): + """ + Descriptor for aRGB values + If not supplied alpha is 00 + """ + + expected_type = str + + def __set__(self, instance, value): + if not self.allow_none: + m = aRGB_REGEX.match(value) + if m is None: + raise ValueError("Colors must be aRGB hex values") + if len(value) == 6: + value = "00" + value + super().__set__(instance, value) + + +class Color(Serialisable): + """Named colors for use in styles.""" + + tagname = "color" + + rgb = RGB() + indexed = Integer() + auto = Bool() + theme = Integer() + tint = MinMax(min=-1, max=1, expected_type=float) + type = String() + + + def __init__(self, rgb=BLACK, indexed=None, auto=None, theme=None, tint=0.0, index=None, type='rgb'): + if index is not None: + indexed = index + if indexed is not None: + self.type = 'indexed' + self.indexed = indexed + elif theme is not None: + self.type = 'theme' + self.theme = theme + elif auto is not None: + self.type = 'auto' + self.auto = auto + else: + self.rgb = rgb + self.type = 'rgb' + self.tint = tint + + @property + def value(self): + return getattr(self, self.type) + + @value.setter + def value(self, value): + setattr(self, self.type, value) + + def __iter__(self): + attrs = [(self.type, self.value)] + if self.tint != 0: + attrs.append(('tint', self.tint)) + for k, v in attrs: + yield k, safe_string(v) + + @property + def index(self): + # legacy + return self.value + + + def __add__(self, other): + """ + Adding colours is undefined behaviour best do nothing + """ + if not isinstance(other, Color): + return super().__add__(other) + return self + + +class ColorDescriptor(Typed): + + expected_type = Color + + def __set__(self, instance, value): + if isinstance(value, str): + value = Color(rgb=value) + super().__set__(instance, value) + + +class RgbColor(Serialisable): + + tagname = "rgbColor" + + rgb = RGB() + + def __init__(self, + rgb=None, + ): + self.rgb = rgb + + +class ColorList(Serialisable): + + tagname = "colors" + + indexedColors = NestedSequence(expected_type=RgbColor) + mruColors = NestedSequence(expected_type=Color) + + __elements__ = ('indexedColors', 'mruColors') + + def __init__(self, + indexedColors=(), + mruColors=(), + ): + self.indexedColors = indexedColors + self.mruColors = mruColors + + + def __bool__(self): + return bool(self.indexedColors) or bool(self.mruColors) + + + @property + def index(self): + return [val.rgb for val in self.indexedColors] diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/differential.py b/venv/lib/python3.12/site-packages/openpyxl/styles/differential.py new file mode 100644 index 0000000..109577e --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/differential.py @@ -0,0 +1,95 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Typed, + Sequence, + Alias, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.styles import ( + Font, + Fill, + Border, + Alignment, + Protection, + ) +from .numbers import NumberFormat + + +class DifferentialStyle(Serialisable): + + tagname = "dxf" + + __elements__ = ("font", "numFmt", "fill", "alignment", "border", "protection") + + font = Typed(expected_type=Font, allow_none=True) + numFmt = Typed(expected_type=NumberFormat, allow_none=True) + fill = Typed(expected_type=Fill, allow_none=True) + alignment = Typed(expected_type=Alignment, allow_none=True) + border = Typed(expected_type=Border, allow_none=True) + protection = Typed(expected_type=Protection, allow_none=True) + + def __init__(self, + font=None, + numFmt=None, + fill=None, + alignment=None, + border=None, + protection=None, + extLst=None, + ): + self.font = font + self.numFmt = numFmt + self.fill = fill + self.alignment = alignment + self.border = border + self.protection = protection + self.extLst = extLst + + +class DifferentialStyleList(Serialisable): + """ + Dedupable container for differential styles. + """ + + tagname = "dxfs" + + dxf = Sequence(expected_type=DifferentialStyle) + styles = Alias("dxf") + __attrs__ = ("count",) + + + def __init__(self, dxf=(), count=None): + self.dxf = dxf + + + def append(self, dxf): + """ + Check to see whether style already exists and append it if does not. + """ + if not isinstance(dxf, DifferentialStyle): + raise TypeError('expected ' + str(DifferentialStyle)) + if dxf in self.styles: + return + self.styles.append(dxf) + + + def add(self, dxf): + """ + Add a differential style and return its index + """ + self.append(dxf) + return self.styles.index(dxf) + + + def __bool__(self): + return bool(self.styles) + + + def __getitem__(self, idx): + return self.styles[idx] + + + @property + def count(self): + return len(self.dxf) diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/fills.py b/venv/lib/python3.12/site-packages/openpyxl/styles/fills.py new file mode 100644 index 0000000..7071abd --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/fills.py @@ -0,0 +1,224 @@ + +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.descriptors import ( + Float, + Set, + Alias, + NoneSet, + Sequence, + Integer, + MinMax, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.compat import safe_string + +from .colors import ColorDescriptor, Color + +from openpyxl.xml.functions import Element, localname +from openpyxl.xml.constants import SHEET_MAIN_NS + + +FILL_NONE = 'none' +FILL_SOLID = 'solid' +FILL_PATTERN_DARKDOWN = 'darkDown' +FILL_PATTERN_DARKGRAY = 'darkGray' +FILL_PATTERN_DARKGRID = 'darkGrid' +FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal' +FILL_PATTERN_DARKTRELLIS = 'darkTrellis' +FILL_PATTERN_DARKUP = 'darkUp' +FILL_PATTERN_DARKVERTICAL = 'darkVertical' +FILL_PATTERN_GRAY0625 = 'gray0625' +FILL_PATTERN_GRAY125 = 'gray125' +FILL_PATTERN_LIGHTDOWN = 'lightDown' +FILL_PATTERN_LIGHTGRAY = 'lightGray' +FILL_PATTERN_LIGHTGRID = 'lightGrid' +FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal' +FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis' +FILL_PATTERN_LIGHTUP = 'lightUp' +FILL_PATTERN_LIGHTVERTICAL = 'lightVertical' +FILL_PATTERN_MEDIUMGRAY = 'mediumGray' + +fills = (FILL_SOLID, FILL_PATTERN_DARKDOWN, FILL_PATTERN_DARKGRAY, + FILL_PATTERN_DARKGRID, FILL_PATTERN_DARKHORIZONTAL, FILL_PATTERN_DARKTRELLIS, + FILL_PATTERN_DARKUP, FILL_PATTERN_DARKVERTICAL, FILL_PATTERN_GRAY0625, + FILL_PATTERN_GRAY125, FILL_PATTERN_LIGHTDOWN, FILL_PATTERN_LIGHTGRAY, + FILL_PATTERN_LIGHTGRID, FILL_PATTERN_LIGHTHORIZONTAL, + FILL_PATTERN_LIGHTTRELLIS, FILL_PATTERN_LIGHTUP, FILL_PATTERN_LIGHTVERTICAL, + FILL_PATTERN_MEDIUMGRAY) + + +class Fill(Serialisable): + + """Base class""" + + tagname = "fill" + + @classmethod + def from_tree(cls, el): + children = [c for c in el] + if not children: + return + child = children[0] + if "patternFill" in child.tag: + return PatternFill._from_tree(child) + return super(Fill, GradientFill).from_tree(child) + + +class PatternFill(Fill): + """Area fill patterns for use in styles. + Caution: if you do not specify a fill_type, other attributes will have + no effect !""" + + tagname = "patternFill" + + __elements__ = ('fgColor', 'bgColor') + + patternType = NoneSet(values=fills) + fill_type = Alias("patternType") + fgColor = ColorDescriptor() + start_color = Alias("fgColor") + bgColor = ColorDescriptor() + end_color = Alias("bgColor") + + def __init__(self, patternType=None, fgColor=Color(), bgColor=Color(), + fill_type=None, start_color=None, end_color=None): + if fill_type is not None: + patternType = fill_type + self.patternType = patternType + if start_color is not None: + fgColor = start_color + self.fgColor = fgColor + if end_color is not None: + bgColor = end_color + self.bgColor = bgColor + + @classmethod + def _from_tree(cls, el): + attrib = dict(el.attrib) + for child in el: + desc = localname(child) + attrib[desc] = Color.from_tree(child) + return cls(**attrib) + + + def to_tree(self, tagname=None, idx=None): + parent = Element("fill") + el = Element(self.tagname) + if self.patternType is not None: + el.set('patternType', self.patternType) + for c in self.__elements__: + value = getattr(self, c) + if value != Color(): + el.append(value.to_tree(c)) + parent.append(el) + return parent + + +DEFAULT_EMPTY_FILL = PatternFill() +DEFAULT_GRAY_FILL = PatternFill(patternType='gray125') + + +class Stop(Serialisable): + + tagname = "stop" + + position = MinMax(min=0, max=1) + color = ColorDescriptor() + + def __init__(self, color, position): + self.position = position + self.color = color + + +def _assign_position(values): + """ + Automatically assign positions if a list of colours is provided. + + It is not permitted to mix colours and stops + """ + n_values = len(values) + n_stops = sum(isinstance(value, Stop) for value in values) + + if n_stops == 0: + interval = 1 + if n_values > 2: + interval = 1 / (n_values - 1) + values = [Stop(value, i * interval) + for i, value in enumerate(values)] + + elif n_stops < n_values: + raise ValueError('Cannot interpret mix of Stops and Colors in GradientFill') + + pos = set() + for stop in values: + if stop.position in pos: + raise ValueError("Duplicate position {0}".format(stop.position)) + pos.add(stop.position) + + return values + + +class StopList(Sequence): + + expected_type = Stop + + def __set__(self, obj, values): + values = _assign_position(values) + super().__set__(obj, values) + + +class GradientFill(Fill): + """Fill areas with gradient + + Two types of gradient fill are supported: + + - A type='linear' gradient interpolates colours between + a set of specified Stops, across the length of an area. + The gradient is left-to-right by default, but this + orientation can be modified with the degree + attribute. A list of Colors can be provided instead + and they will be positioned with equal distance between them. + + - A type='path' gradient applies a linear gradient from each + edge of the area. Attributes top, right, bottom, left specify + the extent of fill from the respective borders. Thus top="0.2" + will fill the top 20% of the cell. + + """ + + tagname = "gradientFill" + + type = Set(values=('linear', 'path')) + fill_type = Alias("type") + degree = Float() + left = Float() + right = Float() + top = Float() + bottom = Float() + stop = StopList() + + + def __init__(self, type="linear", degree=0, left=0, right=0, top=0, + bottom=0, stop=()): + self.degree = degree + self.left = left + self.right = right + self.top = top + self.bottom = bottom + self.stop = stop + self.type = type + + + def __iter__(self): + for attr in self.__attrs__: + value = getattr(self, attr) + if value: + yield attr, safe_string(value) + + + def to_tree(self, tagname=None, namespace=None, idx=None): + parent = Element("fill") + el = super().to_tree() + parent.append(el) + return parent diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py b/venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py new file mode 100644 index 0000000..06e343f --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py @@ -0,0 +1,113 @@ +# Copyright (c) 2010-2024 openpyxl + + +from openpyxl.descriptors import ( + Alias, + Sequence, + Integer +) +from openpyxl.descriptors.serialisable import Serialisable + +from openpyxl.descriptors.nested import ( + NestedValue, + NestedBool, + NestedNoneSet, + NestedMinMax, + NestedString, + NestedInteger, + NestedFloat, +) +from .colors import ColorDescriptor, Color, BLACK + +from openpyxl.compat import safe_string +from openpyxl.xml.functions import Element, SubElement +from openpyxl.xml.constants import SHEET_MAIN_NS + + +def _no_value(tagname, value, namespace=None): + if value: + return Element(tagname, val=safe_string(value)) + + +class Font(Serialisable): + """Font options used in styles.""" + + UNDERLINE_DOUBLE = 'double' + UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' + UNDERLINE_SINGLE = 'single' + UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' + + name = NestedString(allow_none=True) + charset = NestedInteger(allow_none=True) + family = NestedMinMax(min=0, max=14, allow_none=True) + sz = NestedFloat(allow_none=True) + size = Alias("sz") + b = NestedBool(to_tree=_no_value) + bold = Alias("b") + i = NestedBool(to_tree=_no_value) + italic = Alias("i") + strike = NestedBool(allow_none=True) + strikethrough = Alias("strike") + outline = NestedBool(allow_none=True) + shadow = NestedBool(allow_none=True) + condense = NestedBool(allow_none=True) + extend = NestedBool(allow_none=True) + u = NestedNoneSet(values=('single', 'double', 'singleAccounting', + 'doubleAccounting')) + underline = Alias("u") + vertAlign = NestedNoneSet(values=('superscript', 'subscript', 'baseline')) + color = ColorDescriptor(allow_none=True) + scheme = NestedNoneSet(values=("major", "minor")) + + tagname = "font" + + __elements__ = ('name', 'charset', 'family', 'b', 'i', 'strike', 'outline', + 'shadow', 'condense', 'color', 'extend', 'sz', 'u', 'vertAlign', + 'scheme') + + + def __init__(self, name=None, sz=None, b=None, i=None, charset=None, + u=None, strike=None, color=None, scheme=None, family=None, size=None, + bold=None, italic=None, strikethrough=None, underline=None, + vertAlign=None, outline=None, shadow=None, condense=None, + extend=None): + self.name = name + self.family = family + if size is not None: + sz = size + self.sz = sz + if bold is not None: + b = bold + self.b = b + if italic is not None: + i = italic + self.i = i + if underline is not None: + u = underline + self.u = u + if strikethrough is not None: + strike = strikethrough + self.strike = strike + self.color = color + self.vertAlign = vertAlign + self.charset = charset + self.outline = outline + self.shadow = shadow + self.condense = condense + self.extend = extend + self.scheme = scheme + + + @classmethod + def from_tree(cls, node): + """ + Set default value for underline if child element is present + """ + underline = node.find("{%s}u" % SHEET_MAIN_NS) + if underline is not None and underline.get('val') is None: + underline.set("val", "single") + return super().from_tree(node) + + +DEFAULT_FONT = Font(name="Calibri", sz=11, family=2, b=False, i=False, + color=Color(theme=1), scheme="minor") diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py b/venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py new file mode 100644 index 0000000..221d333 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py @@ -0,0 +1,282 @@ +# Copyright (c) 2010-2024 openpyxl + +from openpyxl.compat import safe_string + +from openpyxl.descriptors import ( + Typed, + Integer, + Bool, + String, + Sequence, +) +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.descriptors.serialisable import Serialisable + +from .fills import PatternFill, Fill +from .fonts import Font +from .borders import Border +from .alignment import Alignment +from .protection import Protection +from .numbers import ( + NumberFormatDescriptor, + BUILTIN_FORMATS_MAX_SIZE, + BUILTIN_FORMATS_REVERSE, +) +from .cell_style import ( + StyleArray, + CellStyle, +) + + +class NamedStyle(Serialisable): + + """ + Named and editable styles + """ + + font = Typed(expected_type=Font) + fill = Typed(expected_type=Fill) + border = Typed(expected_type=Border) + alignment = Typed(expected_type=Alignment) + number_format = NumberFormatDescriptor() + protection = Typed(expected_type=Protection) + builtinId = Integer(allow_none=True) + hidden = Bool(allow_none=True) + name = String() + _wb = None + _style = StyleArray() + + + def __init__(self, + name="Normal", + font=None, + fill=None, + border=None, + alignment=None, + number_format=None, + protection=None, + builtinId=None, + hidden=False, + ): + self.name = name + self.font = font or Font() + self.fill = fill or PatternFill() + self.border = border or Border() + self.alignment = alignment or Alignment() + self.number_format = number_format + self.protection = protection or Protection() + self.builtinId = builtinId + self.hidden = hidden + self._wb = None + self._style = StyleArray() + + + def __setattr__(self, attr, value): + super().__setattr__(attr, value) + if getattr(self, '_wb', None) and attr in ( + 'font', 'fill', 'border', 'alignment', 'number_format', 'protection', + ): + self._recalculate() + + + def __iter__(self): + for key in ('name', 'builtinId', 'hidden', 'xfId'): + value = getattr(self, key, None) + if value is not None: + yield key, safe_string(value) + + + def bind(self, wb): + """ + Bind a named style to a workbook + """ + self._wb = wb + self._recalculate() + + + def _recalculate(self): + self._style.fontId = self._wb._fonts.add(self.font) + self._style.borderId = self._wb._borders.add(self.border) + self._style.fillId = self._wb._fills.add(self.fill) + self._style.protectionId = self._wb._protections.add(self.protection) + self._style.alignmentId = self._wb._alignments.add(self.alignment) + fmt = self.number_format + if fmt in BUILTIN_FORMATS_REVERSE: + fmt = BUILTIN_FORMATS_REVERSE[fmt] + else: + fmt = self._wb._number_formats.add(self.number_format) + ( + BUILTIN_FORMATS_MAX_SIZE) + self._style.numFmtId = fmt + + + def as_tuple(self): + """Return a style array representing the current style""" + return self._style + + + def as_xf(self): + """ + Return equivalent XfStyle + """ + xf = CellStyle.from_array(self._style) + xf.xfId = None + xf.pivotButton = None + xf.quotePrefix = None + if self.alignment != Alignment(): + xf.alignment = self.alignment + if self.protection != Protection(): + xf.protection = self.protection + return xf + + + def as_name(self): + """ + Return relevant named style + + """ + named = _NamedCellStyle( + name=self.name, + builtinId=self.builtinId, + hidden=self.hidden, + xfId=self._style.xfId + ) + return named + + +class NamedStyleList(list): + """ + Named styles are editable and can be applied to multiple objects + + As only the index is stored in referencing objects the order mus + be preserved. + + Returns a list of NamedStyles + """ + + def __init__(self, iterable=()): + """ + Allow a list of named styles to be passed in and index them. + """ + + for idx, s in enumerate(iterable, len(self)): + s._style.xfId = idx + super().__init__(iterable) + + + @property + def names(self): + return [s.name for s in self] + + + def __getitem__(self, key): + if isinstance(key, int): + return super().__getitem__(key) + + + for idx, name in enumerate(self.names): + if name == key: + return self[idx] + + raise KeyError("No named style with the name{0} exists".format(key)) + + def append(self, style): + if not isinstance(style, NamedStyle): + raise TypeError("""Only NamedStyle instances can be added""") + elif style.name in self.names: # hotspot + raise ValueError("""Style {0} exists already""".format(style.name)) + style._style.xfId = (len(self)) + super().append(style) + + +class _NamedCellStyle(Serialisable): + + """ + Pointer-based representation of named styles in XML + xfId refers to the corresponding CellStyleXfs + + Not used in client code. + """ + + tagname = "cellStyle" + + name = String() + xfId = Integer() + builtinId = Integer(allow_none=True) + iLevel = Integer(allow_none=True) + hidden = Bool(allow_none=True) + customBuiltin = Bool(allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = () + + + def __init__(self, + name=None, + xfId=None, + builtinId=None, + iLevel=None, + hidden=None, + customBuiltin=None, + extLst=None, + ): + self.name = name + self.xfId = xfId + self.builtinId = builtinId + self.iLevel = iLevel + self.hidden = hidden + self.customBuiltin = customBuiltin + + +class _NamedCellStyleList(Serialisable): + """ + Container for named cell style objects + + Not used in client code + """ + + tagname = "cellStyles" + + count = Integer(allow_none=True) + cellStyle = Sequence(expected_type=_NamedCellStyle) + + __attrs__ = ("count",) + + def __init__(self, + count=None, + cellStyle=(), + ): + self.cellStyle = cellStyle + + + @property + def count(self): + return len(self.cellStyle) + + + def remove_duplicates(self): + """ + Some applications contain duplicate definitions either by name or + referenced style. + + As the references are 0-based indices, styles are sorted by + index. + + Returns a list of style references with duplicates removed + """ + + def sort_fn(v): + return v.xfId + + styles = [] + names = set() + ids = set() + + for ns in sorted(self.cellStyle, key=sort_fn): + if ns.xfId in ids or ns.name in names: # skip duplicates + continue + ids.add(ns.xfId) + names.add(ns.name) + + styles.append(ns) + + return styles diff --git a/venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py b/venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py new file mode 100644 index 0000000..b548cc7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py @@ -0,0 +1,200 @@ +# Copyright (c) 2010-2024 openpyxl + +import re + +from openpyxl.descriptors import ( + String, + Sequence, + Integer, +) +from openpyxl.descriptors.serialisable import Serialisable + + +BUILTIN_FORMATS = { + 0: 'General', + 1: '0', + 2: '0.00', + 3: '#,##0', + 4: '#,##0.00', + 5: '"$"#,##0_);("$"#,##0)', + 6: '"$"#,##0_);[Red]("$"#,##0)', + 7: '"$"#,##0.00_);("$"#,##0.00)', + 8: '"$"#,##0.00_);[Red]("$"#,##0.00)', + 9: '0%', + 10: '0.00%', + 11: '0.00E+00', + 12: '# ?/?', + 13: '# ??/??', + 14: 'mm-dd-yy', + 15: 'd-mmm-yy', + 16: 'd-mmm', + 17: 'mmm-yy', + 18: 'h:mm AM/PM', + 19: 'h:mm:ss AM/PM', + 20: 'h:mm', + 21: 'h:mm:ss', + 22: 'm/d/yy h:mm', + + 37: '#,##0_);(#,##0)', + 38: '#,##0_);[Red](#,##0)', + 39: '#,##0.00_);(#,##0.00)', + 40: '#,##0.00_);[Red](#,##0.00)', + + 41: r'_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)', + 42: r'_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)', + 43: r'_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)', + + 44: r'_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)', + 45: 'mm:ss', + 46: '[h]:mm:ss', + 47: 'mmss.0', + 48: '##0.0E+0', + 49: '@', } + +BUILTIN_FORMATS_MAX_SIZE = 164 +BUILTIN_FORMATS_REVERSE = dict( + [(value, key) for key, value in BUILTIN_FORMATS.items()]) + +FORMAT_GENERAL = BUILTIN_FORMATS[0] +FORMAT_TEXT = BUILTIN_FORMATS[49] +FORMAT_NUMBER = BUILTIN_FORMATS[1] +FORMAT_NUMBER_00 = BUILTIN_FORMATS[2] +FORMAT_NUMBER_COMMA_SEPARATED1 = BUILTIN_FORMATS[4] +FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-' +FORMAT_PERCENTAGE = BUILTIN_FORMATS[9] +FORMAT_PERCENTAGE_00 = BUILTIN_FORMATS[10] +FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd' +FORMAT_DATE_YYMMDD = 'yy-mm-dd' +FORMAT_DATE_DDMMYY = 'dd/mm/yy' +FORMAT_DATE_DMYSLASH = 'd/m/y' +FORMAT_DATE_DMYMINUS = 'd-m-y' +FORMAT_DATE_DMMINUS = 'd-m' +FORMAT_DATE_MYMINUS = 'm-y' +FORMAT_DATE_XLSX14 = BUILTIN_FORMATS[14] +FORMAT_DATE_XLSX15 = BUILTIN_FORMATS[15] +FORMAT_DATE_XLSX16 = BUILTIN_FORMATS[16] +FORMAT_DATE_XLSX17 = BUILTIN_FORMATS[17] +FORMAT_DATE_XLSX22 = BUILTIN_FORMATS[22] +FORMAT_DATE_DATETIME = 'yyyy-mm-dd h:mm:ss' +FORMAT_DATE_TIME1 = BUILTIN_FORMATS[18] +FORMAT_DATE_TIME2 = BUILTIN_FORMATS[19] +FORMAT_DATE_TIME3 = BUILTIN_FORMATS[20] +FORMAT_DATE_TIME4 = BUILTIN_FORMATS[21] +FORMAT_DATE_TIME5 = BUILTIN_FORMATS[45] +FORMAT_DATE_TIME6 = BUILTIN_FORMATS[21] +FORMAT_DATE_TIME7 = 'i:s.S' +FORMAT_DATE_TIME8 = 'h:mm:ss@' +FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss' +FORMAT_DATE_YYMMDDSLASH = 'yy/mm/dd@' +FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-' +FORMAT_CURRENCY_USD = '$#,##0_-' +FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-' + + +COLORS = r"\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]" +LITERAL_GROUP = r'".*?"' # anything in quotes +LOCALE_GROUP = r'\[(?!hh?\]|mm?\]|ss?\])[^\]]*\]' # anything in square brackets, except hours or minutes or seconds +STRIP_RE = re.compile(f"{LITERAL_GROUP}|{LOCALE_GROUP}") +TIMEDELTA_RE = re.compile(r'\[hh?\](:mm(:ss(\.0*)?)?)?|\[mm?\](:ss(\.0*)?)?|\[ss?\](\.0*)?', re.I) + + +# Spec 18.8.31 numFmts +# +ve;-ve;zero;text + +def is_date_format(fmt): + if fmt is None: + return False + fmt = fmt.split(";")[0] # only look at the first format + fmt = STRIP_RE.sub("", fmt) # ignore some formats + return re.search(r"(?[A-Za-z]{1,3})? +[$]?(?P\d+)? +(:[$]?(?P[A-Za-z]{1,3})? +[$]?(?P\d+)?)? +""" +ABSOLUTE_RE = re.compile('^' + RANGE_EXPR +'$', re.VERBOSE) +SHEET_TITLE = r""" +(('(?P([^']|'')*)')|(?P[^'^ ^!]*))!""" +SHEETRANGE_RE = re.compile("""{0}(?P{1})(?=,?)""".format( + SHEET_TITLE, RANGE_EXPR), re.VERBOSE) + + +def get_column_interval(start, end): + """ + Given the start and end columns, return all the columns in the series. + + The start and end columns can be either column letters or 1-based + indexes. + """ + if isinstance(start, str): + start = column_index_from_string(start) + if isinstance(end, str): + end = column_index_from_string(end) + return [get_column_letter(x) for x in range(start, end + 1)] + + +def coordinate_from_string(coord_string): + """Convert a coordinate string like 'B12' to a tuple ('B', 12)""" + match = COORD_RE.match(coord_string) + if not match: + msg = f"Invalid cell coordinates ({coord_string})" + raise CellCoordinatesException(msg) + column, row = match.groups() + row = int(row) + if not row: + msg = f"There is no row 0 ({coord_string})" + raise CellCoordinatesException(msg) + return column, row + + +def absolute_coordinate(coord_string): + """Convert a coordinate to an absolute coordinate string (B12 -> $B$12)""" + m = ABSOLUTE_RE.match(coord_string) + if not m: + raise ValueError(f"{coord_string} is not a valid coordinate range") + + d = m.groupdict('') + for k, v in d.items(): + if v: + d[k] = f"${v}" + + if d['max_col'] or d['max_row']: + fmt = "{min_col}{min_row}:{max_col}{max_row}" + else: + fmt = "{min_col}{min_row}" + return fmt.format(**d) + + +__decimal_to_alpha = [""] + list(ascii_uppercase) + +@lru_cache(maxsize=None) +def get_column_letter(col_idx): + """ + Convert decimal column position to its ASCII (base 26) form. + + Because column indices are 1-based, strides are actually pow(26, n) + 26 + Hence, a correction is applied between pow(26, n) and pow(26, 2) + 26 to + prevent and additional column letter being prepended + + "A" == 1 == pow(26, 0) + "Z" == 26 == pow(26, 0) + 26 // decimal equivalent 10 + "AA" == 27 == pow(26, 1) + 1 + "ZZ" == 702 == pow(26, 2) + 26 // decimal equivalent 100 + """ + + if not 1 <= col_idx <= 18278: + raise ValueError("Invalid column index {0}".format(col_idx)) + + result = [] + + if col_idx < 26: + return __decimal_to_alpha[col_idx] + + while col_idx: + col_idx, remainder = divmod(col_idx, 26) + result.insert(0, __decimal_to_alpha[remainder]) + if not remainder: + col_idx -= 1 + result.insert(0, "Z") + + return "".join(result) + + +__alpha_to_decimal = {letter:pos for pos, letter in enumerate(ascii_uppercase, 1)} +__powers = (1, 26, 676) + +@lru_cache(maxsize=None) +def column_index_from_string(col): + """ + Convert ASCII column name (base 26) to decimal with 1-based index + + Characters represent descending multiples of powers of 26 + + "AFZ" == 26 * pow(26, 0) + 6 * pow(26, 1) + 1 * pow(26, 2) + """ + error_msg = f"'{col}' is not a valid column name. Column names are from A to ZZZ" + if len(col) > 3: + raise ValueError(error_msg) + idx = 0 + col = reversed(col.upper()) + for letter, power in zip(col, __powers): + try: + pos = __alpha_to_decimal[letter] + except KeyError: + raise ValueError(error_msg) + idx += pos * power + if not 0 < idx < 18279: + raise ValueError(error_msg) + return idx + + +def range_boundaries(range_string): + """ + Convert a range string into a tuple of boundaries: + (min_col, min_row, max_col, max_row) + Cell coordinates will be converted into a range with the cell at both end + """ + msg = "{0} is not a valid coordinate or range".format(range_string) + m = ABSOLUTE_RE.match(range_string) + if not m: + raise ValueError(msg) + + min_col, min_row, sep, max_col, max_row = m.groups() + + if sep: + cols = min_col, max_col + rows = min_row, max_row + + if not ( + all(cols + rows) or + all(cols) and not any(rows) or + all(rows) and not any(cols) + ): + raise ValueError(msg) + + if min_col is not None: + min_col = column_index_from_string(min_col) + + if min_row is not None: + min_row = int(min_row) + + if max_col is not None: + max_col = column_index_from_string(max_col) + else: + max_col = min_col + + if max_row is not None: + max_row = int(max_row) + else: + max_row = min_row + + return min_col, min_row, max_col, max_row + + +def rows_from_range(range_string): + """ + Get individual addresses for every cell in a range. + Yields one row at a time. + """ + min_col, min_row, max_col, max_row = range_boundaries(range_string) + rows = range(min_row, max_row + 1) + cols = [get_column_letter(col) for col in range(min_col, max_col + 1)] + for row in rows: + yield tuple('{0}{1}'.format(col, row) for col in cols) + + +def cols_from_range(range_string): + """ + Get individual addresses for every cell in a range. + Yields one row at a time. + """ + min_col, min_row, max_col, max_row = range_boundaries(range_string) + rows = range(min_row, max_row+1) + cols = (get_column_letter(col) for col in range(min_col, max_col+1)) + for col in cols: + yield tuple('{0}{1}'.format(col, row) for row in rows) + + +def coordinate_to_tuple(coordinate): + """ + Convert an Excel style coordinate to (row, column) tuple + """ + for idx, c in enumerate(coordinate): + if c in digits: + break + col = coordinate[:idx] + row = coordinate[idx:] + return int(row), column_index_from_string(col) + + +def range_to_tuple(range_string): + """ + Convert a worksheet range to the sheetname and maximum and minimum + coordinate indices + """ + m = SHEETRANGE_RE.match(range_string) + if m is None: + raise ValueError("Value must be of the form sheetname!A1:E4") + sheetname = m.group("quoted") or m.group("notquoted") + cells = m.group("cells") + boundaries = range_boundaries(cells) + return sheetname, boundaries + + +def quote_sheetname(sheetname): + """ + Add quotes around sheetnames if they contain spaces. + """ + if "'" in sheetname: + sheetname = sheetname.replace("'", "''") + + sheetname = u"'{0}'".format(sheetname) + return sheetname diff --git a/venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py b/venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py new file mode 100644 index 0000000..f56a488 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/utils/dataframe.py @@ -0,0 +1,87 @@ +# Copyright (c) 2010-2024 openpyxl + +from itertools import accumulate +import operator +import numpy +from openpyxl.compat.product import prod + + +def dataframe_to_rows(df, index=True, header=True): + """ + Convert a Pandas dataframe into something suitable for passing into a worksheet. + If index is True then the index will be included, starting one row below the header. + If header is True then column headers will be included starting one column to the right. + Formatting should be done by client code. + """ + from pandas import Timestamp + + if header: + if df.columns.nlevels > 1: + rows = expand_index(df.columns, header) + else: + rows = [list(df.columns.values)] + for row in rows: + n = [] + for v in row: + if isinstance(v, numpy.datetime64): + v = Timestamp(v) + n.append(v) + row = n + if index: + row = [None]*df.index.nlevels + row + yield row + + if index: + yield df.index.names + + expanded = ([v] for v in df.index) + if df.index.nlevels > 1: + expanded = expand_index(df.index) + + # Using the expanded index is preferable to df.itertuples(index=True) so that we have 'None' inserted where applicable + for (df_index, row) in zip(expanded, df.itertuples(index=False)): + row = list(row) + if index: + row = df_index + row + yield row + + +def expand_index(index, header=False): + """ + Expand axis or column Multiindex + For columns use header = True + For axes use header = False (default) + """ + + # For each element of the index, zip the members with the previous row + # If the 2 elements of the zipped list do not match, we can insert the new value into the row + # or if an earlier member was different, all later members should be added to the row + values = list(index.values) + previous_value = [None] * len(values[0]) + result = [] + + for value in values: + row = [None] * len(value) + + # Once there's a difference in member of an index with the prior index, we need to store all subsequent members in the row + prior_change = False + for idx, (current_index_member, previous_index_member) in enumerate(zip(value, previous_value)): + + if current_index_member != previous_index_member or prior_change: + row[idx] = current_index_member + prior_change = True + + previous_value = value + + # If this is for a row index, we're already returning a row so just yield + if not header: + yield row + else: + result.append(row) + + # If it's for a header, we need to transpose to get it in row order + # Example: result = [['A', 'A'], [None, 'B']] -> [['A', None], ['A', 'B']] + if header: + result = numpy.array(result).transpose().tolist() + for row in result: + yield row diff --git a/venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py b/venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py new file mode 100644 index 0000000..bf7e500 --- /dev/null +++ b/venv/lib/python3.12/site-packages/openpyxl/utils/datetime.py @@ -0,0 +1,140 @@ +# Copyright (c) 2010-2024 openpyxl + +"""Manage Excel date weirdness.""" + +# Python stdlib imports +import datetime +from math import isnan +import re + + +# constants +MAC_EPOCH = datetime.datetime(1904, 1, 1) +WINDOWS_EPOCH = datetime.datetime(1899, 12, 30) +CALENDAR_WINDOWS_1900 = 2415018.5 # Julian date of WINDOWS_EPOCH +CALENDAR_MAC_1904 = 2416480.5 # Julian date of MAC_EPOCH +CALENDAR_WINDOWS_1900 = WINDOWS_EPOCH +CALENDAR_MAC_1904 = MAC_EPOCH +SECS_PER_DAY = 86400 + +ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ' +ISO_REGEX = re.compile(r''' +(?P(?P\d{4})-(?P\d{2})-(?P\d{2}))?T? +(?P

`` which has one row and two + cells: one containing the line numbers and one containing the code. + Example: + + .. sourcecode:: html + +
+
+ + +
+
1
+            2
+
+
def foo(bar):
+              pass
+            
+
+ + (whitespace added to improve clarity). + + A list of lines can be specified using the `hl_lines` option to make these + lines highlighted (as of Pygments 0.11). + + With the `full` option, a complete HTML 4 document is output, including + the style definitions inside a `` + {% else %} + {{ head | safe }} + {% endif %} +{% if not embed %} + + +{% endif %} +{{ body | safe }} +{% for diagram in diagrams %} +

+{% endfor %} +{% if not embed %} + + +{% endif %} +""" + +template = Template(jinja2_template_source) + +# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet +NamedDiagram = NamedTuple( + "NamedDiagram", + [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)], +) +""" +A simple structure for associating a name with a railroad diagram +""" + +T = TypeVar("T") + + +class EachItem(railroad.Group): + """ + Custom railroad item to compose a: + - Group containing a + - OneOrMore containing a + - Choice of the elements in the Each + with the group label indicating that all must be matched + """ + + all_label = "[ALL]" + + def __init__(self, *items): + choice_item = railroad.Choice(len(items) - 1, *items) + one_or_more_item = railroad.OneOrMore(item=choice_item) + super().__init__(one_or_more_item, label=self.all_label) + + +class AnnotatedItem(railroad.Group): + """ + Simple subclass of Group that creates an annotation label + """ + + def __init__(self, label: str, item): + super().__init__(item=item, label="[{}]".format(label) if label else label) + + +class EditablePartial(Generic[T]): + """ + Acts like a functools.partial, but can be edited. In other words, it represents a type that hasn't yet been + constructed. + """ + + # We need this here because the railroad constructors actually transform the data, so can't be called until the + # entire tree is assembled + + def __init__(self, func: Callable[..., T], args: list, kwargs: dict): + self.func = func + self.args = args + self.kwargs = kwargs + + @classmethod + def from_call(cls, func: Callable[..., T], *args, **kwargs) -> "EditablePartial[T]": + """ + If you call this function in the same way that you would call the constructor, it will store the arguments + as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3) + """ + return EditablePartial(func=func, args=list(args), kwargs=kwargs) + + @property + def name(self): + return self.kwargs["name"] + + def __call__(self) -> T: + """ + Evaluate the partial and return the result + """ + args = self.args.copy() + kwargs = self.kwargs.copy() + + # This is a helpful hack to allow you to specify varargs parameters (e.g. *args) as keyword args (e.g. + # args=['list', 'of', 'things']) + arg_spec = inspect.getfullargspec(self.func) + if arg_spec.varargs in self.kwargs: + args += kwargs.pop(arg_spec.varargs) + + return self.func(*args, **kwargs) + + +def railroad_to_html(diagrams: List[NamedDiagram], embed=False, **kwargs) -> str: + """ + Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams + :params kwargs: kwargs to be passed in to the template + """ + data = [] + for diagram in diagrams: + if diagram.diagram is None: + continue + io = StringIO() + try: + css = kwargs.get('css') + diagram.diagram.writeStandalone(io.write, css=css) + except AttributeError: + diagram.diagram.writeSvg(io.write) + title = diagram.name + if diagram.index == 0: + title += " (root)" + data.append({"title": title, "text": "", "svg": io.getvalue()}) + + return template.render(diagrams=data, embed=embed, **kwargs) + + +def resolve_partial(partial: "EditablePartial[T]") -> T: + """ + Recursively resolves a collection of Partials into whatever type they are + """ + if isinstance(partial, EditablePartial): + partial.args = resolve_partial(partial.args) + partial.kwargs = resolve_partial(partial.kwargs) + return partial() + elif isinstance(partial, list): + return [resolve_partial(x) for x in partial] + elif isinstance(partial, dict): + return {key: resolve_partial(x) for key, x in partial.items()} + else: + return partial + + +def to_railroad( + element: pyparsing.ParserElement, + diagram_kwargs: typing.Optional[dict] = None, + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, +) -> List[NamedDiagram]: + """ + Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram + creation if you want to access the Railroad tree before it is converted to HTML + :param element: base element of the parser being diagrammed + :param diagram_kwargs: kwargs to pass to the Diagram() constructor + :param vertical: (optional) - int - limit at which number of alternatives should be + shown vertically instead of horizontally + :param show_results_names - bool to indicate whether results name annotations should be + included in the diagram + :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled + surrounding box + """ + # Convert the whole tree underneath the root + lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) + _to_diagram_element( + element, + lookup=lookup, + parent=None, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + root_id = id(element) + # Convert the root if it hasn't been already + if root_id in lookup: + if not element.customName: + lookup[root_id].name = "" + lookup[root_id].mark_for_extraction(root_id, lookup, force=True) + + # Now that we're finished, we can convert from intermediate structures into Railroad elements + diags = list(lookup.diagrams.values()) + if len(diags) > 1: + # collapse out duplicate diags with the same name + seen = set() + deduped_diags = [] + for d in diags: + # don't extract SkipTo elements, they are uninformative as subdiagrams + if d.name == "...": + continue + if d.name is not None and d.name not in seen: + seen.add(d.name) + deduped_diags.append(d) + resolved = [resolve_partial(partial) for partial in deduped_diags] + else: + # special case - if just one diagram, always display it, even if + # it has no name + resolved = [resolve_partial(partial) for partial in diags] + return sorted(resolved, key=lambda diag: diag.index) + + +def _should_vertical( + specification: int, exprs: Iterable[pyparsing.ParserElement] +) -> bool: + """ + Returns true if we should return a vertical list of elements + """ + if specification is None: + return False + else: + return len(_visible_exprs(exprs)) >= specification + + +class ElementState: + """ + State recorded for an individual pyparsing Element + """ + + # Note: this should be a dataclass, but we have to support Python 3.5 + def __init__( + self, + element: pyparsing.ParserElement, + converted: EditablePartial, + parent: EditablePartial, + number: int, + name: str = None, + parent_index: typing.Optional[int] = None, + ): + #: The pyparsing element that this represents + self.element: pyparsing.ParserElement = element + #: The name of the element + self.name: typing.Optional[str] = name + #: The output Railroad element in an unconverted state + self.converted: EditablePartial = converted + #: The parent Railroad element, which we store so that we can extract this if it's duplicated + self.parent: EditablePartial = parent + #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram + self.number: int = number + #: The index of this inside its parent + self.parent_index: typing.Optional[int] = parent_index + #: If true, we should extract this out into a subdiagram + self.extract: bool = False + #: If true, all of this element's children have been filled out + self.complete: bool = False + + def mark_for_extraction( + self, el_id: int, state: "ConverterState", name: str = None, force: bool = False + ): + """ + Called when this instance has been seen twice, and thus should eventually be extracted into a sub-diagram + :param el_id: id of the element + :param state: element/diagram state tracker + :param name: name to use for this element's text + :param force: If true, force extraction now, regardless of the state of this. Only useful for extracting the + root element when we know we're finished + """ + self.extract = True + + # Set the name + if not self.name: + if name: + # Allow forcing a custom name + self.name = name + elif self.element.customName: + self.name = self.element.customName + else: + self.name = "" + + # Just because this is marked for extraction doesn't mean we can do it yet. We may have to wait for children + # to be added + # Also, if this is just a string literal etc, don't bother extracting it + if force or (self.complete and _worth_extracting(self.element)): + state.extract_into_diagram(el_id) + + +class ConverterState: + """ + Stores some state that persists between recursions into the element tree + """ + + def __init__(self, diagram_kwargs: typing.Optional[dict] = None): + #: A dictionary mapping ParserElements to state relating to them + self._element_diagram_states: Dict[int, ElementState] = {} + #: A dictionary mapping ParserElement IDs to subdiagrams generated from them + self.diagrams: Dict[int, EditablePartial[NamedDiagram]] = {} + #: The index of the next unnamed element + self.unnamed_index: int = 1 + #: The index of the next element. This is used for sorting + self.index: int = 0 + #: Shared kwargs that are used to customize the construction of diagrams + self.diagram_kwargs: dict = diagram_kwargs or {} + self.extracted_diagram_names: Set[str] = set() + + def __setitem__(self, key: int, value: ElementState): + self._element_diagram_states[key] = value + + def __getitem__(self, key: int) -> ElementState: + return self._element_diagram_states[key] + + def __delitem__(self, key: int): + del self._element_diagram_states[key] + + def __contains__(self, key: int): + return key in self._element_diagram_states + + def generate_unnamed(self) -> int: + """ + Generate a number used in the name of an otherwise unnamed diagram + """ + self.unnamed_index += 1 + return self.unnamed_index + + def generate_index(self) -> int: + """ + Generate a number used to index a diagram + """ + self.index += 1 + return self.index + + def extract_into_diagram(self, el_id: int): + """ + Used when we encounter the same token twice in the same tree. When this + happens, we replace all instances of that token with a terminal, and + create a new subdiagram for the token + """ + position = self[el_id] + + # Replace the original definition of this element with a regular block + if position.parent: + ret = EditablePartial.from_call(railroad.NonTerminal, text=position.name) + if "item" in position.parent.kwargs: + position.parent.kwargs["item"] = ret + elif "items" in position.parent.kwargs: + position.parent.kwargs["items"][position.parent_index] = ret + + # If the element we're extracting is a group, skip to its content but keep the title + if position.converted.func == railroad.Group: + content = position.converted.kwargs["item"] + else: + content = position.converted + + self.diagrams[el_id] = EditablePartial.from_call( + NamedDiagram, + name=position.name, + diagram=EditablePartial.from_call( + railroad.Diagram, content, **self.diagram_kwargs + ), + index=position.number, + ) + + del self[el_id] + + +def _worth_extracting(element: pyparsing.ParserElement) -> bool: + """ + Returns true if this element is worth having its own sub-diagram. Simply, if any of its children + themselves have children, then its complex enough to extract + """ + children = element.recurse() + return any(child.recurse() for child in children) + + +def _apply_diagram_item_enhancements(fn): + """ + decorator to ensure enhancements to a diagram item (such as results name annotations) + get applied on return from _to_diagram_element (we do this since there are several + returns in _to_diagram_element) + """ + + def _inner( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, + ) -> typing.Optional[EditablePartial]: + ret = fn( + element, + parent, + lookup, + vertical, + index, + name_hint, + show_results_names, + show_groups, + ) + + # apply annotation for results name, if present + if show_results_names and ret is not None: + element_results_name = element.resultsName + if element_results_name: + # add "*" to indicate if this is a "list all results" name + element_results_name += "" if element.modalResults else "*" + ret = EditablePartial.from_call( + railroad.Group, item=ret, label=element_results_name + ) + + return ret + + return _inner + + +def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]): + non_diagramming_exprs = ( + pyparsing.ParseElementEnhance, + pyparsing.PositionToken, + pyparsing.And._ErrorStop, + ) + return [ + e + for e in exprs + if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs)) + ] + + +@_apply_diagram_item_enhancements +def _to_diagram_element( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, +) -> typing.Optional[EditablePartial]: + """ + Recursively converts a PyParsing Element to a railroad Element + :param lookup: The shared converter state that keeps track of useful things + :param index: The index of this element within the parent + :param parent: The parent of this element in the output tree + :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default), + it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never + do so + :param name_hint: If provided, this will override the generated name + :param show_results_names: bool flag indicating whether to add annotations for results names + :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed + :param show_groups: bool flag indicating whether to show groups using bounding box + """ + exprs = element.recurse() + name = name_hint or element.customName or element.__class__.__name__ + + # Python's id() is used to provide a unique identifier for elements + el_id = id(element) + + element_results_name = element.resultsName + + # Here we basically bypass processing certain wrapper elements if they contribute nothing to the diagram + if not element.customName: + if isinstance( + element, + ( + # pyparsing.TokenConverter, + # pyparsing.Forward, + pyparsing.Located, + ), + ): + # However, if this element has a useful custom name, and its child does not, we can pass it on to the child + if exprs: + if not exprs[0].customName: + propagated_name = name + else: + propagated_name = None + + return _to_diagram_element( + element.expr, + parent=parent, + lookup=lookup, + vertical=vertical, + index=index, + name_hint=propagated_name, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # If the element isn't worth extracting, we always treat it as the first time we say it + if _worth_extracting(element): + if el_id in lookup: + # If we've seen this element exactly once before, we are only just now finding out that it's a duplicate, + # so we have to extract it into a new diagram. + looked_up = lookup[el_id] + looked_up.mark_for_extraction(el_id, lookup, name=name_hint) + ret = EditablePartial.from_call(railroad.NonTerminal, text=looked_up.name) + return ret + + elif el_id in lookup.diagrams: + # If we have seen the element at least twice before, and have already extracted it into a subdiagram, we + # just put in a marker element that refers to the sub-diagram + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + return ret + + # Recursively convert child elements + # Here we find the most relevant Railroad element for matching pyparsing Element + # We use ``items=[]`` here to hold the place for where the child elements will go once created + if isinstance(element, pyparsing.And): + # detect And's created with ``expr*N`` notation - for these use a OneOrMore with a repeat + # (all will have the same name, and resultsName) + if not exprs: + return None + if len(set((e.name, e.resultsName) for e in exprs)) == 1: + ret = EditablePartial.from_call( + railroad.OneOrMore, item="", repeat=str(len(exprs)) + ) + elif _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Stack, items=[]) + else: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)): + if not exprs: + return None + if _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Choice, 0, items=[]) + else: + ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[]) + elif isinstance(element, pyparsing.Each): + if not exprs: + return None + ret = EditablePartial.from_call(EachItem, items=[]) + elif isinstance(element, pyparsing.NotAny): + ret = EditablePartial.from_call(AnnotatedItem, label="NOT", item="") + elif isinstance(element, pyparsing.FollowedBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="") + elif isinstance(element, pyparsing.PrecededBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="") + elif isinstance(element, pyparsing.Group): + if show_groups: + ret = EditablePartial.from_call(AnnotatedItem, label="", item="") + else: + ret = EditablePartial.from_call(railroad.Group, label="", item="") + elif isinstance(element, pyparsing.TokenConverter): + label = type(element).__name__.lower() + if label == "tokenconverter": + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + ret = EditablePartial.from_call(AnnotatedItem, label=label, item="") + elif isinstance(element, pyparsing.Opt): + ret = EditablePartial.from_call(railroad.Optional, item="") + elif isinstance(element, pyparsing.OneOrMore): + ret = EditablePartial.from_call(railroad.OneOrMore, item="") + elif isinstance(element, pyparsing.ZeroOrMore): + ret = EditablePartial.from_call(railroad.ZeroOrMore, item="") + elif isinstance(element, pyparsing.Group): + ret = EditablePartial.from_call( + railroad.Group, item=None, label=element_results_name + ) + elif isinstance(element, pyparsing.Empty) and not element.customName: + # Skip unnamed "Empty" elements + ret = None + elif isinstance(element, pyparsing.ParseElementEnhance): + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif len(exprs) > 0 and not element_results_name: + ret = EditablePartial.from_call(railroad.Group, item="", label=name) + elif len(exprs) > 0: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName) + ret = terminal + + if ret is None: + return + + # Indicate this element's position in the tree so we can extract it if necessary + lookup[el_id] = ElementState( + element=element, + converted=ret, + parent=parent, + parent_index=index, + number=lookup.generate_index(), + ) + if element.customName: + lookup[el_id].mark_for_extraction(el_id, lookup, element.customName) + + i = 0 + for expr in exprs: + # Add a placeholder index in case we have to extract the child before we even add it to the parent + if "items" in ret.kwargs: + ret.kwargs["items"].insert(i, None) + + item = _to_diagram_element( + expr, + parent=ret, + lookup=lookup, + vertical=vertical, + index=i, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # Some elements don't need to be shown in the diagram + if item is not None: + if "item" in ret.kwargs: + ret.kwargs["item"] = item + elif "items" in ret.kwargs: + # If we've already extracted the child, don't touch this index, since it's occupied by a nonterminal + ret.kwargs["items"][i] = item + i += 1 + elif "items" in ret.kwargs: + # If we're supposed to skip this element, remove it from the parent + del ret.kwargs["items"][i] + + # If all this items children are none, skip this item + if ret and ( + ("items" in ret.kwargs and len(ret.kwargs["items"]) == 0) + or ("item" in ret.kwargs and ret.kwargs["item"] is None) + ): + ret = EditablePartial.from_call(railroad.Terminal, name) + + # Mark this element as "complete", ie it has all of its children + if el_id in lookup: + lookup[el_id].complete = True + + if el_id in lookup and lookup[el_id].extract and lookup[el_id].complete: + lookup.extract_into_diagram(el_id) + if ret is not None: + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + + return ret diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py new file mode 100644 index 0000000..12219f1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py @@ -0,0 +1,299 @@ +# exceptions.py + +import re +import sys +import typing + +from .util import ( + col, + line, + lineno, + _collapse_string_to_ranges, + replaced_by_pep8, +) +from .unicode import pyparsing_unicode as ppu + + +class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic): + pass + + +_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums) +_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.") + + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + + loc: int + msg: str + pstr: str + parser_element: typing.Any # "ParserElement" + args: typing.Tuple[str, int, typing.Optional[str]] + + __slots__ = ( + "loc", + "msg", + "pstr", + "parser_element", + "args", + ) + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, + pstr: str, + loc: int = 0, + msg: typing.Optional[str] = None, + elem=None, + ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parser_element = elem + self.args = (pstr, loc, msg) + + @staticmethod + def explain_exception(exc, depth=16): + """ + Method to take an exception and translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - exc - exception raised during parsing (need not be a ParseException, in support + of Python exceptions that might be raised in a parse action) + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + """ + import inspect + from .core import ParserElement + + if depth is None: + depth = sys.getrecursionlimit() + ret = [] + if isinstance(exc, ParseBaseException): + ret.append(exc.line) + ret.append(" " * (exc.column - 1) + "^") + ret.append(f"{type(exc).__name__}: {exc}") + + if depth > 0: + callers = inspect.getinnerframes(exc.__traceback__, context=depth) + seen = set() + for i, ff in enumerate(callers[-depth:]): + frm = ff[0] + + f_self = frm.f_locals.get("self", None) + if isinstance(f_self, ParserElement): + if not frm.f_code.co_name.startswith( + ("parseImpl", "_parseNoCache") + ): + continue + if id(f_self) in seen: + continue + seen.add(id(f_self)) + + self_type = type(f_self) + ret.append( + f"{self_type.__module__}.{self_type.__name__} - {f_self}" + ) + + elif f_self is not None: + self_type = type(f_self) + ret.append(f"{self_type.__module__}.{self_type.__name__}") + + else: + code = frm.f_code + if code.co_name in ("wrapper", ""): + continue + + ret.append(code.co_name) + + depth -= 1 + if not depth: + break + + return "\n".join(ret) + + @classmethod + def _from_exception(cls, pe): + """ + internal factory method to simplify creating one type of ParseException + from another - avoids having __init__ signature conflicts among subclasses + """ + return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element) + + @property + def line(self) -> str: + """ + Return the line of text where the exception occurred. + """ + return line(self.loc, self.pstr) + + @property + def lineno(self) -> int: + """ + Return the 1-based line number of text where the exception occurred. + """ + return lineno(self.loc, self.pstr) + + @property + def col(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + @property + def column(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + # pre-PEP8 compatibility + @property + def parserElement(self): + return self.parser_element + + @parserElement.setter + def parserElement(self, elem): + self.parser_element = elem + + def __str__(self) -> str: + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ", found end of text" + else: + # pull out next word at error location + found_match = _exception_word_extractor.match(self.pstr, self.loc) + if found_match is not None: + found = found_match.group(0) + else: + found = self.pstr[self.loc : self.loc + 1] + foundstr = (", found %r" % found).replace(r"\\", "\\") + else: + foundstr = "" + return f"{self.msg}{foundstr} (at char {self.loc}), (line:{self.lineno}, col:{self.column})" + + def __repr__(self): + return str(self) + + def mark_input_line( + self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<" + ) -> str: + """ + Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + markerString = marker_string if marker_string is not None else markerString + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( + (line_str[:line_column], markerString, line_str[line_column:]) + ) + return line_str.strip() + + def explain(self, depth=16) -> str: + """ + Method to translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + + Example:: + + expr = pp.Word(pp.nums) * 3 + try: + expr.parse_string("123 456 A789") + except pp.ParseException as pe: + print(pe.explain(depth=0)) + + prints:: + + 123 456 A789 + ^ + ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9) + + Note: the diagnostic output will include string representations of the expressions + that failed to parse. These representations will be more helpful if you use `set_name` to + give identifiable names to your expressions. Otherwise they will use the default string + forms, which may be cryptic to read. + + Note: pyparsing's default truncation of exception tracebacks may also truncate the + stack of expressions that are displayed in the ``explain`` output. To get the full listing + of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True`` + """ + return self.explain_exception(self, depth) + + # fmt: off + @replaced_by_pep8(mark_input_line) + def markInputline(self): ... + # fmt: on + + +class ParseException(ParseBaseException): + """ + Exception thrown when a parse expression doesn't match the input string + + Example:: + + try: + Word(nums).set_name("integer").parse_string("ABC") + except ParseException as pe: + print(pe) + print("column: {}".format(pe.column)) + + prints:: + + Expected integer (at char 0), (line:1, col:1) + column: 1 + + """ + + +class ParseFatalException(ParseBaseException): + """ + User-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately + """ + + +class ParseSyntaxException(ParseFatalException): + """ + Just like :class:`ParseFatalException`, but thrown internally + when an :class:`ErrorStop` ('-' operator) indicates + that parsing is to stop immediately because an unbacktrackable + syntax error has been found. + """ + + +class RecursiveGrammarException(Exception): + """ + Exception thrown by :class:`ParserElement.validate` if the + grammar could be left-recursive; parser may need to enable + left recursion using :class:`ParserElement.enable_left_recursion` + """ + + def __init__(self, parseElementList): + self.parseElementTrace = parseElementList + + def __str__(self) -> str: + return f"RecursiveGrammarException: {self.parseElementTrace}" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py new file mode 100644 index 0000000..018f0d6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py @@ -0,0 +1,1100 @@ +# helpers.py +import html.entities +import re +import sys +import typing + +from . import __diag__ +from .core import * +from .util import ( + _bslash, + _flatten, + _escape_regex_range_chars, + replaced_by_pep8, +) + + +# +# global helpers +# +def counted_array( + expr: ParserElement, + int_expr: typing.Optional[ParserElement] = None, + *, + intExpr: typing.Optional[ParserElement] = None, +) -> ParserElement: + """Helper to define a counted list of expressions. + + This helper defines a pattern of the form:: + + integer expr expr expr... + + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the + leading count token is suppressed. + + If ``int_expr`` is specified, it should be a pyparsing expression + that produces an integer value. + + Example:: + + counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd'] + + # in this parser, the leading integer value is given in binary, + # '10' indicating that 2 values are in the array + binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) + counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd'] + + # if other fields must be parsed after the count but before the + # list items, give the fields results names and they will + # be preserved in the returned ParseResults: + count_with_metadata = integer + Word(alphas)("type") + typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items") + result = typed_array.parse_string("3 bool True True False") + print(result.dump()) + + # prints + # ['True', 'True', 'False'] + # - items: ['True', 'True', 'False'] + # - type: 'bool' + """ + intExpr = intExpr or int_expr + array_expr = Forward() + + def count_field_parse_action(s, l, t): + nonlocal array_expr + n = t[0] + array_expr <<= (expr * n) if n else Empty() + # clear list contents, but keep any named results + del t[:] + + if intExpr is None: + intExpr = Word(nums).set_parse_action(lambda t: int(t[0])) + else: + intExpr = intExpr.copy() + intExpr.set_name("arrayLen") + intExpr.add_parse_action(count_field_parse_action, call_during_try=True) + return (intExpr + array_expr).set_name("(len) " + str(expr) + "...") + + +def match_previous_literal(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_literal(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches a previous literal, will also match the leading + ``"1:1"`` in ``"1:10"``. If this is not desired, use + :class:`match_previous_expr`. Do *not* use with packrat parsing + enabled. + """ + rep = Forward() + + def copy_token_to_repeater(s, l, t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.as_list()) + rep << And(Literal(tt) for tt in tflat) + else: + rep << Empty() + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def match_previous_expr(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_expr(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches by expressions, will *not* match the leading ``"1:1"`` + in ``"1:10"``; the expressions are evaluated first, and then + compared, so ``"1"`` is compared with ``"10"``. Do *not* use + with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep <<= e2 + + def copy_token_to_repeater(s, l, t): + matchTokens = _flatten(t.as_list()) + + def must_match_these_tokens(s, l, t): + theseTokens = _flatten(t.as_list()) + if theseTokens != matchTokens: + raise ParseException( + s, l, f"Expected {matchTokens}, found{theseTokens}" + ) + + rep.set_parse_action(must_match_these_tokens, callDuringTry=True) + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def one_of( + strs: Union[typing.Iterable[str], str], + caseless: bool = False, + use_regex: bool = True, + as_keyword: bool = False, + *, + useRegex: bool = True, + asKeyword: bool = False, +) -> ParserElement: + """Helper to quickly define a set of alternative :class:`Literal` s, + and makes sure to do longest-first testing when there is a conflict, + regardless of the input order, but returns + a :class:`MatchFirst` for best performance. + + Parameters: + + - ``strs`` - a string of space-delimited literals, or a collection of + string literals + - ``caseless`` - treat all literals as caseless - (default= ``False``) + - ``use_regex`` - as an optimization, will + generate a :class:`Regex` object; otherwise, will generate + a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if + creating a :class:`Regex` raises an exception) - (default= ``True``) + - ``as_keyword`` - enforce :class:`Keyword`-style matching on the + generated expressions - (default= ``False``) + - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility, + but will be removed in a future release + + Example:: + + comp_oper = one_of("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) + + prints:: + + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + """ + asKeyword = asKeyword or as_keyword + useRegex = useRegex and use_regex + + if ( + isinstance(caseless, str_type) + and __diag__.warn_on_multiple_string_args_to_oneof + ): + warnings.warn( + "More than one string argument passed to one_of, pass" + " choices as a list or space-delimited string", + stacklevel=2, + ) + + if caseless: + isequal = lambda a, b: a.upper() == b.upper() + masks = lambda a, b: b.upper().startswith(a.upper()) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral + else: + isequal = lambda a, b: a == b + masks = lambda a, b: b.startswith(a) + parseElementClass = Keyword if asKeyword else Literal + + symbols: List[str] = [] + if isinstance(strs, str_type): + strs = typing.cast(str, strs) + symbols = strs.split() + elif isinstance(strs, Iterable): + symbols = list(strs) + else: + raise TypeError("Invalid argument to one_of, expected string or iterable") + if not symbols: + return NoMatch() + + # reorder given symbols to take care to avoid masking longer choices with shorter ones + # (but only if the given symbols are not just single characters) + if any(len(sym) > 1 for sym in symbols): + i = 0 + while i < len(symbols) - 1: + cur = symbols[i] + for j, other in enumerate(symbols[i + 1 :]): + if isequal(other, cur): + del symbols[i + j + 1] + break + elif masks(cur, other): + del symbols[i + j + 1] + symbols.insert(i, other) + break + else: + i += 1 + + if useRegex: + re_flags: int = re.IGNORECASE if caseless else 0 + + try: + if all(len(sym) == 1 for sym in symbols): + # symbols are just single characters, create range regex pattern + patt = f"[{''.join(_escape_regex_range_chars(sym) for sym in symbols)}]" + else: + patt = "|".join(re.escape(sym) for sym in symbols) + + # wrap with \b word break markers if defining as keywords + if asKeyword: + patt = rf"\b(?:{patt})\b" + + ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols)) + + if caseless: + # add parse action to return symbols as specified, not in random + # casing as found in input string + symbol_map = {sym.lower(): sym for sym in symbols} + ret.add_parse_action(lambda s, l, t: symbol_map[t[0].lower()]) + + return ret + + except re.error: + warnings.warn( + "Exception creating Regex for one_of, building MatchFirst", stacklevel=2 + ) + + # last resort, just use MatchFirst + return MatchFirst(parseElementClass(sym) for sym in symbols).set_name( + " | ".join(symbols) + ) + + +def dict_of(key: ParserElement, value: ParserElement) -> ParserElement: + """Helper to easily and clearly define a dictionary by specifying + the respective patterns for the key and value. Takes care of + defining the :class:`Dict`, :class:`ZeroOrMore`, and + :class:`Group` tokens in the proper order. The key pattern + can include delimiting markers or punctuation, as long as they are + suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the :class:`Dict` results + can include named token fields. + + Example:: + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + print(attr_expr[1, ...].parse_string(text).dump()) + + attr_label = label + attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + + # similar to Dict, but simpler call format + result = dict_of(attr_label, attr_value).parse_string(text) + print(result.dump()) + print(result['shape']) + print(result.shape) # object attribute access works too + print(result.as_dict()) + + prints:: + + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: 'light blue' + - posn: 'upper left' + - shape: 'SQUARE' + - texture: 'burlap' + SQUARE + SQUARE + {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} + """ + return Dict(OneOrMore(Group(key + value))) + + +def original_text_for( + expr: ParserElement, as_string: bool = True, *, asString: bool = True +) -> ParserElement: + """Helper to return the original, untokenized text for a given + expression. Useful to restore the parsed fields of an HTML start + tag into the raw tag text itself, or to revert separate tokens with + intervening whitespace back to the original matching input text. By + default, returns a string containing the original parsed text. + + If the optional ``as_string`` argument is passed as + ``False``, then the return value is + a :class:`ParseResults` containing any results names that + were originally matched, and a single token containing the original + matched text from the input string. So if the expression passed to + :class:`original_text_for` contains expressions with defined + results names, you must set ``as_string`` to ``False`` if you + want to preserve those results name values. + + The ``asString`` pre-PEP8 argument is retained for compatibility, + but will be removed in a future release. + + Example:: + + src = "this is test bold text normal text " + for tag in ("b", "i"): + opener, closer = make_html_tags(tag) + patt = original_text_for(opener + ... + closer) + print(patt.search_string(src)[0]) + + prints:: + + [' bold text '] + ['text'] + """ + asString = asString and as_string + + locMarker = Empty().set_parse_action(lambda s, loc, t: loc) + endlocMarker = locMarker.copy() + endlocMarker.callPreparse = False + matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") + if asString: + extractText = lambda s, l, t: s[t._original_start : t._original_end] + else: + + def extractText(s, l, t): + t[:] = [s[t.pop("_original_start") : t.pop("_original_end")]] + + matchExpr.set_parse_action(extractText) + matchExpr.ignoreExprs = expr.ignoreExprs + matchExpr.suppress_warning(Diagnostics.warn_ungrouped_named_tokens_in_collection) + return matchExpr + + +def ungroup(expr: ParserElement) -> ParserElement: + """Helper to undo pyparsing's default grouping of And expressions, + even if all but one are non-empty. + """ + return TokenConverter(expr).add_parse_action(lambda t: t[0]) + + +def locatedExpr(expr: ParserElement) -> ParserElement: + """ + (DEPRECATED - future code should use the :class:`Located` class) + Helper to decorate a returned token with its starting and ending + locations in the input string. + + This helper adds the following results names: + + - ``locn_start`` - location where matched expression begins + - ``locn_end`` - location where matched expression ends + - ``value`` - the actual parsed results + + Be careful if the input text contains ```` characters, you + may want to call :class:`ParserElement.parse_with_tabs` + + Example:: + + wd = Word(alphas) + for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] + """ + locator = Empty().set_parse_action(lambda ss, ll, tt: ll) + return Group( + locator("locn_start") + + expr("value") + + locator.copy().leaveWhitespace()("locn_end") + ) + + +def nested_expr( + opener: Union[str, ParserElement] = "(", + closer: Union[str, ParserElement] = ")", + content: typing.Optional[ParserElement] = None, + ignore_expr: ParserElement = quoted_string(), + *, + ignoreExpr: ParserElement = quoted_string(), +) -> ParserElement: + """Helper method for defining nested lists enclosed in opening and + closing delimiters (``"("`` and ``")"`` are the default). + + Parameters: + + - ``opener`` - opening character for a nested list + (default= ``"("``); can also be a pyparsing expression + - ``closer`` - closing character for a nested list + (default= ``")"``); can also be a pyparsing expression + - ``content`` - expression for items within the nested lists + (default= ``None``) + - ``ignore_expr`` - expression for ignoring opening and closing delimiters + (default= :class:`quoted_string`) + - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility + but will be removed in a future release + + If an expression is not provided for the content argument, the + nested expression will capture all whitespace-delimited content + between delimiters as a list of separate values. + + Use the ``ignore_expr`` argument to define expressions that may + contain opening or closing characters that should not be treated as + opening or closing characters for nesting, such as quoted_string or + a comment expression. Specify multiple expressions using an + :class:`Or` or :class:`MatchFirst`. The default is + :class:`quoted_string`, but if no expressions are to be ignored, then + pass ``None`` for this argument. + + Example:: + + data_type = one_of("void int short long char float double") + decl_data_type = Combine(data_type + Opt(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR, RPAR = map(Suppress, "()") + + code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(c_style_comment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.search_string(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + + prints:: + + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if ignoreExpr != ignore_expr: + ignoreExpr = ignore_expr if ignoreExpr == quoted_string() else ignoreExpr + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener, str_type) and isinstance(closer, str_type): + opener = typing.cast(str, opener) + closer = typing.cast(str, closer) + if len(opener) == 1 and len(closer) == 1: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS, + exact=1, + ) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = empty.copy() + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS + ).set_parse_action(lambda t: t[0].strip()) + else: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = Combine( + OneOrMore( + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + raise ValueError( + "opening and closing arguments must be strings if no content expression is given" + ) + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( + Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer) + ) + else: + ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) + ret.set_name("nested %s%s expression" % (opener, closer)) + return ret + + +def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr, str_type): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dbl_quoted_string.copy().set_parse_action(remove_quotes) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + else: + tagAttrValue = quoted_string.copy().set_parse_action(remove_quotes) | Word( + printables, exclude_chars=">" + ) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict( + ZeroOrMore( + Group( + tagAttrName.set_parse_action(lambda t: t[0].lower()) + + Opt(Suppress("=") + tagAttrValue) + ) + ) + ) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + closeTag = Combine(Literal("", adjacent=False) + + openTag.set_name("<%s>" % resname) + # add start results name in parse action now that ungrouped names are not reported at two levels + openTag.add_parse_action( + lambda t: t.__setitem__( + "start" + "".join(resname.replace(":", " ").title().split()), t.copy() + ) + ) + closeTag = closeTag( + "end" + "".join(resname.replace(":", " ").title().split()) + ).set_name("" % resname) + openTag.tag = resname + closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) + return openTag, closeTag + + +def make_html_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for HTML, + given a tag name. Matches tags in either upper or lower case, + attributes with namespaces and with quoted or unquoted values. + + Example:: + + text = 'More info at the pyparsing wiki page' + # make_html_tags returns pyparsing expressions for the opening and + # closing tags as a 2-tuple + a, a_end = make_html_tags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end + + for link in link_expr.search_string(text): + # attributes in the tag (like "href" shown here) are + # also accessible as named results + print(link.link_text, '->', link.href) + + prints:: + + pyparsing -> https://github.com/pyparsing/pyparsing/wiki + """ + return _makeTags(tag_str, False) + + +def make_xml_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for XML, + given a tag name. Matches tags only in the given upper/lower case. + + Example: similar to :class:`make_html_tags` + """ + return _makeTags(tag_str, True) + + +any_open_tag: ParserElement +any_close_tag: ParserElement +any_open_tag, any_close_tag = make_html_tags( + Word(alphas, alphanums + "_:").set_name("any tag") +) + +_htmlEntityMap = {k.rstrip(";"): v for k, v in html.entities.html5.items()} +common_html_entity = Regex("&(?P" + "|".join(_htmlEntityMap) + ");").set_name( + "common HTML entity" +) + + +def replace_html_entity(s, l, t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + + +class OpAssoc(Enum): + """Enumeration of operator associativity + - used in constructing InfixNotationOperatorSpec for :class:`infix_notation`""" + + LEFT = 1 + RIGHT = 2 + + +InfixNotationOperatorArgType = Union[ + ParserElement, str, Tuple[Union[ParserElement, str], Union[ParserElement, str]] +] +InfixNotationOperatorSpec = Union[ + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + typing.Optional[ParseAction], + ], + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + ], +] + + +def infix_notation( + base_expr: ParserElement, + op_list: List[InfixNotationOperatorSpec], + lpar: Union[str, ParserElement] = Suppress("("), + rpar: Union[str, ParserElement] = Suppress(")"), +) -> ParserElement: + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary + or binary, left- or right-associative. Parse actions can also be + attached to operator expressions. The generated parser will also + recognize the use of parentheses to override operator precedences + (see example below). + + Note: if you define a deep operator list, you may see performance + issues when using infix_notation. See + :class:`ParserElement.enable_packrat` for a mechanism to potentially + improve your parser performance. + + Parameters: + + - ``base_expr`` - expression representing the most basic operand to + be used in the expression + - ``op_list`` - list of tuples, one for each operator precedence level + in the expression grammar; each tuple is of the form ``(op_expr, + num_operands, right_left_assoc, (optional)parse_action)``, where: + + - ``op_expr`` is the pyparsing expression for the operator; may also + be a string, which will be converted to a Literal; if ``num_operands`` + is 3, ``op_expr`` is a tuple of two expressions, for the two + operators separating the 3 terms + - ``num_operands`` is the number of terms for this operator (must be 1, + 2, or 3) + - ``right_left_assoc`` is the indicator whether the operator is right + or left associative, using the pyparsing-defined constants + ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. + - ``parse_action`` is the parse action to be associated with + expressions matching this operator expression (the parse action + tuple member may be omitted); if the parse action is passed + a tuple or list of functions, this is equivalent to calling + ``set_parse_action(*fn)`` + (:class:`ParserElement.set_parse_action`) + - ``lpar`` - expression for matching left-parentheses; if passed as a + str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as + an expression (such as ``Literal('(')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress('(')``) + - ``rpar`` - expression for matching right-parentheses; if passed as a + str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as + an expression (such as ``Literal(')')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress(')')``) + + Example:: + + # simple example of four-function arithmetic with ints and + # variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infix_notation(integer | varname, + [ + ('-', 1, OpAssoc.RIGHT), + (one_of('* /'), 2, OpAssoc.LEFT), + (one_of('+ -'), 2, OpAssoc.LEFT), + ]) + + arith_expr.run_tests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', full_dump=False) + + prints:: + + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + (5+x)*y + [[[5, '+', 'x'], '*', 'y']] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + + # captive version of FollowedBy that does not do parse actions or capture results names + class _FB(FollowedBy): + def parseImpl(self, instring, loc, doActions=True): + self.expr.try_parse(instring, loc) + return loc, [] + + _FB.__name__ = "FollowedBy>" + + ret = Forward() + if isinstance(lpar, str): + lpar = Suppress(lpar) + if isinstance(rpar, str): + rpar = Suppress(rpar) + + # if lpar and rpar are not suppressed, wrap in group + if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)): + lastExpr = base_expr | Group(lpar + ret + rpar) + else: + lastExpr = base_expr | (lpar + ret + rpar) + + arity: int + rightLeftAssoc: opAssoc + pa: typing.Optional[ParseAction] + opExpr1: ParserElement + opExpr2: ParserElement + for i, operDef in enumerate(op_list): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] # type: ignore[assignment] + if isinstance(opExpr, str_type): + opExpr = ParserElement._literalStringClass(opExpr) + opExpr = typing.cast(ParserElement, opExpr) + if arity == 3: + if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2: + raise ValueError( + "if numterms=3, opExpr must be a tuple or list of two expressions" + ) + opExpr1, opExpr2 = opExpr + term_name = f"{opExpr1}{opExpr2} term" + else: + term_name = f"{opExpr} term" + + if not 1 <= arity <= 3: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + + if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): + raise ValueError("operator must indicate right or left associativity") + + thisExpr: ParserElement = Forward().set_name(term_name) + thisExpr = typing.cast(Forward, thisExpr) + if rightLeftAssoc is OpAssoc.LEFT: + if arity == 1: + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( + lastExpr + (opExpr + lastExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr[2, ...]) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr + ) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr)) + elif rightLeftAssoc is OpAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Opt): + opExpr = Opt(opExpr) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( + lastExpr + (opExpr + thisExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + thisExpr) + Group( + lastExpr + thisExpr[1, ...] + ) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr + ) + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.set_parse_action(*pa) + else: + matchExpr.set_parse_action(pa) + thisExpr <<= (matchExpr | lastExpr).setName(term_name) + lastExpr = thisExpr + ret <<= lastExpr + return ret + + +def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): + """ + (DEPRECATED - use :class:`IndentedBlock` class instead) + Helper method for defining space-delimited indentation blocks, + such as those used to define block statements in Python source code. + + Parameters: + + - ``blockStatementExpr`` - expression defining syntax of statement that + is repeated within the indented block + - ``indentStack`` - list created by caller to manage indentation stack + (multiple ``statementWithIndentedBlock`` expressions within a single + grammar should share a common ``indentStack``) + - ``indent`` - boolean indicating whether block must be indented beyond + the current level; set to ``False`` for block of left-most statements + (default= ``True``) + + A valid block must contain at least one ``blockStatement``. + + (Note that indentedBlock uses internal parse actions which make it + incompatible with packrat parsing.) + + Example:: + + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) + + module_body = stmt[1, ...] + + parseTree = module_body.parseString(data) + parseTree.pprint() + + prints:: + + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + backup_stacks.append(indentStack[:]) + + def reset_stack(): + indentStack[:] = backup_stacks[-1] + + def checkPeerIndent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseException(s, l, "illegal nesting") + raise ParseException(s, l, "not a peer entry") + + def checkSubIndent(s, l, t): + curCol = col(l, s) + if curCol > indentStack[-1]: + indentStack.append(curCol) + else: + raise ParseException(s, l, "not a subentry") + + def checkUnindent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if not (indentStack and curCol in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() + + NL = OneOrMore(LineEnd().set_whitespace_chars("\t ").suppress()) + INDENT = (Empty() + Empty().set_parse_action(checkSubIndent)).set_name("INDENT") + PEER = Empty().set_parse_action(checkPeerIndent).set_name("") + UNDENT = Empty().set_parse_action(checkUnindent).set_name("UNINDENT") + if indent: + smExpr = Group( + Opt(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + UNDENT + ) + else: + smExpr = Group( + Opt(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + Opt(UNDENT) + ) + + # add a parse action to remove backup_stack from list of backups + smExpr.add_parse_action( + lambda: backup_stacks.pop(-1) and None if backup_stacks else None + ) + smExpr.set_fail_action(lambda a, b, c, d: reset_stack()) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.set_name("indented block") + + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +c_style_comment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").set_name( + "C style comment" +) +"Comment of the form ``/* ... */``" + +html_comment = Regex(r"").set_name("HTML comment") +"Comment of the form ````" + +rest_of_line = Regex(r".*").leave_whitespace().set_name("rest of line") +dbl_slash_comment = Regex(r"//(?:\\\n|[^\n])*").set_name("// comment") +"Comment of the form ``// ... (to end of line)``" + +cpp_style_comment = Combine( + Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/" | dbl_slash_comment +).set_name("C++ style comment") +"Comment of either form :class:`c_style_comment` or :class:`dbl_slash_comment`" + +java_style_comment = cpp_style_comment +"Same as :class:`cpp_style_comment`" + +python_style_comment = Regex(r"#.*").set_name("Python style comment") +"Comment of the form ``# ... (to end of line)``" + + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + + +# compatibility function, superseded by DelimitedList class +def delimited_list( + expr: Union[str, ParserElement], + delim: Union[str, ParserElement] = ",", + combine: bool = False, + min: typing.Optional[int] = None, + max: typing.Optional[int] = None, + *, + allow_trailing_delim: bool = False, +) -> ParserElement: + """(DEPRECATED - use :class:`DelimitedList` class)""" + return DelimitedList( + expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim + ) + + +# pre-PEP8 compatible names +# fmt: off +opAssoc = OpAssoc +anyOpenTag = any_open_tag +anyCloseTag = any_close_tag +commonHTMLEntity = common_html_entity +cStyleComment = c_style_comment +htmlComment = html_comment +restOfLine = rest_of_line +dblSlashComment = dbl_slash_comment +cppStyleComment = cpp_style_comment +javaStyleComment = java_style_comment +pythonStyleComment = python_style_comment + +@replaced_by_pep8(DelimitedList) +def delimitedList(): ... + +@replaced_by_pep8(DelimitedList) +def delimited_list(): ... + +@replaced_by_pep8(counted_array) +def countedArray(): ... + +@replaced_by_pep8(match_previous_literal) +def matchPreviousLiteral(): ... + +@replaced_by_pep8(match_previous_expr) +def matchPreviousExpr(): ... + +@replaced_by_pep8(one_of) +def oneOf(): ... + +@replaced_by_pep8(dict_of) +def dictOf(): ... + +@replaced_by_pep8(original_text_for) +def originalTextFor(): ... + +@replaced_by_pep8(nested_expr) +def nestedExpr(): ... + +@replaced_by_pep8(make_html_tags) +def makeHTMLTags(): ... + +@replaced_by_pep8(make_xml_tags) +def makeXMLTags(): ... + +@replaced_by_pep8(replace_html_entity) +def replaceHTMLEntity(): ... + +@replaced_by_pep8(infix_notation) +def infixNotation(): ... +# fmt: on diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py new file mode 100644 index 0000000..0313049 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py @@ -0,0 +1,796 @@ +# results.py +from collections.abc import ( + MutableMapping, + Mapping, + MutableSequence, + Iterator, + Sequence, + Container, +) +import pprint +from typing import Tuple, Any, Dict, Set, List + +str_type: Tuple[type, ...] = (str, bytes) +_generator_type = type((_ for _ in ())) + + +class _ParseResultsWithOffset: + tup: Tuple["ParseResults", int] + __slots__ = ["tup"] + + def __init__(self, p1: "ParseResults", p2: int): + self.tup: Tuple[ParseResults, int] = (p1, p2) + + def __getitem__(self, i): + return self.tup[i] + + def __getstate__(self): + return self.tup + + def __setstate__(self, *args): + self.tup = args[0] + + +class ParseResults: + """Structured parse results, to provide multiple means of access to + the parsed data: + + - as a list (``len(results)``) + - by list index (``results[0], results[1]``, etc.) + - by attribute (``results.`` - see :class:`ParserElement.set_results_name`) + + Example:: + + integer = Word(nums) + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + # equivalent form: + # date_str = (integer("year") + '/' + # + integer("month") + '/' + # + integer("day")) + + # parse_string returns a ParseResults object + result = date_str.parse_string("1999/12/31") + + def test(s, fn=repr): + print(f"{s} -> {fn(eval(s))}") + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) + + prints:: + + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + + _null_values: Tuple[Any, ...] = (None, [], ()) + + _name: str + _parent: "ParseResults" + _all_names: Set[str] + _modal: bool + _toklist: List[Any] + _tokdict: Dict[str, Any] + + __slots__ = ( + "_name", + "_parent", + "_all_names", + "_modal", + "_toklist", + "_tokdict", + ) + + class List(list): + """ + Simple wrapper class to distinguish parsed list results that should be preserved + as actual Python lists, instead of being converted to :class:`ParseResults`:: + + LBRACK, RBRACK = map(pp.Suppress, "[]") + element = pp.Forward() + item = ppc.integer + element_list = LBRACK + pp.DelimitedList(element) + RBRACK + + # add parse actions to convert from ParseResults to actual Python collection types + def as_python_list(t): + return pp.ParseResults.List(t.as_list()) + element_list.add_parse_action(as_python_list) + + element <<= item | element_list + + element.run_tests(''' + 100 + [2,3,4] + [[2, 1],3,4] + [(2, 1),3,4] + (2,3,4) + ''', post_parse=lambda s, r: (r[0], type(r[0]))) + + prints:: + + 100 + (100, ) + + [2,3,4] + ([2, 3, 4], ) + + [[2, 1],3,4] + ([[2, 1], 3, 4], ) + + (Used internally by :class:`Group` when `aslist=True`.) + """ + + def __new__(cls, contained=None): + if contained is None: + contained = [] + + if not isinstance(contained, list): + raise TypeError( + f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}" + ) + + return list.__new__(cls) + + def __new__(cls, toklist=None, name=None, **kwargs): + if isinstance(toklist, ParseResults): + return toklist + self = object.__new__(cls) + self._name = None + self._parent = None + self._all_names = set() + + if toklist is None: + self._toklist = [] + elif isinstance(toklist, (list, _generator_type)): + self._toklist = ( + [toklist[:]] + if isinstance(toklist, ParseResults.List) + else list(toklist) + ) + else: + self._toklist = [toklist] + self._tokdict = dict() + return self + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance + ): + self._tokdict: Dict[str, _ParseResultsWithOffset] + self._modal = modal + if name is not None and name != "": + if isinstance(name, int): + name = str(name) + if not modal: + self._all_names = {name} + self._name = name + if toklist not in self._null_values: + if isinstance(toklist, (str_type, type)): + toklist = [toklist] + if asList: + if isinstance(toklist, ParseResults): + self[name] = _ParseResultsWithOffset( + ParseResults(toklist._toklist), 0 + ) + else: + self[name] = _ParseResultsWithOffset( + ParseResults(toklist[0]), 0 + ) + self[name]._name = name + else: + try: + self[name] = toklist[0] + except (KeyError, TypeError, IndexError): + if toklist is not self: + self[name] = toklist + else: + self._name = name + + def __getitem__(self, i): + if isinstance(i, (int, slice)): + return self._toklist[i] + else: + if i not in self._all_names: + return self._tokdict[i][-1][0] + else: + return ParseResults([v[0] for v in self._tokdict[i]]) + + def __setitem__(self, k, v, isinstance=isinstance): + if isinstance(v, _ParseResultsWithOffset): + self._tokdict[k] = self._tokdict.get(k, list()) + [v] + sub = v[0] + elif isinstance(k, (int, slice)): + self._toklist[k] = v + sub = v + else: + self._tokdict[k] = self._tokdict.get(k, list()) + [ + _ParseResultsWithOffset(v, 0) + ] + sub = v + if isinstance(sub, ParseResults): + sub._parent = self + + def __delitem__(self, i): + if isinstance(i, (int, slice)): + mylen = len(self._toklist) + del self._toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i + 1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position - (position > j) + ) + else: + del self._tokdict[i] + + def __contains__(self, k) -> bool: + return k in self._tokdict + + def __len__(self) -> int: + return len(self._toklist) + + def __bool__(self) -> bool: + return not not (self._toklist or self._tokdict) + + def __iter__(self) -> Iterator: + return iter(self._toklist) + + def __reversed__(self) -> Iterator: + return iter(self._toklist[::-1]) + + def keys(self): + return iter(self._tokdict) + + def values(self): + return (self[k] for k in self.keys()) + + def items(self): + return ((k, self[k]) for k in self.keys()) + + def haskeys(self) -> bool: + """ + Since ``keys()`` returns an iterator, this method is helpful in bypassing + code that looks for the existence of any defined results names.""" + return not not self._tokdict + + def pop(self, *args, **kwargs): + """ + Removes and returns item at specified index (default= ``last``). + Supports both ``list`` and ``dict`` semantics for ``pop()``. If + passed no argument or an integer argument, it will use ``list`` + semantics and pop tokens from the list of parsed tokens. If passed + a non-integer argument (most likely a string), it will use ``dict`` + semantics and pop the corresponding value from any defined results + names. A second default return value argument is supported, just as in + ``dict.pop()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + def remove_first(tokens): + tokens.pop(0) + numlist.add_parse_action(remove_first) + print(numlist.parse_string("0 123 321")) # -> ['123', '321'] + + label = Word(alphas) + patt = label("LABEL") + Word(nums)[1, ...] + print(patt.parse_string("AAB 123 321").dump()) + + # Use pop() in a parse action to remove named result (note that corresponding value is not + # removed from list form of results) + def remove_LABEL(tokens): + tokens.pop("LABEL") + return tokens + patt.add_parse_action(remove_LABEL) + print(patt.parse_string("AAB 123 321").dump()) + + prints:: + + ['AAB', '123', '321'] + - LABEL: 'AAB' + + ['AAB', '123', '321'] + """ + if not args: + args = [-1] + for k, v in kwargs.items(): + if k == "default": + args = (args[0], v) + else: + raise TypeError(f"pop() got an unexpected keyword argument {k!r}") + if isinstance(args[0], int) or len(args) == 1 or args[0] in self: + index = args[0] + ret = self[index] + del self[index] + return ret + else: + defaultvalue = args[1] + return defaultvalue + + def get(self, key, default_value=None): + """ + Returns named result matching the given key, or if there is no + such name, then returns the given ``default_value`` or ``None`` if no + ``default_value`` is specified. + + Similar to ``dict.get()``. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string("1999/12/31") + print(result.get("year")) # -> '1999' + print(result.get("hour", "not specified")) # -> 'not specified' + print(result.get("hour")) # -> None + """ + if key in self: + return self[key] + else: + return default_value + + def insert(self, index, ins_string): + """ + Inserts new element at location index in the list of parsed tokens. + + Similar to ``list.insert()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to insert the parse location in the front of the parsed results + def insert_locn(locn, tokens): + tokens.insert(0, locn) + numlist.add_parse_action(insert_locn) + print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321'] + """ + self._toklist.insert(index, ins_string) + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position + (position > index) + ) + + def append(self, item): + """ + Add single element to end of ``ParseResults`` list of elements. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to compute the sum of the parsed integers, and add it to the end + def append_sum(tokens): + tokens.append(sum(map(int, tokens))) + numlist.add_parse_action(append_sum) + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321', 444] + """ + self._toklist.append(item) + + def extend(self, itemseq): + """ + Add sequence of elements to end of ``ParseResults`` list of elements. + + Example:: + + patt = Word(alphas)[1, ...] + + # use a parse action to append the reverse of the matched strings, to make a palindrome + def make_palindrome(tokens): + tokens.extend(reversed([t[::-1] for t in tokens])) + return ''.join(tokens) + patt.add_parse_action(make_palindrome) + print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + """ + if isinstance(itemseq, ParseResults): + self.__iadd__(itemseq) + else: + self._toklist.extend(itemseq) + + def clear(self): + """ + Clear all elements and results names. + """ + del self._toklist[:] + self._tokdict.clear() + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + if name.startswith("__"): + raise AttributeError(name) + return "" + + def __add__(self, other: "ParseResults") -> "ParseResults": + ret = self.copy() + ret += other + return ret + + def __iadd__(self, other: "ParseResults") -> "ParseResults": + if not other: + return self + + if other._tokdict: + offset = len(self._toklist) + addoffset = lambda a: offset if a < 0 else a + offset + otheritems = other._tokdict.items() + otherdictitems = [ + (k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) + for k, vlist in otheritems + for v in vlist + ] + for k, v in otherdictitems: + self[k] = v + if isinstance(v[0], ParseResults): + v[0]._parent = self + + self._toklist += other._toklist + self._all_names |= other._all_names + return self + + def __radd__(self, other) -> "ParseResults": + if isinstance(other, int) and other == 0: + # useful for merging many ParseResults using sum() builtin + return self.copy() + else: + # this may raise a TypeError - so be it + return other + self + + def __repr__(self) -> str: + return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})" + + def __str__(self) -> str: + return ( + "[" + + ", ".join( + [ + str(i) if isinstance(i, ParseResults) else repr(i) + for i in self._toklist + ] + ) + + "]" + ) + + def _asStringList(self, sep=""): + out = [] + for item in self._toklist: + if out and sep: + out.append(sep) + if isinstance(item, ParseResults): + out += item._asStringList() + else: + out.append(str(item)) + return out + + def as_list(self) -> list: + """ + Returns the parse results as a nested list of matching tokens, all converted to strings. + + Example:: + + patt = Word(alphas)[1, ...] + result = patt.parse_string("sldkj lsdkj sldkj") + # even though the result prints in string-like form, it is actually a pyparsing ParseResults + print(type(result), result) # -> ['sldkj', 'lsdkj', 'sldkj'] + + # Use as_list() to create an actual list + result_list = result.as_list() + print(type(result_list), result_list) # -> ['sldkj', 'lsdkj', 'sldkj'] + """ + return [ + res.as_list() if isinstance(res, ParseResults) else res + for res in self._toklist + ] + + def as_dict(self) -> dict: + """ + Returns the named parse results as a nested dictionary. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('12/31/1999') + print(type(result), repr(result)) # -> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + + result_dict = result.as_dict() + print(type(result_dict), repr(result_dict)) # -> {'day': '1999', 'year': '12', 'month': '31'} + + # even though a ParseResults supports dict-like access, sometime you just need to have a dict + import json + print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable + print(json.dumps(result.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"} + """ + + def to_item(obj): + if isinstance(obj, ParseResults): + return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj] + else: + return obj + + return dict((k, to_item(v)) for k, v in self.items()) + + def copy(self) -> "ParseResults": + """ + Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults` + items contained within the source are shared with the copy. Use + :class:`ParseResults.deepcopy()` to create a copy with its own separate + content values. + """ + ret = ParseResults(self._toklist) + ret._tokdict = self._tokdict.copy() + ret._parent = self._parent + ret._all_names |= self._all_names + ret._name = self._name + return ret + + def deepcopy(self) -> "ParseResults": + """ + Returns a new deep copy of a :class:`ParseResults` object. + """ + ret = self.copy() + # replace values with copies if they are of known mutable types + for i, obj in enumerate(self._toklist): + if isinstance(obj, ParseResults): + self._toklist[i] = obj.deepcopy() + elif isinstance(obj, (str, bytes)): + pass + elif isinstance(obj, MutableMapping): + self._toklist[i] = dest = type(obj)() + for k, v in obj.items(): + dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v + elif isinstance(obj, Container): + self._toklist[i] = type(obj)( + v.deepcopy() if isinstance(v, ParseResults) else v for v in obj + ) + return ret + + def get_name(self): + r""" + Returns the results name for this token expression. Useful when several + different expressions might match at a particular location. + + Example:: + + integer = Word(nums) + ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") + house_number_expr = Suppress('#') + Word(nums, alphanums) + user_data = (Group(house_number_expr)("house_number") + | Group(ssn_expr)("ssn") + | Group(integer)("age")) + user_info = user_data[1, ...] + + result = user_info.parse_string("22 111-22-3333 #221B") + for item in result: + print(item.get_name(), ':', item[0]) + + prints:: + + age : 22 + ssn : 111-22-3333 + house_number : 221B + """ + if self._name: + return self._name + elif self._parent: + par: "ParseResults" = self._parent + parent_tokdict_items = par._tokdict.items() + return next( + ( + k + for k, vlist in parent_tokdict_items + for v, loc in vlist + if v is self + ), + None, + ) + elif ( + len(self) == 1 + and len(self._tokdict) == 1 + and next(iter(self._tokdict.values()))[0][1] in (0, -1) + ): + return next(iter(self._tokdict.keys())) + else: + return None + + def dump(self, indent="", full=True, include_list=True, _depth=0) -> str: + """ + Diagnostic method for listing out the contents of + a :class:`ParseResults`. Accepts an optional ``indent`` argument so + that this string can be embedded in a nested display of other data. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('1999/12/31') + print(result.dump()) + + prints:: + + ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + out = [] + NL = "\n" + out.append(indent + str(self.as_list()) if include_list else "") + + if full: + if self.haskeys(): + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: + if out: + out.append(NL) + out.append(f"{indent}{(' ' * _depth)}- {k}: ") + if isinstance(v, ParseResults): + if v: + out.append( + v.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ) + ) + else: + out.append(str(v)) + else: + out.append(repr(v)) + if any(isinstance(vv, ParseResults) for vv in self): + v = self + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append( + "\n{}{}[{}]:\n{}{}{}".format( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + vv.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ), + ) + ) + else: + out.append( + "\n%s%s[%d]:\n%s%s%s" + % ( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + str(vv), + ) + ) + + return "".join(out) + + def pprint(self, *args, **kwargs): + """ + Pretty-printer for parsed results as a list, using the + `pprint `_ module. + Accepts additional positional or keyword args as defined for + `pprint.pprint `_ . + + Example:: + + ident = Word(alphas, alphanums) + num = Word(nums) + func = Forward() + term = ident | num | Group('(' + func + ')') + func <<= ident + Group(Optional(DelimitedList(term))) + result = func.parse_string("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) + + prints:: + + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] + """ + pprint.pprint(self.as_list(), *args, **kwargs) + + # add support for pickle protocol + def __getstate__(self): + return ( + self._toklist, + ( + self._tokdict.copy(), + None, + self._all_names, + self._name, + ), + ) + + def __setstate__(self, state): + self._toklist, (self._tokdict, par, inAccumNames, self._name) = state + self._all_names = set(inAccumNames) + self._parent = None + + def __getnewargs__(self): + return self._toklist, self._name + + def __dir__(self): + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None) -> "ParseResults": + """ + Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the + name-value relations as results names. If an optional ``name`` argument is + given, a nested ``ParseResults`` will be returned. + """ + + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + # str's are iterable, but in pyparsing, we don't want to iterate over them + else: + return not isinstance(obj, str_type) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret + + asList = as_list + """Deprecated - use :class:`as_list`""" + asDict = as_dict + """Deprecated - use :class:`as_dict`""" + getName = get_name + """Deprecated - use :class:`get_name`""" + + +MutableMapping.register(ParseResults) +MutableSequence.register(ParseResults) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py new file mode 100644 index 0000000..6a254c1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py @@ -0,0 +1,331 @@ +# testing.py + +from contextlib import contextmanager +import typing + +from .core import ( + ParserElement, + ParseException, + Keyword, + __diag__, + __compat__, +) + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - bounded recursion parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example:: + + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + + self._save_context[ + "literal_string_class" + ] = ParserElement._literalStringClass + + self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace + + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + if ParserElement._packratEnabled: + self._save_context[ + "packrat_cache_size" + ] = ParserElement.packrat_cache.size + else: + self._save_context["packrat_cache_size"] = None + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context[ + "recursion_enabled" + ] = ParserElement._left_recursion_enabled + + self._save_context["__diag__"] = { + name: getattr(__diag__, name) for name in __diag__._all_names + } + + self._save_context["__compat__"] = { + "collect_all_And_tokens": __compat__.collect_all_And_tokens + } + + return self + + def restore(self): + # reset pyparsing global state + if ( + ParserElement.DEFAULT_WHITE_CHARS + != self._save_context["default_whitespace"] + ): + ParserElement.set_default_whitespace_chars( + self._save_context["default_whitespace"] + ) + + ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"] + + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing( + self._save_context["literal_string_class"] + ) + + for name, value in self._save_context["__diag__"].items(): + (__diag__.enable if value else __diag__.disable)(name) + + ParserElement._packratEnabled = False + if self._save_context["packrat_enabled"]: + ParserElement.enable_packrat(self._save_context["packrat_cache_size"]) + else: + ParserElement._parse = self._save_context["packrat_parse"] + ParserElement._left_recursion_enabled = self._save_context[ + "recursion_enabled" + ] + + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + return self + + def copy(self): + ret = type(self)() + ret._save_context.update(self._save_context) + return ret + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + + def assertParseResultsEquals( + self, result, expected_list=None, expected_dict=None, msg=None + ): + """ + Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``, + and compare any defined results names with an optional ``expected_dict``. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.as_list(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.as_dict(), msg=msg) + + def assertParseAndCheckList( + self, expr, test_string, expected_list, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asList()`` is equal to the ``expected_list``. + """ + result = expr.parse_string(test_string, parse_all=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict( + self, expr, test_string, expected_dict, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``. + """ + result = expr.parse_string(test_string, parseAll=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults( + self, run_tests_report, expected_parse_results=None, msg=None + ): + """ + Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of + list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped + with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``. + Finally, asserts that the overall ``runTests()`` success value is ``True``. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [ + (*rpt, expected) + for rpt, expected in zip(run_test_results, expected_parse_results) + ] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next( + (exp for exp in expected if isinstance(exp, str)), None + ) + expected_exception = next( + ( + exp + for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception) + ), + None, + ) + if expected_exception is not None: + with self.assertRaises( + expected_exception=expected_exception, msg=fail_msg or msg + ): + if isinstance(result, Exception): + raise result + else: + expected_list = next( + (exp for exp in expected if isinstance(exp, list)), None + ) + expected_dict = next( + (exp for exp in expected if isinstance(exp, dict)), None + ) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, + expected_list=expected_list, + expected_dict=expected_dict, + msg=fail_msg or msg, + ) + else: + # warning here maybe? + print(f"no validation for {test_string!r}") + + # do this last, in case some specific test results can be reported instead + self.assertTrue( + run_test_success, msg=msg if msg is not None else "failed runTests" + ) + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield + + @staticmethod + def with_line_numbers( + s: str, + start_line: typing.Optional[int] = None, + end_line: typing.Optional[int] = None, + expand_tabs: bool = True, + eol_mark: str = "|", + mark_spaces: typing.Optional[str] = None, + mark_control: typing.Optional[str] = None, + ) -> str: + """ + Helpful method for debugging a parser - prints a string with line and column numbers. + (Line and column numbers are 1-based.) + + :param s: tuple(bool, str - string to be printed with line and column numbers + :param start_line: int - (optional) starting line number in s to print (default=1) + :param end_line: int - (optional) ending line number in s to print (default=len(s)) + :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default + :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|") + :param mark_spaces: str - (optional) special character to display in place of spaces + :param mark_control: str - (optional) convert non-printing control characters to a placeholding + character; valid values: + - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊" + - any single character string - replace control characters with given string + - None (default) - string is displayed as-is + + :return: str - input string with leading line numbers and column number headers + """ + if expand_tabs: + s = s.expandtabs() + if mark_control is not None: + mark_control = typing.cast(str, mark_control) + if mark_control == "unicode": + transtable_map = { + c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433)) + } + transtable_map[127] = 0x2421 + tbl = str.maketrans(transtable_map) + eol_mark = "" + else: + ord_mark_control = ord(mark_control) + tbl = str.maketrans( + {c: ord_mark_control for c in list(range(0, 32)) + [127]} + ) + s = s.translate(tbl) + if mark_spaces is not None and mark_spaces != " ": + if mark_spaces == "unicode": + tbl = str.maketrans({9: 0x2409, 32: 0x2423}) + s = s.translate(tbl) + else: + s = s.replace(" ", mark_spaces) + if start_line is None: + start_line = 1 + if end_line is None: + end_line = len(s) + end_line = min(end_line, len(s)) + start_line = min(max(1, start_line), end_line) + + if mark_control != "unicode": + s_lines = s.splitlines()[start_line - 1 : end_line] + else: + s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]] + if not s_lines: + return "" + + lineno_width = len(str(end_line)) + max_line_len = max(len(line) for line in s_lines) + lead = " " * (lineno_width + 1) + if max_line_len >= 99: + header0 = ( + lead + + "".join( + f"{' ' * 99}{(i + 1) % 100}" + for i in range(max(max_line_len // 100, 1)) + ) + + "\n" + ) + else: + header0 = "" + header1 = ( + header0 + + lead + + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10))) + + "\n" + ) + header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n" + return ( + header1 + + header2 + + "\n".join( + f"{i:{lineno_width}d}:{line}{eol_mark}" + for i, line in enumerate(s_lines, start=start_line) + ) + + "\n" + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py new file mode 100644 index 0000000..ec0b3a4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py @@ -0,0 +1,361 @@ +# unicode.py + +import sys +from itertools import filterfalse +from typing import List, Tuple, Union + + +class _lazyclassproperty: + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + self.__name__ = fn.__name__ + + def __get__(self, obj, cls): + if cls is None: + cls = type(obj) + if not hasattr(cls, "_intern") or any( + cls._intern is getattr(superclass, "_intern", []) + for superclass in cls.__mro__[1:] + ): + cls._intern = {} + attrname = self.fn.__name__ + if attrname not in cls._intern: + cls._intern[attrname] = self.fn(cls) + return cls._intern[attrname] + + +UnicodeRangeList = List[Union[Tuple[int, int], Tuple[int]]] + + +class unicode_set: + """ + A set of Unicode characters, for language-specific strings for + ``alphas``, ``nums``, ``alphanums``, and ``printables``. + A unicode_set is defined by a list of ranges in the Unicode character + set, in a class attribute ``_ranges``. Ranges can be specified using + 2-tuples or a 1-tuple, such as:: + + _ranges = [ + (0x0020, 0x007e), + (0x00a0, 0x00ff), + (0x0100,), + ] + + Ranges are left- and right-inclusive. A 1-tuple of (x,) is treated as (x, x). + + A unicode set can also be defined using multiple inheritance of other unicode sets:: + + class CJK(Chinese, Japanese, Korean): + pass + """ + + _ranges: UnicodeRangeList = [] + + @_lazyclassproperty + def _chars_for_ranges(cls): + ret = [] + for cc in cls.__mro__: + if cc is unicode_set: + break + for rr in getattr(cc, "_ranges", ()): + ret.extend(range(rr[0], rr[-1] + 1)) + return [chr(c) for c in sorted(set(ret))] + + @_lazyclassproperty + def printables(cls): + """all non-whitespace characters in this range""" + return "".join(filterfalse(str.isspace, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphas(cls): + """all alphabetic characters in this range""" + return "".join(filter(str.isalpha, cls._chars_for_ranges)) + + @_lazyclassproperty + def nums(cls): + """all numeric digit characters in this range""" + return "".join(filter(str.isdigit, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphanums(cls): + """all alphanumeric characters in this range""" + return cls.alphas + cls.nums + + @_lazyclassproperty + def identchars(cls): + """all characters in this range that are valid identifier characters, plus underscore '_'""" + return "".join( + sorted( + set( + "".join(filter(str.isidentifier, cls._chars_for_ranges)) + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªµº" + + "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ" + + "_" + ) + ) + ) + + @_lazyclassproperty + def identbodychars(cls): + """ + all characters in this range that are valid identifier body characters, + plus the digits 0-9, and · (Unicode MIDDLE DOT) + """ + return "".join( + sorted( + set( + cls.identchars + + "0123456789·" + + "".join( + [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()] + ) + ) + ) + ) + + @_lazyclassproperty + def identifier(cls): + """ + a pyparsing Word expression for an identifier using this range's definitions for + identchars and identbodychars + """ + from pip._vendor.pyparsing import Word + + return Word(cls.identchars, cls.identbodychars) + + +class pyparsing_unicode(unicode_set): + """ + A namespace class for defining common language unicode_sets. + """ + + # fmt: off + + # define ranges in language character sets + _ranges: UnicodeRangeList = [ + (0x0020, sys.maxunicode), + ] + + class BasicMultilingualPlane(unicode_set): + """Unicode set for the Basic Multilingual Plane""" + _ranges: UnicodeRangeList = [ + (0x0020, 0xFFFF), + ] + + class Latin1(unicode_set): + """Unicode set for Latin-1 Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0020, 0x007E), + (0x00A0, 0x00FF), + ] + + class LatinA(unicode_set): + """Unicode set for Latin-A Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0100, 0x017F), + ] + + class LatinB(unicode_set): + """Unicode set for Latin-B Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0180, 0x024F), + ] + + class Greek(unicode_set): + """Unicode set for Greek Unicode Character Ranges""" + _ranges: UnicodeRangeList = [ + (0x0342, 0x0345), + (0x0370, 0x0377), + (0x037A, 0x037F), + (0x0384, 0x038A), + (0x038C,), + (0x038E, 0x03A1), + (0x03A3, 0x03E1), + (0x03F0, 0x03FF), + (0x1D26, 0x1D2A), + (0x1D5E,), + (0x1D60,), + (0x1D66, 0x1D6A), + (0x1F00, 0x1F15), + (0x1F18, 0x1F1D), + (0x1F20, 0x1F45), + (0x1F48, 0x1F4D), + (0x1F50, 0x1F57), + (0x1F59,), + (0x1F5B,), + (0x1F5D,), + (0x1F5F, 0x1F7D), + (0x1F80, 0x1FB4), + (0x1FB6, 0x1FC4), + (0x1FC6, 0x1FD3), + (0x1FD6, 0x1FDB), + (0x1FDD, 0x1FEF), + (0x1FF2, 0x1FF4), + (0x1FF6, 0x1FFE), + (0x2129,), + (0x2719, 0x271A), + (0xAB65,), + (0x10140, 0x1018D), + (0x101A0,), + (0x1D200, 0x1D245), + (0x1F7A1, 0x1F7A7), + ] + + class Cyrillic(unicode_set): + """Unicode set for Cyrillic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0400, 0x052F), + (0x1C80, 0x1C88), + (0x1D2B,), + (0x1D78,), + (0x2DE0, 0x2DFF), + (0xA640, 0xA672), + (0xA674, 0xA69F), + (0xFE2E, 0xFE2F), + ] + + class Chinese(unicode_set): + """Unicode set for Chinese Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x2E80, 0x2E99), + (0x2E9B, 0x2EF3), + (0x31C0, 0x31E3), + (0x3400, 0x4DB5), + (0x4E00, 0x9FEF), + (0xA700, 0xA707), + (0xF900, 0xFA6D), + (0xFA70, 0xFAD9), + (0x16FE2, 0x16FE3), + (0x1F210, 0x1F212), + (0x1F214, 0x1F23B), + (0x1F240, 0x1F248), + (0x20000, 0x2A6D6), + (0x2A700, 0x2B734), + (0x2B740, 0x2B81D), + (0x2B820, 0x2CEA1), + (0x2CEB0, 0x2EBE0), + (0x2F800, 0x2FA1D), + ] + + class Japanese(unicode_set): + """Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges""" + + class Kanji(unicode_set): + "Unicode set for Kanji Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x4E00, 0x9FBF), + (0x3000, 0x303F), + ] + + class Hiragana(unicode_set): + """Unicode set for Hiragana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3041, 0x3096), + (0x3099, 0x30A0), + (0x30FC,), + (0xFF70,), + (0x1B001,), + (0x1B150, 0x1B152), + (0x1F200,), + ] + + class Katakana(unicode_set): + """Unicode set for Katakana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3099, 0x309C), + (0x30A0, 0x30FF), + (0x31F0, 0x31FF), + (0x32D0, 0x32FE), + (0xFF65, 0xFF9F), + (0x1B000,), + (0x1B164, 0x1B167), + (0x1F201, 0x1F202), + (0x1F213,), + ] + + 漢字 = Kanji + カタカナ = Katakana + ひらがな = Hiragana + + _ranges = ( + Kanji._ranges + + Hiragana._ranges + + Katakana._ranges + ) + + class Hangul(unicode_set): + """Unicode set for Hangul (Korean) Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x1100, 0x11FF), + (0x302E, 0x302F), + (0x3131, 0x318E), + (0x3200, 0x321C), + (0x3260, 0x327B), + (0x327E,), + (0xA960, 0xA97C), + (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), + (0xD7CB, 0xD7FB), + (0xFFA0, 0xFFBE), + (0xFFC2, 0xFFC7), + (0xFFCA, 0xFFCF), + (0xFFD2, 0xFFD7), + (0xFFDA, 0xFFDC), + ] + + Korean = Hangul + + class CJK(Chinese, Japanese, Hangul): + """Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range""" + + class Thai(unicode_set): + """Unicode set for Thai Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0E01, 0x0E3A), + (0x0E3F, 0x0E5B) + ] + + class Arabic(unicode_set): + """Unicode set for Arabic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0600, 0x061B), + (0x061E, 0x06FF), + (0x0700, 0x077F), + ] + + class Hebrew(unicode_set): + """Unicode set for Hebrew Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0591, 0x05C7), + (0x05D0, 0x05EA), + (0x05EF, 0x05F4), + (0xFB1D, 0xFB36), + (0xFB38, 0xFB3C), + (0xFB3E,), + (0xFB40, 0xFB41), + (0xFB43, 0xFB44), + (0xFB46, 0xFB4F), + ] + + class Devanagari(unicode_set): + """Unicode set for Devanagari Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0900, 0x097F), + (0xA8E0, 0xA8FF) + ] + + BMP = BasicMultilingualPlane + + # add language identifiers using language Unicode + العربية = Arabic + 中文 = Chinese + кириллица = Cyrillic + Ελληνικά = Greek + עִברִית = Hebrew + 日本語 = Japanese + 한국어 = Korean + ไทย = Thai + देवनागरी = Devanagari + + # fmt: on diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py new file mode 100644 index 0000000..d8d3f41 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py @@ -0,0 +1,284 @@ +# util.py +import inspect +import warnings +import types +import collections +import itertools +from functools import lru_cache, wraps +from typing import Callable, List, Union, Iterable, TypeVar, cast + +_bslash = chr(92) +C = TypeVar("C", bound=Callable) + + +class __config_flags: + """Internal class for defining compatibility and debugging flags""" + + _all_names: List[str] = [] + _fixed_names: List[str] = [] + _type_desc = "configuration" + + @classmethod + def _set(cls, dname, value): + if dname in cls._fixed_names: + warnings.warn( + f"{cls.__name__}.{dname} {cls._type_desc} is {str(getattr(cls, dname)).upper()}" + f" and cannot be overridden", + stacklevel=3, + ) + return + if dname in cls._all_names: + setattr(cls, dname, value) + else: + raise ValueError(f"no such {cls._type_desc} {dname!r}") + + enable = classmethod(lambda cls, name: cls._set(name, True)) + disable = classmethod(lambda cls, name: cls._set(name, False)) + + +@lru_cache(maxsize=128) +def col(loc: int, strg: str) -> int: + """ + Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See + :class:`ParserElement.parse_string` for more + information on parsing strings containing ```` s, and suggested + methods to maintain a consistent view of the parsed string, the parse + location, and line and column positions within the parsed string. + """ + s = strg + return 1 if 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc) + + +@lru_cache(maxsize=128) +def lineno(loc: int, strg: str) -> int: + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note - the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See :class:`ParserElement.parse_string` + for more information on parsing strings containing ```` s, and + suggested methods to maintain a consistent view of the parsed string, the + parse location, and line and column positions within the parsed string. + """ + return strg.count("\n", 0, loc) + 1 + + +@lru_cache(maxsize=128) +def line(loc: int, strg: str) -> str: + """ + Returns the line of text containing loc within a string, counting newlines as line separators. + """ + last_cr = strg.rfind("\n", 0, loc) + next_cr = strg.find("\n", loc) + return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :] + + +class _UnboundedCache: + def __init__(self): + cache = {} + cache_get = cache.get + self.not_in_cache = not_in_cache = object() + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + + def clear(_): + cache.clear() + + self.size = None + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class _FifoCache: + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + cache = {} + keyring = [object()] * size + cache_get = cache.get + cache_pop = cache.pop + keyiter = itertools.cycle(range(size)) + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + i = next(keyiter) + cache_pop(keyring[i], None) + keyring[i] = key + + def clear(_): + cache.clear() + keyring[:] = [object()] * size + + self.size = size + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class LRUMemo: + """ + A memoizing mapping that retains `capacity` deleted items + + The memo tracks retained items by their access order; once `capacity` items + are retained, the least recently used item is discarded. + """ + + def __init__(self, capacity): + self._capacity = capacity + self._active = {} + self._memory = collections.OrderedDict() + + def __getitem__(self, key): + try: + return self._active[key] + except KeyError: + self._memory.move_to_end(key) + return self._memory[key] + + def __setitem__(self, key, value): + self._memory.pop(key, None) + self._active[key] = value + + def __delitem__(self, key): + try: + value = self._active.pop(key) + except KeyError: + pass + else: + while len(self._memory) >= self._capacity: + self._memory.popitem(last=False) + self._memory[key] = value + + def clear(self): + self._active.clear() + self._memory.clear() + + +class UnboundedMemo(dict): + """ + A memoizing mapping that retains all deleted items + """ + + def __delitem__(self, key): + pass + + +def _escape_regex_range_chars(s: str) -> str: + # escape these chars: ^-[] + for c in r"\^-[]": + s = s.replace(c, _bslash + c) + s = s.replace("\n", r"\n") + s = s.replace("\t", r"\t") + return str(s) + + +def _collapse_string_to_ranges( + s: Union[str, Iterable[str]], re_escape: bool = True +) -> str: + def is_consecutive(c): + c_int = ord(c) + is_consecutive.prev, prev = c_int, is_consecutive.prev + if c_int - prev > 1: + is_consecutive.value = next(is_consecutive.counter) + return is_consecutive.value + + is_consecutive.prev = 0 # type: ignore [attr-defined] + is_consecutive.counter = itertools.count() # type: ignore [attr-defined] + is_consecutive.value = -1 # type: ignore [attr-defined] + + def escape_re_range_char(c): + return "\\" + c if c in r"\^-][" else c + + def no_escape_re_range_char(c): + return c + + if not re_escape: + escape_re_range_char = no_escape_re_range_char + + ret = [] + s = "".join(sorted(set(s))) + if len(s) > 3: + for _, chars in itertools.groupby(s, key=is_consecutive): + first = last = next(chars) + last = collections.deque( + itertools.chain(iter([last]), chars), maxlen=1 + ).pop() + if first == last: + ret.append(escape_re_range_char(first)) + else: + sep = "" if ord(last) == ord(first) + 1 else "-" + ret.append( + f"{escape_re_range_char(first)}{sep}{escape_re_range_char(last)}" + ) + else: + ret = [escape_re_range_char(c) for c in s] + + return "".join(ret) + + +def _flatten(ll: list) -> list: + ret = [] + for i in ll: + if isinstance(i, list): + ret.extend(_flatten(i)) + else: + ret.append(i) + return ret + + +def _make_synonym_function(compat_name: str, fn: C) -> C: + # In a future version, uncomment the code in the internal _inner() functions + # to begin emitting DeprecationWarnings. + + # Unwrap staticmethod/classmethod + fn = getattr(fn, "__func__", fn) + + # (Presence of 'self' arg in signature is used by explain_exception() methods, so we take + # some extra steps to add it if present in decorated function.) + if "self" == list(inspect.signature(fn).parameters)[0]: + + @wraps(fn) + def _inner(self, *args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(self, *args, **kwargs) + + else: + + @wraps(fn) + def _inner(*args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(*args, **kwargs) + + _inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`""" + _inner.__name__ = compat_name + _inner.__annotations__ = fn.__annotations__ + if isinstance(fn, types.FunctionType): + _inner.__kwdefaults__ = fn.__kwdefaults__ + elif isinstance(fn, type) and hasattr(fn, "__init__"): + _inner.__kwdefaults__ = fn.__init__.__kwdefaults__ + else: + _inner.__kwdefaults__ = None + _inner.__qualname__ = fn.__qualname__ + return cast(C, _inner) + + +def replaced_by_pep8(fn: C) -> Callable[[Callable], C]: + """ + Decorator for pre-PEP8 compatibility synonyms, to link them to the new function. + """ + return lambda other: _make_synonym_function(other.__name__, fn) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py new file mode 100644 index 0000000..ddfcf7f --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py @@ -0,0 +1,23 @@ +"""Wrappers to call pyproject.toml-based build backend hooks. +""" + +from ._impl import ( + BackendInvalid, + BackendUnavailable, + BuildBackendHookCaller, + HookMissing, + UnsupportedOperation, + default_subprocess_runner, + quiet_subprocess_runner, +) + +__version__ = '1.0.0' +__all__ = [ + 'BackendUnavailable', + 'BackendInvalid', + 'HookMissing', + 'UnsupportedOperation', + 'default_subprocess_runner', + 'quiet_subprocess_runner', + 'BuildBackendHookCaller', +] diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py new file mode 100644 index 0000000..95e509c --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py @@ -0,0 +1,8 @@ +__all__ = ("tomllib",) + +import sys + +if sys.version_info >= (3, 11): + import tomllib +else: + from pip._vendor import tomli as tomllib diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py new file mode 100644 index 0000000..37b0e65 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py @@ -0,0 +1,330 @@ +import json +import os +import sys +import tempfile +from contextlib import contextmanager +from os.path import abspath +from os.path import join as pjoin +from subprocess import STDOUT, check_call, check_output + +from ._in_process import _in_proc_script_path + + +def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + +def read_json(path): + with open(path, encoding='utf-8') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Will be raised if the backend cannot be imported in the hook process.""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Will be raised if the backend is invalid.""" + def __init__(self, backend_name, backend_path, message): + super().__init__(message) + self.backend_name = backend_name + self.backend_path = backend_path + + +class HookMissing(Exception): + """Will be raised on missing hooks (if a fallback can't be used).""" + def __init__(self, hook_name): + super().__init__(hook_name) + self.hook_name = hook_name + + +class UnsupportedOperation(Exception): + """May be raised by build_sdist if the backend indicates that it can't.""" + def __init__(self, traceback): + self.traceback = traceback + + +def default_subprocess_runner(cmd, cwd=None, extra_environ=None): + """The default method of calling the wrapper subprocess. + + This uses :func:`subprocess.check_call` under the hood. + """ + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_call(cmd, cwd=cwd, env=env) + + +def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): + """Call the subprocess while suppressing output. + + This uses :func:`subprocess.check_output` under the hood. + """ + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + + +def norm_and_check(source_tree, requested): + """Normalise and check a backend path. + + Ensure that the requested backend path is specified as a relative path, + and resolves to a location under the given source tree. + + Return an absolute version of the requested path. + """ + if os.path.isabs(requested): + raise ValueError("paths must be relative") + + abs_source = os.path.abspath(source_tree) + abs_requested = os.path.normpath(os.path.join(abs_source, requested)) + # We have to use commonprefix for Python 2.7 compatibility. So we + # normalise case to avoid problems because commonprefix is a character + # based comparison :-( + norm_source = os.path.normcase(abs_source) + norm_requested = os.path.normcase(abs_requested) + if os.path.commonprefix([norm_source, norm_requested]) != norm_source: + raise ValueError("paths must be inside source tree") + + return abs_requested + + +class BuildBackendHookCaller: + """A wrapper to call the build backend hooks for a source directory. + """ + + def __init__( + self, + source_dir, + build_backend, + backend_path=None, + runner=None, + python_executable=None, + ): + """ + :param source_dir: The source directory to invoke the build backend for + :param build_backend: The build backend spec + :param backend_path: Additional path entries for the build backend spec + :param runner: The :ref:`subprocess runner ` to use + :param python_executable: + The Python executable used to invoke the build backend + """ + if runner is None: + runner = default_subprocess_runner + + self.source_dir = abspath(source_dir) + self.build_backend = build_backend + if backend_path: + backend_path = [ + norm_and_check(self.source_dir, p) for p in backend_path + ] + self.backend_path = backend_path + self._subprocess_runner = runner + if not python_executable: + python_executable = sys.executable + self.python_executable = python_executable + + @contextmanager + def subprocess_runner(self, runner): + """A context manager for temporarily overriding the default + :ref:`subprocess runner `. + + .. code-block:: python + + hook_caller = BuildBackendHookCaller(...) + with hook_caller.subprocess_runner(quiet_subprocess_runner): + ... + """ + prev = self._subprocess_runner + self._subprocess_runner = runner + try: + yield + finally: + self._subprocess_runner = prev + + def _supported_features(self): + """Return the list of optional features supported by the backend.""" + return self._call_hook('_supported_features', {}) + + def get_requires_for_build_wheel(self, config_settings=None): + """Get additional dependencies required for building a wheel. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + + .. admonition:: Fallback + + If the build backend does not defined a hook with this name, an + empty list will be returned. + """ + return self._call_hook('get_requires_for_build_wheel', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_wheel( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + :returns: Name of the newly created subfolder within + ``metadata_directory``, containing the metadata. + :rtype: str + + .. admonition:: Fallback + + If the build backend does not define a hook with this name and + ``_allow_fallback`` is truthy, the backend will be asked to build a + wheel via the ``build_wheel`` hook and the dist-info extracted from + that will be returned. + """ + return self._call_hook('prepare_metadata_for_build_wheel', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_wheel( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build a wheel from this project. + + :returns: + The name of the newly created wheel within ``wheel_directory``. + + .. admonition:: Interaction with fallback + + If the ``build_wheel`` hook was called in the fallback for + :meth:`prepare_metadata_for_build_wheel`, the build backend would + not be invoked. Instead, the previously built wheel will be copied + to ``wheel_directory`` and the name of that file will be returned. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_wheel', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + + def get_requires_for_build_editable(self, config_settings=None): + """Get additional dependencies required for building an editable wheel. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + + .. admonition:: Fallback + + If the build backend does not defined a hook with this name, an + empty list will be returned. + """ + return self._call_hook('get_requires_for_build_editable', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_editable( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + :returns: Name of the newly created subfolder within + ``metadata_directory``, containing the metadata. + :rtype: str + + .. admonition:: Fallback + + If the build backend does not define a hook with this name and + ``_allow_fallback`` is truthy, the backend will be asked to build a + wheel via the ``build_editable`` hook and the dist-info + extracted from that will be returned. + """ + return self._call_hook('prepare_metadata_for_build_editable', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_editable( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build an editable wheel from this project. + + :returns: + The name of the newly created wheel within ``wheel_directory``. + + .. admonition:: Interaction with fallback + + If the ``build_editable`` hook was called in the fallback for + :meth:`prepare_metadata_for_build_editable`, the build backend + would not be invoked. Instead, the previously built wheel will be + copied to ``wheel_directory`` and the name of that file will be + returned. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_editable', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + + def get_requires_for_build_sdist(self, config_settings=None): + """Get additional dependencies required for building an sdist. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + """ + return self._call_hook('get_requires_for_build_sdist', { + 'config_settings': config_settings + }) + + def build_sdist(self, sdist_directory, config_settings=None): + """Build an sdist from this project. + + :returns: + The name of the newly created sdist within ``wheel_directory``. + """ + return self._call_hook('build_sdist', { + 'sdist_directory': abspath(sdist_directory), + 'config_settings': config_settings, + }) + + def _call_hook(self, hook_name, kwargs): + extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend} + + if self.backend_path: + backend_path = os.pathsep.join(self.backend_path) + extra_environ['PEP517_BACKEND_PATH'] = backend_path + + with tempfile.TemporaryDirectory() as td: + hook_input = {'kwargs': kwargs} + write_json(hook_input, pjoin(td, 'input.json'), indent=2) + + # Run the hook in a subprocess + with _in_proc_script_path() as script: + python = self.python_executable + self._subprocess_runner( + [python, abspath(str(script)), hook_name, td], + cwd=self.source_dir, + extra_environ=extra_environ + ) + + data = read_json(pjoin(td, 'output.json')) + if data.get('unsupported'): + raise UnsupportedOperation(data.get('traceback', '')) + if data.get('no_backend'): + raise BackendUnavailable(data.get('traceback', '')) + if data.get('backend_invalid'): + raise BackendInvalid( + backend_name=self.build_backend, + backend_path=self.backend_path, + message=data.get('backend_error', '') + ) + if data.get('hook_missing'): + raise HookMissing(data.get('missing_hook_name') or hook_name) + return data['return_val'] diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py new file mode 100644 index 0000000..917fa06 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py @@ -0,0 +1,18 @@ +"""This is a subpackage because the directory is on sys.path for _in_process.py + +The subpackage should stay as empty as possible to avoid shadowing modules that +the backend might import. +""" + +import importlib.resources as resources + +try: + resources.files +except AttributeError: + # Python 3.8 compatibility + def _in_proc_script_path(): + return resources.path(__package__, '_in_process.py') +else: + def _in_proc_script_path(): + return resources.as_file( + resources.files(__package__).joinpath('_in_process.py')) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py new file mode 100644 index 0000000..ee511ff --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py @@ -0,0 +1,353 @@ +"""This is invoked in a subprocess to call the build backend hooks. + +It expects: +- Command line args: hook_name, control_dir +- Environment variables: + PEP517_BUILD_BACKEND=entry.point:spec + PEP517_BACKEND_PATH=paths (separated with os.pathsep) +- control_dir/input.json: + - {"kwargs": {...}} + +Results: +- control_dir/output.json + - {"return_val": ...} +""" +import json +import os +import os.path +import re +import shutil +import sys +import traceback +from glob import glob +from importlib import import_module +from os.path import join as pjoin + +# This file is run as a script, and `import wrappers` is not zip-safe, so we +# include write_json() and read_json() from wrappers.py. + + +def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + +def read_json(path): + with open(path, encoding='utf-8') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Raised if we cannot import the backend""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Raised if the backend is invalid""" + def __init__(self, message): + self.message = message + + +class HookMissing(Exception): + """Raised if a hook is missing and we are not executing the fallback""" + def __init__(self, hook_name=None): + super().__init__(hook_name) + self.hook_name = hook_name + + +def contained_in(filename, directory): + """Test if a file is located within the given directory.""" + filename = os.path.normcase(os.path.abspath(filename)) + directory = os.path.normcase(os.path.abspath(directory)) + return os.path.commonprefix([filename, directory]) == directory + + +def _build_backend(): + """Find and load the build backend""" + # Add in-tree backend directories to the front of sys.path. + backend_path = os.environ.get('PEP517_BACKEND_PATH') + if backend_path: + extra_pathitems = backend_path.split(os.pathsep) + sys.path[:0] = extra_pathitems + + ep = os.environ['PEP517_BUILD_BACKEND'] + mod_path, _, obj_path = ep.partition(':') + try: + obj = import_module(mod_path) + except ImportError: + raise BackendUnavailable(traceback.format_exc()) + + if backend_path: + if not any( + contained_in(obj.__file__, path) + for path in extra_pathitems + ): + raise BackendInvalid("Backend was not loaded from backend-path") + + if obj_path: + for path_part in obj_path.split('.'): + obj = getattr(obj, path_part) + return obj + + +def _supported_features(): + """Return the list of options features supported by the backend. + + Returns a list of strings. + The only possible value is 'build_editable'. + """ + backend = _build_backend() + features = [] + if hasattr(backend, "build_editable"): + features.append("build_editable") + return features + + +def get_requires_for_build_wheel(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_wheel + except AttributeError: + return [] + else: + return hook(config_settings) + + +def get_requires_for_build_editable(config_settings): + """Invoke the optional get_requires_for_build_editable hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_editable + except AttributeError: + return [] + else: + return hook(config_settings) + + +def prepare_metadata_for_build_wheel( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_wheel + + Implements a fallback by building a wheel if the hook isn't defined, + unless _allow_fallback is False in which case HookMissing is raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_wheel + except AttributeError: + if not _allow_fallback: + raise HookMissing() + else: + return hook(metadata_directory, config_settings) + # fallback to build_wheel outside the try block to avoid exception chaining + # which can be confusing to users and is not relevant + whl_basename = backend.build_wheel(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, + config_settings) + + +def prepare_metadata_for_build_editable( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_editable + + Implements a fallback by building an editable wheel if the hook isn't + defined, unless _allow_fallback is False in which case HookMissing is + raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_editable + except AttributeError: + if not _allow_fallback: + raise HookMissing() + try: + build_hook = backend.build_editable + except AttributeError: + raise HookMissing(hook_name='build_editable') + else: + whl_basename = build_hook(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, + metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' + + +def _dist_info_files(whl_zip): + """Identify the .dist-info folder inside a wheel ZipFile.""" + res = [] + for path in whl_zip.namelist(): + m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + if m: + res.append(path) + if res: + return res + raise Exception("No .dist-info folder found in wheel") + + +def _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings): + """Extract the metadata from a wheel. + + Fallback for when the build backend does not + define the 'get_wheel_metadata' hook. + """ + from zipfile import ZipFile + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + pass # Touch marker file + + whl_file = os.path.join(metadata_directory, whl_basename) + with ZipFile(whl_file) as zipf: + dist_info = _dist_info_files(zipf) + zipf.extractall(path=metadata_directory, members=dist_info) + return dist_info[0].split('/')[0] + + +def _find_already_built_wheel(metadata_directory): + """Check for a wheel already built during the get_wheel_metadata hook. + """ + if not metadata_directory: + return None + metadata_parent = os.path.dirname(metadata_directory) + if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): + return None + + whl_files = glob(os.path.join(metadata_parent, '*.whl')) + if not whl_files: + print('Found wheel built marker, but no .whl files') + return None + if len(whl_files) > 1: + print('Found multiple .whl files; unspecified behaviour. ' + 'Will call build_wheel.') + return None + + # Exactly one .whl file + return whl_files[0] + + +def build_wheel(wheel_directory, config_settings, metadata_directory=None): + """Invoke the mandatory build_wheel hook. + + If a wheel was already built in the + prepare_metadata_for_build_wheel fallback, this + will copy it rather than rebuilding the wheel. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return _build_backend().build_wheel(wheel_directory, config_settings, + metadata_directory) + + +def build_editable(wheel_directory, config_settings, metadata_directory=None): + """Invoke the optional build_editable hook. + + If a wheel was already built in the + prepare_metadata_for_build_editable fallback, this + will copy it rather than rebuilding the wheel. + """ + backend = _build_backend() + try: + hook = backend.build_editable + except AttributeError: + raise HookMissing() + else: + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return hook(wheel_directory, config_settings, metadata_directory) + + +def get_requires_for_build_sdist(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_sdist + except AttributeError: + return [] + else: + return hook(config_settings) + + +class _DummyException(Exception): + """Nothing should ever raise this exception""" + + +class GotUnsupportedOperation(Exception): + """For internal use when backend raises UnsupportedOperation""" + def __init__(self, traceback): + self.traceback = traceback + + +def build_sdist(sdist_directory, config_settings): + """Invoke the mandatory build_sdist hook.""" + backend = _build_backend() + try: + return backend.build_sdist(sdist_directory, config_settings) + except getattr(backend, 'UnsupportedOperation', _DummyException): + raise GotUnsupportedOperation(traceback.format_exc()) + + +HOOK_NAMES = { + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'get_requires_for_build_editable', + 'prepare_metadata_for_build_editable', + 'build_editable', + 'get_requires_for_build_sdist', + 'build_sdist', + '_supported_features', +} + + +def main(): + if len(sys.argv) < 3: + sys.exit("Needs args: hook_name, control_dir") + hook_name = sys.argv[1] + control_dir = sys.argv[2] + if hook_name not in HOOK_NAMES: + sys.exit("Unknown hook: %s" % hook_name) + hook = globals()[hook_name] + + hook_input = read_json(pjoin(control_dir, 'input.json')) + + json_out = {'unsupported': False, 'return_val': None} + try: + json_out['return_val'] = hook(**hook_input['kwargs']) + except BackendUnavailable as e: + json_out['no_backend'] = True + json_out['traceback'] = e.traceback + except BackendInvalid as e: + json_out['backend_invalid'] = True + json_out['backend_error'] = e.message + except GotUnsupportedOperation as e: + json_out['unsupported'] = True + json_out['traceback'] = e.traceback + except HookMissing as e: + json_out['hook_missing'] = True + json_out['missing_hook_name'] = e.hook_name or hook_name + + write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + + +if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py new file mode 100644 index 0000000..10ff67f --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py @@ -0,0 +1,182 @@ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import warnings + +from pip._vendor import urllib3 + +from .exceptions import RequestsDependencyWarning + +charset_normalizer_version = None + +try: + from pip._vendor.chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split(".") + assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append("0") + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1 + assert major >= 1 + if major == 1: + assert minor >= 21 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 6.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 4.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split("."))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = "Old version of cryptography ({}) may cause slowdown.".format( + cryptography_version + ) + warnings.warn(warning, RequestsDependencyWarning) + + +# Check imported dependencies for compatibility. +try: + check_compatibility( + urllib3.__version__, chardet_version, charset_normalizer_version + ) +except (AssertionError, ValueError): + warnings.warn( + "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format( + urllib3.__version__, chardet_version, charset_normalizer_version + ), + RequestsDependencyWarning, + ) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + # Note: This logic prevents upgrading cryptography on Windows, if imported + # as part of pip. + from pip._internal.utils.compat import WINDOWS + if not WINDOWS: + raise ImportError("pip internals: don't import cryptography on Windows") + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from pip._vendor.urllib3.contrib import pyopenssl + + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from pip._vendor.urllib3.exceptions import DependencyWarning + +warnings.simplefilter("ignore", DependencyWarning) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +from . import packages, utils +from .__version__ import ( + __author__, + __author_email__, + __build__, + __cake__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) +from .api import delete, get, head, options, patch, post, put, request +from .exceptions import ( + ConnectionError, + ConnectTimeout, + FileModeWarning, + HTTPError, + JSONDecodeError, + ReadTimeout, + RequestException, + Timeout, + TooManyRedirects, + URLRequired, +) +from .models import PreparedRequest, Request, Response +from .sessions import Session, session +from .status_codes import codes + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter("default", FileModeWarning, append=True) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py new file mode 100644 index 0000000..5063c3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = "requests" +__description__ = "Python HTTP for Humans." +__url__ = "https://requests.readthedocs.io" +__version__ = "2.31.0" +__build__ = 0x023100 +__author__ = "Kenneth Reitz" +__author_email__ = "me@kennethreitz.org" +__license__ = "Apache 2.0" +__copyright__ = "Copyright Kenneth Reitz" +__cake__ = "\u2728 \U0001f370 \u2728" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py new file mode 100644 index 0000000..f2cf635 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py @@ -0,0 +1,50 @@ +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" +import re + +from .compat import builtin_str + +_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$") +_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$") +_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$") +_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$") + +_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR) +_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE) +HEADER_VALIDATORS = { + bytes: _HEADER_VALIDATORS_BYTE, + str: _HEADER_VALIDATORS_STR, +} + + +def to_native_string(string, encoding="ascii"): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode("ascii") + return True + except UnicodeEncodeError: + return False diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py new file mode 100644 index 0000000..10c1767 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py @@ -0,0 +1,538 @@ +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket # noqa: F401 + +from pip._vendor.urllib3.exceptions import ClosedPoolError, ConnectTimeoutError +from pip._vendor.urllib3.exceptions import HTTPError as _HTTPError +from pip._vendor.urllib3.exceptions import InvalidHeader as _InvalidHeader +from pip._vendor.urllib3.exceptions import ( + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, +) +from pip._vendor.urllib3.exceptions import ProxyError as _ProxyError +from pip._vendor.urllib3.exceptions import ReadTimeoutError, ResponseError +from pip._vendor.urllib3.exceptions import SSLError as _SSLError +from pip._vendor.urllib3.poolmanager import PoolManager, proxy_from_url +from pip._vendor.urllib3.util import Timeout as TimeoutSauce +from pip._vendor.urllib3.util import parse_url +from pip._vendor.urllib3.util.retry import Retry + +from .auth import _basic_auth_str +from .compat import basestring, urlparse +from .cookies import extract_cookies_to_jar +from .exceptions import ( + ConnectionError, + ConnectTimeout, + InvalidHeader, + InvalidProxyURL, + InvalidSchema, + InvalidURL, + ProxyError, + ReadTimeout, + RetryError, + SSLError, +) +from .models import Response +from .structures import CaseInsensitiveDict +from .utils import ( + DEFAULT_CA_BUNDLE_PATH, + extract_zipped_paths, + get_auth_from_url, + get_encoding_from_headers, + prepend_scheme_if_needed, + select_proxy, + urldefragauth, +) + +try: + from pip._vendor.urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter: + """The Base Transport Adapter""" + + def __init__(self): + super().__init__() + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + + __attrs__ = [ + "max_retries", + "config", + "_pool_connections", + "_pool_maxsize", + "_pool_block", + ] + + def __init__( + self, + pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, + max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK, + ): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super().__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager( + self._pool_connections, self._pool_maxsize, block=self._pool_block + ) + + def init_poolmanager( + self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs + ): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + **pool_kwargs, + ) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith("socks"): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith("https") and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise OSError( + f"Could not find a suitable TLS CA certificate bundle, " + f"invalid path: {cert_loc}" + ) + + conn.cert_reqs = "CERT_REQUIRED" + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = "CERT_NONE" + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise OSError( + f"Could not find the TLS certificate file, " + f"invalid path: {conn.cert_file}" + ) + if conn.key_file and not os.path.exists(conn.key_file): + raise OSError( + f"Could not find the TLS key file, invalid path: {conn.key_file}" + ) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, "status", None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, "headers", {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode("utf-8") + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = proxy and scheme != "https" + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith("socks") + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return headers + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers( + request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + ) + + chunked = not (request.body is None or "Content-Length" in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError: + raise ValueError( + f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, " + f"or a single float to set both timeouts to the same value." + ) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout, + chunked=chunked, + ) + + except (ProtocolError, OSError) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py new file mode 100644 index 0000000..cd0b3ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py @@ -0,0 +1,157 @@ +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("get", url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("options", url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return request("head", url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("post", url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("put", url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("patch", url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("delete", url, **kwargs) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py new file mode 100644 index 0000000..9733686 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py @@ -0,0 +1,315 @@ +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import hashlib +import os +import re +import threading +import time +import warnings +from base64 import b64encode + +from ._internal_utils import to_native_string +from .compat import basestring, str, urlparse +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded" +CONTENT_TYPE_MULTI_PART = "multipart/form-data" + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(type(password)), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode("latin1") + + if isinstance(password, str): + password = password.encode("latin1") + + authstr = "Basic " + to_native_string( + b64encode(b":".join((username, password))).strip() + ) + + return authstr + + +class AuthBase: + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError("Auth hooks must be callable.") + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers["Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, "init"): + self._thread_local.init = True + self._thread_local.last_nonce = "" + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal["realm"] + nonce = self._thread_local.chal["nonce"] + qop = self._thread_local.chal.get("qop") + algorithm = self._thread_local.chal.get("algorithm") + opaque = self._thread_local.chal.get("opaque") + hash_utf8 = None + + if algorithm is None: + _algorithm = "MD5" + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == "MD5" or _algorithm == "MD5-SESS": + + def md5_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.md5(x).hexdigest() + + hash_utf8 = md5_utf8 + elif _algorithm == "SHA": + + def sha_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha1(x).hexdigest() + + hash_utf8 = sha_utf8 + elif _algorithm == "SHA-256": + + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha256(x).hexdigest() + + hash_utf8 = sha256_utf8 + elif _algorithm == "SHA-512": + + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha512(x).hexdigest() + + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731 + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += f"?{p_parsed.query}" + + A1 = f"{self.username}:{realm}:{self.password}" + A2 = f"{method}:{path}" + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = f"{self._thread_local.nonce_count:08x}" + s = str(self._thread_local.nonce_count).encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") + s += os.urandom(8) + + cnonce = hashlib.sha1(s).hexdigest()[:16] + if _algorithm == "MD5-SESS": + HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") + + if not qop: + respdig = KD(HA1, f"{nonce}:{HA2}") + elif qop == "auth" or "auth" in qop.split(","): + noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}" + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = ( + f'username="{self.username}", realm="{realm}", nonce="{nonce}", ' + f'uri="{path}", response="{respdig}"' + ) + if opaque: + base += f', opaque="{opaque}"' + if algorithm: + base += f', algorithm="{algorithm}"' + if entdig: + base += f', digest="{entdig}"' + if qop: + base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"' + + return f"Digest {base}" + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get("www-authenticate", "") + + if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r"digest ", flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers["Authorization"] = self.build_digest_header( + prep.method, prep.url + ) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers["Authorization"] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook("response", self.handle_401) + r.register_hook("response", self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py new file mode 100644 index 0000000..38696a1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" + +import os + +if "_PIP_STANDALONE_CERT" not in os.environ: + from pip._vendor.certifi import where +else: + def where(): + return os.environ["_PIP_STANDALONE_CERT"] + +if __name__ == "__main__": + print(where()) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py new file mode 100644 index 0000000..9ab2bb4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py @@ -0,0 +1,67 @@ +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module previously handled import compatibility issues +between Python 2 and Python 3. It remains for backwards +compatibility until the next major version. +""" + +from pip._vendor import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = _ver[0] == 2 + +#: Python 3.x? +is_py3 = _ver[0] == 3 + +# Note: We've patched out simplejson support in pip because it prevents +# upgrading simplejson on Windows. +import json +from json import JSONDecodeError + +# Keep OrderedDict for backwards compatibility. +from collections import OrderedDict +from collections.abc import Callable, Mapping, MutableMapping +from http import cookiejar as cookielib +from http.cookies import Morsel +from io import StringIO + +# -------------- +# Legacy Imports +# -------------- +from urllib.parse import ( + quote, + quote_plus, + unquote, + unquote_plus, + urldefrag, + urlencode, + urljoin, + urlparse, + urlsplit, + urlunparse, +) +from urllib.request import ( + getproxies, + getproxies_environment, + parse_http_list, + proxy_bypass, + proxy_bypass_environment, +) + +builtin_str = str +str = str +bytes = bytes +basestring = (str, bytes) +numeric_types = (int, float) +integer_types = (int,) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py new file mode 100644 index 0000000..bf54ab2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py @@ -0,0 +1,561 @@ +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import calendar +import copy +import time + +from ._internal_utils import to_native_string +from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest: + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get("Host"): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers["Host"], encoding="utf-8") + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse( + [ + parsed.scheme, + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + ] + ) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError( + "Cookie headers should be added with add_unredirected_header()" + ) + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse: + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, "_original_response") and response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get("Cookie") + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name( + self, name, domain=kwargs.get("domain"), path=kwargs.get("path") + ) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and ( + path is None or cookie.path == path + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super().__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if ( + hasattr(cookie.value, "startswith") + and cookie.value.startswith('"') + and cookie.value.endswith('"') + ): + cookie.value = cookie.value.replace('\\"', "") + return super().set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super().update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: + # if there are multiple cookies that meet passed in criteria + raise CookieConflictError( + f"There are multiple cookies with name, {name!r}" + ) + # we will eventually return this as long as no cookie conflict + toReturn = cookie.value + + if toReturn: + return toReturn + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop("_cookies_lock") + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if "_cookies_lock" not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, "copy"): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + "version": 0, + "name": name, + "value": value, + "port": None, + "domain": "", + "path": "/", + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + + badargs = set(kwargs) - set(result) + if badargs: + raise TypeError( + f"create_cookie() got unexpected keyword arguments: {list(badargs)}" + ) + + result.update(kwargs) + result["port_specified"] = bool(result["port"]) + result["domain_specified"] = bool(result["domain"]) + result["domain_initial_dot"] = result["domain"].startswith(".") + result["path_specified"] = bool(result["path"]) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel["max-age"]: + try: + expires = int(time.time() + int(morsel["max-age"])) + except ValueError: + raise TypeError(f"max-age: {morsel['max-age']} must be integer") + elif morsel["expires"]: + time_template = "%a, %d-%b-%Y %H:%M:%S GMT" + expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) + return create_cookie( + comment=morsel["comment"], + comment_url=bool(morsel["comment"]), + discard=False, + domain=morsel["domain"], + expires=expires, + name=morsel.key, + path=morsel["path"], + port=None, + rest={"HttpOnly": morsel["httponly"]}, + rfc2109=False, + secure=bool(morsel["secure"]), + value=morsel.value, + version=morsel["version"] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError("You can only merge into CookieJar") + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py new file mode 100644 index 0000000..168d073 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py @@ -0,0 +1,141 @@ +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from pip._vendor.urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop("response", None) + self.response = response + self.request = kwargs.pop("request", None) + if response is not None and not self.request and hasattr(response, "request"): + self.request = self.response.request + super().__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + def __init__(self, *args, **kwargs): + """ + Construct the JSONDecodeError instance first with all + args. Then use it's args to construct the IOError so that + the json specific args aren't used as IOError specific args + and the error message from JSONDecodeError is preserved. + """ + CompatJSONDecodeError.__init__(self, *args) + InvalidJSONError.__init__(self, *self.args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py new file mode 100644 index 0000000..2d292c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py @@ -0,0 +1,131 @@ +"""Module containing bug report helper(s).""" + +import json +import platform +import ssl +import sys + +from pip._vendor import idna +from pip._vendor import urllib3 + +from . import __version__ as requests_version + +charset_normalizer = None + +try: + from pip._vendor import chardet +except ImportError: + chardet = None + +try: + from pip._vendor.urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import cryptography + import OpenSSL + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 3.10.3 it will return + {'name': 'CPython', 'version': '3.10.3'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + implementation_version = "{}.{}.{}".format( + sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro, + ) + if sys.pypy_version_info.releaselevel != "final": + implementation_version = "".join( + [implementation_version, sys.pypy_version_info.releaselevel] + ) + elif implementation == "Jython": + implementation_version = platform.python_version() # Complete Guess + elif implementation == "IronPython": + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = "Unknown" + + return {"name": implementation, "version": implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = { + "system": "Unknown", + "release": "Unknown", + } + + implementation_info = _implementation() + urllib3_info = {"version": urllib3.__version__} + charset_normalizer_info = {"version": None} + chardet_info = {"version": None} + if charset_normalizer: + charset_normalizer_info = {"version": charset_normalizer.__version__} + if chardet: + chardet_info = {"version": chardet.__version__} + + pyopenssl_info = { + "version": None, + "openssl_version": "", + } + if OpenSSL: + pyopenssl_info = { + "version": OpenSSL.__version__, + "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}", + } + cryptography_info = { + "version": getattr(cryptography, "__version__", ""), + } + idna_info = { + "version": getattr(idna, "__version__", ""), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} + + return { + "platform": platform_info, + "implementation": implementation_info, + "system_ssl": system_ssl_info, + "using_pyopenssl": pyopenssl is not None, + "using_charset_normalizer": chardet is None, + "pyOpenSSL": pyopenssl_info, + "urllib3": urllib3_info, + "chardet": chardet_info, + "charset_normalizer": charset_normalizer_info, + "cryptography": cryptography_info, + "idna": idna_info, + "requests": { + "version": requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py new file mode 100644 index 0000000..d181ba2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py @@ -0,0 +1,33 @@ +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ["response"] + + +def default_hooks(): + return {event: [] for event in HOOKS} + + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, "__call__"): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py new file mode 100644 index 0000000..76e6f19 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py @@ -0,0 +1,1034 @@ +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna # noqa: F401 +from io import UnsupportedOperation + +from pip._vendor.urllib3.exceptions import ( + DecodeError, + LocationParseError, + ProtocolError, + ReadTimeoutError, + SSLError, +) +from pip._vendor.urllib3.fields import RequestField +from pip._vendor.urllib3.filepost import encode_multipart_formdata +from pip._vendor.urllib3.util import parse_url + +from ._internal_utils import to_native_string, unicode_is_ascii +from .auth import HTTPBasicAuth +from .compat import ( + Callable, + JSONDecodeError, + Mapping, + basestring, + builtin_str, + chardet, + cookielib, +) +from .compat import json as complexjson +from .compat import urlencode, urlsplit, urlunparse +from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header +from .exceptions import ( + ChunkedEncodingError, + ConnectionError, + ContentDecodingError, + HTTPError, + InvalidJSONError, + InvalidURL, +) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from .exceptions import MissingSchema +from .exceptions import SSLError as RequestsSSLError +from .exceptions import StreamConsumedError +from .hooks import default_hooks +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( + check_header_validity, + get_auth_from_url, + guess_filename, + guess_json_utf, + iter_slices, + parse_header_links, + requote_uri, + stream_decode_response_unicode, + super_len, + to_key_val_list, +) + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin: + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = "/" + + url.append(path) + + query = p.query + if query: + url.append("?") + url.append(query) + + return "".join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, "read"): + return data + elif hasattr(data, "__iter__"): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if not files: + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, "__iter__"): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + ( + field.decode("utf-8") + if isinstance(field, bytes) + else field, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, "read"): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin: + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError(f'Unsupported event specified, with event name "{event}"') + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, "__iter__"): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return f"" + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return f"" + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + from pip._vendor import idna + + try: + host = idna.encode(host, uts46=True).decode("utf-8") + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode("utf8") + else: + url = str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ":" in url and not url.lower().startswith("http"): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + raise MissingSchema( + f"Invalid URL {url!r}: No scheme supplied. " + f"Perhaps you meant https://{url}?" + ) + + if not host: + raise InvalidURL(f"Invalid URL {url!r}: No host supplied") + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL("URL has an invalid label.") + elif host.startswith(("*", ".")): + raise InvalidURL("URL has an invalid label.") + + # Carefully reconstruct the network location + netloc = auth or "" + if netloc: + netloc += "@" + netloc += host + if port: + netloc += f":{port}" + + # Bare domains aren't valid URLs. + if not path: + path = "/" + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = f"{query}&{enc_params}" + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = "application/json" + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode("utf-8") + + is_stream = all( + [ + hasattr(data, "__iter__"), + not isinstance(data, (basestring, list, tuple, Mapping)), + ] + ) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, "tell", None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError( + "Streamed bodies and files are mutually exclusive." + ) + + if length: + self.headers["Content-Length"] = builtin_str(length) + else: + self.headers["Transfer-Encoding"] = "chunked" + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, "read"): + content_type = None + else: + content_type = "application/x-www-form-urlencoded" + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ("content-type" not in self.headers): + self.headers["Content-Type"] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers["Content-Length"] = builtin_str(length) + elif ( + self.method not in ("GET", "HEAD") + and self.headers.get("Content-Length") is None + ): + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers["Content-Length"] = "0" + + def prepare_auth(self, auth, url=""): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers["Cookie"] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response: + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + "_content", + "status_code", + "headers", + "url", + "history", + "encoding", + "reason", + "cookies", + "elapsed", + "request", + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, "_content_consumed", True) + setattr(self, "raw", None) + + def __repr__(self): + return f"" + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return "location" in self.headers and self.status_code in REDIRECT_STATI + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return "location" in self.headers and self.status_code in ( + codes.moved_permanently, + codes.permanent_redirect, + ) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + return chardet.detect(self.content)["encoding"] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, "stream"): + try: + yield from self.raw.stream(chunk_size, decode_content=True) + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + except SSLError as e: + raise RequestsSSLError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError( + f"chunk_size must be an int, it is instead a {type(chunk_size)}." + ) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines( + self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None + ): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content( + chunk_size=chunk_size, decode_unicode=decode_unicode + ): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + yield from lines + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError("The content for this response was already consumed") + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return "" + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors="replace") + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors="replace") + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + except JSONDecodeError as e: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get("link") + + resolved_links = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get("rel") or link.get("url") + resolved_links[key] = link + + return resolved_links + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = "" + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode("utf-8") + except UnicodeDecodeError: + reason = self.reason.decode("iso-8859-1") + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = ( + f"{self.status_code} Client Error: {reason} for url: {self.url}" + ) + + elif 500 <= self.status_code < 600: + http_error_msg = ( + f"{self.status_code} Server Error: {reason} for url: {self.url}" + ) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, "release_conn", None) + if release_conn is not None: + release_conn() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py new file mode 100644 index 0000000..9582fa7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py @@ -0,0 +1,16 @@ +import sys + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna', 'chardet'): + vendored_package = "pip._vendor." + package + locals()[package] = __import__(vendored_package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == vendored_package or mod.startswith(vendored_package + '.'): + unprefixed_mod = mod[len("pip._vendor."):] + sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] + +# Kinda cool, though, right? diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py new file mode 100644 index 0000000..dbcf2a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py @@ -0,0 +1,833 @@ +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from collections import OrderedDict +from datetime import timedelta + +from ._internal_utils import to_native_string +from .adapters import HTTPAdapter +from .auth import _basic_auth_str +from .compat import Mapping, cookielib, urljoin, urlparse +from .cookies import ( + RequestsCookieJar, + cookiejar_from_dict, + extract_cookies_to_jar, + merge_cookies, +) +from .exceptions import ( + ChunkedEncodingError, + ContentDecodingError, + InvalidSchema, + TooManyRedirects, +) +from .hooks import default_hooks, dispatch_hook + +# formerly defined here, reexposed here for backward compatibility +from .models import ( # noqa: F401 + DEFAULT_REDIRECT_LIMIT, + REDIRECT_STATI, + PreparedRequest, + Request, +) +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( # noqa: F401 + DEFAULT_PORTS, + default_headers, + get_auth_from_url, + get_environ_proxies, + get_netrc_auth, + requote_uri, + resolve_proxies, + rewind_body, + should_bypass_proxies, + to_key_val_list, +) + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == "win32": + preferred_clock = time.perf_counter +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get("response") == []: + return request_hooks + + if request_hooks is None or request_hooks.get("response") == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin: + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers["location"] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + location = location.encode("latin1") + return to_native_string(location, "utf8") + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if ( + old_parsed.scheme == "http" + and old_parsed.port in (80, None) + and new_parsed.scheme == "https" + and new_parsed.port in (443, None) + ): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if ( + not changed_scheme + and old_parsed.port in default_port + and new_parsed.port in default_port + ): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects( + self, + resp, + req, + stream=False, + timeout=None, + verify=True, + cert=None, + proxies=None, + yield_requests=False, + **adapter_kwargs, + ): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects( + f"Exceeded {self.max_redirects} redirects.", response=resp + ) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith("//"): + parsed_rurl = urlparse(resp.url) + url = ":".join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == "" and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in ( + codes.temporary_redirect, + codes.permanent_redirect, + ): + # https://github.com/psf/requests/issues/3490 + purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop("Cookie", None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = prepared_request._body_position is not None and ( + "Content-Length" in headers or "Transfer-Encoding" in headers + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs, + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if "Authorization" in headers and self.should_strip_auth( + response.request.url, url + ): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers["Authorization"] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if "Proxy-Authorization" in headers: + del headers["Proxy-Authorization"] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + # urllib3 handles proxy authorization for us in the standard adapter. + # Avoid appending this to TLS tunneled requests where it may be leaked. + if not scheme.startswith('https') and username and password: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != "HEAD": + method = "GET" + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == "POST": + method = "GET" + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + "headers", + "cookies", + "auth", + "proxies", + "hooks", + "params", + "verify", + "cert", + "adapters", + "stream", + "trust_env", + "max_redirects", + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount("https://", HTTPAdapter()) + self.mount("http://", HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies + ) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting( + request.headers, self.headers, dict_class=CaseInsensitiveDict + ), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request( + self, + method, + url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=True, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None, + ): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + "timeout": timeout, + "allow_redirects": allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("POST", url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PUT", url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PATCH", url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("DELETE", url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault("stream", self.stream) + kwargs.setdefault("verify", self.verify) + kwargs.setdefault("cert", self.cert) + if "proxies" not in kwargs: + kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError("You can only send PreparedRequests.") + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop("allow_redirects", True) + stream = kwargs.get("stream") + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook("response", hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next( + self.resolve_redirects(r, request, yield_requests=True, **kwargs) + ) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get("no_proxy") if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration + # and be compatible with cURL. + if verify is True or verify is None: + verify = ( + os.environ.get("REQUESTS_CA_BUNDLE") + or os.environ.get("CURL_CA_BUNDLE") + or verify + ) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema(f"No connection adapters were found for {url!r}") + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py new file mode 100644 index 0000000..4bd072b --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py @@ -0,0 +1,128 @@ +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + # Informational. + 100: ("continue",), + 101: ("switching_protocols",), + 102: ("processing",), + 103: ("checkpoint",), + 122: ("uri_too_long", "request_uri_too_long"), + 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"), + 201: ("created",), + 202: ("accepted",), + 203: ("non_authoritative_info", "non_authoritative_information"), + 204: ("no_content",), + 205: ("reset_content", "reset"), + 206: ("partial_content", "partial"), + 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"), + 208: ("already_reported",), + 226: ("im_used",), + # Redirection. + 300: ("multiple_choices",), + 301: ("moved_permanently", "moved", "\\o-"), + 302: ("found",), + 303: ("see_other", "other"), + 304: ("not_modified",), + 305: ("use_proxy",), + 306: ("switch_proxy",), + 307: ("temporary_redirect", "temporary_moved", "temporary"), + 308: ( + "permanent_redirect", + "resume_incomplete", + "resume", + ), # "resume" and "resume_incomplete" to be removed in 3.0 + # Client Error. + 400: ("bad_request", "bad"), + 401: ("unauthorized",), + 402: ("payment_required", "payment"), + 403: ("forbidden",), + 404: ("not_found", "-o-"), + 405: ("method_not_allowed", "not_allowed"), + 406: ("not_acceptable",), + 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"), + 408: ("request_timeout", "timeout"), + 409: ("conflict",), + 410: ("gone",), + 411: ("length_required",), + 412: ("precondition_failed", "precondition"), + 413: ("request_entity_too_large",), + 414: ("request_uri_too_large",), + 415: ("unsupported_media_type", "unsupported_media", "media_type"), + 416: ( + "requested_range_not_satisfiable", + "requested_range", + "range_not_satisfiable", + ), + 417: ("expectation_failed",), + 418: ("im_a_teapot", "teapot", "i_am_a_teapot"), + 421: ("misdirected_request",), + 422: ("unprocessable_entity", "unprocessable"), + 423: ("locked",), + 424: ("failed_dependency", "dependency"), + 425: ("unordered_collection", "unordered"), + 426: ("upgrade_required", "upgrade"), + 428: ("precondition_required", "precondition"), + 429: ("too_many_requests", "too_many"), + 431: ("header_fields_too_large", "fields_too_large"), + 444: ("no_response", "none"), + 449: ("retry_with", "retry"), + 450: ("blocked_by_windows_parental_controls", "parental_controls"), + 451: ("unavailable_for_legal_reasons", "legal_reasons"), + 499: ("client_closed_request",), + # Server Error. + 500: ("internal_server_error", "server_error", "/o\\", "✗"), + 501: ("not_implemented",), + 502: ("bad_gateway",), + 503: ("service_unavailable", "unavailable"), + 504: ("gateway_timeout",), + 505: ("http_version_not_supported", "http_version"), + 506: ("variant_also_negotiates",), + 507: ("insufficient_storage",), + 509: ("bandwidth_limit_exceeded", "bandwidth"), + 510: ("not_extended",), + 511: ("network_authentication_required", "network_auth", "network_authentication"), +} + +codes = LookupDict(name="status_codes") + + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(("\\", "/")): + setattr(codes, title.upper(), code) + + def doc(code): + names = ", ".join(f"``{n}``" for n in _codes[code]) + return "* %d: %s" % (code, names) + + global __doc__ + __doc__ = ( + __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) + if __doc__ is not None + else None + ) + + +_init() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py new file mode 100644 index 0000000..188e13e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py @@ -0,0 +1,99 @@ +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super().__init__() + + def __repr__(self): + return f"" + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py b/venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py new file mode 100644 index 0000000..36607ed --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py @@ -0,0 +1,1094 @@ +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict + +from pip._vendor.urllib3.util import make_headers, parse_url + +from . import certs +from .__version__ import __version__ + +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import ( # noqa: F401 + _HEADER_VALIDATORS_BYTE, + _HEADER_VALIDATORS_STR, + HEADER_VALIDATORS, + to_native_string, +) +from .compat import ( + Mapping, + basestring, + bytes, + getproxies, + getproxies_environment, + integer_types, +) +from .compat import parse_http_list as _parse_list_header +from .compat import ( + proxy_bypass, + proxy_bypass_environment, + quote, + str, + unquote, + urlparse, + urlunparse, +) +from .cookies import cookiejar_from_dict +from .exceptions import ( + FileModeWarning, + InvalidHeader, + InvalidURL, + UnrewindableBodyError, +) +from .structures import CaseInsensitiveDict + +NETRC_FILES = (".netrc", "_netrc") + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {"http": 80, "https": 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == "win32": + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + import winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0] + except (OSError, ValueError): + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(";") + # now check if we match one of the registry values. + for test in proxyOverride: + if test == "": + if "." not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, "items"): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, "__len__"): + total_length = len(o) + + elif hasattr(o, "len"): + total_length = o.len + + elif hasattr(o, "fileno"): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if "b" not in o.mode: + warnings.warn( + ( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode." + ), + FileModeWarning, + ) + + if hasattr(o, "tell"): + try: + current_position = o.tell() + except OSError: + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, "seek") and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except OSError: + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get("NETRC") + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = (f"~/{f}" for f in NETRC_FILES) + + try: + from netrc import NetrcParseError, netrc + + netrc_path = None + + for f in netrc_locations: + try: + loc = os.path.expanduser(f) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/psf/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b":" + if isinstance(url, str): + splitstr = splitstr.decode("ascii") + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = 0 if _netrc[0] else 1 + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, OSError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, "name", None) + if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">": + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = "/".join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split("/")[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, "wb") as tmp_handler: + yield tmp_handler + os.replace(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn( + ( + "In requests 3.0, get_encodings_from_content will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return ( + charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content) + ) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(";") + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1 :].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get("content-type") + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if "charset" in params: + return params["charset"].strip("'\"") + + if "text" in content_type: + return "ISO-8859-1" + + if "application/json" in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return "utf-8" + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes an iterator.""" + + if r.encoding is None: + yield from iterator + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace") + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b"", final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos : pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn( + ( + "In requests 3.0, get_unicode_from_response will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors="replace") + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~" +) + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split("%") + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL(f"Invalid percent-escape sequence: '{h}'") + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = f"%{parts[i]}" + else: + parts[i] = f"%{parts[i]}" + return "".join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0] + netaddr, bits = net.split("/") + netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack(">I", bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except OSError: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count("/") == 1: + try: + mask = int(string_network.split("/")[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split("/")[0]) + except OSError: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + def get_proxy(key): + return os.environ.get(key) or os.environ.get(key.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy("no_proxy") + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += f":{parsed.port}" + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ("no_proxy", no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get("all")) + + proxy_keys = [ + urlparts.scheme + "://" + urlparts.hostname, + urlparts.scheme, + "all://" + urlparts.hostname, + "all", + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such a NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get("no_proxy") + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get("all")) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return f"{name}/{__version__}" + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict( + { + "User-Agent": default_user_agent(), + "Accept-Encoding": DEFAULT_ACCEPT_ENCODING, + "Accept": "*/*", + "Connection": "keep-alive", + } + ) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = " '\"" + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + + link = {"url": url.strip("<> '\"")} + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = "\x00".encode("ascii") # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return "utf-32" # BOM included + if sample[:3] == codecs.BOM_UTF8: + return "utf-8-sig" # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return "utf-16" # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return "utf-8" + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return "utf-16-be" + if sample[1::2] == _null2: # 2nd and 4th are null + return "utf-16-le" + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return "utf-32-be" + if sample[1:] == _null3: + return "utf-32-le" + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = "@".join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = "" + + return urlunparse((scheme, netloc, path, "", query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ("", "") + + return auth + + +def check_header_validity(header): + """Verifies that header parts don't contain leading whitespace + reserved characters, or return characters. + + :param header: tuple, in the format (name, value). + """ + name, value = header + _validate_header_part(header, name, 0) + _validate_header_part(header, value, 1) + + +def _validate_header_part(header, header_part, header_validator_index): + if isinstance(header_part, str): + validator = _HEADER_VALIDATORS_STR[header_validator_index] + elif isinstance(header_part, bytes): + validator = _HEADER_VALIDATORS_BYTE[header_validator_index] + else: + raise InvalidHeader( + f"Header part ({header_part!r}) from {header} " + f"must be of type str or bytes, not {type(header_part)}" + ) + + if not validator.match(header_part): + header_kind = "name" if header_validator_index == 0 else "value" + raise InvalidHeader( + f"Invalid leading whitespace, reserved character(s), or return" + f"character(s) in header {header_kind}: {header_part!r}" + ) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit("@", 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, "")) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, "seek", None) + if body_seek is not None and isinstance( + prepared_request._body_position, integer_types + ): + try: + body_seek(prepared_request._body_position) + except OSError: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect." + ) + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py new file mode 100644 index 0000000..d92acc7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py @@ -0,0 +1,26 @@ +__all__ = [ + "__version__", + "AbstractProvider", + "AbstractResolver", + "BaseReporter", + "InconsistentCandidate", + "Resolver", + "RequirementsConflicted", + "ResolutionError", + "ResolutionImpossible", + "ResolutionTooDeep", +] + +__version__ = "1.0.1" + + +from .providers import AbstractProvider, AbstractResolver +from .reporters import BaseReporter +from .resolvers import ( + InconsistentCandidate, + RequirementsConflicted, + ResolutionError, + ResolutionImpossible, + ResolutionTooDeep, + Resolver, +) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py new file mode 100644 index 0000000..1becc50 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py @@ -0,0 +1,6 @@ +__all__ = ["Mapping", "Sequence"] + +try: + from collections.abc import Mapping, Sequence +except ImportError: + from collections import Mapping, Sequence diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py new file mode 100644 index 0000000..e99d87e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py @@ -0,0 +1,133 @@ +class AbstractProvider(object): + """Delegate class to provide the required interface for the resolver.""" + + def identify(self, requirement_or_candidate): + """Given a requirement, return an identifier for it. + + This is used to identify a requirement, e.g. whether two requirements + should have their specifier parts merged. + """ + raise NotImplementedError + + def get_preference( + self, + identifier, + resolutions, + candidates, + information, + backtrack_causes, + ): + """Produce a sort key for given requirement based on preference. + + The preference is defined as "I think this requirement should be + resolved first". The lower the return value is, the more preferred + this group of arguments is. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches which should be returned. + :param resolutions: Mapping of candidates currently pinned by the + resolver. Each key is an identifier, and the value is a candidate. + The candidate may conflict with requirements from ``information``. + :param candidates: Mapping of each dependency's possible candidates. + Each value is an iterator of candidates. + :param information: Mapping of requirement information of each package. + Each value is an iterator of *requirement information*. + :param backtrack_causes: Sequence of requirement information that were + the requirements that caused the resolver to most recently backtrack. + + A *requirement information* instance is a named tuple with two members: + + * ``requirement`` specifies a requirement contributing to the current + list of candidates. + * ``parent`` specifies the candidate that provides (depended on) the + requirement, or ``None`` to indicate a root requirement. + + The preference could depend on various issues, including (not + necessarily in this order): + + * Is this package pinned in the current resolution result? + * How relaxed is the requirement? Stricter ones should probably be + worked on first? (I don't know, actually.) + * How many possibilities are there to satisfy this requirement? Those + with few left should likely be worked on first, I guess? + * Are there any known conflicts for this requirement? We should + probably work on those with the most known conflicts. + + A sortable value should be returned (this will be used as the ``key`` + parameter of the built-in sorting function). The smaller the value is, + the more preferred this requirement is (i.e. the sorting function + is called with ``reverse=False``). + """ + raise NotImplementedError + + def find_matches(self, identifier, requirements, incompatibilities): + """Find all possible candidates that satisfy the given constraints. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches of which should be returned. + :param requirements: A mapping of requirements that all returned + candidates must satisfy. Each key is an identifier, and the value + an iterator of requirements for that dependency. + :param incompatibilities: A mapping of known incompatibilities of + each dependency. Each key is an identifier, and the value an + iterator of incompatibilities known to the resolver. All + incompatibilities *must* be excluded from the return value. + + This should try to get candidates based on the requirements' types. + For VCS, local, and archive requirements, the one-and-only match is + returned, and for a "named" requirement, the index(es) should be + consulted to find concrete candidates for this requirement. + + The return value should produce candidates ordered by preference; the + most preferred candidate should come first. The return type may be one + of the following: + + * A callable that returns an iterator that yields candidates. + * An collection of candidates. + * An iterable of candidates. This will be consumed immediately into a + list of candidates. + """ + raise NotImplementedError + + def is_satisfied_by(self, requirement, candidate): + """Whether the given requirement can be satisfied by a candidate. + + The candidate is guaranteed to have been generated from the + requirement. + + A boolean should be returned to indicate whether ``candidate`` is a + viable solution to the requirement. + """ + raise NotImplementedError + + def get_dependencies(self, candidate): + """Get dependencies of a candidate. + + This should return a collection of requirements that `candidate` + specifies as its dependencies. + """ + raise NotImplementedError + + +class AbstractResolver(object): + """The thing that performs the actual resolution work.""" + + base_exception = Exception + + def __init__(self, provider, reporter): + self.provider = provider + self.reporter = reporter + + def resolve(self, requirements, **kwargs): + """Take a collection of constraints, spit out the resolution result. + + This returns a representation of the final resolution state, with one + guarenteed attribute ``mapping`` that contains resolved candidates as + values. The keys are their respective identifiers. + + :param requirements: A collection of constraints. + :param kwargs: Additional keyword arguments that subclasses may accept. + + :raises: ``self.base_exception`` or its subclass. + """ + raise NotImplementedError diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py new file mode 100644 index 0000000..688b5e1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py @@ -0,0 +1,43 @@ +class BaseReporter(object): + """Delegate class to provider progress reporting for the resolver.""" + + def starting(self): + """Called before the resolution actually starts.""" + + def starting_round(self, index): + """Called before each round of resolution starts. + + The index is zero-based. + """ + + def ending_round(self, index, state): + """Called before each round of resolution ends. + + This is NOT called if the resolution ends at this round. Use `ending` + if you want to report finalization. The index is zero-based. + """ + + def ending(self, state): + """Called before the resolution ends successfully.""" + + def adding_requirement(self, requirement, parent): + """Called when adding a new requirement into the resolve criteria. + + :param requirement: The additional requirement to be applied to filter + the available candidaites. + :param parent: The candidate that requires ``requirement`` as a + dependency, or None if ``requirement`` is one of the root + requirements passed in from ``Resolver.resolve()``. + """ + + def resolving_conflicts(self, causes): + """Called when starting to attempt requirement conflict resolution. + + :param causes: The information on the collision that caused the backtracking. + """ + + def rejecting_candidate(self, criterion, candidate): + """Called when rejecting a candidate during backtracking.""" + + def pinning(self, candidate): + """Called when adding a candidate to the potential solution.""" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py new file mode 100644 index 0000000..2c3d0e3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py @@ -0,0 +1,547 @@ +import collections +import itertools +import operator + +from .providers import AbstractResolver +from .structs import DirectedGraph, IteratorMapping, build_iter_view + +RequirementInformation = collections.namedtuple( + "RequirementInformation", ["requirement", "parent"] +) + + +class ResolverException(Exception): + """A base class for all exceptions raised by this module. + + Exceptions derived by this class should all be handled in this module. Any + bubbling pass the resolver should be treated as a bug. + """ + + +class RequirementsConflicted(ResolverException): + def __init__(self, criterion): + super(RequirementsConflicted, self).__init__(criterion) + self.criterion = criterion + + def __str__(self): + return "Requirements conflict: {}".format( + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class InconsistentCandidate(ResolverException): + def __init__(self, candidate, criterion): + super(InconsistentCandidate, self).__init__(candidate, criterion) + self.candidate = candidate + self.criterion = criterion + + def __str__(self): + return "Provided candidate {!r} does not satisfy {}".format( + self.candidate, + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class Criterion(object): + """Representation of possible resolution results of a package. + + This holds three attributes: + + * `information` is a collection of `RequirementInformation` pairs. + Each pair is a requirement contributing to this criterion, and the + candidate that provides the requirement. + * `incompatibilities` is a collection of all known not-to-work candidates + to exclude from consideration. + * `candidates` is a collection containing all possible candidates deducted + from the union of contributing requirements and known incompatibilities. + It should never be empty, except when the criterion is an attribute of a + raised `RequirementsConflicted` (in which case it is always empty). + + .. note:: + This class is intended to be externally immutable. **Do not** mutate + any of its attribute containers. + """ + + def __init__(self, candidates, information, incompatibilities): + self.candidates = candidates + self.information = information + self.incompatibilities = incompatibilities + + def __repr__(self): + requirements = ", ".join( + "({!r}, via={!r})".format(req, parent) + for req, parent in self.information + ) + return "Criterion({})".format(requirements) + + def iter_requirement(self): + return (i.requirement for i in self.information) + + def iter_parent(self): + return (i.parent for i in self.information) + + +class ResolutionError(ResolverException): + pass + + +class ResolutionImpossible(ResolutionError): + def __init__(self, causes): + super(ResolutionImpossible, self).__init__(causes) + # causes is a list of RequirementInformation objects + self.causes = causes + + +class ResolutionTooDeep(ResolutionError): + def __init__(self, round_count): + super(ResolutionTooDeep, self).__init__(round_count) + self.round_count = round_count + + +# Resolution state in a round. +State = collections.namedtuple("State", "mapping criteria backtrack_causes") + + +class Resolution(object): + """Stateful resolution object. + + This is designed as a one-off object that holds information to kick start + the resolution process, and holds the results afterwards. + """ + + def __init__(self, provider, reporter): + self._p = provider + self._r = reporter + self._states = [] + + @property + def state(self): + try: + return self._states[-1] + except IndexError: + raise AttributeError("state") + + def _push_new_state(self): + """Push a new state into history. + + This new state will be used to hold resolution results of the next + coming round. + """ + base = self._states[-1] + state = State( + mapping=base.mapping.copy(), + criteria=base.criteria.copy(), + backtrack_causes=base.backtrack_causes[:], + ) + self._states.append(state) + + def _add_to_criteria(self, criteria, requirement, parent): + self._r.adding_requirement(requirement=requirement, parent=parent) + + identifier = self._p.identify(requirement_or_candidate=requirement) + criterion = criteria.get(identifier) + if criterion: + incompatibilities = list(criterion.incompatibilities) + else: + incompatibilities = [] + + matches = self._p.find_matches( + identifier=identifier, + requirements=IteratorMapping( + criteria, + operator.methodcaller("iter_requirement"), + {identifier: [requirement]}, + ), + incompatibilities=IteratorMapping( + criteria, + operator.attrgetter("incompatibilities"), + {identifier: incompatibilities}, + ), + ) + + if criterion: + information = list(criterion.information) + information.append(RequirementInformation(requirement, parent)) + else: + information = [RequirementInformation(requirement, parent)] + + criterion = Criterion( + candidates=build_iter_view(matches), + information=information, + incompatibilities=incompatibilities, + ) + if not criterion.candidates: + raise RequirementsConflicted(criterion) + criteria[identifier] = criterion + + def _remove_information_from_criteria(self, criteria, parents): + """Remove information from parents of criteria. + + Concretely, removes all values from each criterion's ``information`` + field that have one of ``parents`` as provider of the requirement. + + :param criteria: The criteria to update. + :param parents: Identifiers for which to remove information from all criteria. + """ + if not parents: + return + for key, criterion in criteria.items(): + criteria[key] = Criterion( + criterion.candidates, + [ + information + for information in criterion.information + if ( + information.parent is None + or self._p.identify(information.parent) not in parents + ) + ], + criterion.incompatibilities, + ) + + def _get_preference(self, name): + return self._p.get_preference( + identifier=name, + resolutions=self.state.mapping, + candidates=IteratorMapping( + self.state.criteria, + operator.attrgetter("candidates"), + ), + information=IteratorMapping( + self.state.criteria, + operator.attrgetter("information"), + ), + backtrack_causes=self.state.backtrack_causes, + ) + + def _is_current_pin_satisfying(self, name, criterion): + try: + current_pin = self.state.mapping[name] + except KeyError: + return False + return all( + self._p.is_satisfied_by(requirement=r, candidate=current_pin) + for r in criterion.iter_requirement() + ) + + def _get_updated_criteria(self, candidate): + criteria = self.state.criteria.copy() + for requirement in self._p.get_dependencies(candidate=candidate): + self._add_to_criteria(criteria, requirement, parent=candidate) + return criteria + + def _attempt_to_pin_criterion(self, name): + criterion = self.state.criteria[name] + + causes = [] + for candidate in criterion.candidates: + try: + criteria = self._get_updated_criteria(candidate) + except RequirementsConflicted as e: + self._r.rejecting_candidate(e.criterion, candidate) + causes.append(e.criterion) + continue + + # Check the newly-pinned candidate actually works. This should + # always pass under normal circumstances, but in the case of a + # faulty provider, we will raise an error to notify the implementer + # to fix find_matches() and/or is_satisfied_by(). + satisfied = all( + self._p.is_satisfied_by(requirement=r, candidate=candidate) + for r in criterion.iter_requirement() + ) + if not satisfied: + raise InconsistentCandidate(candidate, criterion) + + self._r.pinning(candidate=candidate) + self.state.criteria.update(criteria) + + # Put newly-pinned candidate at the end. This is essential because + # backtracking looks at this mapping to get the last pin. + self.state.mapping.pop(name, None) + self.state.mapping[name] = candidate + + return [] + + # All candidates tried, nothing works. This criterion is a dead + # end, signal for backtracking. + return causes + + def _backjump(self, causes): + """Perform backjumping. + + When we enter here, the stack is like this:: + + [ state Z ] + [ state Y ] + [ state X ] + .... earlier states are irrelevant. + + 1. No pins worked for Z, so it does not have a pin. + 2. We want to reset state Y to unpinned, and pin another candidate. + 3. State X holds what state Y was before the pin, but does not + have the incompatibility information gathered in state Y. + + Each iteration of the loop will: + + 1. Identify Z. The incompatibility is not always caused by the latest + state. For example, given three requirements A, B and C, with + dependencies A1, B1 and C1, where A1 and B1 are incompatible: the + last state might be related to C, so we want to discard the + previous state. + 2. Discard Z. + 3. Discard Y but remember its incompatibility information gathered + previously, and the failure we're dealing with right now. + 4. Push a new state Y' based on X, and apply the incompatibility + information from Y to Y'. + 5a. If this causes Y' to conflict, we need to backtrack again. Make Y' + the new Z and go back to step 2. + 5b. If the incompatibilities apply cleanly, end backtracking. + """ + incompatible_reqs = itertools.chain( + (c.parent for c in causes if c.parent is not None), + (c.requirement for c in causes), + ) + incompatible_deps = {self._p.identify(r) for r in incompatible_reqs} + while len(self._states) >= 3: + # Remove the state that triggered backtracking. + del self._states[-1] + + # Ensure to backtrack to a state that caused the incompatibility + incompatible_state = False + while not incompatible_state: + # Retrieve the last candidate pin and known incompatibilities. + try: + broken_state = self._states.pop() + name, candidate = broken_state.mapping.popitem() + except (IndexError, KeyError): + raise ResolutionImpossible(causes) + current_dependencies = { + self._p.identify(d) + for d in self._p.get_dependencies(candidate) + } + incompatible_state = not current_dependencies.isdisjoint( + incompatible_deps + ) + + incompatibilities_from_broken = [ + (k, list(v.incompatibilities)) + for k, v in broken_state.criteria.items() + ] + + # Also mark the newly known incompatibility. + incompatibilities_from_broken.append((name, [candidate])) + + # Create a new state from the last known-to-work one, and apply + # the previously gathered incompatibility information. + def _patch_criteria(): + for k, incompatibilities in incompatibilities_from_broken: + if not incompatibilities: + continue + try: + criterion = self.state.criteria[k] + except KeyError: + continue + matches = self._p.find_matches( + identifier=k, + requirements=IteratorMapping( + self.state.criteria, + operator.methodcaller("iter_requirement"), + ), + incompatibilities=IteratorMapping( + self.state.criteria, + operator.attrgetter("incompatibilities"), + {k: incompatibilities}, + ), + ) + candidates = build_iter_view(matches) + if not candidates: + return False + incompatibilities.extend(criterion.incompatibilities) + self.state.criteria[k] = Criterion( + candidates=candidates, + information=list(criterion.information), + incompatibilities=incompatibilities, + ) + return True + + self._push_new_state() + success = _patch_criteria() + + # It works! Let's work on this new state. + if success: + return True + + # State does not work after applying known incompatibilities. + # Try the still previous state. + + # No way to backtrack anymore. + return False + + def resolve(self, requirements, max_rounds): + if self._states: + raise RuntimeError("already resolved") + + self._r.starting() + + # Initialize the root state. + self._states = [ + State( + mapping=collections.OrderedDict(), + criteria={}, + backtrack_causes=[], + ) + ] + for r in requirements: + try: + self._add_to_criteria(self.state.criteria, r, parent=None) + except RequirementsConflicted as e: + raise ResolutionImpossible(e.criterion.information) + + # The root state is saved as a sentinel so the first ever pin can have + # something to backtrack to if it fails. The root state is basically + # pinning the virtual "root" package in the graph. + self._push_new_state() + + for round_index in range(max_rounds): + self._r.starting_round(index=round_index) + + unsatisfied_names = [ + key + for key, criterion in self.state.criteria.items() + if not self._is_current_pin_satisfying(key, criterion) + ] + + # All criteria are accounted for. Nothing more to pin, we are done! + if not unsatisfied_names: + self._r.ending(state=self.state) + return self.state + + # keep track of satisfied names to calculate diff after pinning + satisfied_names = set(self.state.criteria.keys()) - set( + unsatisfied_names + ) + + # Choose the most preferred unpinned criterion to try. + name = min(unsatisfied_names, key=self._get_preference) + failure_causes = self._attempt_to_pin_criterion(name) + + if failure_causes: + causes = [i for c in failure_causes for i in c.information] + # Backjump if pinning fails. The backjump process puts us in + # an unpinned state, so we can work on it in the next round. + self._r.resolving_conflicts(causes=causes) + success = self._backjump(causes) + self.state.backtrack_causes[:] = causes + + # Dead ends everywhere. Give up. + if not success: + raise ResolutionImpossible(self.state.backtrack_causes) + else: + # discard as information sources any invalidated names + # (unsatisfied names that were previously satisfied) + newly_unsatisfied_names = { + key + for key, criterion in self.state.criteria.items() + if key in satisfied_names + and not self._is_current_pin_satisfying(key, criterion) + } + self._remove_information_from_criteria( + self.state.criteria, newly_unsatisfied_names + ) + # Pinning was successful. Push a new state to do another pin. + self._push_new_state() + + self._r.ending_round(index=round_index, state=self.state) + + raise ResolutionTooDeep(max_rounds) + + +def _has_route_to_root(criteria, key, all_keys, connected): + if key in connected: + return True + if key not in criteria: + return False + for p in criteria[key].iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey in connected: + connected.add(key) + return True + if _has_route_to_root(criteria, pkey, all_keys, connected): + connected.add(key) + return True + return False + + +Result = collections.namedtuple("Result", "mapping graph criteria") + + +def _build_result(state): + mapping = state.mapping + all_keys = {id(v): k for k, v in mapping.items()} + all_keys[id(None)] = None + + graph = DirectedGraph() + graph.add(None) # Sentinel as root dependencies' parent. + + connected = {None} + for key, criterion in state.criteria.items(): + if not _has_route_to_root(state.criteria, key, all_keys, connected): + continue + if key not in graph: + graph.add(key) + for p in criterion.iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey not in graph: + graph.add(pkey) + graph.connect(pkey, key) + + return Result( + mapping={k: v for k, v in mapping.items() if k in connected}, + graph=graph, + criteria=state.criteria, + ) + + +class Resolver(AbstractResolver): + """The thing that performs the actual resolution work.""" + + base_exception = ResolverException + + def resolve(self, requirements, max_rounds=100): + """Take a collection of constraints, spit out the resolution result. + + The return value is a representation to the final resolution result. It + is a tuple subclass with three public members: + + * `mapping`: A dict of resolved candidates. Each key is an identifier + of a requirement (as returned by the provider's `identify` method), + and the value is the resolved candidate. + * `graph`: A `DirectedGraph` instance representing the dependency tree. + The vertices are keys of `mapping`, and each edge represents *why* + a particular package is included. A special vertex `None` is + included to represent parents of user-supplied requirements. + * `criteria`: A dict of "criteria" that hold detailed information on + how edges in the graph are derived. Each key is an identifier of a + requirement, and the value is a `Criterion` instance. + + The following exceptions may be raised if a resolution cannot be found: + + * `ResolutionImpossible`: A resolution cannot be found for the given + combination of requirements. The `causes` attribute of the + exception is a list of (requirement, parent), giving the + requirements that could not be satisfied. + * `ResolutionTooDeep`: The dependency tree is too deeply nested and + the resolver gave up. This is usually caused by a circular + dependency, but you can try to resolve this by increasing the + `max_rounds` argument. + """ + resolution = Resolution(self.provider, self.reporter) + state = resolution.resolve(requirements, max_rounds=max_rounds) + return _build_result(state) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py new file mode 100644 index 0000000..359a34f --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py @@ -0,0 +1,170 @@ +import itertools + +from .compat import collections_abc + + +class DirectedGraph(object): + """A graph structure with directed edges.""" + + def __init__(self): + self._vertices = set() + self._forwards = {} # -> Set[] + self._backwards = {} # -> Set[] + + def __iter__(self): + return iter(self._vertices) + + def __len__(self): + return len(self._vertices) + + def __contains__(self, key): + return key in self._vertices + + def copy(self): + """Return a shallow copy of this graph.""" + other = DirectedGraph() + other._vertices = set(self._vertices) + other._forwards = {k: set(v) for k, v in self._forwards.items()} + other._backwards = {k: set(v) for k, v in self._backwards.items()} + return other + + def add(self, key): + """Add a new vertex to the graph.""" + if key in self._vertices: + raise ValueError("vertex exists") + self._vertices.add(key) + self._forwards[key] = set() + self._backwards[key] = set() + + def remove(self, key): + """Remove a vertex from the graph, disconnecting all edges from/to it.""" + self._vertices.remove(key) + for f in self._forwards.pop(key): + self._backwards[f].remove(key) + for t in self._backwards.pop(key): + self._forwards[t].remove(key) + + def connected(self, f, t): + return f in self._backwards[t] and t in self._forwards[f] + + def connect(self, f, t): + """Connect two existing vertices. + + Nothing happens if the vertices are already connected. + """ + if t not in self._vertices: + raise KeyError(t) + self._forwards[f].add(t) + self._backwards[t].add(f) + + def iter_edges(self): + for f, children in self._forwards.items(): + for t in children: + yield f, t + + def iter_children(self, key): + return iter(self._forwards[key]) + + def iter_parents(self, key): + return iter(self._backwards[key]) + + +class IteratorMapping(collections_abc.Mapping): + def __init__(self, mapping, accessor, appends=None): + self._mapping = mapping + self._accessor = accessor + self._appends = appends or {} + + def __repr__(self): + return "IteratorMapping({!r}, {!r}, {!r})".format( + self._mapping, + self._accessor, + self._appends, + ) + + def __bool__(self): + return bool(self._mapping or self._appends) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __contains__(self, key): + return key in self._mapping or key in self._appends + + def __getitem__(self, k): + try: + v = self._mapping[k] + except KeyError: + return iter(self._appends[k]) + return itertools.chain(self._accessor(v), self._appends.get(k, ())) + + def __iter__(self): + more = (k for k in self._appends if k not in self._mapping) + return itertools.chain(self._mapping, more) + + def __len__(self): + more = sum(1 for k in self._appends if k not in self._mapping) + return len(self._mapping) + more + + +class _FactoryIterableView(object): + """Wrap an iterator factory returned by `find_matches()`. + + Calling `iter()` on this class would invoke the underlying iterator + factory, making it a "collection with ordering" that can be iterated + through multiple times, but lacks random access methods presented in + built-in Python sequence types. + """ + + def __init__(self, factory): + self._factory = factory + self._iterable = None + + def __repr__(self): + return "{}({})".format(type(self).__name__, list(self)) + + def __bool__(self): + try: + next(iter(self)) + except StopIteration: + return False + return True + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + iterable = ( + self._factory() if self._iterable is None else self._iterable + ) + self._iterable, current = itertools.tee(iterable) + return current + + +class _SequenceIterableView(object): + """Wrap an iterable returned by find_matches(). + + This is essentially just a proxy to the underlying sequence that provides + the same interface as `_FactoryIterableView`. + """ + + def __init__(self, sequence): + self._sequence = sequence + + def __repr__(self): + return "{}({})".format(type(self).__name__, self._sequence) + + def __bool__(self): + return bool(self._sequence) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + return iter(self._sequence) + + +def build_iter_view(matches): + """Build an iterable view from the value returned by `find_matches()`.""" + if callable(matches): + return _FactoryIterableView(matches) + if not isinstance(matches, collections_abc.Sequence): + matches = list(matches) + return _SequenceIterableView(matches) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py new file mode 100644 index 0000000..73f58d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py @@ -0,0 +1,177 @@ +"""Rich text and beautiful formatting in the terminal.""" + +import os +from typing import IO, TYPE_CHECKING, Any, Callable, Optional, Union + +from ._extension import load_ipython_extension # noqa: F401 + +__all__ = ["get_console", "reconfigure", "print", "inspect", "print_json"] + +if TYPE_CHECKING: + from .console import Console + +# Global console used by alternative print +_console: Optional["Console"] = None + +try: + _IMPORT_CWD = os.path.abspath(os.getcwd()) +except FileNotFoundError: + # Can happen if the cwd has been deleted + _IMPORT_CWD = "" + + +def get_console() -> "Console": + """Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console, + and hasn't been explicitly given one. + + Returns: + Console: A console instance. + """ + global _console + if _console is None: + from .console import Console + + _console = Console() + + return _console + + +def reconfigure(*args: Any, **kwargs: Any) -> None: + """Reconfigures the global console by replacing it with another. + + Args: + *args (Any): Positional arguments for the replacement :class:`~rich.console.Console`. + **kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`. + """ + from pip._vendor.rich.console import Console + + new_console = Console(*args, **kwargs) + _console = get_console() + _console.__dict__ = new_console.__dict__ + + +def print( + *objects: Any, + sep: str = " ", + end: str = "\n", + file: Optional[IO[str]] = None, + flush: bool = False, +) -> None: + r"""Print object(s) supplied via positional arguments. + This function has an identical signature to the built-in print. + For more advanced features, see the :class:`~rich.console.Console` class. + + Args: + sep (str, optional): Separator between printed objects. Defaults to " ". + end (str, optional): Character to write at end of output. Defaults to "\\n". + file (IO[str], optional): File to write to, or None for stdout. Defaults to None. + flush (bool, optional): Has no effect as Rich always flushes output. Defaults to False. + + """ + from .console import Console + + write_console = get_console() if file is None else Console(file=file) + return write_console.print(*objects, sep=sep, end=end) + + +def print_json( + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = False, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, +) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (str): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (int, optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + + get_console().print_json( + json, + data=data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + + +def inspect( + obj: Any, + *, + console: Optional["Console"] = None, + title: Optional[str] = None, + help: bool = False, + methods: bool = False, + docs: bool = True, + private: bool = False, + dunder: bool = False, + sort: bool = True, + all: bool = False, + value: bool = True, +) -> None: + """Inspect any Python object. + + * inspect() to see summarized info. + * inspect(, methods=True) to see methods. + * inspect(, help=True) to see full (non-abbreviated) help. + * inspect(, private=True) to see private attributes (single underscore). + * inspect(, dunder=True) to see attributes beginning with double underscore. + * inspect(, all=True) to see all attributes. + + Args: + obj (Any): An object to inspect. + title (str, optional): Title to display over inspect result, or None use type. Defaults to None. + help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. + methods (bool, optional): Enable inspection of callables. Defaults to False. + docs (bool, optional): Also render doc strings. Defaults to True. + private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. + dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. + sort (bool, optional): Sort attributes alphabetically. Defaults to True. + all (bool, optional): Show all attributes. Defaults to False. + value (bool, optional): Pretty print value. Defaults to True. + """ + _console = console or get_console() + from pip._vendor.rich._inspect import Inspect + + # Special case for inspect(inspect) + is_inspect = obj is inspect + + _inspect = Inspect( + obj, + title=title, + help=is_inspect or help, + methods=is_inspect or methods, + docs=is_inspect or docs, + private=private, + dunder=dunder, + sort=sort, + all=all, + value=value, + ) + _console.print(_inspect) + + +if __name__ == "__main__": # pragma: no cover + print("Hello, **World**") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py new file mode 100644 index 0000000..270629f --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py @@ -0,0 +1,274 @@ +import colorsys +import io +from time import process_time + +from pip._vendor.rich import box +from pip._vendor.rich.color import Color +from pip._vendor.rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult +from pip._vendor.rich.markdown import Markdown +from pip._vendor.rich.measure import Measurement +from pip._vendor.rich.pretty import Pretty +from pip._vendor.rich.segment import Segment +from pip._vendor.rich.style import Style +from pip._vendor.rich.syntax import Syntax +from pip._vendor.rich.table import Table +from pip._vendor.rich.text import Text + + +class ColorBox: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + for y in range(0, 5): + for x in range(options.max_width): + h = x / options.max_width + l = 0.1 + ((y / 5) * 0.7) + r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0) + r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0) + bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255) + color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255) + yield Segment("▄", Style(color=color, bgcolor=bgcolor)) + yield Segment.line() + + def __rich_measure__( + self, console: "Console", options: ConsoleOptions + ) -> Measurement: + return Measurement(1, options.max_width) + + +def make_test_card() -> Table: + """Get a renderable that demonstrates a number of features.""" + table = Table.grid(padding=1, pad_edge=True) + table.title = "Rich features" + table.add_column("Feature", no_wrap=True, justify="center", style="bold red") + table.add_column("Demonstration") + + color_table = Table( + box=None, + expand=False, + show_header=False, + show_edge=False, + pad_edge=False, + ) + color_table.add_row( + ( + "✓ [bold green]4-bit color[/]\n" + "✓ [bold blue]8-bit color[/]\n" + "✓ [bold magenta]Truecolor (16.7 million)[/]\n" + "✓ [bold yellow]Dumb terminals[/]\n" + "✓ [bold cyan]Automatic color conversion" + ), + ColorBox(), + ) + + table.add_row("Colors", color_table) + + table.add_row( + "Styles", + "All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].", + ) + + lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus." + lorem_table = Table.grid(padding=1, collapse_padding=True) + lorem_table.pad_edge = False + lorem_table.add_row( + Text(lorem, justify="left", style="green"), + Text(lorem, justify="center", style="yellow"), + Text(lorem, justify="right", style="blue"), + Text(lorem, justify="full", style="red"), + ) + table.add_row( + "Text", + Group( + Text.from_markup( + """Word wrap text. Justify [green]left[/], [yellow]center[/], [blue]right[/] or [red]full[/].\n""" + ), + lorem_table, + ), + ) + + def comparison(renderable1: RenderableType, renderable2: RenderableType) -> Table: + table = Table(show_header=False, pad_edge=False, box=None, expand=True) + table.add_column("1", ratio=1) + table.add_column("2", ratio=1) + table.add_row(renderable1, renderable2) + return table + + table.add_row( + "Asian\nlanguage\nsupport", + ":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다", + ) + + markup_example = ( + "[bold magenta]Rich[/] supports a simple [i]bbcode[/i]-like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! " + ":+1: :apple: :ant: :bear: :baguette_bread: :bus: " + ) + table.add_row("Markup", markup_example) + + example_table = Table( + show_edge=False, + show_header=True, + expand=False, + row_styles=["none", "dim"], + box=box.SIMPLE, + ) + example_table.add_column("[green]Date", style="green", no_wrap=True) + example_table.add_column("[blue]Title", style="blue") + example_table.add_column( + "[cyan]Production Budget", + style="cyan", + justify="right", + no_wrap=True, + ) + example_table.add_column( + "[magenta]Box Office", + style="magenta", + justify="right", + no_wrap=True, + ) + example_table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$275,000,000", + "$375,126,118", + ) + example_table.add_row( + "May 25, 2018", + "[b]Solo[/]: A Star Wars Story", + "$275,000,000", + "$393,151,347", + ) + example_table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", + ) + example_table.add_row( + "May 19, 1999", + "Star Wars Ep. [b]I[/b]: [i]The phantom Menace", + "$115,000,000", + "$1,027,044,677", + ) + + table.add_row("Tables", example_table) + + code = '''\ +def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''' + + pretty_data = { + "foo": [ + 3.1427, + ( + "Paul Atreides", + "Vladimir Harkonnen", + "Thufir Hawat", + ), + ], + "atomic": (False, True, None), + } + table.add_row( + "Syntax\nhighlighting\n&\npretty\nprinting", + comparison( + Syntax(code, "python3", line_numbers=True, indent_guides=True), + Pretty(pretty_data, indent_guides=True), + ), + ) + + markdown_example = """\ +# Markdown + +Supports much of the *markdown* __syntax__! + +- Headers +- Basic formatting: **bold**, *italic*, `code` +- Block quotes +- Lists, and more... + """ + table.add_row( + "Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example)) + ) + + table.add_row( + "+more!", + """Progress bars, columns, styled logging handler, tracebacks, etc...""", + ) + return table + + +if __name__ == "__main__": # pragma: no cover + + console = Console( + file=io.StringIO(), + force_terminal=True, + ) + test_card = make_test_card() + + # Print once to warm cache + start = process_time() + console.print(test_card) + pre_cache_taken = round((process_time() - start) * 1000.0, 1) + + console.file = io.StringIO() + + start = process_time() + console.print(test_card) + taken = round((process_time() - start) * 1000.0, 1) + + c = Console(record=True) + c.print(test_card) + + print(f"rendered in {pre_cache_taken}ms (cold cache)") + print(f"rendered in {taken}ms (warm cache)") + + from pip._vendor.rich.panel import Panel + + console = Console() + + sponsor_message = Table.grid(padding=1) + sponsor_message.add_column(style="green", justify="right") + sponsor_message.add_column(no_wrap=True) + + sponsor_message.add_row( + "Textualize", + "[u blue link=https://github.com/textualize]https://github.com/textualize", + ) + sponsor_message.add_row( + "Twitter", + "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan", + ) + + intro_message = Text.from_markup( + """\ +We hope you enjoy using Rich! + +Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/] + +- Will McGugan""" + ) + + message = Table.grid(padding=2) + message.add_column() + message.add_column(no_wrap=True) + message.add_row(intro_message, sponsor_message) + + console.print( + Panel.fit( + message, + box=box.ROUNDED, + padding=(1, 2), + title="[b red]Thanks for trying out Rich!", + border_style="bright_blue", + ), + justify="center", + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py new file mode 100644 index 0000000..36286df --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py @@ -0,0 +1,451 @@ +# Auto generated by make_terminal_widths.py + +CELL_WIDTHS = [ + (0, 0, 0), + (1, 31, -1), + (127, 159, -1), + (768, 879, 0), + (1155, 1161, 0), + (1425, 1469, 0), + (1471, 1471, 0), + (1473, 1474, 0), + (1476, 1477, 0), + (1479, 1479, 0), + (1552, 1562, 0), + (1611, 1631, 0), + (1648, 1648, 0), + (1750, 1756, 0), + (1759, 1764, 0), + (1767, 1768, 0), + (1770, 1773, 0), + (1809, 1809, 0), + (1840, 1866, 0), + (1958, 1968, 0), + (2027, 2035, 0), + (2045, 2045, 0), + (2070, 2073, 0), + (2075, 2083, 0), + (2085, 2087, 0), + (2089, 2093, 0), + (2137, 2139, 0), + (2259, 2273, 0), + (2275, 2306, 0), + (2362, 2362, 0), + (2364, 2364, 0), + (2369, 2376, 0), + (2381, 2381, 0), + (2385, 2391, 0), + (2402, 2403, 0), + (2433, 2433, 0), + (2492, 2492, 0), + (2497, 2500, 0), + (2509, 2509, 0), + (2530, 2531, 0), + (2558, 2558, 0), + (2561, 2562, 0), + (2620, 2620, 0), + (2625, 2626, 0), + (2631, 2632, 0), + (2635, 2637, 0), + (2641, 2641, 0), + (2672, 2673, 0), + (2677, 2677, 0), + (2689, 2690, 0), + (2748, 2748, 0), + (2753, 2757, 0), + (2759, 2760, 0), + (2765, 2765, 0), + (2786, 2787, 0), + (2810, 2815, 0), + (2817, 2817, 0), + (2876, 2876, 0), + (2879, 2879, 0), + (2881, 2884, 0), + (2893, 2893, 0), + (2901, 2902, 0), + (2914, 2915, 0), + (2946, 2946, 0), + (3008, 3008, 0), + (3021, 3021, 0), + (3072, 3072, 0), + (3076, 3076, 0), + (3134, 3136, 0), + (3142, 3144, 0), + (3146, 3149, 0), + (3157, 3158, 0), + (3170, 3171, 0), + (3201, 3201, 0), + (3260, 3260, 0), + (3263, 3263, 0), + (3270, 3270, 0), + (3276, 3277, 0), + (3298, 3299, 0), + (3328, 3329, 0), + (3387, 3388, 0), + (3393, 3396, 0), + (3405, 3405, 0), + (3426, 3427, 0), + (3457, 3457, 0), + (3530, 3530, 0), + (3538, 3540, 0), + (3542, 3542, 0), + (3633, 3633, 0), + (3636, 3642, 0), + (3655, 3662, 0), + (3761, 3761, 0), + (3764, 3772, 0), + (3784, 3789, 0), + (3864, 3865, 0), + (3893, 3893, 0), + (3895, 3895, 0), + (3897, 3897, 0), + (3953, 3966, 0), + (3968, 3972, 0), + (3974, 3975, 0), + (3981, 3991, 0), + (3993, 4028, 0), + (4038, 4038, 0), + (4141, 4144, 0), + (4146, 4151, 0), + (4153, 4154, 0), + (4157, 4158, 0), + (4184, 4185, 0), + (4190, 4192, 0), + (4209, 4212, 0), + (4226, 4226, 0), + (4229, 4230, 0), + (4237, 4237, 0), + (4253, 4253, 0), + (4352, 4447, 2), + (4957, 4959, 0), + (5906, 5908, 0), + (5938, 5940, 0), + (5970, 5971, 0), + (6002, 6003, 0), + (6068, 6069, 0), + (6071, 6077, 0), + (6086, 6086, 0), + (6089, 6099, 0), + (6109, 6109, 0), + (6155, 6157, 0), + (6277, 6278, 0), + (6313, 6313, 0), + (6432, 6434, 0), + (6439, 6440, 0), + (6450, 6450, 0), + (6457, 6459, 0), + (6679, 6680, 0), + (6683, 6683, 0), + (6742, 6742, 0), + (6744, 6750, 0), + (6752, 6752, 0), + (6754, 6754, 0), + (6757, 6764, 0), + (6771, 6780, 0), + (6783, 6783, 0), + (6832, 6848, 0), + (6912, 6915, 0), + (6964, 6964, 0), + (6966, 6970, 0), + (6972, 6972, 0), + (6978, 6978, 0), + (7019, 7027, 0), + (7040, 7041, 0), + (7074, 7077, 0), + (7080, 7081, 0), + (7083, 7085, 0), + (7142, 7142, 0), + (7144, 7145, 0), + (7149, 7149, 0), + (7151, 7153, 0), + (7212, 7219, 0), + (7222, 7223, 0), + (7376, 7378, 0), + (7380, 7392, 0), + (7394, 7400, 0), + (7405, 7405, 0), + (7412, 7412, 0), + (7416, 7417, 0), + (7616, 7673, 0), + (7675, 7679, 0), + (8203, 8207, 0), + (8232, 8238, 0), + (8288, 8291, 0), + (8400, 8432, 0), + (8986, 8987, 2), + (9001, 9002, 2), + (9193, 9196, 2), + (9200, 9200, 2), + (9203, 9203, 2), + (9725, 9726, 2), + (9748, 9749, 2), + (9800, 9811, 2), + (9855, 9855, 2), + (9875, 9875, 2), + (9889, 9889, 2), + (9898, 9899, 2), + (9917, 9918, 2), + (9924, 9925, 2), + (9934, 9934, 2), + (9940, 9940, 2), + (9962, 9962, 2), + (9970, 9971, 2), + (9973, 9973, 2), + (9978, 9978, 2), + (9981, 9981, 2), + (9989, 9989, 2), + (9994, 9995, 2), + (10024, 10024, 2), + (10060, 10060, 2), + (10062, 10062, 2), + (10067, 10069, 2), + (10071, 10071, 2), + (10133, 10135, 2), + (10160, 10160, 2), + (10175, 10175, 2), + (11035, 11036, 2), + (11088, 11088, 2), + (11093, 11093, 2), + (11503, 11505, 0), + (11647, 11647, 0), + (11744, 11775, 0), + (11904, 11929, 2), + (11931, 12019, 2), + (12032, 12245, 2), + (12272, 12283, 2), + (12288, 12329, 2), + (12330, 12333, 0), + (12334, 12350, 2), + (12353, 12438, 2), + (12441, 12442, 0), + (12443, 12543, 2), + (12549, 12591, 2), + (12593, 12686, 2), + (12688, 12771, 2), + (12784, 12830, 2), + (12832, 12871, 2), + (12880, 19903, 2), + (19968, 42124, 2), + (42128, 42182, 2), + (42607, 42610, 0), + (42612, 42621, 0), + (42654, 42655, 0), + (42736, 42737, 0), + (43010, 43010, 0), + (43014, 43014, 0), + (43019, 43019, 0), + (43045, 43046, 0), + (43052, 43052, 0), + (43204, 43205, 0), + (43232, 43249, 0), + (43263, 43263, 0), + (43302, 43309, 0), + (43335, 43345, 0), + (43360, 43388, 2), + (43392, 43394, 0), + (43443, 43443, 0), + (43446, 43449, 0), + (43452, 43453, 0), + (43493, 43493, 0), + (43561, 43566, 0), + (43569, 43570, 0), + (43573, 43574, 0), + (43587, 43587, 0), + (43596, 43596, 0), + (43644, 43644, 0), + (43696, 43696, 0), + (43698, 43700, 0), + (43703, 43704, 0), + (43710, 43711, 0), + (43713, 43713, 0), + (43756, 43757, 0), + (43766, 43766, 0), + (44005, 44005, 0), + (44008, 44008, 0), + (44013, 44013, 0), + (44032, 55203, 2), + (63744, 64255, 2), + (64286, 64286, 0), + (65024, 65039, 0), + (65040, 65049, 2), + (65056, 65071, 0), + (65072, 65106, 2), + (65108, 65126, 2), + (65128, 65131, 2), + (65281, 65376, 2), + (65504, 65510, 2), + (66045, 66045, 0), + (66272, 66272, 0), + (66422, 66426, 0), + (68097, 68099, 0), + (68101, 68102, 0), + (68108, 68111, 0), + (68152, 68154, 0), + (68159, 68159, 0), + (68325, 68326, 0), + (68900, 68903, 0), + (69291, 69292, 0), + (69446, 69456, 0), + (69633, 69633, 0), + (69688, 69702, 0), + (69759, 69761, 0), + (69811, 69814, 0), + (69817, 69818, 0), + (69888, 69890, 0), + (69927, 69931, 0), + (69933, 69940, 0), + (70003, 70003, 0), + (70016, 70017, 0), + (70070, 70078, 0), + (70089, 70092, 0), + (70095, 70095, 0), + (70191, 70193, 0), + (70196, 70196, 0), + (70198, 70199, 0), + (70206, 70206, 0), + (70367, 70367, 0), + (70371, 70378, 0), + (70400, 70401, 0), + (70459, 70460, 0), + (70464, 70464, 0), + (70502, 70508, 0), + (70512, 70516, 0), + (70712, 70719, 0), + (70722, 70724, 0), + (70726, 70726, 0), + (70750, 70750, 0), + (70835, 70840, 0), + (70842, 70842, 0), + (70847, 70848, 0), + (70850, 70851, 0), + (71090, 71093, 0), + (71100, 71101, 0), + (71103, 71104, 0), + (71132, 71133, 0), + (71219, 71226, 0), + (71229, 71229, 0), + (71231, 71232, 0), + (71339, 71339, 0), + (71341, 71341, 0), + (71344, 71349, 0), + (71351, 71351, 0), + (71453, 71455, 0), + (71458, 71461, 0), + (71463, 71467, 0), + (71727, 71735, 0), + (71737, 71738, 0), + (71995, 71996, 0), + (71998, 71998, 0), + (72003, 72003, 0), + (72148, 72151, 0), + (72154, 72155, 0), + (72160, 72160, 0), + (72193, 72202, 0), + (72243, 72248, 0), + (72251, 72254, 0), + (72263, 72263, 0), + (72273, 72278, 0), + (72281, 72283, 0), + (72330, 72342, 0), + (72344, 72345, 0), + (72752, 72758, 0), + (72760, 72765, 0), + (72767, 72767, 0), + (72850, 72871, 0), + (72874, 72880, 0), + (72882, 72883, 0), + (72885, 72886, 0), + (73009, 73014, 0), + (73018, 73018, 0), + (73020, 73021, 0), + (73023, 73029, 0), + (73031, 73031, 0), + (73104, 73105, 0), + (73109, 73109, 0), + (73111, 73111, 0), + (73459, 73460, 0), + (92912, 92916, 0), + (92976, 92982, 0), + (94031, 94031, 0), + (94095, 94098, 0), + (94176, 94179, 2), + (94180, 94180, 0), + (94192, 94193, 2), + (94208, 100343, 2), + (100352, 101589, 2), + (101632, 101640, 2), + (110592, 110878, 2), + (110928, 110930, 2), + (110948, 110951, 2), + (110960, 111355, 2), + (113821, 113822, 0), + (119143, 119145, 0), + (119163, 119170, 0), + (119173, 119179, 0), + (119210, 119213, 0), + (119362, 119364, 0), + (121344, 121398, 0), + (121403, 121452, 0), + (121461, 121461, 0), + (121476, 121476, 0), + (121499, 121503, 0), + (121505, 121519, 0), + (122880, 122886, 0), + (122888, 122904, 0), + (122907, 122913, 0), + (122915, 122916, 0), + (122918, 122922, 0), + (123184, 123190, 0), + (123628, 123631, 0), + (125136, 125142, 0), + (125252, 125258, 0), + (126980, 126980, 2), + (127183, 127183, 2), + (127374, 127374, 2), + (127377, 127386, 2), + (127488, 127490, 2), + (127504, 127547, 2), + (127552, 127560, 2), + (127568, 127569, 2), + (127584, 127589, 2), + (127744, 127776, 2), + (127789, 127797, 2), + (127799, 127868, 2), + (127870, 127891, 2), + (127904, 127946, 2), + (127951, 127955, 2), + (127968, 127984, 2), + (127988, 127988, 2), + (127992, 128062, 2), + (128064, 128064, 2), + (128066, 128252, 2), + (128255, 128317, 2), + (128331, 128334, 2), + (128336, 128359, 2), + (128378, 128378, 2), + (128405, 128406, 2), + (128420, 128420, 2), + (128507, 128591, 2), + (128640, 128709, 2), + (128716, 128716, 2), + (128720, 128722, 2), + (128725, 128727, 2), + (128747, 128748, 2), + (128756, 128764, 2), + (128992, 129003, 2), + (129292, 129338, 2), + (129340, 129349, 2), + (129351, 129400, 2), + (129402, 129483, 2), + (129485, 129535, 2), + (129648, 129652, 2), + (129656, 129658, 2), + (129664, 129670, 2), + (129680, 129704, 2), + (129712, 129718, 2), + (129728, 129730, 2), + (129744, 129750, 2), + (131072, 196605, 2), + (196608, 262141, 2), + (917760, 917999, 0), +] diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py new file mode 100644 index 0000000..1f2877b --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py @@ -0,0 +1,3610 @@ +EMOJI = { + "1st_place_medal": "🥇", + "2nd_place_medal": "🥈", + "3rd_place_medal": "🥉", + "ab_button_(blood_type)": "🆎", + "atm_sign": "🏧", + "a_button_(blood_type)": "🅰", + "afghanistan": "🇦🇫", + "albania": "🇦🇱", + "algeria": "🇩🇿", + "american_samoa": "🇦🇸", + "andorra": "🇦🇩", + "angola": "🇦🇴", + "anguilla": "🇦🇮", + "antarctica": "🇦🇶", + "antigua_&_barbuda": "🇦🇬", + "aquarius": "♒", + "argentina": "🇦🇷", + "aries": "♈", + "armenia": "🇦🇲", + "aruba": "🇦🇼", + "ascension_island": "🇦🇨", + "australia": "🇦🇺", + "austria": "🇦🇹", + "azerbaijan": "🇦🇿", + "back_arrow": "🔙", + "b_button_(blood_type)": "🅱", + "bahamas": "🇧🇸", + "bahrain": "🇧🇭", + "bangladesh": "🇧🇩", + "barbados": "🇧🇧", + "belarus": "🇧🇾", + "belgium": "🇧🇪", + "belize": "🇧🇿", + "benin": "🇧🇯", + "bermuda": "🇧🇲", + "bhutan": "🇧🇹", + "bolivia": "🇧🇴", + "bosnia_&_herzegovina": "🇧🇦", + "botswana": "🇧🇼", + "bouvet_island": "🇧🇻", + "brazil": "🇧🇷", + "british_indian_ocean_territory": "🇮🇴", + "british_virgin_islands": "🇻🇬", + "brunei": "🇧🇳", + "bulgaria": "🇧🇬", + "burkina_faso": "🇧🇫", + "burundi": "🇧🇮", + "cl_button": "🆑", + "cool_button": "🆒", + "cambodia": "🇰🇭", + "cameroon": "🇨🇲", + "canada": "🇨🇦", + "canary_islands": "🇮🇨", + "cancer": "♋", + "cape_verde": "🇨🇻", + "capricorn": "♑", + "caribbean_netherlands": "🇧🇶", + "cayman_islands": "🇰🇾", + "central_african_republic": "🇨🇫", + "ceuta_&_melilla": "🇪🇦", + "chad": "🇹🇩", + "chile": "🇨🇱", + "china": "🇨🇳", + "christmas_island": "🇨🇽", + "christmas_tree": "🎄", + "clipperton_island": "🇨🇵", + "cocos_(keeling)_islands": "🇨🇨", + "colombia": "🇨🇴", + "comoros": "🇰🇲", + "congo_-_brazzaville": "🇨🇬", + "congo_-_kinshasa": "🇨🇩", + "cook_islands": "🇨🇰", + "costa_rica": "🇨🇷", + "croatia": "🇭🇷", + "cuba": "🇨🇺", + "curaçao": "🇨🇼", + "cyprus": "🇨🇾", + "czechia": "🇨🇿", + "côte_d’ivoire": "🇨🇮", + "denmark": "🇩🇰", + "diego_garcia": "🇩🇬", + "djibouti": "🇩🇯", + "dominica": "🇩🇲", + "dominican_republic": "🇩🇴", + "end_arrow": "🔚", + "ecuador": "🇪🇨", + "egypt": "🇪🇬", + "el_salvador": "🇸🇻", + "england": "🏴\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", + "equatorial_guinea": "🇬🇶", + "eritrea": "🇪🇷", + "estonia": "🇪🇪", + "ethiopia": "🇪🇹", + "european_union": "🇪🇺", + "free_button": "🆓", + "falkland_islands": "🇫🇰", + "faroe_islands": "🇫🇴", + "fiji": "🇫🇯", + "finland": "🇫🇮", + "france": "🇫🇷", + "french_guiana": "🇬🇫", + "french_polynesia": "🇵🇫", + "french_southern_territories": "🇹🇫", + "gabon": "🇬🇦", + "gambia": "🇬🇲", + "gemini": "♊", + "georgia": "🇬🇪", + "germany": "🇩🇪", + "ghana": "🇬🇭", + "gibraltar": "🇬🇮", + "greece": "🇬🇷", + "greenland": "🇬🇱", + "grenada": "🇬🇩", + "guadeloupe": "🇬🇵", + "guam": "🇬🇺", + "guatemala": "🇬🇹", + "guernsey": "🇬🇬", + "guinea": "🇬🇳", + "guinea-bissau": "🇬🇼", + "guyana": "🇬🇾", + "haiti": "🇭🇹", + "heard_&_mcdonald_islands": "🇭🇲", + "honduras": "🇭🇳", + "hong_kong_sar_china": "🇭🇰", + "hungary": "🇭🇺", + "id_button": "🆔", + "iceland": "🇮🇸", + "india": "🇮🇳", + "indonesia": "🇮🇩", + "iran": "🇮🇷", + "iraq": "🇮🇶", + "ireland": "🇮🇪", + "isle_of_man": "🇮🇲", + "israel": "🇮🇱", + "italy": "🇮🇹", + "jamaica": "🇯🇲", + "japan": "🗾", + "japanese_acceptable_button": "🉑", + "japanese_application_button": "🈸", + "japanese_bargain_button": "🉐", + "japanese_castle": "🏯", + "japanese_congratulations_button": "㊗", + "japanese_discount_button": "🈹", + "japanese_dolls": "🎎", + "japanese_free_of_charge_button": "🈚", + "japanese_here_button": "🈁", + "japanese_monthly_amount_button": "🈷", + "japanese_no_vacancy_button": "🈵", + "japanese_not_free_of_charge_button": "🈶", + "japanese_open_for_business_button": "🈺", + "japanese_passing_grade_button": "🈴", + "japanese_post_office": "🏣", + "japanese_prohibited_button": "🈲", + "japanese_reserved_button": "🈯", + "japanese_secret_button": "㊙", + "japanese_service_charge_button": "🈂", + "japanese_symbol_for_beginner": "🔰", + "japanese_vacancy_button": "🈳", + "jersey": "🇯🇪", + "jordan": "🇯🇴", + "kazakhstan": "🇰🇿", + "kenya": "🇰🇪", + "kiribati": "🇰🇮", + "kosovo": "🇽🇰", + "kuwait": "🇰🇼", + "kyrgyzstan": "🇰🇬", + "laos": "🇱🇦", + "latvia": "🇱🇻", + "lebanon": "🇱🇧", + "leo": "♌", + "lesotho": "🇱🇸", + "liberia": "🇱🇷", + "libra": "♎", + "libya": "🇱🇾", + "liechtenstein": "🇱🇮", + "lithuania": "🇱🇹", + "luxembourg": "🇱🇺", + "macau_sar_china": "🇲🇴", + "macedonia": "🇲🇰", + "madagascar": "🇲🇬", + "malawi": "🇲🇼", + "malaysia": "🇲🇾", + "maldives": "🇲🇻", + "mali": "🇲🇱", + "malta": "🇲🇹", + "marshall_islands": "🇲🇭", + "martinique": "🇲🇶", + "mauritania": "🇲🇷", + "mauritius": "🇲🇺", + "mayotte": "🇾🇹", + "mexico": "🇲🇽", + "micronesia": "🇫🇲", + "moldova": "🇲🇩", + "monaco": "🇲🇨", + "mongolia": "🇲🇳", + "montenegro": "🇲🇪", + "montserrat": "🇲🇸", + "morocco": "🇲🇦", + "mozambique": "🇲🇿", + "mrs._claus": "🤶", + "mrs._claus_dark_skin_tone": "🤶🏿", + "mrs._claus_light_skin_tone": "🤶🏻", + "mrs._claus_medium-dark_skin_tone": "🤶🏾", + "mrs._claus_medium-light_skin_tone": "🤶🏼", + "mrs._claus_medium_skin_tone": "🤶🏽", + "myanmar_(burma)": "🇲🇲", + "new_button": "🆕", + "ng_button": "🆖", + "namibia": "🇳🇦", + "nauru": "🇳🇷", + "nepal": "🇳🇵", + "netherlands": "🇳🇱", + "new_caledonia": "🇳🇨", + "new_zealand": "🇳🇿", + "nicaragua": "🇳🇮", + "niger": "🇳🇪", + "nigeria": "🇳🇬", + "niue": "🇳🇺", + "norfolk_island": "🇳🇫", + "north_korea": "🇰🇵", + "northern_mariana_islands": "🇲🇵", + "norway": "🇳🇴", + "ok_button": "🆗", + "ok_hand": "👌", + "ok_hand_dark_skin_tone": "👌🏿", + "ok_hand_light_skin_tone": "👌🏻", + "ok_hand_medium-dark_skin_tone": "👌🏾", + "ok_hand_medium-light_skin_tone": "👌🏼", + "ok_hand_medium_skin_tone": "👌🏽", + "on!_arrow": "🔛", + "o_button_(blood_type)": "🅾", + "oman": "🇴🇲", + "ophiuchus": "⛎", + "p_button": "🅿", + "pakistan": "🇵🇰", + "palau": "🇵🇼", + "palestinian_territories": "🇵🇸", + "panama": "🇵🇦", + "papua_new_guinea": "🇵🇬", + "paraguay": "🇵🇾", + "peru": "🇵🇪", + "philippines": "🇵🇭", + "pisces": "♓", + "pitcairn_islands": "🇵🇳", + "poland": "🇵🇱", + "portugal": "🇵🇹", + "puerto_rico": "🇵🇷", + "qatar": "🇶🇦", + "romania": "🇷🇴", + "russia": "🇷🇺", + "rwanda": "🇷🇼", + "réunion": "🇷🇪", + "soon_arrow": "🔜", + "sos_button": "🆘", + "sagittarius": "♐", + "samoa": "🇼🇸", + "san_marino": "🇸🇲", + "santa_claus": "🎅", + "santa_claus_dark_skin_tone": "🎅🏿", + "santa_claus_light_skin_tone": "🎅🏻", + "santa_claus_medium-dark_skin_tone": "🎅🏾", + "santa_claus_medium-light_skin_tone": "🎅🏼", + "santa_claus_medium_skin_tone": "🎅🏽", + "saudi_arabia": "🇸🇦", + "scorpio": "♏", + "scotland": "🏴\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f", + "senegal": "🇸🇳", + "serbia": "🇷🇸", + "seychelles": "🇸🇨", + "sierra_leone": "🇸🇱", + "singapore": "🇸🇬", + "sint_maarten": "🇸🇽", + "slovakia": "🇸🇰", + "slovenia": "🇸🇮", + "solomon_islands": "🇸🇧", + "somalia": "🇸🇴", + "south_africa": "🇿🇦", + "south_georgia_&_south_sandwich_islands": "🇬🇸", + "south_korea": "🇰🇷", + "south_sudan": "🇸🇸", + "spain": "🇪🇸", + "sri_lanka": "🇱🇰", + "st._barthélemy": "🇧🇱", + "st._helena": "🇸🇭", + "st._kitts_&_nevis": "🇰🇳", + "st._lucia": "🇱🇨", + "st._martin": "🇲🇫", + "st._pierre_&_miquelon": "🇵🇲", + "st._vincent_&_grenadines": "🇻🇨", + "statue_of_liberty": "🗽", + "sudan": "🇸🇩", + "suriname": "🇸🇷", + "svalbard_&_jan_mayen": "🇸🇯", + "swaziland": "🇸🇿", + "sweden": "🇸🇪", + "switzerland": "🇨🇭", + "syria": "🇸🇾", + "são_tomé_&_príncipe": "🇸🇹", + "t-rex": "🦖", + "top_arrow": "🔝", + "taiwan": "🇹🇼", + "tajikistan": "🇹🇯", + "tanzania": "🇹🇿", + "taurus": "♉", + "thailand": "🇹🇭", + "timor-leste": "🇹🇱", + "togo": "🇹🇬", + "tokelau": "🇹🇰", + "tokyo_tower": "🗼", + "tonga": "🇹🇴", + "trinidad_&_tobago": "🇹🇹", + "tristan_da_cunha": "🇹🇦", + "tunisia": "🇹🇳", + "turkey": "🦃", + "turkmenistan": "🇹🇲", + "turks_&_caicos_islands": "🇹🇨", + "tuvalu": "🇹🇻", + "u.s._outlying_islands": "🇺🇲", + "u.s._virgin_islands": "🇻🇮", + "up!_button": "🆙", + "uganda": "🇺🇬", + "ukraine": "🇺🇦", + "united_arab_emirates": "🇦🇪", + "united_kingdom": "🇬🇧", + "united_nations": "🇺🇳", + "united_states": "🇺🇸", + "uruguay": "🇺🇾", + "uzbekistan": "🇺🇿", + "vs_button": "🆚", + "vanuatu": "🇻🇺", + "vatican_city": "🇻🇦", + "venezuela": "🇻🇪", + "vietnam": "🇻🇳", + "virgo": "♍", + "wales": "🏴\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f", + "wallis_&_futuna": "🇼🇫", + "western_sahara": "🇪🇭", + "yemen": "🇾🇪", + "zambia": "🇿🇲", + "zimbabwe": "🇿🇼", + "abacus": "🧮", + "adhesive_bandage": "🩹", + "admission_tickets": "🎟", + "adult": "🧑", + "adult_dark_skin_tone": "🧑🏿", + "adult_light_skin_tone": "🧑🏻", + "adult_medium-dark_skin_tone": "🧑🏾", + "adult_medium-light_skin_tone": "🧑🏼", + "adult_medium_skin_tone": "🧑🏽", + "aerial_tramway": "🚡", + "airplane": "✈", + "airplane_arrival": "🛬", + "airplane_departure": "🛫", + "alarm_clock": "⏰", + "alembic": "⚗", + "alien": "👽", + "alien_monster": "👾", + "ambulance": "🚑", + "american_football": "🏈", + "amphora": "🏺", + "anchor": "⚓", + "anger_symbol": "💢", + "angry_face": "😠", + "angry_face_with_horns": "👿", + "anguished_face": "😧", + "ant": "🐜", + "antenna_bars": "📶", + "anxious_face_with_sweat": "😰", + "articulated_lorry": "🚛", + "artist_palette": "🎨", + "astonished_face": "😲", + "atom_symbol": "⚛", + "auto_rickshaw": "🛺", + "automobile": "🚗", + "avocado": "🥑", + "axe": "🪓", + "baby": "👶", + "baby_angel": "👼", + "baby_angel_dark_skin_tone": "👼🏿", + "baby_angel_light_skin_tone": "👼🏻", + "baby_angel_medium-dark_skin_tone": "👼🏾", + "baby_angel_medium-light_skin_tone": "👼🏼", + "baby_angel_medium_skin_tone": "👼🏽", + "baby_bottle": "🍼", + "baby_chick": "🐤", + "baby_dark_skin_tone": "👶🏿", + "baby_light_skin_tone": "👶🏻", + "baby_medium-dark_skin_tone": "👶🏾", + "baby_medium-light_skin_tone": "👶🏼", + "baby_medium_skin_tone": "👶🏽", + "baby_symbol": "🚼", + "backhand_index_pointing_down": "👇", + "backhand_index_pointing_down_dark_skin_tone": "👇🏿", + "backhand_index_pointing_down_light_skin_tone": "👇🏻", + "backhand_index_pointing_down_medium-dark_skin_tone": "👇🏾", + "backhand_index_pointing_down_medium-light_skin_tone": "👇🏼", + "backhand_index_pointing_down_medium_skin_tone": "👇🏽", + "backhand_index_pointing_left": "👈", + "backhand_index_pointing_left_dark_skin_tone": "👈🏿", + "backhand_index_pointing_left_light_skin_tone": "👈🏻", + "backhand_index_pointing_left_medium-dark_skin_tone": "👈🏾", + "backhand_index_pointing_left_medium-light_skin_tone": "👈🏼", + "backhand_index_pointing_left_medium_skin_tone": "👈🏽", + "backhand_index_pointing_right": "👉", + "backhand_index_pointing_right_dark_skin_tone": "👉🏿", + "backhand_index_pointing_right_light_skin_tone": "👉🏻", + "backhand_index_pointing_right_medium-dark_skin_tone": "👉🏾", + "backhand_index_pointing_right_medium-light_skin_tone": "👉🏼", + "backhand_index_pointing_right_medium_skin_tone": "👉🏽", + "backhand_index_pointing_up": "👆", + "backhand_index_pointing_up_dark_skin_tone": "👆🏿", + "backhand_index_pointing_up_light_skin_tone": "👆🏻", + "backhand_index_pointing_up_medium-dark_skin_tone": "👆🏾", + "backhand_index_pointing_up_medium-light_skin_tone": "👆🏼", + "backhand_index_pointing_up_medium_skin_tone": "👆🏽", + "bacon": "🥓", + "badger": "🦡", + "badminton": "🏸", + "bagel": "🥯", + "baggage_claim": "🛄", + "baguette_bread": "🥖", + "balance_scale": "⚖", + "bald": "🦲", + "bald_man": "👨\u200d🦲", + "bald_woman": "👩\u200d🦲", + "ballet_shoes": "🩰", + "balloon": "🎈", + "ballot_box_with_ballot": "🗳", + "ballot_box_with_check": "☑", + "banana": "🍌", + "banjo": "🪕", + "bank": "🏦", + "bar_chart": "📊", + "barber_pole": "💈", + "baseball": "⚾", + "basket": "🧺", + "basketball": "🏀", + "bat": "🦇", + "bathtub": "🛁", + "battery": "🔋", + "beach_with_umbrella": "🏖", + "beaming_face_with_smiling_eyes": "😁", + "bear_face": "🐻", + "bearded_person": "🧔", + "bearded_person_dark_skin_tone": "🧔🏿", + "bearded_person_light_skin_tone": "🧔🏻", + "bearded_person_medium-dark_skin_tone": "🧔🏾", + "bearded_person_medium-light_skin_tone": "🧔🏼", + "bearded_person_medium_skin_tone": "🧔🏽", + "beating_heart": "💓", + "bed": "🛏", + "beer_mug": "🍺", + "bell": "🔔", + "bell_with_slash": "🔕", + "bellhop_bell": "🛎", + "bento_box": "🍱", + "beverage_box": "🧃", + "bicycle": "🚲", + "bikini": "👙", + "billed_cap": "🧢", + "biohazard": "☣", + "bird": "🐦", + "birthday_cake": "🎂", + "black_circle": "⚫", + "black_flag": "🏴", + "black_heart": "🖤", + "black_large_square": "⬛", + "black_medium-small_square": "◾", + "black_medium_square": "◼", + "black_nib": "✒", + "black_small_square": "▪", + "black_square_button": "🔲", + "blond-haired_man": "👱\u200d♂️", + "blond-haired_man_dark_skin_tone": "👱🏿\u200d♂️", + "blond-haired_man_light_skin_tone": "👱🏻\u200d♂️", + "blond-haired_man_medium-dark_skin_tone": "👱🏾\u200d♂️", + "blond-haired_man_medium-light_skin_tone": "👱🏼\u200d♂️", + "blond-haired_man_medium_skin_tone": "👱🏽\u200d♂️", + "blond-haired_person": "👱", + "blond-haired_person_dark_skin_tone": "👱🏿", + "blond-haired_person_light_skin_tone": "👱🏻", + "blond-haired_person_medium-dark_skin_tone": "👱🏾", + "blond-haired_person_medium-light_skin_tone": "👱🏼", + "blond-haired_person_medium_skin_tone": "👱🏽", + "blond-haired_woman": "👱\u200d♀️", + "blond-haired_woman_dark_skin_tone": "👱🏿\u200d♀️", + "blond-haired_woman_light_skin_tone": "👱🏻\u200d♀️", + "blond-haired_woman_medium-dark_skin_tone": "👱🏾\u200d♀️", + "blond-haired_woman_medium-light_skin_tone": "👱🏼\u200d♀️", + "blond-haired_woman_medium_skin_tone": "👱🏽\u200d♀️", + "blossom": "🌼", + "blowfish": "🐡", + "blue_book": "📘", + "blue_circle": "🔵", + "blue_heart": "💙", + "blue_square": "🟦", + "boar": "🐗", + "bomb": "💣", + "bone": "🦴", + "bookmark": "🔖", + "bookmark_tabs": "📑", + "books": "📚", + "bottle_with_popping_cork": "🍾", + "bouquet": "💐", + "bow_and_arrow": "🏹", + "bowl_with_spoon": "🥣", + "bowling": "🎳", + "boxing_glove": "🥊", + "boy": "👦", + "boy_dark_skin_tone": "👦🏿", + "boy_light_skin_tone": "👦🏻", + "boy_medium-dark_skin_tone": "👦🏾", + "boy_medium-light_skin_tone": "👦🏼", + "boy_medium_skin_tone": "👦🏽", + "brain": "🧠", + "bread": "🍞", + "breast-feeding": "🤱", + "breast-feeding_dark_skin_tone": "🤱🏿", + "breast-feeding_light_skin_tone": "🤱🏻", + "breast-feeding_medium-dark_skin_tone": "🤱🏾", + "breast-feeding_medium-light_skin_tone": "🤱🏼", + "breast-feeding_medium_skin_tone": "🤱🏽", + "brick": "🧱", + "bride_with_veil": "👰", + "bride_with_veil_dark_skin_tone": "👰🏿", + "bride_with_veil_light_skin_tone": "👰🏻", + "bride_with_veil_medium-dark_skin_tone": "👰🏾", + "bride_with_veil_medium-light_skin_tone": "👰🏼", + "bride_with_veil_medium_skin_tone": "👰🏽", + "bridge_at_night": "🌉", + "briefcase": "💼", + "briefs": "🩲", + "bright_button": "🔆", + "broccoli": "🥦", + "broken_heart": "💔", + "broom": "🧹", + "brown_circle": "🟤", + "brown_heart": "🤎", + "brown_square": "🟫", + "bug": "🐛", + "building_construction": "🏗", + "bullet_train": "🚅", + "burrito": "🌯", + "bus": "🚌", + "bus_stop": "🚏", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "butter": "🧈", + "butterfly": "🦋", + "cactus": "🌵", + "calendar": "📆", + "call_me_hand": "🤙", + "call_me_hand_dark_skin_tone": "🤙🏿", + "call_me_hand_light_skin_tone": "🤙🏻", + "call_me_hand_medium-dark_skin_tone": "🤙🏾", + "call_me_hand_medium-light_skin_tone": "🤙🏼", + "call_me_hand_medium_skin_tone": "🤙🏽", + "camel": "🐫", + "camera": "📷", + "camera_with_flash": "📸", + "camping": "🏕", + "candle": "🕯", + "candy": "🍬", + "canned_food": "🥫", + "canoe": "🛶", + "card_file_box": "🗃", + "card_index": "📇", + "card_index_dividers": "🗂", + "carousel_horse": "🎠", + "carp_streamer": "🎏", + "carrot": "🥕", + "castle": "🏰", + "cat": "🐱", + "cat_face": "🐱", + "cat_face_with_tears_of_joy": "😹", + "cat_face_with_wry_smile": "😼", + "chains": "⛓", + "chair": "🪑", + "chart_decreasing": "📉", + "chart_increasing": "📈", + "chart_increasing_with_yen": "💹", + "cheese_wedge": "🧀", + "chequered_flag": "🏁", + "cherries": "🍒", + "cherry_blossom": "🌸", + "chess_pawn": "♟", + "chestnut": "🌰", + "chicken": "🐔", + "child": "🧒", + "child_dark_skin_tone": "🧒🏿", + "child_light_skin_tone": "🧒🏻", + "child_medium-dark_skin_tone": "🧒🏾", + "child_medium-light_skin_tone": "🧒🏼", + "child_medium_skin_tone": "🧒🏽", + "children_crossing": "🚸", + "chipmunk": "🐿", + "chocolate_bar": "🍫", + "chopsticks": "🥢", + "church": "⛪", + "cigarette": "🚬", + "cinema": "🎦", + "circled_m": "Ⓜ", + "circus_tent": "🎪", + "cityscape": "🏙", + "cityscape_at_dusk": "🌆", + "clamp": "🗜", + "clapper_board": "🎬", + "clapping_hands": "👏", + "clapping_hands_dark_skin_tone": "👏🏿", + "clapping_hands_light_skin_tone": "👏🏻", + "clapping_hands_medium-dark_skin_tone": "👏🏾", + "clapping_hands_medium-light_skin_tone": "👏🏼", + "clapping_hands_medium_skin_tone": "👏🏽", + "classical_building": "🏛", + "clinking_beer_mugs": "🍻", + "clinking_glasses": "🥂", + "clipboard": "📋", + "clockwise_vertical_arrows": "🔃", + "closed_book": "📕", + "closed_mailbox_with_lowered_flag": "📪", + "closed_mailbox_with_raised_flag": "📫", + "closed_umbrella": "🌂", + "cloud": "☁", + "cloud_with_lightning": "🌩", + "cloud_with_lightning_and_rain": "⛈", + "cloud_with_rain": "🌧", + "cloud_with_snow": "🌨", + "clown_face": "🤡", + "club_suit": "♣", + "clutch_bag": "👝", + "coat": "🧥", + "cocktail_glass": "🍸", + "coconut": "🥥", + "coffin": "⚰", + "cold_face": "🥶", + "collision": "💥", + "comet": "☄", + "compass": "🧭", + "computer_disk": "💽", + "computer_mouse": "🖱", + "confetti_ball": "🎊", + "confounded_face": "😖", + "confused_face": "😕", + "construction": "🚧", + "construction_worker": "👷", + "construction_worker_dark_skin_tone": "👷🏿", + "construction_worker_light_skin_tone": "👷🏻", + "construction_worker_medium-dark_skin_tone": "👷🏾", + "construction_worker_medium-light_skin_tone": "👷🏼", + "construction_worker_medium_skin_tone": "👷🏽", + "control_knobs": "🎛", + "convenience_store": "🏪", + "cooked_rice": "🍚", + "cookie": "🍪", + "cooking": "🍳", + "copyright": "©", + "couch_and_lamp": "🛋", + "counterclockwise_arrows_button": "🔄", + "couple_with_heart": "💑", + "couple_with_heart_man_man": "👨\u200d❤️\u200d👨", + "couple_with_heart_woman_man": "👩\u200d❤️\u200d👨", + "couple_with_heart_woman_woman": "👩\u200d❤️\u200d👩", + "cow": "🐮", + "cow_face": "🐮", + "cowboy_hat_face": "🤠", + "crab": "🦀", + "crayon": "🖍", + "credit_card": "💳", + "crescent_moon": "🌙", + "cricket": "🦗", + "cricket_game": "🏏", + "crocodile": "🐊", + "croissant": "🥐", + "cross_mark": "❌", + "cross_mark_button": "❎", + "crossed_fingers": "🤞", + "crossed_fingers_dark_skin_tone": "🤞🏿", + "crossed_fingers_light_skin_tone": "🤞🏻", + "crossed_fingers_medium-dark_skin_tone": "🤞🏾", + "crossed_fingers_medium-light_skin_tone": "🤞🏼", + "crossed_fingers_medium_skin_tone": "🤞🏽", + "crossed_flags": "🎌", + "crossed_swords": "⚔", + "crown": "👑", + "crying_cat_face": "😿", + "crying_face": "😢", + "crystal_ball": "🔮", + "cucumber": "🥒", + "cupcake": "🧁", + "cup_with_straw": "🥤", + "curling_stone": "🥌", + "curly_hair": "🦱", + "curly-haired_man": "👨\u200d🦱", + "curly-haired_woman": "👩\u200d🦱", + "curly_loop": "➰", + "currency_exchange": "💱", + "curry_rice": "🍛", + "custard": "🍮", + "customs": "🛃", + "cut_of_meat": "🥩", + "cyclone": "🌀", + "dagger": "🗡", + "dango": "🍡", + "dashing_away": "💨", + "deaf_person": "🧏", + "deciduous_tree": "🌳", + "deer": "🦌", + "delivery_truck": "🚚", + "department_store": "🏬", + "derelict_house": "🏚", + "desert": "🏜", + "desert_island": "🏝", + "desktop_computer": "🖥", + "detective": "🕵", + "detective_dark_skin_tone": "🕵🏿", + "detective_light_skin_tone": "🕵🏻", + "detective_medium-dark_skin_tone": "🕵🏾", + "detective_medium-light_skin_tone": "🕵🏼", + "detective_medium_skin_tone": "🕵🏽", + "diamond_suit": "♦", + "diamond_with_a_dot": "💠", + "dim_button": "🔅", + "direct_hit": "🎯", + "disappointed_face": "😞", + "diving_mask": "🤿", + "diya_lamp": "🪔", + "dizzy": "💫", + "dizzy_face": "😵", + "dna": "🧬", + "dog": "🐶", + "dog_face": "🐶", + "dollar_banknote": "💵", + "dolphin": "🐬", + "door": "🚪", + "dotted_six-pointed_star": "🔯", + "double_curly_loop": "➿", + "double_exclamation_mark": "‼", + "doughnut": "🍩", + "dove": "🕊", + "down-left_arrow": "↙", + "down-right_arrow": "↘", + "down_arrow": "⬇", + "downcast_face_with_sweat": "😓", + "downwards_button": "🔽", + "dragon": "🐉", + "dragon_face": "🐲", + "dress": "👗", + "drooling_face": "🤤", + "drop_of_blood": "🩸", + "droplet": "💧", + "drum": "🥁", + "duck": "🦆", + "dumpling": "🥟", + "dvd": "📀", + "e-mail": "📧", + "eagle": "🦅", + "ear": "👂", + "ear_dark_skin_tone": "👂🏿", + "ear_light_skin_tone": "👂🏻", + "ear_medium-dark_skin_tone": "👂🏾", + "ear_medium-light_skin_tone": "👂🏼", + "ear_medium_skin_tone": "👂🏽", + "ear_of_corn": "🌽", + "ear_with_hearing_aid": "🦻", + "egg": "🍳", + "eggplant": "🍆", + "eight-pointed_star": "✴", + "eight-spoked_asterisk": "✳", + "eight-thirty": "🕣", + "eight_o’clock": "🕗", + "eject_button": "⏏", + "electric_plug": "🔌", + "elephant": "🐘", + "eleven-thirty": "🕦", + "eleven_o’clock": "🕚", + "elf": "🧝", + "elf_dark_skin_tone": "🧝🏿", + "elf_light_skin_tone": "🧝🏻", + "elf_medium-dark_skin_tone": "🧝🏾", + "elf_medium-light_skin_tone": "🧝🏼", + "elf_medium_skin_tone": "🧝🏽", + "envelope": "✉", + "envelope_with_arrow": "📩", + "euro_banknote": "💶", + "evergreen_tree": "🌲", + "ewe": "🐑", + "exclamation_mark": "❗", + "exclamation_question_mark": "⁉", + "exploding_head": "🤯", + "expressionless_face": "😑", + "eye": "👁", + "eye_in_speech_bubble": "👁️\u200d🗨️", + "eyes": "👀", + "face_blowing_a_kiss": "😘", + "face_savoring_food": "😋", + "face_screaming_in_fear": "😱", + "face_vomiting": "🤮", + "face_with_hand_over_mouth": "🤭", + "face_with_head-bandage": "🤕", + "face_with_medical_mask": "😷", + "face_with_monocle": "🧐", + "face_with_open_mouth": "😮", + "face_with_raised_eyebrow": "🤨", + "face_with_rolling_eyes": "🙄", + "face_with_steam_from_nose": "😤", + "face_with_symbols_on_mouth": "🤬", + "face_with_tears_of_joy": "😂", + "face_with_thermometer": "🤒", + "face_with_tongue": "😛", + "face_without_mouth": "😶", + "factory": "🏭", + "fairy": "🧚", + "fairy_dark_skin_tone": "🧚🏿", + "fairy_light_skin_tone": "🧚🏻", + "fairy_medium-dark_skin_tone": "🧚🏾", + "fairy_medium-light_skin_tone": "🧚🏼", + "fairy_medium_skin_tone": "🧚🏽", + "falafel": "🧆", + "fallen_leaf": "🍂", + "family": "👪", + "family_man_boy": "👨\u200d👦", + "family_man_boy_boy": "👨\u200d👦\u200d👦", + "family_man_girl": "👨\u200d👧", + "family_man_girl_boy": "👨\u200d👧\u200d👦", + "family_man_girl_girl": "👨\u200d👧\u200d👧", + "family_man_man_boy": "👨\u200d👨\u200d👦", + "family_man_man_boy_boy": "👨\u200d👨\u200d👦\u200d👦", + "family_man_man_girl": "👨\u200d👨\u200d👧", + "family_man_man_girl_boy": "👨\u200d👨\u200d👧\u200d👦", + "family_man_man_girl_girl": "👨\u200d👨\u200d👧\u200d👧", + "family_man_woman_boy": "👨\u200d👩\u200d👦", + "family_man_woman_boy_boy": "👨\u200d👩\u200d👦\u200d👦", + "family_man_woman_girl": "👨\u200d👩\u200d👧", + "family_man_woman_girl_boy": "👨\u200d👩\u200d👧\u200d👦", + "family_man_woman_girl_girl": "👨\u200d👩\u200d👧\u200d👧", + "family_woman_boy": "👩\u200d👦", + "family_woman_boy_boy": "👩\u200d👦\u200d👦", + "family_woman_girl": "👩\u200d👧", + "family_woman_girl_boy": "👩\u200d👧\u200d👦", + "family_woman_girl_girl": "👩\u200d👧\u200d👧", + "family_woman_woman_boy": "👩\u200d👩\u200d👦", + "family_woman_woman_boy_boy": "👩\u200d👩\u200d👦\u200d👦", + "family_woman_woman_girl": "👩\u200d👩\u200d👧", + "family_woman_woman_girl_boy": "👩\u200d👩\u200d👧\u200d👦", + "family_woman_woman_girl_girl": "👩\u200d👩\u200d👧\u200d👧", + "fast-forward_button": "⏩", + "fast_down_button": "⏬", + "fast_reverse_button": "⏪", + "fast_up_button": "⏫", + "fax_machine": "📠", + "fearful_face": "😨", + "female_sign": "♀", + "ferris_wheel": "🎡", + "ferry": "⛴", + "field_hockey": "🏑", + "file_cabinet": "🗄", + "file_folder": "📁", + "film_frames": "🎞", + "film_projector": "📽", + "fire": "🔥", + "fire_extinguisher": "🧯", + "firecracker": "🧨", + "fire_engine": "🚒", + "fireworks": "🎆", + "first_quarter_moon": "🌓", + "first_quarter_moon_face": "🌛", + "fish": "🐟", + "fish_cake_with_swirl": "🍥", + "fishing_pole": "🎣", + "five-thirty": "🕠", + "five_o’clock": "🕔", + "flag_in_hole": "⛳", + "flamingo": "🦩", + "flashlight": "🔦", + "flat_shoe": "🥿", + "fleur-de-lis": "⚜", + "flexed_biceps": "💪", + "flexed_biceps_dark_skin_tone": "💪🏿", + "flexed_biceps_light_skin_tone": "💪🏻", + "flexed_biceps_medium-dark_skin_tone": "💪🏾", + "flexed_biceps_medium-light_skin_tone": "💪🏼", + "flexed_biceps_medium_skin_tone": "💪🏽", + "floppy_disk": "💾", + "flower_playing_cards": "🎴", + "flushed_face": "😳", + "flying_disc": "🥏", + "flying_saucer": "🛸", + "fog": "🌫", + "foggy": "🌁", + "folded_hands": "🙏", + "folded_hands_dark_skin_tone": "🙏🏿", + "folded_hands_light_skin_tone": "🙏🏻", + "folded_hands_medium-dark_skin_tone": "🙏🏾", + "folded_hands_medium-light_skin_tone": "🙏🏼", + "folded_hands_medium_skin_tone": "🙏🏽", + "foot": "🦶", + "footprints": "👣", + "fork_and_knife": "🍴", + "fork_and_knife_with_plate": "🍽", + "fortune_cookie": "🥠", + "fountain": "⛲", + "fountain_pen": "🖋", + "four-thirty": "🕟", + "four_leaf_clover": "🍀", + "four_o’clock": "🕓", + "fox_face": "🦊", + "framed_picture": "🖼", + "french_fries": "🍟", + "fried_shrimp": "🍤", + "frog_face": "🐸", + "front-facing_baby_chick": "🐥", + "frowning_face": "☹", + "frowning_face_with_open_mouth": "😦", + "fuel_pump": "⛽", + "full_moon": "🌕", + "full_moon_face": "🌝", + "funeral_urn": "⚱", + "game_die": "🎲", + "garlic": "🧄", + "gear": "⚙", + "gem_stone": "💎", + "genie": "🧞", + "ghost": "👻", + "giraffe": "🦒", + "girl": "👧", + "girl_dark_skin_tone": "👧🏿", + "girl_light_skin_tone": "👧🏻", + "girl_medium-dark_skin_tone": "👧🏾", + "girl_medium-light_skin_tone": "👧🏼", + "girl_medium_skin_tone": "👧🏽", + "glass_of_milk": "🥛", + "glasses": "👓", + "globe_showing_americas": "🌎", + "globe_showing_asia-australia": "🌏", + "globe_showing_europe-africa": "🌍", + "globe_with_meridians": "🌐", + "gloves": "🧤", + "glowing_star": "🌟", + "goal_net": "🥅", + "goat": "🐐", + "goblin": "👺", + "goggles": "🥽", + "gorilla": "🦍", + "graduation_cap": "🎓", + "grapes": "🍇", + "green_apple": "🍏", + "green_book": "📗", + "green_circle": "🟢", + "green_heart": "💚", + "green_salad": "🥗", + "green_square": "🟩", + "grimacing_face": "😬", + "grinning_cat_face": "😺", + "grinning_cat_face_with_smiling_eyes": "😸", + "grinning_face": "😀", + "grinning_face_with_big_eyes": "😃", + "grinning_face_with_smiling_eyes": "😄", + "grinning_face_with_sweat": "😅", + "grinning_squinting_face": "😆", + "growing_heart": "💗", + "guard": "💂", + "guard_dark_skin_tone": "💂🏿", + "guard_light_skin_tone": "💂🏻", + "guard_medium-dark_skin_tone": "💂🏾", + "guard_medium-light_skin_tone": "💂🏼", + "guard_medium_skin_tone": "💂🏽", + "guide_dog": "🦮", + "guitar": "🎸", + "hamburger": "🍔", + "hammer": "🔨", + "hammer_and_pick": "⚒", + "hammer_and_wrench": "🛠", + "hamster_face": "🐹", + "hand_with_fingers_splayed": "🖐", + "hand_with_fingers_splayed_dark_skin_tone": "🖐🏿", + "hand_with_fingers_splayed_light_skin_tone": "🖐🏻", + "hand_with_fingers_splayed_medium-dark_skin_tone": "🖐🏾", + "hand_with_fingers_splayed_medium-light_skin_tone": "🖐🏼", + "hand_with_fingers_splayed_medium_skin_tone": "🖐🏽", + "handbag": "👜", + "handshake": "🤝", + "hatching_chick": "🐣", + "headphone": "🎧", + "hear-no-evil_monkey": "🙉", + "heart_decoration": "💟", + "heart_suit": "♥", + "heart_with_arrow": "💘", + "heart_with_ribbon": "💝", + "heavy_check_mark": "✔", + "heavy_division_sign": "➗", + "heavy_dollar_sign": "💲", + "heavy_heart_exclamation": "❣", + "heavy_large_circle": "⭕", + "heavy_minus_sign": "➖", + "heavy_multiplication_x": "✖", + "heavy_plus_sign": "➕", + "hedgehog": "🦔", + "helicopter": "🚁", + "herb": "🌿", + "hibiscus": "🌺", + "high-heeled_shoe": "👠", + "high-speed_train": "🚄", + "high_voltage": "⚡", + "hiking_boot": "🥾", + "hindu_temple": "🛕", + "hippopotamus": "🦛", + "hole": "🕳", + "honey_pot": "🍯", + "honeybee": "🐝", + "horizontal_traffic_light": "🚥", + "horse": "🐴", + "horse_face": "🐴", + "horse_racing": "🏇", + "horse_racing_dark_skin_tone": "🏇🏿", + "horse_racing_light_skin_tone": "🏇🏻", + "horse_racing_medium-dark_skin_tone": "🏇🏾", + "horse_racing_medium-light_skin_tone": "🏇🏼", + "horse_racing_medium_skin_tone": "🏇🏽", + "hospital": "🏥", + "hot_beverage": "☕", + "hot_dog": "🌭", + "hot_face": "🥵", + "hot_pepper": "🌶", + "hot_springs": "♨", + "hotel": "🏨", + "hourglass_done": "⌛", + "hourglass_not_done": "⏳", + "house": "🏠", + "house_with_garden": "🏡", + "houses": "🏘", + "hugging_face": "🤗", + "hundred_points": "💯", + "hushed_face": "😯", + "ice": "🧊", + "ice_cream": "🍨", + "ice_hockey": "🏒", + "ice_skate": "⛸", + "inbox_tray": "📥", + "incoming_envelope": "📨", + "index_pointing_up": "☝", + "index_pointing_up_dark_skin_tone": "☝🏿", + "index_pointing_up_light_skin_tone": "☝🏻", + "index_pointing_up_medium-dark_skin_tone": "☝🏾", + "index_pointing_up_medium-light_skin_tone": "☝🏼", + "index_pointing_up_medium_skin_tone": "☝🏽", + "infinity": "♾", + "information": "ℹ", + "input_latin_letters": "🔤", + "input_latin_lowercase": "🔡", + "input_latin_uppercase": "🔠", + "input_numbers": "🔢", + "input_symbols": "🔣", + "jack-o-lantern": "🎃", + "jeans": "👖", + "jigsaw": "🧩", + "joker": "🃏", + "joystick": "🕹", + "kaaba": "🕋", + "kangaroo": "🦘", + "key": "🔑", + "keyboard": "⌨", + "keycap_#": "#️⃣", + "keycap_*": "*️⃣", + "keycap_0": "0️⃣", + "keycap_1": "1️⃣", + "keycap_10": "🔟", + "keycap_2": "2️⃣", + "keycap_3": "3️⃣", + "keycap_4": "4️⃣", + "keycap_5": "5️⃣", + "keycap_6": "6️⃣", + "keycap_7": "7️⃣", + "keycap_8": "8️⃣", + "keycap_9": "9️⃣", + "kick_scooter": "🛴", + "kimono": "👘", + "kiss": "💋", + "kiss_man_man": "👨\u200d❤️\u200d💋\u200d👨", + "kiss_mark": "💋", + "kiss_woman_man": "👩\u200d❤️\u200d💋\u200d👨", + "kiss_woman_woman": "👩\u200d❤️\u200d💋\u200d👩", + "kissing_cat_face": "😽", + "kissing_face": "😗", + "kissing_face_with_closed_eyes": "😚", + "kissing_face_with_smiling_eyes": "😙", + "kitchen_knife": "🔪", + "kite": "🪁", + "kiwi_fruit": "🥝", + "koala": "🐨", + "lab_coat": "🥼", + "label": "🏷", + "lacrosse": "🥍", + "lady_beetle": "🐞", + "laptop_computer": "💻", + "large_blue_diamond": "🔷", + "large_orange_diamond": "🔶", + "last_quarter_moon": "🌗", + "last_quarter_moon_face": "🌜", + "last_track_button": "⏮", + "latin_cross": "✝", + "leaf_fluttering_in_wind": "🍃", + "leafy_green": "🥬", + "ledger": "📒", + "left-facing_fist": "🤛", + "left-facing_fist_dark_skin_tone": "🤛🏿", + "left-facing_fist_light_skin_tone": "🤛🏻", + "left-facing_fist_medium-dark_skin_tone": "🤛🏾", + "left-facing_fist_medium-light_skin_tone": "🤛🏼", + "left-facing_fist_medium_skin_tone": "🤛🏽", + "left-right_arrow": "↔", + "left_arrow": "⬅", + "left_arrow_curving_right": "↪", + "left_luggage": "🛅", + "left_speech_bubble": "🗨", + "leg": "🦵", + "lemon": "🍋", + "leopard": "🐆", + "level_slider": "🎚", + "light_bulb": "💡", + "light_rail": "🚈", + "link": "🔗", + "linked_paperclips": "🖇", + "lion_face": "🦁", + "lipstick": "💄", + "litter_in_bin_sign": "🚮", + "lizard": "🦎", + "llama": "🦙", + "lobster": "🦞", + "locked": "🔒", + "locked_with_key": "🔐", + "locked_with_pen": "🔏", + "locomotive": "🚂", + "lollipop": "🍭", + "lotion_bottle": "🧴", + "loudly_crying_face": "😭", + "loudspeaker": "📢", + "love-you_gesture": "🤟", + "love-you_gesture_dark_skin_tone": "🤟🏿", + "love-you_gesture_light_skin_tone": "🤟🏻", + "love-you_gesture_medium-dark_skin_tone": "🤟🏾", + "love-you_gesture_medium-light_skin_tone": "🤟🏼", + "love-you_gesture_medium_skin_tone": "🤟🏽", + "love_hotel": "🏩", + "love_letter": "💌", + "luggage": "🧳", + "lying_face": "🤥", + "mage": "🧙", + "mage_dark_skin_tone": "🧙🏿", + "mage_light_skin_tone": "🧙🏻", + "mage_medium-dark_skin_tone": "🧙🏾", + "mage_medium-light_skin_tone": "🧙🏼", + "mage_medium_skin_tone": "🧙🏽", + "magnet": "🧲", + "magnifying_glass_tilted_left": "🔍", + "magnifying_glass_tilted_right": "🔎", + "mahjong_red_dragon": "🀄", + "male_sign": "♂", + "man": "👨", + "man_and_woman_holding_hands": "👫", + "man_artist": "👨\u200d🎨", + "man_artist_dark_skin_tone": "👨🏿\u200d🎨", + "man_artist_light_skin_tone": "👨🏻\u200d🎨", + "man_artist_medium-dark_skin_tone": "👨🏾\u200d🎨", + "man_artist_medium-light_skin_tone": "👨🏼\u200d🎨", + "man_artist_medium_skin_tone": "👨🏽\u200d🎨", + "man_astronaut": "👨\u200d🚀", + "man_astronaut_dark_skin_tone": "👨🏿\u200d🚀", + "man_astronaut_light_skin_tone": "👨🏻\u200d🚀", + "man_astronaut_medium-dark_skin_tone": "👨🏾\u200d🚀", + "man_astronaut_medium-light_skin_tone": "👨🏼\u200d🚀", + "man_astronaut_medium_skin_tone": "👨🏽\u200d🚀", + "man_biking": "🚴\u200d♂️", + "man_biking_dark_skin_tone": "🚴🏿\u200d♂️", + "man_biking_light_skin_tone": "🚴🏻\u200d♂️", + "man_biking_medium-dark_skin_tone": "🚴🏾\u200d♂️", + "man_biking_medium-light_skin_tone": "🚴🏼\u200d♂️", + "man_biking_medium_skin_tone": "🚴🏽\u200d♂️", + "man_bouncing_ball": "⛹️\u200d♂️", + "man_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♂️", + "man_bouncing_ball_light_skin_tone": "⛹🏻\u200d♂️", + "man_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♂️", + "man_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♂️", + "man_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♂️", + "man_bowing": "🙇\u200d♂️", + "man_bowing_dark_skin_tone": "🙇🏿\u200d♂️", + "man_bowing_light_skin_tone": "🙇🏻\u200d♂️", + "man_bowing_medium-dark_skin_tone": "🙇🏾\u200d♂️", + "man_bowing_medium-light_skin_tone": "🙇🏼\u200d♂️", + "man_bowing_medium_skin_tone": "🙇🏽\u200d♂️", + "man_cartwheeling": "🤸\u200d♂️", + "man_cartwheeling_dark_skin_tone": "🤸🏿\u200d♂️", + "man_cartwheeling_light_skin_tone": "🤸🏻\u200d♂️", + "man_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♂️", + "man_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♂️", + "man_cartwheeling_medium_skin_tone": "🤸🏽\u200d♂️", + "man_climbing": "🧗\u200d♂️", + "man_climbing_dark_skin_tone": "🧗🏿\u200d♂️", + "man_climbing_light_skin_tone": "🧗🏻\u200d♂️", + "man_climbing_medium-dark_skin_tone": "🧗🏾\u200d♂️", + "man_climbing_medium-light_skin_tone": "🧗🏼\u200d♂️", + "man_climbing_medium_skin_tone": "🧗🏽\u200d♂️", + "man_construction_worker": "👷\u200d♂️", + "man_construction_worker_dark_skin_tone": "👷🏿\u200d♂️", + "man_construction_worker_light_skin_tone": "👷🏻\u200d♂️", + "man_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♂️", + "man_construction_worker_medium-light_skin_tone": "👷🏼\u200d♂️", + "man_construction_worker_medium_skin_tone": "👷🏽\u200d♂️", + "man_cook": "👨\u200d🍳", + "man_cook_dark_skin_tone": "👨🏿\u200d🍳", + "man_cook_light_skin_tone": "👨🏻\u200d🍳", + "man_cook_medium-dark_skin_tone": "👨🏾\u200d🍳", + "man_cook_medium-light_skin_tone": "👨🏼\u200d🍳", + "man_cook_medium_skin_tone": "👨🏽\u200d🍳", + "man_dancing": "🕺", + "man_dancing_dark_skin_tone": "🕺🏿", + "man_dancing_light_skin_tone": "🕺🏻", + "man_dancing_medium-dark_skin_tone": "🕺🏾", + "man_dancing_medium-light_skin_tone": "🕺🏼", + "man_dancing_medium_skin_tone": "🕺🏽", + "man_dark_skin_tone": "👨🏿", + "man_detective": "🕵️\u200d♂️", + "man_detective_dark_skin_tone": "🕵🏿\u200d♂️", + "man_detective_light_skin_tone": "🕵🏻\u200d♂️", + "man_detective_medium-dark_skin_tone": "🕵🏾\u200d♂️", + "man_detective_medium-light_skin_tone": "🕵🏼\u200d♂️", + "man_detective_medium_skin_tone": "🕵🏽\u200d♂️", + "man_elf": "🧝\u200d♂️", + "man_elf_dark_skin_tone": "🧝🏿\u200d♂️", + "man_elf_light_skin_tone": "🧝🏻\u200d♂️", + "man_elf_medium-dark_skin_tone": "🧝🏾\u200d♂️", + "man_elf_medium-light_skin_tone": "🧝🏼\u200d♂️", + "man_elf_medium_skin_tone": "🧝🏽\u200d♂️", + "man_facepalming": "🤦\u200d♂️", + "man_facepalming_dark_skin_tone": "🤦🏿\u200d♂️", + "man_facepalming_light_skin_tone": "🤦🏻\u200d♂️", + "man_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♂️", + "man_facepalming_medium-light_skin_tone": "🤦🏼\u200d♂️", + "man_facepalming_medium_skin_tone": "🤦🏽\u200d♂️", + "man_factory_worker": "👨\u200d🏭", + "man_factory_worker_dark_skin_tone": "👨🏿\u200d🏭", + "man_factory_worker_light_skin_tone": "👨🏻\u200d🏭", + "man_factory_worker_medium-dark_skin_tone": "👨🏾\u200d🏭", + "man_factory_worker_medium-light_skin_tone": "👨🏼\u200d🏭", + "man_factory_worker_medium_skin_tone": "👨🏽\u200d🏭", + "man_fairy": "🧚\u200d♂️", + "man_fairy_dark_skin_tone": "🧚🏿\u200d♂️", + "man_fairy_light_skin_tone": "🧚🏻\u200d♂️", + "man_fairy_medium-dark_skin_tone": "🧚🏾\u200d♂️", + "man_fairy_medium-light_skin_tone": "🧚🏼\u200d♂️", + "man_fairy_medium_skin_tone": "🧚🏽\u200d♂️", + "man_farmer": "👨\u200d🌾", + "man_farmer_dark_skin_tone": "👨🏿\u200d🌾", + "man_farmer_light_skin_tone": "👨🏻\u200d🌾", + "man_farmer_medium-dark_skin_tone": "👨🏾\u200d🌾", + "man_farmer_medium-light_skin_tone": "👨🏼\u200d🌾", + "man_farmer_medium_skin_tone": "👨🏽\u200d🌾", + "man_firefighter": "👨\u200d🚒", + "man_firefighter_dark_skin_tone": "👨🏿\u200d🚒", + "man_firefighter_light_skin_tone": "👨🏻\u200d🚒", + "man_firefighter_medium-dark_skin_tone": "👨🏾\u200d🚒", + "man_firefighter_medium-light_skin_tone": "👨🏼\u200d🚒", + "man_firefighter_medium_skin_tone": "👨🏽\u200d🚒", + "man_frowning": "🙍\u200d♂️", + "man_frowning_dark_skin_tone": "🙍🏿\u200d♂️", + "man_frowning_light_skin_tone": "🙍🏻\u200d♂️", + "man_frowning_medium-dark_skin_tone": "🙍🏾\u200d♂️", + "man_frowning_medium-light_skin_tone": "🙍🏼\u200d♂️", + "man_frowning_medium_skin_tone": "🙍🏽\u200d♂️", + "man_genie": "🧞\u200d♂️", + "man_gesturing_no": "🙅\u200d♂️", + "man_gesturing_no_dark_skin_tone": "🙅🏿\u200d♂️", + "man_gesturing_no_light_skin_tone": "🙅🏻\u200d♂️", + "man_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♂️", + "man_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♂️", + "man_gesturing_no_medium_skin_tone": "🙅🏽\u200d♂️", + "man_gesturing_ok": "🙆\u200d♂️", + "man_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♂️", + "man_gesturing_ok_light_skin_tone": "🙆🏻\u200d♂️", + "man_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♂️", + "man_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♂️", + "man_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♂️", + "man_getting_haircut": "💇\u200d♂️", + "man_getting_haircut_dark_skin_tone": "💇🏿\u200d♂️", + "man_getting_haircut_light_skin_tone": "💇🏻\u200d♂️", + "man_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♂️", + "man_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♂️", + "man_getting_haircut_medium_skin_tone": "💇🏽\u200d♂️", + "man_getting_massage": "💆\u200d♂️", + "man_getting_massage_dark_skin_tone": "💆🏿\u200d♂️", + "man_getting_massage_light_skin_tone": "💆🏻\u200d♂️", + "man_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♂️", + "man_getting_massage_medium-light_skin_tone": "💆🏼\u200d♂️", + "man_getting_massage_medium_skin_tone": "💆🏽\u200d♂️", + "man_golfing": "🏌️\u200d♂️", + "man_golfing_dark_skin_tone": "🏌🏿\u200d♂️", + "man_golfing_light_skin_tone": "🏌🏻\u200d♂️", + "man_golfing_medium-dark_skin_tone": "🏌🏾\u200d♂️", + "man_golfing_medium-light_skin_tone": "🏌🏼\u200d♂️", + "man_golfing_medium_skin_tone": "🏌🏽\u200d♂️", + "man_guard": "💂\u200d♂️", + "man_guard_dark_skin_tone": "💂🏿\u200d♂️", + "man_guard_light_skin_tone": "💂🏻\u200d♂️", + "man_guard_medium-dark_skin_tone": "💂🏾\u200d♂️", + "man_guard_medium-light_skin_tone": "💂🏼\u200d♂️", + "man_guard_medium_skin_tone": "💂🏽\u200d♂️", + "man_health_worker": "👨\u200d⚕️", + "man_health_worker_dark_skin_tone": "👨🏿\u200d⚕️", + "man_health_worker_light_skin_tone": "👨🏻\u200d⚕️", + "man_health_worker_medium-dark_skin_tone": "👨🏾\u200d⚕️", + "man_health_worker_medium-light_skin_tone": "👨🏼\u200d⚕️", + "man_health_worker_medium_skin_tone": "👨🏽\u200d⚕️", + "man_in_lotus_position": "🧘\u200d♂️", + "man_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♂️", + "man_in_lotus_position_light_skin_tone": "🧘🏻\u200d♂️", + "man_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♂️", + "man_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♂️", + "man_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♂️", + "man_in_manual_wheelchair": "👨\u200d🦽", + "man_in_motorized_wheelchair": "👨\u200d🦼", + "man_in_steamy_room": "🧖\u200d♂️", + "man_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♂️", + "man_in_steamy_room_light_skin_tone": "🧖🏻\u200d♂️", + "man_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♂️", + "man_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♂️", + "man_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♂️", + "man_in_suit_levitating": "🕴", + "man_in_suit_levitating_dark_skin_tone": "🕴🏿", + "man_in_suit_levitating_light_skin_tone": "🕴🏻", + "man_in_suit_levitating_medium-dark_skin_tone": "🕴🏾", + "man_in_suit_levitating_medium-light_skin_tone": "🕴🏼", + "man_in_suit_levitating_medium_skin_tone": "🕴🏽", + "man_in_tuxedo": "🤵", + "man_in_tuxedo_dark_skin_tone": "🤵🏿", + "man_in_tuxedo_light_skin_tone": "🤵🏻", + "man_in_tuxedo_medium-dark_skin_tone": "🤵🏾", + "man_in_tuxedo_medium-light_skin_tone": "🤵🏼", + "man_in_tuxedo_medium_skin_tone": "🤵🏽", + "man_judge": "👨\u200d⚖️", + "man_judge_dark_skin_tone": "👨🏿\u200d⚖️", + "man_judge_light_skin_tone": "👨🏻\u200d⚖️", + "man_judge_medium-dark_skin_tone": "👨🏾\u200d⚖️", + "man_judge_medium-light_skin_tone": "👨🏼\u200d⚖️", + "man_judge_medium_skin_tone": "👨🏽\u200d⚖️", + "man_juggling": "🤹\u200d♂️", + "man_juggling_dark_skin_tone": "🤹🏿\u200d♂️", + "man_juggling_light_skin_tone": "🤹🏻\u200d♂️", + "man_juggling_medium-dark_skin_tone": "🤹🏾\u200d♂️", + "man_juggling_medium-light_skin_tone": "🤹🏼\u200d♂️", + "man_juggling_medium_skin_tone": "🤹🏽\u200d♂️", + "man_lifting_weights": "🏋️\u200d♂️", + "man_lifting_weights_dark_skin_tone": "🏋🏿\u200d♂️", + "man_lifting_weights_light_skin_tone": "🏋🏻\u200d♂️", + "man_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♂️", + "man_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♂️", + "man_lifting_weights_medium_skin_tone": "🏋🏽\u200d♂️", + "man_light_skin_tone": "👨🏻", + "man_mage": "🧙\u200d♂️", + "man_mage_dark_skin_tone": "🧙🏿\u200d♂️", + "man_mage_light_skin_tone": "🧙🏻\u200d♂️", + "man_mage_medium-dark_skin_tone": "🧙🏾\u200d♂️", + "man_mage_medium-light_skin_tone": "🧙🏼\u200d♂️", + "man_mage_medium_skin_tone": "🧙🏽\u200d♂️", + "man_mechanic": "👨\u200d🔧", + "man_mechanic_dark_skin_tone": "👨🏿\u200d🔧", + "man_mechanic_light_skin_tone": "👨🏻\u200d🔧", + "man_mechanic_medium-dark_skin_tone": "👨🏾\u200d🔧", + "man_mechanic_medium-light_skin_tone": "👨🏼\u200d🔧", + "man_mechanic_medium_skin_tone": "👨🏽\u200d🔧", + "man_medium-dark_skin_tone": "👨🏾", + "man_medium-light_skin_tone": "👨🏼", + "man_medium_skin_tone": "👨🏽", + "man_mountain_biking": "🚵\u200d♂️", + "man_mountain_biking_dark_skin_tone": "🚵🏿\u200d♂️", + "man_mountain_biking_light_skin_tone": "🚵🏻\u200d♂️", + "man_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♂️", + "man_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♂️", + "man_mountain_biking_medium_skin_tone": "🚵🏽\u200d♂️", + "man_office_worker": "👨\u200d💼", + "man_office_worker_dark_skin_tone": "👨🏿\u200d💼", + "man_office_worker_light_skin_tone": "👨🏻\u200d💼", + "man_office_worker_medium-dark_skin_tone": "👨🏾\u200d💼", + "man_office_worker_medium-light_skin_tone": "👨🏼\u200d💼", + "man_office_worker_medium_skin_tone": "👨🏽\u200d💼", + "man_pilot": "👨\u200d✈️", + "man_pilot_dark_skin_tone": "👨🏿\u200d✈️", + "man_pilot_light_skin_tone": "👨🏻\u200d✈️", + "man_pilot_medium-dark_skin_tone": "👨🏾\u200d✈️", + "man_pilot_medium-light_skin_tone": "👨🏼\u200d✈️", + "man_pilot_medium_skin_tone": "👨🏽\u200d✈️", + "man_playing_handball": "🤾\u200d♂️", + "man_playing_handball_dark_skin_tone": "🤾🏿\u200d♂️", + "man_playing_handball_light_skin_tone": "🤾🏻\u200d♂️", + "man_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♂️", + "man_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♂️", + "man_playing_handball_medium_skin_tone": "🤾🏽\u200d♂️", + "man_playing_water_polo": "🤽\u200d♂️", + "man_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♂️", + "man_playing_water_polo_light_skin_tone": "🤽🏻\u200d♂️", + "man_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♂️", + "man_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♂️", + "man_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♂️", + "man_police_officer": "👮\u200d♂️", + "man_police_officer_dark_skin_tone": "👮🏿\u200d♂️", + "man_police_officer_light_skin_tone": "👮🏻\u200d♂️", + "man_police_officer_medium-dark_skin_tone": "👮🏾\u200d♂️", + "man_police_officer_medium-light_skin_tone": "👮🏼\u200d♂️", + "man_police_officer_medium_skin_tone": "👮🏽\u200d♂️", + "man_pouting": "🙎\u200d♂️", + "man_pouting_dark_skin_tone": "🙎🏿\u200d♂️", + "man_pouting_light_skin_tone": "🙎🏻\u200d♂️", + "man_pouting_medium-dark_skin_tone": "🙎🏾\u200d♂️", + "man_pouting_medium-light_skin_tone": "🙎🏼\u200d♂️", + "man_pouting_medium_skin_tone": "🙎🏽\u200d♂️", + "man_raising_hand": "🙋\u200d♂️", + "man_raising_hand_dark_skin_tone": "🙋🏿\u200d♂️", + "man_raising_hand_light_skin_tone": "🙋🏻\u200d♂️", + "man_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♂️", + "man_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♂️", + "man_raising_hand_medium_skin_tone": "🙋🏽\u200d♂️", + "man_rowing_boat": "🚣\u200d♂️", + "man_rowing_boat_dark_skin_tone": "🚣🏿\u200d♂️", + "man_rowing_boat_light_skin_tone": "🚣🏻\u200d♂️", + "man_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♂️", + "man_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♂️", + "man_rowing_boat_medium_skin_tone": "🚣🏽\u200d♂️", + "man_running": "🏃\u200d♂️", + "man_running_dark_skin_tone": "🏃🏿\u200d♂️", + "man_running_light_skin_tone": "🏃🏻\u200d♂️", + "man_running_medium-dark_skin_tone": "🏃🏾\u200d♂️", + "man_running_medium-light_skin_tone": "🏃🏼\u200d♂️", + "man_running_medium_skin_tone": "🏃🏽\u200d♂️", + "man_scientist": "👨\u200d🔬", + "man_scientist_dark_skin_tone": "👨🏿\u200d🔬", + "man_scientist_light_skin_tone": "👨🏻\u200d🔬", + "man_scientist_medium-dark_skin_tone": "👨🏾\u200d🔬", + "man_scientist_medium-light_skin_tone": "👨🏼\u200d🔬", + "man_scientist_medium_skin_tone": "👨🏽\u200d🔬", + "man_shrugging": "🤷\u200d♂️", + "man_shrugging_dark_skin_tone": "🤷🏿\u200d♂️", + "man_shrugging_light_skin_tone": "🤷🏻\u200d♂️", + "man_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♂️", + "man_shrugging_medium-light_skin_tone": "🤷🏼\u200d♂️", + "man_shrugging_medium_skin_tone": "🤷🏽\u200d♂️", + "man_singer": "👨\u200d🎤", + "man_singer_dark_skin_tone": "👨🏿\u200d🎤", + "man_singer_light_skin_tone": "👨🏻\u200d🎤", + "man_singer_medium-dark_skin_tone": "👨🏾\u200d🎤", + "man_singer_medium-light_skin_tone": "👨🏼\u200d🎤", + "man_singer_medium_skin_tone": "👨🏽\u200d🎤", + "man_student": "👨\u200d🎓", + "man_student_dark_skin_tone": "👨🏿\u200d🎓", + "man_student_light_skin_tone": "👨🏻\u200d🎓", + "man_student_medium-dark_skin_tone": "👨🏾\u200d🎓", + "man_student_medium-light_skin_tone": "👨🏼\u200d🎓", + "man_student_medium_skin_tone": "👨🏽\u200d🎓", + "man_surfing": "🏄\u200d♂️", + "man_surfing_dark_skin_tone": "🏄🏿\u200d♂️", + "man_surfing_light_skin_tone": "🏄🏻\u200d♂️", + "man_surfing_medium-dark_skin_tone": "🏄🏾\u200d♂️", + "man_surfing_medium-light_skin_tone": "🏄🏼\u200d♂️", + "man_surfing_medium_skin_tone": "🏄🏽\u200d♂️", + "man_swimming": "🏊\u200d♂️", + "man_swimming_dark_skin_tone": "🏊🏿\u200d♂️", + "man_swimming_light_skin_tone": "🏊🏻\u200d♂️", + "man_swimming_medium-dark_skin_tone": "🏊🏾\u200d♂️", + "man_swimming_medium-light_skin_tone": "🏊🏼\u200d♂️", + "man_swimming_medium_skin_tone": "🏊🏽\u200d♂️", + "man_teacher": "👨\u200d🏫", + "man_teacher_dark_skin_tone": "👨🏿\u200d🏫", + "man_teacher_light_skin_tone": "👨🏻\u200d🏫", + "man_teacher_medium-dark_skin_tone": "👨🏾\u200d🏫", + "man_teacher_medium-light_skin_tone": "👨🏼\u200d🏫", + "man_teacher_medium_skin_tone": "👨🏽\u200d🏫", + "man_technologist": "👨\u200d💻", + "man_technologist_dark_skin_tone": "👨🏿\u200d💻", + "man_technologist_light_skin_tone": "👨🏻\u200d💻", + "man_technologist_medium-dark_skin_tone": "👨🏾\u200d💻", + "man_technologist_medium-light_skin_tone": "👨🏼\u200d💻", + "man_technologist_medium_skin_tone": "👨🏽\u200d💻", + "man_tipping_hand": "💁\u200d♂️", + "man_tipping_hand_dark_skin_tone": "💁🏿\u200d♂️", + "man_tipping_hand_light_skin_tone": "💁🏻\u200d♂️", + "man_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♂️", + "man_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♂️", + "man_tipping_hand_medium_skin_tone": "💁🏽\u200d♂️", + "man_vampire": "🧛\u200d♂️", + "man_vampire_dark_skin_tone": "🧛🏿\u200d♂️", + "man_vampire_light_skin_tone": "🧛🏻\u200d♂️", + "man_vampire_medium-dark_skin_tone": "🧛🏾\u200d♂️", + "man_vampire_medium-light_skin_tone": "🧛🏼\u200d♂️", + "man_vampire_medium_skin_tone": "🧛🏽\u200d♂️", + "man_walking": "🚶\u200d♂️", + "man_walking_dark_skin_tone": "🚶🏿\u200d♂️", + "man_walking_light_skin_tone": "🚶🏻\u200d♂️", + "man_walking_medium-dark_skin_tone": "🚶🏾\u200d♂️", + "man_walking_medium-light_skin_tone": "🚶🏼\u200d♂️", + "man_walking_medium_skin_tone": "🚶🏽\u200d♂️", + "man_wearing_turban": "👳\u200d♂️", + "man_wearing_turban_dark_skin_tone": "👳🏿\u200d♂️", + "man_wearing_turban_light_skin_tone": "👳🏻\u200d♂️", + "man_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♂️", + "man_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♂️", + "man_wearing_turban_medium_skin_tone": "👳🏽\u200d♂️", + "man_with_probing_cane": "👨\u200d🦯", + "man_with_chinese_cap": "👲", + "man_with_chinese_cap_dark_skin_tone": "👲🏿", + "man_with_chinese_cap_light_skin_tone": "👲🏻", + "man_with_chinese_cap_medium-dark_skin_tone": "👲🏾", + "man_with_chinese_cap_medium-light_skin_tone": "👲🏼", + "man_with_chinese_cap_medium_skin_tone": "👲🏽", + "man_zombie": "🧟\u200d♂️", + "mango": "🥭", + "mantelpiece_clock": "🕰", + "manual_wheelchair": "🦽", + "man’s_shoe": "👞", + "map_of_japan": "🗾", + "maple_leaf": "🍁", + "martial_arts_uniform": "🥋", + "mate": "🧉", + "meat_on_bone": "🍖", + "mechanical_arm": "🦾", + "mechanical_leg": "🦿", + "medical_symbol": "⚕", + "megaphone": "📣", + "melon": "🍈", + "memo": "📝", + "men_with_bunny_ears": "👯\u200d♂️", + "men_wrestling": "🤼\u200d♂️", + "menorah": "🕎", + "men’s_room": "🚹", + "mermaid": "🧜\u200d♀️", + "mermaid_dark_skin_tone": "🧜🏿\u200d♀️", + "mermaid_light_skin_tone": "🧜🏻\u200d♀️", + "mermaid_medium-dark_skin_tone": "🧜🏾\u200d♀️", + "mermaid_medium-light_skin_tone": "🧜🏼\u200d♀️", + "mermaid_medium_skin_tone": "🧜🏽\u200d♀️", + "merman": "🧜\u200d♂️", + "merman_dark_skin_tone": "🧜🏿\u200d♂️", + "merman_light_skin_tone": "🧜🏻\u200d♂️", + "merman_medium-dark_skin_tone": "🧜🏾\u200d♂️", + "merman_medium-light_skin_tone": "🧜🏼\u200d♂️", + "merman_medium_skin_tone": "🧜🏽\u200d♂️", + "merperson": "🧜", + "merperson_dark_skin_tone": "🧜🏿", + "merperson_light_skin_tone": "🧜🏻", + "merperson_medium-dark_skin_tone": "🧜🏾", + "merperson_medium-light_skin_tone": "🧜🏼", + "merperson_medium_skin_tone": "🧜🏽", + "metro": "🚇", + "microbe": "🦠", + "microphone": "🎤", + "microscope": "🔬", + "middle_finger": "🖕", + "middle_finger_dark_skin_tone": "🖕🏿", + "middle_finger_light_skin_tone": "🖕🏻", + "middle_finger_medium-dark_skin_tone": "🖕🏾", + "middle_finger_medium-light_skin_tone": "🖕🏼", + "middle_finger_medium_skin_tone": "🖕🏽", + "military_medal": "🎖", + "milky_way": "🌌", + "minibus": "🚐", + "moai": "🗿", + "mobile_phone": "📱", + "mobile_phone_off": "📴", + "mobile_phone_with_arrow": "📲", + "money-mouth_face": "🤑", + "money_bag": "💰", + "money_with_wings": "💸", + "monkey": "🐒", + "monkey_face": "🐵", + "monorail": "🚝", + "moon_cake": "🥮", + "moon_viewing_ceremony": "🎑", + "mosque": "🕌", + "mosquito": "🦟", + "motor_boat": "🛥", + "motor_scooter": "🛵", + "motorcycle": "🏍", + "motorized_wheelchair": "🦼", + "motorway": "🛣", + "mount_fuji": "🗻", + "mountain": "⛰", + "mountain_cableway": "🚠", + "mountain_railway": "🚞", + "mouse": "🐭", + "mouse_face": "🐭", + "mouth": "👄", + "movie_camera": "🎥", + "mushroom": "🍄", + "musical_keyboard": "🎹", + "musical_note": "🎵", + "musical_notes": "🎶", + "musical_score": "🎼", + "muted_speaker": "🔇", + "nail_polish": "💅", + "nail_polish_dark_skin_tone": "💅🏿", + "nail_polish_light_skin_tone": "💅🏻", + "nail_polish_medium-dark_skin_tone": "💅🏾", + "nail_polish_medium-light_skin_tone": "💅🏼", + "nail_polish_medium_skin_tone": "💅🏽", + "name_badge": "📛", + "national_park": "🏞", + "nauseated_face": "🤢", + "nazar_amulet": "🧿", + "necktie": "👔", + "nerd_face": "🤓", + "neutral_face": "😐", + "new_moon": "🌑", + "new_moon_face": "🌚", + "newspaper": "📰", + "next_track_button": "⏭", + "night_with_stars": "🌃", + "nine-thirty": "🕤", + "nine_o’clock": "🕘", + "no_bicycles": "🚳", + "no_entry": "⛔", + "no_littering": "🚯", + "no_mobile_phones": "📵", + "no_one_under_eighteen": "🔞", + "no_pedestrians": "🚷", + "no_smoking": "🚭", + "non-potable_water": "🚱", + "nose": "👃", + "nose_dark_skin_tone": "👃🏿", + "nose_light_skin_tone": "👃🏻", + "nose_medium-dark_skin_tone": "👃🏾", + "nose_medium-light_skin_tone": "👃🏼", + "nose_medium_skin_tone": "👃🏽", + "notebook": "📓", + "notebook_with_decorative_cover": "📔", + "nut_and_bolt": "🔩", + "octopus": "🐙", + "oden": "🍢", + "office_building": "🏢", + "ogre": "👹", + "oil_drum": "🛢", + "old_key": "🗝", + "old_man": "👴", + "old_man_dark_skin_tone": "👴🏿", + "old_man_light_skin_tone": "👴🏻", + "old_man_medium-dark_skin_tone": "👴🏾", + "old_man_medium-light_skin_tone": "👴🏼", + "old_man_medium_skin_tone": "👴🏽", + "old_woman": "👵", + "old_woman_dark_skin_tone": "👵🏿", + "old_woman_light_skin_tone": "👵🏻", + "old_woman_medium-dark_skin_tone": "👵🏾", + "old_woman_medium-light_skin_tone": "👵🏼", + "old_woman_medium_skin_tone": "👵🏽", + "older_adult": "🧓", + "older_adult_dark_skin_tone": "🧓🏿", + "older_adult_light_skin_tone": "🧓🏻", + "older_adult_medium-dark_skin_tone": "🧓🏾", + "older_adult_medium-light_skin_tone": "🧓🏼", + "older_adult_medium_skin_tone": "🧓🏽", + "om": "🕉", + "oncoming_automobile": "🚘", + "oncoming_bus": "🚍", + "oncoming_fist": "👊", + "oncoming_fist_dark_skin_tone": "👊🏿", + "oncoming_fist_light_skin_tone": "👊🏻", + "oncoming_fist_medium-dark_skin_tone": "👊🏾", + "oncoming_fist_medium-light_skin_tone": "👊🏼", + "oncoming_fist_medium_skin_tone": "👊🏽", + "oncoming_police_car": "🚔", + "oncoming_taxi": "🚖", + "one-piece_swimsuit": "🩱", + "one-thirty": "🕜", + "one_o’clock": "🕐", + "onion": "🧅", + "open_book": "📖", + "open_file_folder": "📂", + "open_hands": "👐", + "open_hands_dark_skin_tone": "👐🏿", + "open_hands_light_skin_tone": "👐🏻", + "open_hands_medium-dark_skin_tone": "👐🏾", + "open_hands_medium-light_skin_tone": "👐🏼", + "open_hands_medium_skin_tone": "👐🏽", + "open_mailbox_with_lowered_flag": "📭", + "open_mailbox_with_raised_flag": "📬", + "optical_disk": "💿", + "orange_book": "📙", + "orange_circle": "🟠", + "orange_heart": "🧡", + "orange_square": "🟧", + "orangutan": "🦧", + "orthodox_cross": "☦", + "otter": "🦦", + "outbox_tray": "📤", + "owl": "🦉", + "ox": "🐂", + "oyster": "🦪", + "package": "📦", + "page_facing_up": "📄", + "page_with_curl": "📃", + "pager": "📟", + "paintbrush": "🖌", + "palm_tree": "🌴", + "palms_up_together": "🤲", + "palms_up_together_dark_skin_tone": "🤲🏿", + "palms_up_together_light_skin_tone": "🤲🏻", + "palms_up_together_medium-dark_skin_tone": "🤲🏾", + "palms_up_together_medium-light_skin_tone": "🤲🏼", + "palms_up_together_medium_skin_tone": "🤲🏽", + "pancakes": "🥞", + "panda_face": "🐼", + "paperclip": "📎", + "parrot": "🦜", + "part_alternation_mark": "〽", + "party_popper": "🎉", + "partying_face": "🥳", + "passenger_ship": "🛳", + "passport_control": "🛂", + "pause_button": "⏸", + "paw_prints": "🐾", + "peace_symbol": "☮", + "peach": "🍑", + "peacock": "🦚", + "peanuts": "🥜", + "pear": "🍐", + "pen": "🖊", + "pencil": "📝", + "penguin": "🐧", + "pensive_face": "😔", + "people_holding_hands": "🧑\u200d🤝\u200d🧑", + "people_with_bunny_ears": "👯", + "people_wrestling": "🤼", + "performing_arts": "🎭", + "persevering_face": "😣", + "person_biking": "🚴", + "person_biking_dark_skin_tone": "🚴🏿", + "person_biking_light_skin_tone": "🚴🏻", + "person_biking_medium-dark_skin_tone": "🚴🏾", + "person_biking_medium-light_skin_tone": "🚴🏼", + "person_biking_medium_skin_tone": "🚴🏽", + "person_bouncing_ball": "⛹", + "person_bouncing_ball_dark_skin_tone": "⛹🏿", + "person_bouncing_ball_light_skin_tone": "⛹🏻", + "person_bouncing_ball_medium-dark_skin_tone": "⛹🏾", + "person_bouncing_ball_medium-light_skin_tone": "⛹🏼", + "person_bouncing_ball_medium_skin_tone": "⛹🏽", + "person_bowing": "🙇", + "person_bowing_dark_skin_tone": "🙇🏿", + "person_bowing_light_skin_tone": "🙇🏻", + "person_bowing_medium-dark_skin_tone": "🙇🏾", + "person_bowing_medium-light_skin_tone": "🙇🏼", + "person_bowing_medium_skin_tone": "🙇🏽", + "person_cartwheeling": "🤸", + "person_cartwheeling_dark_skin_tone": "🤸🏿", + "person_cartwheeling_light_skin_tone": "🤸🏻", + "person_cartwheeling_medium-dark_skin_tone": "🤸🏾", + "person_cartwheeling_medium-light_skin_tone": "🤸🏼", + "person_cartwheeling_medium_skin_tone": "🤸🏽", + "person_climbing": "🧗", + "person_climbing_dark_skin_tone": "🧗🏿", + "person_climbing_light_skin_tone": "🧗🏻", + "person_climbing_medium-dark_skin_tone": "🧗🏾", + "person_climbing_medium-light_skin_tone": "🧗🏼", + "person_climbing_medium_skin_tone": "🧗🏽", + "person_facepalming": "🤦", + "person_facepalming_dark_skin_tone": "🤦🏿", + "person_facepalming_light_skin_tone": "🤦🏻", + "person_facepalming_medium-dark_skin_tone": "🤦🏾", + "person_facepalming_medium-light_skin_tone": "🤦🏼", + "person_facepalming_medium_skin_tone": "🤦🏽", + "person_fencing": "🤺", + "person_frowning": "🙍", + "person_frowning_dark_skin_tone": "🙍🏿", + "person_frowning_light_skin_tone": "🙍🏻", + "person_frowning_medium-dark_skin_tone": "🙍🏾", + "person_frowning_medium-light_skin_tone": "🙍🏼", + "person_frowning_medium_skin_tone": "🙍🏽", + "person_gesturing_no": "🙅", + "person_gesturing_no_dark_skin_tone": "🙅🏿", + "person_gesturing_no_light_skin_tone": "🙅🏻", + "person_gesturing_no_medium-dark_skin_tone": "🙅🏾", + "person_gesturing_no_medium-light_skin_tone": "🙅🏼", + "person_gesturing_no_medium_skin_tone": "🙅🏽", + "person_gesturing_ok": "🙆", + "person_gesturing_ok_dark_skin_tone": "🙆🏿", + "person_gesturing_ok_light_skin_tone": "🙆🏻", + "person_gesturing_ok_medium-dark_skin_tone": "🙆🏾", + "person_gesturing_ok_medium-light_skin_tone": "🙆🏼", + "person_gesturing_ok_medium_skin_tone": "🙆🏽", + "person_getting_haircut": "💇", + "person_getting_haircut_dark_skin_tone": "💇🏿", + "person_getting_haircut_light_skin_tone": "💇🏻", + "person_getting_haircut_medium-dark_skin_tone": "💇🏾", + "person_getting_haircut_medium-light_skin_tone": "💇🏼", + "person_getting_haircut_medium_skin_tone": "💇🏽", + "person_getting_massage": "💆", + "person_getting_massage_dark_skin_tone": "💆🏿", + "person_getting_massage_light_skin_tone": "💆🏻", + "person_getting_massage_medium-dark_skin_tone": "💆🏾", + "person_getting_massage_medium-light_skin_tone": "💆🏼", + "person_getting_massage_medium_skin_tone": "💆🏽", + "person_golfing": "🏌", + "person_golfing_dark_skin_tone": "🏌🏿", + "person_golfing_light_skin_tone": "🏌🏻", + "person_golfing_medium-dark_skin_tone": "🏌🏾", + "person_golfing_medium-light_skin_tone": "🏌🏼", + "person_golfing_medium_skin_tone": "🏌🏽", + "person_in_bed": "🛌", + "person_in_bed_dark_skin_tone": "🛌🏿", + "person_in_bed_light_skin_tone": "🛌🏻", + "person_in_bed_medium-dark_skin_tone": "🛌🏾", + "person_in_bed_medium-light_skin_tone": "🛌🏼", + "person_in_bed_medium_skin_tone": "🛌🏽", + "person_in_lotus_position": "🧘", + "person_in_lotus_position_dark_skin_tone": "🧘🏿", + "person_in_lotus_position_light_skin_tone": "🧘🏻", + "person_in_lotus_position_medium-dark_skin_tone": "🧘🏾", + "person_in_lotus_position_medium-light_skin_tone": "🧘🏼", + "person_in_lotus_position_medium_skin_tone": "🧘🏽", + "person_in_steamy_room": "🧖", + "person_in_steamy_room_dark_skin_tone": "🧖🏿", + "person_in_steamy_room_light_skin_tone": "🧖🏻", + "person_in_steamy_room_medium-dark_skin_tone": "🧖🏾", + "person_in_steamy_room_medium-light_skin_tone": "🧖🏼", + "person_in_steamy_room_medium_skin_tone": "🧖🏽", + "person_juggling": "🤹", + "person_juggling_dark_skin_tone": "🤹🏿", + "person_juggling_light_skin_tone": "🤹🏻", + "person_juggling_medium-dark_skin_tone": "🤹🏾", + "person_juggling_medium-light_skin_tone": "🤹🏼", + "person_juggling_medium_skin_tone": "🤹🏽", + "person_kneeling": "🧎", + "person_lifting_weights": "🏋", + "person_lifting_weights_dark_skin_tone": "🏋🏿", + "person_lifting_weights_light_skin_tone": "🏋🏻", + "person_lifting_weights_medium-dark_skin_tone": "🏋🏾", + "person_lifting_weights_medium-light_skin_tone": "🏋🏼", + "person_lifting_weights_medium_skin_tone": "🏋🏽", + "person_mountain_biking": "🚵", + "person_mountain_biking_dark_skin_tone": "🚵🏿", + "person_mountain_biking_light_skin_tone": "🚵🏻", + "person_mountain_biking_medium-dark_skin_tone": "🚵🏾", + "person_mountain_biking_medium-light_skin_tone": "🚵🏼", + "person_mountain_biking_medium_skin_tone": "🚵🏽", + "person_playing_handball": "🤾", + "person_playing_handball_dark_skin_tone": "🤾🏿", + "person_playing_handball_light_skin_tone": "🤾🏻", + "person_playing_handball_medium-dark_skin_tone": "🤾🏾", + "person_playing_handball_medium-light_skin_tone": "🤾🏼", + "person_playing_handball_medium_skin_tone": "🤾🏽", + "person_playing_water_polo": "🤽", + "person_playing_water_polo_dark_skin_tone": "🤽🏿", + "person_playing_water_polo_light_skin_tone": "🤽🏻", + "person_playing_water_polo_medium-dark_skin_tone": "🤽🏾", + "person_playing_water_polo_medium-light_skin_tone": "🤽🏼", + "person_playing_water_polo_medium_skin_tone": "🤽🏽", + "person_pouting": "🙎", + "person_pouting_dark_skin_tone": "🙎🏿", + "person_pouting_light_skin_tone": "🙎🏻", + "person_pouting_medium-dark_skin_tone": "🙎🏾", + "person_pouting_medium-light_skin_tone": "🙎🏼", + "person_pouting_medium_skin_tone": "🙎🏽", + "person_raising_hand": "🙋", + "person_raising_hand_dark_skin_tone": "🙋🏿", + "person_raising_hand_light_skin_tone": "🙋🏻", + "person_raising_hand_medium-dark_skin_tone": "🙋🏾", + "person_raising_hand_medium-light_skin_tone": "🙋🏼", + "person_raising_hand_medium_skin_tone": "🙋🏽", + "person_rowing_boat": "🚣", + "person_rowing_boat_dark_skin_tone": "🚣🏿", + "person_rowing_boat_light_skin_tone": "🚣🏻", + "person_rowing_boat_medium-dark_skin_tone": "🚣🏾", + "person_rowing_boat_medium-light_skin_tone": "🚣🏼", + "person_rowing_boat_medium_skin_tone": "🚣🏽", + "person_running": "🏃", + "person_running_dark_skin_tone": "🏃🏿", + "person_running_light_skin_tone": "🏃🏻", + "person_running_medium-dark_skin_tone": "🏃🏾", + "person_running_medium-light_skin_tone": "🏃🏼", + "person_running_medium_skin_tone": "🏃🏽", + "person_shrugging": "🤷", + "person_shrugging_dark_skin_tone": "🤷🏿", + "person_shrugging_light_skin_tone": "🤷🏻", + "person_shrugging_medium-dark_skin_tone": "🤷🏾", + "person_shrugging_medium-light_skin_tone": "🤷🏼", + "person_shrugging_medium_skin_tone": "🤷🏽", + "person_standing": "🧍", + "person_surfing": "🏄", + "person_surfing_dark_skin_tone": "🏄🏿", + "person_surfing_light_skin_tone": "🏄🏻", + "person_surfing_medium-dark_skin_tone": "🏄🏾", + "person_surfing_medium-light_skin_tone": "🏄🏼", + "person_surfing_medium_skin_tone": "🏄🏽", + "person_swimming": "🏊", + "person_swimming_dark_skin_tone": "🏊🏿", + "person_swimming_light_skin_tone": "🏊🏻", + "person_swimming_medium-dark_skin_tone": "🏊🏾", + "person_swimming_medium-light_skin_tone": "🏊🏼", + "person_swimming_medium_skin_tone": "🏊🏽", + "person_taking_bath": "🛀", + "person_taking_bath_dark_skin_tone": "🛀🏿", + "person_taking_bath_light_skin_tone": "🛀🏻", + "person_taking_bath_medium-dark_skin_tone": "🛀🏾", + "person_taking_bath_medium-light_skin_tone": "🛀🏼", + "person_taking_bath_medium_skin_tone": "🛀🏽", + "person_tipping_hand": "💁", + "person_tipping_hand_dark_skin_tone": "💁🏿", + "person_tipping_hand_light_skin_tone": "💁🏻", + "person_tipping_hand_medium-dark_skin_tone": "💁🏾", + "person_tipping_hand_medium-light_skin_tone": "💁🏼", + "person_tipping_hand_medium_skin_tone": "💁🏽", + "person_walking": "🚶", + "person_walking_dark_skin_tone": "🚶🏿", + "person_walking_light_skin_tone": "🚶🏻", + "person_walking_medium-dark_skin_tone": "🚶🏾", + "person_walking_medium-light_skin_tone": "🚶🏼", + "person_walking_medium_skin_tone": "🚶🏽", + "person_wearing_turban": "👳", + "person_wearing_turban_dark_skin_tone": "👳🏿", + "person_wearing_turban_light_skin_tone": "👳🏻", + "person_wearing_turban_medium-dark_skin_tone": "👳🏾", + "person_wearing_turban_medium-light_skin_tone": "👳🏼", + "person_wearing_turban_medium_skin_tone": "👳🏽", + "petri_dish": "🧫", + "pick": "⛏", + "pie": "🥧", + "pig": "🐷", + "pig_face": "🐷", + "pig_nose": "🐽", + "pile_of_poo": "💩", + "pill": "💊", + "pinching_hand": "🤏", + "pine_decoration": "🎍", + "pineapple": "🍍", + "ping_pong": "🏓", + "pirate_flag": "🏴\u200d☠️", + "pistol": "🔫", + "pizza": "🍕", + "place_of_worship": "🛐", + "play_button": "▶", + "play_or_pause_button": "⏯", + "pleading_face": "🥺", + "police_car": "🚓", + "police_car_light": "🚨", + "police_officer": "👮", + "police_officer_dark_skin_tone": "👮🏿", + "police_officer_light_skin_tone": "👮🏻", + "police_officer_medium-dark_skin_tone": "👮🏾", + "police_officer_medium-light_skin_tone": "👮🏼", + "police_officer_medium_skin_tone": "👮🏽", + "poodle": "🐩", + "pool_8_ball": "🎱", + "popcorn": "🍿", + "post_office": "🏣", + "postal_horn": "📯", + "postbox": "📮", + "pot_of_food": "🍲", + "potable_water": "🚰", + "potato": "🥔", + "poultry_leg": "🍗", + "pound_banknote": "💷", + "pouting_cat_face": "😾", + "pouting_face": "😡", + "prayer_beads": "📿", + "pregnant_woman": "🤰", + "pregnant_woman_dark_skin_tone": "🤰🏿", + "pregnant_woman_light_skin_tone": "🤰🏻", + "pregnant_woman_medium-dark_skin_tone": "🤰🏾", + "pregnant_woman_medium-light_skin_tone": "🤰🏼", + "pregnant_woman_medium_skin_tone": "🤰🏽", + "pretzel": "🥨", + "probing_cane": "🦯", + "prince": "🤴", + "prince_dark_skin_tone": "🤴🏿", + "prince_light_skin_tone": "🤴🏻", + "prince_medium-dark_skin_tone": "🤴🏾", + "prince_medium-light_skin_tone": "🤴🏼", + "prince_medium_skin_tone": "🤴🏽", + "princess": "👸", + "princess_dark_skin_tone": "👸🏿", + "princess_light_skin_tone": "👸🏻", + "princess_medium-dark_skin_tone": "👸🏾", + "princess_medium-light_skin_tone": "👸🏼", + "princess_medium_skin_tone": "👸🏽", + "printer": "🖨", + "prohibited": "🚫", + "purple_circle": "🟣", + "purple_heart": "💜", + "purple_square": "🟪", + "purse": "👛", + "pushpin": "📌", + "question_mark": "❓", + "rabbit": "🐰", + "rabbit_face": "🐰", + "raccoon": "🦝", + "racing_car": "🏎", + "radio": "📻", + "radio_button": "🔘", + "radioactive": "☢", + "railway_car": "🚃", + "railway_track": "🛤", + "rainbow": "🌈", + "rainbow_flag": "🏳️\u200d🌈", + "raised_back_of_hand": "🤚", + "raised_back_of_hand_dark_skin_tone": "🤚🏿", + "raised_back_of_hand_light_skin_tone": "🤚🏻", + "raised_back_of_hand_medium-dark_skin_tone": "🤚🏾", + "raised_back_of_hand_medium-light_skin_tone": "🤚🏼", + "raised_back_of_hand_medium_skin_tone": "🤚🏽", + "raised_fist": "✊", + "raised_fist_dark_skin_tone": "✊🏿", + "raised_fist_light_skin_tone": "✊🏻", + "raised_fist_medium-dark_skin_tone": "✊🏾", + "raised_fist_medium-light_skin_tone": "✊🏼", + "raised_fist_medium_skin_tone": "✊🏽", + "raised_hand": "✋", + "raised_hand_dark_skin_tone": "✋🏿", + "raised_hand_light_skin_tone": "✋🏻", + "raised_hand_medium-dark_skin_tone": "✋🏾", + "raised_hand_medium-light_skin_tone": "✋🏼", + "raised_hand_medium_skin_tone": "✋🏽", + "raising_hands": "🙌", + "raising_hands_dark_skin_tone": "🙌🏿", + "raising_hands_light_skin_tone": "🙌🏻", + "raising_hands_medium-dark_skin_tone": "🙌🏾", + "raising_hands_medium-light_skin_tone": "🙌🏼", + "raising_hands_medium_skin_tone": "🙌🏽", + "ram": "🐏", + "rat": "🐀", + "razor": "🪒", + "ringed_planet": "🪐", + "receipt": "🧾", + "record_button": "⏺", + "recycling_symbol": "♻", + "red_apple": "🍎", + "red_circle": "🔴", + "red_envelope": "🧧", + "red_hair": "🦰", + "red-haired_man": "👨\u200d🦰", + "red-haired_woman": "👩\u200d🦰", + "red_heart": "❤", + "red_paper_lantern": "🏮", + "red_square": "🟥", + "red_triangle_pointed_down": "🔻", + "red_triangle_pointed_up": "🔺", + "registered": "®", + "relieved_face": "😌", + "reminder_ribbon": "🎗", + "repeat_button": "🔁", + "repeat_single_button": "🔂", + "rescue_worker’s_helmet": "⛑", + "restroom": "🚻", + "reverse_button": "◀", + "revolving_hearts": "💞", + "rhinoceros": "🦏", + "ribbon": "🎀", + "rice_ball": "🍙", + "rice_cracker": "🍘", + "right-facing_fist": "🤜", + "right-facing_fist_dark_skin_tone": "🤜🏿", + "right-facing_fist_light_skin_tone": "🤜🏻", + "right-facing_fist_medium-dark_skin_tone": "🤜🏾", + "right-facing_fist_medium-light_skin_tone": "🤜🏼", + "right-facing_fist_medium_skin_tone": "🤜🏽", + "right_anger_bubble": "🗯", + "right_arrow": "➡", + "right_arrow_curving_down": "⤵", + "right_arrow_curving_left": "↩", + "right_arrow_curving_up": "⤴", + "ring": "💍", + "roasted_sweet_potato": "🍠", + "robot_face": "🤖", + "rocket": "🚀", + "roll_of_paper": "🧻", + "rolled-up_newspaper": "🗞", + "roller_coaster": "🎢", + "rolling_on_the_floor_laughing": "🤣", + "rooster": "🐓", + "rose": "🌹", + "rosette": "🏵", + "round_pushpin": "📍", + "rugby_football": "🏉", + "running_shirt": "🎽", + "running_shoe": "👟", + "sad_but_relieved_face": "😥", + "safety_pin": "🧷", + "safety_vest": "🦺", + "salt": "🧂", + "sailboat": "⛵", + "sake": "🍶", + "sandwich": "🥪", + "sari": "🥻", + "satellite": "📡", + "satellite_antenna": "📡", + "sauropod": "🦕", + "saxophone": "🎷", + "scarf": "🧣", + "school": "🏫", + "school_backpack": "🎒", + "scissors": "✂", + "scorpion": "🦂", + "scroll": "📜", + "seat": "💺", + "see-no-evil_monkey": "🙈", + "seedling": "🌱", + "selfie": "🤳", + "selfie_dark_skin_tone": "🤳🏿", + "selfie_light_skin_tone": "🤳🏻", + "selfie_medium-dark_skin_tone": "🤳🏾", + "selfie_medium-light_skin_tone": "🤳🏼", + "selfie_medium_skin_tone": "🤳🏽", + "service_dog": "🐕\u200d🦺", + "seven-thirty": "🕢", + "seven_o’clock": "🕖", + "shallow_pan_of_food": "🥘", + "shamrock": "☘", + "shark": "🦈", + "shaved_ice": "🍧", + "sheaf_of_rice": "🌾", + "shield": "🛡", + "shinto_shrine": "⛩", + "ship": "🚢", + "shooting_star": "🌠", + "shopping_bags": "🛍", + "shopping_cart": "🛒", + "shortcake": "🍰", + "shorts": "🩳", + "shower": "🚿", + "shrimp": "🦐", + "shuffle_tracks_button": "🔀", + "shushing_face": "🤫", + "sign_of_the_horns": "🤘", + "sign_of_the_horns_dark_skin_tone": "🤘🏿", + "sign_of_the_horns_light_skin_tone": "🤘🏻", + "sign_of_the_horns_medium-dark_skin_tone": "🤘🏾", + "sign_of_the_horns_medium-light_skin_tone": "🤘🏼", + "sign_of_the_horns_medium_skin_tone": "🤘🏽", + "six-thirty": "🕡", + "six_o’clock": "🕕", + "skateboard": "🛹", + "skier": "⛷", + "skis": "🎿", + "skull": "💀", + "skull_and_crossbones": "☠", + "skunk": "🦨", + "sled": "🛷", + "sleeping_face": "😴", + "sleepy_face": "😪", + "slightly_frowning_face": "🙁", + "slightly_smiling_face": "🙂", + "slot_machine": "🎰", + "sloth": "🦥", + "small_airplane": "🛩", + "small_blue_diamond": "🔹", + "small_orange_diamond": "🔸", + "smiling_cat_face_with_heart-eyes": "😻", + "smiling_face": "☺", + "smiling_face_with_halo": "😇", + "smiling_face_with_3_hearts": "🥰", + "smiling_face_with_heart-eyes": "😍", + "smiling_face_with_horns": "😈", + "smiling_face_with_smiling_eyes": "😊", + "smiling_face_with_sunglasses": "😎", + "smirking_face": "😏", + "snail": "🐌", + "snake": "🐍", + "sneezing_face": "🤧", + "snow-capped_mountain": "🏔", + "snowboarder": "🏂", + "snowboarder_dark_skin_tone": "🏂🏿", + "snowboarder_light_skin_tone": "🏂🏻", + "snowboarder_medium-dark_skin_tone": "🏂🏾", + "snowboarder_medium-light_skin_tone": "🏂🏼", + "snowboarder_medium_skin_tone": "🏂🏽", + "snowflake": "❄", + "snowman": "☃", + "snowman_without_snow": "⛄", + "soap": "🧼", + "soccer_ball": "⚽", + "socks": "🧦", + "softball": "🥎", + "soft_ice_cream": "🍦", + "spade_suit": "♠", + "spaghetti": "🍝", + "sparkle": "❇", + "sparkler": "🎇", + "sparkles": "✨", + "sparkling_heart": "💖", + "speak-no-evil_monkey": "🙊", + "speaker_high_volume": "🔊", + "speaker_low_volume": "🔈", + "speaker_medium_volume": "🔉", + "speaking_head": "🗣", + "speech_balloon": "💬", + "speedboat": "🚤", + "spider": "🕷", + "spider_web": "🕸", + "spiral_calendar": "🗓", + "spiral_notepad": "🗒", + "spiral_shell": "🐚", + "spoon": "🥄", + "sponge": "🧽", + "sport_utility_vehicle": "🚙", + "sports_medal": "🏅", + "spouting_whale": "🐳", + "squid": "🦑", + "squinting_face_with_tongue": "😝", + "stadium": "🏟", + "star-struck": "🤩", + "star_and_crescent": "☪", + "star_of_david": "✡", + "station": "🚉", + "steaming_bowl": "🍜", + "stethoscope": "🩺", + "stop_button": "⏹", + "stop_sign": "🛑", + "stopwatch": "⏱", + "straight_ruler": "📏", + "strawberry": "🍓", + "studio_microphone": "🎙", + "stuffed_flatbread": "🥙", + "sun": "☀", + "sun_behind_cloud": "⛅", + "sun_behind_large_cloud": "🌥", + "sun_behind_rain_cloud": "🌦", + "sun_behind_small_cloud": "🌤", + "sun_with_face": "🌞", + "sunflower": "🌻", + "sunglasses": "😎", + "sunrise": "🌅", + "sunrise_over_mountains": "🌄", + "sunset": "🌇", + "superhero": "🦸", + "supervillain": "🦹", + "sushi": "🍣", + "suspension_railway": "🚟", + "swan": "🦢", + "sweat_droplets": "💦", + "synagogue": "🕍", + "syringe": "💉", + "t-shirt": "👕", + "taco": "🌮", + "takeout_box": "🥡", + "tanabata_tree": "🎋", + "tangerine": "🍊", + "taxi": "🚕", + "teacup_without_handle": "🍵", + "tear-off_calendar": "📆", + "teddy_bear": "🧸", + "telephone": "☎", + "telephone_receiver": "📞", + "telescope": "🔭", + "television": "📺", + "ten-thirty": "🕥", + "ten_o’clock": "🕙", + "tennis": "🎾", + "tent": "⛺", + "test_tube": "🧪", + "thermometer": "🌡", + "thinking_face": "🤔", + "thought_balloon": "💭", + "thread": "🧵", + "three-thirty": "🕞", + "three_o’clock": "🕒", + "thumbs_down": "👎", + "thumbs_down_dark_skin_tone": "👎🏿", + "thumbs_down_light_skin_tone": "👎🏻", + "thumbs_down_medium-dark_skin_tone": "👎🏾", + "thumbs_down_medium-light_skin_tone": "👎🏼", + "thumbs_down_medium_skin_tone": "👎🏽", + "thumbs_up": "👍", + "thumbs_up_dark_skin_tone": "👍🏿", + "thumbs_up_light_skin_tone": "👍🏻", + "thumbs_up_medium-dark_skin_tone": "👍🏾", + "thumbs_up_medium-light_skin_tone": "👍🏼", + "thumbs_up_medium_skin_tone": "👍🏽", + "ticket": "🎫", + "tiger": "🐯", + "tiger_face": "🐯", + "timer_clock": "⏲", + "tired_face": "😫", + "toolbox": "🧰", + "toilet": "🚽", + "tomato": "🍅", + "tongue": "👅", + "tooth": "🦷", + "top_hat": "🎩", + "tornado": "🌪", + "trackball": "🖲", + "tractor": "🚜", + "trade_mark": "™", + "train": "🚋", + "tram": "🚊", + "tram_car": "🚋", + "triangular_flag": "🚩", + "triangular_ruler": "📐", + "trident_emblem": "🔱", + "trolleybus": "🚎", + "trophy": "🏆", + "tropical_drink": "🍹", + "tropical_fish": "🐠", + "trumpet": "🎺", + "tulip": "🌷", + "tumbler_glass": "🥃", + "turtle": "🐢", + "twelve-thirty": "🕧", + "twelve_o’clock": "🕛", + "two-hump_camel": "🐫", + "two-thirty": "🕝", + "two_hearts": "💕", + "two_men_holding_hands": "👬", + "two_o’clock": "🕑", + "two_women_holding_hands": "👭", + "umbrella": "☂", + "umbrella_on_ground": "⛱", + "umbrella_with_rain_drops": "☔", + "unamused_face": "😒", + "unicorn_face": "🦄", + "unlocked": "🔓", + "up-down_arrow": "↕", + "up-left_arrow": "↖", + "up-right_arrow": "↗", + "up_arrow": "⬆", + "upside-down_face": "🙃", + "upwards_button": "🔼", + "vampire": "🧛", + "vampire_dark_skin_tone": "🧛🏿", + "vampire_light_skin_tone": "🧛🏻", + "vampire_medium-dark_skin_tone": "🧛🏾", + "vampire_medium-light_skin_tone": "🧛🏼", + "vampire_medium_skin_tone": "🧛🏽", + "vertical_traffic_light": "🚦", + "vibration_mode": "📳", + "victory_hand": "✌", + "victory_hand_dark_skin_tone": "✌🏿", + "victory_hand_light_skin_tone": "✌🏻", + "victory_hand_medium-dark_skin_tone": "✌🏾", + "victory_hand_medium-light_skin_tone": "✌🏼", + "victory_hand_medium_skin_tone": "✌🏽", + "video_camera": "📹", + "video_game": "🎮", + "videocassette": "📼", + "violin": "🎻", + "volcano": "🌋", + "volleyball": "🏐", + "vulcan_salute": "🖖", + "vulcan_salute_dark_skin_tone": "🖖🏿", + "vulcan_salute_light_skin_tone": "🖖🏻", + "vulcan_salute_medium-dark_skin_tone": "🖖🏾", + "vulcan_salute_medium-light_skin_tone": "🖖🏼", + "vulcan_salute_medium_skin_tone": "🖖🏽", + "waffle": "🧇", + "waning_crescent_moon": "🌘", + "waning_gibbous_moon": "🌖", + "warning": "⚠", + "wastebasket": "🗑", + "watch": "⌚", + "water_buffalo": "🐃", + "water_closet": "🚾", + "water_wave": "🌊", + "watermelon": "🍉", + "waving_hand": "👋", + "waving_hand_dark_skin_tone": "👋🏿", + "waving_hand_light_skin_tone": "👋🏻", + "waving_hand_medium-dark_skin_tone": "👋🏾", + "waving_hand_medium-light_skin_tone": "👋🏼", + "waving_hand_medium_skin_tone": "👋🏽", + "wavy_dash": "〰", + "waxing_crescent_moon": "🌒", + "waxing_gibbous_moon": "🌔", + "weary_cat_face": "🙀", + "weary_face": "😩", + "wedding": "💒", + "whale": "🐳", + "wheel_of_dharma": "☸", + "wheelchair_symbol": "♿", + "white_circle": "⚪", + "white_exclamation_mark": "❕", + "white_flag": "🏳", + "white_flower": "💮", + "white_hair": "🦳", + "white-haired_man": "👨\u200d🦳", + "white-haired_woman": "👩\u200d🦳", + "white_heart": "🤍", + "white_heavy_check_mark": "✅", + "white_large_square": "⬜", + "white_medium-small_square": "◽", + "white_medium_square": "◻", + "white_medium_star": "⭐", + "white_question_mark": "❔", + "white_small_square": "▫", + "white_square_button": "🔳", + "wilted_flower": "🥀", + "wind_chime": "🎐", + "wind_face": "🌬", + "wine_glass": "🍷", + "winking_face": "😉", + "winking_face_with_tongue": "😜", + "wolf_face": "🐺", + "woman": "👩", + "woman_artist": "👩\u200d🎨", + "woman_artist_dark_skin_tone": "👩🏿\u200d🎨", + "woman_artist_light_skin_tone": "👩🏻\u200d🎨", + "woman_artist_medium-dark_skin_tone": "👩🏾\u200d🎨", + "woman_artist_medium-light_skin_tone": "👩🏼\u200d🎨", + "woman_artist_medium_skin_tone": "👩🏽\u200d🎨", + "woman_astronaut": "👩\u200d🚀", + "woman_astronaut_dark_skin_tone": "👩🏿\u200d🚀", + "woman_astronaut_light_skin_tone": "👩🏻\u200d🚀", + "woman_astronaut_medium-dark_skin_tone": "👩🏾\u200d🚀", + "woman_astronaut_medium-light_skin_tone": "👩🏼\u200d🚀", + "woman_astronaut_medium_skin_tone": "👩🏽\u200d🚀", + "woman_biking": "🚴\u200d♀️", + "woman_biking_dark_skin_tone": "🚴🏿\u200d♀️", + "woman_biking_light_skin_tone": "🚴🏻\u200d♀️", + "woman_biking_medium-dark_skin_tone": "🚴🏾\u200d♀️", + "woman_biking_medium-light_skin_tone": "🚴🏼\u200d♀️", + "woman_biking_medium_skin_tone": "🚴🏽\u200d♀️", + "woman_bouncing_ball": "⛹️\u200d♀️", + "woman_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♀️", + "woman_bouncing_ball_light_skin_tone": "⛹🏻\u200d♀️", + "woman_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♀️", + "woman_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♀️", + "woman_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♀️", + "woman_bowing": "🙇\u200d♀️", + "woman_bowing_dark_skin_tone": "🙇🏿\u200d♀️", + "woman_bowing_light_skin_tone": "🙇🏻\u200d♀️", + "woman_bowing_medium-dark_skin_tone": "🙇🏾\u200d♀️", + "woman_bowing_medium-light_skin_tone": "🙇🏼\u200d♀️", + "woman_bowing_medium_skin_tone": "🙇🏽\u200d♀️", + "woman_cartwheeling": "🤸\u200d♀️", + "woman_cartwheeling_dark_skin_tone": "🤸🏿\u200d♀️", + "woman_cartwheeling_light_skin_tone": "🤸🏻\u200d♀️", + "woman_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♀️", + "woman_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♀️", + "woman_cartwheeling_medium_skin_tone": "🤸🏽\u200d♀️", + "woman_climbing": "🧗\u200d♀️", + "woman_climbing_dark_skin_tone": "🧗🏿\u200d♀️", + "woman_climbing_light_skin_tone": "🧗🏻\u200d♀️", + "woman_climbing_medium-dark_skin_tone": "🧗🏾\u200d♀️", + "woman_climbing_medium-light_skin_tone": "🧗🏼\u200d♀️", + "woman_climbing_medium_skin_tone": "🧗🏽\u200d♀️", + "woman_construction_worker": "👷\u200d♀️", + "woman_construction_worker_dark_skin_tone": "👷🏿\u200d♀️", + "woman_construction_worker_light_skin_tone": "👷🏻\u200d♀️", + "woman_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♀️", + "woman_construction_worker_medium-light_skin_tone": "👷🏼\u200d♀️", + "woman_construction_worker_medium_skin_tone": "👷🏽\u200d♀️", + "woman_cook": "👩\u200d🍳", + "woman_cook_dark_skin_tone": "👩🏿\u200d🍳", + "woman_cook_light_skin_tone": "👩🏻\u200d🍳", + "woman_cook_medium-dark_skin_tone": "👩🏾\u200d🍳", + "woman_cook_medium-light_skin_tone": "👩🏼\u200d🍳", + "woman_cook_medium_skin_tone": "👩🏽\u200d🍳", + "woman_dancing": "💃", + "woman_dancing_dark_skin_tone": "💃🏿", + "woman_dancing_light_skin_tone": "💃🏻", + "woman_dancing_medium-dark_skin_tone": "💃🏾", + "woman_dancing_medium-light_skin_tone": "💃🏼", + "woman_dancing_medium_skin_tone": "💃🏽", + "woman_dark_skin_tone": "👩🏿", + "woman_detective": "🕵️\u200d♀️", + "woman_detective_dark_skin_tone": "🕵🏿\u200d♀️", + "woman_detective_light_skin_tone": "🕵🏻\u200d♀️", + "woman_detective_medium-dark_skin_tone": "🕵🏾\u200d♀️", + "woman_detective_medium-light_skin_tone": "🕵🏼\u200d♀️", + "woman_detective_medium_skin_tone": "🕵🏽\u200d♀️", + "woman_elf": "🧝\u200d♀️", + "woman_elf_dark_skin_tone": "🧝🏿\u200d♀️", + "woman_elf_light_skin_tone": "🧝🏻\u200d♀️", + "woman_elf_medium-dark_skin_tone": "🧝🏾\u200d♀️", + "woman_elf_medium-light_skin_tone": "🧝🏼\u200d♀️", + "woman_elf_medium_skin_tone": "🧝🏽\u200d♀️", + "woman_facepalming": "🤦\u200d♀️", + "woman_facepalming_dark_skin_tone": "🤦🏿\u200d♀️", + "woman_facepalming_light_skin_tone": "🤦🏻\u200d♀️", + "woman_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♀️", + "woman_facepalming_medium-light_skin_tone": "🤦🏼\u200d♀️", + "woman_facepalming_medium_skin_tone": "🤦🏽\u200d♀️", + "woman_factory_worker": "👩\u200d🏭", + "woman_factory_worker_dark_skin_tone": "👩🏿\u200d🏭", + "woman_factory_worker_light_skin_tone": "👩🏻\u200d🏭", + "woman_factory_worker_medium-dark_skin_tone": "👩🏾\u200d🏭", + "woman_factory_worker_medium-light_skin_tone": "👩🏼\u200d🏭", + "woman_factory_worker_medium_skin_tone": "👩🏽\u200d🏭", + "woman_fairy": "🧚\u200d♀️", + "woman_fairy_dark_skin_tone": "🧚🏿\u200d♀️", + "woman_fairy_light_skin_tone": "🧚🏻\u200d♀️", + "woman_fairy_medium-dark_skin_tone": "🧚🏾\u200d♀️", + "woman_fairy_medium-light_skin_tone": "🧚🏼\u200d♀️", + "woman_fairy_medium_skin_tone": "🧚🏽\u200d♀️", + "woman_farmer": "👩\u200d🌾", + "woman_farmer_dark_skin_tone": "👩🏿\u200d🌾", + "woman_farmer_light_skin_tone": "👩🏻\u200d🌾", + "woman_farmer_medium-dark_skin_tone": "👩🏾\u200d🌾", + "woman_farmer_medium-light_skin_tone": "👩🏼\u200d🌾", + "woman_farmer_medium_skin_tone": "👩🏽\u200d🌾", + "woman_firefighter": "👩\u200d🚒", + "woman_firefighter_dark_skin_tone": "👩🏿\u200d🚒", + "woman_firefighter_light_skin_tone": "👩🏻\u200d🚒", + "woman_firefighter_medium-dark_skin_tone": "👩🏾\u200d🚒", + "woman_firefighter_medium-light_skin_tone": "👩🏼\u200d🚒", + "woman_firefighter_medium_skin_tone": "👩🏽\u200d🚒", + "woman_frowning": "🙍\u200d♀️", + "woman_frowning_dark_skin_tone": "🙍🏿\u200d♀️", + "woman_frowning_light_skin_tone": "🙍🏻\u200d♀️", + "woman_frowning_medium-dark_skin_tone": "🙍🏾\u200d♀️", + "woman_frowning_medium-light_skin_tone": "🙍🏼\u200d♀️", + "woman_frowning_medium_skin_tone": "🙍🏽\u200d♀️", + "woman_genie": "🧞\u200d♀️", + "woman_gesturing_no": "🙅\u200d♀️", + "woman_gesturing_no_dark_skin_tone": "🙅🏿\u200d♀️", + "woman_gesturing_no_light_skin_tone": "🙅🏻\u200d♀️", + "woman_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♀️", + "woman_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♀️", + "woman_gesturing_no_medium_skin_tone": "🙅🏽\u200d♀️", + "woman_gesturing_ok": "🙆\u200d♀️", + "woman_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♀️", + "woman_gesturing_ok_light_skin_tone": "🙆🏻\u200d♀️", + "woman_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♀️", + "woman_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♀️", + "woman_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♀️", + "woman_getting_haircut": "💇\u200d♀️", + "woman_getting_haircut_dark_skin_tone": "💇🏿\u200d♀️", + "woman_getting_haircut_light_skin_tone": "💇🏻\u200d♀️", + "woman_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♀️", + "woman_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♀️", + "woman_getting_haircut_medium_skin_tone": "💇🏽\u200d♀️", + "woman_getting_massage": "💆\u200d♀️", + "woman_getting_massage_dark_skin_tone": "💆🏿\u200d♀️", + "woman_getting_massage_light_skin_tone": "💆🏻\u200d♀️", + "woman_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♀️", + "woman_getting_massage_medium-light_skin_tone": "💆🏼\u200d♀️", + "woman_getting_massage_medium_skin_tone": "💆🏽\u200d♀️", + "woman_golfing": "🏌️\u200d♀️", + "woman_golfing_dark_skin_tone": "🏌🏿\u200d♀️", + "woman_golfing_light_skin_tone": "🏌🏻\u200d♀️", + "woman_golfing_medium-dark_skin_tone": "🏌🏾\u200d♀️", + "woman_golfing_medium-light_skin_tone": "🏌🏼\u200d♀️", + "woman_golfing_medium_skin_tone": "🏌🏽\u200d♀️", + "woman_guard": "💂\u200d♀️", + "woman_guard_dark_skin_tone": "💂🏿\u200d♀️", + "woman_guard_light_skin_tone": "💂🏻\u200d♀️", + "woman_guard_medium-dark_skin_tone": "💂🏾\u200d♀️", + "woman_guard_medium-light_skin_tone": "💂🏼\u200d♀️", + "woman_guard_medium_skin_tone": "💂🏽\u200d♀️", + "woman_health_worker": "👩\u200d⚕️", + "woman_health_worker_dark_skin_tone": "👩🏿\u200d⚕️", + "woman_health_worker_light_skin_tone": "👩🏻\u200d⚕️", + "woman_health_worker_medium-dark_skin_tone": "👩🏾\u200d⚕️", + "woman_health_worker_medium-light_skin_tone": "👩🏼\u200d⚕️", + "woman_health_worker_medium_skin_tone": "👩🏽\u200d⚕️", + "woman_in_lotus_position": "🧘\u200d♀️", + "woman_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♀️", + "woman_in_lotus_position_light_skin_tone": "🧘🏻\u200d♀️", + "woman_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♀️", + "woman_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♀️", + "woman_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♀️", + "woman_in_manual_wheelchair": "👩\u200d🦽", + "woman_in_motorized_wheelchair": "👩\u200d🦼", + "woman_in_steamy_room": "🧖\u200d♀️", + "woman_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♀️", + "woman_in_steamy_room_light_skin_tone": "🧖🏻\u200d♀️", + "woman_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♀️", + "woman_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♀️", + "woman_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♀️", + "woman_judge": "👩\u200d⚖️", + "woman_judge_dark_skin_tone": "👩🏿\u200d⚖️", + "woman_judge_light_skin_tone": "👩🏻\u200d⚖️", + "woman_judge_medium-dark_skin_tone": "👩🏾\u200d⚖️", + "woman_judge_medium-light_skin_tone": "👩🏼\u200d⚖️", + "woman_judge_medium_skin_tone": "👩🏽\u200d⚖️", + "woman_juggling": "🤹\u200d♀️", + "woman_juggling_dark_skin_tone": "🤹🏿\u200d♀️", + "woman_juggling_light_skin_tone": "🤹🏻\u200d♀️", + "woman_juggling_medium-dark_skin_tone": "🤹🏾\u200d♀️", + "woman_juggling_medium-light_skin_tone": "🤹🏼\u200d♀️", + "woman_juggling_medium_skin_tone": "🤹🏽\u200d♀️", + "woman_lifting_weights": "🏋️\u200d♀️", + "woman_lifting_weights_dark_skin_tone": "🏋🏿\u200d♀️", + "woman_lifting_weights_light_skin_tone": "🏋🏻\u200d♀️", + "woman_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♀️", + "woman_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♀️", + "woman_lifting_weights_medium_skin_tone": "🏋🏽\u200d♀️", + "woman_light_skin_tone": "👩🏻", + "woman_mage": "🧙\u200d♀️", + "woman_mage_dark_skin_tone": "🧙🏿\u200d♀️", + "woman_mage_light_skin_tone": "🧙🏻\u200d♀️", + "woman_mage_medium-dark_skin_tone": "🧙🏾\u200d♀️", + "woman_mage_medium-light_skin_tone": "🧙🏼\u200d♀️", + "woman_mage_medium_skin_tone": "🧙🏽\u200d♀️", + "woman_mechanic": "👩\u200d🔧", + "woman_mechanic_dark_skin_tone": "👩🏿\u200d🔧", + "woman_mechanic_light_skin_tone": "👩🏻\u200d🔧", + "woman_mechanic_medium-dark_skin_tone": "👩🏾\u200d🔧", + "woman_mechanic_medium-light_skin_tone": "👩🏼\u200d🔧", + "woman_mechanic_medium_skin_tone": "👩🏽\u200d🔧", + "woman_medium-dark_skin_tone": "👩🏾", + "woman_medium-light_skin_tone": "👩🏼", + "woman_medium_skin_tone": "👩🏽", + "woman_mountain_biking": "🚵\u200d♀️", + "woman_mountain_biking_dark_skin_tone": "🚵🏿\u200d♀️", + "woman_mountain_biking_light_skin_tone": "🚵🏻\u200d♀️", + "woman_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♀️", + "woman_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♀️", + "woman_mountain_biking_medium_skin_tone": "🚵🏽\u200d♀️", + "woman_office_worker": "👩\u200d💼", + "woman_office_worker_dark_skin_tone": "👩🏿\u200d💼", + "woman_office_worker_light_skin_tone": "👩🏻\u200d💼", + "woman_office_worker_medium-dark_skin_tone": "👩🏾\u200d💼", + "woman_office_worker_medium-light_skin_tone": "👩🏼\u200d💼", + "woman_office_worker_medium_skin_tone": "👩🏽\u200d💼", + "woman_pilot": "👩\u200d✈️", + "woman_pilot_dark_skin_tone": "👩🏿\u200d✈️", + "woman_pilot_light_skin_tone": "👩🏻\u200d✈️", + "woman_pilot_medium-dark_skin_tone": "👩🏾\u200d✈️", + "woman_pilot_medium-light_skin_tone": "👩🏼\u200d✈️", + "woman_pilot_medium_skin_tone": "👩🏽\u200d✈️", + "woman_playing_handball": "🤾\u200d♀️", + "woman_playing_handball_dark_skin_tone": "🤾🏿\u200d♀️", + "woman_playing_handball_light_skin_tone": "🤾🏻\u200d♀️", + "woman_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♀️", + "woman_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♀️", + "woman_playing_handball_medium_skin_tone": "🤾🏽\u200d♀️", + "woman_playing_water_polo": "🤽\u200d♀️", + "woman_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♀️", + "woman_playing_water_polo_light_skin_tone": "🤽🏻\u200d♀️", + "woman_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♀️", + "woman_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♀️", + "woman_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♀️", + "woman_police_officer": "👮\u200d♀️", + "woman_police_officer_dark_skin_tone": "👮🏿\u200d♀️", + "woman_police_officer_light_skin_tone": "👮🏻\u200d♀️", + "woman_police_officer_medium-dark_skin_tone": "👮🏾\u200d♀️", + "woman_police_officer_medium-light_skin_tone": "👮🏼\u200d♀️", + "woman_police_officer_medium_skin_tone": "👮🏽\u200d♀️", + "woman_pouting": "🙎\u200d♀️", + "woman_pouting_dark_skin_tone": "🙎🏿\u200d♀️", + "woman_pouting_light_skin_tone": "🙎🏻\u200d♀️", + "woman_pouting_medium-dark_skin_tone": "🙎🏾\u200d♀️", + "woman_pouting_medium-light_skin_tone": "🙎🏼\u200d♀️", + "woman_pouting_medium_skin_tone": "🙎🏽\u200d♀️", + "woman_raising_hand": "🙋\u200d♀️", + "woman_raising_hand_dark_skin_tone": "🙋🏿\u200d♀️", + "woman_raising_hand_light_skin_tone": "🙋🏻\u200d♀️", + "woman_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♀️", + "woman_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♀️", + "woman_raising_hand_medium_skin_tone": "🙋🏽\u200d♀️", + "woman_rowing_boat": "🚣\u200d♀️", + "woman_rowing_boat_dark_skin_tone": "🚣🏿\u200d♀️", + "woman_rowing_boat_light_skin_tone": "🚣🏻\u200d♀️", + "woman_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♀️", + "woman_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♀️", + "woman_rowing_boat_medium_skin_tone": "🚣🏽\u200d♀️", + "woman_running": "🏃\u200d♀️", + "woman_running_dark_skin_tone": "🏃🏿\u200d♀️", + "woman_running_light_skin_tone": "🏃🏻\u200d♀️", + "woman_running_medium-dark_skin_tone": "🏃🏾\u200d♀️", + "woman_running_medium-light_skin_tone": "🏃🏼\u200d♀️", + "woman_running_medium_skin_tone": "🏃🏽\u200d♀️", + "woman_scientist": "👩\u200d🔬", + "woman_scientist_dark_skin_tone": "👩🏿\u200d🔬", + "woman_scientist_light_skin_tone": "👩🏻\u200d🔬", + "woman_scientist_medium-dark_skin_tone": "👩🏾\u200d🔬", + "woman_scientist_medium-light_skin_tone": "👩🏼\u200d🔬", + "woman_scientist_medium_skin_tone": "👩🏽\u200d🔬", + "woman_shrugging": "🤷\u200d♀️", + "woman_shrugging_dark_skin_tone": "🤷🏿\u200d♀️", + "woman_shrugging_light_skin_tone": "🤷🏻\u200d♀️", + "woman_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♀️", + "woman_shrugging_medium-light_skin_tone": "🤷🏼\u200d♀️", + "woman_shrugging_medium_skin_tone": "🤷🏽\u200d♀️", + "woman_singer": "👩\u200d🎤", + "woman_singer_dark_skin_tone": "👩🏿\u200d🎤", + "woman_singer_light_skin_tone": "👩🏻\u200d🎤", + "woman_singer_medium-dark_skin_tone": "👩🏾\u200d🎤", + "woman_singer_medium-light_skin_tone": "👩🏼\u200d🎤", + "woman_singer_medium_skin_tone": "👩🏽\u200d🎤", + "woman_student": "👩\u200d🎓", + "woman_student_dark_skin_tone": "👩🏿\u200d🎓", + "woman_student_light_skin_tone": "👩🏻\u200d🎓", + "woman_student_medium-dark_skin_tone": "👩🏾\u200d🎓", + "woman_student_medium-light_skin_tone": "👩🏼\u200d🎓", + "woman_student_medium_skin_tone": "👩🏽\u200d🎓", + "woman_surfing": "🏄\u200d♀️", + "woman_surfing_dark_skin_tone": "🏄🏿\u200d♀️", + "woman_surfing_light_skin_tone": "🏄🏻\u200d♀️", + "woman_surfing_medium-dark_skin_tone": "🏄🏾\u200d♀️", + "woman_surfing_medium-light_skin_tone": "🏄🏼\u200d♀️", + "woman_surfing_medium_skin_tone": "🏄🏽\u200d♀️", + "woman_swimming": "🏊\u200d♀️", + "woman_swimming_dark_skin_tone": "🏊🏿\u200d♀️", + "woman_swimming_light_skin_tone": "🏊🏻\u200d♀️", + "woman_swimming_medium-dark_skin_tone": "🏊🏾\u200d♀️", + "woman_swimming_medium-light_skin_tone": "🏊🏼\u200d♀️", + "woman_swimming_medium_skin_tone": "🏊🏽\u200d♀️", + "woman_teacher": "👩\u200d🏫", + "woman_teacher_dark_skin_tone": "👩🏿\u200d🏫", + "woman_teacher_light_skin_tone": "👩🏻\u200d🏫", + "woman_teacher_medium-dark_skin_tone": "👩🏾\u200d🏫", + "woman_teacher_medium-light_skin_tone": "👩🏼\u200d🏫", + "woman_teacher_medium_skin_tone": "👩🏽\u200d🏫", + "woman_technologist": "👩\u200d💻", + "woman_technologist_dark_skin_tone": "👩🏿\u200d💻", + "woman_technologist_light_skin_tone": "👩🏻\u200d💻", + "woman_technologist_medium-dark_skin_tone": "👩🏾\u200d💻", + "woman_technologist_medium-light_skin_tone": "👩🏼\u200d💻", + "woman_technologist_medium_skin_tone": "👩🏽\u200d💻", + "woman_tipping_hand": "💁\u200d♀️", + "woman_tipping_hand_dark_skin_tone": "💁🏿\u200d♀️", + "woman_tipping_hand_light_skin_tone": "💁🏻\u200d♀️", + "woman_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♀️", + "woman_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♀️", + "woman_tipping_hand_medium_skin_tone": "💁🏽\u200d♀️", + "woman_vampire": "🧛\u200d♀️", + "woman_vampire_dark_skin_tone": "🧛🏿\u200d♀️", + "woman_vampire_light_skin_tone": "🧛🏻\u200d♀️", + "woman_vampire_medium-dark_skin_tone": "🧛🏾\u200d♀️", + "woman_vampire_medium-light_skin_tone": "🧛🏼\u200d♀️", + "woman_vampire_medium_skin_tone": "🧛🏽\u200d♀️", + "woman_walking": "🚶\u200d♀️", + "woman_walking_dark_skin_tone": "🚶🏿\u200d♀️", + "woman_walking_light_skin_tone": "🚶🏻\u200d♀️", + "woman_walking_medium-dark_skin_tone": "🚶🏾\u200d♀️", + "woman_walking_medium-light_skin_tone": "🚶🏼\u200d♀️", + "woman_walking_medium_skin_tone": "🚶🏽\u200d♀️", + "woman_wearing_turban": "👳\u200d♀️", + "woman_wearing_turban_dark_skin_tone": "👳🏿\u200d♀️", + "woman_wearing_turban_light_skin_tone": "👳🏻\u200d♀️", + "woman_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♀️", + "woman_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♀️", + "woman_wearing_turban_medium_skin_tone": "👳🏽\u200d♀️", + "woman_with_headscarf": "🧕", + "woman_with_headscarf_dark_skin_tone": "🧕🏿", + "woman_with_headscarf_light_skin_tone": "🧕🏻", + "woman_with_headscarf_medium-dark_skin_tone": "🧕🏾", + "woman_with_headscarf_medium-light_skin_tone": "🧕🏼", + "woman_with_headscarf_medium_skin_tone": "🧕🏽", + "woman_with_probing_cane": "👩\u200d🦯", + "woman_zombie": "🧟\u200d♀️", + "woman’s_boot": "👢", + "woman’s_clothes": "👚", + "woman’s_hat": "👒", + "woman’s_sandal": "👡", + "women_with_bunny_ears": "👯\u200d♀️", + "women_wrestling": "🤼\u200d♀️", + "women’s_room": "🚺", + "woozy_face": "🥴", + "world_map": "🗺", + "worried_face": "😟", + "wrapped_gift": "🎁", + "wrench": "🔧", + "writing_hand": "✍", + "writing_hand_dark_skin_tone": "✍🏿", + "writing_hand_light_skin_tone": "✍🏻", + "writing_hand_medium-dark_skin_tone": "✍🏾", + "writing_hand_medium-light_skin_tone": "✍🏼", + "writing_hand_medium_skin_tone": "✍🏽", + "yarn": "🧶", + "yawning_face": "🥱", + "yellow_circle": "🟡", + "yellow_heart": "💛", + "yellow_square": "🟨", + "yen_banknote": "💴", + "yo-yo": "🪀", + "yin_yang": "☯", + "zany_face": "🤪", + "zebra": "🦓", + "zipper-mouth_face": "🤐", + "zombie": "🧟", + "zzz": "💤", + "åland_islands": "🇦🇽", + "keycap_asterisk": "*⃣", + "keycap_digit_eight": "8⃣", + "keycap_digit_five": "5⃣", + "keycap_digit_four": "4⃣", + "keycap_digit_nine": "9⃣", + "keycap_digit_one": "1⃣", + "keycap_digit_seven": "7⃣", + "keycap_digit_six": "6⃣", + "keycap_digit_three": "3⃣", + "keycap_digit_two": "2⃣", + "keycap_digit_zero": "0⃣", + "keycap_number_sign": "#⃣", + "light_skin_tone": "🏻", + "medium_light_skin_tone": "🏼", + "medium_skin_tone": "🏽", + "medium_dark_skin_tone": "🏾", + "dark_skin_tone": "🏿", + "regional_indicator_symbol_letter_a": "🇦", + "regional_indicator_symbol_letter_b": "🇧", + "regional_indicator_symbol_letter_c": "🇨", + "regional_indicator_symbol_letter_d": "🇩", + "regional_indicator_symbol_letter_e": "🇪", + "regional_indicator_symbol_letter_f": "🇫", + "regional_indicator_symbol_letter_g": "🇬", + "regional_indicator_symbol_letter_h": "🇭", + "regional_indicator_symbol_letter_i": "🇮", + "regional_indicator_symbol_letter_j": "🇯", + "regional_indicator_symbol_letter_k": "🇰", + "regional_indicator_symbol_letter_l": "🇱", + "regional_indicator_symbol_letter_m": "🇲", + "regional_indicator_symbol_letter_n": "🇳", + "regional_indicator_symbol_letter_o": "🇴", + "regional_indicator_symbol_letter_p": "🇵", + "regional_indicator_symbol_letter_q": "🇶", + "regional_indicator_symbol_letter_r": "🇷", + "regional_indicator_symbol_letter_s": "🇸", + "regional_indicator_symbol_letter_t": "🇹", + "regional_indicator_symbol_letter_u": "🇺", + "regional_indicator_symbol_letter_v": "🇻", + "regional_indicator_symbol_letter_w": "🇼", + "regional_indicator_symbol_letter_x": "🇽", + "regional_indicator_symbol_letter_y": "🇾", + "regional_indicator_symbol_letter_z": "🇿", + "airplane_arriving": "🛬", + "space_invader": "👾", + "football": "🏈", + "anger": "💢", + "angry": "😠", + "anguished": "😧", + "signal_strength": "📶", + "arrows_counterclockwise": "🔄", + "arrow_heading_down": "⤵", + "arrow_heading_up": "⤴", + "art": "🎨", + "astonished": "😲", + "athletic_shoe": "👟", + "atm": "🏧", + "car": "🚗", + "red_car": "🚗", + "angel": "👼", + "back": "🔙", + "badminton_racquet_and_shuttlecock": "🏸", + "dollar": "💵", + "euro": "💶", + "pound": "💷", + "yen": "💴", + "barber": "💈", + "bath": "🛀", + "bear": "🐻", + "heartbeat": "💓", + "beer": "🍺", + "no_bell": "🔕", + "bento": "🍱", + "bike": "🚲", + "bicyclist": "🚴", + "8ball": "🎱", + "biohazard_sign": "☣", + "birthday": "🎂", + "black_circle_for_record": "⏺", + "clubs": "♣", + "diamonds": "♦", + "arrow_double_down": "⏬", + "hearts": "♥", + "rewind": "⏪", + "black_left__pointing_double_triangle_with_vertical_bar": "⏮", + "arrow_backward": "◀", + "black_medium_small_square": "◾", + "question": "❓", + "fast_forward": "⏩", + "black_right__pointing_double_triangle_with_vertical_bar": "⏭", + "arrow_forward": "▶", + "black_right__pointing_triangle_with_double_vertical_bar": "⏯", + "arrow_right": "➡", + "spades": "♠", + "black_square_for_stop": "⏹", + "sunny": "☀", + "phone": "☎", + "recycle": "♻", + "arrow_double_up": "⏫", + "busstop": "🚏", + "date": "📅", + "flags": "🎏", + "cat2": "🐈", + "joy_cat": "😹", + "smirk_cat": "😼", + "chart_with_downwards_trend": "📉", + "chart_with_upwards_trend": "📈", + "chart": "💹", + "mega": "📣", + "checkered_flag": "🏁", + "accept": "🉑", + "ideograph_advantage": "🉐", + "congratulations": "㊗", + "secret": "㊙", + "m": "Ⓜ", + "city_sunset": "🌆", + "clapper": "🎬", + "clap": "👏", + "beers": "🍻", + "clock830": "🕣", + "clock8": "🕗", + "clock1130": "🕦", + "clock11": "🕚", + "clock530": "🕠", + "clock5": "🕔", + "clock430": "🕟", + "clock4": "🕓", + "clock930": "🕤", + "clock9": "🕘", + "clock130": "🕜", + "clock1": "🕐", + "clock730": "🕢", + "clock7": "🕖", + "clock630": "🕡", + "clock6": "🕕", + "clock1030": "🕥", + "clock10": "🕙", + "clock330": "🕞", + "clock3": "🕒", + "clock1230": "🕧", + "clock12": "🕛", + "clock230": "🕝", + "clock2": "🕑", + "arrows_clockwise": "🔃", + "repeat": "🔁", + "repeat_one": "🔂", + "closed_lock_with_key": "🔐", + "mailbox_closed": "📪", + "mailbox": "📫", + "cloud_with_tornado": "🌪", + "cocktail": "🍸", + "boom": "💥", + "compression": "🗜", + "confounded": "😖", + "confused": "😕", + "rice": "🍚", + "cow2": "🐄", + "cricket_bat_and_ball": "🏏", + "x": "❌", + "cry": "😢", + "curry": "🍛", + "dagger_knife": "🗡", + "dancer": "💃", + "dark_sunglasses": "🕶", + "dash": "💨", + "truck": "🚚", + "derelict_house_building": "🏚", + "diamond_shape_with_a_dot_inside": "💠", + "dart": "🎯", + "disappointed_relieved": "😥", + "disappointed": "😞", + "do_not_litter": "🚯", + "dog2": "🐕", + "flipper": "🐬", + "loop": "➿", + "bangbang": "‼", + "double_vertical_bar": "⏸", + "dove_of_peace": "🕊", + "small_red_triangle_down": "🔻", + "arrow_down_small": "🔽", + "arrow_down": "⬇", + "dromedary_camel": "🐪", + "e__mail": "📧", + "corn": "🌽", + "ear_of_rice": "🌾", + "earth_americas": "🌎", + "earth_asia": "🌏", + "earth_africa": "🌍", + "eight_pointed_black_star": "✴", + "eight_spoked_asterisk": "✳", + "eject_symbol": "⏏", + "bulb": "💡", + "emoji_modifier_fitzpatrick_type__1__2": "🏻", + "emoji_modifier_fitzpatrick_type__3": "🏼", + "emoji_modifier_fitzpatrick_type__4": "🏽", + "emoji_modifier_fitzpatrick_type__5": "🏾", + "emoji_modifier_fitzpatrick_type__6": "🏿", + "end": "🔚", + "email": "✉", + "european_castle": "🏰", + "european_post_office": "🏤", + "interrobang": "⁉", + "expressionless": "😑", + "eyeglasses": "👓", + "massage": "💆", + "yum": "😋", + "scream": "😱", + "kissing_heart": "😘", + "sweat": "😓", + "face_with_head__bandage": "🤕", + "triumph": "😤", + "mask": "😷", + "no_good": "🙅", + "ok_woman": "🙆", + "open_mouth": "😮", + "cold_sweat": "😰", + "stuck_out_tongue": "😛", + "stuck_out_tongue_closed_eyes": "😝", + "stuck_out_tongue_winking_eye": "😜", + "joy": "😂", + "no_mouth": "😶", + "santa": "🎅", + "fax": "📠", + "fearful": "😨", + "field_hockey_stick_and_ball": "🏑", + "first_quarter_moon_with_face": "🌛", + "fish_cake": "🍥", + "fishing_pole_and_fish": "🎣", + "facepunch": "👊", + "punch": "👊", + "flag_for_afghanistan": "🇦🇫", + "flag_for_albania": "🇦🇱", + "flag_for_algeria": "🇩🇿", + "flag_for_american_samoa": "🇦🇸", + "flag_for_andorra": "🇦🇩", + "flag_for_angola": "🇦🇴", + "flag_for_anguilla": "🇦🇮", + "flag_for_antarctica": "🇦🇶", + "flag_for_antigua_&_barbuda": "🇦🇬", + "flag_for_argentina": "🇦🇷", + "flag_for_armenia": "🇦🇲", + "flag_for_aruba": "🇦🇼", + "flag_for_ascension_island": "🇦🇨", + "flag_for_australia": "🇦🇺", + "flag_for_austria": "🇦🇹", + "flag_for_azerbaijan": "🇦🇿", + "flag_for_bahamas": "🇧🇸", + "flag_for_bahrain": "🇧🇭", + "flag_for_bangladesh": "🇧🇩", + "flag_for_barbados": "🇧🇧", + "flag_for_belarus": "🇧🇾", + "flag_for_belgium": "🇧🇪", + "flag_for_belize": "🇧🇿", + "flag_for_benin": "🇧🇯", + "flag_for_bermuda": "🇧🇲", + "flag_for_bhutan": "🇧🇹", + "flag_for_bolivia": "🇧🇴", + "flag_for_bosnia_&_herzegovina": "🇧🇦", + "flag_for_botswana": "🇧🇼", + "flag_for_bouvet_island": "🇧🇻", + "flag_for_brazil": "🇧🇷", + "flag_for_british_indian_ocean_territory": "🇮🇴", + "flag_for_british_virgin_islands": "🇻🇬", + "flag_for_brunei": "🇧🇳", + "flag_for_bulgaria": "🇧🇬", + "flag_for_burkina_faso": "🇧🇫", + "flag_for_burundi": "🇧🇮", + "flag_for_cambodia": "🇰🇭", + "flag_for_cameroon": "🇨🇲", + "flag_for_canada": "🇨🇦", + "flag_for_canary_islands": "🇮🇨", + "flag_for_cape_verde": "🇨🇻", + "flag_for_caribbean_netherlands": "🇧🇶", + "flag_for_cayman_islands": "🇰🇾", + "flag_for_central_african_republic": "🇨🇫", + "flag_for_ceuta_&_melilla": "🇪🇦", + "flag_for_chad": "🇹🇩", + "flag_for_chile": "🇨🇱", + "flag_for_china": "🇨🇳", + "flag_for_christmas_island": "🇨🇽", + "flag_for_clipperton_island": "🇨🇵", + "flag_for_cocos__islands": "🇨🇨", + "flag_for_colombia": "🇨🇴", + "flag_for_comoros": "🇰🇲", + "flag_for_congo____brazzaville": "🇨🇬", + "flag_for_congo____kinshasa": "🇨🇩", + "flag_for_cook_islands": "🇨🇰", + "flag_for_costa_rica": "🇨🇷", + "flag_for_croatia": "🇭🇷", + "flag_for_cuba": "🇨🇺", + "flag_for_curaçao": "🇨🇼", + "flag_for_cyprus": "🇨🇾", + "flag_for_czech_republic": "🇨🇿", + "flag_for_côte_d’ivoire": "🇨🇮", + "flag_for_denmark": "🇩🇰", + "flag_for_diego_garcia": "🇩🇬", + "flag_for_djibouti": "🇩🇯", + "flag_for_dominica": "🇩🇲", + "flag_for_dominican_republic": "🇩🇴", + "flag_for_ecuador": "🇪🇨", + "flag_for_egypt": "🇪🇬", + "flag_for_el_salvador": "🇸🇻", + "flag_for_equatorial_guinea": "🇬🇶", + "flag_for_eritrea": "🇪🇷", + "flag_for_estonia": "🇪🇪", + "flag_for_ethiopia": "🇪🇹", + "flag_for_european_union": "🇪🇺", + "flag_for_falkland_islands": "🇫🇰", + "flag_for_faroe_islands": "🇫🇴", + "flag_for_fiji": "🇫🇯", + "flag_for_finland": "🇫🇮", + "flag_for_france": "🇫🇷", + "flag_for_french_guiana": "🇬🇫", + "flag_for_french_polynesia": "🇵🇫", + "flag_for_french_southern_territories": "🇹🇫", + "flag_for_gabon": "🇬🇦", + "flag_for_gambia": "🇬🇲", + "flag_for_georgia": "🇬🇪", + "flag_for_germany": "🇩🇪", + "flag_for_ghana": "🇬🇭", + "flag_for_gibraltar": "🇬🇮", + "flag_for_greece": "🇬🇷", + "flag_for_greenland": "🇬🇱", + "flag_for_grenada": "🇬🇩", + "flag_for_guadeloupe": "🇬🇵", + "flag_for_guam": "🇬🇺", + "flag_for_guatemala": "🇬🇹", + "flag_for_guernsey": "🇬🇬", + "flag_for_guinea": "🇬🇳", + "flag_for_guinea__bissau": "🇬🇼", + "flag_for_guyana": "🇬🇾", + "flag_for_haiti": "🇭🇹", + "flag_for_heard_&_mcdonald_islands": "🇭🇲", + "flag_for_honduras": "🇭🇳", + "flag_for_hong_kong": "🇭🇰", + "flag_for_hungary": "🇭🇺", + "flag_for_iceland": "🇮🇸", + "flag_for_india": "🇮🇳", + "flag_for_indonesia": "🇮🇩", + "flag_for_iran": "🇮🇷", + "flag_for_iraq": "🇮🇶", + "flag_for_ireland": "🇮🇪", + "flag_for_isle_of_man": "🇮🇲", + "flag_for_israel": "🇮🇱", + "flag_for_italy": "🇮🇹", + "flag_for_jamaica": "🇯🇲", + "flag_for_japan": "🇯🇵", + "flag_for_jersey": "🇯🇪", + "flag_for_jordan": "🇯🇴", + "flag_for_kazakhstan": "🇰🇿", + "flag_for_kenya": "🇰🇪", + "flag_for_kiribati": "🇰🇮", + "flag_for_kosovo": "🇽🇰", + "flag_for_kuwait": "🇰🇼", + "flag_for_kyrgyzstan": "🇰🇬", + "flag_for_laos": "🇱🇦", + "flag_for_latvia": "🇱🇻", + "flag_for_lebanon": "🇱🇧", + "flag_for_lesotho": "🇱🇸", + "flag_for_liberia": "🇱🇷", + "flag_for_libya": "🇱🇾", + "flag_for_liechtenstein": "🇱🇮", + "flag_for_lithuania": "🇱🇹", + "flag_for_luxembourg": "🇱🇺", + "flag_for_macau": "🇲🇴", + "flag_for_macedonia": "🇲🇰", + "flag_for_madagascar": "🇲🇬", + "flag_for_malawi": "🇲🇼", + "flag_for_malaysia": "🇲🇾", + "flag_for_maldives": "🇲🇻", + "flag_for_mali": "🇲🇱", + "flag_for_malta": "🇲🇹", + "flag_for_marshall_islands": "🇲🇭", + "flag_for_martinique": "🇲🇶", + "flag_for_mauritania": "🇲🇷", + "flag_for_mauritius": "🇲🇺", + "flag_for_mayotte": "🇾🇹", + "flag_for_mexico": "🇲🇽", + "flag_for_micronesia": "🇫🇲", + "flag_for_moldova": "🇲🇩", + "flag_for_monaco": "🇲🇨", + "flag_for_mongolia": "🇲🇳", + "flag_for_montenegro": "🇲🇪", + "flag_for_montserrat": "🇲🇸", + "flag_for_morocco": "🇲🇦", + "flag_for_mozambique": "🇲🇿", + "flag_for_myanmar": "🇲🇲", + "flag_for_namibia": "🇳🇦", + "flag_for_nauru": "🇳🇷", + "flag_for_nepal": "🇳🇵", + "flag_for_netherlands": "🇳🇱", + "flag_for_new_caledonia": "🇳🇨", + "flag_for_new_zealand": "🇳🇿", + "flag_for_nicaragua": "🇳🇮", + "flag_for_niger": "🇳🇪", + "flag_for_nigeria": "🇳🇬", + "flag_for_niue": "🇳🇺", + "flag_for_norfolk_island": "🇳🇫", + "flag_for_north_korea": "🇰🇵", + "flag_for_northern_mariana_islands": "🇲🇵", + "flag_for_norway": "🇳🇴", + "flag_for_oman": "🇴🇲", + "flag_for_pakistan": "🇵🇰", + "flag_for_palau": "🇵🇼", + "flag_for_palestinian_territories": "🇵🇸", + "flag_for_panama": "🇵🇦", + "flag_for_papua_new_guinea": "🇵🇬", + "flag_for_paraguay": "🇵🇾", + "flag_for_peru": "🇵🇪", + "flag_for_philippines": "🇵🇭", + "flag_for_pitcairn_islands": "🇵🇳", + "flag_for_poland": "🇵🇱", + "flag_for_portugal": "🇵🇹", + "flag_for_puerto_rico": "🇵🇷", + "flag_for_qatar": "🇶🇦", + "flag_for_romania": "🇷🇴", + "flag_for_russia": "🇷🇺", + "flag_for_rwanda": "🇷🇼", + "flag_for_réunion": "🇷🇪", + "flag_for_samoa": "🇼🇸", + "flag_for_san_marino": "🇸🇲", + "flag_for_saudi_arabia": "🇸🇦", + "flag_for_senegal": "🇸🇳", + "flag_for_serbia": "🇷🇸", + "flag_for_seychelles": "🇸🇨", + "flag_for_sierra_leone": "🇸🇱", + "flag_for_singapore": "🇸🇬", + "flag_for_sint_maarten": "🇸🇽", + "flag_for_slovakia": "🇸🇰", + "flag_for_slovenia": "🇸🇮", + "flag_for_solomon_islands": "🇸🇧", + "flag_for_somalia": "🇸🇴", + "flag_for_south_africa": "🇿🇦", + "flag_for_south_georgia_&_south_sandwich_islands": "🇬🇸", + "flag_for_south_korea": "🇰🇷", + "flag_for_south_sudan": "🇸🇸", + "flag_for_spain": "🇪🇸", + "flag_for_sri_lanka": "🇱🇰", + "flag_for_st._barthélemy": "🇧🇱", + "flag_for_st._helena": "🇸🇭", + "flag_for_st._kitts_&_nevis": "🇰🇳", + "flag_for_st._lucia": "🇱🇨", + "flag_for_st._martin": "🇲🇫", + "flag_for_st._pierre_&_miquelon": "🇵🇲", + "flag_for_st._vincent_&_grenadines": "🇻🇨", + "flag_for_sudan": "🇸🇩", + "flag_for_suriname": "🇸🇷", + "flag_for_svalbard_&_jan_mayen": "🇸🇯", + "flag_for_swaziland": "🇸🇿", + "flag_for_sweden": "🇸🇪", + "flag_for_switzerland": "🇨🇭", + "flag_for_syria": "🇸🇾", + "flag_for_são_tomé_&_príncipe": "🇸🇹", + "flag_for_taiwan": "🇹🇼", + "flag_for_tajikistan": "🇹🇯", + "flag_for_tanzania": "🇹🇿", + "flag_for_thailand": "🇹🇭", + "flag_for_timor__leste": "🇹🇱", + "flag_for_togo": "🇹🇬", + "flag_for_tokelau": "🇹🇰", + "flag_for_tonga": "🇹🇴", + "flag_for_trinidad_&_tobago": "🇹🇹", + "flag_for_tristan_da_cunha": "🇹🇦", + "flag_for_tunisia": "🇹🇳", + "flag_for_turkey": "🇹🇷", + "flag_for_turkmenistan": "🇹🇲", + "flag_for_turks_&_caicos_islands": "🇹🇨", + "flag_for_tuvalu": "🇹🇻", + "flag_for_u.s._outlying_islands": "🇺🇲", + "flag_for_u.s._virgin_islands": "🇻🇮", + "flag_for_uganda": "🇺🇬", + "flag_for_ukraine": "🇺🇦", + "flag_for_united_arab_emirates": "🇦🇪", + "flag_for_united_kingdom": "🇬🇧", + "flag_for_united_states": "🇺🇸", + "flag_for_uruguay": "🇺🇾", + "flag_for_uzbekistan": "🇺🇿", + "flag_for_vanuatu": "🇻🇺", + "flag_for_vatican_city": "🇻🇦", + "flag_for_venezuela": "🇻🇪", + "flag_for_vietnam": "🇻🇳", + "flag_for_wallis_&_futuna": "🇼🇫", + "flag_for_western_sahara": "🇪🇭", + "flag_for_yemen": "🇾🇪", + "flag_for_zambia": "🇿🇲", + "flag_for_zimbabwe": "🇿🇼", + "flag_for_åland_islands": "🇦🇽", + "golf": "⛳", + "fleur__de__lis": "⚜", + "muscle": "💪", + "flushed": "😳", + "frame_with_picture": "🖼", + "fries": "🍟", + "frog": "🐸", + "hatched_chick": "🐥", + "frowning": "😦", + "fuelpump": "⛽", + "full_moon_with_face": "🌝", + "gem": "💎", + "star2": "🌟", + "golfer": "🏌", + "mortar_board": "🎓", + "grimacing": "😬", + "smile_cat": "😸", + "grinning": "😀", + "grin": "😁", + "heartpulse": "💗", + "guardsman": "💂", + "haircut": "💇", + "hamster": "🐹", + "raising_hand": "🙋", + "headphones": "🎧", + "hear_no_evil": "🙉", + "cupid": "💘", + "gift_heart": "💝", + "heart": "❤", + "exclamation": "❗", + "heavy_exclamation_mark": "❗", + "heavy_heart_exclamation_mark_ornament": "❣", + "o": "⭕", + "helm_symbol": "⎈", + "helmet_with_white_cross": "⛑", + "high_heel": "👠", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "high_brightness": "🔆", + "zap": "⚡", + "hocho": "🔪", + "knife": "🔪", + "bee": "🐝", + "traffic_light": "🚥", + "racehorse": "🐎", + "coffee": "☕", + "hotsprings": "♨", + "hourglass": "⌛", + "hourglass_flowing_sand": "⏳", + "house_buildings": "🏘", + "100": "💯", + "hushed": "😯", + "ice_hockey_stick_and_puck": "🏒", + "imp": "👿", + "information_desk_person": "💁", + "information_source": "ℹ", + "capital_abcd": "🔠", + "abc": "🔤", + "abcd": "🔡", + "1234": "🔢", + "symbols": "🔣", + "izakaya_lantern": "🏮", + "lantern": "🏮", + "jack_o_lantern": "🎃", + "dolls": "🎎", + "japanese_goblin": "👺", + "japanese_ogre": "👹", + "beginner": "🔰", + "zero": "0️⃣", + "one": "1️⃣", + "ten": "🔟", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "couplekiss": "💏", + "kissing_cat": "😽", + "kissing": "😗", + "kissing_closed_eyes": "😚", + "kissing_smiling_eyes": "😙", + "beetle": "🐞", + "large_blue_circle": "🔵", + "last_quarter_moon_with_face": "🌜", + "leaves": "🍃", + "mag": "🔍", + "left_right_arrow": "↔", + "leftwards_arrow_with_hook": "↩", + "arrow_left": "⬅", + "lock": "🔒", + "lock_with_ink_pen": "🔏", + "sob": "😭", + "low_brightness": "🔅", + "lower_left_ballpoint_pen": "🖊", + "lower_left_crayon": "🖍", + "lower_left_fountain_pen": "🖋", + "lower_left_paintbrush": "🖌", + "mahjong": "🀄", + "couple": "👫", + "man_in_business_suit_levitating": "🕴", + "man_with_gua_pi_mao": "👲", + "man_with_turban": "👳", + "mans_shoe": "👞", + "shoe": "👞", + "menorah_with_nine_branches": "🕎", + "mens": "🚹", + "minidisc": "💽", + "iphone": "📱", + "calling": "📲", + "money__mouth_face": "🤑", + "moneybag": "💰", + "rice_scene": "🎑", + "mountain_bicyclist": "🚵", + "mouse2": "🐁", + "lips": "👄", + "moyai": "🗿", + "notes": "🎶", + "nail_care": "💅", + "ab": "🆎", + "negative_squared_cross_mark": "❎", + "a": "🅰", + "b": "🅱", + "o2": "🅾", + "parking": "🅿", + "new_moon_with_face": "🌚", + "no_entry_sign": "🚫", + "underage": "🔞", + "non__potable_water": "🚱", + "arrow_upper_right": "↗", + "arrow_upper_left": "↖", + "office": "🏢", + "older_man": "👴", + "older_woman": "👵", + "om_symbol": "🕉", + "on": "🔛", + "book": "📖", + "unlock": "🔓", + "mailbox_with_no_mail": "📭", + "mailbox_with_mail": "📬", + "cd": "💿", + "tada": "🎉", + "feet": "🐾", + "walking": "🚶", + "pencil2": "✏", + "pensive": "😔", + "persevere": "😣", + "bow": "🙇", + "raised_hands": "🙌", + "person_with_ball": "⛹", + "person_with_blond_hair": "👱", + "pray": "🙏", + "person_with_pouting_face": "🙎", + "computer": "💻", + "pig2": "🐖", + "hankey": "💩", + "poop": "💩", + "shit": "💩", + "bamboo": "🎍", + "gun": "🔫", + "black_joker": "🃏", + "rotating_light": "🚨", + "cop": "👮", + "stew": "🍲", + "pouch": "👝", + "pouting_cat": "😾", + "rage": "😡", + "put_litter_in_its_place": "🚮", + "rabbit2": "🐇", + "racing_motorcycle": "🏍", + "radioactive_sign": "☢", + "fist": "✊", + "hand": "✋", + "raised_hand_with_fingers_splayed": "🖐", + "raised_hand_with_part_between_middle_and_ring_fingers": "🖖", + "blue_car": "🚙", + "apple": "🍎", + "relieved": "😌", + "reversed_hand_with_middle_finger_extended": "🖕", + "mag_right": "🔎", + "arrow_right_hook": "↪", + "sweet_potato": "🍠", + "robot": "🤖", + "rolled__up_newspaper": "🗞", + "rowboat": "🚣", + "runner": "🏃", + "running": "🏃", + "running_shirt_with_sash": "🎽", + "boat": "⛵", + "scales": "⚖", + "school_satchel": "🎒", + "scorpius": "♏", + "see_no_evil": "🙈", + "sheep": "🐑", + "stars": "🌠", + "cake": "🍰", + "six_pointed_star": "🔯", + "ski": "🎿", + "sleeping_accommodation": "🛌", + "sleeping": "😴", + "sleepy": "😪", + "sleuth_or_spy": "🕵", + "heart_eyes_cat": "😻", + "smiley_cat": "😺", + "innocent": "😇", + "heart_eyes": "😍", + "smiling_imp": "😈", + "smiley": "😃", + "sweat_smile": "😅", + "smile": "😄", + "laughing": "😆", + "satisfied": "😆", + "blush": "😊", + "smirk": "😏", + "smoking": "🚬", + "snow_capped_mountain": "🏔", + "soccer": "⚽", + "icecream": "🍦", + "soon": "🔜", + "arrow_lower_right": "↘", + "arrow_lower_left": "↙", + "speak_no_evil": "🙊", + "speaker": "🔈", + "mute": "🔇", + "sound": "🔉", + "loud_sound": "🔊", + "speaking_head_in_silhouette": "🗣", + "spiral_calendar_pad": "🗓", + "spiral_note_pad": "🗒", + "shell": "🐚", + "sweat_drops": "💦", + "u5272": "🈹", + "u5408": "🈴", + "u55b6": "🈺", + "u6307": "🈯", + "u6708": "🈷", + "u6709": "🈶", + "u6e80": "🈵", + "u7121": "🈚", + "u7533": "🈸", + "u7981": "🈲", + "u7a7a": "🈳", + "cl": "🆑", + "cool": "🆒", + "free": "🆓", + "id": "🆔", + "koko": "🈁", + "sa": "🈂", + "new": "🆕", + "ng": "🆖", + "ok": "🆗", + "sos": "🆘", + "up": "🆙", + "vs": "🆚", + "steam_locomotive": "🚂", + "ramen": "🍜", + "partly_sunny": "⛅", + "city_sunrise": "🌇", + "surfer": "🏄", + "swimmer": "🏊", + "shirt": "👕", + "tshirt": "👕", + "table_tennis_paddle_and_ball": "🏓", + "tea": "🍵", + "tv": "📺", + "three_button_mouse": "🖱", + "+1": "👍", + "thumbsup": "👍", + "__1": "👎", + "-1": "👎", + "thumbsdown": "👎", + "thunder_cloud_and_rain": "⛈", + "tiger2": "🐅", + "tophat": "🎩", + "top": "🔝", + "tm": "™", + "train2": "🚆", + "triangular_flag_on_post": "🚩", + "trident": "🔱", + "twisted_rightwards_arrows": "🔀", + "unamused": "😒", + "small_red_triangle": "🔺", + "arrow_up_small": "🔼", + "arrow_up_down": "↕", + "upside__down_face": "🙃", + "arrow_up": "⬆", + "v": "✌", + "vhs": "📼", + "wc": "🚾", + "ocean": "🌊", + "waving_black_flag": "🏴", + "wave": "👋", + "waving_white_flag": "🏳", + "moon": "🌔", + "scream_cat": "🙀", + "weary": "😩", + "weight_lifter": "🏋", + "whale2": "🐋", + "wheelchair": "♿", + "point_down": "👇", + "grey_exclamation": "❕", + "white_frowning_face": "☹", + "white_check_mark": "✅", + "point_left": "👈", + "white_medium_small_square": "◽", + "star": "⭐", + "grey_question": "❔", + "point_right": "👉", + "relaxed": "☺", + "white_sun_behind_cloud": "🌥", + "white_sun_behind_cloud_with_rain": "🌦", + "white_sun_with_small_cloud": "🌤", + "point_up_2": "👆", + "point_up": "☝", + "wind_blowing_face": "🌬", + "wink": "😉", + "wolf": "🐺", + "dancers": "👯", + "boot": "👢", + "womans_clothes": "👚", + "womans_hat": "👒", + "sandal": "👡", + "womens": "🚺", + "worried": "😟", + "gift": "🎁", + "zipper__mouth_face": "🤐", + "regional_indicator_a": "🇦", + "regional_indicator_b": "🇧", + "regional_indicator_c": "🇨", + "regional_indicator_d": "🇩", + "regional_indicator_e": "🇪", + "regional_indicator_f": "🇫", + "regional_indicator_g": "🇬", + "regional_indicator_h": "🇭", + "regional_indicator_i": "🇮", + "regional_indicator_j": "🇯", + "regional_indicator_k": "🇰", + "regional_indicator_l": "🇱", + "regional_indicator_m": "🇲", + "regional_indicator_n": "🇳", + "regional_indicator_o": "🇴", + "regional_indicator_p": "🇵", + "regional_indicator_q": "🇶", + "regional_indicator_r": "🇷", + "regional_indicator_s": "🇸", + "regional_indicator_t": "🇹", + "regional_indicator_u": "🇺", + "regional_indicator_v": "🇻", + "regional_indicator_w": "🇼", + "regional_indicator_x": "🇽", + "regional_indicator_y": "🇾", + "regional_indicator_z": "🇿", +} diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py new file mode 100644 index 0000000..bb2cafa --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py @@ -0,0 +1,32 @@ +from typing import Callable, Match, Optional +import re + +from ._emoji_codes import EMOJI + + +_ReStringMatch = Match[str] # regex match object +_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub +_EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re + + +def _emoji_replace( + text: str, + default_variant: Optional[str] = None, + _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub, +) -> str: + """Replace emoji code in text.""" + get_emoji = EMOJI.__getitem__ + variants = {"text": "\uFE0E", "emoji": "\uFE0F"} + get_variant = variants.get + default_variant_code = variants.get(default_variant, "") if default_variant else "" + + def do_replace(match: Match[str]) -> str: + emoji_code, emoji_name, variant = match.groups() + try: + return get_emoji(emoji_name.lower()) + get_variant( + variant, default_variant_code + ) + except KeyError: + return emoji_code + + return _emoji_sub(do_replace, text) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py new file mode 100644 index 0000000..094d2dc --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py @@ -0,0 +1,76 @@ +CONSOLE_HTML_FORMAT = """\ + + + + + + + +
{code}
+ + +""" + +CONSOLE_SVG_FORMAT = """\ + + + + + + + + + {lines} + + + {chrome} + + {backgrounds} + + {matrix} + + + +""" + +_SVG_FONT_FAMILY = "Rich Fira Code" +_SVG_CLASSES_PREFIX = "rich-svg" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py new file mode 100644 index 0000000..cbd6da9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py @@ -0,0 +1,10 @@ +from typing import Any + + +def load_ipython_extension(ip: Any) -> None: # pragma: no cover + # prevent circular import + from pip._vendor.rich.pretty import install + from pip._vendor.rich.traceback import install as tr_install + + install() + tr_install() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py new file mode 100644 index 0000000..b17ee65 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import IO, Callable + + +def get_fileno(file_like: IO[str]) -> int | None: + """Get fileno() from a file, accounting for poorly implemented file-like objects. + + Args: + file_like (IO): A file-like object. + + Returns: + int | None: The result of fileno if available, or None if operation failed. + """ + fileno: Callable[[], int] | None = getattr(file_like, "fileno", None) + if fileno is not None: + try: + return fileno() + except Exception: + # `fileno` is documented as potentially raising a OSError + # Alas, from the issues, there are so many poorly implemented file-like objects, + # that `fileno()` can raise just about anything. + return None + return None diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py new file mode 100644 index 0000000..30446ce --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py @@ -0,0 +1,270 @@ +from __future__ import absolute_import + +import inspect +from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature +from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union + +from .console import Group, RenderableType +from .control import escape_control_codes +from .highlighter import ReprHighlighter +from .jupyter import JupyterMixin +from .panel import Panel +from .pretty import Pretty +from .table import Table +from .text import Text, TextType + + +def _first_paragraph(doc: str) -> str: + """Get the first paragraph from a docstring.""" + paragraph, _, _ = doc.partition("\n\n") + return paragraph + + +class Inspect(JupyterMixin): + """A renderable to inspect any Python Object. + + Args: + obj (Any): An object to inspect. + title (str, optional): Title to display over inspect result, or None use type. Defaults to None. + help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. + methods (bool, optional): Enable inspection of callables. Defaults to False. + docs (bool, optional): Also render doc strings. Defaults to True. + private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. + dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. + sort (bool, optional): Sort attributes alphabetically. Defaults to True. + all (bool, optional): Show all attributes. Defaults to False. + value (bool, optional): Pretty print value of object. Defaults to True. + """ + + def __init__( + self, + obj: Any, + *, + title: Optional[TextType] = None, + help: bool = False, + methods: bool = False, + docs: bool = True, + private: bool = False, + dunder: bool = False, + sort: bool = True, + all: bool = True, + value: bool = True, + ) -> None: + self.highlighter = ReprHighlighter() + self.obj = obj + self.title = title or self._make_title(obj) + if all: + methods = private = dunder = True + self.help = help + self.methods = methods + self.docs = docs or help + self.private = private or dunder + self.dunder = dunder + self.sort = sort + self.value = value + + def _make_title(self, obj: Any) -> Text: + """Make a default title.""" + title_str = ( + str(obj) + if (isclass(obj) or callable(obj) or ismodule(obj)) + else str(type(obj)) + ) + title_text = self.highlighter(title_str) + return title_text + + def __rich__(self) -> Panel: + return Panel.fit( + Group(*self._render()), + title=self.title, + border_style="scope.border", + padding=(0, 1), + ) + + def _get_signature(self, name: str, obj: Any) -> Optional[Text]: + """Get a signature for a callable.""" + try: + _signature = str(signature(obj)) + ":" + except ValueError: + _signature = "(...)" + except TypeError: + return None + + source_filename: Optional[str] = None + try: + source_filename = getfile(obj) + except (OSError, TypeError): + # OSError is raised if obj has no source file, e.g. when defined in REPL. + pass + + callable_name = Text(name, style="inspect.callable") + if source_filename: + callable_name.stylize(f"link file://{source_filename}") + signature_text = self.highlighter(_signature) + + qualname = name or getattr(obj, "__qualname__", name) + + # If obj is a module, there may be classes (which are callable) to display + if inspect.isclass(obj): + prefix = "class" + elif inspect.iscoroutinefunction(obj): + prefix = "async def" + else: + prefix = "def" + + qual_signature = Text.assemble( + (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"), + (qualname, "inspect.callable"), + signature_text, + ) + + return qual_signature + + def _render(self) -> Iterable[RenderableType]: + """Render object.""" + + def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]: + key, (_error, value) = item + return (callable(value), key.strip("_").lower()) + + def safe_getattr(attr_name: str) -> Tuple[Any, Any]: + """Get attribute or any exception.""" + try: + return (None, getattr(obj, attr_name)) + except Exception as error: + return (error, None) + + obj = self.obj + keys = dir(obj) + total_items = len(keys) + if not self.dunder: + keys = [key for key in keys if not key.startswith("__")] + if not self.private: + keys = [key for key in keys if not key.startswith("_")] + not_shown_count = total_items - len(keys) + items = [(key, safe_getattr(key)) for key in keys] + if self.sort: + items.sort(key=sort_items) + + items_table = Table.grid(padding=(0, 1), expand=False) + items_table.add_column(justify="right") + add_row = items_table.add_row + highlighter = self.highlighter + + if callable(obj): + signature = self._get_signature("", obj) + if signature is not None: + yield signature + yield "" + + if self.docs: + _doc = self._get_formatted_doc(obj) + if _doc is not None: + doc_text = Text(_doc, style="inspect.help") + doc_text = highlighter(doc_text) + yield doc_text + yield "" + + if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)): + yield Panel( + Pretty(obj, indent_guides=True, max_length=10, max_string=60), + border_style="inspect.value.border", + ) + yield "" + + for key, (error, value) in items: + key_text = Text.assemble( + ( + key, + "inspect.attr.dunder" if key.startswith("__") else "inspect.attr", + ), + (" =", "inspect.equals"), + ) + if error is not None: + warning = key_text.copy() + warning.stylize("inspect.error") + add_row(warning, highlighter(repr(error))) + continue + + if callable(value): + if not self.methods: + continue + + _signature_text = self._get_signature(key, value) + if _signature_text is None: + add_row(key_text, Pretty(value, highlighter=highlighter)) + else: + if self.docs: + docs = self._get_formatted_doc(value) + if docs is not None: + _signature_text.append("\n" if "\n" in docs else " ") + doc = highlighter(docs) + doc.stylize("inspect.doc") + _signature_text.append(doc) + + add_row(key_text, _signature_text) + else: + add_row(key_text, Pretty(value, highlighter=highlighter)) + if items_table.row_count: + yield items_table + elif not_shown_count: + yield Text.from_markup( + f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " + f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." + ) + + def _get_formatted_doc(self, object_: Any) -> Optional[str]: + """ + Extract the docstring of an object, process it and returns it. + The processing consists in cleaning up the doctring's indentation, + taking only its 1st paragraph if `self.help` is not True, + and escape its control codes. + + Args: + object_ (Any): the object to get the docstring from. + + Returns: + Optional[str]: the processed docstring, or None if no docstring was found. + """ + docs = getdoc(object_) + if docs is None: + return None + docs = cleandoc(docs).strip() + if not self.help: + docs = _first_paragraph(docs) + return escape_control_codes(docs) + + +def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: + """Returns the MRO of an object's class, or of the object itself if it's a class.""" + if not hasattr(obj, "__mro__"): + # N.B. we cannot use `if type(obj) is type` here because it doesn't work with + # some types of classes, such as the ones that use abc.ABCMeta. + obj = type(obj) + return getattr(obj, "__mro__", ()) + + +def get_object_types_mro_as_strings(obj: object) -> Collection[str]: + """ + Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class. + + Examples: + `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']` + """ + return [ + f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' + for type_ in get_object_types_mro(obj) + ] + + +def is_object_one_of_types( + obj: object, fully_qualified_types_names: Collection[str] +) -> bool: + """ + Returns `True` if the given object's class (or the object itself, if it's a class) has one of the + fully qualified names in its MRO. + """ + for type_name in get_object_types_mro_as_strings(obj): + if type_name in fully_qualified_types_names: + return True + return False diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py new file mode 100644 index 0000000..fc16c84 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py @@ -0,0 +1,94 @@ +from datetime import datetime +from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable + + +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import Console, ConsoleRenderable, RenderableType + from .table import Table + +FormatTimeCallable = Callable[[datetime], Text] + + +class LogRender: + def __init__( + self, + show_time: bool = True, + show_level: bool = False, + show_path: bool = True, + time_format: Union[str, FormatTimeCallable] = "[%x %X]", + omit_repeated_times: bool = True, + level_width: Optional[int] = 8, + ) -> None: + self.show_time = show_time + self.show_level = show_level + self.show_path = show_path + self.time_format = time_format + self.omit_repeated_times = omit_repeated_times + self.level_width = level_width + self._last_time: Optional[Text] = None + + def __call__( + self, + console: "Console", + renderables: Iterable["ConsoleRenderable"], + log_time: Optional[datetime] = None, + time_format: Optional[Union[str, FormatTimeCallable]] = None, + level: TextType = "", + path: Optional[str] = None, + line_no: Optional[int] = None, + link_path: Optional[str] = None, + ) -> "Table": + from .containers import Renderables + from .table import Table + + output = Table.grid(padding=(0, 1)) + output.expand = True + if self.show_time: + output.add_column(style="log.time") + if self.show_level: + output.add_column(style="log.level", width=self.level_width) + output.add_column(ratio=1, style="log.message", overflow="fold") + if self.show_path and path: + output.add_column(style="log.path") + row: List["RenderableType"] = [] + if self.show_time: + log_time = log_time or console.get_datetime() + time_format = time_format or self.time_format + if callable(time_format): + log_time_display = time_format(log_time) + else: + log_time_display = Text(log_time.strftime(time_format)) + if log_time_display == self._last_time and self.omit_repeated_times: + row.append(Text(" " * len(log_time_display))) + else: + row.append(log_time_display) + self._last_time = log_time_display + if self.show_level: + row.append(level) + + row.append(Renderables(renderables)) + if self.show_path and path: + path_text = Text() + path_text.append( + path, style=f"link file://{link_path}" if link_path else "" + ) + if line_no: + path_text.append(":") + path_text.append( + f"{line_no}", + style=f"link file://{link_path}#{line_no}" if link_path else "", + ) + row.append(path_text) + + output.add_row(*row) + return output + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + c = Console() + c.print("[on blue]Hello", justify="right") + c.log("[on blue]hello", justify="right") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py new file mode 100644 index 0000000..01c6caf --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py @@ -0,0 +1,43 @@ +from typing import Iterable, Tuple, TypeVar + +T = TypeVar("T") + + +def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for first value.""" + iter_values = iter(values) + try: + value = next(iter_values) + except StopIteration: + return + yield True, value + for value in iter_values: + yield False, value + + +def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value + + +def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py new file mode 100644 index 0000000..b659673 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py @@ -0,0 +1,69 @@ +from types import TracebackType +from typing import IO, Iterable, Iterator, List, Optional, Type + + +class NullFile(IO[str]): + def close(self) -> None: + pass + + def isatty(self) -> bool: + return False + + def read(self, __n: int = 1) -> str: + return "" + + def readable(self) -> bool: + return False + + def readline(self, __limit: int = 1) -> str: + return "" + + def readlines(self, __hint: int = 1) -> List[str]: + return [] + + def seek(self, __offset: int, __whence: int = 1) -> int: + return 0 + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + return 0 + + def truncate(self, __size: Optional[int] = 1) -> int: + return 0 + + def writable(self) -> bool: + return False + + def writelines(self, __lines: Iterable[str]) -> None: + pass + + def __next__(self) -> str: + return "" + + def __iter__(self) -> Iterator[str]: + return iter([""]) + + def __enter__(self) -> IO[str]: + pass + + def __exit__( + self, + __t: Optional[Type[BaseException]], + __value: Optional[BaseException], + __traceback: Optional[TracebackType], + ) -> None: + pass + + def write(self, text: str) -> int: + return 0 + + def flush(self) -> None: + pass + + def fileno(self) -> int: + return -1 + + +NULL_FILE = NullFile() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py new file mode 100644 index 0000000..3c748d3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py @@ -0,0 +1,309 @@ +from .palette import Palette + + +# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column) +WINDOWS_PALETTE = Palette( + [ + (12, 12, 12), + (197, 15, 31), + (19, 161, 14), + (193, 156, 0), + (0, 55, 218), + (136, 23, 152), + (58, 150, 221), + (204, 204, 204), + (118, 118, 118), + (231, 72, 86), + (22, 198, 12), + (249, 241, 165), + (59, 120, 255), + (180, 0, 158), + (97, 214, 214), + (242, 242, 242), + ] +) + +# # The standard ansi colors (including bright variants) +STANDARD_PALETTE = Palette( + [ + (0, 0, 0), + (170, 0, 0), + (0, 170, 0), + (170, 85, 0), + (0, 0, 170), + (170, 0, 170), + (0, 170, 170), + (170, 170, 170), + (85, 85, 85), + (255, 85, 85), + (85, 255, 85), + (255, 255, 85), + (85, 85, 255), + (255, 85, 255), + (85, 255, 255), + (255, 255, 255), + ] +) + + +# The 256 color palette +EIGHT_BIT_PALETTE = Palette( + [ + (0, 0, 0), + (128, 0, 0), + (0, 128, 0), + (128, 128, 0), + (0, 0, 128), + (128, 0, 128), + (0, 128, 128), + (192, 192, 192), + (128, 128, 128), + (255, 0, 0), + (0, 255, 0), + (255, 255, 0), + (0, 0, 255), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + (0, 0, 0), + (0, 0, 95), + (0, 0, 135), + (0, 0, 175), + (0, 0, 215), + (0, 0, 255), + (0, 95, 0), + (0, 95, 95), + (0, 95, 135), + (0, 95, 175), + (0, 95, 215), + (0, 95, 255), + (0, 135, 0), + (0, 135, 95), + (0, 135, 135), + (0, 135, 175), + (0, 135, 215), + (0, 135, 255), + (0, 175, 0), + (0, 175, 95), + (0, 175, 135), + (0, 175, 175), + (0, 175, 215), + (0, 175, 255), + (0, 215, 0), + (0, 215, 95), + (0, 215, 135), + (0, 215, 175), + (0, 215, 215), + (0, 215, 255), + (0, 255, 0), + (0, 255, 95), + (0, 255, 135), + (0, 255, 175), + (0, 255, 215), + (0, 255, 255), + (95, 0, 0), + (95, 0, 95), + (95, 0, 135), + (95, 0, 175), + (95, 0, 215), + (95, 0, 255), + (95, 95, 0), + (95, 95, 95), + (95, 95, 135), + (95, 95, 175), + (95, 95, 215), + (95, 95, 255), + (95, 135, 0), + (95, 135, 95), + (95, 135, 135), + (95, 135, 175), + (95, 135, 215), + (95, 135, 255), + (95, 175, 0), + (95, 175, 95), + (95, 175, 135), + (95, 175, 175), + (95, 175, 215), + (95, 175, 255), + (95, 215, 0), + (95, 215, 95), + (95, 215, 135), + (95, 215, 175), + (95, 215, 215), + (95, 215, 255), + (95, 255, 0), + (95, 255, 95), + (95, 255, 135), + (95, 255, 175), + (95, 255, 215), + (95, 255, 255), + (135, 0, 0), + (135, 0, 95), + (135, 0, 135), + (135, 0, 175), + (135, 0, 215), + (135, 0, 255), + (135, 95, 0), + (135, 95, 95), + (135, 95, 135), + (135, 95, 175), + (135, 95, 215), + (135, 95, 255), + (135, 135, 0), + (135, 135, 95), + (135, 135, 135), + (135, 135, 175), + (135, 135, 215), + (135, 135, 255), + (135, 175, 0), + (135, 175, 95), + (135, 175, 135), + (135, 175, 175), + (135, 175, 215), + (135, 175, 255), + (135, 215, 0), + (135, 215, 95), + (135, 215, 135), + (135, 215, 175), + (135, 215, 215), + (135, 215, 255), + (135, 255, 0), + (135, 255, 95), + (135, 255, 135), + (135, 255, 175), + (135, 255, 215), + (135, 255, 255), + (175, 0, 0), + (175, 0, 95), + (175, 0, 135), + (175, 0, 175), + (175, 0, 215), + (175, 0, 255), + (175, 95, 0), + (175, 95, 95), + (175, 95, 135), + (175, 95, 175), + (175, 95, 215), + (175, 95, 255), + (175, 135, 0), + (175, 135, 95), + (175, 135, 135), + (175, 135, 175), + (175, 135, 215), + (175, 135, 255), + (175, 175, 0), + (175, 175, 95), + (175, 175, 135), + (175, 175, 175), + (175, 175, 215), + (175, 175, 255), + (175, 215, 0), + (175, 215, 95), + (175, 215, 135), + (175, 215, 175), + (175, 215, 215), + (175, 215, 255), + (175, 255, 0), + (175, 255, 95), + (175, 255, 135), + (175, 255, 175), + (175, 255, 215), + (175, 255, 255), + (215, 0, 0), + (215, 0, 95), + (215, 0, 135), + (215, 0, 175), + (215, 0, 215), + (215, 0, 255), + (215, 95, 0), + (215, 95, 95), + (215, 95, 135), + (215, 95, 175), + (215, 95, 215), + (215, 95, 255), + (215, 135, 0), + (215, 135, 95), + (215, 135, 135), + (215, 135, 175), + (215, 135, 215), + (215, 135, 255), + (215, 175, 0), + (215, 175, 95), + (215, 175, 135), + (215, 175, 175), + (215, 175, 215), + (215, 175, 255), + (215, 215, 0), + (215, 215, 95), + (215, 215, 135), + (215, 215, 175), + (215, 215, 215), + (215, 215, 255), + (215, 255, 0), + (215, 255, 95), + (215, 255, 135), + (215, 255, 175), + (215, 255, 215), + (215, 255, 255), + (255, 0, 0), + (255, 0, 95), + (255, 0, 135), + (255, 0, 175), + (255, 0, 215), + (255, 0, 255), + (255, 95, 0), + (255, 95, 95), + (255, 95, 135), + (255, 95, 175), + (255, 95, 215), + (255, 95, 255), + (255, 135, 0), + (255, 135, 95), + (255, 135, 135), + (255, 135, 175), + (255, 135, 215), + (255, 135, 255), + (255, 175, 0), + (255, 175, 95), + (255, 175, 135), + (255, 175, 175), + (255, 175, 215), + (255, 175, 255), + (255, 215, 0), + (255, 215, 95), + (255, 215, 135), + (255, 215, 175), + (255, 215, 215), + (255, 215, 255), + (255, 255, 0), + (255, 255, 95), + (255, 255, 135), + (255, 255, 175), + (255, 255, 215), + (255, 255, 255), + (8, 8, 8), + (18, 18, 18), + (28, 28, 28), + (38, 38, 38), + (48, 48, 48), + (58, 58, 58), + (68, 68, 68), + (78, 78, 78), + (88, 88, 88), + (98, 98, 98), + (108, 108, 108), + (118, 118, 118), + (128, 128, 128), + (138, 138, 138), + (148, 148, 148), + (158, 158, 158), + (168, 168, 168), + (178, 178, 178), + (188, 188, 188), + (198, 198, 198), + (208, 208, 208), + (218, 218, 218), + (228, 228, 228), + (238, 238, 238), + ] +) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py new file mode 100644 index 0000000..4f6d8b2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py @@ -0,0 +1,17 @@ +from typing import Optional + + +def pick_bool(*values: Optional[bool]) -> bool: + """Pick the first non-none bool or return the last value. + + Args: + *values (bool): Any number of boolean or None values. + + Returns: + bool: First non-none boolean. + """ + assert values, "1 or more values required" + for value in values: + if value is not None: + return value + return bool(value) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py new file mode 100644 index 0000000..e8a3a67 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py @@ -0,0 +1,160 @@ +import sys +from fractions import Fraction +from math import ceil +from typing import cast, List, Optional, Sequence + +if sys.version_info >= (3, 8): + from typing import Protocol +else: + from pip._vendor.typing_extensions import Protocol # pragma: no cover + + +class Edge(Protocol): + """Any object that defines an edge (such as Layout).""" + + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + +def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]: + """Divide total space to satisfy size, ratio, and minimum_size, constraints. + + The returned list of integers should add up to total in most cases, unless it is + impossible to satisfy all the constraints. For instance, if there are two edges + with a minimum size of 20 each and `total` is 30 then the returned list will be + greater than total. In practice, this would mean that a Layout object would + clip the rows that would overflow the screen height. + + Args: + total (int): Total number of characters. + edges (List[Edge]): Edges within total space. + + Returns: + List[int]: Number of characters for each edge. + """ + # Size of edge or None for yet to be determined + sizes = [(edge.size or None) for edge in edges] + + _Fraction = Fraction + + # While any edges haven't been calculated + while None in sizes: + # Get flexible edges and index to map these back on to sizes list + flexible_edges = [ + (index, edge) + for index, (size, edge) in enumerate(zip(sizes, edges)) + if size is None + ] + # Remaining space in total + remaining = total - sum(size or 0 for size in sizes) + if remaining <= 0: + # No room for flexible edges + return [ + ((edge.minimum_size or 1) if size is None else size) + for size, edge in zip(sizes, edges) + ] + # Calculate number of characters in a ratio portion + portion = _Fraction( + remaining, sum((edge.ratio or 1) for _, edge in flexible_edges) + ) + + # If any edges will be less than their minimum, replace size with the minimum + for index, edge in flexible_edges: + if portion * edge.ratio <= edge.minimum_size: + sizes[index] = edge.minimum_size + # New fixed size will invalidate calculations, so we need to repeat the process + break + else: + # Distribute flexible space and compensate for rounding error + # Since edge sizes can only be integers we need to add the remainder + # to the following line + remainder = _Fraction(0) + for index, edge in flexible_edges: + size, remainder = divmod(portion * edge.ratio + remainder, 1) + sizes[index] = size + break + # Sizes now contains integers only + return cast(List[int], sizes) + + +def ratio_reduce( + total: int, ratios: List[int], maximums: List[int], values: List[int] +) -> List[int]: + """Divide an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + maximums (List[int]): List of maximums values for each slot. + values (List[int]): List of values + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)] + total_ratio = sum(ratios) + if not total_ratio: + return values[:] + total_remaining = total + result: List[int] = [] + append = result.append + for ratio, maximum, value in zip(ratios, maximums, values): + if ratio and total_ratio > 0: + distributed = min(maximum, round(ratio * total_remaining / total_ratio)) + append(value - distributed) + total_remaining -= distributed + total_ratio -= ratio + else: + append(value) + return result + + +def ratio_distribute( + total: int, ratios: List[int], minimums: Optional[List[int]] = None +) -> List[int]: + """Distribute an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + minimums (List[int]): List of minimum values for each slot. + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + if minimums: + ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)] + total_ratio = sum(ratios) + assert total_ratio > 0, "Sum of ratios must be > 0" + + total_remaining = total + distributed_total: List[int] = [] + append = distributed_total.append + if minimums is None: + _minimums = [0] * len(ratios) + else: + _minimums = minimums + for ratio, minimum in zip(ratios, _minimums): + if total_ratio > 0: + distributed = max(minimum, ceil(ratio * total_remaining / total_ratio)) + else: + distributed = total_remaining + append(distributed) + total_ratio -= ratio + total_remaining -= distributed + return distributed_total + + +if __name__ == "__main__": + from dataclasses import dataclass + + @dataclass + class E: + + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)]) + print(sum(resolved)) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py new file mode 100644 index 0000000..d0bb1fe --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py @@ -0,0 +1,482 @@ +""" +Spinners are from: +* cli-spinners: + MIT License + Copyright (c) Sindre Sorhus (sindresorhus.com) + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +""" + +SPINNERS = { + "dots": { + "interval": 80, + "frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", + }, + "dots2": {"interval": 80, "frames": "⣾⣽⣻⢿⡿⣟⣯⣷"}, + "dots3": { + "interval": 80, + "frames": "⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓", + }, + "dots4": { + "interval": 80, + "frames": "⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆", + }, + "dots5": { + "interval": 80, + "frames": "⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋", + }, + "dots6": { + "interval": 80, + "frames": "⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁", + }, + "dots7": { + "interval": 80, + "frames": "⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈", + }, + "dots8": { + "interval": 80, + "frames": "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈", + }, + "dots9": {"interval": 80, "frames": "⢹⢺⢼⣸⣇⡧⡗⡏"}, + "dots10": {"interval": 80, "frames": "⢄⢂⢁⡁⡈⡐⡠"}, + "dots11": {"interval": 100, "frames": "⠁⠂⠄⡀⢀⠠⠐⠈"}, + "dots12": { + "interval": 80, + "frames": [ + "⢀⠀", + "⡀⠀", + "⠄⠀", + "⢂⠀", + "⡂⠀", + "⠅⠀", + "⢃⠀", + "⡃⠀", + "⠍⠀", + "⢋⠀", + "⡋⠀", + "⠍⠁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⢈⠩", + "⡀⢙", + "⠄⡙", + "⢂⠩", + "⡂⢘", + "⠅⡘", + "⢃⠨", + "⡃⢐", + "⠍⡐", + "⢋⠠", + "⡋⢀", + "⠍⡁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⠈⠩", + "⠀⢙", + "⠀⡙", + "⠀⠩", + "⠀⢘", + "⠀⡘", + "⠀⠨", + "⠀⢐", + "⠀⡐", + "⠀⠠", + "⠀⢀", + "⠀⡀", + ], + }, + "dots8Bit": { + "interval": 80, + "frames": "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙" + "⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻" + "⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕" + "⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷" + "⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿", + }, + "line": {"interval": 130, "frames": ["-", "\\", "|", "/"]}, + "line2": {"interval": 100, "frames": "⠂-–—–-"}, + "pipe": {"interval": 100, "frames": "┤┘┴└├┌┬┐"}, + "simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]}, + "simpleDotsScrolling": { + "interval": 200, + "frames": [". ", ".. ", "...", " ..", " .", " "], + }, + "star": {"interval": 70, "frames": "✶✸✹✺✹✷"}, + "star2": {"interval": 80, "frames": "+x*"}, + "flip": { + "interval": 70, + "frames": "___-``'´-___", + }, + "hamburger": {"interval": 100, "frames": "☱☲☴"}, + "growVertical": { + "interval": 120, + "frames": "▁▃▄▅▆▇▆▅▄▃", + }, + "growHorizontal": { + "interval": 120, + "frames": "▏▎▍▌▋▊▉▊▋▌▍▎", + }, + "balloon": {"interval": 140, "frames": " .oO@* "}, + "balloon2": {"interval": 120, "frames": ".oO°Oo."}, + "noise": {"interval": 100, "frames": "▓▒░"}, + "bounce": {"interval": 120, "frames": "⠁⠂⠄⠂"}, + "boxBounce": {"interval": 120, "frames": "▖▘▝▗"}, + "boxBounce2": {"interval": 100, "frames": "▌▀▐▄"}, + "triangle": {"interval": 50, "frames": "◢◣◤◥"}, + "arc": {"interval": 100, "frames": "◜◠◝◞◡◟"}, + "circle": {"interval": 120, "frames": "◡⊙◠"}, + "squareCorners": {"interval": 180, "frames": "◰◳◲◱"}, + "circleQuarters": {"interval": 120, "frames": "◴◷◶◵"}, + "circleHalves": {"interval": 50, "frames": "◐◓◑◒"}, + "squish": {"interval": 100, "frames": "╫╪"}, + "toggle": {"interval": 250, "frames": "⊶⊷"}, + "toggle2": {"interval": 80, "frames": "▫▪"}, + "toggle3": {"interval": 120, "frames": "□■"}, + "toggle4": {"interval": 100, "frames": "■□▪▫"}, + "toggle5": {"interval": 100, "frames": "▮▯"}, + "toggle6": {"interval": 300, "frames": "ဝ၀"}, + "toggle7": {"interval": 80, "frames": "⦾⦿"}, + "toggle8": {"interval": 100, "frames": "◍◌"}, + "toggle9": {"interval": 100, "frames": "◉◎"}, + "toggle10": {"interval": 100, "frames": "㊂㊀㊁"}, + "toggle11": {"interval": 50, "frames": "⧇⧆"}, + "toggle12": {"interval": 120, "frames": "☗☖"}, + "toggle13": {"interval": 80, "frames": "=*-"}, + "arrow": {"interval": 100, "frames": "←↖↑↗→↘↓↙"}, + "arrow2": { + "interval": 80, + "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "], + }, + "arrow3": { + "interval": 120, + "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"], + }, + "bouncingBar": { + "interval": 80, + "frames": [ + "[ ]", + "[= ]", + "[== ]", + "[=== ]", + "[ ===]", + "[ ==]", + "[ =]", + "[ ]", + "[ =]", + "[ ==]", + "[ ===]", + "[====]", + "[=== ]", + "[== ]", + "[= ]", + ], + }, + "bouncingBall": { + "interval": 80, + "frames": [ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )", + ], + }, + "smiley": {"interval": 200, "frames": ["😄 ", "😝 "]}, + "monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]}, + "hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]}, + "clock": { + "interval": 100, + "frames": [ + "🕛 ", + "🕐 ", + "🕑 ", + "🕒 ", + "🕓 ", + "🕔 ", + "🕕 ", + "🕖 ", + "🕗 ", + "🕘 ", + "🕙 ", + "🕚 ", + ], + }, + "earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]}, + "material": { + "interval": 17, + "frames": [ + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "██████████▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "█████████████▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁██████████████▁▁▁▁", + "▁▁▁██████████████▁▁▁", + "▁▁▁▁█████████████▁▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁▁▁████████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁▁█████████████▁▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁▁███████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁▁█████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + ], + }, + "moon": { + "interval": 80, + "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "], + }, + "runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]}, + "pong": { + "interval": 80, + "frames": [ + "▐⠂ ▌", + "▐⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂▌", + "▐ ⠠▌", + "▐ ⡀▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐⠠ ▌", + ], + }, + "shark": { + "interval": 120, + "frames": [ + "▐|\\____________▌", + "▐_|\\___________▌", + "▐__|\\__________▌", + "▐___|\\_________▌", + "▐____|\\________▌", + "▐_____|\\_______▌", + "▐______|\\______▌", + "▐_______|\\_____▌", + "▐________|\\____▌", + "▐_________|\\___▌", + "▐__________|\\__▌", + "▐___________|\\_▌", + "▐____________|\\▌", + "▐____________/|▌", + "▐___________/|_▌", + "▐__________/|__▌", + "▐_________/|___▌", + "▐________/|____▌", + "▐_______/|_____▌", + "▐______/|______▌", + "▐_____/|_______▌", + "▐____/|________▌", + "▐___/|_________▌", + "▐__/|__________▌", + "▐_/|___________▌", + "▐/|____________▌", + ], + }, + "dqpb": {"interval": 100, "frames": "dqpb"}, + "weather": { + "interval": 100, + "frames": [ + "☀️ ", + "☀️ ", + "☀️ ", + "🌤 ", + "⛅️ ", + "🌥 ", + "☁️ ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "⛈ ", + "🌨 ", + "🌧 ", + "🌨 ", + "☁️ ", + "🌥 ", + "⛅️ ", + "🌤 ", + "☀️ ", + "☀️ ", + ], + }, + "christmas": {"interval": 400, "frames": "🌲🎄"}, + "grenade": { + "interval": 80, + "frames": [ + "، ", + "′ ", + " ´ ", + " ‾ ", + " ⸌", + " ⸊", + " |", + " ⁎", + " ⁕", + " ෴ ", + " ⁓", + " ", + " ", + " ", + ], + }, + "point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]}, + "layer": {"interval": 150, "frames": "-=≡"}, + "betaWave": { + "interval": 80, + "frames": [ + "ρββββββ", + "βρβββββ", + "ββρββββ", + "βββρβββ", + "ββββρββ", + "βββββρβ", + "ββββββρ", + ], + }, + "aesthetic": { + "interval": 80, + "frames": [ + "▰▱▱▱▱▱▱", + "▰▰▱▱▱▱▱", + "▰▰▰▱▱▱▱", + "▰▰▰▰▱▱▱", + "▰▰▰▰▰▱▱", + "▰▰▰▰▰▰▱", + "▰▰▰▰▰▰▰", + "▰▱▱▱▱▱▱", + ], + }, +} diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py new file mode 100644 index 0000000..194564e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py @@ -0,0 +1,16 @@ +from typing import List, TypeVar + +T = TypeVar("T") + + +class Stack(List[T]): + """A small shim over builtin list.""" + + @property + def top(self) -> T: + """Get top of stack.""" + return self[-1] + + def push(self, item: T) -> None: + """Push an item on to the stack (append in stack nomenclature).""" + self.append(item) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py new file mode 100644 index 0000000..a2ca6be --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py @@ -0,0 +1,19 @@ +""" +Timer context manager, only used in debug. + +""" + +from time import time + +import contextlib +from typing import Generator + + +@contextlib.contextmanager +def timer(subject: str = "time") -> Generator[None, None, None]: + """print the elapsed time. (only used in debugging)""" + start = time() + yield + elapsed = time() - start + elapsed_ms = elapsed * 1000 + print(f"{subject} elapsed {elapsed_ms:.1f}ms") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py new file mode 100644 index 0000000..81b1082 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py @@ -0,0 +1,662 @@ +"""Light wrapper around the Win32 Console API - this module should only be imported on Windows + +The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions +""" +import ctypes +import sys +from typing import Any + +windll: Any = None +if sys.platform == "win32": + windll = ctypes.LibraryLoader(ctypes.WinDLL) +else: + raise ImportError(f"{__name__} can only be imported on Windows") + +import time +from ctypes import Structure, byref, wintypes +from typing import IO, NamedTuple, Type, cast + +from pip._vendor.rich.color import ColorSystem +from pip._vendor.rich.style import Style + +STDOUT = -11 +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + +COORD = wintypes._COORD + + +class LegacyWindowsError(Exception): + pass + + +class WindowsCoordinates(NamedTuple): + """Coordinates in the Windows Console API are (y, x), not (x, y). + This class is intended to prevent that confusion. + Rows and columns are indexed from 0. + This class can be used in place of wintypes._COORD in arguments and argtypes. + """ + + row: int + col: int + + @classmethod + def from_param(cls, value: "WindowsCoordinates") -> COORD: + """Converts a WindowsCoordinates into a wintypes _COORD structure. + This classmethod is internally called by ctypes to perform the conversion. + + Args: + value (WindowsCoordinates): The input coordinates to convert. + + Returns: + wintypes._COORD: The converted coordinates struct. + """ + return COORD(value.col, value.row) + + +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + + +class CONSOLE_CURSOR_INFO(ctypes.Structure): + _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)] + + +_GetStdHandle = windll.kernel32.GetStdHandle +_GetStdHandle.argtypes = [ + wintypes.DWORD, +] +_GetStdHandle.restype = wintypes.HANDLE + + +def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE: + """Retrieves a handle to the specified standard device (standard input, standard output, or standard error). + + Args: + handle (int): Integer identifier for the handle. Defaults to -11 (stdout). + + Returns: + wintypes.HANDLE: The handle + """ + return cast(wintypes.HANDLE, _GetStdHandle(handle)) + + +_GetConsoleMode = windll.kernel32.GetConsoleMode +_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD] +_GetConsoleMode.restype = wintypes.BOOL + + +def GetConsoleMode(std_handle: wintypes.HANDLE) -> int: + """Retrieves the current input mode of a console's input buffer + or the current output mode of a console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Raises: + LegacyWindowsError: If any error occurs while calling the Windows console API. + + Returns: + int: Value representing the current console mode as documented at + https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters + """ + + console_mode = wintypes.DWORD() + success = bool(_GetConsoleMode(std_handle, console_mode)) + if not success: + raise LegacyWindowsError("Unable to get legacy Windows Console Mode") + return console_mode.value + + +_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW +_FillConsoleOutputCharacterW.argtypes = [ + wintypes.HANDLE, + ctypes.c_char, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputCharacterW.restype = wintypes.BOOL + + +def FillConsoleOutputCharacter( + std_handle: wintypes.HANDLE, + char: str, + length: int, + start: WindowsCoordinates, +) -> int: + """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + char (str): The character to write. Must be a string of length 1. + length (int): The number of times to write the character. + start (WindowsCoordinates): The coordinates to start writing at. + + Returns: + int: The number of characters written. + """ + character = ctypes.c_char(char.encode()) + num_characters = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + _FillConsoleOutputCharacterW( + std_handle, + character, + num_characters, + start, + byref(num_written), + ) + return num_written.value + + +_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute +_FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputAttribute.restype = wintypes.BOOL + + +def FillConsoleOutputAttribute( + std_handle: wintypes.HANDLE, + attributes: int, + length: int, + start: WindowsCoordinates, +) -> int: + """Sets the character attributes for a specified number of character cells, + beginning at the specified coordinates in a screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours of the cells. + length (int): The number of cells to set the output attribute of. + start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set. + + Returns: + int: The number of cells whose attributes were actually set. + """ + num_cells = wintypes.DWORD(length) + style_attrs = wintypes.WORD(attributes) + num_written = wintypes.DWORD(0) + _FillConsoleOutputAttribute( + std_handle, style_attrs, num_cells, start, byref(num_written) + ) + return num_written.value + + +_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute +_SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, +] +_SetConsoleTextAttribute.restype = wintypes.BOOL + + +def SetConsoleTextAttribute( + std_handle: wintypes.HANDLE, attributes: wintypes.WORD +) -> bool: + """Set the colour attributes for all text written after this function is called. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours. + + + Returns: + bool: True if the attribute was set successfully, otherwise False. + """ + return bool(_SetConsoleTextAttribute(std_handle, attributes)) + + +_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo +_GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO), +] +_GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + +def GetConsoleScreenBufferInfo( + std_handle: wintypes.HANDLE, +) -> CONSOLE_SCREEN_BUFFER_INFO: + """Retrieves information about the specified console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Returns: + CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about + screen size, cursor position, colour attributes, and more.""" + console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO() + _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info)) + return console_screen_buffer_info + + +_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition +_SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + cast(Type[COORD], WindowsCoordinates), +] +_SetConsoleCursorPosition.restype = wintypes.BOOL + + +def SetConsoleCursorPosition( + std_handle: wintypes.HANDLE, coords: WindowsCoordinates +) -> bool: + """Set the position of the cursor in the console screen + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + coords (WindowsCoordinates): The coordinates to move the cursor to. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorPosition(std_handle, coords)) + + +_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo +_GetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_GetConsoleCursorInfo.restype = wintypes.BOOL + + +def GetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Get the cursor info - used to get cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information + about the console's cursor. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo +_SetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_SetConsoleCursorInfo.restype = wintypes.BOOL + + +def SetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Set the cursor info - used for adjusting cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleTitle = windll.kernel32.SetConsoleTitleW +_SetConsoleTitle.argtypes = [wintypes.LPCWSTR] +_SetConsoleTitle.restype = wintypes.BOOL + + +def SetConsoleTitle(title: str) -> bool: + """Sets the title of the current console window + + Args: + title (str): The new title of the console window. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleTitle(title)) + + +class LegacyWindowsTerm: + """This class allows interaction with the legacy Windows Console API. It should only be used in the context + of environments where virtual terminal processing is not available. However, if it is used in a Windows environment, + the entire API should work. + + Args: + file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout. + """ + + BRIGHT_BIT = 8 + + # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers + ANSI_TO_WINDOWS = [ + 0, # black The Windows colours are defined in wincon.h as follows: + 4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001 + 2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010 + 6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100 + 1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000 + 5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000 + 3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000 + 7, # white define BACKGROUND_RED 0x0040 -- 0100 0000 + 8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000 + 12, # bright red + 10, # bright green + 14, # bright yellow + 9, # bright blue + 13, # bright magenta + 11, # bright cyan + 15, # bright white + ] + + def __init__(self, file: "IO[str]") -> None: + handle = GetStdHandle(STDOUT) + self._handle = handle + default_text = GetConsoleScreenBufferInfo(handle).wAttributes + self._default_text = default_text + + self._default_fore = default_text & 7 + self._default_back = (default_text >> 4) & 7 + self._default_attrs = self._default_fore | (self._default_back << 4) + + self._file = file + self.write = file.write + self.flush = file.flush + + @property + def cursor_position(self) -> WindowsCoordinates: + """Returns the current position of the cursor (0-based) + + Returns: + WindowsCoordinates: The current cursor position. + """ + coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition + return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X)) + + @property + def screen_size(self) -> WindowsCoordinates: + """Returns the current size of the console screen buffer, in character columns and rows + + Returns: + WindowsCoordinates: The width and height of the screen as WindowsCoordinates. + """ + screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize + return WindowsCoordinates( + row=cast(int, screen_size.Y), col=cast(int, screen_size.X) + ) + + def write_text(self, text: str) -> None: + """Write text directly to the terminal without any modification of styles + + Args: + text (str): The text to write to the console + """ + self.write(text) + self.flush() + + def write_styled(self, text: str, style: Style) -> None: + """Write styled text to the terminal. + + Args: + text (str): The text to write + style (Style): The style of the text + """ + color = style.color + bgcolor = style.bgcolor + if style.reverse: + color, bgcolor = bgcolor, color + + if color: + fore = color.downgrade(ColorSystem.WINDOWS).number + fore = fore if fore is not None else 7 # Default to ANSI 7: White + if style.bold: + fore = fore | self.BRIGHT_BIT + if style.dim: + fore = fore & ~self.BRIGHT_BIT + fore = self.ANSI_TO_WINDOWS[fore] + else: + fore = self._default_fore + + if bgcolor: + back = bgcolor.downgrade(ColorSystem.WINDOWS).number + back = back if back is not None else 0 # Default to ANSI 0: Black + back = self.ANSI_TO_WINDOWS[back] + else: + back = self._default_back + + assert fore is not None + assert back is not None + + SetConsoleTextAttribute( + self._handle, attributes=ctypes.c_ushort(fore | (back << 4)) + ) + self.write_text(text) + SetConsoleTextAttribute(self._handle, attributes=self._default_text) + + def move_cursor_to(self, new_position: WindowsCoordinates) -> None: + """Set the position of the cursor + + Args: + new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor. + """ + if new_position.col < 0 or new_position.row < 0: + return + SetConsoleCursorPosition(self._handle, coords=new_position) + + def erase_line(self) -> None: + """Erase all content on the line the cursor is currently located at""" + screen_size = self.screen_size + cursor_position = self.cursor_position + cells_to_erase = screen_size.col + start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0) + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=start_coordinates + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=start_coordinates, + ) + + def erase_end_of_line(self) -> None: + """Erase all content from the cursor position to the end of that line""" + cursor_position = self.cursor_position + cells_to_erase = self.screen_size.col - cursor_position.col + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=cursor_position + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=cursor_position, + ) + + def erase_start_of_line(self) -> None: + """Erase all content from the cursor position to the start of that line""" + row, col = self.cursor_position + start = WindowsCoordinates(row, 0) + FillConsoleOutputCharacter(self._handle, " ", length=col, start=start) + FillConsoleOutputAttribute( + self._handle, self._default_attrs, length=col, start=start + ) + + def move_cursor_up(self) -> None: + """Move the cursor up a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row - 1, col=cursor_position.col + ), + ) + + def move_cursor_down(self) -> None: + """Move the cursor down a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row + 1, + col=cursor_position.col, + ), + ) + + def move_cursor_forward(self) -> None: + """Move the cursor forward a single cell. Wrap to the next line if required.""" + row, col = self.cursor_position + if col == self.screen_size.col - 1: + row += 1 + col = 0 + else: + col += 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def move_cursor_to_column(self, column: int) -> None: + """Move cursor to the column specified by the zero-based column index, staying on the same row + + Args: + column (int): The zero-based column index to move the cursor to. + """ + row, _ = self.cursor_position + SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column)) + + def move_cursor_backward(self) -> None: + """Move the cursor backward a single cell. Wrap to the previous line if required.""" + row, col = self.cursor_position + if col == 0: + row -= 1 + col = self.screen_size.col - 1 + else: + col -= 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def hide_cursor(self) -> None: + """Hide the cursor""" + current_cursor_size = self._get_cursor_size() + invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0) + SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor) + + def show_cursor(self) -> None: + """Show the cursor""" + current_cursor_size = self._get_cursor_size() + visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1) + SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor) + + def set_title(self, title: str) -> None: + """Set the title of the terminal window + + Args: + title (str): The new title of the console window + """ + assert len(title) < 255, "Console title must be less than 255 characters" + SetConsoleTitle(title) + + def _get_cursor_size(self) -> int: + """Get the percentage of the character cell that is filled by the cursor""" + cursor_info = CONSOLE_CURSOR_INFO() + GetConsoleCursorInfo(self._handle, cursor_info=cursor_info) + return int(cursor_info.dwSize) + + +if __name__ == "__main__": + handle = GetStdHandle() + + from pip._vendor.rich.console import Console + + console = Console() + + term = LegacyWindowsTerm(sys.stdout) + term.set_title("Win32 Console Examples") + + style = Style(color="black", bgcolor="red") + + heading = Style.parse("black on green") + + # Check colour output + console.rule("Checking colour output") + console.print("[on red]on red!") + console.print("[blue]blue!") + console.print("[yellow]yellow!") + console.print("[bold yellow]bold yellow!") + console.print("[bright_yellow]bright_yellow!") + console.print("[dim bright_yellow]dim bright_yellow!") + console.print("[italic cyan]italic cyan!") + console.print("[bold white on blue]bold white on blue!") + console.print("[reverse bold white on blue]reverse bold white on blue!") + console.print("[bold black on cyan]bold black on cyan!") + console.print("[black on green]black on green!") + console.print("[blue on green]blue on green!") + console.print("[white on black]white on black!") + console.print("[black on white]black on white!") + console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!") + + # Check cursor movement + console.rule("Checking cursor movement") + console.print() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("went back and wrapped to prev line") + time.sleep(1) + term.move_cursor_up() + term.write_text("we go up") + time.sleep(1) + term.move_cursor_down() + term.write_text("and down") + time.sleep(1) + term.move_cursor_up() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went up and back 2") + time.sleep(1) + term.move_cursor_down() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went down and back 2") + time.sleep(1) + + # Check erasing of lines + term.hide_cursor() + console.print() + console.rule("Checking line erasing") + console.print("\n...Deleting to the start of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + term.move_cursor_to_column(16) + term.write_styled("<", Style.parse("black on red")) + term.move_cursor_backward() + time.sleep(1) + term.erase_start_of_line() + time.sleep(1) + + console.print("\n\n...And to the end of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + + term.move_cursor_to_column(16) + term.write_styled(">", Style.parse("black on red")) + time.sleep(1) + term.erase_end_of_line() + time.sleep(1) + + console.print("\n\n...Now the whole line will be erased...") + term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan")) + time.sleep(1) + term.erase_line() + + term.show_cursor() + print("\n") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py new file mode 100644 index 0000000..10fc0d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py @@ -0,0 +1,72 @@ +import sys +from dataclasses import dataclass + + +@dataclass +class WindowsConsoleFeatures: + """Windows features available.""" + + vt: bool = False + """The console supports VT codes.""" + truecolor: bool = False + """The console supports truecolor.""" + + +try: + import ctypes + from ctypes import LibraryLoader + + if sys.platform == "win32": + windll = LibraryLoader(ctypes.WinDLL) + else: + windll = None + raise ImportError("Not windows") + + from pip._vendor.rich._win32_console import ( + ENABLE_VIRTUAL_TERMINAL_PROCESSING, + GetConsoleMode, + GetStdHandle, + LegacyWindowsError, + ) + +except (AttributeError, ImportError, ValueError): + + # Fallback if we can't load the Windows DLL + def get_windows_console_features() -> WindowsConsoleFeatures: + features = WindowsConsoleFeatures() + return features + +else: + + def get_windows_console_features() -> WindowsConsoleFeatures: + """Get windows console features. + + Returns: + WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. + """ + handle = GetStdHandle() + try: + console_mode = GetConsoleMode(handle) + success = True + except LegacyWindowsError: + console_mode = 0 + success = False + vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + truecolor = False + if vt: + win_version = sys.getwindowsversion() + truecolor = win_version.major > 10 or ( + win_version.major == 10 and win_version.build >= 15063 + ) + features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor) + return features + + +if __name__ == "__main__": + import platform + + features = get_windows_console_features() + from pip._vendor.rich import print + + print(f'platform="{platform.system()}"') + print(repr(features)) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py new file mode 100644 index 0000000..5ece056 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py @@ -0,0 +1,56 @@ +from typing import Iterable, Sequence, Tuple, cast + +from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates +from pip._vendor.rich.segment import ControlCode, ControlType, Segment + + +def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None: + """Makes appropriate Windows Console API calls based on the segments in the buffer. + + Args: + buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls. + term (LegacyWindowsTerm): Used to call the Windows Console API. + """ + for text, style, control in buffer: + if not control: + if style: + term.write_styled(text, style) + else: + term.write_text(text) + else: + control_codes: Sequence[ControlCode] = control + for control_code in control_codes: + control_type = control_code[0] + if control_type == ControlType.CURSOR_MOVE_TO: + _, x, y = cast(Tuple[ControlType, int, int], control_code) + term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1)) + elif control_type == ControlType.CARRIAGE_RETURN: + term.write_text("\r") + elif control_type == ControlType.HOME: + term.move_cursor_to(WindowsCoordinates(0, 0)) + elif control_type == ControlType.CURSOR_UP: + term.move_cursor_up() + elif control_type == ControlType.CURSOR_DOWN: + term.move_cursor_down() + elif control_type == ControlType.CURSOR_FORWARD: + term.move_cursor_forward() + elif control_type == ControlType.CURSOR_BACKWARD: + term.move_cursor_backward() + elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN: + _, column = cast(Tuple[ControlType, int], control_code) + term.move_cursor_to_column(column - 1) + elif control_type == ControlType.HIDE_CURSOR: + term.hide_cursor() + elif control_type == ControlType.SHOW_CURSOR: + term.show_cursor() + elif control_type == ControlType.ERASE_IN_LINE: + _, mode = cast(Tuple[ControlType, int], control_code) + if mode == 0: + term.erase_end_of_line() + elif mode == 1: + term.erase_start_of_line() + elif mode == 2: + term.erase_line() + elif control_type == ControlType.SET_WINDOW_TITLE: + _, title = cast(Tuple[ControlType, str], control_code) + term.set_title(title) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py new file mode 100644 index 0000000..c45f193 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py @@ -0,0 +1,56 @@ +import re +from typing import Iterable, List, Tuple + +from ._loop import loop_last +from .cells import cell_len, chop_cells + +re_word = re.compile(r"\s*\S+\s*") + + +def words(text: str) -> Iterable[Tuple[int, int, str]]: + position = 0 + word_match = re_word.match(text, position) + while word_match is not None: + start, end = word_match.span() + word = word_match.group(0) + yield start, end, word + word_match = re_word.match(text, end) + + +def divide_line(text: str, width: int, fold: bool = True) -> List[int]: + divides: List[int] = [] + append = divides.append + line_position = 0 + _cell_len = cell_len + for start, _end, word in words(text): + word_length = _cell_len(word.rstrip()) + if line_position + word_length > width: + if word_length > width: + if fold: + chopped_words = chop_cells(word, max_size=width, position=0) + for last, line in loop_last(chopped_words): + if start: + append(start) + + if last: + line_position = _cell_len(line) + else: + start += len(line) + else: + if start: + append(start) + line_position = _cell_len(word) + elif line_position and start: + append(start) + line_position = _cell_len(word) + else: + line_position += _cell_len(word) + return divides + + +if __name__ == "__main__": # pragma: no cover + from .console import Console + + console = Console(width=10) + console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345") + print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10, position=2)) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py new file mode 100644 index 0000000..e6e498e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py @@ -0,0 +1,33 @@ +from abc import ABC + + +class RichRenderable(ABC): + """An abstract base class for Rich renderables. + + Note that there is no need to extend this class, the intended use is to check if an + object supports the Rich renderable protocol. For example:: + + if isinstance(my_object, RichRenderable): + console.print(my_object) + + """ + + @classmethod + def __subclasshook__(cls, other: type) -> bool: + """Check if this class supports the rich render protocol.""" + return hasattr(other, "__rich_console__") or hasattr(other, "__rich__") + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.text import Text + + t = Text() + print(isinstance(Text, RichRenderable)) + print(isinstance(t, RichRenderable)) + + class Foo: + pass + + f = Foo() + print(isinstance(f, RichRenderable)) + print(isinstance("", RichRenderable)) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py new file mode 100644 index 0000000..c310b66 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py @@ -0,0 +1,311 @@ +import sys +from itertools import chain +from typing import TYPE_CHECKING, Iterable, Optional + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + +from .constrain import Constrain +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + +AlignMethod = Literal["left", "center", "right"] +VerticalAlignMethod = Literal["top", "middle", "bottom"] + + +class Align(JupyterMixin): + """Align a renderable by adding spaces if necessary. + + Args: + renderable (RenderableType): A console renderable. + align (AlignMethod): One of "left", "center", or "right"" + style (StyleType, optional): An optional style to apply to the background. + vertical (Optional[VerticalAlginMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None. + pad (bool, optional): Pad the right with spaces. Defaults to True. + width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None. + height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None. + + Raises: + ValueError: if ``align`` is not one of the expected values. + """ + + def __init__( + self, + renderable: "RenderableType", + align: AlignMethod = "left", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> None: + if align not in ("left", "center", "right"): + raise ValueError( + f'invalid value for align, expected "left", "center", or "right" (not {align!r})' + ) + if vertical is not None and vertical not in ("top", "middle", "bottom"): + raise ValueError( + f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})' + ) + self.renderable = renderable + self.align = align + self.style = style + self.vertical = vertical + self.pad = pad + self.width = width + self.height = height + + def __repr__(self) -> str: + return f"Align({self.renderable!r}, {self.align!r})" + + @classmethod + def left( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the left.""" + return cls( + renderable, + "left", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def center( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the center.""" + return cls( + renderable, + "center", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def right( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the right.""" + return cls( + renderable, + "right", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + align = self.align + width = console.measure(self.renderable, options=options).maximum + rendered = console.render( + Constrain( + self.renderable, width if self.width is None else min(width, self.width) + ), + options.update(height=None), + ) + lines = list(Segment.split_lines(rendered)) + width, height = Segment.get_shape(lines) + lines = Segment.set_shape(lines, width, height) + new_line = Segment.line() + excess_space = options.max_width - width + style = console.get_style(self.style) if self.style is not None else None + + def generate_segments() -> Iterable[Segment]: + if excess_space <= 0: + # Exact fit + for line in lines: + yield from line + yield new_line + + elif align == "left": + # Pad on the right + pad = Segment(" " * excess_space, style) if self.pad else None + for line in lines: + yield from line + if pad: + yield pad + yield new_line + + elif align == "center": + # Pad left and right + left = excess_space // 2 + pad = Segment(" " * left, style) + pad_right = ( + Segment(" " * (excess_space - left), style) if self.pad else None + ) + for line in lines: + if left: + yield pad + yield from line + if pad_right: + yield pad_right + yield new_line + + elif align == "right": + # Padding on left + pad = Segment(" " * excess_space, style) + for line in lines: + yield pad + yield from line + yield new_line + + blank_line = ( + Segment(f"{' ' * (self.width or options.max_width)}\n", style) + if self.pad + else Segment("\n") + ) + + def blank_lines(count: int) -> Iterable[Segment]: + if count > 0: + for _ in range(count): + yield blank_line + + vertical_height = self.height or options.height + iter_segments: Iterable[Segment] + if self.vertical and vertical_height is not None: + if self.vertical == "top": + bottom_space = vertical_height - height + iter_segments = chain(generate_segments(), blank_lines(bottom_space)) + elif self.vertical == "middle": + top_space = (vertical_height - height) // 2 + bottom_space = vertical_height - top_space - height + iter_segments = chain( + blank_lines(top_space), + generate_segments(), + blank_lines(bottom_space), + ) + else: # self.vertical == "bottom": + top_space = vertical_height - height + iter_segments = chain(blank_lines(top_space), generate_segments()) + else: + iter_segments = generate_segments() + if self.style: + style = console.get_style(self.style) + iter_segments = Segment.apply_style(iter_segments, style) + yield from iter_segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +class VerticalCenter(JupyterMixin): + """Vertically aligns a renderable. + + Warn: + This class is deprecated and may be removed in a future version. Use Align class with + `vertical="middle"`. + + Args: + renderable (RenderableType): A renderable object. + """ + + def __init__( + self, + renderable: "RenderableType", + style: Optional[StyleType] = None, + ) -> None: + self.renderable = renderable + self.style = style + + def __repr__(self) -> str: + return f"VerticalCenter({self.renderable!r})" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) if self.style is not None else None + lines = console.render_lines( + self.renderable, options.update(height=None), pad=False + ) + width, _height = Segment.get_shape(lines) + new_line = Segment.line() + height = options.height or options.size.height + top_space = (height - len(lines)) // 2 + bottom_space = height - top_space - len(lines) + blank_line = Segment(f"{' ' * width}", style) + + def blank_lines(count: int) -> Iterable[Segment]: + for _ in range(count): + yield blank_line + yield new_line + + if top_space > 0: + yield from blank_lines(top_space) + for line in lines: + yield from line + yield new_line + if bottom_space > 0: + yield from blank_lines(bottom_space) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console, Group + from pip._vendor.rich.highlighter import ReprHighlighter + from pip._vendor.rich.panel import Panel + + highlighter = ReprHighlighter() + console = Console() + + panel = Panel( + Group( + Align.left(highlighter("align='left'")), + Align.center(highlighter("align='center'")), + Align.right(highlighter("align='right'")), + ), + width=60, + style="on dark_blue", + title="Align", + ) + + console.print( + Align.center(panel, vertical="middle", style="on red", height=console.height) + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py new file mode 100644 index 0000000..66365e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py @@ -0,0 +1,240 @@ +import re +import sys +from contextlib import suppress +from typing import Iterable, NamedTuple, Optional + +from .color import Color +from .style import Style +from .text import Text + +re_ansi = re.compile( + r""" +(?:\x1b\](.*?)\x1b\\)| +(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~])) +""", + re.VERBOSE, +) + + +class _AnsiToken(NamedTuple): + """Result of ansi tokenized string.""" + + plain: str = "" + sgr: Optional[str] = "" + osc: Optional[str] = "" + + +def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]: + """Tokenize a string in to plain text and ANSI codes. + + Args: + ansi_text (str): A String containing ANSI codes. + + Yields: + AnsiToken: A named tuple of (plain, sgr, osc) + """ + + position = 0 + sgr: Optional[str] + osc: Optional[str] + for match in re_ansi.finditer(ansi_text): + start, end = match.span(0) + osc, sgr = match.groups() + if start > position: + yield _AnsiToken(ansi_text[position:start]) + if sgr: + if sgr == "(": + position = end + 1 + continue + if sgr.endswith("m"): + yield _AnsiToken("", sgr[1:-1], osc) + else: + yield _AnsiToken("", sgr, osc) + position = end + if position < len(ansi_text): + yield _AnsiToken(ansi_text[position:]) + + +SGR_STYLE_MAP = { + 1: "bold", + 2: "dim", + 3: "italic", + 4: "underline", + 5: "blink", + 6: "blink2", + 7: "reverse", + 8: "conceal", + 9: "strike", + 21: "underline2", + 22: "not dim not bold", + 23: "not italic", + 24: "not underline", + 25: "not blink", + 26: "not blink2", + 27: "not reverse", + 28: "not conceal", + 29: "not strike", + 30: "color(0)", + 31: "color(1)", + 32: "color(2)", + 33: "color(3)", + 34: "color(4)", + 35: "color(5)", + 36: "color(6)", + 37: "color(7)", + 39: "default", + 40: "on color(0)", + 41: "on color(1)", + 42: "on color(2)", + 43: "on color(3)", + 44: "on color(4)", + 45: "on color(5)", + 46: "on color(6)", + 47: "on color(7)", + 49: "on default", + 51: "frame", + 52: "encircle", + 53: "overline", + 54: "not frame not encircle", + 55: "not overline", + 90: "color(8)", + 91: "color(9)", + 92: "color(10)", + 93: "color(11)", + 94: "color(12)", + 95: "color(13)", + 96: "color(14)", + 97: "color(15)", + 100: "on color(8)", + 101: "on color(9)", + 102: "on color(10)", + 103: "on color(11)", + 104: "on color(12)", + 105: "on color(13)", + 106: "on color(14)", + 107: "on color(15)", +} + + +class AnsiDecoder: + """Translate ANSI code in to styled Text.""" + + def __init__(self) -> None: + self.style = Style.null() + + def decode(self, terminal_text: str) -> Iterable[Text]: + """Decode ANSI codes in an iterable of lines. + + Args: + lines (Iterable[str]): An iterable of lines of terminal output. + + Yields: + Text: Marked up Text. + """ + for line in terminal_text.splitlines(): + yield self.decode_line(line) + + def decode_line(self, line: str) -> Text: + """Decode a line containing ansi codes. + + Args: + line (str): A line of terminal output. + + Returns: + Text: A Text instance marked up according to ansi codes. + """ + from_ansi = Color.from_ansi + from_rgb = Color.from_rgb + _Style = Style + text = Text() + append = text.append + line = line.rsplit("\r", 1)[-1] + for plain_text, sgr, osc in _ansi_tokenize(line): + if plain_text: + append(plain_text, self.style or None) + elif osc is not None: + if osc.startswith("8;"): + _params, semicolon, link = osc[2:].partition(";") + if semicolon: + self.style = self.style.update_link(link or None) + elif sgr is not None: + # Translate in to semi-colon separated codes + # Ignore invalid codes, because we want to be lenient + codes = [ + min(255, int(_code) if _code else 0) + for _code in sgr.split(";") + if _code.isdigit() or _code == "" + ] + iter_codes = iter(codes) + for code in iter_codes: + if code == 0: + # reset + self.style = _Style.null() + elif code in SGR_STYLE_MAP: + # styles + self.style += _Style.parse(SGR_STYLE_MAP[code]) + elif code == 38: + #  Foreground + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ) + ) + elif code == 48: + # Background + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + None, from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + None, + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ), + ) + + return text + + +if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover + import io + import os + import pty + import sys + + decoder = AnsiDecoder() + + stdout = io.BytesIO() + + def read(fd: int) -> bytes: + data = os.read(fd, 1024) + stdout.write(data) + return data + + pty.spawn(sys.argv[1:], read) + + from .console import Console + + console = Console(record=True) + + stdout_result = stdout.getvalue().decode("utf-8") + print(stdout_result) + + for line in decoder.decode(stdout_result): + console.print(line) + + console.save_html("stdout.html") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py new file mode 100644 index 0000000..ed86a55 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py @@ -0,0 +1,94 @@ +from typing import Optional, Union + +from .color import Color +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style + +# There are left-aligned characters for 1/8 to 7/8, but +# the right-aligned characters exist only for 1/8 and 4/8. +BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"] +END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"] +FULL_BLOCK = "█" + + +class Bar(JupyterMixin): + """Renders a solid block bar. + + Args: + size (float): Value for the end of the bar. + begin (float): Begin point (between 0 and size, inclusive). + end (float): End point (between 0 and size, inclusive). + width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. + color (Union[Color, str], optional): Color of the bar. Defaults to "default". + bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default". + """ + + def __init__( + self, + size: float, + begin: float, + end: float, + *, + width: Optional[int] = None, + color: Union[Color, str] = "default", + bgcolor: Union[Color, str] = "default", + ): + self.size = size + self.begin = max(begin, 0) + self.end = min(end, size) + self.width = width + self.style = Style(color=color, bgcolor=bgcolor) + + def __repr__(self) -> str: + return f"Bar({self.size}, {self.begin}, {self.end})" + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + + width = min( + self.width if self.width is not None else options.max_width, + options.max_width, + ) + + if self.begin >= self.end: + yield Segment(" " * width, self.style) + yield Segment.line() + return + + prefix_complete_eights = int(width * 8 * self.begin / self.size) + prefix_bar_count = prefix_complete_eights // 8 + prefix_eights_count = prefix_complete_eights % 8 + + body_complete_eights = int(width * 8 * self.end / self.size) + body_bar_count = body_complete_eights // 8 + body_eights_count = body_complete_eights % 8 + + # When start and end fall into the same cell, we ideally should render + # a symbol that's "center-aligned", but there is no good symbol in Unicode. + # In this case, we fall back to right-aligned block symbol for simplicity. + + prefix = " " * prefix_bar_count + if prefix_eights_count: + prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count] + + body = FULL_BLOCK * body_bar_count + if body_eights_count: + body += END_BLOCK_ELEMENTS[body_eights_count] + + suffix = " " * (width - len(body)) + + yield Segment(prefix + body[len(prefix) :] + suffix, self.style) + yield Segment.line() + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + return ( + Measurement(self.width, self.width) + if self.width is not None + else Measurement(4, options.max_width) + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py new file mode 100644 index 0000000..97d2a94 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py @@ -0,0 +1,517 @@ +import sys +from typing import TYPE_CHECKING, Iterable, List + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +from ._loop import loop_last + +if TYPE_CHECKING: + from pip._vendor.rich.console import ConsoleOptions + + +class Box: + """Defines characters to render boxes. + + ┌─┬┐ top + │ ││ head + ├─┼┤ head_row + │ ││ mid + ├─┼┤ row + ├─┼┤ foot_row + │ ││ foot + └─┴┘ bottom + + Args: + box (str): Characters making up box. + ascii (bool, optional): True if this box uses ascii characters only. Default is False. + """ + + def __init__(self, box: str, *, ascii: bool = False) -> None: + self._box = box + self.ascii = ascii + line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines() + # top + self.top_left, self.top, self.top_divider, self.top_right = iter(line1) + # head + self.head_left, _, self.head_vertical, self.head_right = iter(line2) + # head_row + ( + self.head_row_left, + self.head_row_horizontal, + self.head_row_cross, + self.head_row_right, + ) = iter(line3) + + # mid + self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4) + # row + self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5) + # foot_row + ( + self.foot_row_left, + self.foot_row_horizontal, + self.foot_row_cross, + self.foot_row_right, + ) = iter(line6) + # foot + self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7) + # bottom + self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter( + line8 + ) + + def __repr__(self) -> str: + return "Box(...)" + + def __str__(self) -> str: + return self._box + + def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box": + """Substitute this box for another if it won't render due to platform issues. + + Args: + options (ConsoleOptions): Console options used in rendering. + safe (bool, optional): Substitute this for another Box if there are known problems + displaying on the platform (currently only relevant on Windows). Default is True. + + Returns: + Box: A different Box or the same Box. + """ + box = self + if options.legacy_windows and safe: + box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) + if options.ascii_only and not box.ascii: + box = ASCII + return box + + def get_plain_headed_box(self) -> "Box": + """If this box uses special characters for the borders of the header, then + return the equivalent box that does not. + + Returns: + Box: The most similar Box that doesn't use header-specific box characters. + If the current Box already satisfies this criterion, then it's returned. + """ + return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) + + def get_top(self, widths: Iterable[int]) -> str: + """Get the top of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.top_left) + for last, width in loop_last(widths): + append(self.top * width) + if not last: + append(self.top_divider) + append(self.top_right) + return "".join(parts) + + def get_row( + self, + widths: Iterable[int], + level: Literal["head", "row", "foot", "mid"] = "row", + edge: bool = True, + ) -> str: + """Get the top of a simple box. + + Args: + width (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + if level == "head": + left = self.head_row_left + horizontal = self.head_row_horizontal + cross = self.head_row_cross + right = self.head_row_right + elif level == "row": + left = self.row_left + horizontal = self.row_horizontal + cross = self.row_cross + right = self.row_right + elif level == "mid": + left = self.mid_left + horizontal = " " + cross = self.mid_vertical + right = self.mid_right + elif level == "foot": + left = self.foot_row_left + horizontal = self.foot_row_horizontal + cross = self.foot_row_cross + right = self.foot_row_right + else: + raise ValueError("level must be 'head', 'row' or 'foot'") + + parts: List[str] = [] + append = parts.append + if edge: + append(left) + for last, width in loop_last(widths): + append(horizontal * width) + if not last: + append(cross) + if edge: + append(right) + return "".join(parts) + + def get_bottom(self, widths: Iterable[int]) -> str: + """Get the bottom of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.bottom_left) + for last, width in loop_last(widths): + append(self.bottom * width) + if not last: + append(self.bottom_divider) + append(self.bottom_right) + return "".join(parts) + + +ASCII: Box = Box( + """\ ++--+ +| || +|-+| +| || +|-+| +|-+| +| || ++--+ +""", + ascii=True, +) + +ASCII2: Box = Box( + """\ ++-++ +| || ++-++ +| || ++-++ ++-++ +| || ++-++ +""", + ascii=True, +) + +ASCII_DOUBLE_HEAD: Box = Box( + """\ ++-++ +| || ++=++ +| || ++-++ ++-++ +| || ++-++ +""", + ascii=True, +) + +SQUARE: Box = Box( + """\ +┌─┬┐ +│ ││ +├─┼┤ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +SQUARE_DOUBLE_HEAD: Box = Box( + """\ +┌─┬┐ +│ ││ +╞═╪╡ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +MINIMAL: Box = Box( + """\ + ╷ + │ +╶─┼╴ + │ +╶─┼╴ +╶─┼╴ + │ + ╵ +""" +) + + +MINIMAL_HEAVY_HEAD: Box = Box( + """\ + ╷ + │ +╺━┿╸ + │ +╶─┼╴ +╶─┼╴ + │ + ╵ +""" +) + +MINIMAL_DOUBLE_HEAD: Box = Box( + """\ + ╷ + │ + ═╪ + │ + ─┼ + ─┼ + │ + ╵ +""" +) + + +SIMPLE: Box = Box( + """\ + + + ── + + + ── + + +""" +) + +SIMPLE_HEAD: Box = Box( + """\ + + + ── + + + + + +""" +) + + +SIMPLE_HEAVY: Box = Box( + """\ + + + ━━ + + + ━━ + + +""" +) + + +HORIZONTALS: Box = Box( + """\ + ── + + ── + + ── + ── + + ── +""" +) + +ROUNDED: Box = Box( + """\ +╭─┬╮ +│ ││ +├─┼┤ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +╰─┴╯ +""" +) + +HEAVY: Box = Box( + """\ +┏━┳┓ +┃ ┃┃ +┣━╋┫ +┃ ┃┃ +┣━╋┫ +┣━╋┫ +┃ ┃┃ +┗━┻┛ +""" +) + +HEAVY_EDGE: Box = Box( + """\ +┏━┯┓ +┃ │┃ +┠─┼┨ +┃ │┃ +┠─┼┨ +┠─┼┨ +┃ │┃ +┗━┷┛ +""" +) + +HEAVY_HEAD: Box = Box( + """\ +┏━┳┓ +┃ ┃┃ +┡━╇┩ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +DOUBLE: Box = Box( + """\ +╔═╦╗ +║ ║║ +╠═╬╣ +║ ║║ +╠═╬╣ +╠═╬╣ +║ ║║ +╚═╩╝ +""" +) + +DOUBLE_EDGE: Box = Box( + """\ +╔═╤╗ +║ │║ +╟─┼╢ +║ │║ +╟─┼╢ +╟─┼╢ +║ │║ +╚═╧╝ +""" +) + +MARKDOWN: Box = Box( + """\ + +| || +|-|| +| || +|-|| +|-|| +| || + +""", + ascii=True, +) + +# Map Boxes that don't render with raster fonts on to equivalent that do +LEGACY_WINDOWS_SUBSTITUTIONS = { + ROUNDED: SQUARE, + MINIMAL_HEAVY_HEAD: MINIMAL, + SIMPLE_HEAVY: SIMPLE, + HEAVY: SQUARE, + HEAVY_EDGE: SQUARE, + HEAVY_HEAD: SQUARE, +} + +# Map headed boxes to their headerless equivalents +PLAIN_HEADED_SUBSTITUTIONS = { + HEAVY_HEAD: SQUARE, + SQUARE_DOUBLE_HEAD: SQUARE, + MINIMAL_DOUBLE_HEAD: MINIMAL, + MINIMAL_HEAVY_HEAD: MINIMAL, + ASCII_DOUBLE_HEAD: ASCII2, +} + + +if __name__ == "__main__": # pragma: no cover + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.panel import Panel + + from . import box as box + from .console import Console + from .table import Table + from .text import Text + + console = Console(record=True) + + BOXES = [ + "ASCII", + "ASCII2", + "ASCII_DOUBLE_HEAD", + "SQUARE", + "SQUARE_DOUBLE_HEAD", + "MINIMAL", + "MINIMAL_HEAVY_HEAD", + "MINIMAL_DOUBLE_HEAD", + "SIMPLE", + "SIMPLE_HEAD", + "SIMPLE_HEAVY", + "HORIZONTALS", + "ROUNDED", + "HEAVY", + "HEAVY_EDGE", + "HEAVY_HEAD", + "DOUBLE", + "DOUBLE_EDGE", + "MARKDOWN", + ] + + console.print(Panel("[bold green]Box Constants", style="green"), justify="center") + console.print() + + columns = Columns(expand=True, padding=2) + for box_name in sorted(BOXES): + table = Table( + show_footer=True, style="dim", border_style="not dim", expand=True + ) + table.add_column("Header 1", "Footer 1") + table.add_column("Header 2", "Footer 2") + table.add_row("Cell", "Cell") + table.add_row("Cell", "Cell") + table.box = getattr(box, box_name) + table.title = Text(f"box.{box_name}", style="magenta") + columns.add_renderable(table) + console.print(columns) + + # console.save_svg("box.svg") diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py new file mode 100644 index 0000000..9354f9e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py @@ -0,0 +1,154 @@ +import re +from functools import lru_cache +from typing import Callable, List + +from ._cell_widths import CELL_WIDTHS + +# Regex to match sequence of the most common character ranges +_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match + + +@lru_cache(4096) +def cached_cell_len(text: str) -> int: + """Get the number of cells required to display text. + + This method always caches, which may use up a lot of memory. It is recommended to use + `cell_len` over this method. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + _get_size = get_character_cell_size + total_size = sum(_get_size(character) for character in text) + return total_size + + +def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: + """Get the number of cells required to display text. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + if len(text) < 512: + return _cell_len(text) + _get_size = get_character_cell_size + total_size = sum(_get_size(character) for character in text) + return total_size + + +@lru_cache(maxsize=4096) +def get_character_cell_size(character: str) -> int: + """Get the cell size of a character. + + Args: + character (str): A single character. + + Returns: + int: Number of cells (0, 1 or 2) occupied by that character. + """ + return _get_codepoint_cell_size(ord(character)) + + +@lru_cache(maxsize=4096) +def _get_codepoint_cell_size(codepoint: int) -> int: + """Get the cell size of a character. + + Args: + codepoint (int): Codepoint of a character. + + Returns: + int: Number of cells (0, 1 or 2) occupied by that character. + """ + + _table = CELL_WIDTHS + lower_bound = 0 + upper_bound = len(_table) - 1 + index = (lower_bound + upper_bound) // 2 + while True: + start, end, width = _table[index] + if codepoint < start: + upper_bound = index - 1 + elif codepoint > end: + lower_bound = index + 1 + else: + return 0 if width == -1 else width + if upper_bound < lower_bound: + break + index = (lower_bound + upper_bound) // 2 + return 1 + + +def set_cell_size(text: str, total: int) -> str: + """Set the length of a string to fit within given number of cells.""" + + if _is_single_cell_widths(text): + size = len(text) + if size < total: + return text + " " * (total - size) + return text[:total] + + if total <= 0: + return "" + cell_size = cell_len(text) + if cell_size == total: + return text + if cell_size < total: + return text + " " * (total - cell_size) + + start = 0 + end = len(text) + + # Binary search until we find the right size + while True: + pos = (start + end) // 2 + before = text[: pos + 1] + before_len = cell_len(before) + if before_len == total + 1 and cell_len(before[-1]) == 2: + return before[:-1] + " " + if before_len == total: + return before + if before_len > total: + end = pos + else: + start = pos + + +# TODO: This is inefficient +# TODO: This might not work with CWJ type characters +def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]: + """Break text in to equal (cell) length strings, returning the characters in reverse + order""" + _get_character_cell_size = get_character_cell_size + characters = [ + (character, _get_character_cell_size(character)) for character in text + ] + total_size = position + lines: List[List[str]] = [[]] + append = lines[-1].append + + for character, size in reversed(characters): + if total_size + size > max_size: + lines.append([character]) + append = lines[-1].append + total_size = size + else: + total_size += size + append(character) + + return ["".join(line) for line in lines] + + +if __name__ == "__main__": # pragma: no cover + + print(get_character_cell_size("😽")) + for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8): + print(line) + for n in range(80, 1, -1): + print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|") + print("x" * n) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py new file mode 100644 index 0000000..dfe4559 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py @@ -0,0 +1,622 @@ +import platform +import re +from colorsys import rgb_to_hls +from enum import IntEnum +from functools import lru_cache +from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple + +from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE +from .color_triplet import ColorTriplet +from .repr import Result, rich_repr +from .terminal_theme import DEFAULT_TERMINAL_THEME + +if TYPE_CHECKING: # pragma: no cover + from .terminal_theme import TerminalTheme + from .text import Text + + +WINDOWS = platform.system() == "Windows" + + +class ColorSystem(IntEnum): + """One of the 3 color system supported by terminals.""" + + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorSystem.{self.name}" + + def __str__(self) -> str: + return repr(self) + + +class ColorType(IntEnum): + """Type of color stored in Color class.""" + + DEFAULT = 0 + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorType.{self.name}" + + +ANSI_COLOR_NAMES = { + "black": 0, + "red": 1, + "green": 2, + "yellow": 3, + "blue": 4, + "magenta": 5, + "cyan": 6, + "white": 7, + "bright_black": 8, + "bright_red": 9, + "bright_green": 10, + "bright_yellow": 11, + "bright_blue": 12, + "bright_magenta": 13, + "bright_cyan": 14, + "bright_white": 15, + "grey0": 16, + "gray0": 16, + "navy_blue": 17, + "dark_blue": 18, + "blue3": 20, + "blue1": 21, + "dark_green": 22, + "deep_sky_blue4": 25, + "dodger_blue3": 26, + "dodger_blue2": 27, + "green4": 28, + "spring_green4": 29, + "turquoise4": 30, + "deep_sky_blue3": 32, + "dodger_blue1": 33, + "green3": 40, + "spring_green3": 41, + "dark_cyan": 36, + "light_sea_green": 37, + "deep_sky_blue2": 38, + "deep_sky_blue1": 39, + "spring_green2": 47, + "cyan3": 43, + "dark_turquoise": 44, + "turquoise2": 45, + "green1": 46, + "spring_green1": 48, + "medium_spring_green": 49, + "cyan2": 50, + "cyan1": 51, + "dark_red": 88, + "deep_pink4": 125, + "purple4": 55, + "purple3": 56, + "blue_violet": 57, + "orange4": 94, + "grey37": 59, + "gray37": 59, + "medium_purple4": 60, + "slate_blue3": 62, + "royal_blue1": 63, + "chartreuse4": 64, + "dark_sea_green4": 71, + "pale_turquoise4": 66, + "steel_blue": 67, + "steel_blue3": 68, + "cornflower_blue": 69, + "chartreuse3": 76, + "cadet_blue": 73, + "sky_blue3": 74, + "steel_blue1": 81, + "pale_green3": 114, + "sea_green3": 78, + "aquamarine3": 79, + "medium_turquoise": 80, + "chartreuse2": 112, + "sea_green2": 83, + "sea_green1": 85, + "aquamarine1": 122, + "dark_slate_gray2": 87, + "dark_magenta": 91, + "dark_violet": 128, + "purple": 129, + "light_pink4": 95, + "plum4": 96, + "medium_purple3": 98, + "slate_blue1": 99, + "yellow4": 106, + "wheat4": 101, + "grey53": 102, + "gray53": 102, + "light_slate_grey": 103, + "light_slate_gray": 103, + "medium_purple": 104, + "light_slate_blue": 105, + "dark_olive_green3": 149, + "dark_sea_green": 108, + "light_sky_blue3": 110, + "sky_blue2": 111, + "dark_sea_green3": 150, + "dark_slate_gray3": 116, + "sky_blue1": 117, + "chartreuse1": 118, + "light_green": 120, + "pale_green1": 156, + "dark_slate_gray1": 123, + "red3": 160, + "medium_violet_red": 126, + "magenta3": 164, + "dark_orange3": 166, + "indian_red": 167, + "hot_pink3": 168, + "medium_orchid3": 133, + "medium_orchid": 134, + "medium_purple2": 140, + "dark_goldenrod": 136, + "light_salmon3": 173, + "rosy_brown": 138, + "grey63": 139, + "gray63": 139, + "medium_purple1": 141, + "gold3": 178, + "dark_khaki": 143, + "navajo_white3": 144, + "grey69": 145, + "gray69": 145, + "light_steel_blue3": 146, + "light_steel_blue": 147, + "yellow3": 184, + "dark_sea_green2": 157, + "light_cyan3": 152, + "light_sky_blue1": 153, + "green_yellow": 154, + "dark_olive_green2": 155, + "dark_sea_green1": 193, + "pale_turquoise1": 159, + "deep_pink3": 162, + "magenta2": 200, + "hot_pink2": 169, + "orchid": 170, + "medium_orchid1": 207, + "orange3": 172, + "light_pink3": 174, + "pink3": 175, + "plum3": 176, + "violet": 177, + "light_goldenrod3": 179, + "tan": 180, + "misty_rose3": 181, + "thistle3": 182, + "plum2": 183, + "khaki3": 185, + "light_goldenrod2": 222, + "light_yellow3": 187, + "grey84": 188, + "gray84": 188, + "light_steel_blue1": 189, + "yellow2": 190, + "dark_olive_green1": 192, + "honeydew2": 194, + "light_cyan1": 195, + "red1": 196, + "deep_pink2": 197, + "deep_pink1": 199, + "magenta1": 201, + "orange_red1": 202, + "indian_red1": 204, + "hot_pink": 206, + "dark_orange": 208, + "salmon1": 209, + "light_coral": 210, + "pale_violet_red1": 211, + "orchid2": 212, + "orchid1": 213, + "orange1": 214, + "sandy_brown": 215, + "light_salmon1": 216, + "light_pink1": 217, + "pink1": 218, + "plum1": 219, + "gold1": 220, + "navajo_white1": 223, + "misty_rose1": 224, + "thistle1": 225, + "yellow1": 226, + "light_goldenrod1": 227, + "khaki1": 228, + "wheat1": 229, + "cornsilk1": 230, + "grey100": 231, + "gray100": 231, + "grey3": 232, + "gray3": 232, + "grey7": 233, + "gray7": 233, + "grey11": 234, + "gray11": 234, + "grey15": 235, + "gray15": 235, + "grey19": 236, + "gray19": 236, + "grey23": 237, + "gray23": 237, + "grey27": 238, + "gray27": 238, + "grey30": 239, + "gray30": 239, + "grey35": 240, + "gray35": 240, + "grey39": 241, + "gray39": 241, + "grey42": 242, + "gray42": 242, + "grey46": 243, + "gray46": 243, + "grey50": 244, + "gray50": 244, + "grey54": 245, + "gray54": 245, + "grey58": 246, + "gray58": 246, + "grey62": 247, + "gray62": 247, + "grey66": 248, + "gray66": 248, + "grey70": 249, + "gray70": 249, + "grey74": 250, + "gray74": 250, + "grey78": 251, + "gray78": 251, + "grey82": 252, + "gray82": 252, + "grey85": 253, + "gray85": 253, + "grey89": 254, + "gray89": 254, + "grey93": 255, + "gray93": 255, +} + + +class ColorParseError(Exception): + """The color could not be parsed.""" + + +RE_COLOR = re.compile( + r"""^ +\#([0-9a-f]{6})$| +color\(([0-9]{1,3})\)$| +rgb\(([\d\s,]+)\)$ +""", + re.VERBOSE, +) + + +@rich_repr +class Color(NamedTuple): + """Terminal color definition.""" + + name: str + """The name of the color (typically the input to Color.parse).""" + type: ColorType + """The type of the color.""" + number: Optional[int] = None + """The color number, if a standard color, or None.""" + triplet: Optional[ColorTriplet] = None + """A triplet of color components, if an RGB color.""" + + def __rich__(self) -> "Text": + """Displays the actual color if Rich printed.""" + from .style import Style + from .text import Text + + return Text.assemble( + f"", + ) + + def __rich_repr__(self) -> Result: + yield self.name + yield self.type + yield "number", self.number, None + yield "triplet", self.triplet, None + + @property + def system(self) -> ColorSystem: + """Get the native color system for this color.""" + if self.type == ColorType.DEFAULT: + return ColorSystem.STANDARD + return ColorSystem(int(self.type)) + + @property + def is_system_defined(self) -> bool: + """Check if the color is ultimately defined by the system.""" + return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR) + + @property + def is_default(self) -> bool: + """Check if the color is a default color.""" + return self.type == ColorType.DEFAULT + + def get_truecolor( + self, theme: Optional["TerminalTheme"] = None, foreground: bool = True + ) -> ColorTriplet: + """Get an equivalent color triplet for this color. + + Args: + theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None. + foreground (bool, optional): True for a foreground color, or False for background. Defaults to True. + + Returns: + ColorTriplet: A color triplet containing RGB components. + """ + + if theme is None: + theme = DEFAULT_TERMINAL_THEME + if self.type == ColorType.TRUECOLOR: + assert self.triplet is not None + return self.triplet + elif self.type == ColorType.EIGHT_BIT: + assert self.number is not None + return EIGHT_BIT_PALETTE[self.number] + elif self.type == ColorType.STANDARD: + assert self.number is not None + return theme.ansi_colors[self.number] + elif self.type == ColorType.WINDOWS: + assert self.number is not None + return WINDOWS_PALETTE[self.number] + else: # self.type == ColorType.DEFAULT: + assert self.number is None + return theme.foreground_color if foreground else theme.background_color + + @classmethod + def from_ansi(cls, number: int) -> "Color": + """Create a Color number from it's 8-bit ansi number. + + Args: + number (int): A number between 0-255 inclusive. + + Returns: + Color: A new Color instance. + """ + return cls( + name=f"color({number})", + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + @classmethod + def from_triplet(cls, triplet: "ColorTriplet") -> "Color": + """Create a truecolor RGB color from a triplet of values. + + Args: + triplet (ColorTriplet): A color triplet containing red, green and blue components. + + Returns: + Color: A new color object. + """ + return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet) + + @classmethod + def from_rgb(cls, red: float, green: float, blue: float) -> "Color": + """Create a truecolor from three color components in the range(0->255). + + Args: + red (float): Red component in range 0-255. + green (float): Green component in range 0-255. + blue (float): Blue component in range 0-255. + + Returns: + Color: A new color object. + """ + return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue))) + + @classmethod + def default(cls) -> "Color": + """Get a Color instance representing the default color. + + Returns: + Color: Default color. + """ + return cls(name="default", type=ColorType.DEFAULT) + + @classmethod + @lru_cache(maxsize=1024) + def parse(cls, color: str) -> "Color": + """Parse a color definition.""" + original_color = color + color = color.lower().strip() + + if color == "default": + return cls(color, type=ColorType.DEFAULT) + + color_number = ANSI_COLOR_NAMES.get(color) + if color_number is not None: + return cls( + color, + type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT), + number=color_number, + ) + + color_match = RE_COLOR.match(color) + if color_match is None: + raise ColorParseError(f"{original_color!r} is not a valid color") + + color_24, color_8, color_rgb = color_match.groups() + if color_24: + triplet = ColorTriplet( + int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16) + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + elif color_8: + number = int(color_8) + if number > 255: + raise ColorParseError(f"color number must be <= 255 in {color!r}") + return cls( + color, + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + else: # color_rgb: + components = color_rgb.split(",") + if len(components) != 3: + raise ColorParseError( + f"expected three components in {original_color!r}" + ) + red, green, blue = components + triplet = ColorTriplet(int(red), int(green), int(blue)) + if not all(component <= 255 for component in triplet): + raise ColorParseError( + f"color components must be <= 255 in {original_color!r}" + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + @lru_cache(maxsize=1024) + def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]: + """Get the ANSI escape codes for this color.""" + _type = self.type + if _type == ColorType.DEFAULT: + return ("39" if foreground else "49",) + + elif _type == ColorType.WINDOWS: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.STANDARD: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.EIGHT_BIT: + assert self.number is not None + return ("38" if foreground else "48", "5", str(self.number)) + + else: # self.standard == ColorStandard.TRUECOLOR: + assert self.triplet is not None + red, green, blue = self.triplet + return ("38" if foreground else "48", "2", str(red), str(green), str(blue)) + + @lru_cache(maxsize=1024) + def downgrade(self, system: ColorSystem) -> "Color": + """Downgrade a color system to a system with fewer colors.""" + + if self.type in (ColorType.DEFAULT, system): + return self + # Convert to 8-bit color from truecolor color + if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + _h, l, s = rgb_to_hls(*self.triplet.normalized) + # If saturation is under 15% assume it is grayscale + if s < 0.15: + gray = round(l * 25.0) + if gray == 0: + color_number = 16 + elif gray == 25: + color_number = 231 + else: + color_number = 231 + gray + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + red, green, blue = self.triplet + six_red = red / 95 if red < 95 else 1 + (red - 95) / 40 + six_green = green / 95 if green < 95 else 1 + (green - 95) / 40 + six_blue = blue / 95 if blue < 95 else 1 + (blue - 95) / 40 + + color_number = ( + 16 + 36 * round(six_red) + 6 * round(six_green) + round(six_blue) + ) + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + # Convert to standard from truecolor or 8-bit + elif system == ColorSystem.STANDARD: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = STANDARD_PALETTE.match(triplet) + return Color(self.name, ColorType.STANDARD, number=color_number) + + elif system == ColorSystem.WINDOWS: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + if self.number < 16: + return Color(self.name, ColorType.WINDOWS, number=self.number) + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = WINDOWS_PALETTE.match(triplet) + return Color(self.name, ColorType.WINDOWS, number=color_number) + + return self + + +def parse_rgb_hex(hex_color: str) -> ColorTriplet: + """Parse six hex characters in to RGB triplet.""" + assert len(hex_color) == 6, "must be 6 characters" + color = ColorTriplet( + int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + ) + return color + + +def blend_rgb( + color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5 +) -> ColorTriplet: + """Blend one RGB color in to another.""" + r1, g1, b1 = color1 + r2, g2, b2 = color2 + new_color = ColorTriplet( + int(r1 + (r2 - r1) * cross_fade), + int(g1 + (g2 - g1) * cross_fade), + int(b1 + (b2 - b1) * cross_fade), + ) + return new_color + + +if __name__ == "__main__": # pragma: no cover + + from .console import Console + from .table import Table + from .text import Text + + console = Console() + + table = Table(show_footer=False, show_edge=True) + table.add_column("Color", width=10, overflow="ellipsis") + table.add_column("Number", justify="right", style="yellow") + table.add_column("Name", style="green") + table.add_column("Hex", style="blue") + table.add_column("RGB", style="magenta") + + colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items()) + for color_number, name in colors: + if "grey" in name: + continue + color_cell = Text(" " * 10, style=f"on {name}") + if color_number < 16: + table.add_row(color_cell, f"{color_number}", Text(f'"{name}"')) + else: + color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type] + table.add_row( + color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb + ) + + console.print(table) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py new file mode 100644 index 0000000..02cab32 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py @@ -0,0 +1,38 @@ +from typing import NamedTuple, Tuple + + +class ColorTriplet(NamedTuple): + """The red, green, and blue components of a color.""" + + red: int + """Red component in 0 to 255 range.""" + green: int + """Green component in 0 to 255 range.""" + blue: int + """Blue component in 0 to 255 range.""" + + @property + def hex(self) -> str: + """get the color triplet in CSS style.""" + red, green, blue = self + return f"#{red:02x}{green:02x}{blue:02x}" + + @property + def rgb(self) -> str: + """The color in RGB format. + + Returns: + str: An rgb color, e.g. ``"rgb(100,23,255)"``. + """ + red, green, blue = self + return f"rgb({red},{green},{blue})" + + @property + def normalized(self) -> Tuple[float, float, float]: + """Convert components into floats between 0 and 1. + + Returns: + Tuple[float, float, float]: A tuple of three normalized colour components. + """ + red, green, blue = self + return red / 255.0, green / 255.0, blue / 255.0 diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py new file mode 100644 index 0000000..669a3a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py @@ -0,0 +1,187 @@ +from collections import defaultdict +from itertools import chain +from operator import itemgetter +from typing import Dict, Iterable, List, Optional, Tuple + +from .align import Align, AlignMethod +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .constrain import Constrain +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .table import Table +from .text import TextType +from .jupyter import JupyterMixin + + +class Columns(JupyterMixin): + """Display renderables in neat columns. + + Args: + renderables (Iterable[RenderableType]): Any number of Rich renderables (including str). + width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. + padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). + expand (bool, optional): Expand columns to full width. Defaults to False. + equal (bool, optional): Arrange in to equal sized columns. Defaults to False. + column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False. + right_to_left (bool, optional): Start column from right hand side. Defaults to False. + align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None. + title (TextType, optional): Optional title for Columns. + """ + + def __init__( + self, + renderables: Optional[Iterable[RenderableType]] = None, + padding: PaddingDimensions = (0, 1), + *, + width: Optional[int] = None, + expand: bool = False, + equal: bool = False, + column_first: bool = False, + right_to_left: bool = False, + align: Optional[AlignMethod] = None, + title: Optional[TextType] = None, + ) -> None: + self.renderables = list(renderables or []) + self.width = width + self.padding = padding + self.expand = expand + self.equal = equal + self.column_first = column_first + self.right_to_left = right_to_left + self.align: Optional[AlignMethod] = align + self.title = title + + def add_renderable(self, renderable: RenderableType) -> None: + """Add a renderable to the columns. + + Args: + renderable (RenderableType): Any renderable object. + """ + self.renderables.append(renderable) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + render_str = console.render_str + renderables = [ + render_str(renderable) if isinstance(renderable, str) else renderable + for renderable in self.renderables + ] + if not renderables: + return + _top, right, _bottom, left = Padding.unpack(self.padding) + width_padding = max(left, right) + max_width = options.max_width + widths: Dict[int, int] = defaultdict(int) + column_count = len(renderables) + + get_measurement = Measurement.get + renderable_widths = [ + get_measurement(console, options, renderable).maximum + for renderable in renderables + ] + if self.equal: + renderable_widths = [max(renderable_widths)] * len(renderable_widths) + + def iter_renderables( + column_count: int, + ) -> Iterable[Tuple[int, Optional[RenderableType]]]: + item_count = len(renderables) + if self.column_first: + width_renderables = list(zip(renderable_widths, renderables)) + + column_lengths: List[int] = [item_count // column_count] * column_count + for col_no in range(item_count % column_count): + column_lengths[col_no] += 1 + + row_count = (item_count + column_count - 1) // column_count + cells = [[-1] * column_count for _ in range(row_count)] + row = col = 0 + for index in range(item_count): + cells[row][col] = index + column_lengths[col] -= 1 + if column_lengths[col]: + row += 1 + else: + col += 1 + row = 0 + for index in chain.from_iterable(cells): + if index == -1: + break + yield width_renderables[index] + else: + yield from zip(renderable_widths, renderables) + # Pad odd elements with spaces + if item_count % column_count: + for _ in range(column_count - (item_count % column_count)): + yield 0, None + + table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) + table.expand = self.expand + table.title = self.title + + if self.width is not None: + column_count = (max_width) // (self.width + width_padding) + for _ in range(column_count): + table.add_column(width=self.width) + else: + while column_count > 1: + widths.clear() + column_no = 0 + for renderable_width, _ in iter_renderables(column_count): + widths[column_no] = max(widths[column_no], renderable_width) + total_width = sum(widths.values()) + width_padding * ( + len(widths) - 1 + ) + if total_width > max_width: + column_count = len(widths) - 1 + break + else: + column_no = (column_no + 1) % column_count + else: + break + + get_renderable = itemgetter(1) + _renderables = [ + get_renderable(_renderable) + for _renderable in iter_renderables(column_count) + ] + if self.equal: + _renderables = [ + None + if renderable is None + else Constrain(renderable, renderable_widths[0]) + for renderable in _renderables + ] + if self.align: + align = self.align + _Align = Align + _renderables = [ + None if renderable is None else _Align(renderable, align) + for renderable in _renderables + ] + + right_to_left = self.right_to_left + add_row = table.add_row + for start in range(0, len(_renderables), column_count): + row = _renderables[start : start + column_count] + if right_to_left: + row = row[::-1] + add_row(*row) + yield table + + +if __name__ == "__main__": # pragma: no cover + import os + + console = Console() + + files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))] + columns = Columns(files, padding=(0, 1), expand=False, equal=False) + console.print(columns) + console.rule() + columns.column_first = True + console.print(columns) + columns.right_to_left = True + console.rule() + console.print(columns) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py new file mode 100644 index 0000000..e559cbb --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py @@ -0,0 +1,2633 @@ +import inspect +import os +import platform +import sys +import threading +import zlib +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from datetime import datetime +from functools import wraps +from getpass import getpass +from html import escape +from inspect import isclass +from itertools import islice +from math import ceil +from time import monotonic +from types import FrameType, ModuleType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + NamedTuple, + Optional, + TextIO, + Tuple, + Type, + Union, + cast, +) + +from pip._vendor.rich._null_file import NULL_FILE + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol, runtime_checkable +else: + from pip._vendor.typing_extensions import ( + Literal, + Protocol, + runtime_checkable, + ) # pragma: no cover + +from . import errors, themes +from ._emoji_replace import _emoji_replace +from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT +from ._fileno import get_fileno +from ._log_render import FormatTimeCallable, LogRender +from .align import Align, AlignMethod +from .color import ColorSystem, blend_rgb +from .control import Control +from .emoji import EmojiVariant +from .highlighter import NullHighlighter, ReprHighlighter +from .markup import render as render_markup +from .measure import Measurement, measure_renderables +from .pager import Pager, SystemPager +from .pretty import Pretty, is_expandable +from .protocol import rich_cast +from .region import Region +from .scope import render_scope +from .screen import Screen +from .segment import Segment +from .style import Style, StyleType +from .styled import Styled +from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme +from .text import Text, TextType +from .theme import Theme, ThemeStack + +if TYPE_CHECKING: + from ._windows import WindowsConsoleFeatures + from .live import Live + from .status import Status + +JUPYTER_DEFAULT_COLUMNS = 115 +JUPYTER_DEFAULT_LINES = 100 +WINDOWS = platform.system() == "Windows" + +HighlighterType = Callable[[Union[str, "Text"]], "Text"] +JustifyMethod = Literal["default", "left", "center", "right", "full"] +OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"] + + +class NoChange: + pass + + +NO_CHANGE = NoChange() + +try: + _STDIN_FILENO = sys.__stdin__.fileno() +except Exception: + _STDIN_FILENO = 0 +try: + _STDOUT_FILENO = sys.__stdout__.fileno() +except Exception: + _STDOUT_FILENO = 1 +try: + _STDERR_FILENO = sys.__stderr__.fileno() +except Exception: + _STDERR_FILENO = 2 + +_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) +_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) + + +_TERM_COLORS = { + "kitty": ColorSystem.EIGHT_BIT, + "256color": ColorSystem.EIGHT_BIT, + "16color": ColorSystem.STANDARD, +} + + +class ConsoleDimensions(NamedTuple): + """Size of the terminal.""" + + width: int + """The width of the console in 'cells'.""" + height: int + """The height of the console in lines.""" + + +@dataclass +class ConsoleOptions: + """Options for __rich_console__ method.""" + + size: ConsoleDimensions + """Size of console.""" + legacy_windows: bool + """legacy_windows: flag for legacy windows.""" + min_width: int + """Minimum width of renderable.""" + max_width: int + """Maximum width of renderable.""" + is_terminal: bool + """True if the target is a terminal, otherwise False.""" + encoding: str + """Encoding of terminal.""" + max_height: int + """Height of container (starts as terminal)""" + justify: Optional[JustifyMethod] = None + """Justify value override for renderable.""" + overflow: Optional[OverflowMethod] = None + """Overflow value override for renderable.""" + no_wrap: Optional[bool] = False + """Disable wrapping for text.""" + highlight: Optional[bool] = None + """Highlight override for render_str.""" + markup: Optional[bool] = None + """Enable markup when rendering strings.""" + height: Optional[int] = None + + @property + def ascii_only(self) -> bool: + """Check if renderables should use ascii only.""" + return not self.encoding.startswith("utf") + + def copy(self) -> "ConsoleOptions": + """Return a copy of the options. + + Returns: + ConsoleOptions: a copy of self. + """ + options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) + options.__dict__ = self.__dict__.copy() + return options + + def update( + self, + *, + width: Union[int, NoChange] = NO_CHANGE, + min_width: Union[int, NoChange] = NO_CHANGE, + max_width: Union[int, NoChange] = NO_CHANGE, + justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, + overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, + no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, + highlight: Union[Optional[bool], NoChange] = NO_CHANGE, + markup: Union[Optional[bool], NoChange] = NO_CHANGE, + height: Union[Optional[int], NoChange] = NO_CHANGE, + ) -> "ConsoleOptions": + """Update values, return a copy.""" + options = self.copy() + if not isinstance(width, NoChange): + options.min_width = options.max_width = max(0, width) + if not isinstance(min_width, NoChange): + options.min_width = min_width + if not isinstance(max_width, NoChange): + options.max_width = max_width + if not isinstance(justify, NoChange): + options.justify = justify + if not isinstance(overflow, NoChange): + options.overflow = overflow + if not isinstance(no_wrap, NoChange): + options.no_wrap = no_wrap + if not isinstance(highlight, NoChange): + options.highlight = highlight + if not isinstance(markup, NoChange): + options.markup = markup + if not isinstance(height, NoChange): + if height is not None: + options.max_height = height + options.height = None if height is None else max(0, height) + return options + + def update_width(self, width: int) -> "ConsoleOptions": + """Update just the width, return a copy. + + Args: + width (int): New width (sets both min_width and max_width) + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + return options + + def update_height(self, height: int) -> "ConsoleOptions": + """Update the height, and return a copy. + + Args: + height (int): New height + + Returns: + ~ConsoleOptions: New Console options instance. + """ + options = self.copy() + options.max_height = options.height = height + return options + + def reset_height(self) -> "ConsoleOptions": + """Return a copy of the options with height set to ``None``. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.height = None + return options + + def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": + """Update the width and height, and return a copy. + + Args: + width (int): New width (sets both min_width and max_width). + height (int): New height. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + options.height = options.max_height = height + return options + + +@runtime_checkable +class RichCast(Protocol): + """An object that may be 'cast' to a console renderable.""" + + def __rich__( + self, + ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover + ... + + +@runtime_checkable +class ConsoleRenderable(Protocol): + """An object that supports the console protocol.""" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": # pragma: no cover + ... + + +# A type that may be rendered by Console. +RenderableType = Union[ConsoleRenderable, RichCast, str] + +# The result of calling a __rich_console__ method. +RenderResult = Iterable[Union[RenderableType, Segment]] + +_null_highlighter = NullHighlighter() + + +class CaptureError(Exception): + """An error in the Capture context manager.""" + + +class NewLine: + """A renderable to generate new line(s)""" + + def __init__(self, count: int = 1) -> None: + self.count = count + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + yield Segment("\n" * self.count) + + +class ScreenUpdate: + """Render a list of lines at a given offset.""" + + def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: + self._lines = lines + self.x = x + self.y = y + + def __rich_console__( + self, console: "Console", options: ConsoleOptions + ) -> RenderResult: + x = self.x + move_to = Control.move_to + for offset, line in enumerate(self._lines, self.y): + yield move_to(x, offset) + yield from line + + +class Capture: + """Context manager to capture the result of printing to the console. + See :meth:`~rich.console.Console.capture` for how to use. + + Args: + console (Console): A console instance to capture output. + """ + + def __init__(self, console: "Console") -> None: + self._console = console + self._result: Optional[str] = None + + def __enter__(self) -> "Capture": + self._console.begin_capture() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._result = self._console.end_capture() + + def get(self) -> str: + """Get the result of the capture.""" + if self._result is None: + raise CaptureError( + "Capture result is not available until context manager exits." + ) + return self._result + + +class ThemeContext: + """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" + + def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: + self.console = console + self.theme = theme + self.inherit = inherit + + def __enter__(self) -> "ThemeContext": + self.console.push_theme(self.theme) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.console.pop_theme() + + +class PagerContext: + """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" + + def __init__( + self, + console: "Console", + pager: Optional[Pager] = None, + styles: bool = False, + links: bool = False, + ) -> None: + self._console = console + self.pager = SystemPager() if pager is None else pager + self.styles = styles + self.links = links + + def __enter__(self) -> "PagerContext": + self._console._enter_buffer() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if exc_type is None: + with self._console._lock: + buffer: List[Segment] = self._console._buffer[:] + del self._console._buffer[:] + segments: Iterable[Segment] = buffer + if not self.styles: + segments = Segment.strip_styles(segments) + elif not self.links: + segments = Segment.strip_links(segments) + content = self._console._render_buffer(segments) + self.pager.show(content) + self._console._exit_buffer() + + +class ScreenContext: + """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" + + def __init__( + self, console: "Console", hide_cursor: bool, style: StyleType = "" + ) -> None: + self.console = console + self.hide_cursor = hide_cursor + self.screen = Screen(style=style) + self._changed = False + + def update( + self, *renderables: RenderableType, style: Optional[StyleType] = None + ) -> None: + """Update the screen. + + Args: + renderable (RenderableType, optional): Optional renderable to replace current renderable, + or None for no change. Defaults to None. + style: (Style, optional): Replacement style, or None for no change. Defaults to None. + """ + if renderables: + self.screen.renderable = ( + Group(*renderables) if len(renderables) > 1 else renderables[0] + ) + if style is not None: + self.screen.style = style + self.console.print(self.screen, end="") + + def __enter__(self) -> "ScreenContext": + self._changed = self.console.set_alt_screen(True) + if self._changed and self.hide_cursor: + self.console.show_cursor(False) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if self._changed: + self.console.set_alt_screen(False) + if self.hide_cursor: + self.console.show_cursor(True) + + +class Group: + """Takes a group of renderables and returns a renderable object that renders the group. + + Args: + renderables (Iterable[RenderableType]): An iterable of renderable objects. + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: + self._renderables = renderables + self.fit = fit + self._render: Optional[List[RenderableType]] = None + + @property + def renderables(self) -> List["RenderableType"]: + if self._render is None: + self._render = list(self._renderables) + return self._render + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.fit: + return measure_renderables(console, options, self.renderables) + else: + return Measurement(options.max_width, options.max_width) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> RenderResult: + yield from self.renderables + + +def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: + """A decorator that turns an iterable of renderables in to a group. + + Args: + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def decorator( + method: Callable[..., Iterable[RenderableType]] + ) -> Callable[..., Group]: + """Convert a method that returns an iterable of renderables in to a Group.""" + + @wraps(method) + def _replace(*args: Any, **kwargs: Any) -> Group: + renderables = method(*args, **kwargs) + return Group(*renderables, fit=fit) + + return _replace + + return decorator + + +def _is_jupyter() -> bool: # pragma: no cover + """Check if we're running in a Jupyter notebook.""" + try: + get_ipython # type: ignore[name-defined] + except NameError: + return False + ipython = get_ipython() # type: ignore[name-defined] + shell = ipython.__class__.__name__ + if ( + "google.colab" in str(ipython.__class__) + or os.getenv("DATABRICKS_RUNTIME_VERSION") + or shell == "ZMQInteractiveShell" + ): + return True # Jupyter notebook or qtconsole + elif shell == "TerminalInteractiveShell": + return False # Terminal running IPython + else: + return False # Other type (?) + + +COLOR_SYSTEMS = { + "standard": ColorSystem.STANDARD, + "256": ColorSystem.EIGHT_BIT, + "truecolor": ColorSystem.TRUECOLOR, + "windows": ColorSystem.WINDOWS, +} + +_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} + + +@dataclass +class ConsoleThreadLocals(threading.local): + """Thread local values for Console context.""" + + theme_stack: ThemeStack + buffer: List[Segment] = field(default_factory=list) + buffer_index: int = 0 + + +class RenderHook(ABC): + """Provides hooks in to the render process.""" + + @abstractmethod + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Called with a list of objects to render. + + This method can return a new list of renderables, or modify and return the same list. + + Args: + renderables (List[ConsoleRenderable]): A number of renderable objects. + + Returns: + List[ConsoleRenderable]: A replacement list of renderables. + """ + + +_windows_console_features: Optional["WindowsConsoleFeatures"] = None + + +def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover + global _windows_console_features + if _windows_console_features is not None: + return _windows_console_features + from ._windows import get_windows_console_features + + _windows_console_features = get_windows_console_features() + return _windows_console_features + + +def detect_legacy_windows() -> bool: + """Detect legacy Windows.""" + return WINDOWS and not get_windows_console_features().vt + + +class Console: + """A high level console interface. + + Args: + color_system (str, optional): The color system supported by your terminal, + either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. + force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. + force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. + force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. + soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. + theme (Theme, optional): An optional style theme object, or ``None`` for default theme. + stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. + file (IO, optional): A file object where the console should write to. Defaults to stdout. + quiet (bool, Optional): Boolean to suppress all output. Defaults to False. + width (int, optional): The width of the terminal. Leave as default to auto-detect width. + height (int, optional): The height of the terminal. Leave as default to auto-detect height. + style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. + no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. + tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. + record (bool, optional): Boolean to enable recording of terminal output, + required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False. + markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. + emoji (bool, optional): Enable emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + highlight (bool, optional): Enable automatic highlighting. Defaults to True. + log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. + log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. + log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". + highlighter (HighlighterType, optional): Default highlighter. + legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. + safe_box (bool, optional): Restrict box options that don't render on legacy Windows. + get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), + or None for datetime.now. + get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. + """ + + _environ: Mapping[str, str] = os.environ + + def __init__( + self, + *, + color_system: Optional[ + Literal["auto", "standard", "256", "truecolor", "windows"] + ] = "auto", + force_terminal: Optional[bool] = None, + force_jupyter: Optional[bool] = None, + force_interactive: Optional[bool] = None, + soft_wrap: bool = False, + theme: Optional[Theme] = None, + stderr: bool = False, + file: Optional[IO[str]] = None, + quiet: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + style: Optional[StyleType] = None, + no_color: Optional[bool] = None, + tab_size: int = 8, + record: bool = False, + markup: bool = True, + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + highlight: bool = True, + log_time: bool = True, + log_path: bool = True, + log_time_format: Union[str, FormatTimeCallable] = "[%X]", + highlighter: Optional["HighlighterType"] = ReprHighlighter(), + legacy_windows: Optional[bool] = None, + safe_box: bool = True, + get_datetime: Optional[Callable[[], datetime]] = None, + get_time: Optional[Callable[[], float]] = None, + _environ: Optional[Mapping[str, str]] = None, + ): + # Copy of os.environ allows us to replace it for testing + if _environ is not None: + self._environ = _environ + + self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter + if self.is_jupyter: + if width is None: + jupyter_columns = self._environ.get("JUPYTER_COLUMNS") + if jupyter_columns is not None and jupyter_columns.isdigit(): + width = int(jupyter_columns) + else: + width = JUPYTER_DEFAULT_COLUMNS + if height is None: + jupyter_lines = self._environ.get("JUPYTER_LINES") + if jupyter_lines is not None and jupyter_lines.isdigit(): + height = int(jupyter_lines) + else: + height = JUPYTER_DEFAULT_LINES + + self.tab_size = tab_size + self.record = record + self._markup = markup + self._emoji = emoji + self._emoji_variant: Optional[EmojiVariant] = emoji_variant + self._highlight = highlight + self.legacy_windows: bool = ( + (detect_legacy_windows() and not self.is_jupyter) + if legacy_windows is None + else legacy_windows + ) + + if width is None: + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) - self.legacy_windows + if height is None: + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + self.soft_wrap = soft_wrap + self._width = width + self._height = height + + self._color_system: Optional[ColorSystem] + + self._force_terminal = None + if force_terminal is not None: + self._force_terminal = force_terminal + + self._file = file + self.quiet = quiet + self.stderr = stderr + + if color_system is None: + self._color_system = None + elif color_system == "auto": + self._color_system = self._detect_color_system() + else: + self._color_system = COLOR_SYSTEMS[color_system] + + self._lock = threading.RLock() + self._log_render = LogRender( + show_time=log_time, + show_path=log_path, + time_format=log_time_format, + ) + self.highlighter: HighlighterType = highlighter or _null_highlighter + self.safe_box = safe_box + self.get_datetime = get_datetime or datetime.now + self.get_time = get_time or monotonic + self.style = style + self.no_color = ( + no_color if no_color is not None else "NO_COLOR" in self._environ + ) + self.is_interactive = ( + (self.is_terminal and not self.is_dumb_terminal) + if force_interactive is None + else force_interactive + ) + + self._record_buffer_lock = threading.RLock() + self._thread_locals = ConsoleThreadLocals( + theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) + ) + self._record_buffer: List[Segment] = [] + self._render_hooks: List[RenderHook] = [] + self._live: Optional["Live"] = None + self._is_alt_screen = False + + def __repr__(self) -> str: + return f"" + + @property + def file(self) -> IO[str]: + """Get the file object to write to.""" + file = self._file or (sys.stderr if self.stderr else sys.stdout) + file = getattr(file, "rich_proxied_file", file) + if file is None: + file = NULL_FILE + return file + + @file.setter + def file(self, new_file: IO[str]) -> None: + """Set a new file object.""" + self._file = new_file + + @property + def _buffer(self) -> List[Segment]: + """Get a thread local buffer.""" + return self._thread_locals.buffer + + @property + def _buffer_index(self) -> int: + """Get a thread local buffer.""" + return self._thread_locals.buffer_index + + @_buffer_index.setter + def _buffer_index(self, value: int) -> None: + self._thread_locals.buffer_index = value + + @property + def _theme_stack(self) -> ThemeStack: + """Get the thread local theme stack.""" + return self._thread_locals.theme_stack + + def _detect_color_system(self) -> Optional[ColorSystem]: + """Detect color system from env vars.""" + if self.is_jupyter: + return ColorSystem.TRUECOLOR + if not self.is_terminal or self.is_dumb_terminal: + return None + if WINDOWS: # pragma: no cover + if self.legacy_windows: # pragma: no cover + return ColorSystem.WINDOWS + windows_console_features = get_windows_console_features() + return ( + ColorSystem.TRUECOLOR + if windows_console_features.truecolor + else ColorSystem.EIGHT_BIT + ) + else: + color_term = self._environ.get("COLORTERM", "").strip().lower() + if color_term in ("truecolor", "24bit"): + return ColorSystem.TRUECOLOR + term = self._environ.get("TERM", "").strip().lower() + _term_name, _hyphen, colors = term.rpartition("-") + color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) + return color_system + + def _enter_buffer(self) -> None: + """Enter in to a buffer context, and buffer all output.""" + self._buffer_index += 1 + + def _exit_buffer(self) -> None: + """Leave buffer context, and render content if required.""" + self._buffer_index -= 1 + self._check_buffer() + + def set_live(self, live: "Live") -> None: + """Set Live instance. Used by Live context manager. + + Args: + live (Live): Live instance using this Console. + + Raises: + errors.LiveError: If this Console has a Live context currently active. + """ + with self._lock: + if self._live is not None: + raise errors.LiveError("Only one live display may be active at once") + self._live = live + + def clear_live(self) -> None: + """Clear the Live instance.""" + with self._lock: + self._live = None + + def push_render_hook(self, hook: RenderHook) -> None: + """Add a new render hook to the stack. + + Args: + hook (RenderHook): Render hook instance. + """ + with self._lock: + self._render_hooks.append(hook) + + def pop_render_hook(self) -> None: + """Pop the last renderhook from the stack.""" + with self._lock: + self._render_hooks.pop() + + def __enter__(self) -> "Console": + """Own context manager to enter buffer context.""" + self._enter_buffer() + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + """Exit buffer context.""" + self._exit_buffer() + + def begin_capture(self) -> None: + """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" + self._enter_buffer() + + def end_capture(self) -> str: + """End capture mode and return captured string. + + Returns: + str: Console output. + """ + render_result = self._render_buffer(self._buffer) + del self._buffer[:] + self._exit_buffer() + return render_result + + def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: + """Push a new theme on to the top of the stack, replacing the styles from the previous theme. + Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather + than calling this method directly. + + Args: + theme (Theme): A theme instance. + inherit (bool, optional): Inherit existing styles. Defaults to True. + """ + self._theme_stack.push_theme(theme, inherit=inherit) + + def pop_theme(self) -> None: + """Remove theme from top of stack, restoring previous theme.""" + self._theme_stack.pop_theme() + + def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: + """Use a different theme for the duration of the context manager. + + Args: + theme (Theme): Theme instance to user. + inherit (bool, optional): Inherit existing console styles. Defaults to True. + + Returns: + ThemeContext: [description] + """ + return ThemeContext(self, theme, inherit) + + @property + def color_system(self) -> Optional[str]: + """Get color system string. + + Returns: + Optional[str]: "standard", "256" or "truecolor". + """ + + if self._color_system is not None: + return _COLOR_SYSTEMS_NAMES[self._color_system] + else: + return None + + @property + def encoding(self) -> str: + """Get the encoding of the console file, e.g. ``"utf-8"``. + + Returns: + str: A standard encoding string. + """ + return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() + + @property + def is_terminal(self) -> bool: + """Check if the console is writing to a terminal. + + Returns: + bool: True if the console writing to a device capable of + understanding terminal codes, otherwise False. + """ + if self._force_terminal is not None: + return self._force_terminal + + if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith( + "idlelib" + ): + # Return False for Idle which claims to be a tty but can't handle ansi codes + return False + + if self.is_jupyter: + # return False for Jupyter, which may have FORCE_COLOR set + return False + + # If FORCE_COLOR env var has any value at all, we assume a terminal. + force_color = self._environ.get("FORCE_COLOR") + if force_color is not None: + self._force_terminal = True + return True + + isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) + try: + return False if isatty is None else isatty() + except ValueError: + # in some situation (at the end of a pytest run for example) isatty() can raise + # ValueError: I/O operation on closed file + # return False because we aren't in a terminal anymore + return False + + @property + def is_dumb_terminal(self) -> bool: + """Detect dumb terminal. + + Returns: + bool: True if writing to a dumb terminal, otherwise False. + + """ + _term = self._environ.get("TERM", "") + is_dumb = _term.lower() in ("dumb", "unknown") + return self.is_terminal and is_dumb + + @property + def options(self) -> ConsoleOptions: + """Get default console options.""" + return ConsoleOptions( + max_height=self.size.height, + size=self.size, + legacy_windows=self.legacy_windows, + min_width=1, + max_width=self.width, + encoding=self.encoding, + is_terminal=self.is_terminal, + ) + + @property + def size(self) -> ConsoleDimensions: + """Get the size of the console. + + Returns: + ConsoleDimensions: A named tuple containing the dimensions. + """ + + if self._width is not None and self._height is not None: + return ConsoleDimensions(self._width - self.legacy_windows, self._height) + + if self.is_dumb_terminal: + return ConsoleDimensions(80, 25) + + width: Optional[int] = None + height: Optional[int] = None + + if WINDOWS: # pragma: no cover + try: + width, height = os.get_terminal_size() + except (AttributeError, ValueError, OSError): # Probably not a terminal + pass + else: + for file_descriptor in _STD_STREAMS: + try: + width, height = os.get_terminal_size(file_descriptor) + except (AttributeError, ValueError, OSError): + pass + else: + break + + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + # get_terminal_size can report 0, 0 if run from pseudo-terminal + width = width or 80 + height = height or 25 + return ConsoleDimensions( + width - self.legacy_windows if self._width is None else self._width, + height if self._height is None else self._height, + ) + + @size.setter + def size(self, new_size: Tuple[int, int]) -> None: + """Set a new size for the terminal. + + Args: + new_size (Tuple[int, int]): New width and height. + """ + width, height = new_size + self._width = width + self._height = height + + @property + def width(self) -> int: + """Get the width of the console. + + Returns: + int: The width (in characters) of the console. + """ + return self.size.width + + @width.setter + def width(self, width: int) -> None: + """Set width. + + Args: + width (int): New width. + """ + self._width = width + + @property + def height(self) -> int: + """Get the height of the console. + + Returns: + int: The height (in lines) of the console. + """ + return self.size.height + + @height.setter + def height(self, height: int) -> None: + """Set height. + + Args: + height (int): new height. + """ + self._height = height + + def bell(self) -> None: + """Play a 'bell' sound (if supported by the terminal).""" + self.control(Control.bell()) + + def capture(self) -> Capture: + """A context manager to *capture* the result of print() or log() in a string, + rather than writing it to the console. + + Example: + >>> from rich.console import Console + >>> console = Console() + >>> with console.capture() as capture: + ... console.print("[bold magenta]Hello World[/]") + >>> print(capture.get()) + + Returns: + Capture: Context manager with disables writing to the terminal. + """ + capture = Capture(self) + return capture + + def pager( + self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False + ) -> PagerContext: + """A context manager to display anything printed within a "pager". The pager application + is defined by the system and will typically support at least pressing a key to scroll. + + Args: + pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. + styles (bool, optional): Show styles in pager. Defaults to False. + links (bool, optional): Show links in pager. Defaults to False. + + Example: + >>> from rich.console import Console + >>> from rich.__main__ import make_test_card + >>> console = Console() + >>> with console.pager(): + console.print(make_test_card()) + + Returns: + PagerContext: A context manager. + """ + return PagerContext(self, pager=pager, styles=styles, links=links) + + def line(self, count: int = 1) -> None: + """Write new line(s). + + Args: + count (int, optional): Number of new lines. Defaults to 1. + """ + + assert count >= 0, "count must be >= 0" + self.print(NewLine(count)) + + def clear(self, home: bool = True) -> None: + """Clear the screen. + + Args: + home (bool, optional): Also move the cursor to 'home' position. Defaults to True. + """ + if home: + self.control(Control.clear(), Control.home()) + else: + self.control(Control.clear()) + + def status( + self, + status: RenderableType, + *, + spinner: str = "dots", + spinner_style: StyleType = "status.spinner", + speed: float = 1.0, + refresh_per_second: float = 12.5, + ) -> "Status": + """Display a status and spinner. + + Args: + status (RenderableType): A status renderable (str or Text typically). + spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". + spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". + speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. + refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. + + Returns: + Status: A Status object that may be used as a context manager. + """ + from .status import Status + + status_renderable = Status( + status, + console=self, + spinner=spinner, + spinner_style=spinner_style, + speed=speed, + refresh_per_second=refresh_per_second, + ) + return status_renderable + + def show_cursor(self, show: bool = True) -> bool: + """Show or hide the cursor. + + Args: + show (bool, optional): Set visibility of the cursor. + """ + if self.is_terminal: + self.control(Control.show_cursor(show)) + return True + return False + + def set_alt_screen(self, enable: bool = True) -> bool: + """Enables alternative screen mode. + + Note, if you enable this mode, you should ensure that is disabled before + the application exits. See :meth:`~rich.Console.screen` for a context manager + that handles this for you. + + Args: + enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. + + Returns: + bool: True if the control codes were written. + + """ + changed = False + if self.is_terminal and not self.legacy_windows: + self.control(Control.alt_screen(enable)) + changed = True + self._is_alt_screen = enable + return changed + + @property + def is_alt_screen(self) -> bool: + """Check if the alt screen was enabled. + + Returns: + bool: True if the alt screen was enabled, otherwise False. + """ + return self._is_alt_screen + + def set_window_title(self, title: str) -> bool: + """Set the title of the console terminal window. + + Warning: There is no means within Rich of "resetting" the window title to its + previous value, meaning the title you set will persist even after your application + exits. + + ``fish`` shell resets the window title before and after each command by default, + negating this issue. Windows Terminal and command prompt will also reset the title for you. + Most other shells and terminals, however, do not do this. + + Some terminals may require configuration changes before you can set the title. + Some terminals may not support setting the title at all. + + Other software (including the terminal itself, the shell, custom prompts, plugins, etc.) + may also set the terminal window title. This could result in whatever value you write + using this method being overwritten. + + Args: + title (str): The new title of the terminal window. + + Returns: + bool: True if the control code to change the terminal title was + written, otherwise False. Note that a return value of True + does not guarantee that the window title has actually changed, + since the feature may be unsupported/disabled in some terminals. + """ + if self.is_terminal: + self.control(Control.title(title)) + return True + return False + + def screen( + self, hide_cursor: bool = True, style: Optional[StyleType] = None + ) -> "ScreenContext": + """Context manager to enable and disable 'alternative screen' mode. + + Args: + hide_cursor (bool, optional): Also hide the cursor. Defaults to False. + style (Style, optional): Optional style for screen. Defaults to None. + + Returns: + ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. + """ + return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") + + def measure( + self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None + ) -> Measurement: + """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains + information regarding the number of characters required to print the renderable. + + Args: + renderable (RenderableType): Any renderable or string. + options (Optional[ConsoleOptions], optional): Options to use when measuring, or None + to use default options. Defaults to None. + + Returns: + Measurement: A measurement of the renderable. + """ + measurement = Measurement.get(self, options or self.options, renderable) + return measurement + + def render( + self, renderable: RenderableType, options: Optional[ConsoleOptions] = None + ) -> Iterable[Segment]: + """Render an object in to an iterable of `Segment` instances. + + This method contains the logic for rendering objects with the console protocol. + You are unlikely to need to use it directly, unless you are extending the library. + + Args: + renderable (RenderableType): An object supporting the console protocol, or + an object that may be converted to a string. + options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. + + Returns: + Iterable[Segment]: An iterable of segments that may be rendered. + """ + + _options = options or self.options + if _options.max_width < 1: + # No space to render anything. This prevents potential recursion errors. + return + render_iterable: RenderResult + + renderable = rich_cast(renderable) + if hasattr(renderable, "__rich_console__") and not isclass(renderable): + render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr] + elif isinstance(renderable, str): + text_renderable = self.render_str( + renderable, highlight=_options.highlight, markup=_options.markup + ) + render_iterable = text_renderable.__rich_console__(self, _options) + else: + raise errors.NotRenderableError( + f"Unable to render {renderable!r}; " + "A str, Segment or object with __rich_console__ method is required" + ) + + try: + iter_render = iter(render_iterable) + except TypeError: + raise errors.NotRenderableError( + f"object {render_iterable!r} is not renderable" + ) + _Segment = Segment + _options = _options.reset_height() + for render_output in iter_render: + if isinstance(render_output, _Segment): + yield render_output + else: + yield from self.render(render_output, _options) + + def render_lines( + self, + renderable: RenderableType, + options: Optional[ConsoleOptions] = None, + *, + style: Optional[Style] = None, + pad: bool = True, + new_lines: bool = False, + ) -> List[List[Segment]]: + """Render objects in to a list of lines. + + The output of render_lines is useful when further formatting of rendered console text + is required, such as the Panel class which draws a border around any renderable object. + + Args: + renderable (RenderableType): Any object renderable in the console. + options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. + style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. + pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. + new_lines (bool, optional): Include "\n" characters at end of lines. + + Returns: + List[List[Segment]]: A list of lines, where a line is a list of Segment objects. + """ + with self._lock: + render_options = options or self.options + _rendered = self.render(renderable, render_options) + if style: + _rendered = Segment.apply_style(_rendered, style) + + render_height = render_options.height + if render_height is not None: + render_height = max(0, render_height) + + lines = list( + islice( + Segment.split_and_crop_lines( + _rendered, + render_options.max_width, + include_new_lines=new_lines, + pad=pad, + style=style, + ), + None, + render_height, + ) + ) + if render_options.height is not None: + extra_lines = render_options.height - len(lines) + if extra_lines > 0: + pad_line = [ + [Segment(" " * render_options.max_width, style), Segment("\n")] + if new_lines + else [Segment(" " * render_options.max_width, style)] + ] + lines.extend(pad_line * extra_lines) + + return lines + + def render_str( + self, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + highlighter: Optional[HighlighterType] = None, + ) -> "Text": + """Convert a string to a Text instance. This is called automatically if + you print or log a string. + + Args: + text (str): Text to render. + style (Union[str, Style], optional): Style to apply to rendered text. + justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. + highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. + highlighter (HighlighterType, optional): Optional highlighter to apply. + Returns: + ConsoleRenderable: Renderable object. + + """ + emoji_enabled = emoji or (emoji is None and self._emoji) + markup_enabled = markup or (markup is None and self._markup) + highlight_enabled = highlight or (highlight is None and self._highlight) + + if markup_enabled: + rich_text = render_markup( + text, + style=style, + emoji=emoji_enabled, + emoji_variant=self._emoji_variant, + ) + rich_text.justify = justify + rich_text.overflow = overflow + else: + rich_text = Text( + _emoji_replace(text, default_variant=self._emoji_variant) + if emoji_enabled + else text, + justify=justify, + overflow=overflow, + style=style, + ) + + _highlighter = (highlighter or self.highlighter) if highlight_enabled else None + if _highlighter is not None: + highlight_text = _highlighter(str(rich_text)) + highlight_text.copy_styles(rich_text) + return highlight_text + + return rich_text + + def get_style( + self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None + ) -> Style: + """Get a Style instance by its theme name or parse a definition. + + Args: + name (str): The name of a style or a style definition. + + Returns: + Style: A Style object. + + Raises: + MissingStyle: If no style could be parsed from name. + + """ + if isinstance(name, Style): + return name + + try: + style = self._theme_stack.get(name) + if style is None: + style = Style.parse(name) + return style.copy() if style.link else style + except errors.StyleSyntaxError as error: + if default is not None: + return self.get_style(default) + raise errors.MissingStyle( + f"Failed to get style {name!r}; {error}" + ) from None + + def _collect_renderables( + self, + objects: Iterable[Any], + sep: str, + end: str, + *, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + ) -> List[ConsoleRenderable]: + """Combine a number of renderables and text into one renderable. + + Args: + objects (Iterable[Any]): Anything that Rich can render. + sep (str): String to write between print data. + end (str): String to write at end of print data. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. + + Returns: + List[ConsoleRenderable]: A list of things to render. + """ + renderables: List[ConsoleRenderable] = [] + _append = renderables.append + text: List[Text] = [] + append_text = text.append + + append = _append + if justify in ("left", "center", "right"): + + def align_append(renderable: RenderableType) -> None: + _append(Align(renderable, cast(AlignMethod, justify))) + + append = align_append + + _highlighter: HighlighterType = _null_highlighter + if highlight or (highlight is None and self._highlight): + _highlighter = self.highlighter + + def check_text() -> None: + if text: + sep_text = Text(sep, justify=justify, end=end) + append(sep_text.join(text)) + text.clear() + + for renderable in objects: + renderable = rich_cast(renderable) + if isinstance(renderable, str): + append_text( + self.render_str( + renderable, emoji=emoji, markup=markup, highlighter=_highlighter + ) + ) + elif isinstance(renderable, Text): + append_text(renderable) + elif isinstance(renderable, ConsoleRenderable): + check_text() + append(renderable) + elif is_expandable(renderable): + check_text() + append(Pretty(renderable, highlighter=_highlighter)) + else: + append_text(_highlighter(str(renderable))) + + check_text() + + if self.style is not None: + style = self.get_style(self.style) + renderables = [Styled(renderable, style) for renderable in renderables] + + return renderables + + def rule( + self, + title: TextType = "", + *, + characters: str = "─", + style: Union[str, Style] = "rule.line", + align: AlignMethod = "center", + ) -> None: + """Draw a line with optional centered title. + + Args: + title (str, optional): Text to render over the rule. Defaults to "". + characters (str, optional): Character(s) to form the line. Defaults to "─". + style (str, optional): Style of line. Defaults to "rule.line". + align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". + """ + from .rule import Rule + + rule = Rule(title=title, characters=characters, style=style, align=align) + self.print(rule) + + def control(self, *control: Control) -> None: + """Insert non-printing control codes. + + Args: + control_codes (str): Control codes, such as those that may move the cursor. + """ + if not self.is_dumb_terminal: + with self: + self._buffer.extend(_control.segment for _control in control) + + def out( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + highlight: Optional[bool] = None, + ) -> None: + """Output to the terminal. This is a low-level way of writing to the terminal which unlike + :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will + optionally apply highlighting and a basic style. + + Args: + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use + console default. Defaults to ``None``. + """ + raw_output: str = sep.join(str(_object) for _object in objects) + self.print( + raw_output, + style=style, + highlight=highlight, + emoji=False, + markup=False, + no_wrap=True, + overflow="ignore", + crop=False, + end=end, + ) + + def print( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + no_wrap: Optional[bool] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + width: Optional[int] = None, + height: Optional[int] = None, + crop: bool = True, + soft_wrap: Optional[bool] = None, + new_line_start: bool = False, + ) -> None: + """Print to the console. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. + width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. + crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. + soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for + Console default. Defaults to ``None``. + new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. + """ + if not objects: + objects = (NewLine(),) + + if soft_wrap is None: + soft_wrap = self.soft_wrap + if soft_wrap: + if no_wrap is None: + no_wrap = True + if overflow is None: + overflow = "ignore" + crop = False + render_hooks = self._render_hooks[:] + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + render_options = self.options.update( + justify=justify, + overflow=overflow, + width=min(width, self.width) if width is not None else NO_CHANGE, + height=height, + no_wrap=no_wrap, + markup=markup, + highlight=highlight, + ) + + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + if style is None: + for renderable in renderables: + extend(render(renderable, render_options)) + else: + for renderable in renderables: + extend( + Segment.apply_style( + render(renderable, render_options), self.get_style(style) + ) + ) + if new_line_start: + if ( + len("".join(segment.text for segment in new_segments).splitlines()) + > 1 + ): + new_segments.insert(0, Segment.line()) + if crop: + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + else: + self._buffer.extend(new_segments) + + def print_json( + self, + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = False, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (Optional[str]): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + from pip._vendor.rich.json import JSON + + if json is None: + json_renderable = JSON.from_data( + data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + else: + if not isinstance(json, str): + raise TypeError( + f"json must be str. Did you mean print_json(data={json!r}) ?" + ) + json_renderable = JSON( + json, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + self.print(json_renderable, soft_wrap=True) + + def update_screen( + self, + renderable: RenderableType, + *, + region: Optional[Region] = None, + options: Optional[ConsoleOptions] = None, + ) -> None: + """Update the screen at a given offset. + + Args: + renderable (RenderableType): A Rich renderable. + region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. + x (int, optional): x offset. Defaults to 0. + y (int, optional): y offset. Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + render_options = options or self.options + if region is None: + x = y = 0 + render_options = render_options.update_dimensions( + render_options.max_width, render_options.height or self.height + ) + else: + x, y, width, height = region + render_options = render_options.update_dimensions(width, height) + + lines = self.render_lines(renderable, options=render_options) + self.update_screen_lines(lines, x, y) + + def update_screen_lines( + self, lines: List[List[Segment]], x: int = 0, y: int = 0 + ) -> None: + """Update lines of the screen at a given offset. + + Args: + lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). + x (int, optional): x offset (column no). Defaults to 0. + y (int, optional): y offset (column no). Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + screen_update = ScreenUpdate(lines, x, y) + segments = self.render(screen_update) + self._buffer.extend(segments) + self._check_buffer() + + def print_exception( + self, + *, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> None: + """Prints a rich render of the last exception and traceback. + + Args: + width (Optional[int], optional): Number of characters used to render code. Defaults to 100. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from .traceback import Traceback + + traceback = Traceback( + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + suppress=suppress, + max_frames=max_frames, + ) + self.print(traceback) + + @staticmethod + def _caller_frame_info( + offset: int, + currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, + ) -> Tuple[str, int, Dict[str, Any]]: + """Get caller frame information. + + Args: + offset (int): the caller offset within the current frame stack. + currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to + retrieve the current frame. Defaults to ``inspect.currentframe``. + + Returns: + Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and + the dictionary of local variables associated with the caller frame. + + Raises: + RuntimeError: If the stack offset is invalid. + """ + # Ignore the frame of this local helper + offset += 1 + + frame = currentframe() + if frame is not None: + # Use the faster currentframe where implemented + while offset and frame is not None: + frame = frame.f_back + offset -= 1 + assert frame is not None + return frame.f_code.co_filename, frame.f_lineno, frame.f_locals + else: + # Fallback to the slower stack + frame_info = inspect.stack()[offset] + return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals + + def log( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + log_locals: bool = False, + _stack_offset: int = 1, + ) -> None: + """Log rich content to the terminal. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. + log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` + was called. Defaults to False. + _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. + """ + if not objects: + objects = (NewLine(),) + + render_hooks = self._render_hooks[:] + + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + if style is not None: + renderables = [Styled(renderable, style) for renderable in renderables] + + filename, line_no, locals = self._caller_frame_info(_stack_offset) + link_path = None if filename.startswith("<") else os.path.abspath(filename) + path = filename.rpartition(os.sep)[-1] + if log_locals: + locals_map = { + key: value + for key, value in locals.items() + if not key.startswith("__") + } + renderables.append(render_scope(locals_map, title="[i]locals")) + + renderables = [ + self._log_render( + self, + renderables, + log_time=self.get_datetime(), + path=path, + line_no=line_no, + link_path=link_path, + ) + ] + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + render_options = self.options + for renderable in renderables: + extend(render(renderable, render_options)) + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + + def _check_buffer(self) -> None: + """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) + Rendering is supported on Windows, Unix and Jupyter environments. For + legacy Windows consoles, the win32 API is called directly. + This method will also record what it renders if recording is enabled via Console.record. + """ + if self.quiet: + del self._buffer[:] + return + with self._lock: + if self.record: + with self._record_buffer_lock: + self._record_buffer.extend(self._buffer[:]) + + if self._buffer_index == 0: + if self.is_jupyter: # pragma: no cover + from .jupyter import display + + display(self._buffer, self._render_buffer(self._buffer[:])) + del self._buffer[:] + else: + if WINDOWS: + use_legacy_windows_render = False + if self.legacy_windows: + fileno = get_fileno(self.file) + if fileno is not None: + use_legacy_windows_render = ( + fileno in _STD_STREAMS_OUTPUT + ) + + if use_legacy_windows_render: + from pip._vendor.rich._win32_console import LegacyWindowsTerm + from pip._vendor.rich._windows_renderer import legacy_windows_render + + buffer = self._buffer[:] + if self.no_color and self._color_system: + buffer = list(Segment.remove_color(buffer)) + + legacy_windows_render(buffer, LegacyWindowsTerm(self.file)) + else: + # Either a non-std stream on legacy Windows, or modern Windows. + text = self._render_buffer(self._buffer[:]) + # https://bugs.python.org/issue37871 + # https://github.com/python/cpython/issues/82052 + # We need to avoid writing more than 32Kb in a single write, due to the above bug + write = self.file.write + # Worse case scenario, every character is 4 bytes of utf-8 + MAX_WRITE = 32 * 1024 // 4 + try: + if len(text) <= MAX_WRITE: + write(text) + else: + batch: List[str] = [] + batch_append = batch.append + size = 0 + for line in text.splitlines(True): + if size + len(line) > MAX_WRITE and batch: + write("".join(batch)) + batch.clear() + size = 0 + batch_append(line) + size += len(line) + if batch: + write("".join(batch)) + batch.clear() + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + else: + text = self._render_buffer(self._buffer[:]) + try: + self.file.write(text) + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + + self.file.flush() + del self._buffer[:] + + def _render_buffer(self, buffer: Iterable[Segment]) -> str: + """Render buffered output, and clear buffer.""" + output: List[str] = [] + append = output.append + color_system = self._color_system + legacy_windows = self.legacy_windows + not_terminal = not self.is_terminal + if self.no_color and color_system: + buffer = Segment.remove_color(buffer) + for text, style, control in buffer: + if style: + append( + style.render( + text, + color_system=color_system, + legacy_windows=legacy_windows, + ) + ) + elif not (not_terminal and control): + append(text) + + rendered = "".join(output) + return rendered + + def input( + self, + prompt: TextType = "", + *, + markup: bool = True, + emoji: bool = True, + password: bool = False, + stream: Optional[TextIO] = None, + ) -> str: + """Displays a prompt and waits for input from the user. The prompt may contain color / style. + + It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. + + Args: + prompt (Union[str, Text]): Text to render in the prompt. + markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. + emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. + password: (bool, optional): Hide typed text. Defaults to False. + stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. + + Returns: + str: Text read from stdin. + """ + if prompt: + self.print(prompt, markup=markup, emoji=emoji, end="") + if password: + result = getpass("", stream=stream) + else: + if stream: + result = stream.readline() + else: + result = input() + return result + + def export_text(self, *, clear: bool = True, styles: bool = False) -> str: + """Generate text from console contents (requires record=True argument in constructor). + + Args: + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. + Defaults to ``False``. + + Returns: + str: String containing console contents. + + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + + with self._record_buffer_lock: + if styles: + text = "".join( + (style.render(text) if style else text) + for text, style, _ in self._record_buffer + ) + else: + text = "".join( + segment.text + for segment in self._record_buffer + if not segment.control + ) + if clear: + del self._record_buffer[:] + return text + + def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: + """Generate text from console and save to a given location (requires record=True argument in constructor). + + Args: + path (str): Path to write text files. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. + Defaults to ``False``. + + """ + text = self.export_text(clear=clear, styles=styles) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(text) + + def export_html( + self, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: Optional[str] = None, + inline_styles: bool = False, + ) -> str: + """Generate HTML from console contents (requires record=True argument in constructor). + + Args: + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + Returns: + str: String containing console contents as HTML. + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + fragments: List[str] = [] + append = fragments.append + _theme = theme or DEFAULT_TERMINAL_THEME + stylesheet = "" + + render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format + + with self._record_buffer_lock: + if inline_styles: + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + if style.link: + text = f'{text}' + text = f'{text}' if rule else text + append(text) + else: + styles: Dict[str, int] = {} + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + style_number = styles.setdefault(rule, len(styles) + 1) + if style.link: + text = f'{text}' + else: + text = f'{text}' + append(text) + stylesheet_rules: List[str] = [] + stylesheet_append = stylesheet_rules.append + for style_rule, style_number in styles.items(): + if style_rule: + stylesheet_append(f".r{style_number} {{{style_rule}}}") + stylesheet = "\n".join(stylesheet_rules) + + rendered_code = render_code_format.format( + code="".join(fragments), + stylesheet=stylesheet, + foreground=_theme.foreground_color.hex, + background=_theme.background_color.hex, + ) + if clear: + del self._record_buffer[:] + return rendered_code + + def save_html( + self, + path: str, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_HTML_FORMAT, + inline_styles: bool = False, + ) -> None: + """Generate HTML from console contents and write to a file (requires record=True argument in constructor). + + Args: + path (str): Path to write html file. + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + """ + html = self.export_html( + theme=theme, + clear=clear, + code_format=code_format, + inline_styles=inline_styles, + ) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(html) + + def export_svg( + self, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> str: + """ + Generate an SVG from the console contents (requires record=True in Console constructor). + + Args: + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + + from pip._vendor.rich.cells import cell_len + + style_cache: Dict[Style, str] = {} + + def get_svg_style(style: Style) -> str: + """Convert a Style to CSS rules for SVG.""" + if style in style_cache: + return style_cache[style] + css_rules = [] + color = ( + _theme.foreground_color + if (style.color is None or style.color.is_default) + else style.color.get_truecolor(_theme) + ) + bgcolor = ( + _theme.background_color + if (style.bgcolor is None or style.bgcolor.is_default) + else style.bgcolor.get_truecolor(_theme) + ) + if style.reverse: + color, bgcolor = bgcolor, color + if style.dim: + color = blend_rgb(color, bgcolor, 0.4) + css_rules.append(f"fill: {color.hex}") + if style.bold: + css_rules.append("font-weight: bold") + if style.italic: + css_rules.append("font-style: italic;") + if style.underline: + css_rules.append("text-decoration: underline;") + if style.strike: + css_rules.append("text-decoration: line-through;") + + css = ";".join(css_rules) + style_cache[style] = css + return css + + _theme = theme or SVG_EXPORT_THEME + + width = self.width + char_height = 20 + char_width = char_height * font_aspect_ratio + line_height = char_height * 1.22 + + margin_top = 1 + margin_right = 1 + margin_bottom = 1 + margin_left = 1 + + padding_top = 40 + padding_right = 8 + padding_bottom = 8 + padding_left = 8 + + padding_width = padding_left + padding_right + padding_height = padding_top + padding_bottom + margin_width = margin_left + margin_right + margin_height = margin_top + margin_bottom + + text_backgrounds: List[str] = [] + text_group: List[str] = [] + classes: Dict[str, int] = {} + style_no = 1 + + def escape_text(text: str) -> str: + """HTML escape text and replace spaces with nbsp.""" + return escape(text).replace(" ", " ") + + def make_tag( + name: str, content: Optional[str] = None, **attribs: object + ) -> str: + """Make a tag from name, content, and attributes.""" + + def stringify(value: object) -> str: + if isinstance(value, (float)): + return format(value, "g") + return str(value) + + tag_attribs = " ".join( + f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"' + for k, v in attribs.items() + ) + return ( + f"<{name} {tag_attribs}>{content}" + if content + else f"<{name} {tag_attribs}/>" + ) + + with self._record_buffer_lock: + segments = list(Segment.filter_control(self._record_buffer)) + if clear: + self._record_buffer.clear() + + if unique_id is None: + unique_id = "terminal-" + str( + zlib.adler32( + ("".join(repr(segment) for segment in segments)).encode( + "utf-8", + "ignore", + ) + + title.encode("utf-8", "ignore") + ) + ) + y = 0 + for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)): + x = 0 + for text, style, _control in line: + style = style or Style() + rules = get_svg_style(style) + if rules not in classes: + classes[rules] = style_no + style_no += 1 + class_name = f"r{classes[rules]}" + + if style.reverse: + has_background = True + background = ( + _theme.foreground_color.hex + if style.color is None + else style.color.get_truecolor(_theme).hex + ) + else: + bgcolor = style.bgcolor + has_background = bgcolor is not None and not bgcolor.is_default + background = ( + _theme.background_color.hex + if style.bgcolor is None + else style.bgcolor.get_truecolor(_theme).hex + ) + + text_length = cell_len(text) + if has_background: + text_backgrounds.append( + make_tag( + "rect", + fill=background, + x=x * char_width, + y=y * line_height + 1.5, + width=char_width * text_length, + height=line_height + 0.25, + shape_rendering="crispEdges", + ) + ) + + if text != " " * len(text): + text_group.append( + make_tag( + "text", + escape_text(text), + _class=f"{unique_id}-{class_name}", + x=x * char_width, + y=y * line_height + char_height, + textLength=char_width * len(text), + clip_path=f"url(#{unique_id}-line-{y})", + ) + ) + x += cell_len(text) + + line_offsets = [line_no * line_height + 1.5 for line_no in range(y)] + lines = "\n".join( + f""" + {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)} + """ + for line_no, offset in enumerate(line_offsets) + ) + + styles = "\n".join( + f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items() + ) + backgrounds = "".join(text_backgrounds) + matrix = "".join(text_group) + + terminal_width = ceil(width * char_width + padding_width) + terminal_height = (y + 1) * line_height + padding_height + chrome = make_tag( + "rect", + fill=_theme.background_color.hex, + stroke="rgba(255,255,255,0.35)", + stroke_width="1", + x=margin_left, + y=margin_top, + width=terminal_width, + height=terminal_height, + rx=8, + ) + + title_color = _theme.foreground_color.hex + if title: + chrome += make_tag( + "text", + escape_text(title), + _class=f"{unique_id}-title", + fill=title_color, + text_anchor="middle", + x=terminal_width // 2, + y=margin_top + char_height + 6, + ) + chrome += f""" + + + + + + """ + + svg = code_format.format( + unique_id=unique_id, + char_width=char_width, + char_height=char_height, + line_height=line_height, + terminal_width=char_width * width - 1, + terminal_height=(y + 1) * line_height - 1, + width=terminal_width + margin_width, + height=terminal_height + margin_height, + terminal_x=margin_left + padding_left, + terminal_y=margin_top + padding_top, + styles=styles, + chrome=chrome, + backgrounds=backgrounds, + matrix=matrix, + lines=lines, + ) + return svg + + def save_svg( + self, + path: str, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> None: + """Generate an SVG file from the console contents (requires record=True in Console constructor). + + Args: + path (str): The path to write the SVG to. + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + svg = self.export_svg( + title=title, + theme=theme, + clear=clear, + code_format=code_format, + font_aspect_ratio=font_aspect_ratio, + unique_id=unique_id, + ) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(svg) + + +def _svg_hash(svg_main_code: str) -> str: + """Returns a unique hash for the given SVG main code. + + Args: + svg_main_code (str): The content we're going to inject in the SVG envelope. + + Returns: + str: a hash of the given content + """ + return str(zlib.adler32(svg_main_code.encode())) + + +if __name__ == "__main__": # pragma: no cover + console = Console(record=True) + + console.log( + "JSONRPC [i]request[/i]", + 5, + 1.3, + True, + False, + None, + { + "jsonrpc": "2.0", + "method": "subtract", + "params": {"minuend": 42, "subtrahend": 23}, + "id": 3, + }, + ) + + console.log("Hello, World!", "{'a': 1}", repr(console)) + + console.print( + { + "name": None, + "empty": [], + "quiz": { + "sport": { + "answered": True, + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriors", + "Huston Rocket", + ], + "answer": "Huston Rocket", + }, + }, + "maths": { + "answered": False, + "q1": { + "question": "5 + 7 = ?", + "options": [10, 11, 12, 13], + "answer": 12, + }, + "q2": { + "question": "12 - 8 = ?", + "options": [1, 2, 3, 4], + "answer": 4, + }, + }, + }, + } + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py new file mode 100644 index 0000000..65fdf56 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py @@ -0,0 +1,37 @@ +from typing import Optional, TYPE_CHECKING + +from .jupyter import JupyterMixin +from .measure import Measurement + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + + +class Constrain(JupyterMixin): + """Constrain the width of a renderable to a given number of characters. + + Args: + renderable (RenderableType): A renderable object. + width (int, optional): The maximum width (in characters) to render. Defaults to 80. + """ + + def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None: + self.renderable = renderable + self.width = width + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.width is None: + yield self.renderable + else: + child_options = options.update_width(min(self.width, options.max_width)) + yield from console.render(self.renderable, child_options) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.width is not None: + options = options.update_width(self.width) + measurement = Measurement.get(console, options, self.renderable) + return measurement diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py new file mode 100644 index 0000000..e29cf36 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py @@ -0,0 +1,167 @@ +from itertools import zip_longest +from typing import ( + Iterator, + Iterable, + List, + Optional, + Union, + overload, + TypeVar, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderResult, + RenderableType, + ) + from .text import Text + +from .cells import cell_len +from .measure import Measurement + +T = TypeVar("T") + + +class Renderables: + """A list subclass which renders its contents to the console.""" + + def __init__( + self, renderables: Optional[Iterable["RenderableType"]] = None + ) -> None: + self._renderables: List["RenderableType"] = ( + list(renderables) if renderables is not None else [] + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._renderables + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + dimensions = [ + Measurement.get(console, options, renderable) + for renderable in self._renderables + ] + if not dimensions: + return Measurement(1, 1) + _min = max(dimension.minimum for dimension in dimensions) + _max = max(dimension.maximum for dimension in dimensions) + return Measurement(_min, _max) + + def append(self, renderable: "RenderableType") -> None: + self._renderables.append(renderable) + + def __iter__(self) -> Iterable["RenderableType"]: + return iter(self._renderables) + + +class Lines: + """A list subclass which can render to the console.""" + + def __init__(self, lines: Iterable["Text"] = ()) -> None: + self._lines: List["Text"] = list(lines) + + def __repr__(self) -> str: + return f"Lines({self._lines!r})" + + def __iter__(self) -> Iterator["Text"]: + return iter(self._lines) + + @overload + def __getitem__(self, index: int) -> "Text": + ... + + @overload + def __getitem__(self, index: slice) -> List["Text"]: + ... + + def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]: + return self._lines[index] + + def __setitem__(self, index: int, value: "Text") -> "Lines": + self._lines[index] = value + return self + + def __len__(self) -> int: + return self._lines.__len__() + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._lines + + def append(self, line: "Text") -> None: + self._lines.append(line) + + def extend(self, lines: Iterable["Text"]) -> None: + self._lines.extend(lines) + + def pop(self, index: int = -1) -> "Text": + return self._lines.pop(index) + + def justify( + self, + console: "Console", + width: int, + justify: "JustifyMethod" = "left", + overflow: "OverflowMethod" = "fold", + ) -> None: + """Justify and overflow text to a given width. + + Args: + console (Console): Console instance. + width (int): Number of characters per line. + justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left". + overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold". + + """ + from .text import Text + + if justify == "left": + for line in self._lines: + line.truncate(width, overflow=overflow, pad=True) + elif justify == "center": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left((width - cell_len(line.plain)) // 2) + line.pad_right(width - cell_len(line.plain)) + elif justify == "right": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left(width - cell_len(line.plain)) + elif justify == "full": + for line_index, line in enumerate(self._lines): + if line_index == len(self._lines) - 1: + break + words = line.split(" ") + words_size = sum(cell_len(word.plain) for word in words) + num_spaces = len(words) - 1 + spaces = [1 for _ in range(num_spaces)] + index = 0 + if spaces: + while words_size + num_spaces < width: + spaces[len(spaces) - index - 1] += 1 + num_spaces += 1 + index = (index + 1) % len(spaces) + tokens: List[Text] = [] + for index, (word, next_word) in enumerate( + zip_longest(words, words[1:]) + ): + tokens.append(word) + if index < len(spaces): + style = word.get_style_at_offset(console, -1) + next_style = next_word.get_style_at_offset(console, 0) + space_style = style if style == next_style else line.style + tokens.append(Text(" " * spaces[index], style=space_style)) + self[line_index] = Text("").join(tokens) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py new file mode 100644 index 0000000..88fcb92 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py @@ -0,0 +1,225 @@ +import sys +import time +from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union + +if sys.version_info >= (3, 8): + from typing import Final +else: + from pip._vendor.typing_extensions import Final # pragma: no cover + +from .segment import ControlCode, ControlType, Segment + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + +STRIP_CONTROL_CODES: Final = [ + 7, # Bell + 8, # Backspace + 11, # Vertical tab + 12, # Form feed + 13, # Carriage return +] +_CONTROL_STRIP_TRANSLATE: Final = { + _codepoint: None for _codepoint in STRIP_CONTROL_CODES +} + +CONTROL_ESCAPE: Final = { + 7: "\\a", + 8: "\\b", + 11: "\\v", + 12: "\\f", + 13: "\\r", +} + +CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = { + ControlType.BELL: lambda: "\x07", + ControlType.CARRIAGE_RETURN: lambda: "\r", + ControlType.HOME: lambda: "\x1b[H", + ControlType.CLEAR: lambda: "\x1b[2J", + ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h", + ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l", + ControlType.SHOW_CURSOR: lambda: "\x1b[?25h", + ControlType.HIDE_CURSOR: lambda: "\x1b[?25l", + ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A", + ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B", + ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C", + ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D", + ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G", + ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K", + ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H", + ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07", +} + + +class Control: + """A renderable that inserts a control code (non printable but may move cursor). + + Args: + *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a + tuple of ControlType and an integer parameter + """ + + __slots__ = ["segment"] + + def __init__(self, *codes: Union[ControlType, ControlCode]) -> None: + control_codes: List[ControlCode] = [ + (code,) if isinstance(code, ControlType) else code for code in codes + ] + _format_map = CONTROL_CODES_FORMAT + rendered_codes = "".join( + _format_map[code](*parameters) for code, *parameters in control_codes + ) + self.segment = Segment(rendered_codes, None, control_codes) + + @classmethod + def bell(cls) -> "Control": + """Ring the 'bell'.""" + return cls(ControlType.BELL) + + @classmethod + def home(cls) -> "Control": + """Move cursor to 'home' position.""" + return cls(ControlType.HOME) + + @classmethod + def move(cls, x: int = 0, y: int = 0) -> "Control": + """Move cursor relative to current position. + + Args: + x (int): X offset. + y (int): Y offset. + + Returns: + ~Control: Control object. + + """ + + def get_codes() -> Iterable[ControlCode]: + control = ControlType + if x: + yield ( + control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD, + abs(x), + ) + if y: + yield ( + control.CURSOR_DOWN if y > 0 else control.CURSOR_UP, + abs(y), + ) + + control = cls(*get_codes()) + return control + + @classmethod + def move_to_column(cls, x: int, y: int = 0) -> "Control": + """Move to the given column, optionally add offset to row. + + Returns: + x (int): absolute x (column) + y (int): optional y offset (row) + + Returns: + ~Control: Control object. + """ + + return ( + cls( + (ControlType.CURSOR_MOVE_TO_COLUMN, x), + ( + ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP, + abs(y), + ), + ) + if y + else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x)) + ) + + @classmethod + def move_to(cls, x: int, y: int) -> "Control": + """Move cursor to absolute position. + + Args: + x (int): x offset (column) + y (int): y offset (row) + + Returns: + ~Control: Control object. + """ + return cls((ControlType.CURSOR_MOVE_TO, x, y)) + + @classmethod + def clear(cls) -> "Control": + """Clear the screen.""" + return cls(ControlType.CLEAR) + + @classmethod + def show_cursor(cls, show: bool) -> "Control": + """Show or hide the cursor.""" + return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR) + + @classmethod + def alt_screen(cls, enable: bool) -> "Control": + """Enable or disable alt screen.""" + if enable: + return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME) + else: + return cls(ControlType.DISABLE_ALT_SCREEN) + + @classmethod + def title(cls, title: str) -> "Control": + """Set the terminal window title + + Args: + title (str): The new terminal window title + """ + return cls((ControlType.SET_WINDOW_TITLE, title)) + + def __str__(self) -> str: + return self.segment.text + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.segment.text: + yield self.segment + + +def strip_control_codes( + text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE +) -> str: + """Remove control codes from text. + + Args: + text (str): A string possibly contain control codes. + + Returns: + str: String with control codes removed. + """ + return text.translate(_translate_table) + + +def escape_control_codes( + text: str, + _translate_table: Dict[int, str] = CONTROL_ESCAPE, +) -> str: + """Replace control codes with their "escaped" equivalent in the given text. + (e.g. "\b" becomes "\\b") + + Args: + text (str): A string possibly containing control codes. + + Returns: + str: String with control codes replaced with their escaped version. + """ + return text.translate(_translate_table) + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + console = Console() + console.print("Look at the title of your terminal window ^") + # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!"))) + for i in range(10): + console.set_window_title("🚀 Loading" + "." * i) + time.sleep(0.5) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py new file mode 100644 index 0000000..dca3719 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py @@ -0,0 +1,190 @@ +from typing import Dict + +from .style import Style + +DEFAULT_STYLES: Dict[str, Style] = { + "none": Style.null(), + "reset": Style( + color="default", + bgcolor="default", + dim=False, + bold=False, + italic=False, + underline=False, + blink=False, + blink2=False, + reverse=False, + conceal=False, + strike=False, + ), + "dim": Style(dim=True), + "bright": Style(dim=False), + "bold": Style(bold=True), + "strong": Style(bold=True), + "code": Style(reverse=True, bold=True), + "italic": Style(italic=True), + "emphasize": Style(italic=True), + "underline": Style(underline=True), + "blink": Style(blink=True), + "blink2": Style(blink2=True), + "reverse": Style(reverse=True), + "strike": Style(strike=True), + "black": Style(color="black"), + "red": Style(color="red"), + "green": Style(color="green"), + "yellow": Style(color="yellow"), + "magenta": Style(color="magenta"), + "cyan": Style(color="cyan"), + "white": Style(color="white"), + "inspect.attr": Style(color="yellow", italic=True), + "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True), + "inspect.callable": Style(bold=True, color="red"), + "inspect.async_def": Style(italic=True, color="bright_cyan"), + "inspect.def": Style(italic=True, color="bright_cyan"), + "inspect.class": Style(italic=True, color="bright_cyan"), + "inspect.error": Style(bold=True, color="red"), + "inspect.equals": Style(), + "inspect.help": Style(color="cyan"), + "inspect.doc": Style(dim=True), + "inspect.value.border": Style(color="green"), + "live.ellipsis": Style(bold=True, color="red"), + "layout.tree.row": Style(dim=False, color="red"), + "layout.tree.column": Style(dim=False, color="blue"), + "logging.keyword": Style(bold=True, color="yellow"), + "logging.level.notset": Style(dim=True), + "logging.level.debug": Style(color="green"), + "logging.level.info": Style(color="blue"), + "logging.level.warning": Style(color="red"), + "logging.level.error": Style(color="red", bold=True), + "logging.level.critical": Style(color="red", bold=True, reverse=True), + "log.level": Style.null(), + "log.time": Style(color="cyan", dim=True), + "log.message": Style.null(), + "log.path": Style(dim=True), + "repr.ellipsis": Style(color="yellow"), + "repr.indent": Style(color="green", dim=True), + "repr.error": Style(color="red", bold=True), + "repr.str": Style(color="green", italic=False, bold=False), + "repr.brace": Style(bold=True), + "repr.comma": Style(bold=True), + "repr.ipv4": Style(bold=True, color="bright_green"), + "repr.ipv6": Style(bold=True, color="bright_green"), + "repr.eui48": Style(bold=True, color="bright_green"), + "repr.eui64": Style(bold=True, color="bright_green"), + "repr.tag_start": Style(bold=True), + "repr.tag_name": Style(color="bright_magenta", bold=True), + "repr.tag_contents": Style(color="default"), + "repr.tag_end": Style(bold=True), + "repr.attrib_name": Style(color="yellow", italic=False), + "repr.attrib_equal": Style(bold=True), + "repr.attrib_value": Style(color="magenta", italic=False), + "repr.number": Style(color="cyan", bold=True, italic=False), + "repr.number_complex": Style(color="cyan", bold=True, italic=False), # same + "repr.bool_true": Style(color="bright_green", italic=True), + "repr.bool_false": Style(color="bright_red", italic=True), + "repr.none": Style(color="magenta", italic=True), + "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False), + "repr.uuid": Style(color="bright_yellow", bold=False), + "repr.call": Style(color="magenta", bold=True), + "repr.path": Style(color="magenta"), + "repr.filename": Style(color="bright_magenta"), + "rule.line": Style(color="bright_green"), + "rule.text": Style.null(), + "json.brace": Style(bold=True), + "json.bool_true": Style(color="bright_green", italic=True), + "json.bool_false": Style(color="bright_red", italic=True), + "json.null": Style(color="magenta", italic=True), + "json.number": Style(color="cyan", bold=True, italic=False), + "json.str": Style(color="green", italic=False, bold=False), + "json.key": Style(color="blue", bold=True), + "prompt": Style.null(), + "prompt.choices": Style(color="magenta", bold=True), + "prompt.default": Style(color="cyan", bold=True), + "prompt.invalid": Style(color="red"), + "prompt.invalid.choice": Style(color="red"), + "pretty": Style.null(), + "scope.border": Style(color="blue"), + "scope.key": Style(color="yellow", italic=True), + "scope.key.special": Style(color="yellow", italic=True, dim=True), + "scope.equals": Style(color="red"), + "table.header": Style(bold=True), + "table.footer": Style(bold=True), + "table.cell": Style.null(), + "table.title": Style(italic=True), + "table.caption": Style(italic=True, dim=True), + "traceback.error": Style(color="red", italic=True), + "traceback.border.syntax_error": Style(color="bright_red"), + "traceback.border": Style(color="red"), + "traceback.text": Style.null(), + "traceback.title": Style(color="red", bold=True), + "traceback.exc_type": Style(color="bright_red", bold=True), + "traceback.exc_value": Style.null(), + "traceback.offset": Style(color="bright_red", bold=True), + "bar.back": Style(color="grey23"), + "bar.complete": Style(color="rgb(249,38,114)"), + "bar.finished": Style(color="rgb(114,156,31)"), + "bar.pulse": Style(color="rgb(249,38,114)"), + "progress.description": Style.null(), + "progress.filesize": Style(color="green"), + "progress.filesize.total": Style(color="green"), + "progress.download": Style(color="green"), + "progress.elapsed": Style(color="yellow"), + "progress.percentage": Style(color="magenta"), + "progress.remaining": Style(color="cyan"), + "progress.data.speed": Style(color="red"), + "progress.spinner": Style(color="green"), + "status.spinner": Style(color="green"), + "tree": Style(), + "tree.line": Style(), + "markdown.paragraph": Style(), + "markdown.text": Style(), + "markdown.em": Style(italic=True), + "markdown.emph": Style(italic=True), # For commonmark backwards compatibility + "markdown.strong": Style(bold=True), + "markdown.code": Style(bold=True, color="cyan", bgcolor="black"), + "markdown.code_block": Style(color="cyan", bgcolor="black"), + "markdown.block_quote": Style(color="magenta"), + "markdown.list": Style(color="cyan"), + "markdown.item": Style(), + "markdown.item.bullet": Style(color="yellow", bold=True), + "markdown.item.number": Style(color="yellow", bold=True), + "markdown.hr": Style(color="yellow"), + "markdown.h1.border": Style(), + "markdown.h1": Style(bold=True), + "markdown.h2": Style(bold=True, underline=True), + "markdown.h3": Style(bold=True), + "markdown.h4": Style(bold=True, dim=True), + "markdown.h5": Style(underline=True), + "markdown.h6": Style(italic=True), + "markdown.h7": Style(italic=True, dim=True), + "markdown.link": Style(color="bright_blue"), + "markdown.link_url": Style(color="blue", underline=True), + "markdown.s": Style(strike=True), + "iso8601.date": Style(color="blue"), + "iso8601.time": Style(color="magenta"), + "iso8601.timezone": Style(color="yellow"), +} + + +if __name__ == "__main__": # pragma: no cover + import argparse + import io + + from pip._vendor.rich.console import Console + from pip._vendor.rich.table import Table + from pip._vendor.rich.text import Text + + parser = argparse.ArgumentParser() + parser.add_argument("--html", action="store_true", help="Export as HTML table") + args = parser.parse_args() + html: bool = args.html + console = Console(record=True, width=70, file=io.StringIO()) if html else Console() + + table = Table("Name", "Styling") + + for style_name, style in DEFAULT_STYLES.items(): + table.add_row(Text(style_name, style=style), str(style)) + + console.print(table) + if html: + print(console.export_html(inline_styles=True)) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py new file mode 100644 index 0000000..ad36183 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py @@ -0,0 +1,37 @@ +import os +import platform + +from pip._vendor.rich import inspect +from pip._vendor.rich.console import Console, get_windows_console_features +from pip._vendor.rich.panel import Panel +from pip._vendor.rich.pretty import Pretty + + +def report() -> None: # pragma: no cover + """Print a report to the terminal with debugging information""" + console = Console() + inspect(console) + features = get_windows_console_features() + inspect(features) + + env_names = ( + "TERM", + "COLORTERM", + "CLICOLOR", + "NO_COLOR", + "TERM_PROGRAM", + "COLUMNS", + "LINES", + "JUPYTER_COLUMNS", + "JUPYTER_LINES", + "JPY_PARENT_PID", + "VSCODE_VERBOSE_LOGGING", + ) + env = {name: os.getenv(name) for name in env_names} + console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) + + console.print(f'platform="{platform.system()}"') + + +if __name__ == "__main__": # pragma: no cover + report() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py new file mode 100644 index 0000000..791f046 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py @@ -0,0 +1,96 @@ +import sys +from typing import TYPE_CHECKING, Optional, Union + +from .jupyter import JupyterMixin +from .segment import Segment +from .style import Style +from ._emoji_codes import EMOJI +from ._emoji_replace import _emoji_replace + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + + +EmojiVariant = Literal["emoji", "text"] + + +class NoEmoji(Exception): + """No emoji by that name.""" + + +class Emoji(JupyterMixin): + __slots__ = ["name", "style", "_char", "variant"] + + VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"} + + def __init__( + self, + name: str, + style: Union[str, Style] = "none", + variant: Optional[EmojiVariant] = None, + ) -> None: + """A single emoji character. + + Args: + name (str): Name of emoji. + style (Union[str, Style], optional): Optional style. Defaults to None. + + Raises: + NoEmoji: If the emoji doesn't exist. + """ + self.name = name + self.style = style + self.variant = variant + try: + self._char = EMOJI[name] + except KeyError: + raise NoEmoji(f"No emoji called {name!r}") + if variant is not None: + self._char += self.VARIANTS.get(variant, "") + + @classmethod + def replace(cls, text: str) -> str: + """Replace emoji markup with corresponding unicode characters. + + Args: + text (str): A string with emojis codes, e.g. "Hello :smiley:!" + + Returns: + str: A string with emoji codes replaces with actual emoji. + """ + return _emoji_replace(text) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._char + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield Segment(self._char, console.get_style(self.style)) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.console import Console + + console = Console(record=True) + + columns = Columns( + (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name), + column_first=True, + ) + + console.print(columns) + if len(sys.argv) > 1: + console.save_html(sys.argv[1]) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py new file mode 100644 index 0000000..0bcbe53 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py @@ -0,0 +1,34 @@ +class ConsoleError(Exception): + """An error in console operation.""" + + +class StyleError(Exception): + """An error in styles.""" + + +class StyleSyntaxError(ConsoleError): + """Style was badly formatted.""" + + +class MissingStyle(StyleError): + """No such style.""" + + +class StyleStackError(ConsoleError): + """Style stack is invalid.""" + + +class NotRenderableError(ConsoleError): + """Object is not renderable.""" + + +class MarkupError(ConsoleError): + """Markup was badly formatted.""" + + +class LiveError(ConsoleError): + """Error related to Live display.""" + + +class NoAltScreen(ConsoleError): + """Alt screen mode was required.""" diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py new file mode 100644 index 0000000..4b0b0da --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py @@ -0,0 +1,57 @@ +import io +from typing import IO, TYPE_CHECKING, Any, List + +from .ansi import AnsiDecoder +from .text import Text + +if TYPE_CHECKING: + from .console import Console + + +class FileProxy(io.TextIOBase): + """Wraps a file (e.g. sys.stdout) and redirects writes to a console.""" + + def __init__(self, console: "Console", file: IO[str]) -> None: + self.__console = console + self.__file = file + self.__buffer: List[str] = [] + self.__ansi_decoder = AnsiDecoder() + + @property + def rich_proxied_file(self) -> IO[str]: + """Get proxied file.""" + return self.__file + + def __getattr__(self, name: str) -> Any: + return getattr(self.__file, name) + + def write(self, text: str) -> int: + if not isinstance(text, str): + raise TypeError(f"write() argument must be str, not {type(text).__name__}") + buffer = self.__buffer + lines: List[str] = [] + while text: + line, new_line, text = text.partition("\n") + if new_line: + lines.append("".join(buffer) + line) + buffer.clear() + else: + buffer.append(line) + break + if lines: + console = self.__console + with console: + output = Text("\n").join( + self.__ansi_decoder.decode_line(line) for line in lines + ) + console.print(output) + return len(text) + + def flush(self) -> None: + output = "".join(self.__buffer) + if output: + self.__console.print(output) + del self.__buffer[:] + + def fileno(self) -> int: + return self.__file.fileno() diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py new file mode 100644 index 0000000..99f118e --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py @@ -0,0 +1,89 @@ +# coding: utf-8 +"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2 + +The functions declared in this module should cover the different +use cases needed to generate a string representation of a file size +using several different units. Since there are many standards regarding +file size units, three different functions have been implemented. + +See Also: + * `Wikipedia: Binary prefix `_ + +""" + +__all__ = ["decimal"] + +from typing import Iterable, List, Optional, Tuple + + +def _to_str( + size: int, + suffixes: Iterable[str], + base: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + if size == 1: + return "1 byte" + elif size < base: + return "{:,} bytes".format(size) + + for i, suffix in enumerate(suffixes, 2): # noqa: B007 + unit = base**i + if size < unit: + break + return "{:,.{precision}f}{separator}{}".format( + (base * size / unit), + suffix, + precision=precision, + separator=separator, + ) + + +def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]: + """Pick a suffix and base for the given size.""" + for i, suffix in enumerate(suffixes): + unit = base**i + if size < unit * base: + break + return unit, suffix + + +def decimal( + size: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + """Convert a filesize in to a string (powers of 1000, SI prefixes). + + In this convention, ``1000 B = 1 kB``. + + This is typically the format used to advertise the storage + capacity of USB flash drives and the like (*256 MB* meaning + actually a storage capacity of more than *256 000 000 B*), + or used by **Mac OS X** since v10.6 to report file sizes. + + Arguments: + int (size): A file size. + int (precision): The number of decimal places to include (default = 1). + str (separator): The string to separate the value from the units (default = " "). + + Returns: + `str`: A string containing a abbreviated file size and units. + + Example: + >>> filesize.decimal(30000) + '30.0 kB' + >>> filesize.decimal(30000, precision=2, separator="") + '30.00kB' + + """ + return _to_str( + size, + ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"), + 1000, + precision=precision, + separator=separator, + ) diff --git a/venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py b/venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py new file mode 100644 index 0000000..c264679 --- /dev/null +++ b/venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py @@ -0,0 +1,232 @@ +import re +from abc import ABC, abstractmethod +from typing import List, Union + +from .text import Span, Text + + +def _combine_regex(*regexes: str) -> str: + """Combine a number of regexes in to a single regex. + + Returns: + str: New regex with all regexes ORed together. + """ + return "|".join(regexes) + + +class Highlighter(ABC): + """Abstract base class for highlighters.""" + + def __call__(self, text: Union[str, Text]) -> Text: + """Highlight a str or Text instance. + + Args: + text (Union[str, ~Text]): Text to highlight. + + Raises: + TypeError: If not called with text or str. + + Returns: + Text: A test instance with highlighting applied. + """ + if isinstance(text, str): + highlight_text = Text(text) + elif isinstance(text, Text): + highlight_text = text.copy() + else: + raise TypeError(f"str or Text instance required, not {text!r}") + self.highlight(highlight_text) + return highlight_text + + @abstractmethod + def highlight(self, text: Text) -> None: + """Apply highlighting in place to text. + + Args: + text (~Text): A text object highlight. + """ + + +class NullHighlighter(Highlighter): + """A highlighter object that doesn't highlight. + + May be used to disable highlighting entirely. + + """ + + def highlight(self, text: Text) -> None: + """Nothing to do""" + + +class RegexHighlighter(Highlighter): + """Applies highlighting from a list of regular expressions.""" + + highlights: List[str] = [] + base_style: str = "" + + def highlight(self, text: Text) -> None: + """Highlight :class:`rich.text.Text` using regular expressions. + + Args: + text (~Text): Text to highlighted. + + """ + + highlight_regex = text.highlight_regex + for re_highlight in self.highlights: + highlight_regex(re_highlight, style_prefix=self.base_style) + + +class ReprHighlighter(RegexHighlighter): + """Highlights the text typically produced from ``__repr__`` methods.""" + + base_style = "repr." + highlights = [ + r"(?P<)(?P[-\w.:|]*)(?P[\w\W]*)(?P>)", + r'(?P[\w_]{1,50})=(?P"?[\w_]+"?)?', + r"(?P[][{}()])", + _combine_regex( + r"(?P[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})", + r"(?P([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})", + r"(?P[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})", + r"(?P[\w.]*?)\(", + r"\b(?PTrue)\b|\b(?PFalse)\b|\b(?PNone)\b", + r"(?P\.\.\.)", + r"(?P(?(?\B(/[-\w._+]+)*\/)(?P[-\w._+]*)?", + r"(?b?'''.*?(?(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)", + ), + ] + + +class JSONHighlighter(RegexHighlighter): + """Highlights JSON""" + + # Captures the start and end of JSON strings, handling escaped quotes + JSON_STR = r"(?b?\".*?(?[\{\[\(\)\]\}])", + r"\b(?Ptrue)\b|\b(?Pfalse)\b|\b(?Pnull)\b", + r"(?P(? None: + super().highlight(text) + + # Additional work to handle highlighting JSON keys + plain = text.plain + append = text.spans.append + whitespace = self.JSON_WHITESPACE + for match in re.finditer(self.JSON_STR, plain): + start, end = match.span() + cursor = end + while cursor < len(plain): + char = plain[cursor] + cursor += 1 + if char == ":": + append(Span(start, end, "json.key")) + elif char in whitespace: + continue + break + + +class ISO8601Highlighter(RegexHighlighter): + """Highlights the ISO8601 date time strings. + Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html + """ + + base_style = "iso8601." + highlights = [ + # + # Dates + # + # Calendar month (e.g. 2008-08). The hyphen is required + r"^(?P[0-9]{4})-(?P1[0-2]|0[1-9])$", + # Calendar date w/o hyphens (e.g. 20080830) + r"^(?P(?P[0-9]{4})(?P1[0-2]|0[1-9])(?P3[01]|0[1-9]|[12][0-9]))$", + # Ordinal date (e.g. 2008-243). The hyphen is optional + r"^(?P(?P[0-9]{4})-?(?P36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$", + # + # Weeks + # + # Week of the year (e.g., 2008-W35). The hyphen is optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9]))$", + # Week date (e.g., 2008-W35-6). The hyphens are optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9])-?(?P[1-7]))$", + # + # Times + # + # Hours and minutes (e.g., 17:21). The colon is optional + r"^(?P