xml_string/names/
uri_qualified_name.rs

1//! [`URIQualifiedName`].
2//!
3//! [`URIQualifiedName`]:
4//!     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
5use core::convert::TryFrom;
6use core::fmt;
7use core::num::NonZeroUsize;
8
9use crate::names::error::{NameError, TargetNameType};
10use crate::names::{Eqname, Ncname};
11
12/// String slice for [`URIQualifiedName`].
13///
14/// [`URIQualifiedName`]:
15///     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
16#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(transparent)]
18pub struct UriQualifiedName(str);
19
20#[allow(clippy::len_without_is_empty)]
21impl UriQualifiedName {
22    /// Creates a new `&UriQualifiedName`.
23    ///
24    /// [`URIQualifiedName`] has `Q{uri}ncname` format.
25    /// `UriQualifiedName` type validates NCName part, but does not validate URI part.
26    ///
27    /// > In most contexts, processors are not required to raise errors if a URI
28    /// > is not lexically valid according to [RFC3986] and [RFC3987].
29    /// > See [2.4.5 URI Literals][XPATH31-2.4.5] for details.
30    /// >
31    /// > --- [XML Path Language (XPath) 3.1, 2 Basics][XPATH31-2]
32    ///
33    /// > XPath 3.1 requires a statically known, valid URI in a BracedURILiteral.
34    /// > An implementation may raise a static error err:XQST0046 if the value
35    /// > of a Braced URI Literal is of nonzero length and is neither an
36    /// > absolute URI nor a relative URI.
37    /// >
38    /// > --- [XML Path Language (XPath) 3.1, 2.4.5 URI Literals][XPATH31-2.4.5]
39    ///
40    /// It is user's responsibility to validate URI part if necessary.
41    ///
42    /// # Failures
43    ///
44    /// Fails if the given string is not a valid [`URIQualifiedName`].
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// # use xml_string::names::UriQualifiedName;
50    /// let name = UriQualifiedName::from_str("Q{http://example.com/}name")?;
51    /// assert_eq!(name, "Q{http://example.com/}name");
52    ///
53    /// assert_eq!(
54    ///     UriQualifiedName::from_str("Q{}name")?,
55    ///     "Q{}name",
56    ///     "Empty URI is OK"
57    /// );
58    /// assert_eq!(
59    ///     UriQualifiedName::from_str("Q{foo}bar")?,
60    ///     "Q{foo}bar",
61    ///     "URI is not validated"
62    /// );
63    ///
64    /// assert!(
65    ///     UriQualifiedName::from_str("foo").is_err(),
66    ///     "URIQualifiedName has `Q{{uri}}ncname` format"
67    /// );
68    /// assert!(
69    ///     UriQualifiedName::from_str("Q{http://example.com}foo:bar").is_err(),
70    ///     "Colon is not allowed"
71    /// );
72    /// assert!(
73    ///     UriQualifiedName::from_str("Q{foo{bar}qux").is_err(),
74    ///     "URI part cannot have `{{` and `}}`"
75    /// );
76    /// # Ok::<_, xml_string::names::NameError>(())
77    /// ```
78    ///
79    /// [`URIQualifiedName`]:
80    ///     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
81    /// [RFC3986]: https://tools.ietf.org/html/rfc3986
82    /// [RFC3987]: https://tools.ietf.org/html/rfc3987
83    /// [XPATH31-2]: https://www.w3.org/TR/2017/REC-xpath-31-20170321/#id-basics
84    /// [XPATH31-2.4.5]: https://www.w3.org/TR/2017/REC-xpath-31-20170321/#id-uri-literals
85    // `FromStr` can be implemented only for types with static lifetime.
86    #[allow(clippy::should_implement_trait)]
87    pub fn from_str(s: &str) -> Result<&Self, NameError> {
88        <&Self>::try_from(s)
89    }
90
91    /// Creates a new `&UriQualifiedName` without validation.
92    ///
93    /// # Safety
94    ///
95    /// The given string should be a valid [`URIQualifiedName`].
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// # use xml_string::names::UriQualifiedName;
101    /// let name = unsafe {
102    ///     UriQualifiedName::new_unchecked("Q{foo}bar")
103    /// };
104    /// assert_eq!(name, "Q{foo}bar");
105    /// ```
106    ///
107    /// [`URIQualifiedName`]:
108    ///     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
109    #[inline]
110    #[must_use]
111    pub unsafe fn new_unchecked(s: &str) -> &Self {
112        &*(s as *const str as *const Self)
113    }
114
115    /// Validates the given string.
116    fn validate(s: &str) -> Result<(), NameError> {
117        match Self::parse_as_possible(s) {
118            Ok(_) => Ok(()),
119            Err(e) => Err(NameError::new(
120                TargetNameType::UriQualifiedName,
121                e.map_or(0, |(_local_name_start, valid_up_to)| valid_up_to.get()),
122            )),
123        }
124    }
125
126    /// Parses the given string from the beginning as possible.
127    ///
128    /// Retruns `Ok(local_name_start)` if the string is valid URIQualifiedName.
129    /// Returns `Err(None)` if the string is completely invalid.
130    /// Returns `Err(Some((local_name_start, valid_up_to)))` if the string is invalid
131    /// but has valid substring as the prefix.
132    pub(super) fn parse_as_possible(
133        s: &str,
134    ) -> Result<NonZeroUsize, Option<(NonZeroUsize, NonZeroUsize)>> {
135        let uri_and_rest = s.strip_prefix("Q{").ok_or(None)?;
136        let uri_len = match uri_and_rest.find(|c| c == '{' || c == '}') {
137            Some(pos) if uri_and_rest.as_bytes()[pos] == b'}' => pos,
138            _ => return Err(None),
139        };
140
141        let local_name_start = NonZeroUsize::new(uri_len + 3).expect("Should never be zero");
142        let local_name = &s[local_name_start.get()..];
143        match Ncname::from_str(local_name) {
144            Ok(_) => Ok(local_name_start),
145            Err(e) if e.valid_up_to() == 0 => Err(None),
146            Err(e) => Err(Some((
147                local_name_start,
148                NonZeroUsize::new(local_name_start.get() + e.valid_up_to())
149                    .expect("Should never be zero"),
150            ))),
151        }
152    }
153
154    /// Returns the string as `&str`.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// # use xml_string::names::UriQualifiedName;
160    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
161    /// assert_eq!(name, "Q{foo}bar");
162    ///
163    /// let s: &str = name.as_str();
164    /// assert_eq!(s, "Q{foo}bar");
165    /// # Ok::<_, xml_string::names::NameError>(())
166    /// ```
167    #[inline]
168    #[must_use]
169    pub fn as_str(&self) -> &str {
170        &self.0
171    }
172
173    /// Returns the length of the string in bytes.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// # use xml_string::names::UriQualifiedName;
179    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
180    /// assert_eq!(name.len(), "Q{foo}bar".len());
181    /// # Ok::<_, xml_string::names::NameError>(())
182    /// ```
183    #[inline]
184    #[must_use]
185    pub fn len(&self) -> usize {
186        self.0.len()
187    }
188
189    /// Parses the leading `UriQualifiedName` and returns the value and the rest input.
190    ///
191    /// # Exmaples
192    ///
193    /// ```
194    /// # use xml_string::names::UriQualifiedName;
195    /// let input = "Q{foo}bar:012";
196    /// let expected = UriQualifiedName::from_str("Q{foo}bar")
197    ///     .expect("valid UriQualifiedName");
198    /// assert_eq!(
199    ///     UriQualifiedName::parse_next(input),
200    ///     Ok((expected, ":012"))
201    /// );
202    /// # Ok::<_, xml_string::names::NameError>(())
203    /// ```
204    ///
205    /// ```
206    /// # use xml_string::names::UriQualifiedName;
207    /// let input = "012";
208    /// assert!(UriQualifiedName::parse_next(input).is_err());
209    /// # Ok::<_, xml_string::names::NameError>(())
210    /// ```
211    pub fn parse_next(s: &str) -> Result<(&Self, &str), NameError> {
212        match Self::from_str(s) {
213            Ok(v) => Ok((v, &s[s.len()..])),
214            Err(e) if e.valid_up_to() == 0 => Err(e),
215            Err(e) => {
216                let valid_up_to = e.valid_up_to();
217                let v = unsafe {
218                    let valid = &s[..valid_up_to];
219                    debug_assert!(Self::validate(valid).is_ok());
220                    // This is safe because the substring is valid.
221                    Self::new_unchecked(valid)
222                };
223                Ok((v, &s[valid_up_to..]))
224            }
225        }
226    }
227
228    /// Returns the position where the local name starts.
229    ///
230    /// Note that this is O(length) operation.
231    #[must_use]
232    fn local_name_start(&self) -> NonZeroUsize {
233        // Find `[2..]` since the first two characters are `Q{`.
234        let pos = self.as_str()[2..]
235            .find('}')
236            .expect("Should never fail: Valid URIQualifiedName has `}` character")
237            + 3;
238        NonZeroUsize::new(pos)
239            .expect("Should never fail: URIQualifiedName cannot start with the local name")
240    }
241
242    /// Returns the URI.
243    ///
244    /// Note that this is O(length) operation.
245    /// Consider using [`ParsedUriQualifiedName::uri`] method if possible.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// # use xml_string::names::UriQualifiedName;
251    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
252    /// assert_eq!(name.uri(), "foo");
253    ///
254    /// let empty_uri = UriQualifiedName::from_str("Q{}foo")?;
255    /// assert_eq!(empty_uri.uri(), "");
256    /// # Ok::<_, xml_string::names::NameError>(())
257    /// ```
258    #[inline]
259    #[must_use]
260    pub fn uri(&self) -> &str {
261        ParsedUriQualifiedName::new(self, self.local_name_start()).uri()
262    }
263
264    /// Returns the local name.
265    ///
266    /// Note that this is O(length) operation.
267    /// Consider using [`ParsedUriQualifiedName::local_name`] method if possible.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// # use xml_string::names::UriQualifiedName;
273    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
274    /// assert_eq!(name.local_name(), "bar");
275    /// # Ok::<_, xml_string::names::NameError>(())
276    /// ```
277    #[inline]
278    #[must_use]
279    pub fn local_name(&self) -> &Ncname {
280        ParsedUriQualifiedName::new(self, self.local_name_start()).local_name()
281    }
282
283    /// Returns a pair of the uri and the local name.
284    ///
285    /// Note that this is O(length) operation.
286    /// Consider using [`ParsedUriQualifiedName::uri_and_local`] method if possible.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// # use xml_string::names::UriQualifiedName;
292    /// use std::convert::TryFrom;
293    ///
294    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
295    /// assert_eq!(name.uri_and_local(), (name.uri(), name.local_name()));
296    /// # Ok::<_, xml_string::names::NameError>(())
297    /// ```
298    #[inline]
299    #[must_use]
300    pub fn uri_and_local(&self) -> (&str, &Ncname) {
301        ParsedUriQualifiedName::new(self, self.local_name_start()).uri_and_local()
302    }
303
304    /// Converts a `Box<UriQualifiedName>` into a `Box<str>` without copying or allocating.
305    ///
306    /// # Examples
307    ///
308    /// ```
309    /// # use xml_string::names::UriQualifiedName;
310    /// let name = UriQualifiedName::from_str("Q{foo}bar")?;
311    /// let boxed_name: Box<UriQualifiedName> = name.into();
312    /// assert_eq!(&*boxed_name, name);
313    /// let boxed_str: Box<str> = boxed_name.into_boxed_str();
314    /// assert_eq!(&*boxed_str, name.as_str());
315    /// # Ok::<_, xml_string::names::NameError>(())
316    /// ```
317    #[cfg(feature = "alloc")]
318    pub fn into_boxed_str(self: alloc::boxed::Box<Self>) -> Box<str> {
319        unsafe {
320            // This is safe because `UriQualifiedName` has the same memory layout as `str`
321            // (thanks to `#[repr(transparent)]`).
322            alloc::boxed::Box::<str>::from_raw(alloc::boxed::Box::<Self>::into_raw(self) as *mut str)
323        }
324    }
325}
326
327impl_traits_for_custom_string_slice!(UriQualifiedName);
328
329impl AsRef<Eqname> for UriQualifiedName {
330    #[inline]
331    fn as_ref(&self) -> &Eqname {
332        unsafe {
333            debug_assert!(
334                Eqname::from_str(self.as_str()).is_ok(),
335                "URIQualifiedName {:?} must be a valid Eqname",
336                self.as_str()
337            );
338            // This is safe because a URIQualifiedName is also a valid Eqname.
339            Eqname::new_unchecked(self.as_str())
340        }
341    }
342}
343
344impl<'a> From<ParsedUriQualifiedName<'a>> for &'a UriQualifiedName {
345    #[inline]
346    fn from(s: ParsedUriQualifiedName<'a>) -> Self {
347        s.content
348    }
349}
350
351impl<'a> TryFrom<&'a str> for &'a UriQualifiedName {
352    type Error = NameError;
353
354    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
355        UriQualifiedName::validate(s)?;
356        Ok(unsafe {
357            // This is safe because the string is validated.
358            UriQualifiedName::new_unchecked(s)
359        })
360    }
361}
362
363/// Parsed [`URIQualifiedName`] reference.
364///
365/// [`URIQualifiedName`]:
366///     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
367#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
368pub struct ParsedUriQualifiedName<'a> {
369    /// Content string.
370    content: &'a UriQualifiedName,
371    /// Start position of the local name.
372    local_name_start: NonZeroUsize,
373}
374
375#[allow(clippy::len_without_is_empty)]
376impl<'a> ParsedUriQualifiedName<'a> {
377    /// Creates a new `ParsedUriQualifiedName`.
378    ///
379    /// # Panics
380    ///
381    /// Panics if the `local_name_start` does not point to the start position of
382    /// the local name.
383    #[must_use]
384    pub(super) fn new(content: &'a UriQualifiedName, local_name_start: NonZeroUsize) -> Self {
385        if content.as_str().as_bytes()[local_name_start.get() - 1] != b'}' {
386            panic!(
387                "`local_name_pos` (={:?}) should point to the next position
388                 of the `}}` character in the URIQualifiedName {:?}",
389                local_name_start.get(),
390                content
391            );
392        }
393        Self {
394            content,
395            local_name_start,
396        }
397    }
398
399    /// Creates a new `ParsedUriQualifiedName<'_>` from the given string slice.
400    ///
401    /// # Failures
402    ///
403    /// Fails if the given string is not a valid [`URIQualifiedName`].
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// # use xml_string::names::ParsedUriQualifiedName;
409    /// let name = ParsedUriQualifiedName::from_str("Q{http://example.com/}name")?;
410    /// assert_eq!(name, "Q{http://example.com/}name");
411    ///
412    /// assert_eq!(
413    ///     ParsedUriQualifiedName::from_str("Q{}name")?,
414    ///     "Q{}name",
415    ///     "Empty URI is OK"
416    /// );
417    /// assert_eq!(
418    ///     ParsedUriQualifiedName::from_str("Q{foo}bar")?,
419    ///     "Q{foo}bar",
420    ///     "URI is not validated"
421    /// );
422    ///
423    /// assert!(
424    ///     ParsedUriQualifiedName::from_str("foo").is_err(),
425    ///     "URIQualifiedName has `Q{{uri}}ncname` format"
426    /// );
427    /// assert!(
428    ///     ParsedUriQualifiedName::from_str("Q{http://example.com}foo:bar").is_err(),
429    ///     "Colon is not allowed"
430    /// );
431    /// assert!(
432    ///     ParsedUriQualifiedName::from_str("Q{foo{bar}qux").is_err(),
433    ///     "URI part cannot have `{{` and `}}`"
434    /// );
435    /// # Ok::<_, xml_string::names::NameError>(())
436    /// ```
437    ///
438    /// [`URIQualifiedName`]:
439    ///     https://www.w3.org/TR/2017/REC-xpath-31-20170321/#prod-xpath31-URIQualifiedName
440    // `FromStr` can be implemented only for types with static lifetime.
441    #[allow(clippy::should_implement_trait)]
442    #[inline]
443    pub fn from_str(s: &'a str) -> Result<Self, NameError> {
444        Self::try_from(s)
445    }
446
447    /// Returns the string as `&UriQualifiedName`.
448    ///
449    /// # Exmaples
450    ///
451    /// ```
452    /// # use xml_string::names::ParsedUriQualifiedName;
453    /// use xml_string::names::UriQualifiedName;
454    ///
455    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
456    /// assert_eq!(name, "Q{foo}bar");
457    ///
458    /// let s: &UriQualifiedName = name.as_uri_qualified_name();
459    /// assert_eq!(s, "Q{foo}bar");
460    /// # Ok::<_, xml_string::names::NameError>(())
461    /// ```
462    #[inline]
463    #[must_use]
464    pub fn as_uri_qualified_name(&self) -> &'a UriQualifiedName {
465        self.content
466    }
467
468    /// Returns the string as `&str`.
469    ///
470    /// # Exmaples
471    ///
472    /// ```
473    /// # use xml_string::names::ParsedUriQualifiedName;
474    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
475    /// assert_eq!(name, "Q{foo}bar");
476    ///
477    /// let s: &str = name.as_str();
478    /// assert_eq!(s, "Q{foo}bar");
479    /// # Ok::<_, xml_string::names::NameError>(())
480    /// ```
481    #[inline]
482    #[must_use]
483    pub fn as_str(&self) -> &'a str {
484        self.content.as_str()
485    }
486
487    /// Returns the length of the string in bytes.
488    ///
489    /// # Examples
490    ///
491    /// ```
492    /// # use xml_string::names::ParsedUriQualifiedName;
493    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
494    /// assert_eq!(name.len(), "Q{foo}bar".len());
495    /// # Ok::<_, xml_string::names::NameError>(())
496    /// ```
497    #[inline]
498    #[must_use]
499    pub fn len(&self) -> usize {
500        self.content.len()
501    }
502
503    /// Returns the URI.
504    ///
505    /// # Examples
506    ///
507    /// ```
508    /// # use xml_string::names::ParsedUriQualifiedName;
509    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
510    /// assert_eq!(name.uri(), "foo");
511    ///
512    /// let empty_uri = ParsedUriQualifiedName::from_str("Q{}foo")?;
513    /// assert_eq!(empty_uri.uri(), "");
514    /// # Ok::<_, xml_string::names::NameError>(())
515    /// ```
516    #[must_use]
517    pub fn uri(&self) -> &'a str {
518        &self.as_str()[2..(self.local_name_start.get() - 1)]
519    }
520
521    /// Returns the local name.
522    ///
523    /// # Examples
524    ///
525    /// ```
526    /// # use xml_string::names::ParsedUriQualifiedName;
527    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
528    /// assert_eq!(name.local_name(), "bar");
529    /// # Ok::<_, xml_string::names::NameError>(())
530    /// ```
531    #[must_use]
532    pub fn local_name(&self) -> &'a Ncname {
533        let local_name = &self.as_str()[self.local_name_start.get()..];
534        unsafe {
535            debug_assert!(
536                Ncname::from_str(local_name).is_ok(),
537                "The local name {:?} must be a valid NCName",
538                local_name
539            );
540            // This is safe because the local name is a valid NCName.
541            Ncname::new_unchecked(local_name)
542        }
543    }
544
545    /// Returns a pair of the URI and the local name.
546    ///
547    /// This is efficient version of `(self.uri(), self.local_name())`.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// # use xml_string::names::ParsedUriQualifiedName;
553    /// use std::convert::TryFrom;
554    ///
555    /// let name = ParsedUriQualifiedName::from_str("Q{foo}bar")?;
556    /// assert_eq!(name.uri_and_local(), (name.uri(), name.local_name()));
557    /// # Ok::<_, xml_string::names::NameError>(())
558    /// ```
559    #[must_use]
560    pub fn uri_and_local(&self) -> (&'a str, &'a Ncname) {
561        let local_name_start = self.local_name_start.get();
562        let uri = &self.as_str()[2..(local_name_start - 1)];
563        let local_name = &self.as_str()[local_name_start..];
564        let local_name = unsafe {
565            debug_assert!(
566                Ncname::from_str(local_name).is_ok(),
567                "The local name {:?} must be a valid NCName",
568                local_name
569            );
570            // This is safe because the local name is a valid NCName.
571            Ncname::new_unchecked(local_name)
572        };
573        (uri, local_name)
574    }
575}
576
577impl PartialEq<str> for ParsedUriQualifiedName<'_> {
578    #[inline]
579    fn eq(&self, other: &str) -> bool {
580        self.as_str() == other
581    }
582}
583impl_cmp!(str, ParsedUriQualifiedName<'_>);
584
585impl PartialEq<&'_ str> for ParsedUriQualifiedName<'_> {
586    #[inline]
587    fn eq(&self, other: &&str) -> bool {
588        self.as_str() == *other
589    }
590}
591impl_cmp!(&str, ParsedUriQualifiedName<'_>);
592
593impl PartialEq<str> for &'_ ParsedUriQualifiedName<'_> {
594    #[inline]
595    fn eq(&self, other: &str) -> bool {
596        self.as_str() == other
597    }
598}
599impl_cmp!(str, &ParsedUriQualifiedName<'_>);
600
601#[cfg(feature = "alloc")]
602impl PartialEq<alloc::string::String> for ParsedUriQualifiedName<'_> {
603    #[inline]
604    fn eq(&self, other: &alloc::string::String) -> bool {
605        self.as_str() == *other
606    }
607}
608#[cfg(feature = "alloc")]
609impl_cmp!(alloc::string::String, ParsedUriQualifiedName<'_>);
610
611#[cfg(feature = "alloc")]
612impl PartialEq<&alloc::string::String> for ParsedUriQualifiedName<'_> {
613    #[inline]
614    fn eq(&self, other: &&alloc::string::String) -> bool {
615        self.as_str() == **other
616    }
617}
618#[cfg(feature = "alloc")]
619impl_cmp!(&alloc::string::String, ParsedUriQualifiedName<'_>);
620
621#[cfg(feature = "alloc")]
622impl PartialEq<alloc::boxed::Box<str>> for ParsedUriQualifiedName<'_> {
623    #[inline]
624    fn eq(&self, other: &alloc::boxed::Box<str>) -> bool {
625        self.as_str() == other.as_ref()
626    }
627}
628#[cfg(feature = "alloc")]
629impl_cmp!(alloc::boxed::Box<str>, ParsedUriQualifiedName<'_>);
630
631#[cfg(feature = "alloc")]
632impl PartialEq<alloc::borrow::Cow<'_, str>> for ParsedUriQualifiedName<'_> {
633    #[inline]
634    fn eq(&self, other: &alloc::borrow::Cow<'_, str>) -> bool {
635        self.as_str() == *other
636    }
637}
638#[cfg(feature = "alloc")]
639impl_cmp!(alloc::borrow::Cow<'_, str>, ParsedUriQualifiedName<'_>);
640
641impl AsRef<str> for ParsedUriQualifiedName<'_> {
642    #[inline]
643    fn as_ref(&self) -> &str {
644        self.as_str()
645    }
646}
647
648impl AsRef<UriQualifiedName> for ParsedUriQualifiedName<'_> {
649    #[inline]
650    fn as_ref(&self) -> &UriQualifiedName {
651        self.content
652    }
653}
654
655impl AsRef<Eqname> for ParsedUriQualifiedName<'_> {
656    #[inline]
657    fn as_ref(&self) -> &Eqname {
658        self.content.as_ref()
659    }
660}
661
662impl<'a> From<&'a UriQualifiedName> for ParsedUriQualifiedName<'a> {
663    fn from(s: &'a UriQualifiedName) -> Self {
664        let local_name_start = s.local_name_start();
665        Self {
666            content: s,
667            local_name_start,
668        }
669    }
670}
671
672impl<'a> TryFrom<&'a str> for ParsedUriQualifiedName<'a> {
673    type Error = NameError;
674
675    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
676        match UriQualifiedName::parse_as_possible(s) {
677            Ok(local_name_start) => {
678                let content = unsafe {
679                    // This is safe because the string is validated by
680                    // `UriQualifiedName::parse_as_possible()`.
681                    UriQualifiedName::new_unchecked(s)
682                };
683                Ok(Self {
684                    content,
685                    local_name_start,
686                })
687            }
688            Err(e) => Err(NameError::new(
689                TargetNameType::UriQualifiedName,
690                e.map_or(0, |(_local_name_start, valid_up_to)| valid_up_to.get()),
691            )),
692        }
693    }
694}
695
696impl fmt::Debug for ParsedUriQualifiedName<'_> {
697    #[inline]
698    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699        f.write_str(self.as_str())
700    }
701}
702
703impl fmt::Display for ParsedUriQualifiedName<'_> {
704    #[inline]
705    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
706        f.write_str(self.as_str())
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713
714    fn ncname(s: &str) -> &Ncname {
715        Ncname::from_str(s)
716            .unwrap_or_else(|e| panic!("Failed to cerate Ncname from {:?}: {}", s, e))
717    }
718
719    fn uqn(s: &str) -> &UriQualifiedName {
720        UriQualifiedName::from_str(s)
721            .unwrap_or_else(|e| panic!("Failed to create UriQualifiedName from {:?}: {}", s, e))
722    }
723
724    fn parsed_uqn(s: &str) -> ParsedUriQualifiedName<'_> {
725        ParsedUriQualifiedName::from_str(s).unwrap_or_else(|e| {
726            panic!(
727                "Failed to create ParsedUriQualifiedName from {:?}: {}",
728                s, e
729            )
730        })
731    }
732
733    fn ensure_eq(s: &str) {
734        assert_eq!(
735            UriQualifiedName::from_str(s).expect("Should not fail"),
736            s,
737            "String: {:?}",
738            s
739        );
740    }
741
742    fn ensure_error_at(s: &str, valid_up_to: usize) {
743        let err = UriQualifiedName::from_str(s).expect_err("Should fail");
744        assert_eq!(err.valid_up_to(), valid_up_to, "String: {:?}", s);
745    }
746
747    #[test]
748    fn uqname_str_valid() {
749        ensure_eq("Q{}local");
750        ensure_eq("Q{foo}bar");
751        ensure_eq("Q{http://example.com/}local");
752    }
753
754    #[test]
755    fn uqname_str_invalid() {
756        ensure_error_at("", 0);
757        ensure_error_at("Q", 0);
758        ensure_error_at("Q{", 0);
759        ensure_error_at("Q{}", 0);
760        ensure_error_at("Q{}:", 0);
761        ensure_error_at("Q{}foo:", 6);
762        ensure_error_at("Q{}foo:bar", 6);
763        ensure_error_at("Q{foo}bar:baz", 9);
764        ensure_error_at("Q{foo}bar}baz", 9);
765        ensure_error_at("Q{foo{bar}baz", 0);
766    }
767
768    #[test]
769    fn parse_as_possible() {
770        assert_eq!(
771            UriQualifiedName::parse_as_possible("Q{}bar"),
772            Ok(NonZeroUsize::new(3).expect("Should never fail: not zero"))
773        );
774        assert_eq!(
775            UriQualifiedName::parse_as_possible("Q{foo}bar"),
776            Ok(NonZeroUsize::new(6).expect("Should never fail: not zero"))
777        );
778
779        assert_eq!(UriQualifiedName::parse_as_possible(""), Err(None));
780        assert_eq!(
781            UriQualifiedName::parse_as_possible("Q{}foo:bar"),
782            Err(NonZeroUsize::new(3).zip(NonZeroUsize::new(6)))
783        );
784        assert_eq!(
785            UriQualifiedName::parse_as_possible("Q{foo}bar:baz"),
786            Err(NonZeroUsize::new(6).zip(NonZeroUsize::new(9)))
787        );
788    }
789
790    #[test]
791    fn parsed_uri_qualified_name_from_str() {
792        assert_eq!(
793            ParsedUriQualifiedName::from_str("Q{foo}bar").map(|v| v.as_uri_qualified_name()),
794            Ok(uqn("Q{foo}bar"))
795        );
796
797        assert_eq!(
798            ParsedUriQualifiedName::from_str("Q{foo}:bar"),
799            Err(NameError::new(TargetNameType::UriQualifiedName, 0))
800        );
801
802        assert_eq!(
803            ParsedUriQualifiedName::from_str("Q{foo}bar:baz"),
804            Err(NameError::new(TargetNameType::UriQualifiedName, 9))
805        );
806    }
807
808    #[test]
809    fn parsed_uri_qualified_name_uri() {
810        assert_eq!(parsed_uqn("Q{}foo").uri(), "");
811        assert_eq!(parsed_uqn("Q{foo}bar").uri(), "foo");
812    }
813
814    #[test]
815    fn parsed_uri_qualified_name_local_name() {
816        assert_eq!(parsed_uqn("Q{}foo").local_name(), ncname("foo"));
817        assert_eq!(parsed_uqn("Q{foo}bar").local_name(), ncname("bar"));
818    }
819
820    #[test]
821    fn parsed_uri_qualified_name_uri_and_local() {
822        assert_eq!(parsed_uqn("Q{}foo").uri_and_local(), ("", ncname("foo")));
823        assert_eq!(
824            parsed_uqn("Q{foo}bar").uri_and_local(),
825            ("foo", ncname("bar"))
826        );
827    }
828}