diff --git a/src/attr.rs b/src/attr.rs index 41033a1..f65796a 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -58,6 +58,21 @@ impl<'s> Attributes<'s> { true } + /// Combine all attributes from both objects, prioritizing self on conflicts. + pub fn union(&mut self, other: Self) { + if let Some(attrs0) = &mut self.0 { + if let Some(mut attrs1) = other.0 { + for (attr, val) in attrs1.drain(..) { + if !attrs0.iter().any(|(a, _)| *a == attr) { + attrs0.push((attr, val)); + } + } + } + } else { + self.0 = other.0; + } + } + fn add(&mut self, attr: &'s str, val: CowStr<'s>) { if self.0.is_none() { self.0 = Some(Vec::new().into()); diff --git a/src/lib.rs b/src/lib.rs index c4d77b0..9db844a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,7 +318,7 @@ pub struct Parser<'s> { src: &'s str, /// Link definitions encountered during block parse, written once. - link_definitions: std::collections::HashMap<&'s str, CowStr<'s>>, + link_definitions: std::collections::HashMap<&'s str, (CowStr<'s>, attr::Attributes<'s>)>, /// Block tree cursor. tree: block::Tree, @@ -347,17 +347,24 @@ impl<'s> Parser<'s> { let link_definitions = { let mut branch = tree.clone(); let mut defs = std::collections::HashMap::new(); + let mut attr_prev: Option = None; while let Some(e) = branch.next() { if let tree::EventKind::Enter(block::Node::Leaf(block::Leaf::LinkDefinition)) = e.kind { let tag = e.span.of(src); + let attrs = + attr_prev.map_or_else(Attributes::new, |sp| attr::parse(sp.of(src))); let url = match branch.count_children() { 0 => "".into(), 1 => branch.take_inlines().next().unwrap().of(src).trim().into(), _ => branch.take_inlines().map(|sp| sp.of(src).trim()).collect(), }; - defs.insert(tag, url); + defs.insert(tag, (url, attrs)); + } else if let tree::EventKind::Atom(block::Atom::Attributes) = e.kind { + attr_prev = Some(e.span); + } else { + attr_prev = None; } } defs @@ -386,7 +393,7 @@ impl<'s> Parser<'s> { let mut inline = parser.next(); let mut first_is_attr = false; - let attributes = inline.as_ref().map_or_else(Attributes::new, |inl| { + let mut attributes = inline.as_ref().map_or_else(Attributes::new, |inl| { if let inline::EventKind::Attributes = inl.kind { first_is_attr = true; attr::parse(self.inlines.slice(inl.span)) @@ -433,20 +440,19 @@ impl<'s> Parser<'s> { }, SpanLinkType::Inline, ), - inline::Container::ReferenceLink => Container::Link( - self.link_definitions + inline::Container::ReferenceLink | inline::Container::ReferenceImage => { + let (url, attrs_def) = self + .link_definitions .get(self.inlines.src(inline.span).replace('\n', " ").as_str()) .cloned() - .unwrap_or_else(|| "".into()), - LinkType::Span(SpanLinkType::Reference), - ), - inline::Container::ReferenceImage => Container::Image( - self.link_definitions - .get(self.inlines.src(inline.span).replace('\n', " ").as_str()) - .cloned() - .unwrap_or_else(|| "".into()), - SpanLinkType::Reference, - ), + .unwrap_or_else(|| ("".into(), Attributes::new())); + attributes.union(attrs_def); + if matches!(c, inline::Container::ReferenceLink) { + Container::Link(url, LinkType::Span(SpanLinkType::Reference)) + } else { + Container::Image(url, SpanLinkType::Reference) + } + } inline::Container::Autolink => todo!("{:?}", c), }; if matches!(inline.kind, inline::EventKind::Enter(_)) { @@ -921,6 +927,33 @@ mod test { ); } + #[test] + fn link_reference_attrs() { + test_parse!( + concat!( + "[text][tag]{b=c}\n", + "\n", + "{a=b}\n", + "[tag]: url\n", + "\n", + "para\n", + ), + Start(Paragraph, Attributes::new()), + Start( + Link("url".into(), LinkType::Span(SpanLinkType::Reference)), + [("b", "c"), ("a", "b")].into_iter().collect(), + ), + Str("text".into()), + End(Link("url".into(), LinkType::Span(SpanLinkType::Reference))), + End(Paragraph), + Atom(Blankline), + Atom(Blankline), + Start(Paragraph, Attributes::new()), + Str("para".into()), + End(Paragraph), + ); + } + #[test] fn footnote_references() { test_parse!(