1use std::slice;
4use std::str;
5
6use markup5ever::{
7 expanded_name, local_name, namespace_url, ns, LocalName, Namespace, Prefix, QualName,
8};
9use string_cache::DefaultAtom;
10
11use crate::error::{ImplementationLimit, LoadingError};
12use crate::limits;
13use crate::util::{opt_utf8_cstr, utf8_cstr, utf8_cstr_bounds};
14
15pub type AttributeValue = DefaultAtom;
20
21#[derive(Clone)]
27pub struct Attributes {
28 attrs: Box<[(QualName, AttributeValue)]>,
29 id_idx: Option<u16>,
30 class_idx: Option<u16>,
31}
32
33pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);
35
36#[cfg(test)]
37impl Default for Attributes {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl Attributes {
44 #[cfg(test)]
45 pub fn new() -> Attributes {
46 Attributes {
47 attrs: [].into(),
48 id_idx: None,
49 class_idx: None,
50 }
51 }
52
53 pub unsafe fn new_from_xml2_attributes(
70 n_attributes: usize,
71 attrs: *const *const libc::c_char,
72 ) -> Result<Attributes, LoadingError> {
73 let mut array = Vec::with_capacity(n_attributes);
74 let mut id_idx = None;
75 let mut class_idx = None;
76
77 if n_attributes > limits::MAX_LOADED_ATTRIBUTES {
78 return Err(LoadingError::LimitExceeded(
79 ImplementationLimit::TooManyAttributes,
80 ));
81 }
82
83 if n_attributes > 0 && !attrs.is_null() {
84 for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
85 let localname = attr[0];
86 let prefix = attr[1];
87 let uri = attr[2];
88 let value_start = attr[3];
89 let value_end = attr[4];
90
91 assert!(!localname.is_null());
92
93 let localname = utf8_cstr(localname);
94
95 let prefix = opt_utf8_cstr(prefix);
96 let uri = opt_utf8_cstr(uri);
97 let qual_name = QualName::new(
98 prefix.map(Prefix::from),
99 uri.map(Namespace::from)
100 .unwrap_or_else(|| namespace_url!("")),
101 LocalName::from(localname),
102 );
103
104 if !value_start.is_null() && !value_end.is_null() {
105 assert!(value_end >= value_start);
106
107 let value_str = utf8_cstr_bounds(value_start, value_end);
108 let value_atom = DefaultAtom::from(value_str);
109
110 let idx = array.len() as u16;
111 match qual_name.expanded() {
112 expanded_name!("", "id") => id_idx = Some(idx),
113 expanded_name!("", "class") => class_idx = Some(idx),
114 _ => (),
115 }
116
117 array.push((qual_name, value_atom));
118 }
119 }
120 }
121
122 Ok(Attributes {
123 attrs: array.into(),
124 id_idx,
125 class_idx,
126 })
127 }
128
129 pub fn len(&self) -> usize {
131 self.attrs.len()
132 }
133
134 pub fn iter(&self) -> AttributesIter<'_> {
136 AttributesIter(self.attrs.iter())
137 }
138
139 pub fn get_id(&self) -> Option<&str> {
140 self.id_idx.and_then(|idx| {
141 self.attrs
142 .get(usize::from(idx))
143 .map(|(_name, value)| &value[..])
144 })
145 }
146
147 pub fn get_class(&self) -> Option<&str> {
148 self.class_idx.and_then(|idx| {
149 self.attrs
150 .get(usize::from(idx))
151 .map(|(_name, value)| &value[..])
152 })
153 }
154
155 pub fn clear_class(&mut self) {
156 self.class_idx = None;
157 }
158}
159
160impl<'a> Iterator for AttributesIter<'a> {
161 type Item = (QualName, &'a str);
162
163 fn next(&mut self) -> Option<Self::Item> {
164 self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use markup5ever::{expanded_name, local_name, ns};
172 use std::ffi::CString;
173 use std::ptr;
174
175 #[test]
176 fn empty_attributes() {
177 let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()).unwrap() };
178 assert_eq!(map.len(), 0);
179 }
180
181 #[test]
182 fn attributes_with_namespaces() {
183 let attrs = [
184 (
185 CString::new("href").unwrap(),
186 Some(CString::new("xlink").unwrap()),
187 Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
188 CString::new("1").unwrap(),
189 ),
190 (
191 CString::new("ry").unwrap(),
192 None,
193 None,
194 CString::new("2").unwrap(),
195 ),
196 (
197 CString::new("d").unwrap(),
198 None,
199 None,
200 CString::new("").unwrap(),
201 ),
202 ];
203
204 let mut v: Vec<*const libc::c_char> = Vec::new();
205
206 for (localname, prefix, uri, val) in &attrs {
207 v.push(localname.as_ptr());
208 v.push(
209 prefix
210 .as_ref()
211 .map(|p: &CString| p.as_ptr())
212 .unwrap_or_else(ptr::null),
213 );
214 v.push(
215 uri.as_ref()
216 .map(|p: &CString| p.as_ptr())
217 .unwrap_or_else(ptr::null),
218 );
219
220 let val_start = val.as_ptr();
221 let val_end = unsafe { val_start.add(val.as_bytes().len()) };
222 v.push(val_start); v.push(val_end); }
225
226 let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()).unwrap() };
227
228 let mut had_href: bool = false;
229 let mut had_ry: bool = false;
230 let mut had_d: bool = false;
231
232 for (a, v) in attrs.iter() {
233 match a.expanded() {
234 expanded_name!(xlink "href") => {
235 assert!(v == "1");
236 had_href = true;
237 }
238
239 expanded_name!("", "ry") => {
240 assert!(v == "2");
241 had_ry = true;
242 }
243
244 expanded_name!("", "d") => {
245 assert!(v.is_empty());
246 had_d = true;
247 }
248
249 _ => unreachable!(),
250 }
251 }
252
253 assert!(had_href);
254 assert!(had_ry);
255 assert!(had_d);
256 }
257}