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_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 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 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 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 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        check_no_unknown_attributes!($elem, $name, []);
210    );
211}
212
213macro_rules! check_no_unknown_attributes {
214    ($elem:ident, $name:tt, [$($attr:tt),*]) => (
215        for (_attr, _) in $elem.attrs() {
216            $(
217            if _attr == $attr {
218                continue;
219            }
220            )*
221            return Err(Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
222        }
223    );
224}
225
226macro_rules! generate_empty_element {
227    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr) => (
228        $(#[$meta])*
229        #[derive(Debug, Clone)]
230        pub struct $elem;
231
232        impl TryFrom<Element> for $elem {
233            type Err = Error;
234
235            fn try_from(elem: Element) -> Result<$elem, Error> {
236                check_self!(elem, $name, $ns);
237                check_no_children!(elem, $name);
238                check_no_attributes!(elem, $name);
239                Ok($elem)
240            }
241        }
242
243        impl From<$elem> for Element {
244            fn from(_: $elem) -> Element {
245                Element::builder($name)
246                        .ns($ns)
247                        .build()
248            }
249        }
250    );
251}
252
253macro_rules! generate_element_with_only_attributes {
254    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
255        generate_element_with_only_attributes!($(#[$meta])* $elem, $name, $ns, [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*]);
256    );
257    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
258        $(#[$meta])*
259        #[derive(Debug, Clone)]
260        pub struct $elem {
261            $(
262            $(#[$attr_meta])*
263            pub $attr: $attr_type,
264            )*
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                })
279            }
280        }
281
282        impl From<$elem> for Element {
283            fn from(elem: $elem) -> Element {
284                Element::builder($name)
285                        .ns($ns)
286                        $(
287                        .attr($attr_name, elem.$attr)
288                        )*
289                        .build()
290            }
291        }
292    );
293}
294
295macro_rules! generate_id {
296    ($elem:ident) => (
297        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
298        pub struct $elem(pub String);
299        impl FromStr for $elem {
300            type Err = Error;
301            fn from_str(s: &str) -> Result<$elem, Error> {
302                // TODO: add a way to parse that differently when needed.
303                Ok($elem(String::from(s)))
304            }
305        }
306        impl IntoAttributeValue for $elem {
307            fn into_attribute_value(self) -> Option<String> {
308                Some(self.0)
309            }
310        }
311    );
312}
313
314macro_rules! generate_elem_id {
315    ($elem:ident, $name:tt, $ns:expr) => (
316        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
317        pub struct $elem(pub String);
318        impl FromStr for $elem {
319            type Err = Error;
320            fn from_str(s: &str) -> Result<$elem, Error> {
321                // TODO: add a way to parse that differently when needed.
322                Ok($elem(String::from(s)))
323            }
324        }
325        impl TryFrom<Element> for $elem {
326            type Err = Error;
327            fn try_from(elem: Element) -> Result<$elem, Error> {
328                check_self!(elem, $name, $ns);
329                check_no_children!(elem, $name);
330                check_no_attributes!(elem, $name);
331                // TODO: add a way to parse that differently when needed.
332                Ok($elem(elem.text()))
333            }
334        }
335        impl From<$elem> for Element {
336            fn from(elem: $elem) -> Element {
337                Element::builder($name)
338                        .ns($ns)
339                        .append(elem.0)
340                        .build()
341            }
342        }
343    );
344}
345
346macro_rules! generate_element_with_text {
347    ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:expr, $text_ident:ident: $codec:ident < $text_type:ty >) => (
348        generate_element_with_text!($(#[$meta])* $elem, $name, $ns, [], $text_ident: $codec<$text_type>);
349    );
350    ($(#[$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 >) => (
351        $(#[$meta])*
352        #[derive(Debug, Clone)]
353        pub struct $elem {
354            $(
355            $(#[$attr_meta])*
356            pub $attr: $attr_type,
357            )*
358            pub $text_ident: $text_type,
359        }
360
361        impl TryFrom<Element> for $elem {
362            type Err = Error;
363
364            fn try_from(elem: Element) -> Result<$elem, Error> {
365                check_self!(elem, $name, $ns);
366                check_no_children!(elem, $name);
367                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
368                Ok($elem {
369                    $(
370                    $attr: get_attr!(elem, $attr_name, $attr_action),
371                    )*
372                    $text_ident: $codec::decode(&elem.text())?,
373                })
374            }
375        }
376
377        impl From<$elem> for Element {
378            fn from(elem: $elem) -> Element {
379                Element::builder($name)
380                        .ns($ns)
381                        $(
382                        .attr($attr_name, elem.$attr)
383                        )*
384                        .append($codec::encode(&elem.$text_ident))
385                        .build()
386            }
387        }
388    );
389}
390
391macro_rules! generate_element_with_children {
392    ($(#[$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),+]) => (
393        $(#[$meta])*
394        #[derive(Debug, Clone)]
395        pub struct $elem {
396            $(
397            $(#[$attr_meta])*
398            pub $attr: $attr_type,
399            )*
400            $(
401            $(#[$child_meta])*
402            pub $child_ident: Vec<$child_type>,
403            )*
404        }
405
406        impl TryFrom<Element> for $elem {
407            type Err = Error;
408
409            fn try_from(elem: Element) -> Result<$elem, Error> {
410                check_self!(elem, $name, $ns);
411                check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
412                let mut parsed_children = vec!();
413                for child in elem.children() {
414                    $(
415                    if child.is($child_name, $child_ns) {
416                        let parsed_child = $child_constructor::try_from(child.clone())?;
417                        parsed_children.push(parsed_child);
418                        continue;
419                    }
420                    )*
421                    return Err(Error::ParseError(concat!("Unknown child in ", $name, " element.")));
422                }
423                Ok($elem {
424                    $(
425                    $attr: get_attr!(elem, $attr_name, $attr_action),
426                    )*
427                    $(
428                    $child_ident: parsed_children,
429                    )*
430                })
431            }
432        }
433
434        impl From<$elem> for Element {
435            fn from(elem: $elem) -> Element {
436                Element::builder($name)
437                        .ns($ns)
438                        $(
439                        .attr($attr_name, elem.$attr)
440                        )*
441                        $(
442                        .append(elem.$child_ident)
443                        )*
444                        .build()
445            }
446        }
447    );
448}