diff --git a/src/block.rs b/src/block.rs index 599b8ebf..0ec303cc 100644 --- a/src/block.rs +++ b/src/block.rs @@ -367,6 +367,54 @@ impl<'s> TreeParser<'s> { lines }; + // Check if this paragraph is actually a multi-line attribute + let (kind, span_start) = { + let mut kind = kind; + let mut span_start = span_start; + if matches!(kind, Kind::Paragraph) + && lines.len() > 1 + && self.src[lines[0].clone()].starts_with('{') + { + let mut validator = attr::Validator::new(); + let mut attr_end = 0; + let mut attr_line_count = 0; + for (i, line) in lines.iter().enumerate() { + let line_str = &self.src[line.clone()]; + match validator.parse(line_str) { + Some(0) => break, + Some(len) => { + attr_end = line.start + len; + attr_line_count = i + 1; + break; + } + None => {} + } + } + if attr_end > 0 { + let remaining = &self.src[attr_end..lines.last().unwrap().end]; + if remaining.trim().is_empty() { + // Unattached: entire paragraph is the attribute + kind = Kind::Atom(Atom::Attributes); + span_start = span_start.start..attr_end; + } else if attr_line_count < line_count { + // Attached: attribute followed by content, split the block + let attr_span = lines[0].start..attr_end; + let after_attr = &self.src[attr_end..lines[attr_line_count - 1].end]; + if after_attr.trim().is_empty() { + self.events.push(Event { + kind: EventKind::Atom(Atom::Attributes), + span: attr_span, + }); + self.attr_start = + self.attr_start.or_else(|| Some(self.events.len() - 1)); + return attr_line_count; + } + } + } + } + (kind, span_start) + }; + // close list if a non list item or a list item of new type appeared if let Some(OpenList { ty_start, diff --git a/tests/html-ut/skip b/tests/html-ut/skip index d182e5d5..2737b14e 100644 --- a/tests/html-ut/skip +++ b/tests/html-ut/skip @@ -1,5 +1,3 @@ -38d85f9:multi-line block attributes -6c14561:multi-line block attributes 2fa94d1:bugged left/right quote e1f5b5e:untrimmed whitespace before linebreak 8423412:heading id conflict with existing id diff --git a/tests/html-ut/ut/attributes.test b/tests/html-ut/ut/attributes.test index e4b0f266..dcaa51c6 100644 --- a/tests/html-ut/ut/attributes.test +++ b/tests/html-ut/ut/attributes.test @@ -191,3 +191,13 @@ a{a="?a"} a{a=?a} a
``` + +Multi-line block attributes attach to the following block + +``` +{#id .class + style="color:red"} +A paragraph +. +A paragraph
+``` diff --git a/tests/parse_events.rs b/tests/parse_events.rs index d64c9978..5bc37fb5 100644 --- a/tests/parse_events.rs +++ b/tests/parse_events.rs @@ -1632,6 +1632,52 @@ fn attr_inline_consecutive_invalid() { ); } +#[test] +fn attr_block_multiline_unattached() { + test_parse!( + concat!( + "{ .a }\n", // + ), + (Attributes(attrs![(AttributeKind::Class, "a")]), "{ .a }\n"), + ); + test_parse!( + concat!( + "{ .a\n", // + " }\n", // + ), + (Attributes(attrs![(AttributeKind::Class, "a")]), "{ .a\n }"), + ); +} + +#[test] +fn attr_block_multiline_attached() { + test_parse!( + concat!( + "{#id .class\n", // + " style=\"color:red\"}\n", // + "A paragraph\n", // + ), + ( + Start( + Paragraph, + attrs![ + (AttributeKind::Id, "id"), + (AttributeKind::Class, "class"), + ( + AttributeKind::Pair { + key: "style".into() + }, + "color:red" + ), + ] + ), + "{#id .class\n style=\"color:red\"}\n", + ), + (Str("A paragraph".into()), "A paragraph"), + (End(Paragraph), ""), + ); +} + #[test] fn attr_inline_multiline() { test_parse!(