use crate::error::{Error as RdfError, ErrorKind};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct QName {
prefix: Option<String>,
name: String,
}
const NAME_ONLY: usize = 1;
const PREFIX_AND_NAME: usize = 2;
impl Display for QName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}",
if let Some(prefix) = &self.prefix {
prefix.to_string()
} else {
String::new()
},
&self.name
)
}
}
impl FromStr for QName {
type Err = RdfError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Err(ErrorKind::EmptyQName.into())
} else {
let parts: Vec<&str> = s.split(':').collect();
match parts.len() {
NAME_ONLY => {
let name = *parts.get(0).unwrap();
if QName::is_valid(name) {
Ok(QName {
prefix: None,
name: name.to_string(),
})
} else {
Err(ErrorKind::InvalidQName(s.to_string()).into())
}
}
PREFIX_AND_NAME => {
let prefix = *parts.get(0).unwrap();
let name = *parts.get(1).unwrap();
if QName::is_valid(prefix) && QName::is_valid(name) {
Ok(QName {
prefix: Some(prefix.to_string()),
name: name.to_string(),
})
} else {
Err(ErrorKind::InvalidQName(s.to_string()).into())
}
}
_ => Err(ErrorKind::InvalidQName(s.to_string()).into()),
}
}
}
}
impl QName {
pub fn new(name: &str) -> Result<Self, RdfError> {
if name.is_empty() {
Err(ErrorKind::EmptyQName.into())
} else if !QName::is_valid(name) {
Err(ErrorKind::InvalidQName(name.to_string()).into())
} else {
Ok(Self::new_unchecked(None, name))
}
}
pub fn new_unchecked(prefix: Option<&str>, name: &str) -> Self {
Self {
prefix: prefix.map(str::to_string),
name: name.to_string(),
}
}
pub fn with_prefix(prefix: &str, name: &str) -> Result<Self, RdfError> {
if prefix.is_empty() || name.is_empty() {
Err(ErrorKind::EmptyQName.into())
} else if !QName::is_valid(prefix) {
Err(ErrorKind::InvalidQName(prefix.to_string()).into())
} else if !QName::is_valid(name) {
Err(ErrorKind::InvalidQName(name.to_string()).into())
} else {
Ok(Self::new_unchecked(Some(prefix), name))
}
}
pub fn has_prefix(&self) -> bool {
self.prefix.is_some()
}
pub fn prefix(&self) -> &Option<String> {
&self.prefix
}
pub fn name(&self) -> &String {
&self.name
}
pub fn as_curie(&self) -> String {
format!(
"[{}:{}]",
match &self.prefix {
None => "",
Some(prefix) => prefix,
},
&self.name
)
}
pub fn is_valid(part: &str) -> bool {
is_xml_name(part)
}
}
pub(crate) fn is_xml_name_start_char(c: char) -> bool {
c == ':'
|| ('A'..='Z').contains(&c)
|| c == '_'
|| ('a'..='z').contains(&c)
|| ('\u{C0}'..='\u{D6}').contains(&c)
|| ('\u{D8}'..='\u{F6}').contains(&c)
|| ('\u{0F8}'..='\u{2FF}').contains(&c)
|| ('\u{370}'..='\u{37D}').contains(&c)
|| ('\u{037F}'..='\u{1FFF}').contains(&c)
|| ('\u{200C}'..='\u{200D}').contains(&c)
|| ('\u{2070}'..='\u{218F}').contains(&c)
|| ('\u{2C00}'..='\u{2FEF}').contains(&c)
|| ('\u{3001}'..='\u{D7FF}').contains(&c)
|| ('\u{F900}'..='\u{FDCF}').contains(&c)
|| ('\u{FDF0}'..='\u{FFFD}').contains(&c)
|| ('\u{10000}'..='\u{EFFFF}').contains(&c)
}
pub(crate) fn is_xml_name_char(c: char) -> bool {
is_xml_name_start_char(c)
|| c == '-'
|| c == '.'
|| ('0'..='9').contains(&c)
|| c == '\u{B7}'
|| ('\u{0300}'..='\u{036F}').contains(&c)
|| ('\u{203F}'..='\u{2040}').contains(&c)
}
pub(crate) fn is_xml_name(s: &str) -> bool {
!s.is_empty() && s.starts_with(is_xml_name_start_char) && s[1..].chars().all(is_xml_name_char)
}