#![deny(rust_2018_idioms)]
#[cfg(not(any(feature = "no-unsafe", feature = "raw-pointer-backend")))]
compile_error!("select either `no-unsafe` or `raw-pointer-backend`");
#[cfg(not(feature = "embedded"))]
#[macro_use]
extern crate peresil;
use std::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
};
static NEXT_PACKAGE_IDENTITY: AtomicUsize = AtomicUsize::new(1);
fn next_package_identity() -> usize {
allocate_package_identity(&NEXT_PACKAGE_IDENTITY)
}
fn allocate_package_identity(counter: &AtomicUsize) -> usize {
let mut current = counter.load(Ordering::Relaxed);
loop {
let next = current
.checked_add(1)
.expect("document identity space exhausted");
match counter.compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => return current,
Err(observed) => current = observed,
}
}
}
#[cfg(test)]
mod identity_tests {
use super::*;
#[test]
fn exhausted_identity_space_does_not_wrap() {
let counter = AtomicUsize::new(usize::MAX);
let result = std::panic::catch_unwind(|| allocate_package_identity(&counter));
assert!(result.is_err());
assert_eq!(counter.load(Ordering::Relaxed), usize::MAX);
}
#[test]
fn concurrent_allocations_remain_unique() {
let counter = AtomicUsize::new(1);
let identities = std::thread::scope(|scope| {
let workers: Vec<_> = (0..8)
.map(|_| {
scope.spawn(|| {
(0..128)
.map(|_| allocate_package_identity(&counter))
.collect::<Vec<_>>()
})
})
.collect();
workers
.into_iter()
.flat_map(|worker| worker.join().unwrap())
.collect::<Vec<_>>()
});
let unique: std::collections::HashSet<_> = identities.iter().copied().collect();
assert_eq!(unique.len(), 8 * 128);
assert_eq!(counter.load(Ordering::Relaxed), 1 + 8 * 128);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StorageRequirements {
pub roots: usize,
pub elements: usize,
pub attributes: usize,
pub texts: usize,
pub comments: usize,
pub processing_instructions: usize,
pub child_edges: usize,
pub namespace_bindings: usize,
pub interned_strings: usize,
pub string_bytes: usize,
}
#[must_use]
pub fn estimated_storage_bytes(requirements: StorageRequirements) -> usize {
raw::estimated_storage_bytes(requirements)
}
mod lazy_hash_map;
#[cfg(feature = "no-unsafe")]
mod namespace_storage;
pub(crate) mod str;
mod str_ext;
#[cfg(not(feature = "no-unsafe"))]
pub mod dom;
#[cfg(not(feature = "no-unsafe"))]
mod raw;
#[cfg(not(feature = "no-unsafe"))]
pub(crate) mod string_pool;
#[cfg(not(feature = "no-unsafe"))]
#[doc(hidden)]
pub mod thindom;
#[cfg(not(feature = "no-unsafe"))]
pub mod writer;
#[cfg(feature = "no-unsafe")]
#[path = "raw_no_unsafe.rs"]
mod raw;
#[cfg(feature = "no-unsafe")]
mod storage;
#[cfg(feature = "no-unsafe")]
#[path = "string_pool_no_unsafe.rs"]
pub(crate) mod string_pool;
#[cfg(feature = "no-unsafe")]
pub use string_pool::{AccountedStringPool, InternedString};
#[cfg(feature = "no-unsafe")]
#[path = "dom_no_unsafe.rs"]
pub mod dom;
#[cfg(feature = "no-unsafe")]
#[path = "thindom_no_unsafe.rs"]
#[doc(hidden)]
pub mod thindom;
#[cfg(feature = "no-unsafe")]
#[path = "writer_no_unsafe.rs"]
pub mod writer;
pub mod parser;
#[cfg(not(feature = "no-unsafe"))]
#[cfg(feature = "__internal_expose_string_pool")]
pub mod __internal {
pub use super::string_pool::StringPool;
}
#[cfg(feature = "no-unsafe")]
#[cfg(feature = "__internal_expose_string_pool")]
pub mod __internal {
pub use super::string_pool::StringPool;
}
pub use crate::str::XmlChar;
#[cfg(feature = "embedded")]
pub use crate::{as_opt_str, as_qname, as_str, to_ns_str};
#[cfg(not(feature = "no-unsafe"))]
#[macro_export]
macro_rules! as_str {
($e:expr) => {
$e
};
}
#[cfg(not(feature = "no-unsafe"))]
#[macro_export]
macro_rules! as_opt_str {
($e:expr) => {
$e
};
}
#[cfg(not(feature = "no-unsafe"))]
#[macro_export]
macro_rules! as_qname {
($e:expr) => {
$e
};
}
#[cfg(feature = "no-unsafe")]
#[macro_export]
macro_rules! as_str {
($e:expr) => {
&*$e
};
}
#[cfg(feature = "no-unsafe")]
#[macro_export]
macro_rules! as_opt_str {
($e:expr) => {
$e.as_deref()
};
}
#[cfg(feature = "no-unsafe")]
#[macro_export]
macro_rules! as_qname {
($e:expr) => {
$e.get()
};
}
#[cfg(not(feature = "no-unsafe"))]
#[macro_export]
macro_rules! to_ns_str {
($e:expr) => {
$e
};
}
#[cfg(feature = "no-unsafe")]
#[macro_export]
macro_rules! to_ns_str {
($e:expr) => {
($e).to_owned().into()
};
}
#[cfg(not(feature = "no-unsafe"))]
pub type NsStr<'d> = &'d str;
#[cfg(all(feature = "no-unsafe", not(feature = "embedded")))]
pub type NsStr<'d> = String;
#[cfg(feature = "embedded")]
pub type NsStr<'d> = NamespaceText;
#[cfg(feature = "embedded")]
#[derive(Debug, Clone)]
pub enum NamespaceText {
Native(String),
Current(structured_asn1::memory::SharedString),
Static(&'static str),
}
#[cfg(feature = "embedded")]
impl NamespaceText {
pub(crate) fn can_share(&self) -> bool {
matches!(self, Self::Current(_) | Self::Static(_))
}
}
#[cfg(feature = "embedded")]
impl From<String> for NamespaceText {
fn from(value: String) -> Self {
Self::Native(value)
}
}
#[cfg(feature = "embedded")]
impl From<&str> for NamespaceText {
fn from(value: &str) -> Self {
Self::Native(value.to_owned())
}
}
#[cfg(feature = "embedded")]
impl std::ops::Deref for NamespaceText {
type Target = str;
fn deref(&self) -> &str {
match self {
Self::Native(s) => s,
Self::Current(s) => s,
Self::Static(s) => s,
}
}
}
#[cfg(feature = "embedded")]
impl AsRef<str> for NamespaceText {
fn as_ref(&self) -> &str {
self
}
}
#[cfg(feature = "embedded")]
impl PartialEq for NamespaceText {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
#[cfg(feature = "embedded")]
impl Eq for NamespaceText {}
#[cfg(feature = "embedded")]
impl std::borrow::Borrow<str> for NamespaceText {
fn borrow(&self) -> &str {
self
}
}
#[cfg(feature = "embedded")]
impl structured_asn1::memory::HashWork for NamespaceText {
fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
structured_asn1::memory::HashWork::hash_work(&**self)
}
}
#[cfg(feature = "embedded")]
impl std::hash::Hash for NamespaceText {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&**self, state);
}
}
pub(crate) static XML_NS_PREFIX: &str = "xml";
pub(crate) static XML_NS_URI: &str = "http://www.w3.org/XML/1998/namespace";
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PrefixedName<'a> {
prefix: Option<&'a str>,
local_part: &'a str,
}
impl<'a> PrefixedName<'a> {
pub fn new(local_part: &str) -> PrefixedName<'_> {
PrefixedName::with_prefix(None, local_part)
}
pub fn with_prefix(prefix: Option<&'a str>, local_part: &'a str) -> PrefixedName<'a> {
PrefixedName { prefix, local_part }
}
pub fn prefix(&self) -> Option<&'a str> {
self.prefix
}
pub fn local_part(&self) -> &'a str {
self.local_part
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct QName<'s> {
namespace_uri: Option<&'s str>,
local_part: &'s str,
}
impl<'s> QName<'s> {
pub fn new(local_part: &'s str) -> QName<'s> {
QName::with_namespace_uri(None, local_part)
}
pub fn with_namespace_uri(namespace_uri: Option<&'s str>, local_part: &'s str) -> QName<'s> {
QName {
namespace_uri,
local_part,
}
}
pub fn namespace_uri(&self) -> Option<&'s str> {
self.namespace_uri
}
pub fn local_part(&self) -> &'s str {
self.local_part
}
}
impl<'s> From<(&'s str, &'s str)> for QName<'s> {
fn from(v: (&'s str, &'s str)) -> QName<'s> {
QName {
namespace_uri: Some(v.0),
local_part: v.1,
}
}
}
impl<'s> From<&'s str> for QName<'s> {
fn from(v: &'s str) -> QName<'s> {
QName {
namespace_uri: None,
local_part: v,
}
}
}
pub struct Package {
identity: usize,
storage: raw::Storage,
connections: raw::Connections,
}
impl Default for Package {
fn default() -> Package {
let s = raw::Storage::new();
let root = s.create_root();
Package {
identity: next_package_identity(),
storage: s,
connections: raw::Connections::new(root),
}
}
}
impl Package {
pub fn new() -> Package {
Self::default()
}
#[cfg(feature = "embedded")]
pub(crate) fn try_current(
work: &impl structured_asn1::memory::Work,
) -> Result<Self, structured_asn1::memory::MemoryError> {
let (storage, root) = raw::current::storage(work)?;
Ok(Self {
identity: next_package_identity(),
storage,
connections: raw::Connections::new(root),
})
}
pub fn as_document(&self) -> dom::Document<'_> {
dom::Document::new(self.identity, &self.storage, &self.connections)
}
#[doc(hidden)]
pub fn as_thin_document(&self) -> (thindom::Storage<'_>, thindom::Connections<'_>) {
let identity = std::ptr::from_ref(self).addr();
let s = thindom::Storage::new(&self.storage, identity);
#[cfg(not(feature = "no-unsafe"))]
let c = thindom::Connections::new(&self.connections, identity);
#[cfg(feature = "no-unsafe")]
let c = thindom::Connections::new(&self.connections, &self.storage, identity);
(s, c)
}
}
impl PartialEq for Package {
fn eq(&self, other: &Package) -> bool {
std::ptr::eq(self, other)
}
}
impl fmt::Debug for Package {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Package")
}
}