diff --git a/crates/engine/src/clip_html.rs b/crates/engine/src/clip_html.rs index ca7667694..d208155df 100644 --- a/crates/engine/src/clip_html.rs +++ b/crates/engine/src/clip_html.rs @@ -11,6 +11,11 @@ const MAX_HTML: usize = 16 * 1024 * 1024; /// Widest `colspan` honoured (the sheet's column count). const MAX_SPAN: usize = gridcraft_core::MAX_COLS as usize; +/// Tallest `rowspan` honoured (the sheet's row count). +const MAX_ROW_SPAN: usize = gridcraft_core::MAX_ROWS as usize; +/// Most cells, span placeholders included, one paste reads from the HTML. Past it the rest of the +/// table is ignored: the HTML only refines cells of the tab-separated text, which stays the source. +const MAX_HTML_CELLS: usize = 1_000_000; /// One table cell: its visible text and the number the source recorded for it, if any. #[derive(Clone, Debug, Default, PartialEq)] @@ -19,25 +24,28 @@ pub(crate) struct HtmlCell { pub num: Option, } -/// The cells of the first table in `html`, row by row; a cell spanning columns is followed by -/// empty cells so positions line up with the tab-separated text. Empty when there is no table. -pub(crate) fn table_cells(html: &str) -> Vec> { - let mut rows: Vec> = Vec::new(); +/// The cells of the first table in `html`, row by row, read no further than `max_rows` rows of +/// `max_cols` cells (the pasted text's shape, itself clamped to the sheet) and [`MAX_HTML_CELLS`] +/// in all. A cell spanning columns is followed by empty cells, and a column covered by a `rowspan` +/// from above holds an empty cell, so positions line up with the tab-separated text. Empty when +/// there is no table. +pub(crate) fn table_cells(html: &str, max_rows: usize, max_cols: usize) -> Vec> { + let mut grid = Grid::new(max_rows, max_cols); if html.len() > MAX_HTML { - return rows; + return grid.rows; } - let mut cell: Option<(HtmlCell, usize)> = None; + let mut cell: Option = None; let mut in_table = false; let mut rest = html; - while !rest.is_empty() { + while !rest.is_empty() && !grid.full { let Some(lt) = rest.find('<') else { - if let Some((c, _)) = cell.as_mut() { - c.text.push_str(rest); + if let Some(c) = cell.as_mut() { + c.cell.text.push_str(rest); } break; }; - if let Some((c, _)) = cell.as_mut() { - c.text.push_str(rest.get(..lt).unwrap_or_default()); + if let Some(c) = cell.as_mut() { + c.cell.text.push_str(rest.get(..lt).unwrap_or_default()); } rest = rest.get(lt..).unwrap_or_default(); if let Some(after) = rest.strip_prefix("Rp10,000.00Name 2.50 & upx"#; - let rows = table_cells(excel); + let rows = table_cells(excel, 2, 3); assert_eq!(rows.len(), 2); assert_eq!(rows[0][0], HtmlCell { text: "Rp10,000.00".into(), num: Some(10000.0) }); assert_eq!(rows[0][1], HtmlCell { text: "Name".into(), num: None }); @@ -286,10 +376,10 @@ mod tests { let calc = r#"
Rp10,000.00
"#; - assert_eq!(table_cells(calc), vec![vec![HtmlCell { text: "Rp10,000.00".into(), num: Some(10000.0) }]]); + assert_eq!(table_cells(calc, 1, 1), vec![vec![HtmlCell { text: "Rp10,000.00".into(), num: Some(10000.0) }]]); let quoted_gt = r#"
1500
"#; - assert_eq!(table_cells(quoted_gt), vec![vec![HtmlCell { text: "1500".into(), num: Some(1500.0) }]]); + assert_eq!(table_cells(quoted_gt, 1, 1), vec![vec![HtmlCell { text: "1500".into(), num: Some(1500.0) }]]); } #[test] @@ -306,10 +396,44 @@ mod tests { "