rxml/
context.rs

1use alloc::borrow::Cow;
2use alloc::sync::Arc;
3use core::fmt;
4
5#[cfg(feature = "shared_ns")]
6use std::sync::{Mutex, Weak};
7
8use crate::strings;
9
10#[cfg(feature = "shared_ns")]
11use weak_table;
12
13#[cfg(feature = "shared_ns")]
14use alloc::string::String;
15
16#[cfg(feature = "shared_ns")]
17type StringWeakSet = weak_table::WeakHashSet<Weak<String>>;
18
19/**
20# Shared context for multiple parsers
21
22This context allows parsers to share data. This is useful in cases where many
23parsers are used in the same application, and all of them encountering similar
24data.
25
26As of writing, the context is only used to share namespace URIs encountered in
27XML documents, and only if the `shared_ns` feature is used for building.
28
29Even though the context is internally mutable, it can safely be shared with
30an immutable reference between parsers.
31*/
32#[derive(Default)]
33pub struct Context {
34	#[cfg(feature = "shared_ns")]
35	nss: Mutex<StringWeakSet>,
36	// this is included to avoid this struct being empty (and thus publicly
37	// constructible) on a build without `shared_ns` enabled
38	#[allow(dead_code)]
39	phantom: (),
40}
41
42impl Context {
43	/// Create a new context
44	pub fn new() -> Context {
45		Context::default()
46	}
47
48	/// Intern a piece of text
49	///
50	/// The given cdata is interned in the context and a refcounted pointer
51	/// is returned. When the last reference to that pointer expires, the
52	/// string will be lazily removed from the internal storage.
53	///
54	/// The optimal course is taken depending on whether the Cow is borrowed
55	/// or owned.
56	///
57	/// To force expiry, call [`Context::release_temporaries`], although that
58	/// should only rarely be necessary and may be detrimental to performance.
59	pub fn intern_namespace<'a, T: Into<Cow<'a, str>>>(&self, ns: T) -> strings::Namespace {
60		let ns = ns.into();
61		if let Some(result) = strings::Namespace::try_share_static(&ns) {
62			return result;
63		}
64		#[cfg(feature = "shared_ns")]
65		{
66			let mut nss = self.nss.lock().unwrap();
67			let ptr = match nss.get(&*ns) {
68				Some(ptr) => ptr.clone(),
69				None => {
70					let ptr = Arc::new(ns.into_owned());
71					nss.insert(ptr.clone());
72					ptr
73				}
74			};
75			strings::Namespace::from(ptr)
76		}
77		#[cfg(not(feature = "shared_ns"))]
78		strings::Namespace::from(Arc::new(ns.into_owned()))
79	}
80
81	/// Remove all unreferenced strings from storage and shrink the storage to
82	/// fit the requirements.
83	///
84	/// This should rarely be necessary to call. The internal storage will
85	/// prefer expiring unused strings over reallocating and will only
86	/// reallocate if necessary.
87	pub fn release_temporaries(&self) {
88		#[cfg(feature = "shared_ns")]
89		{
90			let mut nss = self.nss.lock().unwrap();
91			nss.remove_expired();
92			nss.shrink_to_fit();
93		}
94	}
95
96	/// Return the number of namespace strings interned.
97	///
98	/// Returns zero if built without `shared_ns`. This count includes strings
99	/// which are unreferenced and which would be removed before the next
100	/// reallocation.
101	pub fn namespaces(&self) -> usize {
102		#[cfg(feature = "shared_ns")]
103		{
104			let nss = self.nss.lock().unwrap();
105			nss.len()
106		}
107		#[cfg(not(feature = "shared_ns"))]
108		0
109	}
110
111	/// Return the current capacity for the namespace internation structure
112	///
113	/// Returns zero if built without `shared_ns`.
114	pub fn cdata_capacity(&self) -> usize {
115		#[cfg(feature = "shared_ns")]
116		{
117			let nss = self.nss.lock().unwrap();
118			nss.capacity()
119		}
120		#[cfg(not(feature = "shared_ns"))]
121		0
122	}
123}
124
125impl fmt::Debug for Context {
126	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127		let mut f = f.debug_struct("Context");
128		f.field("instance", &(self as *const Context));
129		#[cfg(feature = "shared_ns")]
130		{
131			let nss = self.nss.lock().unwrap();
132			f.field("nss.capacity()", &nss.capacity())
133				.field("nss.length()", &nss.len());
134		}
135		f.finish()
136	}
137}
138
139impl fmt::UpperHex for Context {
140	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141		let mut f = f.debug_set();
142		#[cfg(feature = "shared_ns")]
143		{
144			let nss = self.nss.lock().unwrap();
145			for item in nss.iter() {
146				f.entry(&(Arc::strong_count(&item) - 1, &*item));
147			}
148		}
149		f.finish()
150	}
151}