use crate::xml::dom::Node;
use crate::xml::dom::storage::Storage as DomStorage;
use super::C14nError;
use super::NodeVisibility;
use super::prefix::has_in_scope_default_namespace;
use super::scratch::{List, NamespaceMap, NamespaceRendering, Scratch};
pub(super) fn collect_ns_declarations<'a, S: Scratch, D: DomStorage>(
node: Node<'a, '_, D>,
parent_rendered: &S::Namespaces<'a>,
visibility: Option<&dyn NodeVisibility<D>>,
reset_invisible_binding: impl Fn(&str) -> bool,
include_prefix: impl Fn(&str, &str) -> Result<bool, C14nError>,
scratch: &S,
) -> Result<NamespaceRendering<'a, S>, C14nError> {
let mut rendered = parent_rendered.copy()?;
let mut decls = scratch.values();
for (ordinal, ns) in node.namespaces().enumerate() {
let prefix = ns.name().unwrap_or(""); let uri = ns.uri();
scratch.charge(prefix.len().checked_add(uri.len()).ok_or(
C14nError::OutputLimitExceeded {
max_bytes: usize::MAX,
},
)?)?;
if let Some(set) = visibility
&& !set.contains_namespace_at(node, ordinal, ns)?
{
if reset_invisible_binding(prefix) {
if prefix.is_empty()
&& parent_rendered
.get("", false)?
.flatten()
.is_some_and(|uri| !uri.is_empty())
{
decls.push(("", ""))?;
}
rendered.remove(prefix)?;
}
continue;
}
if prefix == "xml" {
continue;
}
if !include_prefix(prefix, uri)? {
continue;
}
if parent_rendered.get(prefix, false)?.flatten() == Some(uri) {
continue;
}
if prefix.is_empty() && uri.is_empty() {
let has_rendered_default = parent_rendered.get("", false)?.is_some();
let has_in_scope_default = has_in_scope_default_namespace(node);
if !has_rendered_default && !has_in_scope_default {
continue;
}
}
decls.push((prefix, uri))?;
}
decls.sort(|a, b| {
scratch.charge(a.0.len().min(b.0.len()))?;
Ok(a.0.cmp(b.0))
})?;
for &(prefix, uri) in decls.iter() {
rendered.insert(prefix, false, Some(uri))?;
}
Ok((decls, rendered))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::super::scratch::{Native, Scratch};
use super::*;
use crate::xml::dom::Document;
use std::collections::HashMap;
fn collect_ns_declarations(
node: Node<'_, '_>,
parent: &HashMap<String, String>,
visibility: Option<&dyn NodeVisibility>,
reset: impl Fn(&str) -> bool,
include: impl Fn(&str, &str) -> bool,
) -> (Vec<(String, String)>, HashMap<String, String>) {
let mut namespaces = Native.namespaces();
for (prefix, uri) in parent {
namespaces.insert(prefix, false, Some(uri)).unwrap();
}
let (declarations, rendered) = super::collect_ns_declarations(
node,
&namespaces,
visibility,
reset,
|p, u| Ok(include(p, u)),
&Native,
)
.unwrap();
(
declarations
.into_iter()
.map(|(p, u)| (p.to_owned(), u.to_owned()))
.collect(),
rendered
.into_pairs()
.map(|(p, u)| (p.to_owned(), u.to_owned()))
.collect(),
)
}
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(), None, |_| false, |_, _| 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(), None, |_| false, |_, _| true);
let child = root.first_element_child().expect("child");
let (decls, _) = collect_ns_declarations(child, &rendered, None, |_| false, |_, _| 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(), None, |_| false, |_, _| true);
let child = root.first_element_child().expect("child");
let (decls, _) = collect_ns_declarations(child, &rendered, None, |_| false, |_, _| 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(), None, |_| false, |p, _| p == "b");
assert_eq!(decls.len(), 1);
assert_eq!(decls[0].0, "b");
}
}