"""wolfxl <-> openpyxl compatibility spec - source of truth. Editing protocol: change ``ENTRIES`` and ``CATEGORIES`` here, then run ``python scripts/render_compat_matrix.py`` to regenerate ``docs/migration/compatibility-matrix.md``. The ``tests/test_openpyxl_compat_oracle.py`` harness imports ``ENTRIES`` directly to drive its parametrised probes; entries with a ``probe`` field run live against wolfxl + openpyxl in CI. Status values: * ``supported`` ``+`` implemented; covered by tests / fixtures * ``partial`` ``~`` works for the common case, documented caveats * ``not_yet`` ``-`` not implemented; tracked under a gap (gap_id) * ``out_of_scope`` ``x`` explicitly out of roadmap ``gap_id`` matches the G01-G28 rows in ``Plans/openpyxl-parity-program.md``. """ from __future__ import annotations from typing import TypedDict class Category(TypedDict): id: str title: str class _EntryRequired(TypedDict): id: str category: str openpyxl: str wolfxl: str status: str # supported | partial | not_yet | out_of_scope class Entry(_EntryRequired, total=False): gap_id: str # G01..G28 probe: str # probe id wired into the oracle harness secondary_probes: list[str] notes: str STATUS_DISPLAY: dict[str, str] = { "supported": "[+] Supported", "partial": "[~] Partial", "not_yet": "[-] Not Yet", "out_of_scope": "[x] Out of Scope", } CATEGORIES: list[Category] = [ {"id": "workbook", "title": "Workbook + Worksheet"}, {"id": "cell_styles", "title": "Cell + style API"}, {"id": "charts", "title": "Charts"}, {"id": "pivots", "title": "Pivot tables"}, {"id": "images", "title": "Images + drawings"}, {"id": "structural", "title": "Worksheet structural ops"}, {"id": "modify", "title": "Modify-mode mutations"}, {"id": "read", "title": "Read-side parity"}, {"id": "utils", "title": "Utility functions"}, {"id": "streaming", "title": "Streaming write (`write_only=True`)"}, {"id": "protection", "title": "Protection"}, {"id": "external_links", "title": "External links"}, {"id": "vba", "title": "VBA macros"}, {"id": "legacy_formats", "title": "Legacy formats (`.xlsb` / `.xls` / `.ods`)"}, {"id": "comments", "title": "Comments"}, {"id": "rich_text", "title": "Rich text"}, {"id": "cf", "title": "Conditional formatting"}, {"id": "defined_names", "title": "Defined names"}, {"id": "print_settings", "title": "Print settings + page setup"}, {"id": "array_formulas", "title": "Array + data-table formulas"}, {"id": "calc_chain", "title": "Calc chain"}, {"id": "slicers", "title": "Slicers"}, ] ENTRIES: list[Entry] = [ # --- Workbook + Worksheet ---------------------------------------------- { "id": "workbook.open.basic", "category": "workbook", "openpyxl": "Workbook()", "wolfxl": "wolfxl.Workbook()", "status": "supported", "probe": "workbook_open_basic", "notes": "Write-mode default.", }, { "id": "workbook.load.basic", "category": "workbook", "openpyxl": "load_workbook(path)", "wolfxl": "wolfxl.load_workbook(path)", "status": "supported", "probe": "workbook_load_basic", }, { "id": "workbook.load.data_only", "category": "workbook", "openpyxl": "load_workbook(path, data_only=True)", "wolfxl": "wolfxl.load_workbook(path, data_only=True)", "status": "supported", "probe": "workbook_load_data_only", }, { "id": "workbook.load.modify", "category": "workbook", "openpyxl": "Implicit (full DOM rewrite)", "wolfxl": "wolfxl.load_workbook(path, modify=True)", "status": "supported", "probe": "workbook_load_modify", "notes": "Surgical patcher; faster than DOM rewrite.", }, { "id": "workbook.load.read_only", "category": "workbook", "openpyxl": "load_workbook(path, read_only=True)", "wolfxl": "wolfxl.load_workbook(path, read_only=True)", "status": "supported", "probe": "workbook_load_read_only", "notes": "Streaming reads (auto-engages > 50k rows).", }, { "id": "workbook.save.basic", "category": "workbook", "openpyxl": "wb.save(path)", "wolfxl": "wb.save(path)", "status": "supported", "probe": "workbook_save_basic", "notes": "Eager workbooks can be edited and saved repeatedly; write_only=True remains consumed-on-save like openpyxl.", }, { "id": "workbook.sheet_access", "category": "workbook", "openpyxl": 'wb["Sheet"], wb.active, wb.sheetnames', "wolfxl": 'wb["Sheet"], wb.active, wb.sheetnames', "status": "supported", "probe": "workbook_sheet_access", }, { "id": "workbook.create_sheet", "category": "workbook", "openpyxl": "wb.create_sheet(title)", "wolfxl": "wb.create_sheet(title)", "status": "supported", "probe": "workbook_create_sheet", }, { "id": "workbook.create_sheet_modify", "category": "workbook", "openpyxl": "wb.create_sheet(title, index=...) on loaded workbook", "wolfxl": "wb.create_sheet(title, index=...) on modify=True workbook", "status": "supported", "probe": "workbook_create_sheet_modify", "notes": "Modify-mode blank sheet creation updates workbook.xml, workbook rels, content-types, tab order, and accepts cell edits before save.", }, { "id": "workbook.remove_sheet_modify", "category": "workbook", "openpyxl": "wb.remove(ws) on loaded workbook", "wolfxl": "wb.remove(ws) on modify=True workbook", "status": "supported", "probe": "workbook_remove_sheet_modify", "notes": "Modify-mode sheet removal prunes the workbook tab, workbook rel, worksheet part, local defined-name indexes, and reachable sheet subgraph parts.", }, { "id": "workbook.rename_sheet_modify", "category": "workbook", "openpyxl": "ws.title = 'Renamed' on loaded workbook", "wolfxl": "ws.title = 'Renamed' on modify=True workbook", "status": "supported", "probe": "workbook_rename_sheet_modify", "notes": "Modify-mode sheet title changes update workbook.xml and subsequent queued sheet mutations target the renamed tab.", }, { "id": "workbook.copy_worksheet", "category": "workbook", "openpyxl": "wb.copy_worksheet(ws)", "wolfxl": "wb.copy_worksheet(ws)", "status": "supported", "probe": "workbook_copy_worksheet", "notes": "Diverges from openpyxl in 5 documented ways (always more preservation).", }, { "id": "workbook.write_only", "category": "streaming", "openpyxl": "Workbook(write_only=True)", "wolfxl": "wolfxl.Workbook(write_only=True)", "status": "supported", "probe": "workbook_write_only_streaming", "secondary_probes": ["workbook_write_only_bounded_memory"], "notes": "Bounded-memory streaming append-only path (G20 / RFC-073). Per-sheet temp files splice into the `` slot; row data scales O(rows on disk), not O(rows in RAM). SST and styles still grow in-memory as in openpyxl's lxml.xmlfile model.", }, # --- Cell + style API -------------------------------------------------- { "id": "cell.basic_value", "category": "cell_styles", "openpyxl": 'ws["A1"].value, ws.cell(r, c).value', "wolfxl": 'ws["A1"].value, ws.cell(r, c).value', "status": "supported", "probe": "cell_basic_value", }, { "id": "cell.font_fill_border_alignment", "category": "cell_styles", "openpyxl": "cell.font / fill / border / alignment / number_format", "wolfxl": "cell.font / fill / border / alignment / number_format", "status": "supported", "probe": "cell_font_fill_border_alignment", "notes": "All five attributes (plus protection and border) round-trip together on one cell. Python flush layer merges format and border keys into a single dict so the native writer interns one combined xf record, instead of minting two ids that overwrite each other.", }, { "id": "cell.diagonal_borders", "category": "cell_styles", "openpyxl": "Border(diagonal=Side(...), diagonalUp=True)", "wolfxl": "Border(diagonal=Side(...), diagonalUp=True)", "status": "supported", "probe": "cell_diagonal_borders", "notes": "Round-trips through both write mode and modify mode; diagonalUp/diagonalDown attrs and the child of persist via the patcher's BorderSpec and the writer's intern path.", }, { "id": "cell.protection", "category": "cell_styles", "openpyxl": "cell.protection = Protection(...)", "wolfxl": "cell.protection = Protection(...)", "status": "supported", "probe": "cell_protection", "notes": "Round-trips through wolfxl reload; locked/hidden flags persist via child of with applyProtection=\"1\".", }, { "id": "cell.named_style", "category": "cell_styles", "openpyxl": "NamedStyle(name=...) + wb.add_named_style(...)", "wolfxl": "NamedStyle(name=...) + wb.add_named_style(...)", "status": "supported", "probe": "cell_named_style", "notes": "cell.style binds a registered NamedStyle and round-trips through cellStyleXfs/cellStyles + the xfId attr on ; reader resurfaces the name via cell.style.", }, { "id": "cell.gradient_fill", "category": "cell_styles", "openpyxl": "GradientFill(stop=(...))", "wolfxl": "GradientFill(stop=(...))", "status": "supported", "probe": "cell_gradient_fill", "notes": "GradientFill round-trips: writer emits with type/degree/path attrs and ordered stops; reader parses gradient back into Python GradientFill via the format dict's `gradient` sub-dict.", }, # --- Charts ------------------------------------------------------------ { "id": "charts.basic_2d", "category": "charts", "openpyxl": "BarChart / LineChart / PieChart / DoughnutChart", "wolfxl": "BarChart / LineChart / PieChart / DoughnutChart", "status": "supported", "probe": "charts_basic_2d", }, { "id": "charts.advanced_2d", "category": "charts", "openpyxl": "AreaChart / ScatterChart / BubbleChart / RadarChart", "wolfxl": "AreaChart / ScatterChart / BubbleChart / RadarChart", "status": "supported", "probe": "charts_advanced_2d", }, { "id": "charts.3d", "category": "charts", "openpyxl": "BarChart3D / LineChart3D / PieChart3D / AreaChart3D", "wolfxl": "BarChart3D / LineChart3D / PieChart3D / AreaChart3D", "status": "supported", "probe": "charts_3d", }, { "id": "charts.surface_stock_projected", "category": "charts", "openpyxl": "SurfaceChart / SurfaceChart3D / StockChart / ProjectedPieChart", "wolfxl": "SurfaceChart / SurfaceChart3D / StockChart / ProjectedPieChart", "status": "supported", "probe": "charts_surface_stock_projected", }, { "id": "charts.add_remove_replace", "category": "charts", "openpyxl": "ws.add_chart / remove_chart / replace_chart", "wolfxl": "ws.add_chart / remove_chart / replace_chart", "status": "supported", "probe": "charts_add_remove_replace", "notes": "Covers pending charts plus source-loaded worksheet chart removal, replacement, and title edits in modify mode.", }, { "id": "charts.chartsheet", "category": "charts", "openpyxl": "wb.create_chartsheet(...).add_chart(chart)", "wolfxl": "wb.create_chartsheet(...).add_chart(chart)", "status": "supported", "probe": "charts_chartsheet", "notes": "Chartsheet authoring emits chartsheet, drawing, chart, rels, and content-type parts that openpyxl reloads; existing chartsheet tabs load back as workbook.chartsheets rather than worksheets.", }, { "id": "charts.combination", "category": "charts", "openpyxl": "bar + line on shared category axis with secondary value axis", "wolfxl": "bar + line on shared category axis with secondary value axis", "status": "supported", "probe": "charts_combination", "notes": "Combination charts ship as a multi-family `` " "(RFC-069 §6) for real Excel/LibreOffice rendering, plus per-family " "standalone shadow chartspaces parked at row 1048576 so openpyxl's " "reader exposes each family as a distinct `ws._charts` entry " "without the shadows visually overlapping the real combo. " "Secondary value axis (right side) honored when " "`line.y_axis.crosses='max'` and `line.y_axis.axId` is set. Closes G15.", }, { "id": "charts.label_rich_text", "category": "charts", "openpyxl": "data label + axis label rich-text runs", "wolfxl": "data label + axis label rich-text runs", "status": "supported", "gap_id": "G10", "probe": "charts_label_rich_text", "notes": "Data label `` runs and axis-title rich text round-trip end-to-end (G10).", }, { "id": "charts.pivot_chart_per_point", "category": "charts", "openpyxl": "pivot chart with per-point overrides", "wolfxl": "pivot chart with per-point overrides", "status": "supported", "probe": "charts_pivot_chart_per_point", "notes": ( "Per-point `` overrides round-trip through openpyxl on " "pivot-source charts (G16). Bar/line/scatter all share the same " "dict bridge: `Series.dPt` -> `data_points` -> Rust `DataPoint` -> " "`` with ``. Pivot vs non-pivot only differ in the " "chart-level `` block." ), }, # --- Pivot tables ------------------------------------------------------ { "id": "pivots.construction", "category": "pivots", "openpyxl": "PivotCache + PivotTable construction", "wolfxl": "wolfxl.pivot.PivotCache + PivotTable", "status": "supported", "probe": "pivots_construction", "notes": "v2.0 ships pre-aggregated records emit (no refresh-on-open required).", }, { "id": "pivots.linked_chart", "category": "pivots", "openpyxl": "chart.pivot_source = pt", "wolfxl": "chart.pivot_source = pt", "status": "supported", "probe": "pivots_linked_chart", }, { "id": "pivots.in_place_edit", "category": "pivots", "openpyxl": "edit existing pivot's source range, field order, etc.", "wolfxl": "edit existing pivot source + layout fields", "status": "supported", "probe": "pivots_in_place_edit", "notes": ( "Source-range mutation is supported; layout edits that change " "cache semantics stamp refresh-on-open rather than regenerating " "pivot cache records." ), }, { "id": "pivots.field_mutation", "category": "pivots", "openpyxl": "edit existing pivot row/column fields", "wolfxl": "PivotTableHandle.row_fields / column_fields", "status": "supported", "probe": "pivots_field_mutation", "notes": "Mutates existing pivot table layout XML and marks the linked cache refresh-on-open.", }, { "id": "pivots.filter_mutation", "category": "pivots", "openpyxl": "edit existing pivot page field filter", "wolfxl": "PivotTableHandle.page_fields / set_filter", "status": "supported", "probe": "pivots_filter_mutation", }, { "id": "pivots.aggregation_mutation", "category": "pivots", "openpyxl": "edit existing pivot data-field aggregation", "wolfxl": "PivotTableHandle.data_fields / set_aggregation", "status": "supported", "probe": "pivots_aggregation_mutation", }, { "id": "pivots.copy_worksheet", "category": "pivots", "openpyxl": "copy_worksheet of pivot-bearing sheet (drops in openpyxl)", "wolfxl": "copy_worksheet of pivot-bearing sheet (deep-clone)", "status": "supported", "probe": "pivots_copy_worksheet", }, # --- Images + drawings ------------------------------------------------- { "id": "images.basic", "category": "images", "openpyxl": 'Image("logo.png") + ws.add_image(img, "B5")', "wolfxl": 'Image("logo.png") + ws.add_image(img, "B5")', "status": "supported", "probe": "images_basic", }, { "id": "images_replace_remove", "category": "images", "openpyxl": "ws.replace_image / remove_image", "wolfxl": "ws.replace_image / remove_image", "status": "supported", "probe": "images_replace_remove", }, # --- Structural ops ---------------------------------------------------- { "id": "structural.insert_delete_rows", "category": "structural", "openpyxl": "ws.insert_rows / delete_rows", "wolfxl": "ws.insert_rows / delete_rows", "status": "supported", "probe": "structural_insert_delete_rows", }, { "id": "structural.insert_delete_cols", "category": "structural", "openpyxl": "ws.insert_cols / delete_cols", "wolfxl": "ws.insert_cols / delete_cols", "status": "supported", "probe": "structural_insert_delete_cols", }, { "id": "structural.move_range", "category": "structural", "openpyxl": "ws.move_range(range, rows, cols)", "wolfxl": "ws.move_range(range, rows, cols)", "status": "supported", "probe": "structural_move_range", }, # --- Modify-mode mutations --------------------------------------------- { "id": "modify.document_properties", "category": "modify", "openpyxl": "wb.properties.title = ...", "wolfxl": "wb.properties.title = ...", "status": "supported", "probe": "modify_document_properties", }, { "id": "modify.defined_names", "category": "modify", "openpyxl": "wb.defined_names[name] = DefinedName(...)", "wolfxl": "wb.defined_names[name] = DefinedName(...)", "status": "supported", "probe": "modify_defined_names", }, { "id": "modify.tables", "category": "modify", "openpyxl": "ws.add_table(Table(...))", "wolfxl": "ws.add_table(Table(...))", "status": "supported", "probe": "modify_tables", }, { "id": "modify.data_validations", "category": "modify", "openpyxl": "ws.data_validations.append(...)", "wolfxl": "ws.data_validations.append(...)", "status": "supported", "probe": "modify_data_validations", }, # --- Read-side --------------------------------------------------------- { "id": "read.xlsx", "category": "read", "openpyxl": "load_workbook(path)", "wolfxl": "load_workbook(path)", "status": "supported", "probe": "read_xlsx", }, { "id": "read.xlsb", "category": "read", "openpyxl": "not supported in openpyxl", "wolfxl": "native BIFF12; values, cached formulas, read-side styles", "status": "supported", "probe": "read_xlsb", }, { "id": "read.xls", "category": "read", "openpyxl": "not supported in openpyxl", "wolfxl": "calamine; value-only, styles raise", "status": "out_of_scope", "notes": ( "Style accessors raise; values + formulas read. openpyxl does " "not support this surface; tracked as a wolfxl-extra roadmap " "item, not an openpyxl-parity gap." ), }, { "id": "read.ods", "category": "read", "openpyxl": "not supported in openpyxl", "wolfxl": "not supported (out of scope)", "status": "out_of_scope", "gap_id": "G27", }, # --- Utility functions ------------------------------------------------- { "id": "utils.get_column_letter", "category": "utils", "openpyxl": "openpyxl.utils.get_column_letter", "wolfxl": "wolfxl.utils.cell.get_column_letter", "status": "supported", "probe": "utils_get_column_letter", }, { "id": "utils.column_index_from_string", "category": "utils", "openpyxl": "openpyxl.utils.column_index_from_string", "wolfxl": "wolfxl.utils.cell.column_index_from_string", "status": "supported", "probe": "utils_column_index_from_string", }, { "id": "utils.range_boundaries", "category": "utils", "openpyxl": "openpyxl.utils.range_boundaries", "wolfxl": "wolfxl.utils.cell.range_boundaries", "status": "supported", "probe": "utils_range_boundaries", }, { "id": "utils.coordinate_to_tuple", "category": "utils", "openpyxl": "openpyxl.utils.coordinate_to_tuple", "wolfxl": "wolfxl.utils.cell.coordinate_to_tuple", "status": "supported", "probe": "utils_coordinate_to_tuple", }, # --- Protection ------------------------------------------------------- { "id": "protection.sheet", "category": "protection", "openpyxl": "ws.protection = SheetProtection(...)", "wolfxl": "ws.protection = SheetProtection(...)", "status": "supported", "probe": "protection_sheet", "notes": "Round-trips through openpyxl reload (sheet flag, formatCells override, password hash).", }, { "id": "protection.workbook", "category": "protection", "openpyxl": "wb.security = WorkbookProtection(...)", "wolfxl": "wb.security = WorkbookProtection(...)", "status": "supported", "probe": "protection_workbook", "notes": "camelCase aliases (lockStructure, workbookPassword, etc.) accepted alongside snake_case; round-trips through openpyxl reload.", }, # --- External links --------------------------------------------------- { "id": "external_links.workbook_collection", "category": "external_links", "openpyxl": "wb._external_links + xl/externalLinks/* parts", "wolfxl": "wb._external_links + xl/externalLinks/* parts", "status": "supported", "probe": "external_links_collection", "notes": ( "Inspection (target / sheet_names / cached_data), opaque " "modify-mode preservation when unchanged, and append/remove/edit " "authoring. keep_links=False strips preserved source links, and " "file-like/bytes-backed xlsx reads expose the same collection." ), }, { "id": "external_links.authoring", "category": "external_links", "openpyxl": "append/remove/edit external workbook links", "wolfxl": "wb._external_links append/remove/update_target", "status": "supported", "probe": "external_links_authoring", "notes": "Authoring emits externalLink parts, workbook rels/references, content-types, and per-link rels. Cached data is preserved when provided but linked workbooks are not dereferenced.", }, # --- VBA -------------------------------------------------------------- { "id": "vba.preserve", "category": "vba", "openpyxl": ".xlsm preserved on read+write", "wolfxl": ".xlsm preserved on modify-mode save", "status": "supported", "probe": "vba_preserve", }, { "id": "vba.inspect", "category": "vba", "openpyxl": "workbook.vba_archive (read-only inspection)", "wolfxl": "workbook.vba_archive (read-only inspection)", "status": "supported", "probe": "vba_inspect", "notes": ( "v1.0: modify-mode loads only; returns raw xl/vbaProject.bin " "bytes. No authoring API (tracked under G28)." ), }, { "id": "vba.author", "category": "vba", "openpyxl": "not supported in openpyxl", "wolfxl": "macro authoring from Python", "status": "out_of_scope", "gap_id": "G28", "notes": ( "Decision-gated (S11). openpyxl does not support this surface; " "tracked as a wolfxl-extra roadmap item, not an openpyxl-parity " "gap." ), }, # --- Legacy formats --------------------------------------------------- { "id": "legacy_formats.xlsb_write", "category": "legacy_formats", "openpyxl": "not supported", "wolfxl": "write `.xlsb`", "status": "out_of_scope", "gap_id": "G25", "notes": ( "Decision-gated (S9). openpyxl does not support this surface; " "tracked as a wolfxl-extra roadmap item, not an openpyxl-parity " "gap." ), }, { "id": "legacy_formats.xls_write", "category": "legacy_formats", "openpyxl": "not supported", "wolfxl": "write `.xls`", "status": "out_of_scope", "gap_id": "G26", "notes": ( "Decision-gated (S9). openpyxl does not support this surface; " "tracked as a wolfxl-extra roadmap item, not an openpyxl-parity " "gap." ), }, { "id": "legacy_formats.ods_read_write", "category": "legacy_formats", "openpyxl": "not supported", "wolfxl": "read+write `.ods`", "status": "out_of_scope", "gap_id": "G27", "notes": ( "Decision-gated (S10). openpyxl does not support this surface; " "tracked as a wolfxl-extra roadmap item, not an openpyxl-parity " "gap." ), }, # --- Comments --------------------------------------------------------- { "id": "comments.basic", "category": "comments", "openpyxl": "cell.comment = Comment(text, author)", "wolfxl": "cell.comment = Comment(text, author)", "status": "supported", "probe": "comments_basic", }, { "id": "comments.threaded", "category": "comments", "openpyxl": "threaded comment write + modify (xl/threadedComments)", "wolfxl": "threaded comment write + modify (xl/threadedComments)", "status": "supported", "probe": "comments_threaded", }, # --- Rich text -------------------------------------------------------- { "id": "rich_text.cell", "category": "rich_text", "openpyxl": "cell.value = CellRichText(...)", "wolfxl": "cell.value = CellRichText(...)", "status": "supported", "probe": "rich_text_cell", }, { "id": "rich_text.headers_footers", "category": "rich_text", "openpyxl": "ws.oddHeader / oddFooter rich-text runs", "wolfxl": "ws.oddHeader / oddFooter rich-text runs", "status": "supported", "probe": "rich_text_headers_footers", }, # --- Conditional formatting ------------------------------------------- { "id": "cf.basic_rules", "category": "cf", "openpyxl": "cellIs / containsText / expression / colorScale (basic)", "wolfxl": "cellIs / containsText / expression / colorScale (basic)", "status": "supported", "probe": "cf_basic_rules", "secondary_probes": ["cf_cellis_operator_matrix"], }, { "id": "cf.icon_sets", "category": "cf", "openpyxl": "IconSetRule (3 / 4 / 5 icons + percentile / number ladders)", "wolfxl": "IconSetRule (3 / 4 / 5 icons + percentile / number ladders)", "status": "supported", "probe": "cf_icon_sets", "secondary_probes": ["cf_iconset_extended_attrs"], }, { "id": "cf.data_bars", "category": "cf", "openpyxl": "DataBarRule (gradient + solid; min / max / percent / formula)", "wolfxl": "DataBarRule (gradient + solid; min / max / percent / formula)", "status": "supported", "probe": "cf_data_bars", "notes": "Round-trips through openpyxl reload; secondary probes cover cfvo edge cases and minLength/maxLength.", }, { "id": "cf.data_bars_advanced", "category": "cf", "openpyxl": "DataBarRule with percent / num / formula cfvo + showValue=False", "wolfxl": "DataBarRule with percent / num / formula cfvo + showValue=False", "status": "supported", "probe": "cf_data_bars_advanced", "secondary_probes": ["cf_databar_length_attrs"], }, { "id": "cf.color_scales_advanced", "category": "cf", "openpyxl": "ColorScaleRule with 3-stop, percentile / formula / number cfvo", "wolfxl": "ColorScaleRule with 3-stop, percentile / formula / number cfvo", "status": "supported", "probe": "cf_color_scales_advanced", }, { "id": "cf.stop_if_true_priority", "category": "cf", "openpyxl": "stopIfTrue + explicit priority + dxf integration", "wolfxl": "stopIfTrue + explicit priority + dxf integration", "status": "supported", "probe": "cf_stop_if_true_priority", }, # --- Defined names ---------------------------------------------------- { "id": "defined_names.basic", "category": "defined_names", "openpyxl": "wb.defined_names + DefinedName(name=, value=)", "wolfxl": "wb.defined_names + DefinedName(name=, value=)", "status": "supported", "probe": "defined_names_basic", }, { "id": "defined_names.edge_cases", "category": "defined_names", "openpyxl": "full ECMA-376 §18.2.5 attribute surface (13 attrs)", "wolfxl": "full ECMA-376 §18.2.5 attribute surface (13 attrs)", "status": "supported", "probe": "defined_names_edge_cases", }, # --- Print settings --------------------------------------------------- { "id": "print_settings.basic", "category": "print_settings", "openpyxl": "ws.print_options / page_setup / page_margins / print_title_rows", "wolfxl": "ws.print_options / page_setup / page_margins / print_title_rows", "status": "supported", "probe": "print_settings_basic", }, { "id": "print_settings.depth", "category": "print_settings", "openpyxl": "full PageSetup / PrintOptions surface (~30 attrs)", "wolfxl": "full PageSetup / PrintOptions surface (~30 attrs)", "status": "supported", "probe": "print_settings_depth", }, # --- Array + data-table formulas -------------------------------------- { "id": "array_formulas.array_formula", "category": "array_formulas", "openpyxl": "ArrayFormula(ref, text)", "wolfxl": "ArrayFormula(ref, text)", "status": "supported", "probe": "array_formula_basic", "notes": "Round-trips through openpyxl reload (ref + text preserved as openpyxl ArrayFormula).", }, { "id": "array_formulas.data_table", "category": "array_formulas", "openpyxl": "DataTableFormula(...)", "wolfxl": "DataTableFormula(...)", "status": "supported", "probe": "array_formula_data_table", }, { "id": "array_formulas.spill", "category": "array_formulas", "openpyxl": "Worksheet.array_formulae + DataTableFormula del flags", "wolfxl": "Worksheet.array_formulae + DataTableFormula del flags", "status": "supported", "probe": "array_formula_spill_metadata", "notes": "openpyxl 3.1.5 exposes array/spill ranges via Worksheet.array_formulae; formula_attributes is not part of the supported reference surface.", }, # --- Calc chain ------------------------------------------------------- { "id": "calc_chain.basic", "category": "calc_chain", "openpyxl": "calcChain rebuild on modify", "wolfxl": "calcChain rebuild on modify", "status": "supported", "probe": "calc_chain_basic", }, { "id": "calc_chain.edge_cases", "category": "calc_chain", "openpyxl": "cross-sheet calc-chain ordering, deleted-cell pruning", "wolfxl": "cross-sheet calc-chain ordering, deleted-cell pruning", "status": "supported", "probe": "calc_chain_edge_cases", "notes": "Rebuild scans post-mutation sheet XML across tab order, prunes stale refs, and preserves a source calcChain extLst when emitting a non-empty chain.", }, # --- Slicers ---------------------------------------------------------- { "id": "slicers.with_pivot", "category": "slicers", "openpyxl": "Slicer + SlicerCache wired to a PivotCache", "wolfxl": "Slicer + SlicerCache wired to a PivotCache", "status": "supported", "probe": "slicers_with_pivot", }, { "id": "slicers.standalone", "category": "slicers", "openpyxl": "Slicer outside pivot context (table-driven, etc.)", "wolfxl": "Slicer outside pivot context (table-driven, etc.)", "status": "out_of_scope", "gap_id": "G21", "notes": "openpyxl 3.1.5 exposes no public slicer modules/classes or table-driven slicer authoring API; tracked as a possible wolfxl-extra, not an openpyxl parity gap.", }, ] def entries_by_category() -> dict[str, list[Entry]]: """Return entries grouped by category id, preserving definition order.""" grouped: dict[str, list[Entry]] = {c["id"]: [] for c in CATEGORIES} for entry in ENTRIES: grouped.setdefault(entry["category"], []).append(entry) return grouped def status_totals() -> dict[str, int]: """Return per-status totals across all entries.""" totals: dict[str, int] = {} for entry in ENTRIES: totals[entry["status"]] = totals.get(entry["status"], 0) + 1 return totals def probes() -> list[Entry]: """Return entries that carry a `probe` field, in declaration order.""" return [e for e in ENTRIES if e.get("probe")]