macros.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7macro_rules! get_attr {
  8    ($elem:ident, $attr:tt, $type:tt) => (
  9        get_attr!($elem, $attr, $type, value, value.parse()?)
 10    );
 11    ($elem:ident, $attr:tt, optional, $value:ident, $func:expr) => (
 12        match $elem.attr($attr) {
 13            Some($value) => Some($func),
 14            None => None,
 15        }
 16    );
 17    ($elem:ident, $attr:tt, required, $value:ident, $func:expr) => (
 18        match $elem.attr($attr) {
 19            Some($value) => $func,
 20            None => return Err(Error::ParseError(concat!("Required attribute '", $attr, "' missing."))),
 21        }
 22    );
 23    ($elem:ident, $attr:tt, default, $value:ident, $func:expr) => (
 24        match $elem.attr($attr) {
 25            Some($value) => $func,
 26            None => Default::default(),
 27        }
 28    );
 29}
 30
 31macro_rules! generate_attribute {
 32    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+,}) => (
 33        generate_attribute!($elem, $name, {$($a => $b),+});
 34    );
 35    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+,}, Default = $default:ident) => (
 36        generate_attribute!($elem, $name, {$($a => $b),+}, Default = $default);
 37    );
 38    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+}) => (
 39        #[derive(Debug, Clone, PartialEq)]
 40        pub enum $elem {
 41            $(
 42                #[doc=$b]
 43                #[doc="value for this attribute."]
 44                $a
 45            ),+
 46        }
 47        impl FromStr for $elem {
 48            type Err = Error;
 49            fn from_str(s: &str) -> Result<$elem, Error> {
 50                Ok(match s {
 51                    $($b => $elem::$a),+,
 52                    _ => return Err(Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 53                })
 54            }
 55        }
 56        impl IntoAttributeValue for $elem {
 57            fn into_attribute_value(self) -> Option<String> {
 58                Some(String::from(match self {
 59                    $($elem::$a => $b),+
 60                }))
 61            }
 62        }
 63    );
 64    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+}, Default = $default:ident) => (
 65        #[derive(Debug, Clone, PartialEq)]
 66        pub enum $elem {
 67            $(
 68                #[doc=$b]
 69                #[doc="value for this attribute."]
 70                $a
 71            ),+
 72        }
 73        impl FromStr for $elem {
 74            type Err = Error;
 75            fn from_str(s: &str) -> Result<$elem, Error> {
 76                Ok(match s {
 77                    $($b => $elem::$a),+,
 78                    _ => return Err(Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 79                })
 80            }
 81        }
 82        impl IntoAttributeValue for $elem {
 83            #[allow(unreachable_patterns)]
 84            fn into_attribute_value(self) -> Option<String> {
 85                Some(String::from(match self {
 86                    $elem::$default => return None,
 87                    $($elem::$a => $b),+
 88                }))
 89            }
 90        }
 91        impl Default for $elem {
 92            fn default() -> $elem {
 93                $elem::$default
 94            }
 95        }
 96    );
 97}
 98
 99macro_rules! check_self {
100    ($elem:ident, $name:tt, $ns:expr) => (
101        check_self!($elem, $name, $ns, $name);
102    );
103    ($elem:ident, $name:tt, $ns:expr, $pretty_name:tt) => (
104        if !$elem.is($name, $ns) {
105            return Err(Error::ParseError(concat!("This is not a ", $pretty_name, " element.")));
106        }
107    );
108}
109
110macro_rules! check_ns_only {
111    ($elem:ident, $name:tt, $ns:expr) => (
112        if !$elem.has_ns($ns) {
113            return Err(Error::ParseError(concat!("This is not a ", $name, " element.")));
114        }
115    );
116}
117
118macro_rules! check_no_children {
119    ($elem:ident, $name:tt) => (
120        for _ in $elem.children() {
121            return Err(Error::ParseError(concat!("Unknown child in ", $name, " element.")));
122        }
123    );
124}
125
126macro_rules! check_no_attributes {
127    ($elem:ident, $name:tt) => (
128        check_no_unknown_attributes!($elem, $name, []);
129    );
130}
131
132macro_rules! check_no_unknown_attributes {
133    ($elem:ident, $name:tt, [$($attr:tt),*]) => (
134        for (_attr, _) in $elem.attrs() {
135            $(
136            if _attr == $attr {
137                continue;
138            }
139            )*
140            return Err(Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
141        }
142    );
143}
144
145macro_rules! generate_empty_element {
146    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr) => (
147        $(#[$meta])*
148        #[derive(Debug, Clone)]
149        pub struct $elem;
150
151        impl TryFrom<Element> for $elem {
152            type Err = Error;
153
154            fn try_from(elem: Element) -> Result<$elem, Error> {
155                check_self!(elem, $name, $ns);
156                check_no_children!(elem, $name);
157                check_no_attributes!(elem, $name);
158                Ok($elem)
159            }
160        }
161
162        impl From<$elem> for Element {
163            fn from(_: $elem) -> Element {
164                Element::builder($name)
165                        .ns($ns)
166                        .build()
167            }
168        }
169    );
170}
171
172macro_rules! generate_element_with_only_attributes {
173    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
174        generate_element_with_only_attributes!($(#[$meta])* $elem, $name, $ns, [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*]);
175    );
176    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
177        $(#[$meta])*
178        #[derive(Debug, Clone)]
179        pub struct $elem {
180            $(
181            $(#[$attr_meta])*
182            pub $attr: $attr_type,
183            )*
184        }
185
186        impl TryFrom<Element> for $elem {
187            type Err = Error;
188
189            fn try_from(elem: Element) -> Result<$elem, Error> {
190                check_self!(elem, $name, $ns);
191                check_no_children!(elem, $name);
192                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
193                Ok($elem {
194                    $(
195                    $attr: get_attr!(elem, $attr_name, $attr_action),
196                    )*
197                })
198            }
199        }
200
201        impl From<$elem> for Element {
202            fn from(elem: $elem) -> Element {
203                Element::builder($name)
204                        .ns($ns)
205                        $(
206                        .attr($attr_name, elem.$attr)
207                        )*
208                        .build()
209            }
210        }
211    );
212}
213
214macro_rules! generate_id {
215    ($elem:ident) => (
216        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
217        pub struct $elem(pub String);
218        impl FromStr for $elem {
219            type Err = Error;
220            fn from_str(s: &str) -> Result<$elem, Error> {
221                // TODO: add a way to parse that differently when needed.
222                Ok($elem(String::from(s)))
223            }
224        }
225        impl IntoAttributeValue for $elem {
226            fn into_attribute_value(self) -> Option<String> {
227                Some(self.0)
228            }
229        }
230    );
231}
232
233macro_rules! generate_elem_id {
234    ($elem:ident, $name:tt, $ns:expr) => (
235        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
236        pub struct $elem(pub String);
237        impl FromStr for $elem {
238            type Err = Error;
239            fn from_str(s: &str) -> Result<$elem, Error> {
240                // TODO: add a way to parse that differently when needed.
241                Ok($elem(String::from(s)))
242            }
243        }
244        impl From<$elem> for Element {
245            fn from(elem: $elem) -> Element {
246                Element::builder($name)
247                        .ns($ns)
248                        .append(elem.0)
249                        .build()
250            }
251        }
252    );
253}
254
255macro_rules! generate_element_with_text {
256    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], $text_ident:ident: $codec:ident < $text_type:ty >) => (
257        $(#[$meta])*
258        #[derive(Debug, Clone)]
259        pub struct $elem {
260            $(
261            $(#[$attr_meta])*
262            pub $attr: $attr_type,
263            )*
264            pub $text_ident: $text_type,
265        }
266
267        impl TryFrom<Element> for $elem {
268            type Err = Error;
269
270            fn try_from(elem: Element) -> Result<$elem, Error> {
271                check_self!(elem, $name, $ns);
272                check_no_children!(elem, $name);
273                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
274                Ok($elem {
275                    $(
276                    $attr: get_attr!(elem, $attr_name, $attr_action),
277                    )*
278                    $text_ident: $codec::decode(&elem.text())?,
279                })
280            }
281        }
282
283        impl From<$elem> for Element {
284            fn from(elem: $elem) -> Element {
285                Element::builder($name)
286                        .ns($ns)
287                        $(
288                        .attr($attr_name, elem.$attr)
289                        )*
290                        .append($codec::encode(&elem.$text_ident))
291                        .build()
292            }
293        }
294    );
295}
296
297macro_rules! generate_element_with_children {
298    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], children: [$($(#[$child_meta:meta])* $child_ident:ident: Vec<$child_type:ty> = $child_name:tt => $child_constructor:ident),+]) => (
299        $(#[$meta])*
300        #[derive(Debug, Clone)]
301        pub struct $elem {
302            $(
303            $(#[$attr_meta])*
304            pub $attr: $attr_type,
305            )*
306            $(
307            $(#[$child_meta])*
308            pub $child_ident: Vec<$child_type>,
309            )*
310        }
311
312        impl TryFrom<Element> for $elem {
313            type Err = Error;
314
315            fn try_from(elem: Element) -> Result<$elem, Error> {
316                check_self!(elem, $name, $ns);
317                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
318                let mut parsed_children = vec!();
319                for child in elem.children() {
320                    $(
321                    let parsed_child = $child_constructor::try_from(child.clone())?;
322                    parsed_children.push(parsed_child);
323                    )*
324                }
325                Ok($elem {
326                    $(
327                    $attr: get_attr!(elem, $attr_name, $attr_action),
328                    )*
329                    $(
330                    $child_ident: parsed_children,
331                    )*
332                })
333            }
334        }
335
336        impl From<$elem> for Element {
337            fn from(elem: $elem) -> Element {
338                Element::builder($name)
339                        .ns($ns)
340                        $(
341                        .attr($attr_name, elem.$attr)
342                        )*
343                        .build()
344            }
345        }
346    );
347}