diff --git a/moli-html2md/README.md b/moli-html2md/README.md index cd6414ec52..c85505ef29 100644 --- a/moli-html2md/README.md +++ b/moli-html2md/README.md @@ -64,13 +64,15 @@ and cell line breaks and paragraph boundaries use inline `
` tags. Attribute newlines are encoded or normalized so they cannot break a table row. GFM supplies missing trailing cells in short rows; the converter does not allocate a padded grid. -Complex tables retain Turndown core's ordinary block expansion, in DOM order with -blank lines between cells. This includes non-unit spans, multiple `thead` rows, -rows wider than an explicit header, or cells containing nested tables, lists, -headings, quotes or preformatted blocks. Inner simple tables can still produce -GFM when their containing layout table is expanded. Roles, borders and CSS do -not select another conversion path, so structurally simple layout tables also -produce GFM. Complex layouts remain readable blocks instead of raw HTML tables. +Tables with `role="presentation"` or `role="none"` retain ordinary block expansion, +in DOM order with blank lines between cells, even when structurally simple. Role +matching ignores ASCII case and surrounding HTML whitespace. The same fallback +applies to complex tables: non-unit spans, multiple `thead` rows, rows wider than +an explicit header, or cells containing nested tables, lists, headings, quotes +or preformatted blocks. Inner simple tables can still produce GFM when their +containing layout table is expanded. Other roles, borders and CSS do not select +another conversion path. Complex layouts remain readable blocks instead of raw +HTML tables. Supported output includes headings, paragraphs, emphasis, strikethrough, links, images, lists, blockquotes, hard breaks, and fenced code. Inline HTML is used when diff --git a/moli-html2md/src/table.rs b/moli-html2md/src/table.rs index 99cf4456f5..f29e72208e 100644 --- a/moli-html2md/src/table.rs +++ b/moli-html2md/src/table.rs @@ -26,6 +26,12 @@ impl Table { depth: usize, max_depth: usize, ) -> Option { + if dom.attribute(node, "role").is_some_and(|role| { + let role = role.trim_matches(is_space); + role.eq_ignore_ascii_case("presentation") || role.eq_ignore_ascii_case("none") + }) { + return None; + } let mut table = Self { captions: Vec::new(), cells: Vec::new().into_iter(), diff --git a/moli-html2md/tests/tables.rs b/moli-html2md/tests/tables.rs index 5e2bf3ac65..e19c8ee15e 100644 --- a/moli-html2md/tests/tables.rs +++ b/moli-html2md/tests/tables.rs @@ -55,14 +55,55 @@ fn wrapping_a_table_preserves_its_content() { } #[test] -fn table_roles_do_not_change_conversion() { +fn presentational_tables_expand_into_blocks() { + for role in ["presentation", "none", " PRESENTATION ", "\tNoNe\r\n"] { + for header in [ + "", + "NameValue", + "NameValue", + ] { + let html = format!( + "{header}
Title
onetwo|three
end
" + ); + let heading = if header.is_empty() { + "" + } else { + "Name\n\nValue\n\n" + }; + assert_eq!( + markdown(&html), + format!("**Title**\n\n{heading}[one](/one)\n\n`two|three`\n\nend"), + "{html}" + ); + } + } +} + +#[test] +fn presentational_table_roles_do_not_propagate_to_nested_tables() { + for role in ["presentation", "none"] { + let inner = + "
NameValue
onetwo
"; + let html = format!( + "
before{inner}after
" + ); + assert_eq!( + markdown(&html), + "before\n\n| Name | Value |\n| --- | --- |\n| one | two |\n\nafter", + "{html}" + ); + } +} + +#[test] +fn other_table_roles_do_not_change_conversion() { for role in [ "", - "presentation", - "none", - " PRESENTATION ", "table", "grid", + "treegrid", + "presentationish", + "none-other", ] { let html = format!( "
NameValue
onetwo
"