#[cfg(not(feature = "std"))]
use alloc::{
format,
string::{String, ToString},
};
use crate::error::NameParseError;
use core::{
fmt::{Display, Formatter, Result as FmtResult},
str::FromStr,
};
use strum::{EnumIs, EnumTryAs};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, EnumIs, EnumTryAs)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[allow(missing_docs)] pub enum PrefixedName {
Namespace(Namespace),
Local(LocalName),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Namespace(String);
pub const NAMESPACE_SEPARATOR_CHAR: char = ':';
pub const NAMESPACE_DEFAULT_STRING: &str = ":";
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Name(String);
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct LocalName {
namespace: Namespace,
name: Name,
}
impl Display for PrefixedName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Self::Namespace(v) => v.fmt(f),
Self::Local(v) => v.fmt(f),
}
}
}
impl Display for Namespace {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl FromStr for Namespace {
type Err = NameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(NameParseError::EmptyString)
} else if s == NAMESPACE_DEFAULT_STRING {
Ok(Self(s.to_string()))
} else if let Some(pn_prefix) = s.strip_suffix(':') {
if pn_prefix.contains(NAMESPACE_SEPARATOR_CHAR) {
Err(NameParseError::TooManySeparators(s.to_string()))
} else if pn_prefix.is_empty() {
Ok(Self(s.to_string()))
} else {
let mut chars = pn_prefix.chars();
if is_pn_chars_base(chars.next().unwrap()) && chars.all(is_pn_prefix_local_rest) {
Ok(Self(s.to_string()))
} else {
Err(NameParseError::InvalidCharacter(s.to_string()))
}
}
} else {
Err(NameParseError::MissingSeparator(s.to_string()))
}
}
}
impl AsRef<str> for Namespace {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<Namespace> for String {
fn from(value: Namespace) -> Self {
value.0
}
}
impl From<&Namespace> for String {
fn from(value: &Namespace) -> Self {
value.0.clone()
}
}
impl Namespace {
pub fn new_named<S>(s: S) -> Result<Self, NameParseError>
where
S: Into<String>,
{
let s = s.into();
Self::from_str(&if s.ends_with(NAMESPACE_SEPARATOR_CHAR) {
s
} else {
format!("{s}:")
})
}
pub fn new_default() -> Self {
Self(NAMESPACE_DEFAULT_STRING.to_string())
}
pub fn new_unchecked<S>(s: S) -> Self
where
S: Into<String>,
{
let s = s.into();
Self(if s.ends_with(NAMESPACE_SEPARATOR_CHAR) {
s
} else {
format!("{s}:")
})
}
pub fn is_default(&self) -> bool {
self.0 == NAMESPACE_DEFAULT_STRING
}
pub fn name_string(&self) -> Option<&str> {
let name = self.0.strip_suffix(NAMESPACE_SEPARATOR_CHAR).unwrap();
if name.is_empty() { None } else { Some(name) }
}
pub fn qualify(&self, name: &Name) -> LocalName {
LocalName::new(self.clone(), name.clone())
}
pub fn is_valid_str<S>(s: S) -> bool
where
S: AsRef<str>,
{
Self::from_str(s.as_ref()).is_ok()
}
}
impl Display for Name {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl FromStr for Name {
type Err = NameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(NameParseError::EmptyString)
} else if s.ends_with('.') {
Err(NameParseError::InvalidCharacter(s.to_string()))
} else {
let mut chars = s.chars();
if is_pn_local_first(chars.next().unwrap()) && chars.all(is_pn_prefix_local_rest) {
Ok(Self(s.to_string()))
} else {
Err(NameParseError::InvalidCharacter(s.to_string()))
}
}
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<Name> for String {
fn from(value: Name) -> Self {
value.0
}
}
impl From<&Name> for String {
fn from(value: &Name) -> Self {
value.0.clone()
}
}
impl Name {
pub fn new_unchecked<S>(s: S) -> Self
where
S: Into<String>,
{
Self(s.into())
}
pub fn is_valid_str<S>(s: S) -> bool
where
S: AsRef<str>,
{
Self::from_str(s.as_ref()).is_ok()
}
pub fn qualify(&self, in_namespace: &Namespace) -> LocalName {
LocalName::new(in_namespace.clone(), self.clone())
}
}
impl Display for LocalName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}{}", self.namespace, self.name)
}
}
impl FromStr for LocalName {
type Err = NameParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(NameParseError::EmptyString)
} else if let Some(separator) = s.find(':') {
Ok(Self {
namespace: Namespace::from_str(&s[0..=separator])?,
name: Name::from_str(&s[separator + 1..])?,
})
} else {
Err(NameParseError::MissingSeparator(s.to_string()).into())
}
}
}
impl From<LocalName> for String {
fn from(value: LocalName) -> Self {
value.to_string()
}
}
impl From<&LocalName> for String {
fn from(value: &LocalName) -> Self {
value.to_string()
}
}
impl LocalName {
pub fn new(namespace: Namespace, name: Name) -> Self {
Self { namespace, name }
}
pub fn new_in_default(name: Name) -> Self {
Self {
namespace: Namespace::new_default(),
name,
}
}
pub fn new_unchecked<S1, S2>(namespace: S1, name: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Self {
namespace: Namespace::new_unchecked(namespace),
name: Name::new_unchecked(name),
}
}
pub fn namespace(&self) -> &Namespace {
&self.namespace
}
pub fn is_namespace_default(&self) -> bool {
self.namespace.is_default()
}
pub fn name(&self) -> &Name {
&self.name
}
pub fn as_curie(&self) -> String {
format!("[{}{}]", self.namespace, self.name)
}
}
fn is_pn_chars_base(c: char) -> bool {
match c {
'A'..='Z'
| 'a'..='z'
| '\u{00C0}'..='\u{00D6}'
| '\u{00D8}'..='\u{00F6}'
| '\u{00F8}'..='\u{02FF}'
| '\u{0370}'..='\u{037D}'
| '\u{037F}'..='\u{1FFF}'
| '\u{200C}'..='\u{200D}'
| '\u{2070}'..='\u{218F}'
| '\u{2C00}'..='\u{2FEF}'
| '\u{3001}'..='\u{D7FF}'
| '\u{F900}'..='\u{FDCF}'
| '\u{FDF0}'..='\u{FFFD}'
| '\u{10000}'..='\u{EFFFF}' => true,
_ => false,
}
}
fn is_pn_chars_u(c: char) -> bool {
is_pn_chars_base(c) || c == '_'
}
fn is_pn_chars(c: char) -> bool {
is_pn_chars_u(c)
|| c == '-'
|| c.is_ascii_digit()
|| c == '\u{00B7}'
|| ('\u{0300}'..='\u{036F}').contains(&c)
|| ('\u{203F}'..='\u{2040}').contains(&c)
}
fn is_pn_local_first(c: char) -> bool {
is_pn_chars_u(c) || c.is_ascii_digit()
}
fn is_pn_prefix_local_rest(c: char) -> bool {
is_pn_chars(c) || c == '.'
}