use super::Tag;
use crate::base::{Bytes, HasReplacementsError, Range};
use encoding_rs::Encoding;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default, Hash)]
pub struct LocalNameHash(Option<u64>);
impl LocalNameHash {
#[inline]
pub fn new() -> Self {
LocalNameHash(Some(0))
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_none()
}
#[inline]
pub fn update(&mut self, ch: u8) {
if let Some(h) = self.0 {
self.0 = if h >> (64 - 5) == 0 {
match ch {
b'a'..=b'z' | b'A'..=b'Z' => Some((h << 5) | ((u64::from(ch) & 0x1F) + 5)),
b'1'..=b'6' => Some((h << 5) | ((u64::from(ch) & 0x0F) - 1)),
_ => None,
}
} else {
None
};
}
}
}
impl From<&str> for LocalNameHash {
#[inline]
fn from(string: &str) -> Self {
let mut hash = LocalNameHash::new();
for ch in string.bytes() {
hash.update(ch);
}
hash
}
}
impl PartialEq<Tag> for LocalNameHash {
#[inline]
fn eq(&self, tag: &Tag) -> bool {
match self.0 {
Some(h) => *tag as u64 == h,
None => false,
}
}
}
#[derive(Clone, Debug, Eq, Hash)]
pub enum LocalName<'i> {
Hash(LocalNameHash),
Bytes(Bytes<'i>),
}
impl<'i> LocalName<'i> {
#[inline]
pub fn new(input: &'i Bytes<'i>, range: Range, hash: LocalNameHash) -> Self {
if hash.is_empty() {
LocalName::Bytes(input.slice(range))
} else {
LocalName::Hash(hash)
}
}
#[inline]
pub fn into_owned(self) -> LocalName<'static> {
match self {
LocalName::Bytes(b) => LocalName::Bytes(b.into_owned()),
LocalName::Hash(h) => LocalName::Hash(h),
}
}
#[inline]
pub fn from_str_without_replacements<'s>(
string: &'s str,
encoding: &'static Encoding,
) -> Result<LocalName<'s>, HasReplacementsError> {
let hash = LocalNameHash::from(string);
if hash.is_empty() {
Bytes::from_str_without_replacements(string, encoding).map(LocalName::Bytes)
} else {
Ok(LocalName::Hash(hash))
}
}
}
impl PartialEq<Tag> for LocalName<'_> {
#[inline]
fn eq(&self, tag: &Tag) -> bool {
match self {
LocalName::Hash(h) => h == tag,
_ => false,
}
}
}
impl PartialEq<LocalName<'_>> for LocalName<'_> {
#[inline]
fn eq(&self, other: &LocalName<'_>) -> bool {
use LocalName::*;
match (self, other) {
(Hash(s), Hash(o)) => s == o,
(Bytes(s), Bytes(o)) => s.eq_ignore_ascii_case(o),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_str() {
assert_eq!(LocalNameHash::from("div"), LocalNameHash(Some(9691u64)));
}
#[test]
fn hash_invalidation_for_non_ascii_chars() {
assert!(LocalNameHash::from("div@&").is_empty());
}
#[test]
fn hash_invalidation_for_long_values() {
assert!(LocalNameHash::from("aaaaaaaaaaaaaa").is_empty());
}
}