use std::collections::HashMap;
use crate::types::tag::Attribute;
#[derive(Debug, PartialEq, Eq)]
pub struct BlackWhiteList {
default: bool,
items: Vec<(String, bool)>,
whitelist_empty: bool,
}
impl BlackWhiteList {
fn get(&self, name: &str) -> Option<bool> {
self.items.iter().find(|item| item.0 == name).map(|item| item.1)
}
fn get_mut(&mut self, name: &str) -> Option<&mut bool> {
self.items.iter_mut().find(|item| item.0 == name).map(|item| &mut item.1)
}
}
impl BlackWhiteList {
pub fn check(&self, name: &str) -> ElementState {
self.get(name).map_or_else(
|| {
if self.is_empty() && self.default {
ElementState::NotSpecified
} else {
ElementState::BlackListed
}
},
|keep| {
if keep { ElementState::WhiteListed } else { ElementState::BlackListed }
},
)
}
pub const fn is_empty(&self) -> bool {
self.whitelist_empty
}
pub fn is_explicitly_blacklisted(&self, name: &str) -> bool {
self.get(name).map_or_else(|| !self.default, |keep| !keep)
}
pub const fn new() -> Self {
Self { default: true, items: vec![], whitelist_empty: true }
}
pub fn push(&mut self, name: String, keep: bool) -> Result<(), ()> {
if keep {
self.whitelist_empty = false;
}
if let Some(item) = self.get_mut(&name) {
let old = *item;
*item = keep;
if keep == old { Ok(()) } else { Err(()) }
} else {
self.items.push((name, keep));
Ok(())
}
}
pub const fn set_default(&mut self, default: bool) {
self.default = default;
}
}
impl Default for BlackWhiteList {
fn default() -> Self {
Self::new()
}
}
#[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| match self {
Self::Is(this_val) => *this_val == *attr_val,
Self::Contains(this_val) => attr_val.split_whitespace().any(|word| word == this_val),
Self::NoValue => false,
})
}
}
#[derive(Default, Debug, PartialEq, Eq)]
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 const fn new() -> Self {
Self { blacklist: vec![], whitelist: vec![] }
}
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));
};
}
}