rxml_validation/
strings.rs

1use core::borrow::Borrow;
2use core::cmp::{Ordering, PartialOrd};
3use core::convert::{TryFrom, TryInto};
4use core::fmt;
5use core::ops::{
6	Deref, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
7};
8
9#[cfg(feature = "std")]
10use std::{
11	borrow::{Cow, ToOwned},
12	iter::FromIterator,
13	ops::{Add, AddAssign, DerefMut},
14};
15
16/// Type alias to access the inner type used for [`Name`] and [`NcName`]
17/// irrespective of feature flags.
18///
19/// This will be [`compact_str::CompactString`] if the `compact_str` feature
20/// flag is enabled and [`String`] otherwise.
21#[cfg(feature = "compact_str")]
22pub type CompactString = compact_str::CompactString;
23
24/// Type alias to access the inner type used for [`Name`] and [`NcName`]
25/// irrespective of feature flags.
26///
27/// This will be `compact_str::CompactString` if the `compact_str` feature
28/// flag is enabled and [`String`] otherwise.
29#[cfg(all(not(feature = "compact_str"), feature = "std"))]
30pub type CompactString = String;
31
32use crate::selectors;
33use crate::selectors::CharSelector;
34use crate::Error;
35use crate::{validate_name, validate_ncname};
36
37macro_rules! rxml_unsafe_str_construct_doc {
38	($name:ident, $other:ident) => {
39		concat!(
40			"Construct a `",
41			stringify!($name),
42			"` without enforcing anything\n",
43			"\n",
44			"# Safety\n",
45			"\n",
46			"The caller is responsible for ensuring that the passed [`",
47			stringify!($other),
48			"`] is in fact a valid `",
49			stringify!($name),
50			"`.\n",
51		)
52	};
53}
54
55macro_rules! rxml_safe_str_construct_doc {
56	($name:ident, $other:ident, $more:expr) => {
57		concat!(
58			"Converts a [`",
59			stringify!($other),
60			"`] to a `",
61			stringify!($name),
62			"`.\n",
63			"\n",
64			"If the given `",
65			stringify!($other),
66			"` does not conform to the restrictions imposed by `",
67			stringify!($name),
68			"`, an error is returned.\n",
69			$more
70		)
71	};
72}
73
74macro_rules! rxml_split_at_example {
75	($borrowed:ty) => {
76		concat!(
77			"\n\n```\n",
78			"# use std::convert::TryInto;\n",
79			"# use rxml_validation::",
80			stringify!($borrowed),
81			";\n",
82			"let value: &",
83			stringify!($borrowed),
84			" = \"foobar\".try_into().unwrap();\n",
85			"let (lhs, rhs) = value.split_at(3);\n",
86			"assert_eq!(lhs, \"foo\");\n",
87			"assert_eq!(rhs, \"bar\");\n",
88			"```\n",
89		)
90	};
91}
92
93#[cfg(feature = "std")]
94macro_rules! rxml_make_ascii_lowercase_example {
95	($owned:ty, $borrowed:ty) => {
96		concat!(
97			"\n\n# Example\n\n```\n",
98			"# use std::convert::TryInto;\n",
99			"# use rxml_validation::{",
100			stringify!($borrowed),
101			", ",
102			stringify!($owned),
103			"};\n",
104			"let mut owned: ",
105			stringify!($owned),
106			" = \"FÖöBar\".try_into().unwrap();\n",
107			"let borrowed: &mut ",
108			stringify!($borrowed),
109			" = &mut owned;\n",
110			"borrowed.make_ascii_lowercase();\n",
111			"assert_eq!(borrowed, \"fÖöbar\");\n",
112			"```\n",
113		)
114	};
115}
116
117#[cfg(not(feature = "std"))]
118macro_rules! rxml_make_ascii_lowercase_example {
119	($owned:ty, $borrowed:ty) => {
120		""
121	};
122}
123
124#[cfg(feature = "std")]
125macro_rules! rxml_make_ascii_uppercase_example {
126	($owned:ty, $borrowed:ty) => {
127		concat!(
128			"\n\n# Example\n\n```\n",
129			"# use std::convert::TryInto;\n",
130			"# use rxml_validation::{",
131			stringify!($borrowed),
132			", ",
133			stringify!($owned),
134			"};\n",
135			"let mut owned: ",
136			stringify!($owned),
137			" = \"FÖöBar\".try_into().unwrap();\n",
138			"let borrowed: &mut ",
139			stringify!($borrowed),
140			" = &mut owned;\n",
141			"borrowed.make_ascii_uppercase();\n",
142			"assert_eq!(borrowed, \"FÖöBAR\");\n",
143			"```\n",
144		)
145	};
146}
147
148#[cfg(not(feature = "std"))]
149macro_rules! rxml_make_ascii_uppercase_example {
150	($owned:ty, $borrowed:ty) => {
151		""
152	};
153}
154
155#[cfg(feature = "std")]
156macro_rules! rxml_split_off_panic_on_empty {
157	() => {
158		concat!(
159			"\n",
160			"# Panics\n",
161			"\n",
162			"If `idx` is 0 or equal to the length minus one, as the empty ",
163			"string is not valid.\n",
164		)
165	};
166}
167
168#[cfg(feature = "std")]
169macro_rules! rxml_split_off_panics {
170	(NcName) => {
171		rxml_split_off_panic_on_empty!()
172	};
173	(Name) => {
174		rxml_split_off_panic_on_empty!()
175	};
176}
177
178#[cfg(feature = "std")]
179macro_rules! rxml_split_off_example {
180	($ty:ident) => {
181		concat!(
182			"\n",
183			"```\n",
184			"# use std::convert::TryInto;\n",
185			"# use rxml_validation::",
186			stringify!($ty),
187			";\n",
188			"let mut value: ",
189			stringify!($ty),
190			" = \"foobar\".try_into().unwrap();\n",
191			"let rhs: ",
192			stringify!($ty),
193			" = value.split_off(3);\n",
194			"assert_eq!(value, \"foo\");\n",
195			"assert_eq!(rhs, \"bar\");\n",
196			"```\n",
197		)
198	};
199}
200
201#[cfg(feature = "std")]
202macro_rules! rxml_insert_str_example {
203	($owned:ident, $borrowed:ident) => {
204		concat!(
205			"\n",
206			"```\n",
207			"# use std::convert::TryInto;\n",
208			"# use rxml_validation::{",
209			stringify!($owned),
210			", ",
211			stringify!($borrowed),
212			"};\n",
213			"let mut value: ",
214			stringify!($owned),
215			" = \"foobaz\".try_into().unwrap();\n",
216			"let to_insert: &",
217			stringify!($borrowed),
218			" = \"bar\".try_into().unwrap();\n",
219			"value.insert_str(3, to_insert);\n",
220			"assert_eq!(value, \"foobarbaz\");\n",
221			"```\n",
222		)
223	};
224}
225
226#[cfg(feature = "std")]
227macro_rules! rxml_push_str_example {
228	($owned:ident, $borrowed:ident) => {
229		concat!(
230			"\n",
231			"```\n",
232			"# use std::convert::TryInto;\n",
233			"# use rxml_validation::{",
234			stringify!($owned),
235			", ",
236			stringify!($borrowed),
237			"};\n",
238			"let mut value: ",
239			stringify!($owned),
240			" = \"foobar\".try_into().unwrap();\n",
241			"let to_append: &",
242			stringify!($borrowed),
243			" = \"baz\".try_into().unwrap();\n",
244			"value.push_str(to_append);\n",
245			"assert_eq!(value, \"foobarbaz\");\n",
246			"```\n",
247		)
248	};
249}
250
251#[cfg(all(not(feature = "compact_str"), feature = "std"))]
252macro_rules! rxml_non_compact_str_only_note {
253	(CompactString) => {
254		"\n# Note\nThis function is only available *without* the `compact_str` feature!\n"
255	};
256	($other:ident) => {
257		""
258	};
259}
260
261#[cfg(feature = "std")]
262macro_rules! rxml_custom_string_type {
263	(
264		$(#[$outer:meta])*
265		pub struct $name:ident($string:ident) use $check:ident => $borrowed:ident;
266	) => {
267		$(#[$outer])*
268		#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord)]
269		#[repr(transparent)]
270		pub struct $name($string);
271
272		impl $name {
273			/// Extract the inner string and return it.
274			pub fn into_inner(self) -> $string {
275				self.0
276			}
277
278			/// Obtain a reference to the inner string slice.
279			pub fn as_str(&self) -> &str {
280				self.0.as_str()
281			}
282
283			/// Return the capacity, in bytes.
284			pub fn capacity(&self) -> usize {
285				self.0.capacity()
286			}
287
288			/// Inserts a slice into this typed string at a byte position.
289			///
290			/// This is an *O(n)* operation as it requires copying every
291			/// element into the buffer.
292			///
293			/// # Panics
294			///
295			/// Panics if `idx` is larger than the string's length, or if it
296			/// does not lie on a `char` boundary.
297			///
298			/// # Example
299			///
300			#[doc = rxml_insert_str_example!($name, $borrowed)]
301			pub fn insert_str(&mut self, idx: usize, string: &$borrowed) {
302				// CORRECTNESS: as $borrowed is a valid sub-slice of $name,
303				// it is also valid at the start of $name -> we can insert it
304				// at arbitrary positions.
305				self.0.insert_str(idx, &string.0);
306			}
307
308			/// Return the length of this string in bytes.
309			pub fn len(&self) -> usize {
310				self.0.len()
311			}
312
313			/// Reserve capacity for at least `additional` bytes more than the
314			/// current length.
315			pub fn reserve(&mut self, additional: usize) {
316				self.0.reserve(additional)
317			}
318
319			/// Reserve capacity for at least `additional` bytes more than the current
320			/// length.
321			///
322			/// Unlike `reserve`, this will not over-allocate, ever.
323			// ↓ CompactString does not have this.
324			#[doc = rxml_non_compact_str_only_note!($string)]
325			#[cfg(not(feature = "compact_str"))]
326			pub fn reserve_exact(&mut self, additional: usize) {
327				self.0.reserve_exact(additional)
328			}
329
330			/// Shrink the capacity of this string with a lower bound.
331			pub fn shrink_to(&mut self, min_capacity: usize) {
332				self.0.shrink_to(min_capacity)
333			}
334
335			/// Shrink the capacity of this string to match its length.
336			pub fn shrink_to_fit(&mut self) {
337				self.0.shrink_to_fit()
338			}
339
340			#[doc = rxml_unsafe_str_construct_doc!($name, str)]
341			pub unsafe fn from_str_unchecked<T: AsRef<str>>(s: T) -> Self {
342				Self(s.as_ref().into())
343			}
344
345			#[doc = rxml_unsafe_str_construct_doc!($name, String)]
346			pub unsafe fn from_string_unchecked<T: Into<String>>(s: T) -> Self {
347				Self(s.into().into())
348			}
349
350			#[cfg(feature = "compact_str")]
351			#[allow(dead_code)]
352			unsafe fn from_auto_unchecked(s: CompactString) -> Self {
353				Self(s.into())
354			}
355
356			#[cfg(not(feature = "compact_str"))]
357			#[allow(dead_code)]
358			unsafe fn from_auto_unchecked(s: String) -> Self {
359				Self(s.into())
360			}
361
362			#[doc = rxml_unsafe_str_construct_doc!($name, CompactString)]
363			#[cfg(feature = "compact_str")]
364			#[cfg_attr(docsrs, doc(cfg(feature = "compact_str")))]
365			pub unsafe fn from_compact_str_unchecked<T: Into<CompactString>>(s: T) -> Self {
366				Self(s.into().into())
367			}
368
369			unsafe fn from_native_unchecked(s: $string) -> Self {
370				Self(s)
371			}
372
373			/// Appends a slice to this typed string.
374			///
375			/// This is an *O(n)* operation as it requires copying every
376			/// element into the buffer.
377			///
378			/// # Example
379			///
380			#[doc = rxml_push_str_example!($name, $borrowed)]
381			// CORRECTNESS:
382			// For NcName and Name it holds that concatenations of these types
383			// are correct.
384			pub fn push_str(&mut self, v: &$borrowed) {
385				self.0.push_str(&v.0)
386			}
387		}
388
389		impl Deref for $name {
390			type Target = $borrowed;
391
392			fn deref(&self) -> &Self::Target {
393				// SAFETY: $borrowed is assumed to use the same check; this is
394				// enforced by using the pair macro.
395				unsafe { $borrowed::from_str_unchecked(&self.0) }
396			}
397		}
398
399		impl DerefMut for $name {
400			fn deref_mut(&mut self) -> &mut Self::Target {
401				// SAFETY: $borrowed is assumed to use the same check; this is
402				// enforced by using the pair macro.
403				unsafe { $borrowed::from_str_unchecked_mut(&mut self.0) }
404			}
405		}
406
407		impl Borrow<$string> for $name {
408			fn borrow(&self) -> &$string {
409				&self.0
410			}
411		}
412
413		impl Borrow<$borrowed> for $name {
414			fn borrow(&self) -> &$borrowed {
415				// SAFETY: $borrowed is assumed to use the same check; this is
416				// enforced by using the pair macro.
417				unsafe { $borrowed::from_str_unchecked(&self.0) }
418			}
419		}
420
421		impl Borrow<str> for $name {
422			fn borrow(&self) -> &str {
423				&self.0
424			}
425		}
426
427		impl AsRef<$string> for $name {
428			fn as_ref(&self) -> &$string {
429				&self.0
430			}
431		}
432
433		impl AsRef<$borrowed> for $name {
434			fn as_ref(&self) -> &$borrowed {
435				// SAFETY: $borrowed is assumed to use the same check; this is
436				// enforced by using the pair macro.
437				unsafe { $borrowed::from_str_unchecked(&self.0) }
438			}
439		}
440
441		impl AsRef<str> for $name {
442			fn as_ref(&self) -> &str {
443				&self.0
444			}
445		}
446
447		impl PartialEq<str> for $name {
448			fn eq(&self, other: &str) -> bool {
449				self.0 == other
450			}
451		}
452
453		// following the example of std::string::String, we define PartialEq
454		// against the slice and the base type.
455		impl PartialEq<$name> for str {
456			fn eq(&self, other: &$name) -> bool {
457				other.0 == self
458			}
459		}
460
461		impl PartialEq<&str> for $name {
462			fn eq(&self, other: &&str) -> bool {
463				&self.0 == other
464			}
465		}
466
467		impl PartialEq<$name> for &str {
468			fn eq(&self, other: &$name) -> bool {
469				other.0 == *self
470			}
471		}
472
473		impl PartialEq<$borrowed> for $name {
474			fn eq(&self, other: &$borrowed) -> bool {
475				self.0 == &other.0
476			}
477		}
478
479		impl PartialEq<$name> for $borrowed {
480			fn eq(&self, other: &$name) -> bool {
481				other.0 == &self.0
482			}
483		}
484
485		impl PartialEq<&$borrowed> for $name {
486			fn eq(&self, other: &&$borrowed) -> bool {
487				self.0 == &other.0
488			}
489		}
490
491		impl PartialEq<$name> for &$borrowed {
492			fn eq(&self, other: &$name) -> bool {
493				other.0 == &self.0
494			}
495		}
496
497		impl PartialOrd<$name> for $name {
498			fn partial_cmp(&self, other: &$name) -> Option<Ordering> {
499				self.0.partial_cmp(&other.0)
500			}
501		}
502
503		impl From<$name> for String {
504			fn from(other: $name) -> Self {
505				other.0.into()
506			}
507		}
508
509		#[cfg(feature = "compact_str")]
510		#[cfg_attr(docsrs, doc(cfg(feature = "compact_str")))]
511		impl From<$name> for CompactString {
512			fn from(other: $name) -> Self {
513				other.0.into()
514			}
515		}
516
517		impl<'x> From<$name> for Cow<'x, $borrowed> {
518			fn from(other: $name) -> Self {
519				Self::Owned(other)
520			}
521		}
522
523		impl<'x> From<Cow<'x, $borrowed>> for $name {
524			fn from(other: Cow<'x, $borrowed>) -> Self {
525				other.into_owned()
526			}
527		}
528
529		#[cfg(feature = "compact_str")]
530		#[cfg_attr(docsrs, doc(cfg(feature = "compact_str")))]
531		impl TryFrom<CompactString> for $name {
532			type Error = Error;
533
534			#[doc = rxml_safe_str_construct_doc!($name, CompactString, "")]
535			fn try_from(other: CompactString) -> Result<Self, Self::Error> {
536				$check(&other)?;
537				Ok($name(other.into()))
538			}
539		}
540
541		impl TryFrom<String> for $name {
542			type Error = Error;
543
544			#[doc = rxml_safe_str_construct_doc!($name, String, "")]
545			fn try_from(other: String) -> Result<Self, Self::Error> {
546				$check(&other)?;
547				Ok($name(other.into()))
548			}
549		}
550
551		impl TryFrom<&str> for $name {
552			type Error = Error;
553
554			#[doc = rxml_safe_str_construct_doc!($name, str, "")]
555			fn try_from(other: &str) -> Result<Self, Self::Error> {
556				$check(other)?;
557				Ok($name(other.into()))
558			}
559		}
560
561		impl fmt::Display for $name {
562			fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
563				f.write_str(&self.0 as &str)
564			}
565		}
566
567		// The impls below here are inspired by the list of trait impls of
568		// String.
569
570		// CORRECTNESS:
571		// For NcName and Name it holds that concatenations of these types are
572		// correct.
573		//
574		// That is because while they may have constraints on the characters
575		// at the beginning, the characters allowed in the remainder of the
576		// respective string types are a superset of those allowed as first
577		// character.
578		impl Add<&$borrowed> for $name {
579			type Output = $name;
580
581			fn add(mut self, rhs: &$borrowed) -> Self::Output {
582				self += rhs;
583				self
584			}
585		}
586
587		impl AddAssign<&$borrowed> for $name {
588			fn add_assign(&mut self, rhs: &$borrowed) {
589				self.0.push_str(&rhs.0)
590			}
591		}
592
593		impl<'a> Extend<&'a $borrowed> for $name {
594			fn extend<I: IntoIterator<Item = &'a $borrowed>>(&mut self, iter: I) {
595				self.0.extend(iter.into_iter().map(|x| &x.0))
596			}
597		}
598
599		impl Extend<Box<$borrowed>> for $name {
600			fn extend<I: IntoIterator<Item = Box<$borrowed>>>(&mut self, iter: I) {
601				for item in iter {
602					self.add_assign(&item);
603				}
604			}
605		}
606
607		impl<'a> Extend<Cow<'a, $borrowed>> for $name {
608			fn extend<I: IntoIterator<Item = Cow<'a, $borrowed>>>(&mut self, iter: I) {
609				for item in iter {
610					self.add_assign(&item);
611				}
612			}
613		}
614
615		impl Extend<$name> for $name {
616			fn extend<I: IntoIterator<Item = $name>>(&mut self, iter: I) {
617				self.0.extend(iter.into_iter().map(|x| x.0))
618			}
619		}
620
621		impl<'x> FromIterator<&'x $borrowed> for $name {
622			fn from_iter<I: IntoIterator<Item = &'x $borrowed>>(iter: I) -> Self {
623				// SAFETY: see note above impl Add<&$borrowed>.
624				unsafe {
625					Self::from_native_unchecked(
626						<$string>::from_iter(iter.into_iter().map(|x| &x.0))
627					)
628				}
629			}
630		}
631
632		impl FromIterator<Box<$borrowed>> for $name {
633			fn from_iter<I: IntoIterator<Item = Box<$borrowed>>>(iter: I) -> Self {
634				let mut buf = <$string>::with_capacity(0);
635				for item in iter {
636					buf.push_str(&item.0);
637				}
638				unsafe {
639					Self::from_native_unchecked(buf)
640				}
641			}
642		}
643
644		impl<'x> FromIterator<Cow<'x, $borrowed>> for $name {
645			fn from_iter<I: IntoIterator<Item = Cow<'x, $borrowed>>>(iter: I) -> Self {
646				let mut buf = <$string>::with_capacity(0);
647				for item in iter {
648					buf.push_str(&item.0);
649				}
650				unsafe {
651					Self::from_native_unchecked(buf)
652				}
653			}
654		}
655	}
656}
657
658macro_rules! rxml_custom_str_type {
659	(
660		$(#[$outer:meta])*
661		pub struct $name:ident(str) use $check:ident => $owned:ident;
662	) => {
663		$(#[$outer])*
664		#[derive(Debug, Hash, PartialEq, Eq, Ord)]
665		#[repr(transparent)]
666		pub struct $name(str);
667
668		impl $name {
669			#[doc = rxml_safe_str_construct_doc!($name, str, "")]
670			pub fn from_str<'x>(s: &'x str) -> Result<&'x Self, Error> {
671				s.try_into()
672			}
673
674			/// Access the underlying str.
675			///
676			/// This is mostly provided for use in const functions.
677			pub const fn as_str(&self) -> &str {
678				&self.0
679			}
680
681			#[doc = rxml_unsafe_str_construct_doc!($name, str)]
682			pub const unsafe fn from_str_unchecked<'x>(s: &'x str) -> &'x Self {
683				core::mem::transmute(s)
684			}
685
686			#[doc = rxml_unsafe_str_construct_doc!($name, str)]
687			pub unsafe fn from_str_unchecked_mut<'x>(s: &'x mut str) -> &'x mut Self {
688				core::mem::transmute(s)
689			}
690
691			/// Replace A-Z with a-z, in-place.
692			///
693			/// Non-ASCII characters remain unchanged.
694			#[doc = rxml_make_ascii_lowercase_example!($owned, $name)]
695			pub fn make_ascii_lowercase(&mut self) {
696				self.0.make_ascii_lowercase()
697			}
698
699			/// Replace a-z with A-Z, in-place.
700			///
701			/// Non-ASCII characters remain unchanged.
702			#[doc = rxml_make_ascii_uppercase_example!($owned, $name)]
703			pub fn make_ascii_uppercase(&mut self) {
704				self.0.make_ascii_uppercase()
705			}
706		}
707
708		impl Deref for $name {
709			type Target = str;
710
711			fn deref(&self) -> &Self::Target {
712				&self.0
713			}
714		}
715
716		impl AsRef<str> for $name {
717			fn as_ref(&self) -> &str {
718				&self.0
719			}
720		}
721
722		impl AsRef<$name> for &$name {
723			fn as_ref(&self) -> &$name {
724				&self
725			}
726		}
727
728		impl PartialEq<str> for $name {
729			fn eq(&self, other: &str) -> bool {
730				&self.0 == other
731			}
732		}
733
734		impl PartialEq<$name> for str {
735			fn eq(&self, other: &$name) -> bool {
736				self == &other.0
737			}
738		}
739
740		impl PartialOrd<$name> for $name {
741			fn partial_cmp(&self, other: &$name) -> Option<Ordering> {
742				self.0.partial_cmp(&other.0)
743			}
744		}
745
746		#[cfg(feature = "std")]
747		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
748		impl ToOwned for $name {
749			type Owned = $owned;
750
751			fn to_owned(&self) ->Self::Owned {
752				self.into()
753			}
754		}
755
756		#[cfg(feature = "std")]
757		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
758		impl From<&$name> for $owned {
759			fn from(other: &$name) -> Self {
760				// SAFETY: $owned is assumed to use the same check; this is
761				// enforced by using the pair macro.
762				unsafe { $owned::from_str_unchecked(&other.0) }
763			}
764		}
765
766		impl<'x> TryFrom<&'x str> for &'x $name {
767			type Error = Error;
768
769			fn try_from(other: &'x str) -> Result<Self, Self::Error> {
770				$check(other)?;
771				// SAFETY: the content check is executed right above and we're
772				// transmuting &str into a repr(transparent) of &str.
773				Ok(unsafe { core::mem::transmute(other) } )
774			}
775		}
776
777		impl fmt::Display for $name {
778			fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
779				f.write_str(&self.0)
780			}
781		}
782	}
783}
784
785macro_rules! rxml_index_impl {
786	($ty:ty, $selcode:expr, $borrowed:ty, $rangety:ty) => {
787		impl Index<$rangety> for $ty {
788			type Output = $borrowed;
789
790			fn index(&self, index: $rangety) -> &$borrowed {
791				let tmp = &self.0[index];
792				let firstchar = tmp.chars().next();
793				if !($selcode(firstchar)) {
794					panic!(concat!("slice is not a valid ", stringify!($borrowed)));
795				}
796				// SAFETY: please please point $selcode at the right stuff, kthxbai.
797				unsafe { <$borrowed>::from_str_unchecked(tmp) }
798			}
799		}
800	};
801}
802
803macro_rules! rxml_splitting_impls {
804	($ty:ident => $firstsel:path => $borrowed:ident) => {
805		rxml_splitting_impls!($ty => (|firstchar: Option<char>| firstchar.map(|x| $firstsel.select(x)).unwrap_or(false)) => $borrowed);
806	};
807	($ty:ident => $selcode:expr => $borrowed:ident) => {
808		#[cfg(feature = "std")]
809		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
810		rxml_index_impl!($ty, $selcode, $borrowed, Range<usize>);
811
812		#[cfg(feature = "std")]
813		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
814		rxml_index_impl!($ty, $selcode, $borrowed, RangeFrom<usize>);
815
816		#[cfg(feature = "std")]
817		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
818		rxml_index_impl!($ty, $selcode, $borrowed, RangeFull);
819
820		#[cfg(feature = "std")]
821		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
822		rxml_index_impl!($ty, $selcode, $borrowed, RangeInclusive<usize>);
823
824		#[cfg(feature = "std")]
825		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
826		rxml_index_impl!($ty, $selcode, $borrowed, RangeTo<usize>);
827
828		#[cfg(feature = "std")]
829		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
830		rxml_index_impl!($ty, $selcode, $borrowed, RangeToInclusive<usize>);
831
832		rxml_index_impl!($borrowed, $selcode, $borrowed, Range<usize>);
833		rxml_index_impl!($borrowed, $selcode, $borrowed, RangeFrom<usize>);
834		rxml_index_impl!($borrowed, $selcode, $borrowed, RangeFull);
835		rxml_index_impl!($borrowed, $selcode, $borrowed, RangeInclusive<usize>);
836		rxml_index_impl!($borrowed, $selcode, $borrowed, RangeTo<usize>);
837		rxml_index_impl!($borrowed, $selcode, $borrowed, RangeToInclusive<usize>);
838
839
840		#[cfg(feature = "std")]
841		#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
842		impl $ty {
843			/// Splits the string into two at the given byte index.
844			#[doc = rxml_split_off_panics!($ty)]
845			///
846			/// # Example
847			///
848			#[doc = rxml_split_off_example!($ty)]
849			pub fn split_off(&mut self, at: usize) -> Self {
850				let other = self.0.split_off(at);
851				if !<$borrowed>::verify(&other) || !<$borrowed>::verify(&self.0) {
852					panic!(concat!("split string is not a valid ", stringify!($ty)));
853				}
854				// SAFETY: please please point $selcode at the right stuff, kthxbai.
855				unsafe {
856					<$ty>::from_str_unchecked(other)
857				}
858			}
859		}
860
861		impl $borrowed {
862			fn verify(s: &str) -> bool {
863				let firstchar = s.chars().next();
864				return $selcode(firstchar);
865			}
866
867			/// Divide one string slice into two at an index.
868			///
869			/// # Panics
870			///
871			/// Panics if `mid` is not on a UTF-8 code point boundary, or if
872			/// it is past the end of the last code point of the slice, or
873			/// if either resulting part is not a valid slice of this type.
874			///
875			/// # Example
876			#[doc = rxml_split_at_example!($borrowed)]
877			pub fn split_at(&self, mid: usize) -> (&Self, &Self) {
878				let (a, b) = self.0.split_at(mid);
879				if !Self::verify(a) || !Self::verify(b) {
880					panic!(concat!("split_at result is not a valid ", stringify!($borrowed)));
881				}
882				// SAFETY: please please point $selcode at the right stuff, kthxbai.
883				unsafe {
884					(
885						Self::from_str_unchecked(a),
886						Self::from_str_unchecked(b),
887					)
888				}
889			}
890
891			/// Divide one mutable string slice into two at an index.
892			///
893			/// # Panics
894			///
895			/// Panics if `mid` is not on a UTF-8 code point boundary, or if
896			/// it is past the end of the last code point of the slice, or
897			/// if either resulting part is not a valid slice of this type.
898			pub fn split_at_mut(&mut self, mid: usize) -> (&mut Self, &mut Self) {
899				let (a, b) = self.0.split_at_mut(mid);
900				if !Self::verify(a) || !Self::verify(b) {
901					panic!(concat!("split_at_mut result is not a valid ", stringify!($borrowed)));
902				}
903				// SAFETY: please please point $selcode at the right stuff, kthxbai.
904				unsafe {
905					(
906						Self::from_str_unchecked_mut(a),
907						Self::from_str_unchecked_mut(b),
908					)
909				}
910			}
911		}
912	}
913}
914
915macro_rules! rxml_custom_string_type_pair {
916	(
917		$(#[$ownedmeta:meta])*
918		pub struct $owned:ident($string:ident) use $check:ident;
919
920		$(#[$borrowedmeta:meta])*
921		pub struct $borrowed:ident(str);
922	) => {
923		#[cfg(feature = "std")]
924		rxml_custom_string_type!{
925			$(#[$ownedmeta])*
926			#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
927			pub struct $owned($string) use $check => $borrowed;
928		}
929
930		rxml_custom_str_type!{
931			$(#[$borrowedmeta])*
932			pub struct $borrowed(str) use $check => $owned;
933		}
934	}
935}
936
937rxml_custom_string_type_pair! {
938	/// String which conforms to the Name production of XML 1.0.
939	///
940	/// [`Name`] corresponds to a (restricted) [`String`]. For a [`str`]-like type
941	/// with the same restrictions, see [`NameStr`].
942	///
943	/// If using the `rxml` crate and the `macros` feature of the `rxml` crate
944	/// is enabled, `&NameStr` can be created from a string literal at compile
945	/// time using the `rxml::xml_name` macro.
946	///
947	/// Since [`Name`] (indirectly) derefs to [`str`], all (non-mutable)
948	/// methods from [`str`] are available.
949	///
950	/// # Formal definition
951	///
952	/// The data inside [`Name`] (and [`NameStr`]) is guaranteed to conform to
953	/// the `Name` production of the below grammar, quoted from
954	/// [XML 1.0 § 2.3](https://www.w3.org/TR/REC-xml/#NT-NameStartChar):
955	///
956	/// ```text
957	/// [4]  NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6]
958	///                        | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D]
959	///                        | [#x37F-#x1FFF] | [#x200C-#x200D]
960	///                        | [#x2070-#x218F] | [#x2C00-#x2FEF]
961	///                        | [#x3001-#xD7FF] | [#xF900-#xFDCF]
962	///                        | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
963	/// [4a] NameChar      ::= NameStartChar | "-" | "." | [0-9] | #xB7
964	///                        | [#x0300-#x036F] | [#x203F-#x2040]
965	/// [5]  Name          ::= NameStartChar (NameChar)*
966	/// ```
967	pub struct Name(CompactString) use validate_name;
968
969	/// str which conforms to the Name production of XML 1.0.
970	///
971	/// [`NameStr`] corresponds to a (restricted) [`str`]. For a [`String`]-like
972	/// type with the same restrictions as well as the formal definition of those
973	/// restrictions, see [`Name`].
974	///
975	/// If using the `rxml` crate and the `macros` feature of the `rxml` crate
976	/// is enabled, `&NameStr` can be created from a string literal at compile
977	/// time using the `rxml::xml_name` macro.
978	///
979	/// Since [`NameStr`] derefs to [`str`], all (non-mutable) methods from
980	/// [`str`] are available.
981	pub struct NameStr(str);
982}
983
984rxml_splitting_impls! {
985	Name => selectors::CLASS_XML_NAMESTART => NameStr
986}
987
988#[cfg(feature = "std")]
989#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
990impl Name {
991	/// Split the name at a colon, if it exists.
992	///
993	/// If the name contains no colon, the function returns `(None, self)`.
994	/// If the name contains exactly one colon, the function returns the part
995	/// before the colon (the prefix) in the first return value and the part
996	/// following the colon (the suffix) as second return value.
997	///
998	/// If neither of the two cases apply or the string on either side of the
999	/// colon is empty, an error is returned.
1000	///
1001	/// This function optimizes the split (compared to operating on a borrowed
1002	/// [`NameStr`] and then cloning the returned parts) by avoiding
1003	/// unnecessary copying.
1004	///
1005	/// # Example
1006	///
1007	/// ```
1008	/// # use rxml_validation::Name;
1009	/// # use std::convert::TryFrom;
1010	/// // with prefix:
1011	/// let name = Name::try_from("foo:bar").unwrap();
1012	/// let (prefix, local) = name.split_name().unwrap();
1013	/// assert_eq!(prefix.unwrap(), "foo");
1014	/// assert_eq!(local, "bar");
1015	///
1016	/// // without prefix:
1017	/// let name = Name::try_from("no-prefix").unwrap();
1018	/// let (prefix, local) = name.split_name().unwrap();
1019	/// assert!(prefix.is_none());
1020	/// assert_eq!(local, "no-prefix");
1021	/// ```
1022	pub fn split_name(self) -> Result<(Option<NcName>, NcName), Error> {
1023		let mut name = self.0;
1024		let colon_pos = match name.find(':') {
1025			None => return Ok((None, unsafe { NcName::from_auto_unchecked(name) })),
1026			Some(pos) => pos,
1027		};
1028		if colon_pos == 0 || colon_pos == name.len() - 1 {
1029			return Err(Error::EmptyNamePart);
1030		}
1031
1032		let localname = name.split_off(colon_pos + 1);
1033		let mut prefix = name;
1034
1035		if localname.find(':').is_some() {
1036			// Namespaces in XML 1.0 (Third Edition) namespace-well-formed criterium 1
1037			return Err(Error::MultiColonName);
1038		};
1039		if !selectors::CLASS_XML_NAMESTART.select(localname.chars().next().unwrap()) {
1040			// Namespaces in XML 1.0 (Third Edition) NcName production
1041			return Err(Error::InvalidLocalName);
1042		}
1043
1044		prefix.pop();
1045		// do not shrink to fit here -- the prefix will be used when the element
1046		// is finalized to put it on the stack for quick validation of the
1047		// </element> token.
1048
1049		debug_assert!(prefix.len() > 0);
1050		debug_assert!(localname.len() > 0);
1051		Ok((
1052			Some(unsafe { NcName::from_auto_unchecked(prefix) }),
1053			unsafe { NcName::from_auto_unchecked(localname) },
1054		))
1055	}
1056}
1057
1058impl NameStr {
1059	/// Create an owned copy of the string as [`Name`].
1060	///
1061	/// This operation is also available as implementation of the `Into`
1062	/// trait.
1063	#[cfg(feature = "std")]
1064	#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1065	pub fn to_name(&self) -> Name {
1066		self.into()
1067	}
1068
1069	/// Split the name at a colon, if it exists.
1070	///
1071	/// If the name contains no colon, the function returns `(None, self)`.
1072	/// If the name contains exactly one colon, the function returns the part
1073	/// before the colon (the prefix) in the first return value and the part
1074	/// following the colon (the suffix) as second return value.
1075	///
1076	/// If neither of the two cases apply or the string on either side of the
1077	/// colon is empty, an error is returned.
1078	///
1079	/// # Example
1080	///
1081	/// ```
1082	/// # use rxml_validation::NameStr;
1083	/// # use std::convert::TryInto;
1084	/// // with prefix:
1085	/// let name: &NameStr = "foo:bar".try_into().unwrap();
1086	/// let (prefix, local) = name.split_name().unwrap();
1087	/// assert_eq!(prefix.unwrap(), "foo");
1088	/// assert_eq!(local, "bar");
1089	///
1090	/// // without prefix:
1091	/// let name: &NameStr = "no-prefix".try_into().unwrap();
1092	/// let (prefix, local) = name.split_name().unwrap();
1093	/// assert!(prefix.is_none());
1094	/// assert_eq!(local, "no-prefix");
1095	/// ```
1096	pub fn split_name(&self) -> Result<(Option<&'_ NcNameStr>, &'_ NcNameStr), Error> {
1097		let name = &self.0;
1098		let colon_pos = match name.find(':') {
1099			None => return Ok((None, unsafe { NcNameStr::from_str_unchecked(name) })),
1100			Some(pos) => pos,
1101		};
1102		if colon_pos == 0 || colon_pos == name.len() - 1 {
1103			return Err(Error::EmptyNamePart);
1104		}
1105
1106		let (prefix, localname) = name.split_at(colon_pos);
1107		let localname = &localname[1..];
1108
1109		if localname.find(':').is_some() {
1110			// Namespaces in XML 1.0 (Third Edition) namespace-well-formed criterium 1
1111			return Err(Error::MultiColonName);
1112		};
1113		if !selectors::CLASS_XML_NAMESTART.select(localname.chars().next().unwrap()) {
1114			// Namespaces in XML 1.0 (Third Edition) NcName production
1115			return Err(Error::InvalidLocalName);
1116		}
1117
1118		debug_assert!(prefix.len() > 0);
1119		debug_assert!(localname.len() > 0);
1120		Ok((
1121			Some(unsafe { NcNameStr::from_str_unchecked(prefix) }),
1122			unsafe { NcNameStr::from_str_unchecked(localname) },
1123		))
1124	}
1125}
1126
1127#[cfg(feature = "std")]
1128#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1129impl From<NcName> for Name {
1130	fn from(other: NcName) -> Self {
1131		other.into_name()
1132	}
1133}
1134
1135impl<'x> From<&'x NcNameStr> for &'x NameStr {
1136	fn from(other: &'x NcNameStr) -> Self {
1137		other.as_namestr()
1138	}
1139}
1140
1141rxml_custom_string_type_pair! {
1142	/// String which conforms to the NcName production of Namespaces in XML 1.0.
1143	///
1144	/// [`NcName`] corresponds to a (restricted) [`String`]. For a [`str`]-like
1145	/// type with the same restrictions, see [`NcNameStr`].
1146	///
1147	/// If using the `rxml` crate and the `macros` feature of the `rxml` crate
1148	/// is enabled, `&NcNameStr` can be created from a string literal at
1149	/// compile time using the `rxml::xml_ncname` macro.
1150	///
1151	/// Since [`NcName`] (indirectly) derefs to [`str`], all (non-mutable)
1152	/// methods from [`str`] are available.
1153	///
1154	/// # Formal definition
1155	///
1156	/// The data inside [`NcName`] (and [`NcNameStr`]) is guaranteed to conform to
1157	/// the `NcName` production of the below grammar, quoted from
1158	/// [Namespaces in XML 1.0 § 3](https://www.w3.org/TR/REC-xml-names/#NT-NcName):
1159	///
1160	/// ```text
1161	/// [4] NcName ::= Name - (Char* ':' Char*)  /* An XML Name, minus the ":" */
1162	/// ```
1163	pub struct NcName(CompactString) use validate_ncname;
1164
1165	/// str which conforms to the NcName production of Namespaces in XML 1.0.
1166	///
1167	/// [`NcNameStr`] corresponds to a (restricted) [`str`]. For a [`String`]-like
1168	/// type with the same restrictions as well as the formal definition of those
1169	/// restrictions, see [`NcName`].
1170	///
1171	/// If using the `rxml` crate and the `macros` feature of the `rxml` crate
1172	/// is enabled, `&NcNameStr` can be created from a string literal at
1173	/// compile time using the `rxml::xml_ncname` macro.
1174	///
1175	/// Since [`NcNameStr`] derefs to [`str`], all (non-mutable) methods from
1176	/// [`str`] are available.
1177	pub struct NcNameStr(str);
1178}
1179
1180rxml_splitting_impls! {
1181	NcName => selectors::CLASS_XML_NAMESTART => NcNameStr
1182}
1183
1184#[cfg(feature = "std")]
1185#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1186impl NcName {
1187	/// Compose two [`NcName`] objects to one [`Name`], separating them with
1188	/// a colon.
1189	///
1190	/// As an [`NcName`] is always a valid [`Name`], the composition of the
1191	/// two with a `:` as separator is also a valid [`Name`].
1192	///
1193	/// This is the inverse of [`Name::split_name()`].
1194	///
1195	/// # Example
1196	///
1197	/// ```
1198	/// # use rxml_validation::NcName;
1199	/// # use std::convert::TryFrom;
1200	/// let prefix = NcName::try_from("xmlns").unwrap();
1201	/// let localname = NcName::try_from("stream").unwrap();
1202	/// assert_eq!(prefix.add_suffix(&localname), "xmlns:stream");
1203	/// ```
1204	pub fn add_suffix(self, suffix: &NcNameStr) -> Name {
1205		let mut s: String = self.0.into();
1206		s.reserve(suffix.len() + 1);
1207		s.push_str(":");
1208		s.push_str(suffix);
1209		// SAFETY: NcName cannot contain a colon; Name is NcName with colons,
1210		// so we can concat two NcNames to a Name.
1211		unsafe { Name::from_string_unchecked(s) }
1212	}
1213
1214	/// Convert the [`NcName`] into a [`Name`].
1215	///
1216	/// This operation is O(1).
1217	///
1218	/// This operation is also available as implementation of the `Into`
1219	/// trait.
1220	pub fn into_name(self) -> Name {
1221		// SAFETY: NcName is a strict subset of Name
1222		unsafe { Name::from_auto_unchecked(self.0) }
1223	}
1224}
1225
1226#[cfg(feature = "std")]
1227#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1228impl AsRef<NameStr> for NcName {
1229	fn as_ref(&self) -> &NameStr {
1230		<Self as AsRef<NcNameStr>>::as_ref(self).as_ref()
1231	}
1232}
1233
1234#[cfg(feature = "std")]
1235#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1236impl Borrow<NameStr> for NcName {
1237	fn borrow(&self) -> &NameStr {
1238		<Self as Borrow<NcNameStr>>::borrow(self).borrow()
1239	}
1240}
1241
1242impl NcNameStr {
1243	/// Create an owned copy of the string as [`NcName`].
1244	///
1245	/// This operation is also available as implementation of the `Into`
1246	/// trait.
1247	#[cfg(feature = "std")]
1248	#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1249	pub fn to_ncname(&self) -> NcName {
1250		self.into()
1251	}
1252
1253	/// Create an owned copy of the string as [`Name`].
1254	#[cfg(feature = "std")]
1255	#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1256	pub fn to_name(&self) -> Name {
1257		self.to_ncname().into()
1258	}
1259
1260	/// Access the string as [`NameStr`].
1261	///
1262	/// This operation is O(1), as Names are a strict superset of NcNames.
1263	pub fn as_namestr<'x>(&'x self) -> &'x NameStr {
1264		// SAFETY: NcName is a strict subset of Name
1265		unsafe { NameStr::from_str_unchecked(&self.0) }
1266	}
1267
1268	/// Compose two [`NcName`] objects to one [`Name`], separating them with
1269	/// a colon.
1270	///
1271	/// As an [`NcName`] is always a valid [`Name`], the composition of the
1272	/// two with a `:` as separator is also a valid [`Name`].
1273	///
1274	/// This is the inverse of [`Name::split_name()`].
1275	///
1276	/// # Example
1277	///
1278	/// ```
1279	/// # use rxml_validation::NcName;
1280	/// # use std::convert::TryFrom;
1281	/// let prefix = NcName::try_from("xmlns").unwrap();
1282	/// let localname = NcName::try_from("stream").unwrap();
1283	/// assert_eq!(prefix.add_suffix(&localname), "xmlns:stream");
1284	/// ```
1285	#[cfg(feature = "std")]
1286	#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
1287	pub fn with_suffix(&self, suffix: &NcNameStr) -> Name {
1288		let mut s = String::with_capacity(self.len() + 1 + suffix.len());
1289		s.push_str(self);
1290		s.push_str(":");
1291		s.push_str(suffix);
1292		// SAFETY: NcName cannot contain a colon; Name is NcName with colons,
1293		// so we can concat two NcNames to a Name.
1294		unsafe { Name::from_string_unchecked(s) }
1295	}
1296}
1297
1298impl AsRef<NameStr> for NcNameStr {
1299	fn as_ref(&self) -> &NameStr {
1300		// SAFETY: NameStr rules are a superset of the NcNameStr rules.
1301		unsafe { NameStr::from_str_unchecked(&self.0) }
1302	}
1303}
1304
1305impl Borrow<NameStr> for NcNameStr {
1306	fn borrow(&self) -> &NameStr {
1307		self.as_ref()
1308	}
1309}
1310
1311#[cfg(test)]
1312mod tests {
1313	use super::*;
1314
1315	#[test]
1316	fn split_name_on_namestr_with_valid_name() {
1317		let nm: &NameStr = "foo:bar".try_into().unwrap();
1318		let (prefix, localname) = nm.split_name().unwrap();
1319		assert_eq!(prefix.unwrap(), "foo");
1320		assert_eq!(localname, "bar");
1321	}
1322
1323	#[test]
1324	fn split_name_on_namestr_with_prefixless_name() {
1325		let nm: &NameStr = "bar".try_into().unwrap();
1326		let (prefix, localname) = nm.split_name().unwrap();
1327		assert_eq!(prefix, None);
1328		assert_eq!(localname, "bar");
1329	}
1330
1331	#[test]
1332	fn split_name_on_namestr_rejects_localname_with_non_namestart_first_char() {
1333		let nm: &NameStr = "foo:-bar".try_into().unwrap();
1334		let result = nm.split_name();
1335		assert!(matches!(result.err().unwrap(), Error::InvalidLocalName,));
1336	}
1337
1338	#[test]
1339	#[cfg(feature = "std")]
1340	fn split_name_on_name_with_valid_name() {
1341		let nm: Name = "foo:bar".try_into().unwrap();
1342		let (prefix, localname) = nm.split_name().unwrap();
1343		assert_eq!(prefix.unwrap(), "foo");
1344		assert_eq!(localname, "bar");
1345	}
1346
1347	#[test]
1348	#[cfg(feature = "std")]
1349	fn split_name_on_name_with_prefixless_name() {
1350		let nm: Name = "bar".try_into().unwrap();
1351		let (prefix, localname) = nm.split_name().unwrap();
1352		assert_eq!(prefix, None);
1353		assert_eq!(localname, "bar");
1354	}
1355
1356	#[test]
1357	#[cfg(feature = "std")]
1358	fn split_name_on_name_rejects_localname_with_non_namestart_first_char() {
1359		let nm: Name = "foo:-bar".try_into().unwrap();
1360		let result = nm.split_name();
1361		assert!(matches!(result.err().unwrap(), Error::InvalidLocalName,));
1362	}
1363
1364	#[test]
1365	fn split_namestr_on_name_with_valid_name() {
1366		let nm: &NameStr = "foo:bar".try_into().unwrap();
1367		let (prefix, localname) = nm.split_name().unwrap();
1368		assert_eq!(prefix.unwrap(), "foo");
1369		assert_eq!(localname, "bar");
1370	}
1371
1372	#[test]
1373	fn split_namestr_on_name_with_prefixless_name() {
1374		let nm: &NameStr = "bar".try_into().unwrap();
1375		let (prefix, localname) = nm.split_name().unwrap();
1376		assert_eq!(prefix, None);
1377		assert_eq!(localname, "bar");
1378	}
1379
1380	#[test]
1381	fn split_namestr_on_name_rejects_localname_with_non_namestart_first_char() {
1382		let nm: &NameStr = "foo:-bar".try_into().unwrap();
1383		let result = nm.split_name();
1384		assert!(matches!(result.err().unwrap(), Error::InvalidLocalName,));
1385	}
1386
1387	#[test]
1388	#[should_panic(expected = "slice is not a valid NameStr")]
1389	fn namestr_slice_panics_on_non_name_start() {
1390		let x: &NameStr = "foo-bar".try_into().unwrap();
1391		let _: &NameStr = &x[3..];
1392	}
1393
1394	#[test]
1395	#[should_panic(expected = "slice is not a valid NameStr")]
1396	#[cfg(feature = "std")]
1397	fn name_slice_panics_on_non_name_start() {
1398		let x: Name = "foo-bar".try_into().unwrap();
1399		let _: &NameStr = &x[3..];
1400	}
1401
1402	#[test]
1403	#[should_panic(expected = "split string is not a valid Name")]
1404	#[cfg(feature = "std")]
1405	fn name_split_off_refuses_empty_lhs() {
1406		let mut x: Name = "foobar".try_into().unwrap();
1407		x.split_off(0);
1408	}
1409
1410	#[test]
1411	#[should_panic(expected = "split string is not a valid Name")]
1412	#[cfg(feature = "std")]
1413	fn name_split_off_refuses_empty_rhs() {
1414		let mut x: Name = "foobar".try_into().unwrap();
1415		x.split_off(6);
1416	}
1417
1418	#[test]
1419	#[should_panic(expected = "slice is not a valid NcNameStr")]
1420	fn ncnamestr_slice_panics_on_non_name_start() {
1421		let x: &NcNameStr = "foo-bar".try_into().unwrap();
1422		let _: &NcNameStr = &x[3..];
1423	}
1424
1425	#[test]
1426	#[should_panic(expected = "slice is not a valid NcNameStr")]
1427	#[cfg(feature = "std")]
1428	fn ncname_slice_panics_on_non_name_start() {
1429		let x: NcName = "foo-bar".try_into().unwrap();
1430		let _: &NcNameStr = &x[3..];
1431	}
1432
1433	#[test]
1434	fn ncname_refuses_empty_slice() {
1435		match <&str as TryInto<&NcNameStr>>::try_into("") {
1436			Err(_) => (),
1437			other => panic!("unexpected result: {:?}", other),
1438		}
1439	}
1440}