use core::convert::TryFrom;
use crate::names::chars;
use crate::names::error::{NameError, TargetNameType};
use crate::names::{Eqname, Name, Nmtoken, Qname};
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Ncname(str);
#[allow(clippy::len_without_is_empty)]
impl Ncname {
#[allow(clippy::should_implement_trait)]
#[inline]
pub fn from_str(s: &str) -> Result<&Self, NameError> {
<&Self>::try_from(s)
}
#[inline]
#[must_use]
pub unsafe fn new_unchecked(s: &str) -> &Self {
&*(s as *const str as *const Self)
}
fn validate(s: &str) -> Result<(), NameError> {
let mut chars = s.char_indices();
if !chars
.next()
.map_or(false, |(_, c)| chars::is_ncname_start(c))
{
return Err(NameError::new(TargetNameType::Ncname, 0));
}
if let Some((i, _)) = chars.find(|(_, c)| !chars::is_ncname_continue(*c)) {
return Err(NameError::new(TargetNameType::Ncname, i));
}
Ok(())
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn parse_next(s: &str) -> Result<(&Self, &str), NameError> {
match Self::from_str(s) {
Ok(v) => Ok((v, &s[s.len()..])),
Err(e) if e.valid_up_to() == 0 => Err(e),
Err(e) => {
let valid_up_to = e.valid_up_to();
let v = unsafe {
let valid = &s[..valid_up_to];
debug_assert!(Self::validate(valid).is_ok());
Self::new_unchecked(valid)
};
Ok((v, &s[valid_up_to..]))
}
}
}
#[cfg(feature = "alloc")]
pub fn into_boxed_str(self: alloc::boxed::Box<Self>) -> Box<str> {
unsafe {
alloc::boxed::Box::<str>::from_raw(alloc::boxed::Box::<Self>::into_raw(self) as *mut str)
}
}
}
impl_traits_for_custom_string_slice!(Ncname);
impl AsRef<Nmtoken> for Ncname {
#[inline]
fn as_ref(&self) -> &Nmtoken {
unsafe {
debug_assert!(
Nmtoken::from_str(self.as_str()).is_ok(),
"NCName {:?} must be a valid Nmtoken",
self.as_str()
);
Nmtoken::new_unchecked(self.as_str())
}
}
}
impl AsRef<Name> for Ncname {
#[inline]
fn as_ref(&self) -> &Name {
unsafe {
debug_assert!(
Name::from_str(self.as_str()).is_ok(),
"An NCName is also a Name"
);
Name::new_unchecked(self.as_str())
}
}
}
impl AsRef<Qname> for Ncname {
#[inline]
fn as_ref(&self) -> &Qname {
unsafe {
debug_assert!(
Qname::from_str(self.as_str()).is_ok(),
"An NCName is also a Qname"
);
Qname::new_unchecked(self.as_str())
}
}
}
impl AsRef<Eqname> for Ncname {
#[inline]
fn as_ref(&self) -> &Eqname {
unsafe {
debug_assert!(
Eqname::from_str(self.as_str()).is_ok(),
"An NCName is also a Eqname"
);
Eqname::new_unchecked(self.as_str())
}
}
}
impl<'a> TryFrom<&'a str> for &'a Ncname {
type Error = NameError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
Ncname::validate(s)?;
Ok(unsafe {
Ncname::new_unchecked(s)
})
}
}
impl<'a> TryFrom<&'a Name> for &'a Ncname {
type Error = NameError;
fn try_from(s: &'a Name) -> Result<Self, Self::Error> {
if let Some(colon_pos) = s.as_str().find(':') {
return Err(NameError::new(TargetNameType::Ncname, colon_pos));
}
unsafe {
debug_assert!(
Ncname::validate(s.as_str()).is_ok(),
"Name {:?} without colons is also a valid NCName",
s.as_str()
);
Ok(Ncname::new_unchecked(s.as_str()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ensure_eq(s: &str) {
assert_eq!(
Ncname::from_str(s).expect("Should not fail"),
s,
"String: {:?}",
s
);
}
fn ensure_error_at(s: &str, valid_up_to: usize) {
let err = Ncname::from_str(s).expect_err("Should fail");
assert_eq!(err.valid_up_to(), valid_up_to, "String: {:?}", s);
}
#[test]
fn ncname_str_valid() {
ensure_eq("hello");
ensure_eq("abc123");
}
#[test]
fn ncname_str_invalid() {
ensure_error_at("", 0);
ensure_error_at("-foo", 0);
ensure_error_at("0foo", 0);
ensure_error_at("foo bar", 3);
ensure_error_at("foo/bar", 3);
ensure_error_at("foo:bar", 3);
ensure_error_at(":foo", 0);
ensure_error_at("foo:", 3);
}
}