Skip to content

xlsx: set_auto_filter cannot express a checkbox list of more than two values #478

Description

@t-ujiie-g

Summary

<filters> in ECMA-376 (§18.3.2.7, CT_Filters) holds any number of <filter val="…"/> children — that element is Excel's checkbox list, and a user who ticks four regions gets four of them.

Workbook::set_auto_filter can only produce one or two. Its criteria arrive as an expression string whose grammar is exactly 3 tokens, or 7 with a conjunction in the middle, so x == EMEA or x == APAC is the widest checkbox list expressible and a third value is rejected outright.

The writer and the reader both handle N values; only the front door caps it.

Repro

Same module setup as #477.

///|
test "three checkbox values are refused" {
  let wb = @xlsx.Workbook::new()
  wb.new_sheet("S") |> ignore
  wb.set_cell_value("S", "A1", @xlsx.CellValue::String("Region"))
  try
    wb.set_auto_filter("S", "A1:D50", [
      { column: "A", expression: "x == EMEA or x == APAC or x == AMER" },
    ]) catch {
    e => println("three values -> \{@debug.repr(e)}")
  } noraise {
    _ => println("three values -> accepted")
  }
}

Actual:

three values -> InvalidAutoFilter(msg="auto filter expression invalid")

Expected: a filterColumn whose filters holds all three.

Where

xlsx/auto_filter.mbt, parse_filter_expression:

if tokens.length() == 7 {
  ...
} else if tokens.length() == 3 {
  ...
} else {
  raise InvalidAutoFilter(msg="auto filter expression invalid")
}

Downstream of it, nothing else is limited:

  • auto_filter_column_from_expression already collapses a chain of == into filters: Some(values) — it just never sees more than two;
  • the writer (write_worksheet_layout_xml.mbt) loops over column.filters and emits one <filter> per value;
  • the reader (read.mbt, parse_auto_filter_columns) parses every <filter> child it finds.

So a file with a five-value checkbox list reads back correctly and cannot be written.

Suggestion

Two options, and the second is the one I would ask for:

  1. Widen the grammar to accept an or-chain of any length, folding it into filters when every operator is ==. Smallest change, keeps the API as it is.

  2. Expose the structured form. AutoFilterColumn / AutoFilterCustomFilters are already public types, but their fields cannot be constructed outside the package (pub struct, not pub(all)), so the expression string is the only way in. An overload taking a built AutoFilterColumn — or pub(all) on those three structs — would let a caller that already has a typed criterion skip the string round trip entirely.

Option 2 matters for callers that are compilers rather than humans: they hold ["EMEA", "APAC", "AMER"], format it into a string, and hand it to a parser that has to take it apart again — and a value containing a space or an or cannot survive that round trip at all (#477 is one instance of the same seam).

Context

Found while adding per-column auto-filter criteria to yxl, a YAML→xlsx compiler built on bobzhang/mbtexcel. A spec that says which values a column is filtered to is the shape users ask for, and the two-value ceiling is what stops it: filtering to three of five regions is unremarkable in a real workbook.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions