use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::fmt::Write;
use super::escape::write_attribute_value;
const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
pub(super) struct Scope<'a> {
bindings: Vec<(Cow<'a, str>, &'a str)>,
}
impl<'a> Scope<'a> {
pub(super) fn new() -> Self {
Self {
bindings: Vec::from([(Cow::Borrowed("xml"), XML_NAMESPACE)]),
}
}
pub(super) fn len(&self) -> usize {
self.bindings.len()
}
pub(super) fn truncate(&mut self, len: usize) {
self.bindings.truncate(len);
}
pub(super) fn push(&mut self, prefix: Cow<'a, str>, uri: &'a str) -> usize {
self.bindings.push((prefix, uri));
self.bindings.len() - 1
}
pub(super) fn prefix(&self, i: usize) -> &str {
self.bindings.get(i).map_or("", |(p, _)| p.as_ref())
}
pub(super) fn binding(&self, prefix: &str) -> Option<usize> {
self.bindings
.iter()
.rposition(|(p, _)| p.as_ref() == prefix)
}
pub(super) fn lookup(&self, prefix: &str) -> Option<&'a str> {
self.binding(prefix).map(|i| self.bindings[i].1)
}
pub(super) fn bound_to(
&self,
prefix: Option<&str>,
uri: &str,
) -> Option<usize> {
let i = self.binding(prefix?)?;
(self.bindings[i].1 == uri).then_some(i)
}
pub(super) fn is_free(&self, prefix: &str) -> bool {
self.lookup(prefix).is_none_or(str::is_empty)
}
pub(super) fn prefix_for(&self, uri: &str) -> Option<usize> {
(0..self.bindings.len()).rev().find(|&i| {
let (p, u) = &self.bindings[i];
!p.is_empty() && *u == uri && self.lookup(p) == Some(*u)
})
}
pub(super) fn declare(
&mut self,
stored: Option<&'a str>,
uri: &'a str,
) -> usize {
if let Some(p) = stored.filter(|p| self.is_free(p)) {
return self.push(Cow::Borrowed(p), uri);
}
let mut n = 1u32;
loop {
let candidate = alloc::format!("ns{n}");
if self.is_free(&candidate) {
return self.push(Cow::Owned(candidate), uri);
}
n += 1;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Written {
Bare,
Prefixed(usize),
}
pub(super) fn resolve_unqualified(
attribute: bool,
scope: &mut Scope<'_>,
) -> Written {
if !attribute && scope.lookup("").is_some_and(|u| !u.is_empty()) {
let _ = scope.push(Cow::Borrowed(""), "");
}
Written::Bare
}
pub(super) fn resolve_qualified<'a>(
stored: Option<&'a str>,
uri: &'a str,
attribute: bool,
scope: &mut Scope<'a>,
) -> Written {
if let Some(i) = scope.bound_to(stored, uri) {
return Written::Prefixed(i);
}
if !attribute && scope.lookup("") == Some(uri) {
return Written::Bare;
}
let i = scope
.prefix_for(uri)
.unwrap_or_else(|| scope.declare(stored, uri));
Written::Prefixed(i)
}
pub(super) fn write_auto_declarations<W: Write>(
out: &mut W,
scope: &Scope<'_>,
from: usize,
) -> core::fmt::Result {
for (prefix, uri) in &scope.bindings[from..] {
write_declaration(out, prefix, uri)?;
}
Ok(())
}
pub(super) fn write_declaration<W: Write>(
out: &mut W,
prefix: &str,
uri: &str,
) -> core::fmt::Result {
if prefix.is_empty() {
out.write_str(" xmlns=\"")?;
} else {
write!(out, " xmlns:{prefix}=\"")?;
}
write_attribute_value(uri, out)?;
out.write_char('"')
}