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