macros.rs

  1// Copyright (c) 2017-2018 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_empty, $value:ident, $func:expr) => (
 12        match $elem.attr($attr) {
 13            Some("") => None,
 14            Some($value) => Some($func),
 15            None => None,
 16        }
 17    );
 18    ($elem:ident, $attr:tt, optional, $value:ident, $func:expr) => (
 19        match $elem.attr($attr) {
 20            Some($value) => Some($func),
 21            None => None,
 22        }
 23    );
 24    ($elem:ident, $attr:tt, required, $value:ident, $func:expr) => (
 25        match $elem.attr($attr) {
 26            Some($value) => $func,
 27            None => return Err(Error::ParseError(concat!("Required attribute '", $attr, "' missing."))),
 28        }
 29    );
 30    ($elem:ident, $attr:tt, default, $value:ident, $func:expr) => (
 31        match $elem.attr($attr) {
 32            Some($value) => $func,
 33            None => Default::default(),
 34        }
 35    );
 36}
 37
 38macro_rules! generate_attribute {
 39    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+,}) => (
 40        generate_attribute!($elem, $name, {$($a => $b),+});
 41    );
 42    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+,}, Default = $default:ident) => (
 43        generate_attribute!($elem, $name, {$($a => $b),+}, Default = $default);
 44    );
 45    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+}) => (
 46        #[derive(Debug, Clone, PartialEq)]
 47        pub enum $elem {
 48            $(
 49                #[doc=$b]
 50                #[doc="value for this attribute."]
 51                $a
 52            ),+
 53        }
 54        impl ::std::str::FromStr for $elem {
 55            type Err = Error;
 56            fn from_str(s: &str) -> Result<$elem, Error> {
 57                Ok(match s {
 58                    $($b => $elem::$a),+,
 59                    _ => return Err(Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 60                })
 61            }
 62        }
 63        impl IntoAttributeValue for $elem {
 64            fn into_attribute_value(self) -> Option<String> {
 65                Some(String::from(match self {
 66                    $($elem::$a => $b),+
 67                }))
 68            }
 69        }
 70    );
 71    ($elem:ident, $name:tt, {$($a:ident => $b:tt),+}, Default = $default:ident) => (
 72        #[derive(Debug, Clone, PartialEq)]
 73        pub enum $elem {
 74            $(
 75                #[doc=$b]
 76                #[doc="value for this attribute."]
 77                $a
 78            ),+
 79        }
 80        impl ::std::str::FromStr for $elem {
 81            type Err = Error;
 82            fn from_str(s: &str) -> Result<$elem, Error> {
 83                Ok(match s {
 84                    $($b => $elem::$a),+,
 85                    _ => return Err(Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
 86                })
 87            }
 88        }
 89        impl IntoAttributeValue for $elem {
 90            #[allow(unreachable_patterns)]
 91            fn into_attribute_value(self) -> Option<String> {
 92                Some(String::from(match self {
 93                    $elem::$default => return None,
 94                    $($elem::$a => $b),+
 95                }))
 96            }
 97        }
 98        impl Default for $elem {
 99            fn default() -> $elem {
100                $elem::$default
101            }
102        }
103    );
104}
105
106macro_rules! generate_element_enum {
107    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
108        generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+});
109    );
110    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
111        $(#[$meta])*
112        #[derive(Debug, Clone, PartialEq)]
113        pub enum $elem {
114            $(
115            $(#[$enum_meta])*
116            $enum
117            ),+
118        }
119        impl ::try_from::TryFrom<Element> for $elem {
120            type Err = Error;
121            fn try_from(elem: Element) -> Result<$elem, Error> {
122                check_ns_only!(elem, $name, $ns);
123                check_no_children!(elem, $name);
124                check_no_attributes!(elem, $name);
125                Ok(match elem.name() {
126                    $($enum_name => $elem::$enum,)+
127                    _ => return Err(Error::ParseError(concat!("This is not a ", $name, " element."))),
128                })
129            }
130        }
131        impl From<$elem> for Element {
132            fn from(elem: $elem) -> Element {
133                Element::builder(match elem {
134                    $($elem::$enum => $enum_name,)+
135                }).ns($ns)
136                  .build()
137            }
138        }
139    );
140}
141
142macro_rules! generate_attribute_enum {
143    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
144        generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
145    );
146    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
147        $(#[$meta])*
148        #[derive(Debug, Clone, PartialEq)]
149        pub enum $elem {
150            $(
151            $(#[$enum_meta])*
152            $enum
153            ),+
154        }
155        impl ::try_from::TryFrom<Element> for $elem {
156            type Err = Error;
157            fn try_from(elem: Element) -> Result<$elem, Error> {
158                check_ns_only!(elem, $name, $ns);
159                check_no_children!(elem, $name);
160                check_no_unknown_attributes!(elem, $name, [$attr]);
161                Ok(match get_attr!(elem, $attr, required) {
162                    $($enum_name => $elem::$enum,)+
163                    _ => return Err(Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
164                })
165            }
166        }
167        impl From<$elem> for Element {
168            fn from(elem: $elem) -> Element {
169                Element::builder($name)
170                        .ns($ns)
171                        .attr($attr, match elem {
172                             $($elem::$enum => $enum_name,)+
173                         })
174                         .build()
175            }
176        }
177    );
178}
179
180macro_rules! check_self {
181    ($elem:ident, $name:tt, $ns:expr) => (
182        check_self!($elem, $name, $ns, $name);
183    );
184    ($elem:ident, $name:tt, $ns:expr, $pretty_name:tt) => (
185        if !$elem.is($name, $ns) {
186            return Err(Error::ParseError(concat!("This is not a ", $pretty_name, " element.")));
187        }
188    );
189}
190
191macro_rules! check_ns_only {
192    ($elem:ident, $name:tt, $ns:expr) => (
193        if !$elem.has_ns($ns) {
194            return Err(Error::ParseError(concat!("This is not a ", $name, " element.")));
195        }
196    );
197}
198
199macro_rules! check_no_children {
200    ($elem:ident, $name:tt) => (
201        for _ in $elem.children() {
202            return Err(Error::ParseError(concat!("Unknown child in ", $name, " element.")));
203        }
204    );
205}
206
207macro_rules! check_no_attributes {
208    ($elem:ident, $name:tt) => (
209        for _ in $elem.attrs() {
210            return Err(Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
211        }
212    );
213}
214
215macro_rules! check_no_unknown_attributes {
216    ($elem:ident, $name:tt, [$($attr:tt),*]) => (
217        for (_attr, _) in $elem.attrs() {
218            $(
219            if _attr == $attr {
220                continue;
221            }
222            )*
223            return Err(Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
224        }
225    );
226}
227
228macro_rules! generate_empty_element {
229    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr) => (
230        $(#[$meta])*
231        #[derive(Debug, Clone)]
232        pub struct $elem;
233
234        impl ::try_from::TryFrom<Element> for $elem {
235            type Err = Error;
236
237            fn try_from(elem: Element) -> Result<$elem, Error> {
238                check_self!(elem, $name, $ns);
239                check_no_children!(elem, $name);
240                check_no_attributes!(elem, $name);
241                Ok($elem)
242            }
243        }
244
245        impl From<$elem> for Element {
246            fn from(_: $elem) -> Element {
247                Element::builder($name)
248                        .ns($ns)
249                        .build()
250            }
251        }
252    );
253}
254
255macro_rules! generate_element_with_only_attributes {
256    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
257        generate_element_with_only_attributes!($(#[$meta])* $elem, $name, $ns, [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*]);
258    );
259    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
260        $(#[$meta])*
261        #[derive(Debug, Clone)]
262        pub struct $elem {
263            $(
264            $(#[$attr_meta])*
265            pub $attr: $attr_type,
266            )*
267        }
268
269        impl ::try_from::TryFrom<Element> for $elem {
270            type Err = Error;
271
272            fn try_from(elem: Element) -> Result<$elem, Error> {
273                check_self!(elem, $name, $ns);
274                check_no_children!(elem, $name);
275                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
276                Ok($elem {
277                    $(
278                    $attr: get_attr!(elem, $attr_name, $attr_action),
279                    )*
280                })
281            }
282        }
283
284        impl From<$elem> for Element {
285            fn from(elem: $elem) -> Element {
286                Element::builder($name)
287                        .ns($ns)
288                        $(
289                        .attr($attr_name, elem.$attr)
290                        )*
291                        .build()
292            }
293        }
294    );
295}
296
297macro_rules! generate_id {
298    ($elem:ident) => (
299        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
300        pub struct $elem(pub String);
301        impl ::std::str::FromStr for $elem {
302            type Err = Error;
303            fn from_str(s: &str) -> Result<$elem, Error> {
304                // TODO: add a way to parse that differently when needed.
305                Ok($elem(String::from(s)))
306            }
307        }
308        impl IntoAttributeValue for $elem {
309            fn into_attribute_value(self) -> Option<String> {
310                Some(self.0)
311            }
312        }
313    );
314}
315
316macro_rules! generate_elem_id {
317    ($elem:ident, $name:tt, $ns:expr) => (
318        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
319        pub struct $elem(pub String);
320        impl ::std::str::FromStr for $elem {
321            type Err = Error;
322            fn from_str(s: &str) -> Result<$elem, Error> {
323                // TODO: add a way to parse that differently when needed.
324                Ok($elem(String::from(s)))
325            }
326        }
327        impl ::try_from::TryFrom<Element> for $elem {
328            type Err = Error;
329            fn try_from(elem: Element) -> Result<$elem, Error> {
330                check_self!(elem, $name, $ns);
331                check_no_children!(elem, $name);
332                check_no_attributes!(elem, $name);
333                // TODO: add a way to parse that differently when needed.
334                Ok($elem(elem.text()))
335            }
336        }
337        impl From<$elem> for Element {
338            fn from(elem: $elem) -> Element {
339                Element::builder($name)
340                        .ns($ns)
341                        .append(elem.0)
342                        .build()
343            }
344        }
345    );
346}
347
348macro_rules! generate_element_with_text {
349    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, $text_ident:ident: $codec:ident < $text_type:ty >) => (
350        generate_element_with_text!($(#[$meta])* $elem, $name, $ns, [], $text_ident: $codec<$text_type>);
351    );
352    ($(#[$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 >) => (
353        $(#[$meta])*
354        #[derive(Debug, Clone)]
355        pub struct $elem {
356            $(
357            $(#[$attr_meta])*
358            pub $attr: $attr_type,
359            )*
360            pub $text_ident: $text_type,
361        }
362
363        impl ::try_from::TryFrom<Element> for $elem {
364            type Err = Error;
365
366            fn try_from(elem: Element) -> Result<$elem, Error> {
367                check_self!(elem, $name, $ns);
368                check_no_children!(elem, $name);
369                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
370                Ok($elem {
371                    $(
372                    $attr: get_attr!(elem, $attr_name, $attr_action),
373                    )*
374                    $text_ident: $codec::decode(&elem.text())?,
375                })
376            }
377        }
378
379        impl From<$elem> for Element {
380            fn from(elem: $elem) -> Element {
381                Element::builder($name)
382                        .ns($ns)
383                        $(
384                        .attr($attr_name, elem.$attr)
385                        )*
386                        .append($codec::encode(&elem.$text_ident))
387                        .build()
388            }
389        }
390    );
391}
392
393macro_rules! generate_element_with_children {
394    ($(#[$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_ns:expr) => $child_constructor:ident),+]) => (
395        $(#[$meta])*
396        #[derive(Debug, Clone)]
397        pub struct $elem {
398            $(
399            $(#[$attr_meta])*
400            pub $attr: $attr_type,
401            )*
402            $(
403            $(#[$child_meta])*
404            pub $child_ident: Vec<$child_type>,
405            )*
406        }
407
408        impl ::try_from::TryFrom<Element> for $elem {
409            type Err = Error;
410
411            fn try_from(elem: Element) -> Result<$elem, Error> {
412                check_self!(elem, $name, $ns);
413                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
414                let mut parsed_children = vec!();
415                for child in elem.children() {
416                    $(
417                    if child.is($child_name, $child_ns) {
418                        let parsed_child = $child_constructor::try_from(child.clone())?;
419                        parsed_children.push(parsed_child);
420                        continue;
421                    }
422                    )*
423                    return Err(Error::ParseError(concat!("Unknown child in ", $name, " element.")));
424                }
425                Ok($elem {
426                    $(
427                    $attr: get_attr!(elem, $attr_name, $attr_action),
428                    )*
429                    $(
430                    $child_ident: parsed_children,
431                    )*
432                })
433            }
434        }
435
436        impl From<$elem> for Element {
437            fn from(elem: $elem) -> Element {
438                Element::builder($name)
439                        .ns($ns)
440                        $(
441                        .attr($attr_name, elem.$attr)
442                        )*
443                        $(
444                        .append(elem.$child_ident)
445                        )*
446                        .build()
447            }
448        }
449    );
450}