1#![deny(rust_2018_idioms)]
55
56#[cfg(not(any(feature = "no-unsafe", feature = "raw-pointer-backend")))]
57compile_error!("select either `no-unsafe` or `raw-pointer-backend`");
58
59#[cfg(not(feature = "embedded"))]
63#[macro_use]
64extern crate peresil;
65
66use std::{
67 fmt,
68 sync::atomic::{AtomicUsize, Ordering},
69};
70
71static NEXT_PACKAGE_IDENTITY: AtomicUsize = AtomicUsize::new(1);
72
73fn next_package_identity() -> usize {
74 allocate_package_identity(&NEXT_PACKAGE_IDENTITY)
75}
76
77fn allocate_package_identity(counter: &AtomicUsize) -> usize {
78 let mut current = counter.load(Ordering::Relaxed);
79 loop {
80 let next = current
81 .checked_add(1)
82 .expect("document identity space exhausted");
83 match counter.compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed) {
84 Ok(_) => return current,
85 Err(observed) => current = observed,
86 }
87 }
88}
89
90#[cfg(test)]
91mod identity_tests {
92 use super::*;
93
94 #[test]
95 fn exhausted_identity_space_does_not_wrap() {
96 let counter = AtomicUsize::new(usize::MAX);
97 let result = std::panic::catch_unwind(|| allocate_package_identity(&counter));
98 assert!(result.is_err());
99 assert_eq!(counter.load(Ordering::Relaxed), usize::MAX);
100 }
101
102 #[test]
103 fn concurrent_allocations_remain_unique() {
104 let counter = AtomicUsize::new(1);
105 let identities = std::thread::scope(|scope| {
106 let workers: Vec<_> = (0..8)
107 .map(|_| {
108 scope.spawn(|| {
109 (0..128)
110 .map(|_| allocate_package_identity(&counter))
111 .collect::<Vec<_>>()
112 })
113 })
114 .collect();
115 workers
116 .into_iter()
117 .flat_map(|worker| worker.join().unwrap())
118 .collect::<Vec<_>>()
119 });
120 let unique: std::collections::HashSet<_> = identities.iter().copied().collect();
121 assert_eq!(unique.len(), 8 * 128);
122 assert_eq!(counter.load(Ordering::Relaxed), 1 + 8 * 128);
123 }
124}
125
126#[derive(Debug, Clone, Copy, Default)]
128pub struct StorageRequirements {
129 pub roots: usize,
130 pub elements: usize,
131 pub attributes: usize,
132 pub texts: usize,
133 pub comments: usize,
134 pub processing_instructions: usize,
135 pub child_edges: usize,
136 pub namespace_bindings: usize,
137 pub interned_strings: usize,
138 pub string_bytes: usize,
139}
140
141#[must_use]
143pub fn estimated_storage_bytes(requirements: StorageRequirements) -> usize {
144 raw::estimated_storage_bytes(requirements)
145}
146
147mod lazy_hash_map;
148#[cfg(feature = "no-unsafe")]
149mod namespace_storage;
150pub(crate) mod str;
151mod str_ext;
152
153#[cfg(not(feature = "no-unsafe"))]
154pub mod dom;
155#[cfg(not(feature = "no-unsafe"))]
156mod raw;
157#[cfg(not(feature = "no-unsafe"))]
158pub(crate) mod string_pool;
159#[cfg(not(feature = "no-unsafe"))]
160#[doc(hidden)]
161pub mod thindom;
162#[cfg(not(feature = "no-unsafe"))]
163pub mod writer;
164
165#[cfg(feature = "no-unsafe")]
166#[path = "raw_no_unsafe.rs"]
167mod raw;
168#[cfg(feature = "no-unsafe")]
169mod storage;
170#[cfg(feature = "no-unsafe")]
171#[path = "string_pool_no_unsafe.rs"]
172pub(crate) mod string_pool;
173#[cfg(feature = "no-unsafe")]
174pub use string_pool::{AccountedStringPool, InternedString};
175#[cfg(feature = "no-unsafe")]
176#[path = "dom_no_unsafe.rs"]
177pub mod dom;
178#[cfg(feature = "no-unsafe")]
179#[path = "thindom_no_unsafe.rs"]
180#[doc(hidden)]
181pub mod thindom;
182#[cfg(feature = "no-unsafe")]
183#[path = "writer_no_unsafe.rs"]
184pub mod writer;
185
186pub mod parser;
187
188#[cfg(not(feature = "no-unsafe"))]
189#[cfg(feature = "__internal_expose_string_pool")]
190pub mod __internal {
191 pub use super::string_pool::StringPool;
192}
193
194#[cfg(feature = "no-unsafe")]
195#[cfg(feature = "__internal_expose_string_pool")]
196pub mod __internal {
197 pub use super::string_pool::StringPool;
198}
199
200pub use crate::str::XmlChar;
201#[cfg(feature = "embedded")]
202pub use crate::{as_opt_str, as_qname, as_str, to_ns_str};
203
204#[cfg(not(feature = "no-unsafe"))]
205#[macro_export]
206macro_rules! as_str {
207 ($e:expr) => {
208 $e
209 };
210}
211
212#[cfg(not(feature = "no-unsafe"))]
213#[macro_export]
214macro_rules! as_opt_str {
215 ($e:expr) => {
216 $e
217 };
218}
219
220#[cfg(not(feature = "no-unsafe"))]
221#[macro_export]
222macro_rules! as_qname {
223 ($e:expr) => {
224 $e
225 };
226}
227
228#[cfg(feature = "no-unsafe")]
229#[macro_export]
230macro_rules! as_str {
231 ($e:expr) => {
232 &*$e
233 };
234}
235
236#[cfg(feature = "no-unsafe")]
237#[macro_export]
238macro_rules! as_opt_str {
239 ($e:expr) => {
240 $e.as_deref()
241 };
242}
243
244#[cfg(feature = "no-unsafe")]
245#[macro_export]
246macro_rules! as_qname {
247 ($e:expr) => {
248 $e.get()
249 };
250}
251
252#[cfg(not(feature = "no-unsafe"))]
253#[macro_export]
254macro_rules! to_ns_str {
255 ($e:expr) => {
256 $e
257 };
258}
259
260#[cfg(feature = "no-unsafe")]
261#[macro_export]
262macro_rules! to_ns_str {
263 ($e:expr) => {
264 ($e).to_owned().into()
265 };
266}
267
268#[cfg(not(feature = "no-unsafe"))]
271pub type NsStr<'d> = &'d str;
272#[cfg(all(feature = "no-unsafe", not(feature = "embedded")))]
275pub type NsStr<'d> = String;
279
280#[cfg(feature = "embedded")]
281pub type NsStr<'d> = NamespaceText;
282
283#[cfg(feature = "embedded")]
285#[derive(Debug, Clone)]
286pub enum NamespaceText {
287 Native(String),
288 Current(structured_asn1::memory::SharedString),
289 Static(&'static str),
290}
291
292#[cfg(feature = "embedded")]
293impl NamespaceText {
294 pub(crate) fn can_share(&self) -> bool {
295 matches!(self, Self::Current(_) | Self::Static(_))
296 }
297}
298#[cfg(feature = "embedded")]
299impl From<String> for NamespaceText {
300 fn from(value: String) -> Self {
301 Self::Native(value)
302 }
303}
304#[cfg(feature = "embedded")]
305impl From<&str> for NamespaceText {
306 fn from(value: &str) -> Self {
307 Self::Native(value.to_owned())
308 }
309}
310#[cfg(feature = "embedded")]
311impl std::ops::Deref for NamespaceText {
312 type Target = str;
313 fn deref(&self) -> &str {
314 match self {
315 Self::Native(s) => s,
316 Self::Current(s) => s,
317 Self::Static(s) => s,
318 }
319 }
320}
321#[cfg(feature = "embedded")]
322impl AsRef<str> for NamespaceText {
323 fn as_ref(&self) -> &str {
324 self
325 }
326}
327#[cfg(feature = "embedded")]
328impl PartialEq for NamespaceText {
329 fn eq(&self, other: &Self) -> bool {
330 **self == **other
331 }
332}
333#[cfg(feature = "embedded")]
334impl Eq for NamespaceText {}
335#[cfg(feature = "embedded")]
336impl std::borrow::Borrow<str> for NamespaceText {
337 fn borrow(&self) -> &str {
338 self
339 }
340}
341#[cfg(feature = "embedded")]
342impl structured_asn1::memory::HashWork for NamespaceText {
343 fn hash_work(&self) -> Result<u64, structured_asn1::memory::MemoryError> {
344 structured_asn1::memory::HashWork::hash_work(&**self)
345 }
346}
347#[cfg(feature = "embedded")]
348impl std::hash::Hash for NamespaceText {
349 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
350 std::hash::Hash::hash(&**self, state);
351 }
352}
353
354pub(crate) static XML_NS_PREFIX: &str = "xml";
355pub(crate) static XML_NS_URI: &str = "http://www.w3.org/XML/1998/namespace";
356
357#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
360pub struct PrefixedName<'a> {
361 prefix: Option<&'a str>,
362 local_part: &'a str,
363}
364
365impl<'a> PrefixedName<'a> {
366 pub fn new(local_part: &str) -> PrefixedName<'_> {
368 PrefixedName::with_prefix(None, local_part)
369 }
370
371 pub fn with_prefix(prefix: Option<&'a str>, local_part: &'a str) -> PrefixedName<'a> {
373 PrefixedName { prefix, local_part }
374 }
375
376 pub fn prefix(&self) -> Option<&'a str> {
378 self.prefix
379 }
380 pub fn local_part(&self) -> &'a str {
382 self.local_part
383 }
384}
385
386#[derive(Debug, Copy, Clone, PartialEq)]
390pub struct QName<'s> {
391 namespace_uri: Option<&'s str>,
392 local_part: &'s str,
393}
394
395impl<'s> QName<'s> {
396 pub fn new(local_part: &'s str) -> QName<'s> {
398 QName::with_namespace_uri(None, local_part)
399 }
400
401 pub fn with_namespace_uri(namespace_uri: Option<&'s str>, local_part: &'s str) -> QName<'s> {
403 QName {
404 namespace_uri,
405 local_part,
406 }
407 }
408
409 pub fn namespace_uri(&self) -> Option<&'s str> {
410 self.namespace_uri
411 }
412 pub fn local_part(&self) -> &'s str {
413 self.local_part
414 }
415}
416
417impl<'s> From<(&'s str, &'s str)> for QName<'s> {
418 fn from(v: (&'s str, &'s str)) -> QName<'s> {
419 QName {
420 namespace_uri: Some(v.0),
421 local_part: v.1,
422 }
423 }
424}
425
426impl<'s> From<&'s str> for QName<'s> {
427 fn from(v: &'s str) -> QName<'s> {
428 QName {
429 namespace_uri: None,
430 local_part: v,
431 }
432 }
433}
434
435pub struct Package {
440 identity: usize,
441 storage: raw::Storage,
442 connections: raw::Connections,
443}
444
445impl Default for Package {
446 fn default() -> Package {
447 let s = raw::Storage::new();
448 let root = s.create_root();
449 Package {
450 identity: next_package_identity(),
451 storage: s,
452 connections: raw::Connections::new(root),
453 }
454 }
455}
456
457impl Package {
458 pub fn new() -> Package {
459 Self::default()
460 }
461
462 #[cfg(feature = "embedded")]
463 pub(crate) fn try_current(
464 work: &impl structured_asn1::memory::Work,
465 ) -> Result<Self, structured_asn1::memory::MemoryError> {
466 let (storage, root) = raw::current::storage(work)?;
467 Ok(Self {
468 identity: next_package_identity(),
469 storage,
470 connections: raw::Connections::new(root),
471 })
472 }
473
474 pub fn as_document(&self) -> dom::Document<'_> {
475 dom::Document::new(self.identity, &self.storage, &self.connections)
476 }
477
478 #[doc(hidden)]
479 pub fn as_thin_document(&self) -> (thindom::Storage<'_>, thindom::Connections<'_>) {
480 let identity = std::ptr::from_ref(self).addr();
483 let s = thindom::Storage::new(&self.storage, identity);
484 #[cfg(not(feature = "no-unsafe"))]
485 let c = thindom::Connections::new(&self.connections, identity);
486 #[cfg(feature = "no-unsafe")]
487 let c = thindom::Connections::new(&self.connections, &self.storage, identity);
488 (s, c)
489 }
490}
491
492impl PartialEq for Package {
493 fn eq(&self, other: &Package) -> bool {
494 std::ptr::eq(self, other)
495 }
496}
497
498impl fmt::Debug for Package {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 write!(f, "Package")
501 }
502}