xml_sec/sxd_document/
lib.rs1#![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;
148pub(crate) mod str;
149mod str_ext;
150
151#[cfg(not(feature = "no-unsafe"))]
152pub mod dom;
153#[cfg(not(feature = "no-unsafe"))]
154mod raw;
155#[cfg(not(feature = "no-unsafe"))]
156pub(crate) mod string_pool;
157#[cfg(not(feature = "no-unsafe"))]
158#[doc(hidden)]
159pub mod thindom;
160#[cfg(not(feature = "no-unsafe"))]
161pub mod writer;
162
163#[cfg(feature = "no-unsafe")]
164#[path = "raw_no_unsafe.rs"]
165mod raw;
166#[cfg(feature = "no-unsafe")]
167#[path = "string_pool_no_unsafe.rs"]
168pub(crate) mod string_pool;
169#[cfg(feature = "no-unsafe")]
170pub use string_pool::InternedString;
171#[cfg(feature = "no-unsafe")]
172#[path = "dom_no_unsafe.rs"]
173pub mod dom;
174#[cfg(feature = "no-unsafe")]
175#[path = "thindom_no_unsafe.rs"]
176#[doc(hidden)]
177pub mod thindom;
178#[cfg(feature = "no-unsafe")]
179#[path = "writer_no_unsafe.rs"]
180pub mod writer;
181
182pub mod parser;
183
184#[cfg(not(feature = "no-unsafe"))]
185#[cfg(feature = "__internal_expose_string_pool")]
186pub mod __internal {
187 pub use super::string_pool::StringPool;
188}
189
190#[cfg(feature = "no-unsafe")]
191#[cfg(feature = "__internal_expose_string_pool")]
192pub mod __internal {
193 pub use super::string_pool::StringPool;
194}
195
196pub use crate::str::XmlChar;
197#[cfg(feature = "embedded")]
198pub use crate::{as_opt_str, as_qname, as_str, to_ns_str};
199
200#[cfg(not(feature = "no-unsafe"))]
201#[macro_export]
202macro_rules! as_str {
203 ($e:expr) => {
204 $e
205 };
206}
207
208#[cfg(not(feature = "no-unsafe"))]
209#[macro_export]
210macro_rules! as_opt_str {
211 ($e:expr) => {
212 $e
213 };
214}
215
216#[cfg(not(feature = "no-unsafe"))]
217#[macro_export]
218macro_rules! as_qname {
219 ($e:expr) => {
220 $e
221 };
222}
223
224#[cfg(feature = "no-unsafe")]
225#[macro_export]
226macro_rules! as_str {
227 ($e:expr) => {
228 &*$e
229 };
230}
231
232#[cfg(feature = "no-unsafe")]
233#[macro_export]
234macro_rules! as_opt_str {
235 ($e:expr) => {
236 $e.as_deref()
237 };
238}
239
240#[cfg(feature = "no-unsafe")]
241#[macro_export]
242macro_rules! as_qname {
243 ($e:expr) => {
244 $e.get()
245 };
246}
247
248#[cfg(not(feature = "no-unsafe"))]
249#[macro_export]
250macro_rules! to_ns_str {
251 ($e:expr) => {
252 $e
253 };
254}
255
256#[cfg(feature = "no-unsafe")]
257#[macro_export]
258macro_rules! to_ns_str {
259 ($e:expr) => {
260 ($e).to_owned()
261 };
262}
263
264#[cfg(not(feature = "no-unsafe"))]
267pub type NsStr<'d> = &'d str;
268#[cfg(feature = "no-unsafe")]
271pub type NsStr<'d> = String;
275
276pub(crate) static XML_NS_PREFIX: &str = "xml";
277pub(crate) static XML_NS_URI: &str = "http://www.w3.org/XML/1998/namespace";
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
282pub struct PrefixedName<'a> {
283 prefix: Option<&'a str>,
284 local_part: &'a str,
285}
286
287impl<'a> PrefixedName<'a> {
288 pub fn new(local_part: &str) -> PrefixedName<'_> {
290 PrefixedName::with_prefix(None, local_part)
291 }
292
293 pub fn with_prefix(prefix: Option<&'a str>, local_part: &'a str) -> PrefixedName<'a> {
295 PrefixedName { prefix, local_part }
296 }
297
298 pub fn prefix(&self) -> Option<&str> {
299 self.prefix
300 }
301 pub fn local_part(&self) -> &str {
302 self.local_part
303 }
304}
305
306#[derive(Debug, Copy, Clone, PartialEq)]
310pub struct QName<'s> {
311 namespace_uri: Option<&'s str>,
312 local_part: &'s str,
313}
314
315impl<'s> QName<'s> {
316 pub fn new(local_part: &'s str) -> QName<'s> {
318 QName::with_namespace_uri(None, local_part)
319 }
320
321 pub fn with_namespace_uri(namespace_uri: Option<&'s str>, local_part: &'s str) -> QName<'s> {
323 QName {
324 namespace_uri,
325 local_part,
326 }
327 }
328
329 pub fn namespace_uri(&self) -> Option<&'s str> {
330 self.namespace_uri
331 }
332 pub fn local_part(&self) -> &'s str {
333 self.local_part
334 }
335}
336
337impl<'s> From<(&'s str, &'s str)> for QName<'s> {
338 fn from(v: (&'s str, &'s str)) -> QName<'s> {
339 QName {
340 namespace_uri: Some(v.0),
341 local_part: v.1,
342 }
343 }
344}
345
346impl<'s> From<&'s str> for QName<'s> {
347 fn from(v: &'s str) -> QName<'s> {
348 QName {
349 namespace_uri: None,
350 local_part: v,
351 }
352 }
353}
354
355pub struct Package {
360 identity: usize,
361 storage: raw::Storage,
362 connections: raw::Connections,
363}
364
365impl Default for Package {
366 fn default() -> Package {
367 let s = raw::Storage::new();
368 let root = s.create_root();
369 Package {
370 identity: next_package_identity(),
371 storage: s,
372 connections: raw::Connections::new(root),
373 }
374 }
375}
376
377impl Package {
378 pub fn new() -> Package {
379 Self::default()
380 }
381
382 pub fn as_document(&self) -> dom::Document<'_> {
383 dom::Document::new(self.identity, &self.storage, &self.connections)
384 }
385
386 #[doc(hidden)]
387 pub fn as_thin_document(&self) -> (thindom::Storage<'_>, thindom::Connections<'_>) {
388 let identity = std::ptr::from_ref(self).addr();
391 let s = thindom::Storage::new(&self.storage, identity);
392 #[cfg(not(feature = "no-unsafe"))]
393 let c = thindom::Connections::new(&self.connections, identity);
394 #[cfg(feature = "no-unsafe")]
395 let c = thindom::Connections::new(&self.connections, &self.storage, identity);
396 (s, c)
397 }
398}
399
400impl PartialEq for Package {
401 fn eq(&self, other: &Package) -> bool {
402 std::ptr::eq(self, other)
403 }
404}
405
406impl fmt::Debug for Package {
407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408 write!(f, "Package")
409 }
410}