use std::collections::HashSet;
use roxmltree::{Document, Node, NodeId};
pub enum TransformData<'a> {
NodeSet(NodeSet<'a>),
Binary(Vec<u8>),
}
impl std::fmt::Debug for TransformData<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NodeSet(_) => f.debug_tuple("NodeSet").field(&"...").finish(),
Self::Binary(b) => f.debug_tuple("Binary").field(&b.len()).finish(),
}
}
}
impl<'a> TransformData<'a> {
pub fn into_node_set(self) -> Result<NodeSet<'a>, TransformError> {
match self {
Self::NodeSet(ns) => Ok(ns),
Self::Binary(_) => Err(TransformError::TypeMismatch {
expected: "NodeSet",
got: "Binary",
}),
}
}
pub fn into_binary(self) -> Result<Vec<u8>, TransformError> {
match self {
Self::Binary(b) => Ok(b),
Self::NodeSet(_) => Err(TransformError::TypeMismatch {
expected: "Binary",
got: "NodeSet",
}),
}
}
}
pub struct NodeSet<'a> {
doc: &'a Document<'a>,
included: Option<HashSet<NodeId>>,
excluded: HashSet<NodeId>,
with_comments: bool,
}
impl<'a> NodeSet<'a> {
pub fn entire_document_without_comments(doc: &'a Document<'a>) -> Self {
Self {
doc,
included: None,
excluded: HashSet::new(),
with_comments: false,
}
}
pub fn entire_document_with_comments(doc: &'a Document<'a>) -> Self {
Self {
doc,
included: None,
excluded: HashSet::new(),
with_comments: true,
}
}
pub fn subtree(element: Node<'a, 'a>) -> Self {
let mut ids = HashSet::new();
collect_subtree_ids(element, &mut ids);
Self {
doc: element.document(),
included: Some(ids),
excluded: HashSet::new(),
with_comments: true,
}
}
pub fn document(&self) -> &'a Document<'a> {
self.doc
}
pub fn contains(&self, node: Node<'_, '_>) -> bool {
if !std::ptr::eq(node.document() as *const _, self.doc as *const _) {
return false;
}
let id = node.id();
if self.excluded.contains(&id) {
return false;
}
if !self.with_comments && node.is_comment() {
return false;
}
match &self.included {
None => true,
Some(ids) => ids.contains(&id),
}
}
pub fn exclude_subtree(&mut self, node: Node<'_, '_>) {
if !std::ptr::eq(node.document() as *const _, self.doc as *const _) {
return;
}
collect_subtree_ids(node, &mut self.excluded);
}
pub fn with_comments(&self) -> bool {
self.with_comments
}
}
fn collect_subtree_ids(node: Node<'_, '_>, ids: &mut HashSet<NodeId>) {
let mut stack = vec![node];
while let Some(current) = stack.pop() {
ids.insert(current.id());
for child in current.children() {
stack.push(child);
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TransformError {
#[error("type mismatch: expected {expected}, got {got}")]
TypeMismatch {
expected: &'static str,
got: &'static str,
},
#[error("element not found by ID: {0}")]
ElementNotFound(String),
#[error("unsupported URI: {0}")]
UnsupportedUri(String),
#[error("unsupported transform: {0}")]
UnsupportedTransform(String),
#[error("C14N error: {0}")]
C14n(#[from] crate::c14n::C14nError),
#[error("enveloped-signature transform: invalid Signature node for this document")]
CrossDocumentSignatureNode,
}