From f16a8a93ed7342a7dc23151173b439fedc371b05 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 4 Nov 2026 04:24:12 +0000 Subject: [PATCH] address review: keep declarations for prefixes used in QName attribute values --- crates/pptx/src/read.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/pptx/src/read.rs b/crates/pptx/src/read.rs index f97a38cd4..a0dfe7b1a 100644 --- a/crates/pptx/src/read.rs +++ b/crates/pptx/src/read.rs @@ -141,7 +141,8 @@ fn inner_decls(root: &El, ns: &[(String, String)]) -> Vec<(String, String)> { const MAX_NS_VALUE: usize = 2048; /// Prefixes `el` and its descendants use in element and attribute names, and those listed in -/// `mc:Ignorable` or `mc:MustUnderstand` (which must stay declared for the markup-compatibility rules to hold). +/// `mc:Ignorable` or `mc:MustUnderstand` (which must stay declared for the markup-compatibility rules to hold), +/// and those starting a QName-like attribute value. fn used_prefixes(el: &El) -> HashSet<&str> { fn rec<'a>(e: &'a El, used: &mut HashSet<&'a str>, depth: usize) { if depth > crate::xml::MAX_DEPTH { @@ -160,6 +161,17 @@ fn used_prefixes(el: &El) -> HashSet<&str> { used.extend(v.split_whitespace()); } } + // QName-valued attributes (`xsi:type="q:Item"`) need their prefix declared too. + if k != "xmlns" && !k.starts_with("xmlns:") { + for t in v.split_whitespace() { + if let Some((p, _)) = t.split_once(':') + && !p.is_empty() + && p.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.')) + { + used.insert(p); + } + } + } } for c in e.elements() { rec(c, used, depth + 1); @@ -1509,6 +1521,16 @@ mod tests { assert!(kid.attrs.iter().any(|(k, v)| k == "xmlns:q" && v == "urn:q"), "{:?}", kid.attrs); } + #[test] + fn mc_branches_keep_prefixes_used_in_qname_values() { + let x = r#""#; + let mut d = crate::xml::parse(x.as_bytes()).unwrap(); + resolve_mc(&mut d.root, 0); + let kid = d.root.elements().next().unwrap(); + assert!(kid.attrs.iter().any(|(k, v)| k == "xmlns:q" && v == "urn:q"), "{:?}", kid.attrs); + assert!(!kid.attrs.iter().any(|(k, _)| k == "xmlns:z"), "unused declarations stay out: {:?}", kid.attrs); + } + #[test] fn kept_fragments_skip_oversized_namespace_names() { let big = "u".repeat(MAX_NS_VALUE + 1);