use std::collections::HashMap;
use crate::types::tag::Attribute;
#[derive(Debug)]
pub struct BlackWhiteList {
default: bool,
items: HashMap<String, bool>,
whitelist_empty: bool,
}
impl BlackWhiteList {
pub fn check(&self, name: &str) -> ElementState {
self.items.get(name).map_or_else(
|| {
if self.is_empty() && self.default {
ElementState::NotSpecified
} else {
ElementState::BlackListed
}
},
|keep| match keep {
true => ElementState::WhiteListed,
false => ElementState::BlackListed,
},
)
}
pub const fn is_empty(&self) -> bool {
self.whitelist_empty
}
pub fn is_explicitly_blacklisted(&self, name: &str) -> bool {
self.items.get(name).map_or_else(|| false, |keep| !*keep)
}
pub fn push(&mut self, name: String, keep: bool) -> Result<(), ()> {
if keep {
self.whitelist_empty = false;
}
let old = self.items.insert(name, keep);
if old.is_some_and(|inner| inner != keep) {
Err(())
} else {
Ok(())
}
}
pub const fn set_default(&mut self, default: bool) {
self.default = default;
}
}
impl Default for BlackWhiteList {
fn default() -> Self {
Self { items: HashMap::new(), whitelist_empty: true, default: true }
}
}
#[derive(Debug)]
pub enum ElementState {
BlackListed,
NotSpecified,
WhiteListed,
}
impl ElementState {
pub const fn and(&self, other: &Self) -> Self {
match (self, other) {
(Self::BlackListed, _) | (_, Self::BlackListed) => Self::BlackListed,
(Self::NotSpecified, Self::NotSpecified) => Self::NotSpecified,
(Self::WhiteListed | Self::NotSpecified, Self::WhiteListed | Self::NotSpecified) =>
Self::WhiteListed,
}
}
pub const fn is_allowed_or(&self, default: bool) -> bool {
match self {
Self::BlackListed => false,
Self::NotSpecified => default,
Self::WhiteListed => true,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum AttributeMatch {
Contains(String),
Is(String),
NoValue,
}
impl AttributeMatch {
fn matches(&self, attribute_value: Option<&str>) -> bool {
attribute_value.map_or(matches!(self, Self::NoValue), |attr_val| {
if let Self::Is(this_val) = self {
*this_val == *attr_val
} else if let Self::Contains(this_val) = self {
attr_val.split_whitespace().any(|word| word == this_val)
} else {
false
}
})
}
}
#[derive(Default, Debug)]
pub struct ValueAssociateHash {
blacklist: Vec<(String, AttributeMatch)>,
whitelist: Vec<(String, AttributeMatch)>,
}
impl ValueAssociateHash {
pub fn check(&self, attrs: &[Attribute]) -> ElementState {
let attrs_map: HashMap<_, _> = attrs
.iter()
.map(|attr| (attr.as_name().clone(), attr.as_value()))
.collect();
for (wanted_name, wanted_value) in &self.whitelist {
match attrs_map.get(wanted_name) {
None => return ElementState::BlackListed,
Some(found_value) if !wanted_value.matches(found_value.map(String::as_str)) =>
return ElementState::BlackListed,
Some(_) => (),
}
}
for (wanted_name, wanted_value) in &self.blacklist {
match attrs_map.get(wanted_name) {
Some(found_value) if wanted_value.matches(found_value.map(String::as_str)) =>
return ElementState::BlackListed,
Some(_) | None => (),
}
}
if self.is_empty() {
ElementState::NotSpecified
} else {
ElementState::WhiteListed
}
}
pub const fn is_empty(&self) -> bool {
self.whitelist.is_empty() && self.blacklist.is_empty()
}
pub fn is_explicitly_blacklisted(&self, attrs: &[Attribute]) -> bool {
let blacklist = self
.blacklist
.iter()
.map(|(name, value)| (name, value))
.collect::<HashMap<_, _>>();
for attr in attrs {
if let Some(value) = blacklist.get(&attr.as_name().clone())
&& value.matches(attr.as_value().map(String::as_str))
{
return true;
}
}
false
}
pub fn push(&mut self, name: String, value: AttributeMatch, keep: bool) {
let () = if keep {
self.whitelist.push((name, value));
} else {
self.blacklist.push((name, value));
};
}
}