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 [
+ "",
+ "| Name | Value |
",
+ "| Name | Value |
",
+ ] {
+ let html = format!(
+ "Title{header}| one | two|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 =
+ "";
+ let html = format!(
+ ""
+ );
+ 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!(
""