#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
mod differential;
mod preflight;
#[cfg(feature = "xml-backend-roxmltree")]
mod roxmltree;
mod tree;
#[cfg(feature = "xml-backend-xmloxide")]
mod xmloxide;
use std::fmt;
use self::preflight::LexicalPreflight;
pub use tree::{
Ancestors, Attribute, Attributes, Children, Descendants, Document, ExpandedName, Namespace,
Namespaces, Node, NodeId, NodeType, PI,
};
#[derive(Clone, Copy, Debug)]
pub struct ParsingOptions {
pub allow_dtd: bool,
pub nodes_limit: u32,
}
impl Default for ParsingOptions {
fn default() -> Self {
Self {
allow_dtd: false,
nodes_limit: u32::MAX,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum XmlBackend {
Xmloxide,
Roxmltree,
Differential,
}
impl XmlBackend {
pub(crate) const fn build_default() -> Self {
if cfg!(feature = "xml-backend-differential") {
Self::Differential
} else if cfg!(feature = "xml-backend-xmloxide") {
Self::Xmloxide
} else {
Self::Roxmltree
}
}
#[must_use]
pub const fn is_available(self) -> bool {
match self {
Self::Xmloxide => cfg!(feature = "xml-backend-xmloxide"),
Self::Roxmltree => cfg!(feature = "xml-backend-roxmltree"),
Self::Differential => cfg!(all(
feature = "xml-backend-xmloxide",
feature = "xml-backend-roxmltree"
)),
}
}
pub fn available() -> impl Iterator<Item = Self> {
[Self::Xmloxide, Self::Roxmltree, Self::Differential]
.into_iter()
.filter(|backend| backend.is_available())
}
fn parse<'input>(
self,
input: &'input str,
options: ParsingOptions,
preflight: &LexicalPreflight,
) -> Result<Document<'input>, ParseError> {
match self {
Self::Xmloxide => parse_with_xmloxide(input, options, preflight),
Self::Roxmltree => parse_with_roxmltree(input, options, preflight),
Self::Differential => parse_differentially(input, options, preflight),
}
}
}
fn parse_with_xmloxide<'input>(
input: &'input str,
options: ParsingOptions,
preflight: &LexicalPreflight,
) -> Result<Document<'input>, ParseError> {
#[cfg(feature = "xml-backend-xmloxide")]
return xmloxide::XmloxideBackend::parse(input, options, preflight);
#[cfg(not(feature = "xml-backend-xmloxide"))]
{
let _ = (input, options, preflight);
Err(ParseError::BackendUnavailable {
backend: XmlBackend::Xmloxide,
})
}
}
fn parse_with_roxmltree<'input>(
input: &'input str,
options: ParsingOptions,
preflight: &LexicalPreflight,
) -> Result<Document<'input>, ParseError> {
#[cfg(feature = "xml-backend-roxmltree")]
return roxmltree::RoxmltreeBackend::parse(input, options, preflight);
#[cfg(not(feature = "xml-backend-roxmltree"))]
{
let _ = (input, options, preflight);
Err(ParseError::BackendUnavailable {
backend: XmlBackend::Roxmltree,
})
}
}
fn parse_differentially<'input>(
input: &'input str,
options: ParsingOptions,
preflight: &LexicalPreflight,
) -> Result<Document<'input>, ParseError> {
#[cfg(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree"))]
return differential::DifferentialBackend::parse(input, options, preflight);
#[cfg(not(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree")))]
{
let _ = (input, options, preflight);
Err(ParseError::BackendUnavailable {
backend: XmlBackend::Differential,
})
}
}
impl Default for XmlBackend {
fn default() -> Self {
Self::build_default()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
BackendUnavailable {
backend: XmlBackend,
},
ByteLimitReached {
maximum: usize,
actual: usize,
},
DtdDetected,
NodesLimitReached,
EntityExpansionLimitReached {
maximum: u32,
actual: u32,
},
EntityExpansionWorkLimitReached {
maximum: usize,
actual: usize,
},
SourcePositionLimitReached {
maximum: usize,
actual: usize,
},
DepthLimitReached {
maximum: usize,
actual: usize,
},
Backend {
backend: &'static str,
message: String,
},
BackendDivergence {
reason: String,
},
}
impl fmt::Display for ParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BackendUnavailable { backend } => {
write!(
formatter,
"XML backend {backend:?} is not compiled into this build"
)
}
Self::ByteLimitReached { maximum, actual } => {
write!(
formatter,
"XML byte limit reached: maximum {maximum}, actual {actual}"
)
}
Self::DtdDetected => formatter.write_str("DTD detected"),
Self::NodesLimitReached => formatter.write_str("nodes limit reached"),
Self::EntityExpansionLimitReached { maximum, actual } => write!(
formatter,
"XML entity expansion limit {maximum} exceeded at expansion {actual}"
),
Self::EntityExpansionWorkLimitReached { maximum, actual } => write!(
formatter,
"XML entity expansion-work limit {maximum} bytes exceeded at {actual} bytes"
),
Self::SourcePositionLimitReached { maximum, actual } => write!(
formatter,
"XML source-position limit {maximum} exceeded at position {actual}"
),
Self::DepthLimitReached { maximum, actual } => {
write!(
formatter,
"XML depth limit {maximum} exceeded at depth {actual}"
)
}
Self::Backend { backend, message } => {
write!(formatter, "{backend} rejected XML: {message}")
}
Self::BackendDivergence { reason } => {
write!(formatter, "XML backend semantic divergence: {reason}")
}
}
}
}
impl std::error::Error for ParseError {}
trait XmlBackendImplementation {
fn parse<'input>(
input: &'input str,
options: ParsingOptions,
preflight: &LexicalPreflight,
) -> Result<Document<'input>, ParseError>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct SemanticNodeId(u32);
impl SemanticNodeId {
pub(super) const fn new(raw: u32) -> Self {
Self(raw)
}
pub(super) const fn raw(self) -> u32 {
self.0
}
}
pub(crate) trait SemanticDocument {
type Node<'a>: Copy
where
Self: 'a;
fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>>;
fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId;
}
impl SemanticDocument for Document<'_> {
type Node<'a>
= Node<'a, 'a>
where
Self: 'a;
fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>> {
self.get_node(NodeId::from(id.raw()))
}
fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId {
SemanticNodeId::new(node.id().get())
}
}