fix(#1031): constrain Advanced Filter copy output

This commit is contained in:
derechopiano
2026-10-16 05:33:52 +01:00
parent f1894c6da6
commit 3379fb63a5
2 changed files with 49 additions and 11 deletions
+46 -9
View File
@@ -867,28 +867,30 @@ fn advanced_filter(s: &mut Session, p: &Json) -> Result<Json> {
if let Some(t) = str_param(p, "copyTo") {
let (dsi, dest) = parse_range(wb, t, CMD)?;
let dest = dest.start;
let extract = RangeRef::new(dest, dest.offset_clamped(list.height() as i64 - 1, list.width() as i64 - 1));
if dsi == si && extract.intersects(&list) {
return Err(EngineError::Other("The extract range can't overlap the list range.".into()));
}
let mut rows_out: Vec<u32> = vec![list.start.row];
rows_out.extend(keep.iter().filter(|(_, k)| *k).map(|(r, _)| *r));
let end =
dest.offset(rows_out.len() as i64 - 1, list.width() as i64 - 1).ok_or_else(|| bad(CMD, "the extract range exceeds the worksheet"))?;
let out_range = RangeRef::new(dest, end);
if dsi == si && out_range.intersects(&list) {
return Err(EngineError::Other("The extract range can't overlap the list range.".into()));
}
let cells: Vec<(CellRef, Cell)> = rows_out
.iter()
.enumerate()
.flat_map(|(i, row)| {
(list.start.col..=list.end.col).filter_map(move |c| {
(list.start.col..=list.end.col).map(move |c| {
let src = CellRef::new(*row, c);
let to = dest.offset(i as i64, (c - list.start.col) as i64)?;
// The full output footprint was checked above.
let to = CellRef::new(dest.row + i as u32, dest.col + c - list.start.col);
let style = sh.style_id(src);
Some((to, Cell { value: sh.value(src), formula: None, style }))
(to, Cell { value: sh.value(src), formula: None, style })
})
})
.collect();
let out_range = RangeRef::new(dest, dest.offset_clamped(rows_out.len() as i64 - 1, list.width() as i64 - 1));
edit(s, |cx| {
let shm = cx.sheet_mut(dsi)?;
let old: Vec<CellRef> = shm.cells.iter_range(extract).map(|(c, _)| c).collect();
let old: Vec<CellRef> = shm.cells.iter_range(out_range).map(|(c, _)| c).collect();
for c in &old {
shm.remove_cell(*c);
}
@@ -1317,6 +1319,41 @@ mod tests {
assert!(s.execute("data.advancedFilter", json!({"range": "A1:B6", "criteria": "H1:I3", "copyTo": "B2"})).is_err());
}
#[test]
fn advanced_filter_copy_preserves_cells_beyond_result() {
let mut s = s();
s.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Score"], ["A", 1], ["B", 2], ["C", 3]]})).unwrap();
s.execute("range.setValues", json!({"range": "H1", "values": [["Name"], ["B"]]})).unwrap();
s.execute("cell.set", json!({"cell": "K4", "input": "Keep me"})).unwrap();
let r = s.execute("data.advancedFilter", json!({"range": "A1:B4", "criteria": "H1:H2", "copyTo": "K1"})).unwrap();
assert_eq!(r["range"], "K1:L2");
assert_eq!(v(&s, "K2"), Value::from("B"));
assert_eq!(v(&s, "K4"), Value::from("Keep me"));
}
#[test]
fn advanced_filter_copy_rejects_overflow_without_mutation() {
let mut s = s();
s.execute("range.setValues", json!({"range": "A1", "values": [["Name", "Score"], ["A", 1]]})).unwrap();
s.execute("range.setValues", json!({"range": "H1", "values": [["Name"], ["A"]]})).unwrap();
for dest in ["XFD1", "K1048576"] {
s.execute("cell.set", json!({"cell": dest, "input": "Keep me"})).unwrap();
assert!(s.execute("data.advancedFilter", json!({"range": "A1:B2", "criteria": "H1:H2", "copyTo": dest})).is_err());
assert_eq!(v(&s, dest), Value::from("Keep me"));
}
}
#[test]
fn advanced_filter_copy_allows_output_above_source_when_only_old_clear_area_overlaps() {
let mut s = s();
s.execute("range.setValues", json!({"range": "C4", "values": [["Name", "Score"], ["A", 1], ["B", 2], ["C", 3]]})).unwrap();
s.execute("range.setValues", json!({"range": "H1", "values": [["Name"], ["A"]]})).unwrap();
let r = s.execute("data.advancedFilter", json!({"range": "C4:D7", "criteria": "H1:H2", "copyTo": "B1"})).unwrap();
assert_eq!(r["range"], "B1:C2");
assert_eq!(v(&s, "C4"), Value::from("Name"));
assert_eq!(v(&s, "C2"), Value::Number(1.0));
}
#[test]
fn criteria_semantics() {
use super::criterion_matches as m;
+3 -2
View File
@@ -1,6 +1,6 @@
# Target-app parity: GridCraft vs Microsoft Excel
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and row formatting) · **Target:** Microsoft Excel (Microsoft 365)
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-16 · **Change:** minor (Advanced Filter copy-to safety, #1031) · **Target:** Microsoft Excel (Microsoft 365)
This is the authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it,
[`gaps.md`](gaps.md) lists every shortfall, and the area checklists hold the detail:
@@ -83,7 +83,7 @@ performance 8, stability 8, localization 4, hardware 2, platforms 2, ecosystem 2
| Worksheet functions | 12 | 98.1% (measured) | ~82% | 16–26 | 513 / 523 names, 504 working. Missing: GETPIVOTDATA, IMAGE plus cloud functions. No conformance suite against Excel-computed oracles; edge-case bugs keep surfacing (#137, #138). [function-parity.md](function-parity.md) |
| Editing, navigation, clipboard, fill | 12 | ~90% | ~70% | 15–25 | Point mode, F4, AutoComplete, Paste Special, AutoFill, Flash Fill, Find/Replace, Go To Special. Bugs: rectangular drag-select (#28), monthly fill (#132), cut across sheets (#128), Insert Copied Cells missing (#21), key tips only on part of the Home tab (#43; #33, #49). [ui-parity.md](ui-parity.md) |
| Cell formatting, styles, conditional formatting | 10 | ~95% | ~80% | 8–14 | Full number-format language, borders, 47 cell styles, 60 table styles, all CF rule kinds including data bars, colour scales and icon sets. Gaps: font weights beyond regular/bold (#168), system fonts list (#4), themes |
| Sort, filter, tables, validation, outline | 8 | ~82% | ~70% | 10–16 | Multi-level sort, AutoFilter, Advanced Filter, tables with totals, validation, outline, subtotals, consolidate. Bugs: subtotal header (#131), validation edits (#129), table style panic caught (#130); no slicers on tables |
| Sort, filter, tables, validation, outline | 8 | ~82% | ~70% | 10–16 | Multi-level sort, AutoFilter, Advanced Filter, tables with totals, validation, outline, subtotals, consolidate. Advanced Filter copy-to preserves cells outside its result and rejects worksheet overflow (#1031). Bugs: subtotal header (#131), validation edits (#129), table style panic caught (#130); no slicers on tables |
| PivotTables | 7 | ~65% | ~45% | 25–40 | Insert/Recommended, field list, three layouts, values on rows or columns, date grouping, show-values-as, refresh, calculated fields, XLSX round-trip. Missing: slicers, timelines, calculated items, PivotCharts, value/label filters, number grouping, GETPIVOTDATA, data-model pivots. Round-trip never checked in Excel |
| Charts and sparklines | 8 | ~50% | ~30% | 40–60 | 25 chart kinds, all 2-D. The chart model holds title, legend position, a data-labels flag, gridlines flag and axis titles; no axis scaling, trendlines, error bars, per-point formatting, chart sheets, 3-D, surface, map, Pareto. Several kinds don't survive XLSX round trip (open PRs #158, #161). [chart-parity.md](chart-parity.md) |
| Page layout, print, PDF | 5 | ~80% | ~55% | 12–20 | Page setup, print titles, breaks, Page Break Preview, headers/footers, PDF export and printing. No true Page Layout view; the PDF writer uses only the 14 standard fonts without embedding (no CJK or other non-Latin text in PDF); web print only exports (#141, #143) |
@@ -332,6 +332,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land
| Date | Change | Summary |
|---|---|---|
| 2026-10-16 | minor | Advanced Filter copy-to clears only its actual output and rejects overflow without changing cells (#1031) |
| 2026-10-12 | minor | Essentials AutoFit evidence: rows auto-fit oversized fonts on open and row formatting (#833) |
| 2026-10-12 | minor | Sheet tabs: reveal the selected tab on navigation and resize while preserving manual scrolling (#681); no score change |
| 2026-10-12 | minor | PivotTables: values on rows (Σ Values in Rows at any position, all three layouts; `pivot.moveField` and the field pane move it; `dataOnRows` round-trips) (no score change) |