use crate::document::{DocumentView, NodeIdentity, XmlDocumentError};
use structured_asn1::memory::{MemoryError, Work};
#[derive(Debug, thiserror::Error)]
pub enum TreeError {
#[error(transparent)]
Document(#[from] XmlDocumentError),
#[error("XML node is not a direct child of the selected parent")]
NotChild,
#[error("XML binary value owner is not an element")]
NotElement,
#[error("malformed XML base64 content: {0}")]
Base64(&'static str),
#[error("XML tree resource failure: {0}")]
Resource(MemoryError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeView<'a> {
Element {
namespace: Option<&'a str>,
local_name: &'a str,
},
Text(&'a str),
Other,
}
fn charge<W: Work>(work: &W, units: usize) -> Result<(), TreeError> {
let units = u64::try_from(units).map_err(|_| TreeError::Resource(MemoryError::WorkExceeded))?;
work.charge_work(units).map_err(TreeError::Resource)
}
pub fn inspect_node<W: Work, R>(
view: DocumentView<'_>,
node: NodeIdentity,
work: &W,
visitor: impl for<'a> FnOnce(NodeView<'a>) -> R,
) -> Result<R, TreeError> {
charge(work, 1)?;
let node = view.resolve_node(node)?;
Ok(visitor(if node.is_element() {
let name = node.tag_name();
NodeView::Element {
namespace: name.namespace(),
local_name: name.name(),
}
} else if node.is_text() {
NodeView::Text(node.text().unwrap_or_default())
} else {
NodeView::Other
}))
}
pub fn first_child<W: Work>(
view: DocumentView<'_>,
parent: NodeIdentity,
work: &W,
) -> Result<Option<NodeIdentity>, TreeError> {
charge(work, 1)?;
let parent = view.resolve_node(parent)?;
Ok(parent.first_child().map(|child| view.node_identity(child)))
}
pub fn next_sibling<W: Work>(
view: DocumentView<'_>,
parent: NodeIdentity,
node: NodeIdentity,
work: &W,
) -> Result<Option<NodeIdentity>, TreeError> {
charge(work, 1)?;
let parent = view.resolve_node(parent)?;
let node = view.resolve_node(node)?;
if node.parent() != Some(parent) {
return Err(TreeError::NotChild);
}
Ok(node
.following_sibling()
.map(|sibling| view.node_identity(sibling)))
}
pub fn subtree_contains<W: Work>(
view: DocumentView<'_>,
ancestor: NodeIdentity,
node: NodeIdentity,
work: &W,
) -> Result<bool, TreeError> {
charge(work, 1)?;
let ancestor = view.resolve_node(ancestor)?;
let node = view.resolve_node(node)?;
Ok(crate::xmldsig::types::subtree_node_id_range(ancestor).contains(&node.id().get()))
}
pub fn with_base64<W: Work, R>(
view: DocumentView<'_>,
owner: NodeIdentity,
work: &W,
visitor: impl for<'a> FnOnce(&'a [u8]) -> R,
) -> Result<R, TreeError> {
charge(work, 1)?;
let node = view.resolve_node(owner)?;
if !node.is_element() {
return Err(TreeError::NotElement);
}
let mut text = 0usize;
for child in node.children() {
charge(work, 1)?;
if child.is_text() {
text = text
.checked_add(child.text().map_or(0, str::len))
.ok_or(TreeError::Resource(MemoryError::WorkExceeded))?;
}
}
charge(
work,
text.checked_mul(2)
.ok_or(TreeError::Resource(MemoryError::WorkExceeded))?,
)?;
let payload =
crate::xmldsig::whitespace::XmlBase64Payload::new(node).map_err(TreeError::Base64)?;
let mut decoded = work.vector();
decoded
.try_reserve_exact(payload.decoded_len)
.map_err(TreeError::Resource)?;
const ZEROS: [u8; 512] = [0; 512];
let mut remaining = payload.decoded_len;
while remaining != 0 {
let chunk = remaining.min(ZEROS.len());
decoded
.try_extend_from_slice(&ZEROS[..chunk])
.map_err(TreeError::Resource)?;
remaining -= chunk;
}
payload
.decode_into(&mut decoded)
.map_err(TreeError::Base64)?;
Ok(visitor(&decoded))
}