xml_string/names/
name.rs

1//! [`Name`].
2//!
3//! [`Name`]: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name
4
5use core::convert::TryFrom;
6
7use crate::names::chars;
8use crate::names::error::{NameError, TargetNameType};
9use crate::names::{Ncname, Nmtoken, Qname};
10
11/// String slice for [`Name`].
12///
13/// [`Name`]: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name
14#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(transparent)]
16pub struct Name(str);
17
18#[allow(clippy::len_without_is_empty)]
19impl Name {
20    /// Creates a new `&Name`.
21    ///
22    /// # Failures
23    ///
24    /// Fails if the given string is not a valid [`Name`].
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// # use xml_string::names::Name;
30    /// let name = Name::from_str("hello")?;
31    /// assert_eq!(name, "hello");
32    ///
33    /// assert!(Name::from_str("").is_err(), "Empty string is not a Name");
34    /// assert!(Name::from_str("foo bar").is_err(), "Whitespace is not allowed");
35    /// # Ok::<_, xml_string::names::NameError>(())
36    /// ```
37    ///
38    /// [`Name`]: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name
39    // `FromStr` can be implemented only for types with static lifetime.
40    #[allow(clippy::should_implement_trait)]
41    #[inline]
42    pub fn from_str(s: &str) -> Result<&Self, NameError> {
43        <&Self>::try_from(s)
44    }
45
46    /// Creates a new `&Name` without validation.
47    ///
48    /// # Safety
49    ///
50    /// The given string should be a valid [`Name`].
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// # use xml_string::names::Name;
56    /// let name = unsafe {
57    ///     Name::new_unchecked("hello")
58    /// };
59    /// assert_eq!(name, "hello");
60    /// ```
61    ///
62    /// [`Name`]: https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name
63    #[inline]
64    #[must_use]
65    pub unsafe fn new_unchecked(s: &str) -> &Self {
66        &*(s as *const str as *const Self)
67    }
68
69    /// Validates the given string.
70    fn validate(s: &str) -> Result<(), NameError> {
71        let mut chars = s.char_indices();
72
73        // Check the first character.
74        if !chars.next().map_or(false, |(_, c)| chars::is_name_start(c)) {
75            return Err(NameError::new(TargetNameType::Name, 0));
76        }
77
78        // Check the following characters.
79        if let Some((i, _)) = chars.find(|(_, c)| !chars::is_name_continue(*c)) {
80            return Err(NameError::new(TargetNameType::Name, i));
81        }
82
83        Ok(())
84    }
85
86    /// Returns the string as `&str`.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// # use xml_string::names::Name;
92    /// let name = Name::from_str("hello")?;
93    /// assert_eq!(name, "hello");
94    ///
95    /// let s: &str = name.as_str();
96    /// assert_eq!(s, "hello");
97    /// # Ok::<_, xml_string::names::NameError>(())
98    /// ```
99    #[inline]
100    #[must_use]
101    pub fn as_str(&self) -> &str {
102        &self.0
103    }
104
105    /// Returns the length of the string in bytes.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// # use xml_string::names::Name;
111    /// let name = Name::from_str("foo:bar")?;
112    /// assert_eq!(name.len(), 7);
113    /// # Ok::<_, xml_string::names::NameError>(())
114    /// ```
115    #[inline]
116    #[must_use]
117    pub fn len(&self) -> usize {
118        self.0.len()
119    }
120
121    /// Parses the leading `Name` and returns the value and the rest input.
122    ///
123    /// # Exmaples
124    ///
125    /// ```
126    /// # use xml_string::names::Name;
127    /// let input = "hello, world";
128    /// let expected = Name::from_str("hello").expect("valid Name");
129    /// assert_eq!(
130    ///     Name::parse_next(input),
131    ///     Ok((expected, ", world"))
132    /// );
133    /// # Ok::<_, xml_string::names::NameError>(())
134    /// ```
135    ///
136    /// ```
137    /// # use xml_string::names::Name;
138    /// let input = "012";
139    /// assert!(Name::parse_next(input).is_err());
140    /// # Ok::<_, xml_string::names::NameError>(())
141    /// ```
142    pub fn parse_next(s: &str) -> Result<(&Self, &str), NameError> {
143        match Self::from_str(s) {
144            Ok(v) => Ok((v, &s[s.len()..])),
145            Err(e) if e.valid_up_to() == 0 => Err(e),
146            Err(e) => {
147                let valid_up_to = e.valid_up_to();
148                let v = unsafe {
149                    let valid = &s[..valid_up_to];
150                    debug_assert!(Self::validate(valid).is_ok());
151                    // This is safe because the substring is valid.
152                    Self::new_unchecked(valid)
153                };
154                Ok((v, &s[valid_up_to..]))
155            }
156        }
157    }
158
159    /// Converts a `Box<Name>` into a `Box<str>` without copying or allocating.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// # use xml_string::names::Name;
165    /// let name = Name::from_str("name")?;
166    /// let boxed_name: Box<Name> = name.into();
167    /// assert_eq!(&*boxed_name, name);
168    /// let boxed_str: Box<str> = boxed_name.into_boxed_str();
169    /// assert_eq!(&*boxed_str, name.as_str());
170    /// # Ok::<_, xml_string::names::NameError>(())
171    /// ```
172    #[cfg(feature = "alloc")]
173    pub fn into_boxed_str(self: alloc::boxed::Box<Self>) -> Box<str> {
174        unsafe {
175            // This is safe because `Name` has the same memory layout as `str`
176            // (thanks to `#[repr(transparent)]`).
177            alloc::boxed::Box::<str>::from_raw(alloc::boxed::Box::<Self>::into_raw(self) as *mut str)
178        }
179    }
180}
181
182impl_traits_for_custom_string_slice!(Name);
183
184impl AsRef<Nmtoken> for Name {
185    #[inline]
186    fn as_ref(&self) -> &Nmtoken {
187        unsafe {
188            debug_assert!(
189                Nmtoken::from_str(self.as_str()).is_ok(),
190                "Name {:?} must be a valid Nmtoken",
191                self.as_str()
192            );
193            // This is safe because a Name is also a valid Nmtoken.
194            Nmtoken::new_unchecked(self.as_str())
195        }
196    }
197}
198
199impl<'a> From<&'a Ncname> for &'a Name {
200    #[inline]
201    fn from(s: &'a Ncname) -> Self {
202        s.as_ref()
203    }
204}
205
206impl<'a> From<&'a Qname> for &'a Name {
207    #[inline]
208    fn from(s: &'a Qname) -> Self {
209        s.as_ref()
210    }
211}
212
213impl<'a> TryFrom<&'a str> for &'a Name {
214    type Error = NameError;
215
216    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
217        Name::validate(s)?;
218        Ok(unsafe {
219            // This is safe because the string is validated.
220            Name::new_unchecked(s)
221        })
222    }
223}
224
225impl<'a> TryFrom<&'a Nmtoken> for &'a Name {
226    type Error = NameError;
227
228    fn try_from(s: &'a Nmtoken) -> Result<Self, Self::Error> {
229        let first = s
230            .as_str()
231            .chars()
232            .next()
233            .expect("Should never fail: Nmtoken is not empty");
234        if !chars::is_name_start(first) {
235            return Err(NameError::new(TargetNameType::Name, 0));
236        }
237
238        Ok(unsafe {
239            // This is safe because a Nmtoken starting with NameStartChar is also a valid Name.
240            debug_assert!(Name::validate(s.as_str()).is_ok());
241            Name::new_unchecked(s.as_str())
242        })
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    fn ensure_eq(s: &str) {
251        assert_eq!(
252            Name::from_str(s).expect("Should not fail"),
253            s,
254            "String: {:?}",
255            s
256        );
257    }
258
259    fn ensure_error_at(s: &str, valid_up_to: usize) {
260        let err = Name::from_str(s).expect_err("Should fail");
261        assert_eq!(err.valid_up_to(), valid_up_to, "String: {:?}", s);
262    }
263
264    #[test]
265    fn name_str_valid() {
266        ensure_eq("hello");
267        ensure_eq("abc123");
268        ensure_eq("foo:bar");
269        ensure_eq(":foo");
270        ensure_eq("foo:");
271    }
272
273    #[test]
274    fn name_str_invalid() {
275        ensure_error_at("", 0);
276        ensure_error_at("-foo", 0);
277        ensure_error_at("0foo", 0);
278        ensure_error_at("foo bar", 3);
279        ensure_error_at("foo/bar", 3);
280    }
281}