use quick_xml::XmlVersion;
use quick_xml::events::Event;
use quick_xml::name::{Namespace, QName, ResolveResult};
use quick_xml::reader::NsReader;
use crate::error::Result;
pub const W_NS: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
pub const W_PREFIX: &[u8] = b"w";
pub const M_NS: &str = "http://schemas.openxmlformats.org/officeDocument/2006/math";
pub const M_PREFIX: &[u8] = b"m";
pub use oxml_core::xml::{MC_NS, R_NS, matches_local_name};
pub fn undeclared_compatibility_prefixes(xml: &[u8]) -> Result<Vec<(String, String)>> {
let mut reader = NsReader::from_reader(xml);
let mut buffer = Vec::new();
let mut findings: Vec<(String, String)> = Vec::new();
loop {
match reader.read_event_into(&mut buffer)? {
Event::Start(element) | Event::Empty(element) => {
for attribute in element.attributes() {
let attribute = attribute?;
let (namespace, local) = reader.resolver().resolve_attribute(attribute.key);
let local = std::str::from_utf8(local.as_ref())?;
if namespace != ResolveResult::Bound(Namespace(MC_NS.as_bytes()))
|| !matches!(local, "Ignorable" | "MustUnderstand")
{
continue;
}
let value = attribute
.decoded_and_normalized_value(XmlVersion::Implicit1_0, element.decoder())?;
for prefix in value.split_ascii_whitespace() {
let probe = format!("{prefix}:probe");
let (bound, _) = reader.resolver().resolve_element(QName(probe.as_bytes()));
if !matches!(bound, ResolveResult::Bound(_))
&& !findings
.iter()
.any(|(name, known)| name == local && known == prefix)
{
findings.push((local.to_owned(), prefix.to_owned()));
}
}
}
}
Event::Eof => return Ok(findings),
_ => {}
}
buffer.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compatibility_prefixes_must_be_declared_in_scope() {
let xml = br#"<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:compat="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" compat:Ignorable="w14 w15"><w:comment w:id="0" xmlns:w16="urn:w16" compat:Ignorable="w16 w14" compat:MustUnderstand="w17"/><w:comment w:id="1" compat:Ignorable="w16"/></w:comments>"#;
assert_eq!(
undeclared_compatibility_prefixes(xml).unwrap(),
[
("Ignorable".to_owned(), "w14".to_owned()),
("MustUnderstand".to_owned(), "w17".to_owned()),
("Ignorable".to_owned(), "w16".to_owned()),
]
);
let xml = br#"<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:mc="urn:other" mc:Ignorable="w14" Ignorable="w14"/>"#;
assert_eq!(undeclared_compatibility_prefixes(xml).unwrap(), []);
assert!(undeclared_compatibility_prefixes(b"<a><b></a>").is_err());
}
}