Declare the a: and r: namespaces on the modern comments part (#974)

p188:cmLst declared only xmlns:p188 but its comment bodies use a:bodyPr, a:p, a:r and
a:t, so the part was not namespace-well-formed and PowerPoint refused the saved file.
A regression test parses every written XML part with a namespace-aware reader.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eduardo Garcia
2026-10-18 12:22:07 -03:00
co-authored by Claude Sonnet 5.5
parent 22db9261f9
commit 601823d98c
2 changed files with 35 additions and 1 deletions
+1 -1
View File
@@ -876,7 +876,7 @@ fn author_slot(c: &Comment, authors: &mut Vec<(String, String, u32)>) -> usize {
/// the flattened legacy part cannot express (issue #70).
fn modern_comments_xml(list: &[Comment], part: &str, authors: &mut Vec<(String, String, u32)>) -> Vec<u8> {
let mut w = W::new();
w.open("p188:cmLst", A::new().a("xmlns:p188", NS_P188));
w.open("p188:cmLst", A::new().a("xmlns:p188", NS_P188).a("xmlns:a", NS_A).a("xmlns:r", NS_R));
for (ti, c) in list.iter().take(10_000).enumerate() {
modern_comment(&mut w, c, part, ti, authors, 0);
}
+34
View File
@@ -206,3 +206,37 @@ fn modern_only_package_imports() {
assert!(b[0].resolved);
assert_eq!((b[0].x, b[0].y), (90.0, 45.0), "modern positions are EMU");
}
/// Every prefix an exported part uses is bound: PowerPoint refuses a package whose part is not
/// namespace-well-formed (issue #974, the modern comments part used `a:` without declaring it).
#[test]
fn every_written_xml_part_binds_its_prefixes() {
use quick_xml::events::Event;
use quick_xml::name::ResolveResult;
let bytes = deckcraft_pptx::export(&deck()).expect("export");
let mut z = zip::ZipArchive::new(std::io::Cursor::new(&bytes)).expect("zip");
let mut modern = 0;
for i in 0..z.len() {
let mut f = z.by_index(i).expect("entry");
let name = f.name().to_string();
if !(name.ends_with(".xml") || name.ends_with(".rels")) {
continue;
}
let mut data = vec![];
f.read_to_end(&mut data).expect("read");
modern += usize::from(name.contains("modernComment"));
let mut r = quick_xml::NsReader::from_reader(data.as_slice());
loop {
match r.read_resolved_event() {
Ok((ns, Event::Start(e) | Event::Empty(e))) => {
assert!(!matches!(ns, ResolveResult::Unknown(_)), "{name}: unbound prefix on <{}>", String::from_utf8_lossy(e.name().as_ref()));
}
Ok((_, Event::Eof)) => break,
Ok(_) => {}
Err(e) => panic!("{name}: not well-formed: {e}"),
}
}
}
assert!(modern > 0, "the deck should write a modern comments part");
}