Skip to main content

rsvg/xml/
attributes.rs

1//! Store XML element attributes and their values.
2
3use 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
15/// Type used to store attribute values.
16///
17/// Attribute values are often repeated in an SVG file, so we intern them using the
18/// string_cache crate.
19pub type AttributeValue = DefaultAtom;
20
21/// Iterable wrapper for libxml2's representation of attribute/value.
22///
23/// See the [`new_from_xml2_attributes`] function for information.
24///
25/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
26#[derive(Clone)]
27pub struct Attributes {
28    attrs: Box<[(QualName, AttributeValue)]>,
29    id_idx: Option<u16>,
30    class_idx: Option<u16>,
31}
32
33/// Iterator from `Attributes.iter`.
34pub 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    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
54    ///
55    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
56    /// callback gets passed a `xmlChar **` for attributes, which
57    /// comes in groups of (localname/prefix/URI/value_start/value_end).
58    /// In those, localname/prefix/URI are NUL-terminated strings;
59    /// value_start and value_end point to the start-inclusive and
60    /// end-exclusive bytes in the attribute's value.
61    ///
62    /// # Safety
63    ///
64    /// This function is unsafe because the caller must guarantee the following:
65    ///
66    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
67    ///
68    /// * All strings are valid UTF-8.
69    #[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    /// Returns the number of attributes.
131    pub fn len(&self) -> usize {
132        self.attrs.len()
133    }
134
135    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
136    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); // value_start
224            v.push(val_end); // value_end
225        }
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}