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 => {
28 return Err(crate::error::Error::ParseError(concat!(
29 "Required attribute '",
30 $attr,
31 "' missing."
32 )))
33 }
34 }
35 };
36 ($elem:ident, $attr:tt, default, $value:ident, $func:expr) => {
37 match $elem.attr($attr) {
38 Some($value) => $func,
39 None => ::std::default::Default::default(),
40 }
41 };
42}
43
44macro_rules! generate_attribute {
45 ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}) => (
46 generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+});
47 );
48 ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+,}, Default = $default:ident) => (
49 generate_attribute!($(#[$meta])* $elem, $name, {$($(#[$a_meta])* $a => $b),+}, Default = $default);
50 );
51 ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}) => (
52 $(#[$meta])*
53 #[derive(Debug, Clone, PartialEq)]
54 pub enum $elem {
55 $(
56 $(#[$a_meta])*
57 $a
58 ),+
59 }
60 impl ::std::str::FromStr for $elem {
61 type Err = crate::error::Error;
62 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
63 Ok(match s {
64 $($b => $elem::$a),+,
65 _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
66 })
67 }
68 }
69 impl ::minidom::IntoAttributeValue for $elem {
70 fn into_attribute_value(self) -> Option<String> {
71 Some(String::from(match self {
72 $($elem::$a => $b),+
73 }))
74 }
75 }
76 );
77 ($(#[$meta:meta])* $elem:ident, $name:tt, {$($(#[$a_meta:meta])* $a:ident => $b:tt),+}, Default = $default:ident) => (
78 $(#[$meta])*
79 #[derive(Debug, Clone, PartialEq)]
80 pub enum $elem {
81 $(
82 $(#[$a_meta])*
83 $a
84 ),+
85 }
86 impl ::std::str::FromStr for $elem {
87 type Err = crate::error::Error;
88 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
89 Ok(match s {
90 $($b => $elem::$a),+,
91 _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
92 })
93 }
94 }
95 impl ::minidom::IntoAttributeValue for $elem {
96 #[allow(unreachable_patterns)]
97 fn into_attribute_value(self) -> Option<String> {
98 Some(String::from(match self {
99 $elem::$default => return None,
100 $($elem::$a => $b),+
101 }))
102 }
103 }
104 impl ::std::default::Default for $elem {
105 fn default() -> $elem {
106 $elem::$default
107 }
108 }
109 );
110 ($(#[$meta:meta])* $elem:ident, $name:tt, bool) => (
111 $(#[$meta])*
112 #[derive(Debug, Clone, PartialEq)]
113 pub enum $elem {
114 /// True value, represented by either 'true' or '1'.
115 True,
116 /// False value, represented by either 'false' or '0'.
117 False,
118 }
119 impl ::std::str::FromStr for $elem {
120 type Err = crate::error::Error;
121 fn from_str(s: &str) -> Result<Self, crate::error::Error> {
122 Ok(match s {
123 "true" | "1" => $elem::True,
124 "false" | "0" => $elem::False,
125 _ => return Err(crate::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
126 })
127 }
128 }
129 impl ::minidom::IntoAttributeValue for $elem {
130 fn into_attribute_value(self) -> Option<String> {
131 match self {
132 $elem::True => Some(String::from("true")),
133 $elem::False => None
134 }
135 }
136 }
137 impl ::std::default::Default for $elem {
138 fn default() -> $elem {
139 $elem::False
140 }
141 }
142 );
143}
144
145macro_rules! generate_element_enum {
146 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
147 generate_element_enum!($(#[$meta])* $elem, $name, $ns, {$($(#[$enum_meta])* $enum => $enum_name),+});
148 );
149 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
150 $(#[$meta])*
151 #[derive(Debug, Clone, PartialEq)]
152 pub enum $elem {
153 $(
154 $(#[$enum_meta])*
155 $enum
156 ),+
157 }
158 impl ::try_from::TryFrom<::minidom::Element> for $elem {
159 type Err = crate::error::Error;
160 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
161 check_ns_only!(elem, $name, $ns);
162 check_no_children!(elem, $name);
163 check_no_attributes!(elem, $name);
164 Ok(match elem.name() {
165 $($enum_name => $elem::$enum,)+
166 _ => return Err(crate::error::Error::ParseError(concat!("This is not a ", $name, " element."))),
167 })
168 }
169 }
170 impl From<$elem> for ::minidom::Element {
171 fn from(elem: $elem) -> ::minidom::Element {
172 ::minidom::Element::builder(match elem {
173 $($elem::$enum => $enum_name,)+
174 }).ns(crate::ns::$ns)
175 .build()
176 }
177 }
178 );
179}
180
181macro_rules! generate_attribute_enum {
182 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
183 generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
184 );
185 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
186 $(#[$meta])*
187 #[derive(Debug, Clone, PartialEq)]
188 pub enum $elem {
189 $(
190 $(#[$enum_meta])*
191 $enum
192 ),+
193 }
194 impl ::try_from::TryFrom<::minidom::Element> for $elem {
195 type Err = crate::error::Error;
196 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
197 check_ns_only!(elem, $name, $ns);
198 check_no_children!(elem, $name);
199 check_no_unknown_attributes!(elem, $name, [$attr]);
200 Ok(match get_attr!(elem, $attr, required) {
201 $($enum_name => $elem::$enum,)+
202 _ => return Err(crate::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
203 })
204 }
205 }
206 impl From<$elem> for ::minidom::Element {
207 fn from(elem: $elem) -> ::minidom::Element {
208 ::minidom::Element::builder($name)
209 .ns(crate::ns::$ns)
210 .attr($attr, match elem {
211 $($elem::$enum => $enum_name,)+
212 })
213 .build()
214 }
215 }
216 );
217}
218
219macro_rules! check_self {
220 ($elem:ident, $name:tt, $ns:ident) => {
221 check_self!($elem, $name, $ns, $name);
222 };
223 ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
224 if !$elem.is($name, crate::ns::$ns) {
225 return Err(crate::error::Error::ParseError(concat!(
226 "This is not a ",
227 $pretty_name,
228 " element."
229 )));
230 }
231 };
232}
233
234macro_rules! check_ns_only {
235 ($elem:ident, $name:tt, $ns:ident) => {
236 if !$elem.has_ns(crate::ns::$ns) {
237 return Err(crate::error::Error::ParseError(concat!(
238 "This is not a ",
239 $name,
240 " element."
241 )));
242 }
243 };
244}
245
246macro_rules! check_no_children {
247 ($elem:ident, $name:tt) => {
248 for _ in $elem.children() {
249 return Err(crate::error::Error::ParseError(concat!(
250 "Unknown child in ",
251 $name,
252 " element."
253 )));
254 }
255 };
256}
257
258macro_rules! check_no_attributes {
259 ($elem:ident, $name:tt) => {
260 for _ in $elem.attrs() {
261 return Err(crate::error::Error::ParseError(concat!(
262 "Unknown attribute in ",
263 $name,
264 " element."
265 )));
266 }
267 };
268}
269
270macro_rules! check_no_unknown_attributes {
271 ($elem:ident, $name:tt, [$($attr:tt),*]) => (
272 for (_attr, _) in $elem.attrs() {
273 $(
274 if _attr == $attr {
275 continue;
276 }
277 )*
278 return Err(crate::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
279 }
280 );
281}
282
283macro_rules! generate_empty_element {
284 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
285 $(#[$meta])*
286 #[derive(Debug, Clone)]
287 pub struct $elem;
288
289 impl ::try_from::TryFrom<::minidom::Element> for $elem {
290 type Err = crate::error::Error;
291
292 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
293 check_self!(elem, $name, $ns);
294 check_no_children!(elem, $name);
295 check_no_attributes!(elem, $name);
296 Ok($elem)
297 }
298 }
299
300 impl From<$elem> for ::minidom::Element {
301 fn from(_: $elem) -> ::minidom::Element {
302 ::minidom::Element::builder($name)
303 .ns(crate::ns::$ns)
304 .build()
305 }
306 }
307 );
308}
309
310macro_rules! generate_id {
311 ($(#[$meta:meta])* $elem:ident) => (
312 $(#[$meta])*
313 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
314 pub struct $elem(pub String);
315 impl ::std::str::FromStr for $elem {
316 type Err = crate::error::Error;
317 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
318 // TODO: add a way to parse that differently when needed.
319 Ok($elem(String::from(s)))
320 }
321 }
322 impl ::minidom::IntoAttributeValue for $elem {
323 fn into_attribute_value(self) -> Option<String> {
324 Some(self.0)
325 }
326 }
327 );
328}
329
330macro_rules! generate_elem_id {
331 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
332 $(#[$meta])*
333 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
334 pub struct $elem(pub String);
335 impl ::std::str::FromStr for $elem {
336 type Err = crate::error::Error;
337 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
338 // TODO: add a way to parse that differently when needed.
339 Ok($elem(String::from(s)))
340 }
341 }
342 impl ::try_from::TryFrom<::minidom::Element> for $elem {
343 type Err = crate::error::Error;
344 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
345 check_self!(elem, $name, $ns);
346 check_no_children!(elem, $name);
347 check_no_attributes!(elem, $name);
348 // TODO: add a way to parse that differently when needed.
349 Ok($elem(elem.text()))
350 }
351 }
352 impl From<$elem> for ::minidom::Element {
353 fn from(elem: $elem) -> ::minidom::Element {
354 ::minidom::Element::builder($name)
355 .ns(crate::ns::$ns)
356 .append(elem.0)
357 .build()
358 }
359 }
360 );
361}
362
363macro_rules! start_decl {
364 (Vec, $type:ty) => (
365 Vec<$type>
366 );
367 (Option, $type:ty) => (
368 Option<$type>
369 );
370 (Required, $type:ty) => (
371 $type
372 );
373}
374
375macro_rules! start_parse_elem {
376 ($temp:ident: Vec) => {
377 let mut $temp = Vec::new();
378 };
379 ($temp:ident: Option) => {
380 let mut $temp = None;
381 };
382 ($temp:ident: Required) => {
383 let mut $temp = None;
384 };
385}
386
387macro_rules! do_parse {
388 ($elem:ident, Element) => {
389 $elem.clone()
390 };
391 ($elem:ident, String) => {
392 $elem.text()
393 };
394 ($elem:ident, $constructor:ident) => {
395 $constructor::try_from($elem.clone())?
396 };
397}
398
399macro_rules! do_parse_elem {
400 ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
401 $temp.push(do_parse!($elem, $constructor));
402 };
403 ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
404 if $temp.is_some() {
405 return Err(crate::error::Error::ParseError(concat!(
406 "Element ",
407 $parent_name,
408 " must not have more than one ",
409 $name,
410 " child."
411 )));
412 }
413 $temp = Some(do_parse!($elem, $constructor));
414 };
415 ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
416 if $temp.is_some() {
417 return Err(crate::error::Error::ParseError(concat!(
418 "Element ",
419 $parent_name,
420 " must not have more than one ",
421 $name,
422 " child."
423 )));
424 }
425 $temp = Some(do_parse!($elem, $constructor));
426 };
427}
428
429macro_rules! finish_parse_elem {
430 ($temp:ident: Vec = $name:tt, $parent_name:tt) => {
431 $temp
432 };
433 ($temp:ident: Option = $name:tt, $parent_name:tt) => {
434 $temp
435 };
436 ($temp:ident: Required = $name:tt, $parent_name:tt) => {
437 $temp.ok_or(crate::error::Error::ParseError(concat!(
438 "Missing child ",
439 $name,
440 " in ",
441 $parent_name,
442 " element."
443 )))?
444 };
445}
446
447macro_rules! generate_serialiser {
448 ($parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
449 ::minidom::Element::builder($name)
450 .ns(crate::ns::$ns)
451 .append($parent.$elem)
452 .build()
453 };
454 ($parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
455 $parent.$elem.map(|elem| {
456 ::minidom::Element::builder($name)
457 .ns(crate::ns::$ns)
458 .append(elem)
459 .build()
460 })
461 };
462 ($parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
463 $parent.$elem
464 };
465}
466
467macro_rules! generate_element {
468 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+,]) => (
469 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
470 );
471 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
472 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
473 );
474 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*]) => (
475 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
476 );
477 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),*,], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*]) => (
478 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
479 );
480 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
481 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
482 );
483 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
484 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: [], text: ($(#[$text_meta])* $text_ident: $codec<$text_type>));
485 );
486 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),*], children: [$($(#[$child_meta:meta])* $child_ident:ident: $coucou:tt<$child_type:ty> = ($child_name:tt, $child_ns:ident) => $child_constructor:ident),*] $(, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >))*) => (
487 $(#[$meta])*
488 #[derive(Debug, Clone)]
489 pub struct $elem {
490 $(
491 $(#[$attr_meta])*
492 pub $attr: $attr_type,
493 )*
494 $(
495 $(#[$child_meta])*
496 pub $child_ident: start_decl!($coucou, $child_type),
497 )*
498 $(
499 $(#[$text_meta])*
500 pub $text_ident: $text_type,
501 )*
502 }
503
504 impl ::try_from::TryFrom<::minidom::Element> for $elem {
505 type Err = crate::error::Error;
506
507 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
508 check_self!(elem, $name, $ns);
509 check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
510 $(
511 start_parse_elem!($child_ident: $coucou);
512 )*
513 for _child in elem.children() {
514 $(
515 if _child.is($child_name, crate::ns::$child_ns) {
516 do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name);
517 continue;
518 }
519 )*
520 return Err(crate::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
521 }
522 Ok($elem {
523 $(
524 $attr: get_attr!(elem, $attr_name, $attr_action),
525 )*
526 $(
527 $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
528 )*
529 $(
530 $text_ident: $codec::decode(&elem.text())?,
531 )*
532 })
533 }
534 }
535
536 impl From<$elem> for ::minidom::Element {
537 fn from(elem: $elem) -> ::minidom::Element {
538 ::minidom::Element::builder($name)
539 .ns(crate::ns::$ns)
540 $(
541 .attr($attr_name, elem.$attr)
542 )*
543 $(
544 .append(generate_serialiser!(elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)))
545 )*
546 $(
547 .append($codec::encode(&elem.$text_ident))
548 )*
549 .build()
550 }
551 }
552 );
553}
554
555#[cfg(test)]
556macro_rules! assert_size (
557 ($t:ty, $sz:expr) => (
558 assert_eq!(::std::mem::size_of::<$t>(), $sz);
559 );
560);