use structured_asn1::memory::{HashWork, MemoryError, Work};
use structured_ltv::source::{Query, ScopeError};
pub use structured_ltv::source::Snapshot;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Name<'a> {
pub namespace: &'a str,
pub local: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Node<'a> {
Element(Name<'a>),
Text(&'a str),
Other,
}
pub trait Xml {
type Id: Copy + Eq + HashWork;
type Document: Copy + Eq;
type Generation: Copy + Eq;
type Error;
fn snapshot(&self) -> Snapshot<Self::Document, Self::Generation>;
fn inspect<R, W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
node: Self::Id,
execution: &W,
visitor: impl for<'view> FnOnce(Node<'view>) -> R,
) -> Result<R, Self::Error>;
fn attribute<R, W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
node: Self::Id,
name: Name<'_>,
execution: &W,
visitor: impl for<'view> FnOnce(Option<&'view str>) -> R,
) -> Result<R, Self::Error>;
fn first_child<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
parent: Self::Id,
execution: &W,
) -> Result<Option<Self::Id>, Self::Error>;
fn next_sibling<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
parent: Self::Id,
node: Self::Id,
execution: &W,
) -> Result<Option<Self::Id>, Self::Error>;
}
#[derive(Debug)]
pub enum AccessError<E, N> {
Xml(E),
Resource {
node: N,
cause: MemoryError,
},
Changed {
node: N,
},
}
pub type AccessResult<T, X> = Result<T, AccessError<<X as Xml>::Error, <X as Xml>::Id>>;
pub type VisitResult<T, X, E> =
Result<T, AccessError<crate::reference::VisitError<<X as Xml>::Error, E>, <X as Xml>::Id>>;
#[derive(Debug)]
pub enum ChildError<E, N> {
Access(AccessError<E, N>),
Text {
node: N,
},
}
pub struct Access<'a, X: Xml, W: Work> {
xml: &'a X,
snapshot: Snapshot<X::Document, X::Generation>,
execution: &'a W,
}
impl<'a, X: Xml, W: Work> Access<'a, X, W> {
#[must_use]
pub fn new(
xml: &'a X,
snapshot: Snapshot<X::Document, X::Generation>,
execution: &'a W,
) -> Self {
Self {
xml,
snapshot,
execution,
}
}
#[must_use]
pub fn snapshot(&self) -> Snapshot<X::Document, X::Generation> {
self.snapshot
}
#[must_use]
pub fn execution(&self) -> &W {
self.execution
}
pub(crate) fn owner(&self) -> &'a X {
self.xml
}
#[must_use]
pub fn children(&self, parent: X::Id) -> Children<'_, 'a, X, W> {
Children {
access: self,
parent,
previous: None,
started: false,
done: false,
}
}
fn unchanged<E>(&self, node: X::Id) -> Result<(), AccessError<E, X::Id>> {
if self.xml.snapshot() != self.snapshot {
return Err(AccessError::Changed { node });
}
Ok(())
}
fn call<R, E>(
&self,
node: X::Id,
callback: impl FnOnce() -> Result<R, E>,
) -> Result<R, AccessError<E, X::Id>> {
self.unchanged(node)?;
self.execution
.charge_work(1)
.map_err(|cause| AccessError::Resource { node, cause })?;
let result = callback().map_err(AccessError::Xml)?;
self.unchanged(node)?;
Ok(result)
}
pub(crate) fn query<R>(
&self,
node: X::Id,
callback: impl FnOnce(&X, Snapshot<X::Document, X::Generation>, &W) -> Result<R, X::Error>,
) -> AccessResult<R, X> {
self.call(node, || callback(self.xml, self.snapshot, self.execution))
}
pub(crate) fn query_fallible<R, E>(
&self,
node: X::Id,
callback: impl FnOnce(&X, Snapshot<X::Document, X::Generation>, &W) -> Result<R, E>,
) -> Result<R, AccessError<E, X::Id>> {
self.call(node, || callback(self.xml, self.snapshot, self.execution))
}
pub(crate) fn equal(&self, left: &str, right: &str) -> Result<bool, MemoryError> {
equal_text(left, right, self.execution)
}
pub(crate) fn equal_id(&self, left: X::Id, right: X::Id) -> Result<bool, MemoryError> {
equal_id(left, right, self.execution)
}
pub(crate) fn trim_xml_whitespace<'value>(
&self,
value: &'value str,
) -> Result<&'value str, MemoryError> {
self.execution
.charge_work(u64::try_from(value.len()).map_err(|_| MemoryError::WorkExceeded)?)?;
Ok(value.trim_matches([' ', '\t', '\r', '\n']))
}
pub fn inspect<R>(
&self,
node: X::Id,
visitor: impl for<'view> FnOnce(Node<'view>) -> R,
) -> AccessResult<R, X> {
self.call(node, || {
self.xml
.inspect(self.snapshot, node, self.execution, visitor)
})
}
pub fn has_name(&self, node: X::Id, expected: Name<'_>) -> AccessResult<bool, X> {
let compared = self.inspect(node, |value| {
let Node::Element(actual) = value else {
return Ok(false);
};
if !equal_text(actual.namespace, expected.namespace, self.execution)? {
return Ok(false);
}
equal_text(actual.local, expected.local, self.execution)
})?;
compared.map_err(|cause| AccessError::Resource { node, cause })
}
pub fn attribute<R>(
&self,
node: X::Id,
name: Name<'_>,
visitor: impl for<'view> FnOnce(Option<&'view str>) -> R,
) -> AccessResult<R, X> {
self.call(node, || {
self.xml
.attribute(self.snapshot, node, name, self.execution, visitor)
})
}
pub fn first_child(&self, parent: X::Id) -> AccessResult<Option<X::Id>, X> {
self.call(parent, || {
self.xml.first_child(self.snapshot, parent, self.execution)
})
}
pub fn next_sibling(&self, parent: X::Id, node: X::Id) -> AccessResult<Option<X::Id>, X> {
self.call(node, || {
self.xml
.next_sibling(self.snapshot, parent, node, self.execution)
})
}
}
impl<X: Xml, W: Work> Query for Access<'_, X, W> {
type Id = X::Id;
type Document = X::Document;
type Generation = X::Generation;
type Work = W;
fn snapshot(&self) -> Snapshot<X::Document, X::Generation> {
self.snapshot
}
fn execution(&self) -> &W {
self.execution
}
fn admit(&self, node: X::Id, units: u64) -> Result<(), ScopeError<X::Id>> {
let changed = |_: AccessError<(), X::Id>| ScopeError::Changed { node };
self.unchanged(node).map_err(changed)?;
self.execution
.charge_work(units)
.map_err(|cause| ScopeError::Resource { node, cause })?;
self.unchanged(node).map_err(changed)
}
}
impl<E, N> From<ScopeError<N>> for AccessError<E, N> {
fn from(cause: ScopeError<N>) -> Self {
match cause {
ScopeError::Changed { node } => Self::Changed { node },
ScopeError::Resource { node, cause } => Self::Resource { node, cause },
}
}
}
pub(crate) fn equal_id<N: Copy + HashWork, W: Work>(
left: N,
right: N,
execution: &W,
) -> Result<bool, MemoryError> {
let cost = left
.hash_work()?
.checked_add(right.hash_work()?)
.ok_or(MemoryError::WorkExceeded)?;
execution.charge_work(cost)?;
Ok(left == right)
}
pub struct Children<'a, 'xml, X: Xml, W: Work> {
access: &'a Access<'xml, X, W>,
parent: X::Id,
previous: Option<X::Id>,
started: bool,
done: bool,
}
impl<X: Xml, W: Work> Iterator for Children<'_, '_, X, W> {
type Item = Result<X::Id, ChildError<X::Error, X::Id>>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
loop {
let advanced = if self.started {
let Some(previous) = self.previous else {
self.done = true;
return None;
};
self.access.next_sibling(self.parent, previous)
} else {
self.started = true;
self.access.first_child(self.parent)
};
let node = match advanced {
Ok(Some(node)) => node,
Ok(None) => {
self.done = true;
return None;
}
Err(error) => {
self.done = true;
return Some(Err(ChildError::Access(error)));
}
};
self.previous = Some(node);
let inspected = self.access.inspect(node, |value| match value {
Node::Element(_) => Ok(Some(true)),
Node::Other => Ok(None),
Node::Text(text) => {
let length =
u64::try_from(text.len()).map_err(|_| MemoryError::WorkExceeded)?;
self.access.execution.charge_work(length)?;
for byte in text.bytes() {
if !matches!(byte, b' ' | b'\t' | b'\r' | b'\n') {
return Ok(Some(false));
}
}
Ok(None)
}
});
match inspected {
Ok(Ok(Some(true))) => return Some(Ok(node)),
Ok(Ok(None)) => {}
Ok(Ok(Some(false))) => {
self.done = true;
return Some(Err(ChildError::Text { node }));
}
Ok(Err(cause)) => {
self.done = true;
return Some(Err(ChildError::Access(AccessError::Resource {
node,
cause,
})));
}
Err(error) => {
self.done = true;
return Some(Err(ChildError::Access(error)));
}
}
}
}
}
impl<X: Xml, W: Work> core::iter::FusedIterator for Children<'_, '_, X, W> {}
fn equal_text<W: Work>(left: &str, right: &str, execution: &W) -> Result<bool, MemoryError> {
if left.len() != right.len() {
return Ok(false);
}
let length = u64::try_from(left.len()).map_err(|_| MemoryError::WorkExceeded)?;
execution.charge_work(length)?;
Ok(left == right)
}