rxml/
strings.rs

1/*!
2# Strongly-typed strings for use with XML 1.0 documents
3
4These are mostly re-exported from [`rxml_validation`]. See that crate's
5documentation for an overview over the different types and their use cases.
6
7## Construction
8
9In addition to the construction methods described in [`rxml_validation`],
10[`str`]-like references ([`&NameStr`][`NameStr`], [`&NcNameStr`][`NcNameStr`])
11can be created from a string literal, using the macros offered when this crate
12is built with the `macros` feature:
13[`xml_name!`][`crate::xml_name`], [`xml_ncname!`][`crate::xml_ncname`].
14*/
15use alloc::borrow::ToOwned;
16use alloc::string::String;
17use alloc::sync::Arc;
18use core::borrow::Borrow;
19use core::cmp::Ordering;
20use core::fmt;
21use core::hash::{Hash, Hasher};
22use core::ops::Deref;
23
24pub use rxml_validation::{CompactString, Error, Name, NameStr, NcName, NcNameStr};
25
26use crate::error::{Error as XmlError, ErrorContext};
27
28enum Shared {
29	Static(&'static str),
30	Ptr(Arc<String>),
31}
32
33enum Inner {
34	Owned(String),
35	Shared(Shared),
36}
37
38/// An XML namespace name.
39///
40/// Commonly, XML namespace names are URIs. Almost all URIs are also valid
41/// namespace names, with the prominent exception of the empty URI: namespace
42/// names in XML must not be empty. The empty namespace name signals that an
43/// element is not namespaced. For purposes of comparison and others, we can
44/// treat the empty namespace name as just another namespace, which is why it
45/// is an allowed value for this struct.
46///
47/// Internally, `Namespace` is based on [`String`] or a static [`str`]
48/// slice. It can be accessed mutably through [`Namespace::make_mut`].
49///
50/// Depending on the history of a specific `Namespace` instance, cloning
51/// is either `O(1)` or `O(n)`. To ensure that the next clone is `O(1)`, call
52/// [`Namespace::make_shared`] or use [`Namespace::share`] instead of
53/// `clone()`.
54///
55/// `Namespace` is designed such that, no matter which internal
56/// representation is at work, it hashes, compares, and orders exactly like a
57/// `String` (or `str`), allowing it to be borrowed as such.
58///
59/// # Advantages over `String`
60///
61/// Unlike [`String`], `Namespace` is optimized for strings which:
62///
63/// - are not known by the `rxml` library at compile-time
64/// - need to be passed around freely (static lifetime)
65///
66/// Internally, `Namespace` can keep either a `&'static str`, a
67/// shared (ref-counted) pointer to a `String` or an owned instance of a
68/// `String`. If the crate is built with the `shared_ns` feature,
69/// `Namespace` is `Send` and `Sync`, otherwise it is neither.
70pub struct Namespace(Inner);
71
72const fn xml() -> Namespace {
73	Namespace(Inner::Shared(Shared::Static(crate::XMLNS_XML)))
74}
75
76const fn xmlns() -> Namespace {
77	Namespace(Inner::Shared(Shared::Static(crate::XMLNS_XMLNS)))
78}
79
80const fn none() -> Namespace {
81	Namespace(Inner::Shared(Shared::Static(
82		crate::parser::XMLNS_UNNAMESPACED,
83	)))
84}
85
86impl Namespace {
87	/// `Namespace` representing the built-in XML namespace.
88	///
89	/// See also [`crate::XMLNS_XML`].
90	pub const XML: Namespace = xml();
91
92	/// `Namespace` representing the built-in XML namespacing namespace.
93	///
94	/// See also [`crate::XMLNS_XMLNS`].
95	pub const XMLNS: Namespace = xmlns();
96
97	/// `Namespace` representing the empty namespace name.
98	pub const NONE: Namespace = none();
99
100	/// Construct a namespace name for the built-in XML namespace.
101	///
102	/// This function does not allocate.
103	///
104	/// # Example
105	///
106	/// ```
107	/// # use rxml::strings::Namespace;
108	/// let ns = Namespace::xml();
109	/// assert_eq!(ns, rxml::XMLNS_XML);
110	/// ```
111	#[inline(always)]
112	pub fn xml() -> &'static Self {
113		static RESULT: Namespace = Namespace::XML;
114		&RESULT
115	}
116
117	/// Construct a namespace name for the built-in XML namespacing namespace.
118	///
119	/// This function does not allocate.
120	///
121	/// # Example
122	///
123	/// ```
124	/// # use rxml::strings::Namespace;
125	/// let ns = Namespace::xmlns();
126	/// assert_eq!(ns, rxml::XMLNS_XMLNS);
127	/// ```
128	#[inline(always)]
129	pub fn xmlns() -> &'static Self {
130		static RESULT: Namespace = Namespace::XMLNS;
131		&RESULT
132	}
133
134	/// Construct an empty namespace name, representing unnamespaced elements.
135	///
136	/// # Example
137	///
138	/// ```
139	/// # use rxml::strings::Namespace;
140	/// let ns = Namespace::none();
141	/// assert_eq!(ns, "");
142	/// ```
143	#[inline(always)]
144	pub fn none() -> &'static Self {
145		static RESULT: Namespace = Namespace::NONE;
146		&RESULT
147	}
148
149	/// Attempts to deduplicate the given namespace name with a set of
150	/// statically compiled well-known namespaces.
151	///
152	/// If no matching statically compiled namespace is found, `None` is
153	/// returned.
154	///
155	/// Otherwise, a `Namespace` is created without allocating and without
156	/// sharing data with `s`.
157	pub fn try_share_static(s: &str) -> Option<Self> {
158		if s.is_empty() {
159			return Some(Self::NONE);
160		}
161		if s == crate::XMLNS_XML {
162			return Some(Self::XML);
163		}
164		if s == crate::XMLNS_XMLNS {
165			return Some(Self::XMLNS);
166		}
167		None
168	}
169
170	/// Construct a namespace name for a custom namespace from a static string.
171	///
172	/// This function is provided for use in `const` contexts, while normally
173	/// you would use the `From<&'static str>` implementation.
174	///
175	/// # Example
176	///
177	/// ```
178	/// # use rxml::strings::Namespace;
179	/// static NS: Namespace = Namespace::from_str("jabber:client");
180	/// assert_eq!(NS, "jabber:client");
181	/// ```
182	pub const fn from_str(s: &'static str) -> Self {
183		Self(Inner::Shared(Shared::Static(s)))
184	}
185
186	/// Make the `Namespace` mutable and return a mutable reference to its
187	/// inner [`String`].
188	///
189	/// Depending on how the `Namespace` was constructed and how it has
190	/// been used, calling this function may require copying its contents.
191	pub fn make_mut(&mut self) -> &mut String {
192		match self.0 {
193			Inner::Shared(Shared::Static(v)) => {
194				let mut tmp = Inner::Owned(v.to_owned());
195				core::mem::swap(&mut self.0, &mut tmp);
196				let Inner::Owned(ref mut v) = self.0 else {
197					unreachable!()
198				};
199				v
200			}
201			Inner::Shared(Shared::Ptr(ref mut v)) => Arc::make_mut(v),
202			Inner::Owned(ref mut v) => v,
203		}
204	}
205
206	/// Change the representation of this `Namespace` such that it can be
207	/// cloned cheaply.
208	///
209	/// This may make the next call to a mutating method, such as
210	/// [`make_mut`][`Self::make_mut`] more expensive as it may then require
211	/// copying the data.
212	pub fn make_shared(&mut self) {
213		if let Inner::Owned(ref mut v) = self.0 {
214			if let Some(result) = Self::try_share_static(v) {
215				*self = result;
216				return;
217			}
218			let mut tmp = String::new();
219			core::mem::swap(&mut tmp, v);
220			self.0 = Inner::Shared(Shared::Ptr(Arc::new(tmp)));
221		}
222	}
223
224	/// Make this `Namespace` instance cheaply cloneable and clone it.
225	///
226	/// Because this may change the layout of the `Namespace` to make it
227	/// efficiently cloneable, it requires mutating access.
228	pub fn share(&mut self) -> Self {
229		self.make_shared();
230		self.clone()
231	}
232
233	/// Consume this `Namespace` instance, make it cheaply cloneable, and
234	/// return it.
235	pub fn shared(mut self) -> Self {
236		self.make_shared();
237		self.clone()
238	}
239
240	/// Clone this `Namespace` instance and then make sure that further
241	/// clones from the returned value can be made efficiently.
242	pub fn clone_shared(&self) -> Self {
243		self.clone().shared()
244	}
245
246	/// Return a reference to the inner namespace name if it is not empty or
247	/// `None` otherwise.
248	pub fn as_namespace_name(&self) -> Option<&str> {
249		let s = self.deref();
250		if s.is_empty() {
251			None
252		} else {
253			Some(s)
254		}
255	}
256
257	/// Return true if this `Namespace` is the empty namespace name.
258	///
259	/// The empty namespace name is not a valid namespace and is thus used to
260	/// identify unnamespaced elements.
261	pub fn is_none(&self) -> bool {
262		self.deref().is_empty()
263	}
264
265	/// Return true if this `Namespace` is a valid namespace name.
266	pub fn is_some(&self) -> bool {
267		!self.deref().is_empty()
268	}
269
270	/// Return a reference to the string slice inside this [`Namespace`].
271	pub fn as_str(&self) -> &str {
272		self.deref()
273	}
274}
275
276impl Deref for Namespace {
277	type Target = str;
278
279	fn deref(&self) -> &Self::Target {
280		match self.0 {
281			Inner::Shared(Shared::Static(v)) => v,
282			Inner::Shared(Shared::Ptr(ref v)) => v.deref().deref(),
283			Inner::Owned(ref v) => v.deref(),
284		}
285	}
286}
287
288impl AsRef<str> for Namespace {
289	fn as_ref(&self) -> &str {
290		self.deref()
291	}
292}
293
294impl Borrow<str> for Namespace {
295	fn borrow(&self) -> &str {
296		self.deref()
297	}
298}
299
300impl Clone for Namespace {
301	fn clone(&self) -> Self {
302		Self(match self.0 {
303			Inner::Shared(Shared::Static(v)) => Inner::Shared(Shared::Static(v)),
304			Inner::Shared(Shared::Ptr(ref v)) => Inner::Shared(Shared::Ptr(Arc::clone(v))),
305			Inner::Owned(ref v) => Inner::Owned(v.clone()),
306		})
307	}
308}
309
310impl fmt::Debug for Namespace {
311	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312		let mut wrapper = match self.0 {
313			Inner::Shared(Shared::Static(_)) => f.debug_tuple("Namespace<{Static}>"),
314			Inner::Shared(Shared::Ptr(_)) => f.debug_tuple("Namespace<{Ptr}>"),
315			Inner::Owned(_) => f.debug_tuple("Namespace<{Owned}>"),
316		};
317		wrapper.field(&self.deref()).finish()
318	}
319}
320
321impl fmt::Display for Namespace {
322	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323		<str as fmt::Display>::fmt(self.deref(), f)
324	}
325}
326
327impl Eq for Namespace {}
328
329impl From<Namespace> for String {
330	fn from(other: Namespace) -> Self {
331		match other.0 {
332			Inner::Owned(v) => v,
333			Inner::Shared(Shared::Static(v)) => v.to_owned(),
334			Inner::Shared(Shared::Ptr(v)) => Arc::unwrap_or_clone(v),
335		}
336	}
337}
338
339impl From<Namespace> for Arc<String> {
340	fn from(other: Namespace) -> Self {
341		match other.0 {
342			Inner::Owned(v) => Arc::new(v),
343			Inner::Shared(Shared::Static(v)) => Arc::new(v.to_owned()),
344			Inner::Shared(Shared::Ptr(v)) => v,
345		}
346	}
347}
348
349impl From<Name> for Namespace {
350	fn from(other: Name) -> Self {
351		let v: String = other.into();
352		debug_assert!(!v.is_empty()); // enforced by Name
353		Self(Inner::Owned(v))
354	}
355}
356
357impl From<NcName> for Namespace {
358	fn from(other: NcName) -> Self {
359		let v: String = other.into();
360		debug_assert!(!v.is_empty()); // enforced by NcName
361		Self(Inner::Owned(v))
362	}
363}
364
365impl From<&'static Name> for Namespace {
366	fn from(other: &'static Name) -> Self {
367		let v: &'static str = other.as_ref();
368		debug_assert!(!v.is_empty()); // enforced by NameStr
369		Self(Inner::Shared(Shared::Static(v)))
370	}
371}
372
373impl From<&'static NcName> for Namespace {
374	fn from(other: &'static NcName) -> Self {
375		let v: &'static str = other.as_ref();
376		debug_assert!(!v.is_empty()); // enforced by NcNameStr
377		Self(Inner::Shared(Shared::Static(v)))
378	}
379}
380
381impl From<&'static str> for Namespace {
382	fn from(other: &'static str) -> Self {
383		Self(Inner::Shared(Shared::Static(other)))
384	}
385}
386
387impl From<String> for Namespace {
388	fn from(other: String) -> Self {
389		if let Some(result) = Self::try_share_static(&other) {
390			return result;
391		}
392		Self(Inner::Owned(other))
393	}
394}
395
396impl From<Arc<String>> for Namespace {
397	fn from(other: Arc<String>) -> Self {
398		if let Some(result) = Self::try_share_static(&other) {
399			return result;
400		}
401		Self(Inner::Shared(Shared::Ptr(other)))
402	}
403}
404
405impl Hash for Namespace {
406	fn hash<H: Hasher>(&self, h: &mut H) {
407		self.deref().hash(h)
408	}
409}
410
411impl Ord for Namespace {
412	fn cmp(&self, other: &Namespace) -> Ordering {
413		self.deref().cmp(other.deref())
414	}
415}
416
417impl PartialEq for Namespace {
418	fn eq(&self, other: &Namespace) -> bool {
419		self.deref() == other.deref()
420	}
421}
422
423impl PartialEq<&str> for Namespace {
424	fn eq(&self, other: &&str) -> bool {
425		self.deref() == *other
426	}
427}
428
429impl PartialEq<Namespace> for &str {
430	fn eq(&self, other: &Namespace) -> bool {
431		*self == other.deref()
432	}
433}
434
435impl PartialEq<str> for Namespace {
436	fn eq(&self, other: &str) -> bool {
437		self.deref() == other
438	}
439}
440
441impl PartialEq<Namespace> for str {
442	fn eq(&self, other: &Namespace) -> bool {
443		self == other.deref()
444	}
445}
446
447impl PartialOrd for Namespace {
448	fn partial_cmp(&self, other: &Namespace) -> Option<Ordering> {
449		Some(self.cmp(other))
450	}
451}
452
453/**
454Check whether a str is valid XML 1.0 CData
455
456# Example
457
458```rust
459use rxml::Error;
460use rxml::strings::validate_cdata;
461
462assert!(validate_cdata("foo bar baz <fnord!>").is_ok());
463assert!(matches!(validate_cdata("\x01"), Err(Error::UnexpectedChar(_, '\x01', _))));
464*/
465pub fn validate_cdata(s: &str) -> Result<(), XmlError> {
466	rxml_validation::validate_cdata(s).map_err(|e| XmlError::from_validation(e, None))
467}
468
469/**
470Check whether a str is a valid XML 1.0 Name
471
472**Note:** This does *not* enforce that the name contains only a single colon.
473
474# Example
475
476```rust
477use rxml::Error;
478use rxml::strings::validate_name;
479
480assert!(validate_name("foobar").is_ok());
481assert!(validate_name("foo:bar").is_ok());
482assert!(matches!(validate_name("foo bar"), Err(Error::UnexpectedChar(_, ' ', _))));
483assert!(matches!(validate_name(""), Err(Error::InvalidSyntax(_))));
484*/
485pub fn validate_name(s: &str) -> Result<(), XmlError> {
486	rxml_validation::validate_name(s)
487		.map_err(|e| XmlError::from_validation(e, Some(ErrorContext::Name)))
488}
489
490/**
491Check whether a str is a valid XML 1.0 Name, without colons.
492
493# Example
494
495```rust
496use rxml::Error;
497use rxml::strings::validate_ncname;
498
499assert!(validate_ncname("foobar").is_ok());
500assert!(matches!(validate_ncname("foo:bar"), Err(Error::MultiColonName(_))));
501assert!(matches!(validate_ncname(""), Err(Error::EmptyNamePart(_))));
502*/
503pub fn validate_ncname(s: &str) -> Result<(), XmlError> {
504	match rxml_validation::validate_ncname(s)
505		.map_err(|e| XmlError::from_validation(e, Some(ErrorContext::Name)))
506	{
507		Err(XmlError::UnexpectedChar(ctx, ':', _)) => Err(XmlError::MultiColonName(ctx)),
508		Err(XmlError::InvalidSyntax(_)) => Err(XmlError::EmptyNamePart(None)),
509		other => other,
510	}
511}
512
513#[cfg(test)]
514mod tests {
515	use super::*;
516
517	#[test]
518	fn can_slice_namespace_name() {
519		let nsn = Namespace::xml();
520		assert_eq!(&nsn[..4], "http");
521	}
522}