mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-04 01:28:59 +00:00
#2012: chart names and macro examples unquote sheet names like formulas do
Three places still split a `Sheet!Name` reference and stripped the sheet's quotes by hand: the chart resolver (is a series name a defined name?), the XLSX chart writer (the name's cache) and the macro editor's example references. The first two left a doubled quote in the name, so a series named `'Bob''s'!Title` (a name on the sheet "Bob's") wasn't found and showed that text instead of the name's value; they also split at the first `!`, so a quoted sheet holding one broke. All three now use core's split_sheet, the one reading formulas use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
178f3eb745
commit
e89ba799e9
4 files changed
+55
-11
No files matched your search
@@ -179,9 +179,10 @@ fn category_table(wb: &Workbook, sheet: usize, formula: &str, values: &str) -> (
|
||||
/// Whether `t` names a defined name seen from `sheet` (`Name`, or `Sheet1!Name` looked up on
|
||||
/// that sheet), the way the evaluator looks names up.
|
||||
fn is_defined_name(wb: &Workbook, sheet: usize, t: &str) -> bool {
|
||||
match t.split_once('!') {
|
||||
Some((s, bare)) => wb.sheet_index(s.trim_matches('\'')).is_some_and(|i| wb.name(bare, i).is_some()),
|
||||
None => wb.name(t, sheet).is_some(),
|
||||
// `'Bob''s'!Name`: the sheet is unquoted as everywhere else (#2012).
|
||||
match gridcraft_core::split_sheet(t) {
|
||||
(Some(s), bare) => wb.sheet_index(&s).is_some_and(|i| wb.name(bare, i).is_some()),
|
||||
(None, _) => wb.name(t, sheet).is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1258,3 +1258,39 @@ fn box_whisker_draws_each_series_quartile_method() {
|
||||
// The boxes span 3.5 and 2.5 data units on the same value axis.
|
||||
assert!((heights[0] / heights[1] - 1.4).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_series_named_by_a_name_on_a_sheet_with_an_apostrophe_resolves() {
|
||||
// #2012: `'Bob''s'!Title` kept the doubled quote, so the sheet wasn't found and the series
|
||||
// was named by the literal text.
|
||||
let mut wb = Workbook::new();
|
||||
{
|
||||
let s = wb.sheet_mut(0).unwrap();
|
||||
s.name = "Bob's".into();
|
||||
s.set_value(CellRef::new(0, 0), Value::from("Takings"));
|
||||
s.set_value(CellRef::new(1, 0), Value::Number(4.0));
|
||||
s.set_value(CellRef::new(2, 0), Value::Number(9.0));
|
||||
}
|
||||
wb.names.push(gridcraft_model::DefinedName {
|
||||
name: "Title".into(),
|
||||
scope: Some(0),
|
||||
formula: "'Bob''s'!$A$1".into(),
|
||||
comment: String::new(),
|
||||
hidden: false,
|
||||
});
|
||||
let mut c = chart(ChartKind::ColumnClustered);
|
||||
c.series = vec![Series {
|
||||
name: Some("'Bob''s'!Title".into()),
|
||||
categories: None,
|
||||
values: "'Bob''s'!$A$2:$A$3".into(),
|
||||
bubble_sizes: None,
|
||||
color: None,
|
||||
secondary: false,
|
||||
kind: None,
|
||||
quartiles: Default::default(),
|
||||
smooth: false,
|
||||
trendlines: Vec::new(),
|
||||
}];
|
||||
let d = resolve(&wb, 0, &c);
|
||||
assert_eq!(d.series.first().map(|s| s.name.as_str()), Some("Takings"));
|
||||
}
|
||||
@@ -459,10 +459,7 @@ pub fn parse_refs(text: &str) -> Vec<(Option<String>, RangeRef)> {
|
||||
text.split(',')
|
||||
.filter_map(|part| {
|
||||
let part = part.trim();
|
||||
let (sheet, body) = match part.rsplit_once('!') {
|
||||
Some((s, b)) => (Some(s.trim().trim_matches('\'').replace("''", "'")), b),
|
||||
None => (None, part),
|
||||
};
|
||||
let (sheet, body) = gridcraft_engine::core::split_sheet(part);
|
||||
RangeRef::parse(&body.replace('$', "")).map(|r| (sheet, r))
|
||||
})
|
||||
.take(500)
|
||||
@@ -2399,6 +2396,15 @@ pub fn paint_overlay(p: &egui::Painter, geo: &crate::grid::Geo, sh: &gridcraft_e
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn example_references_unquote_sheet_names_as_formulas_do() {
|
||||
// #2012: one shared reading of `'Bob''s'!A1` and `'a!b'!A1`.
|
||||
let refs = parse_refs("'Bob''s'!$A$1:$B$2, 'a!b'!C3, D4");
|
||||
let sheets: Vec<Option<String>> = refs.iter().map(|(s, _)| s.clone()).collect();
|
||||
assert_eq!(sheets, [Some("Bob's".to_string()), Some("a!b".to_string()), None]);
|
||||
assert_eq!(refs.iter().map(|(_, r)| r.a1()).collect::<Vec<_>>(), ["A1:B2", "C3", "D4"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_edited_input_keeps_a_shape_a_macro_may_declare() {
|
||||
let range = NamedInput { rows: [2, 10], cols: [3, 3], header: true, col_types: vec![ColType::Number; 3], ..NamedInput::default() };
|
||||
|
||||
@@ -366,12 +366,13 @@ fn is_ref(f: &str) -> bool {
|
||||
/// when it isn't a name, or names a sheet the workbook doesn't have.
|
||||
fn chart_name<'a>(wb: &'a Workbook, si: usize, f: &str) -> Option<(Option<usize>, String, Option<&'a DefinedName>)> {
|
||||
let Ok(Expr::Name(n)) = gridcraft_formula::parse(f.trim()) else { return None };
|
||||
match n.split_once('!') {
|
||||
Some((s, bare)) => {
|
||||
let i = wb.sheet_index(s.trim_matches('\''))?;
|
||||
// `'Bob''s'!Name`: the sheet is unquoted as everywhere else (#2012).
|
||||
match gridcraft_core::split_sheet(&n) {
|
||||
(Some(s), bare) => {
|
||||
let i = wb.sheet_index(&s)?;
|
||||
Some((Some(i), bare.to_string(), wb.name(bare, i)))
|
||||
}
|
||||
None => {
|
||||
(None, _) => {
|
||||
let def = wb.name(&n, si);
|
||||
Some((None, n, def))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user