diff --git a/crates/engine/src/io.rs b/crates/engine/src/io.rs index f62a55e40..913fcdd5a 100644 --- a/crates/engine/src/io.rs +++ b/crates/engine/src/io.rs @@ -357,7 +357,15 @@ fn part_too_large_message(name: &str, part: &str, size: u64) -> String { fn read_html_table(name: &str, kind: Option, bytes: &[u8], csv_as_text: bool) -> Option)>> { // Decoded as a CSV is (BOM, UTF-8, else Windows-1252/1256), so a legacy page keeps its letters. let text = gridcraft_xlsx::decode_text(bytes); - let head: String = text.trim_start_matches('\u{feff}').trim_start().chars().take(16).collect::().to_ascii_lowercase(); + // Comments an exporter writes before the page (``) don't make it something else. + let mut rest = text.trim_start_matches('\u{feff}').trim_start(); + while let Some(after) = rest.strip_prefix("").and_then(|end| after.get(end + 3..)) { + Some(more) => rest = more.trim_start(), + None => break, + } + } + let head: String = rest.chars().take(16).collect::().to_ascii_lowercase(); if ![", bytes: &[u8], csv_as_text // cell keeps the text it shows. let field = match cell.num.filter(|_| !csv_as_text) { Some(n) => gridcraft_core::number_to_text(n), - None => cell.text.split_whitespace().collect::>().join(" "), + // Whitespace is already collapsed; non-breaking spaces stay, as in Excel. + None => cell.text.clone(), }; if field.contains(['\t', '\n', '\r', '"']) { format!("\"{}\"", field.replace('"', "\"\"")) } else { field } }) diff --git a/crates/engine/src/io/tests.rs b/crates/engine/src/io/tests.rs index fab60261f..ad3fae99c 100644 --- a/crates/engine/src/io/tests.rs +++ b/crates/engine/src/io/tests.rs @@ -656,3 +656,15 @@ fn a_web_page_table_keeps_its_characters_text_and_header_formats() { let (wb, _) = open_bytes_with("menu.xls", &page, None, true).unwrap(); assert_eq!(wb.sheet(0).unwrap().value(CellRef::parse("B2").unwrap()), gridcraft_core::Value::text("4,50")); } + +#[test] +fn a_web_page_table_opens_after_a_leading_comment_and_keeps_nonbreaking_spaces() { + // An exporter's comment before still makes a web page; ` ` stays a non-breaking + // space, as Excel keeps it, while ordinary whitespace collapses. + let page = "\n
New  Yorka \n b
"; + let (wb, warnings) = open_bytes("cities.xls", page.as_bytes()).unwrap(); + assert!(warnings.first().is_some_and(|w| w.contains("is a web page (HTML)")), "{warnings:?}"); + let sh = wb.sheet(0).unwrap(); + assert_eq!(sh.value(CellRef::parse("A1").unwrap()), gridcraft_core::Value::text("New\u{a0}\u{a0}York")); + assert_eq!(sh.value(CellRef::parse("B1").unwrap()), gridcraft_core::Value::text("a b")); +}