use super::escape::{changes_in_11, is_c0_control};
use crate::tree::{Document, NameId, NodeId, NodeKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SerialiseError {
NoRootElement,
VersionConflict {
node: NodeId,
character: char,
},
}
impl core::fmt::Display for SerialiseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoRootElement => {
f.write_str("the document has no root element")
}
Self::VersionConflict { character, .. } => write!(
f,
"the document needs XML 1.1, which cannot hold U+{:04X} \
in a comment or processing instruction",
u32::from(*character)
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SerialiseError {}
impl Document {
pub(super) fn needs_xml_11(&self) -> bool {
self.written().any(|id| match self.kind(id) {
Some(NodeKind::Text(t)) => t.chars().any(is_c0_control),
Some(NodeKind::Attr(a)) => {
a.value.chars().any(is_c0_control) || self.c0_in_uri(a.name)
}
Some(NodeKind::Element { name, .. }) => self.c0_in_uri(name),
Some(NodeKind::Namespace { prefix, uri }) => {
(!prefix.is_empty() && uri.is_empty())
|| uri.chars().any(is_c0_control)
}
_ => false,
})
}
pub(super) fn version_conflict(&self) -> Option<SerialiseError> {
if !self.needs_xml_11() {
return None;
}
self.written().find_map(|node| {
let body = match self.kind(node) {
Some(NodeKind::Comment(c)) => c,
Some(NodeKind::ProcessingInstruction { data, .. }) => data,
_ => return None,
};
let character = body.chars().find(|&c| changes_in_11(c))?;
Some(SerialiseError::VersionConflict { node, character })
})
}
fn written(&self) -> impl Iterator<Item = NodeId> + '_ {
self.descendants_of(self.root()).flat_map(move |id| {
core::iter::once(id)
.chain(self.attribute_nodes(id).iter().copied())
.chain(self.namespace_nodes(id).iter().copied())
})
}
fn c0_in_uri(&self, name: NameId) -> bool {
self.name(name)
.and_then(|n| n.namespace.as_deref())
.is_some_and(|uri| uri.chars().any(is_c0_control))
}
}