iri_string/normalize.rs
1//! Normalization.
2//!
3//! # IRI normalization (and resolution) can fail
4//!
5//! Though this is not explicitly stated in RFC 3986, IRI normalization can fail.
6//! For example, `foo:.///bar`, `foo:./..//bar`, and `foo:/..//bar` are all
7//! normalized to `foo://bar` as a string. However, IRI without authority (note
8//! that this is different from "with empty authority") cannot have a path
9//! starting with `//`, since it is ambiguous and can be interpreted as an IRI
10//! with authority. So, `foo://bar` is decomposed as scheme `foo`, authority
11//! `bar`, and empty path. The expected result is the combination of scheme
12//! `foo`, no authority, and path `//bar` (though this is not possible to
13//! serialize), so the algorithm fails as it cannot return the intended result.
14//!
15//! IRI resolution can also fail since it (conditionally) invokes normalization
16//! during the resolution process. For example, resolving a reference `.///bar`
17//! or `/..//bar` against the base `foo:` fail.
18//!
19//! Thus, IRI resolution can fail for some abnormal cases.
20//!
21//! Note that this kind of failure can happen only when the base IRI has no
22//! authority and empty path. This would be rare in the wild, since many people
23//! would use an IRI with authority part, such as `http://`.
24//!
25//! If you are handling `scheme://`-style URIs and IRIs, don't worry about the
26//! failure. Currently no cases are known to fail when at least one of the base
27//! IRI or the relative IRI contains authorities.
28//!
29//! To know what will happen on resolution failure, see the module documentation
30//! for [`resolve`][`crate::resolve`].
31//!
32//! ## Examples
33//!
34//! ### Normalization failure
35//!
36//! ```
37//! # #[cfg(feature = "alloc")] {
38//! use iri_string::normalize::Error;
39//! use iri_string::types::{IriAbsoluteStr, IriReferenceStr};
40//!
41//! let base = IriAbsoluteStr::new("foo:.///bar")?;
42//! assert!(
43//! base.normalize().ensure_rfc3986_normalizable().is_err(),
44//! "this normalization should fails without WAHTWG URL Standard serialization"
45//! );
46//! # }
47//! # Ok::<_, iri_string::validate::Error>(())
48//! ```
49//!
50//! ### Resolution failure
51//!
52//! ```
53//! # #[cfg(feature = "alloc")] {
54//! use iri_string::types::{IriAbsoluteStr, IriReferenceStr};
55//!
56//! let base = IriAbsoluteStr::new("scheme:")?;
57//! {
58//! let reference = IriReferenceStr::new(".///bar")?;
59//! let result = reference.resolve_against(base)
60//! .ensure_rfc3986_normalizable();
61//! assert!(result.is_err());
62//! }
63//!
64//! {
65//! let reference2 = IriReferenceStr::new("/..//bar")?;
66//! // Resulting string will be `scheme://bar`, but `bar` should be a path
67//! // segment, not a host. So, the semantically correct target IRI cannot
68//! // be represented.
69//! let result2 = reference2.resolve_against(base)
70//! .ensure_rfc3986_normalizable();
71//! assert!(result2.is_err());
72//! }
73//! # }
74//! # Ok::<_, iri_string::validate::Error>(())
75//! ```
76
77mod error;
78mod path;
79mod pct_case;
80
81use core::fmt::{self, Display as _, Write as _};
82use core::marker::PhantomData;
83
84#[cfg(feature = "alloc")]
85use alloc::collections::TryReserveError;
86
87use crate::components::{RiReferenceComponents, Splitter};
88#[cfg(feature = "alloc")]
89use crate::format::{ToDedicatedString, ToStringFallible};
90use crate::parser::str::rfind_split_hole;
91use crate::parser::trusted::is_ascii_only_host;
92use crate::spec::Spec;
93use crate::types::{RiAbsoluteStr, RiReferenceStr, RiStr};
94#[cfg(feature = "alloc")]
95use crate::types::{RiAbsoluteString, RiString};
96
97pub use self::error::Error;
98pub(crate) use self::path::{Path, PathCharacteristic, PathToNormalize};
99pub(crate) use self::pct_case::{
100 is_pct_case_normalized, NormalizedAsciiOnlyHost, PctCaseNormalized,
101};
102
103/// Normalization algorithm.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub(crate) enum NormalizationMode {
106 /// No normalization.
107 None,
108 /// Default normalization mode.
109 ///
110 /// Applies RFC 3986 normalization whenever possible. When not possible,
111 /// applies serialization algorithm defined in WHATWG URL standard.
112 Default,
113 /// WHATWG-like normalization mode.
114 ///
115 /// Preserves relative path as is (modulo case/pct normalization) when the
116 /// authority component is absent.
117 PreserveAuthoritylessRelativePath,
118}
119
120impl NormalizationMode {
121 /// Returns true if case normalization and percent-encoding normalization should be applied.
122 ///
123 /// Note that even when this option is `true`, plain US-ASCII characters
124 /// won't be automatically lowered. Users should apply case normalization
125 /// for US-ASCII only `host` component by themselves.
126 #[inline]
127 #[must_use]
128 fn case_pct_normalization(self) -> bool {
129 match self {
130 Self::None => false,
131 Self::Default | Self::PreserveAuthoritylessRelativePath => true,
132 }
133 }
134}
135
136/// Normalizedness check algorithm.
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub(crate) enum NormalizednessCheckMode {
139 /// Default algorithm (corresponding to [`NormalizationMode::Default`]).
140 Default,
141 /// Strict RFC 3986 normalization.
142 Rfc3986,
143 /// WHATWG-like normalization algorithm (corresponding to
144 /// [`NormalizationMode::PreserveAuthoritylessRelativePath`]).
145 PreserveAuthoritylessRelativePath,
146}
147
148/// Normalization operation.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub(crate) struct NormalizationOp {
151 /// Normalization mode.
152 pub(crate) mode: NormalizationMode,
153}
154
155/// Spec-agnostic IRI normalization/resolution input.
156#[derive(Debug, Clone, Copy)]
157pub(crate) struct NormalizationInput<'a> {
158 /// Target scheme.
159 scheme: &'a str,
160 /// Target authority.
161 authority: Option<&'a str>,
162 /// Target path without dot-removal.
163 path: Path<'a>,
164 /// Target query.
165 query: Option<&'a str>,
166 /// Target fragment.
167 fragment: Option<&'a str>,
168 /// Normalization type.
169 op: NormalizationOp,
170}
171
172impl<'a> NormalizationInput<'a> {
173 /// Creates a `NormalizedInput` from IRIs to resolve.
174 #[inline]
175 #[must_use]
176 pub(crate) fn with_resolution_params<S: Spec>(
177 base_components: &RiReferenceComponents<'a, S>,
178 reference: &'a RiReferenceStr<S>,
179 ) -> Self {
180 let r = RiReferenceComponents::from(reference);
181
182 Self::create_normalization_input(
183 r.iri.as_str(),
184 &r.splitter,
185 base_components.iri.as_str(),
186 &base_components.splitter,
187 )
188 }
189
190 /// Creates a `NormalizationInput` from components to resolve an IRI.
191 #[must_use]
192 fn create_normalization_input(
193 r_iri: &'a str,
194 r: &Splitter,
195 b_iri: &'a str,
196 b: &Splitter,
197 ) -> Self {
198 /// The toplevel component the reference has.
199 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
200 enum RefToplevel {
201 /// Scheme.
202 Scheme,
203 /// Authority.
204 Authority,
205 /// Path.
206 Path,
207 /// Query.
208 Query,
209 /// Reference is empty or has only fragment.
210 None,
211 }
212
213 impl RefToplevel {
214 /// Choose a component from either of the reference or the base,
215 /// based on the toplevel component of the reference.
216 #[inline]
217 #[must_use]
218 fn choose_then<T, F, G>(self, component: RefToplevel, reference: F, base: G) -> T
219 where
220 F: FnOnce() -> T,
221 G: FnOnce() -> T,
222 {
223 if self <= component {
224 reference()
225 } else {
226 base()
227 }
228 }
229 }
230
231 let ref_toplevel = if r.has_scheme() {
232 RefToplevel::Scheme
233 } else if r.has_authority() {
234 RefToplevel::Authority
235 } else if !r.is_path_empty(r_iri.len()) {
236 RefToplevel::Path
237 } else if r.has_query() {
238 RefToplevel::Query
239 } else {
240 RefToplevel::None
241 };
242
243 let path = match ref_toplevel {
244 RefToplevel::Scheme | RefToplevel::Authority => {
245 Path::NeedsProcessing(PathToNormalize::from_single_path(r.path_str(r_iri)))
246 }
247 RefToplevel::Path => {
248 let r_path = r.path_str(r_iri);
249 if r_path.starts_with('/') {
250 Path::NeedsProcessing(PathToNormalize::from_single_path(r_path))
251 } else {
252 // About this branch, see
253 // <https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.3>.
254 //
255 // > o If the base URI has a defined authority component and an empty
256 // > path, then return a string consisting of "/" concatenated with the
257 // > reference's path; otherwise,
258 let b_path = b.path_str(b_iri);
259 let b_path = if b.has_authority() && b_path.is_empty() {
260 "/"
261 } else {
262 b_path
263 };
264 Path::NeedsProcessing(PathToNormalize::from_paths_to_be_resolved(
265 b_path, r_path,
266 ))
267 }
268 }
269 RefToplevel::Query | RefToplevel::None => Path::Done(b.path_str(b_iri)),
270 };
271
272 Self {
273 scheme: r.scheme_str(r_iri).unwrap_or_else(|| {
274 b.scheme_str(b_iri)
275 .expect("non-relative IRI must have a scheme")
276 }),
277 authority: ref_toplevel.choose_then(
278 RefToplevel::Authority,
279 || r.authority_str(r_iri),
280 || b.authority_str(b_iri),
281 ),
282 path,
283 query: ref_toplevel.choose_then(
284 RefToplevel::Query,
285 || r.query_str(r_iri),
286 || b.query_str(b_iri),
287 ),
288 fragment: r.fragment_str(r_iri),
289 op: NormalizationOp {
290 mode: NormalizationMode::None,
291 },
292 }
293 }
294}
295
296impl<'a, S: Spec> From<&'a RiStr<S>> for NormalizationInput<'a> {
297 fn from(iri: &'a RiStr<S>) -> Self {
298 let components = RiReferenceComponents::<S>::from(iri.as_ref());
299 let (scheme, authority, path, query, fragment) = components.to_major();
300 let scheme = scheme.expect("`absolute IRI must have `scheme`");
301 let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
302
303 NormalizationInput {
304 scheme,
305 authority,
306 path,
307 query,
308 fragment,
309 op: NormalizationOp {
310 mode: NormalizationMode::None,
311 },
312 }
313 }
314}
315
316#[cfg(feature = "alloc")]
317impl<'a, S: Spec> From<&'a RiString<S>> for NormalizationInput<'a> {
318 #[inline]
319 fn from(iri: &'a RiString<S>) -> Self {
320 Self::from(iri.as_slice())
321 }
322}
323
324impl<'a, S: Spec> From<&'a RiAbsoluteStr<S>> for NormalizationInput<'a> {
325 fn from(iri: &'a RiAbsoluteStr<S>) -> Self {
326 let components = RiReferenceComponents::<S>::from(iri.as_ref());
327 let (scheme, authority, path, query, fragment) = components.to_major();
328 let scheme = scheme.expect("`absolute IRI must have `scheme`");
329 let path = Path::NeedsProcessing(PathToNormalize::from_single_path(path));
330
331 NormalizationInput {
332 scheme,
333 authority,
334 path,
335 query,
336 fragment,
337 op: NormalizationOp {
338 mode: NormalizationMode::None,
339 },
340 }
341 }
342}
343
344#[cfg(feature = "alloc")]
345impl<'a, S: Spec> From<&'a RiAbsoluteString<S>> for NormalizationInput<'a> {
346 #[inline]
347 fn from(iri: &'a RiAbsoluteString<S>) -> Self {
348 Self::from(iri.as_slice())
349 }
350}
351
352impl NormalizationInput<'_> {
353 /// Checks if the path is normalizable by RFC 3986 algorithm.
354 ///
355 /// Returns `Ok(())` when normalizable, returns `Err(_)` if not.
356 pub(crate) fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
357 if self.authority.is_some() {
358 return Ok(());
359 }
360 match self.path {
361 Path::Done(_) => Ok(()),
362 Path::NeedsProcessing(path) => path.ensure_rfc3986_normalizable_with_authority_absent(),
363 }
364 }
365}
366
367/// Writable as a normalized IRI.
368///
369/// Note that this implicitly apply serialization rule defined by WHATWG URL
370/// Standard (to handle normalization impossible by RFC 3986) because `Display`
371/// should not fail by reasons other than backend I/O failure. If you make the
372/// normalization fail in such cases, check if the path starts with `/./`.
373/// When the normalization succeeds by RFC 3986 algorithm, the path never starts
374/// with `/./`.
375struct NormalizedInner<'a, S> {
376 /// Spec-agnostic normalization input.
377 input: NormalizationInput<'a>,
378 /// Spec.
379 _spec: PhantomData<fn() -> S>,
380}
381
382impl<S: Spec> fmt::Debug for NormalizedInner<'_, S> {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 f.debug_struct("Normalized")
385 .field("input", &self.input)
386 .finish()
387 }
388}
389
390impl<'a, S: Spec> NormalizedInner<'a, S> {
391 /// Creates a new `Normalized` object from the given input.
392 #[inline]
393 #[must_use]
394 fn from_input(input: NormalizationInput<'a>) -> Self {
395 Self {
396 input,
397 _spec: PhantomData,
398 }
399 }
400}
401
402impl<S: Spec> fmt::Display for NormalizedInner<'_, S> {
403 #[inline]
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 // Write the scheme.
406 if self.input.op.mode.case_pct_normalization() {
407 normalize_scheme(f, self.input.scheme)?;
408 } else {
409 f.write_str(self.input.scheme)?;
410 }
411 f.write_str(":")?;
412
413 // Write the authority if available.
414 if let Some(authority) = self.input.authority {
415 f.write_str("//")?;
416 if self.input.op.mode.case_pct_normalization() {
417 normalize_authority::<S>(f, authority)?;
418 } else {
419 // No case/pct normalization.
420 f.write_str(authority)?;
421 }
422 }
423
424 // Process and write the path.
425 match self.input.path {
426 Path::Done(s) => {
427 if self.input.op.mode.case_pct_normalization() {
428 // Normalize the path.
429 PathToNormalize::from_single_path(s).fmt_write_normalize::<S, _>(
430 f,
431 self.input.op,
432 self.input.authority.is_some(),
433 )?
434 } else {
435 // No normalization.
436 f.write_str(s)?
437 }
438 }
439 Path::NeedsProcessing(path) => {
440 path.fmt_write_normalize::<S, _>(f, self.input.op, self.input.authority.is_some())?
441 }
442 }
443
444 // Write the query if available.
445 if let Some(query) = self.input.query {
446 f.write_char('?')?;
447 if self.input.op.mode.case_pct_normalization() {
448 normalize_query::<S>(f, query)?;
449 } else {
450 f.write_str(query)?;
451 }
452 }
453
454 // Write the fragment if available.
455 if let Some(fragment) = self.input.fragment {
456 f.write_char('#')?;
457 if self.input.op.mode.case_pct_normalization() {
458 normalize_fragment::<S>(f, fragment)?;
459 } else {
460 f.write_str(fragment)?;
461 }
462 }
463
464 Ok(())
465 }
466}
467
468/// Writes the normalized scheme.
469pub(crate) fn normalize_scheme(f: &mut fmt::Formatter<'_>, scheme: &str) -> fmt::Result {
470 // Apply case normalization.
471 //
472 // > namely, that the scheme and US-ASCII only host are case
473 // > insensitive and therefore should be normalized to lowercase.
474 // >
475 // > --- <https://datatracker.ietf.org/doc/html/rfc3987#section-5.3.2.1>.
476 //
477 // Note that `scheme` consists of only ASCII characters and contains
478 // no percent-encoded characters.
479 scheme
480 .chars()
481 .try_for_each(|c| f.write_char(c.to_ascii_lowercase()))
482}
483
484/// Writes the normalized authority.
485fn normalize_authority<S: Spec>(f: &mut fmt::Formatter<'_>, authority: &str) -> fmt::Result {
486 let host_port = match rfind_split_hole(authority, b'@') {
487 Some((userinfo, host_port)) => {
488 // Don't lowercase `userinfo` even if it is ASCII only. `userinfo`
489 // is not a part of `host`.
490 PctCaseNormalized::<S>::new(userinfo).fmt(f)?;
491 f.write_char('@')?;
492 host_port
493 }
494 None => authority,
495 };
496 normalize_host_port::<S>(f, host_port)
497}
498
499/// Writes the normalized host and port.
500pub(crate) fn normalize_host_port<S: Spec>(
501 f: &mut fmt::Formatter<'_>,
502 host_port: &str,
503) -> fmt::Result {
504 // If the suffix is a colon, it is a delimiter between the host and empty
505 // port. An empty port should be removed during normalization (see RFC 3986
506 // section 3.2.3), so strip it.
507 //
508 // > URI producers and normalizers should omit the port component and its
509 // > ":" delimiter if port is empty or if its value would be the same as
510 // > that of the scheme's default.
511 // >
512 // > --- [RFC 3986 section 3.2.3. Port](https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2.3)
513 let host_port = host_port.strip_suffix(':').unwrap_or(host_port);
514
515 // Apply case normalization and percent-encoding normalization to `host`.
516 // Optional `":" port` part only consists of an ASCII colon and ASCII
517 // digits, so this won't affect to the test result.
518 if is_ascii_only_host(host_port) {
519 // If the host is ASCII characters only, make plain alphabets lower case.
520 NormalizedAsciiOnlyHost::new(host_port).fmt(f)
521 } else {
522 PctCaseNormalized::<S>::new(host_port).fmt(f)
523 }
524}
525
526/// Writes the normalized query without the '?' prefix.
527pub(crate) fn normalize_query<S: Spec>(f: &mut fmt::Formatter<'_>, query: &str) -> fmt::Result {
528 // Apply percent-encoding normalization.
529 PctCaseNormalized::<S>::new(query).fmt(f)
530}
531
532/// Writes the normalized query without the '#' prefix.
533pub(crate) fn normalize_fragment<S: Spec>(
534 f: &mut fmt::Formatter<'_>,
535 fragment: &str,
536) -> fmt::Result {
537 // Apply percent-encoding normalization.
538 PctCaseNormalized::<S>::new(fragment).fmt(f)
539}
540
541/// Normalized OR resolved IRI.
542///
543/// Resolved IRI can be represented by this type. In that case, the result might
544/// not be normalized. If you want the IRI resolution result to be normalized,
545/// use [`enable_normalization`][`Self::enable_normalization`] method.
546///
547/// [`Display`]: `core::fmt::Display`
548pub struct Normalized<'a, T: ?Sized> {
549 /// Spec-agnostic normalization input.
550 input: NormalizationInput<'a>,
551 /// Expected result type.
552 _ty_str: PhantomData<fn() -> T>,
553}
554
555impl<T: ?Sized> fmt::Debug for Normalized<'_, T> {
556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557 f.debug_struct("Normalized")
558 .field("input", &self.input)
559 .finish()
560 }
561}
562
563impl<'a, T: ?Sized> Normalized<'a, T> {
564 /// Creates a new `Normalized` object from the given input.
565 #[inline]
566 #[must_use]
567 pub(crate) fn from_input(input: NormalizationInput<'a>) -> Self {
568 Self {
569 input,
570 _ty_str: PhantomData,
571 }
572 }
573
574 /// Enables the normalization.
575 ///
576 /// This lets the normalizer apply the case normalization, percent-encoding
577 /// normalization, and dot segments removal.
578 #[inline]
579 pub fn enable_normalization(&mut self) {
580 self.input.op.mode = NormalizationMode::Default;
581 }
582
583 /// Enables the normalization that preserve relative path under some condition.
584 ///
585 /// Note that this normalization algorithm is not compatible with RFC 3986
586 /// algorithm for some inputs.
587 ///
588 /// See [`RiStr::normalize_but_preserve_authorityless_relative_path()`]
589 /// for detail.
590 #[inline]
591 pub fn enable_normalization_preserving_authorityless_relative_path(&mut self) {
592 self.input.op.mode = NormalizationMode::PreserveAuthoritylessRelativePath;
593 }
594
595 /// Returns `Self` with normalization enabled.
596 #[inline]
597 #[must_use]
598 pub fn and_normalize(mut self) -> Self {
599 self.enable_normalization();
600 self
601 }
602
603 /// Returns `Self` with special normalization enabled.
604 ///
605 /// Note that this normalization algorithm is not compatible with RFC 3986
606 /// algorithm for some inputs.
607 ///
608 /// See [`RiStr::normalize_but_preserve_authorityless_relative_path()`]
609 /// for detail.
610 #[inline]
611 #[must_use]
612 pub fn and_normalize_but_preserve_authorityless_relative_path(mut self) -> Self {
613 self.enable_normalization_preserving_authorityless_relative_path();
614 self
615 }
616
617 /// Checks if the path is normalizable by RFC 3986 algorithm.
618 ///
619 /// Returns `Ok(())` when normalizable, returns `Err(_)` if not.
620 #[inline]
621 pub fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
622 self.input.ensure_rfc3986_normalizable()
623 }
624}
625
626impl<S: Spec> fmt::Display for Normalized<'_, RiStr<S>> {
627 #[inline]
628 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629 NormalizedInner::<S>::from_input(self.input).fmt(f)
630 }
631}
632
633impl<S: Spec> fmt::Display for Normalized<'_, RiAbsoluteStr<S>> {
634 #[inline]
635 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636 NormalizedInner::<S>::from_input(self.input).fmt(f)
637 }
638}
639
640#[cfg(feature = "alloc")]
641impl<S: Spec> ToDedicatedString for Normalized<'_, RiStr<S>> {
642 type Target = RiString<S>;
643
644 fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
645 let s = self.try_to_string()?;
646 // SAFETY: Normalization provided by this crate must always succeed for
647 // URIs/IRIs. "Fallible" normalization in this crate mean that the
648 // resulting URIs/IRIs can point to the semantically different resources,
649 // but in such cases they are still syntactically valid as URIs/IRIs.
650 Ok(unsafe {
651 Self::Target::new_unchecked_justified(s, "the normalization result must be a valid IRI")
652 })
653 }
654}
655
656#[cfg(feature = "alloc")]
657impl<S: Spec> From<Normalized<'_, RiStr<S>>> for RiString<S> {
658 #[inline]
659 fn from(v: Normalized<'_, RiStr<S>>) -> Self {
660 v.to_dedicated_string()
661 }
662}
663
664#[cfg(feature = "alloc")]
665impl<S: Spec> From<&Normalized<'_, RiStr<S>>> for RiString<S> {
666 #[inline]
667 fn from(v: &Normalized<'_, RiStr<S>>) -> Self {
668 v.to_dedicated_string()
669 }
670}
671
672#[cfg(feature = "alloc")]
673impl<S: Spec> ToDedicatedString for Normalized<'_, RiAbsoluteStr<S>> {
674 type Target = RiAbsoluteString<S>;
675
676 fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
677 let s = self.try_to_string()?;
678 // SAFETY: Normalization provided by this crate must always succeed for
679 // URIs/IRIs. "Fallible" normalization in this crate mean that the
680 // resulting URIs/IRIs can point to the semantically different resources,
681 // but in such cases they are still syntactically valid as URIs/IRIs.
682 Ok(unsafe {
683 Self::Target::new_unchecked_justified(
684 s,
685 "the normalization result must be a valid absolute IRI",
686 )
687 })
688 }
689}
690
691#[cfg(feature = "alloc")]
692impl<S: Spec> From<Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
693 #[inline]
694 fn from(v: Normalized<'_, RiAbsoluteStr<S>>) -> Self {
695 v.to_dedicated_string()
696 }
697}
698
699#[cfg(feature = "alloc")]
700impl<S: Spec> From<&Normalized<'_, RiAbsoluteStr<S>>> for RiAbsoluteString<S> {
701 #[inline]
702 fn from(v: &Normalized<'_, RiAbsoluteStr<S>>) -> Self {
703 v.to_dedicated_string()
704 }
705}