diff --git a/statement/src/_literal.rs b/statement/src/_literal.rs index efe512a..4c46afb 100644 --- a/statement/src/_literal.rs +++ b/statement/src/_literal.rs @@ -36,7 +36,7 @@ pub enum Literal<'a> { impl Literal<'_> { /// Borrow this [`Literal`] as another [`Literal`]. - pub fn borrowed(&self) -> Literal { + pub fn borrowed(&self) -> Literal<'_> { match self { Literal::Typed(lex, iri) => Literal::Typed(Cow::from(lex.as_ref()), iri.borrowed()), Literal::LanguageString(lex, lang_tag, base_dir) => { @@ -46,7 +46,7 @@ impl Literal<'_> { } /// [lexical form](https://www.w3.org/TR/rdf12-concepts/#dfn-lexical-form) of this literal - pub fn lexical_form(&self) -> Cow { + pub fn lexical_form(&self) -> Cow<'_, str> { let ref_cow = match self { Literal::Typed(lex, ..) => lex, Literal::LanguageString(lex, ..) => lex, diff --git a/statement/src/_literal/_language_tag.rs b/statement/src/_literal/_language_tag.rs index 94189d1..62120d2 100644 --- a/statement/src/_literal/_language_tag.rs +++ b/statement/src/_literal/_language_tag.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, cmp::Ordering}; /// Wrapper around a [`Cow`] guaranteeing that the underlying text satisfies [BCP47]. /// @@ -7,7 +7,7 @@ use std::borrow::Cow; /// (i.e. ISO 639 for 2-3 characters language tag, or ISO 15924 for the script) /// /// [BCP47]: https://datatracker.ietf.org/doc/bcp47/ -#[derive(Clone, Debug, Eq, Ord)] +#[derive(Clone, Debug, Eq)] pub struct LangTag<'a>(Cow<'a, str>); impl<'a> LangTag<'a> { @@ -76,25 +76,27 @@ impl std::cmp::PartialEq> for &str { } } +impl std::cmp::Ord for LangTag<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + cmp_ignore_case_ascii(&self.0, &other.0) + } +} + impl std::cmp::PartialOrd for LangTag<'_> { fn partial_cmp(&self, other: &Self) -> Option { - Some( - self.0 - .to_ascii_lowercase() - .cmp(&other.0.to_ascii_lowercase()), - ) + Some(self.cmp(other)) } } impl std::cmp::PartialOrd<&str> for LangTag<'_> { fn partial_cmp(&self, other: &&'_ str) -> Option { - Some(self.0.to_ascii_lowercase().cmp(&other.to_ascii_lowercase())) + Some(cmp_ignore_case_ascii(&self.0, *other)) } } impl std::cmp::PartialOrd> for &str { fn partial_cmp(&self, other: &LangTag<'_>) -> Option { - Some(self.to_ascii_lowercase().cmp(&other.0.to_ascii_lowercase())) + Some(cmp_ignore_case_ascii(self, &other.0)) } } @@ -104,8 +106,26 @@ impl std::fmt::Display for LangTag<'_> { } } +fn cmp_ignore_case_ascii(a: &str, b: &str) -> Ordering { + cmp_ignore_case_ascii_bytes(a.as_bytes(), b.as_bytes()) +} + +fn cmp_ignore_case_ascii_bytes(a: &[u8], b: &[u8]) -> Ordering { + match (a, b) { + ([], []) => Ordering::Equal, + ([], [_, ..]) => Ordering::Less, + ([_, ..], []) => Ordering::Greater, + ([ah, ar @ ..], [bh, br @ ..]) => ah + .to_ascii_lowercase() + .cmp(&bh.to_ascii_lowercase()) + .then_with(|| cmp_ignore_case_ascii_bytes(ar, br)), + } +} + #[cfg(test)] mod test { + use std::cmp::Ordering; + use super::*; #[test] @@ -140,6 +160,10 @@ mod test { assert_eq!(tag1, tag2); assert_eq!(tag1, "en-gb"); assert!(tag1 <= tag2 && tag2 <= tag1); + assert_eq!(tag1.partial_cmp(&tag2), Some(Ordering::Equal)); + assert_eq!(tag2.partial_cmp(&tag1), Some(Ordering::Equal)); + assert_eq!(tag1.cmp(&tag2), Ordering::Equal); + assert_eq!(tag2.cmp(&tag1), Ordering::Equal); assert!("EN" < tag1 && tag1 < "EN-ZZ"); } } diff --git a/statement/src/impl_oxrdf.rs b/statement/src/impl_oxrdf.rs index c2cab47..b5d6a78 100644 --- a/statement/src/impl_oxrdf.rs +++ b/statement/src/impl_oxrdf.rs @@ -5,7 +5,7 @@ //! This module is developed as if [`oxrdf`] implemented RDF 1.2 completely and strictly, //! which is not entirely true: //! - [`oxrdf`] does not support base direction in literals, so it is not complete; -//! - [`oxrdf`] with the [`rdf-star`] feature allows triple terms in the subject position, so it is not strict. +//! - [`oxrdf`] with the `rdf-star` feature allows triple terms in the subject position, so it is not strict. //! //! This is handled by panic'ing when those situations are encountered. //!