use alloc::string::String;
use super::{Chars, Document, ExpandedName, NameId, NodeData, NodeError};
use crate::xml::{is_name_char, is_name_start, is_xml_char};
pub(super) fn is_ncname(s: &str) -> bool {
let mut chars = s.chars();
chars.next().is_some_and(|c| c != ':' && is_name_start(c))
&& chars.all(|c| c != ':' && is_name_char(c))
}
pub(super) fn check_prefix(prefix: &str) -> Result<(), NodeError> {
if prefix.is_empty()
|| (is_ncname(prefix) && prefix != "xml" && prefix != "xmlns")
{
Ok(())
} else {
Err(NodeError::InvalidName)
}
}
pub(super) const fn is_expressible(c: char) -> bool {
!matches!(c, '\0' | '\u{fffe}' | '\u{ffff}')
}
pub(super) fn check_text(text: &str) -> Result<(), NodeError> {
if text.chars().all(is_expressible) {
Ok(())
} else {
Err(NodeError::InvalidText)
}
}
pub(super) fn check_literal(text: &str) -> Result<(), NodeError> {
if text.chars().all(|c| c != '\r' && is_xml_char(c)) {
Ok(())
} else {
Err(NodeError::InvalidText)
}
}
pub(super) fn check_comment(text: &str) -> Result<(), NodeError> {
check_literal(text)?;
if text.contains("--") || text.ends_with('-') {
return Err(NodeError::InvalidText);
}
Ok(())
}
pub(super) fn check_pi(target: &str, data: &str) -> Result<(), NodeError> {
if !is_ncname(target) || target.eq_ignore_ascii_case("xml") {
return Err(NodeError::InvalidName);
}
check_literal(data)?;
if data.contains("?>") {
return Err(NodeError::InvalidText);
}
Ok(())
}
pub(super) fn namespace_of(namespace: Option<&str>) -> Option<&str> {
namespace.filter(|u| !u.is_empty())
}
pub(super) fn check_namespace(
namespace: Option<&str>,
) -> Result<(), NodeError> {
if let Some(uri) = namespace {
check_text(uri)?;
if uri == "http://www.w3.org/2000/xmlns/" {
return Err(NodeError::InvalidName);
}
}
Ok(())
}
pub(super) fn check_namespace_binding(uri: &str) -> Result<(), NodeError> {
check_namespace(Some(uri))?;
if uri == "http://www.w3.org/XML/1998/namespace" {
return Err(NodeError::InvalidName);
}
Ok(())
}
impl Document {
pub(super) fn intern(
&mut self,
namespace: Option<&str>,
local: &str,
) -> Result<NameId, NodeError> {
if !is_ncname(local) {
return Err(NodeError::InvalidName);
}
check_namespace(namespace)?;
let namespace = namespace_of(namespace);
let found = self.names.iter().position(|n| {
n.namespace.as_deref() == namespace && n.local == local
});
if let Some(i) = found {
return Ok(NameId(u32::try_from(i).unwrap_or(u32::MAX)));
}
let id = u32::try_from(self.names.len())
.expect("document exceeds u32::MAX names");
self.names.push(ExpandedName {
namespace: namespace.map(String::from),
local: String::from(local),
});
self.name_prefixes.push(None);
Ok(NameId(id))
}
pub(super) fn store_text(&mut self, text: &str) -> Chars {
if text.is_empty() {
return Chars::EMPTY;
}
if let Some(i) = self.free_expanded.pop()
&& let Some(slot) = self.expanded.get_mut(i as usize)
{
slot.push_str(text);
return Chars::Expanded(i);
}
let id = u32::try_from(self.expanded.len())
.expect("document exceeds u32::MAX texts");
self.expanded.push(String::from(text));
Chars::Expanded(id)
}
pub(super) fn replace_text(&mut self, old: Chars, text: &str) -> Chars {
if let Chars::Expanded(i) = old
&& let Some(slot) = self.expanded.get_mut(i as usize)
{
slot.clear();
slot.push_str(text);
return old;
}
self.store_text(text)
}
pub(super) fn release_chars(&mut self, chars: Chars) {
if let Chars::Expanded(i) = chars
&& let Some(slot) = self.expanded.get_mut(i as usize)
{
*slot = String::new();
self.free_expanded.push(i);
}
}
pub(super) fn release_node_text(&mut self, index: usize) {
let held = match self.nodes.get(index).map(|n| &n.data) {
Some(NodeData::Attr { value, .. }) => [Some(*value), None],
Some(NodeData::Text(c) | NodeData::Comment(c)) => [Some(*c), None],
Some(NodeData::Namespace { prefix, uri }) => {
[Some(*prefix), Some(*uri)]
}
Some(NodeData::ProcessingInstruction { target, data }) => {
[Some(*target), Some(*data)]
}
_ => [None, None],
};
for chars in held.into_iter().flatten() {
self.release_chars(chars);
}
}
}