1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use super::{At, AtValue};
use indexmap::IndexMap;
use std::fmt;

/// A thinly-wrapped `HashMap` holding DOM attributes
#[derive(Clone, Debug, PartialEq)]
pub struct Attrs {
    // We use an IndexMap instead of HashMap here, and in Style, to preserve order.
    pub vals: IndexMap<At, AtValue>,
}

/// Create an HTML-compatible string representation
impl fmt::Display for Attrs {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let string = self
            .vals
            .iter()
            .filter_map(|(k, v)| match v {
                AtValue::Ignored => None,
                AtValue::None => Some(k.to_string()),
                AtValue::Some(value) => Some(format!("{}=\"{}\"", k.as_str(), value)),
            })
            .collect::<Vec<_>>()
            .join(" ");
        write!(f, "{}", string)
    }
}

impl Attrs {
    pub const fn new(vals: IndexMap<At, AtValue>) -> Self {
        Self { vals }
    }

    pub fn empty() -> Self {
        Self {
            vals: IndexMap::new(),
        }
    }

    /// Convenience function. Ideal when there's one id, and no other attrs.
    /// Generally called with the id! macro.
    pub fn from_id(name: impl Into<AtValue>) -> Self {
        let mut result = Self::empty();
        result.add(At::Id, name.into());
        result
    }

    /// Add a new key, value pair
    pub fn add(&mut self, key: At, val: impl Into<AtValue>) {
        self.vals.insert(key, val.into());
    }

    /// Add multiple values for a single attribute. Useful for classes.
    pub fn add_multiple(&mut self, key: At, items: &[&str]) {
        self.add(
            key,
            &items
                .iter()
                .filter_map(|item| {
                    if item.is_empty() {
                        None
                    } else {
                        #[allow(clippy::useless_asref)]
                        Some(item.as_ref())
                    }
                })
                .collect::<Vec<&str>>()
                .join(" "),
        );
    }

    /// Combine with another Attrs
    pub fn merge(&mut self, other: Self) {
        for (other_key, other_value) in other.vals {
            match self.vals.get_mut(&other_key) {
                Some(original_value) => {
                    Self::merge_attribute_values(&other_key, original_value, other_value);
                }
                None => {
                    self.vals.insert(other_key, other_value);
                }
            }
        }
    }

    fn merge_attribute_values(
        key: &At,
        mut original_value: &mut AtValue,
        mut other_value: AtValue,
    ) {
        match (key, &mut original_value, &mut other_value) {
            (At::Class, AtValue::Some(original), AtValue::Some(other)) => {
                if !original.is_empty() {
                    original.push(' ');
                }
                original.push_str(other);
            }
            (..) => *original_value = other_value,
        }
    }
}