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}