diff --git a/src/formats/odf/text.rs b/src/formats/odf/text.rs index da64da40..7d57d1a1 100644 --- a/src/formats/odf/text.rs +++ b/src/formats/odf/text.rs @@ -326,6 +326,9 @@ fn walk_inlines( } } Node::Elem(child) => { + if child.is(ns::OFFICE, "annotation") { + continue; + } let in_text = child.ns.as_deref().is_some_and(|n| n == ns::TEXT); if in_text { match child.local.as_str() { @@ -396,7 +399,7 @@ fn walk_inlines( out.push(Inline::NoteRef(id)); continue; } - "annotation" | "tracked-changes" | "soft-page-break" => continue, + "tracked-changes" | "soft-page-break" => continue, _ => {} } } diff --git a/tests/odf_annotations.rs b/tests/odf_annotations.rs new file mode 100644 index 00000000..3fe76c1b --- /dev/null +++ b/tests/odf_annotations.rs @@ -0,0 +1,50 @@ +//! ODF comments must not become part of the paragraph they annotate. + +use anydoc::{Format, to_markdown_bytes}; +use std::io::{Cursor, Write}; + +fn odt(body: &str) -> Vec { + let content = format!( + r#" + {body} + "# + ); + let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new())); + let opts = zip::write::SimpleFileOptions::default(); + for (name, body) in + [("mimetype", "application/vnd.oasis.opendocument.text"), ("content.xml", content.as_str())] + { + zip.start_file(name, opts).unwrap(); + zip.write_all(body.as_bytes()).unwrap(); + } + zip.finish().unwrap().into_inner() +} + +#[test] +fn point_comment_does_not_split_the_anchored_word() { + let bytes = odt(r#"Text with a comment on Lamb + Author2026-01-01T00:00:00 + First comment paragraph. + Second comment paragraph. + daword here."#); + let md = to_markdown_bytes(&bytes, Format::Odt).unwrap(); + assert_eq!(md.trim_end(), "Text with a comment on Lambdaword here."); +} + +#[test] +fn ranged_comment_keeps_the_link_and_surrounding_text() { + let bytes = odt(concat!( + r#"Read "#, + r#"the"#, + "ReviewerComment on the link.", + " guide", + r#" today."#, + )); + let md = to_markdown_bytes(&bytes, Format::Odt).unwrap(); + assert_eq!(md.trim_end(), "Read [the guide](https://example.com) today."); +}