mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-03 08:50:02 -06:00
This commit is contained in:
@@ -341,6 +341,25 @@ struct El {
|
||||
struct TableB {
|
||||
rows: Vec<Vec<Cell>>,
|
||||
row: Option<Vec<Cell>>,
|
||||
/// The table or one of its cells asks for borders.
|
||||
bordered: bool,
|
||||
}
|
||||
|
||||
/// Whether an element's `border` attribute or `style` declares a visible border.
|
||||
fn declares_border(attrs: &[(String, String)], is_table: bool) -> bool {
|
||||
if is_table && attr(attrs, "border").is_some_and(|b| b.trim().parse::<u32>().map(|n| n > 0).unwrap_or(b.trim().is_empty())) {
|
||||
return true;
|
||||
}
|
||||
let Some(style) = attr(attrs, "style") else { return false };
|
||||
style.split(';').any(|decl| {
|
||||
let Some((name, value)) = decl.split_once(':') else { return false };
|
||||
let name = name.trim().to_ascii_lowercase();
|
||||
if !matches!(name.as_str(), "border" | "border-top" | "border-right" | "border-bottom" | "border-left") {
|
||||
return false;
|
||||
}
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
!value.is_empty() && !value.split_whitespace().any(|tok| matches!(tok, "none" | "hidden" | "0" | "0px" | "0pt"))
|
||||
})
|
||||
}
|
||||
|
||||
struct Builder {
|
||||
@@ -750,7 +769,8 @@ impl Builder {
|
||||
if let Some(r) = t.row.take() {
|
||||
t.rows.push(r);
|
||||
}
|
||||
let mut ft = FTable { rows: t.rows.into_iter().filter(|r| !r.is_empty()).collect(), widths: Vec::new() };
|
||||
let borderless = !t.bordered;
|
||||
let mut ft = FTable { rows: t.rows.into_iter().filter(|r| !r.is_empty()).collect(), widths: Vec::new(), borderless };
|
||||
if !ft.rows.is_empty() {
|
||||
ft.insert_covered();
|
||||
self.container().push(FBlock::Table(ft));
|
||||
@@ -959,7 +979,7 @@ impl Builder {
|
||||
preserve = true;
|
||||
}
|
||||
match kind {
|
||||
ElKind::Table => self.tables.push(TableB { rows: Vec::new(), row: None }),
|
||||
ElKind::Table => self.tables.push(TableB { rows: Vec::new(), row: None, bordered: declares_border(attrs, true) }),
|
||||
ElKind::Tr => {
|
||||
if let Some(t) = self.tables.last_mut()
|
||||
&& let Some(r) = t.row.take()
|
||||
@@ -969,6 +989,11 @@ impl Builder {
|
||||
}
|
||||
ElKind::Cell => {
|
||||
let tb = self.find_open(|e| e.kind == ElKind::Tr, |e| e.kind == ElKind::Table).is_none();
|
||||
if let Some(t) = self.tables.last_mut()
|
||||
&& declares_border(attrs, false)
|
||||
{
|
||||
t.bordered = true;
|
||||
}
|
||||
if tb && let Some(t) = self.tables.last_mut() {
|
||||
// A cell without a row: start one.
|
||||
if let Some(r) = t.row.take() {
|
||||
@@ -1389,6 +1414,24 @@ mod tests {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn table_style(html: &str) -> Option<String> {
|
||||
let doc = crate::model::to_doc(&parse(html));
|
||||
doc.body.iter().find_map(|b| match &**b {
|
||||
wordcraft_doc::Block::Table(t) => Some(t.props.style.clone().unwrap_or_default()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borderless_tables_get_no_grid_style() {
|
||||
assert_eq!(table_style("<table><tr><td>a<td>b</table>").as_deref(), Some(""));
|
||||
assert_eq!(table_style("<table style='border:none'><tr><td style='border:0'>a</table>").as_deref(), Some(""));
|
||||
assert_eq!(table_style("<table border=0><tr><td style='border: 0px none'>a</table>").as_deref(), Some(""));
|
||||
assert_eq!(table_style("<table border=1><tr><td>a</table>").as_deref(), Some("TableGrid"));
|
||||
assert_eq!(table_style("<table style='border:1px solid #000'><tr><td>a</table>").as_deref(), Some("TableGrid"));
|
||||
assert_eq!(table_style("<table><tr><td style='border:1px solid #999'>a</table>").as_deref(), Some("TableGrid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_soup() {
|
||||
let f = parse(
|
||||
|
||||
@@ -180,6 +180,8 @@ pub struct FTable {
|
||||
pub rows: Vec<Vec<Cell>>,
|
||||
/// Grid column widths in points (empty = equal widths).
|
||||
pub widths: Vec<f32>,
|
||||
/// The source table had no borders (HTML default); the document table gets no grid style.
|
||||
pub borderless: bool,
|
||||
}
|
||||
|
||||
impl FTable {
|
||||
@@ -426,6 +428,9 @@ impl Builder<'_> {
|
||||
let cols = t.cols().clamp(1, wordcraft_doc::table::MAX_COLS);
|
||||
let width = self.doc.last_section.text_width().max(72.0);
|
||||
let mut tb = Table::new(rows.len(), cols, width);
|
||||
if t.borderless {
|
||||
tb.props.style = None;
|
||||
}
|
||||
if t.widths.len() == cols && t.widths.iter().all(|w| w.is_finite() && *w > 1.0) {
|
||||
tb.grid = t.widths.iter().map(|w| w.clamp(6.0, 1584.0)).collect();
|
||||
}
|
||||
@@ -704,7 +709,7 @@ fn flow_table(doc: &Document, t: &Table, depth: usize) -> FTable {
|
||||
}
|
||||
rows.push(cells);
|
||||
}
|
||||
FTable { rows, widths: t.grid.clone() }
|
||||
FTable { rows, widths: t.grid.clone(), borderless: false }
|
||||
}
|
||||
|
||||
/// The document body as a flow.
|
||||
@@ -980,7 +985,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn covered_cells_inserted() {
|
||||
let mut t = FTable { rows: vec![vec![Cell { rowspan: 2, ..Default::default() }, Cell::default()], vec![Cell::default()]], widths: vec![] };
|
||||
let mut t = FTable {
|
||||
rows: vec![vec![Cell { rowspan: 2, ..Default::default() }, Cell::default()], vec![Cell::default()]],
|
||||
widths: vec![],
|
||||
borderless: false,
|
||||
};
|
||||
t.insert_covered();
|
||||
assert_eq!(t.rows[1].len(), 2);
|
||||
assert!(t.rows[1][0].covered);
|
||||
|
||||
@@ -1243,7 +1243,7 @@ impl Body<'_> {
|
||||
if let Some(t) = self.tables.pop()
|
||||
&& !t.rows.is_empty()
|
||||
{
|
||||
self.container().push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new() }));
|
||||
self.container().push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new(), borderless: false }));
|
||||
}
|
||||
}
|
||||
"desc" | "title" => self.in_desc = false,
|
||||
@@ -1366,7 +1366,7 @@ pub fn parse(bytes: &[u8]) -> Result<Flow, String> {
|
||||
let mut blocks = body.containers.pop().unwrap_or_default();
|
||||
for t in body.tables.drain(..) {
|
||||
if !t.rows.is_empty() {
|
||||
blocks.push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new() }));
|
||||
blocks.push(FBlock::Table(FTable { rows: t.rows, widths: Vec::new(), borderless: false }));
|
||||
}
|
||||
}
|
||||
if blocks.is_empty() {
|
||||
|
||||
@@ -850,7 +850,7 @@ impl Reader {
|
||||
}
|
||||
if !self.table.is_empty() {
|
||||
let rows = std::mem::take(&mut self.table);
|
||||
self.body.push(FBlock::Table(FTable { rows, widths: Vec::new() }));
|
||||
self.body.push(FBlock::Table(FTable { rows, widths: Vec::new(), borderless: false }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user