ftml/tree/attribute/
mod.rs1mod safe;
22
23use super::clone::string_to_owned;
24use crate::id_prefix::isolate_ids;
25use crate::parsing::parse_boolean;
26use crate::settings::WikitextSettings;
27use crate::url::normalize_href;
28use std::borrow::Cow;
29use std::collections::{BTreeMap, HashMap};
30use std::fmt::{self, Debug};
31use unicase::UniCase;
32
33pub use self::safe::{
34 BOOLEAN_ATTRIBUTES, SAFE_ATTRIBUTE_PREFIXES, SAFE_ATTRIBUTES, URL_ATTRIBUTES,
35 is_safe_attribute,
36};
37
38#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
39pub struct AttributeMap<'t> {
40 #[serde(flatten)]
41 inner: BTreeMap<Cow<'t, str>, Cow<'t, str>>,
42}
43
44impl<'t> AttributeMap<'t> {
45 #[inline]
46 pub fn new() -> Self {
47 AttributeMap::default()
48 }
49
50 pub fn from_arguments(arguments: &HashMap<UniCase<&'t str>, Cow<'t, str>>) -> Self {
51 let inner = arguments
52 .iter()
53 .filter(|&(key, _)| is_safe_attribute(*key))
54 .filter_map(|(key, value)| {
55 let mut value = Cow::clone(value);
56
57 if BOOLEAN_ATTRIBUTES.contains(key)
59 && let Ok(boolean_value) = parse_boolean(&value)
60 {
61 if boolean_value {
63 value = cow!("");
65 } else {
66 return None;
68 }
69 }
70
71 if URL_ATTRIBUTES.contains(key) {
73 let url = normalize_href(&value, None).into_owned();
74 value = Cow::Owned(url);
75 }
76
77 let key = key.into_inner().to_ascii_lowercase();
79 Some((Cow::Owned(key), value))
80 })
81 .collect();
82
83 AttributeMap { inner }
84 }
85
86 pub fn insert(&mut self, attribute: &'t str, value: Cow<'t, str>) -> bool {
87 let will_insert = is_safe_attribute(UniCase::ascii(attribute));
88 if will_insert {
89 self.inner.insert(cow!(attribute), value);
90 }
91
92 will_insert
93 }
94
95 #[inline]
96 pub fn remove(&mut self, attribute: &str) -> Option<Cow<'t, str>> {
97 self.inner.remove(attribute)
98 }
99
100 #[inline]
101 pub fn get(&self) -> &BTreeMap<Cow<'t, str>, Cow<'t, str>> {
102 &self.inner
103 }
104
105 pub fn isolate_id(&mut self, settings: &WikitextSettings) {
106 if settings.isolate_user_ids
107 && let Some(value) = self.inner.get_mut("id")
108 {
109 trace!("Found 'id' attribute, isolating value");
110 *value = Cow::Owned(isolate_ids(value));
111 }
112 }
113
114 pub fn to_owned(&self) -> AttributeMap<'static> {
115 let mut inner = BTreeMap::new();
116
117 for (key, value) in self.inner.iter() {
118 let key = string_to_owned(key);
119 let value = string_to_owned(value);
120
121 inner.insert(key, value);
122 }
123
124 AttributeMap { inner }
125 }
126}
127
128impl Debug for AttributeMap<'_> {
129 #[inline]
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 self.inner.fmt(f)
132 }
133}
134
135impl<'t> From<BTreeMap<Cow<'t, str>, Cow<'t, str>>> for AttributeMap<'t> {
136 #[inline]
137 fn from(map: BTreeMap<Cow<'t, str>, Cow<'t, str>>) -> AttributeMap<'t> {
138 AttributeMap { inner: map }
139 }
140}