use std::collections::HashMap;
use roxmltree::Node;
use super::prefix::has_in_scope_default_namespace;
pub(crate) fn collect_ns_declarations(
node: Node<'_, '_>,
parent_rendered: &HashMap<String, String>,
include_prefix: impl Fn(&str, &str) -> bool,
) -> (Vec<(String, String)>, HashMap<String, String>) {
let mut rendered = parent_rendered.clone();
let mut decls: Vec<(String, String)> = Vec::new();
for ns in node.namespaces() {
let prefix = ns.name().unwrap_or(""); let uri = ns.uri();
if prefix == "xml" {
continue;
}
if !include_prefix(prefix, uri) {
continue;
}
if parent_rendered.get(prefix).map(|u| u.as_str()) == Some(uri) {
continue;
}
if prefix.is_empty() && uri.is_empty() {
let has_rendered_default = parent_rendered.contains_key("");
let has_in_scope_default = has_in_scope_default_namespace(node);
if !has_rendered_default && !has_in_scope_default {
continue;
}
}
decls.push((prefix.to_string(), uri.to_string()));
}
decls.sort_by(|a, b| a.0.cmp(&b.0));
for (prefix, uri) in &decls {
rendered.insert(prefix.clone(), uri.clone());
}
(decls, rendered)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use roxmltree::Document;
fn collect_all(xml: &str) -> Vec<(String, String)> {
let doc = Document::parse(xml).expect("parse");
let root = doc.root_element();
let (decls, _) = collect_ns_declarations(root, &HashMap::new(), |_, _| true);
decls
}
#[test]
fn xml_prefix_excluded() {
let decls = collect_all(r#"<root xmlns:a="http://a.com"/>"#);
assert!(
decls.iter().all(|(p, _)| p != "xml"),
"xml prefix must be excluded"
);
}
#[test]
fn sorted_by_prefix() {
let decls =
collect_all(r#"<root xmlns:z="http://z" xmlns:a="http://a" xmlns="http://d"/>"#);
let prefixes: Vec<&str> = decls.iter().map(|(p, _)| p.as_str()).collect();
assert_eq!(prefixes, vec!["", "a", "z"]);
}
#[test]
fn redundant_suppressed() {
let doc = Document::parse(r#"<root xmlns:a="http://a"><child/></root>"#).expect("parse");
let root = doc.root_element();
let (_, rendered) = collect_ns_declarations(root, &HashMap::new(), |_, _| true);
let child = root.first_element_child().expect("child");
let (decls, _) = collect_ns_declarations(child, &rendered, |_, _| true);
assert!(decls.is_empty(), "child must not redeclare a:");
}
#[test]
fn spurious_xmlns_empty_suppressed() {
let decls = collect_all(r#"<root xmlns=""/>"#);
assert!(
decls.is_empty(),
"xmlns=\"\" without a default ns in scope is spurious"
);
}
#[test]
fn xmlns_empty_emitted_for_undeclaration() {
let doc = Document::parse(r#"<root xmlns="http://example.com"><child xmlns=""/></root>"#)
.expect("parse");
let root = doc.root_element();
let (_, rendered) = collect_ns_declarations(root, &HashMap::new(), |_, _| true);
let child = root.first_element_child().expect("child");
let (decls, _) = collect_ns_declarations(child, &rendered, |_, _| true);
assert!(
decls.iter().any(|(p, u)| p.is_empty() && u.is_empty()),
"xmlns=\"\" must be emitted to undeclare default ns"
);
}
#[test]
fn predicate_filters_prefixes() {
let doc =
Document::parse(r#"<root xmlns:a="http://a" xmlns:b="http://b" xmlns:c="http://c"/>"#)
.expect("parse");
let root = doc.root_element();
let (decls, _) = collect_ns_declarations(root, &HashMap::new(), |p, _| p == "b");
assert_eq!(decls.len(), 1);
assert_eq!(decls[0].0, "b");
}
}