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(
173 match elem {
174 $($elem::$enum => $enum_name,)+
175 }
176 )
177 .ns(crate::ns::$ns)
178 .build()
179 }
180 }
181 );
182}
183
184macro_rules! generate_attribute_enum {
185 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+,}) => (
186 generate_attribute_enum!($(#[$meta])* $elem, $name, $ns, $attr, {$($(#[$enum_meta])* $enum => $enum_name),+});
187 );
188 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, $attr:tt, {$($(#[$enum_meta:meta])* $enum:ident => $enum_name:tt),+}) => (
189 $(#[$meta])*
190 #[derive(Debug, Clone, PartialEq)]
191 pub enum $elem {
192 $(
193 $(#[$enum_meta])*
194 $enum
195 ),+
196 }
197 impl ::try_from::TryFrom<::minidom::Element> for $elem {
198 type Err = crate::error::Error;
199 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
200 check_ns_only!(elem, $name, $ns);
201 check_no_children!(elem, $name);
202 check_no_unknown_attributes!(elem, $name, [$attr]);
203 Ok(match get_attr!(elem, $attr, required) {
204 $($enum_name => $elem::$enum,)+
205 _ => return Err(crate::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
206 })
207 }
208 }
209 impl From<$elem> for ::minidom::Element {
210 fn from(elem: $elem) -> ::minidom::Element {
211 ::minidom::Element::builder($name)
212 .ns(crate::ns::$ns)
213 .attr($attr, match elem {
214 $($elem::$enum => $enum_name,)+
215 })
216 .build()
217 }
218 }
219 );
220}
221
222macro_rules! check_self {
223 ($elem:ident, $name:tt, $ns:ident) => {
224 check_self!($elem, $name, $ns, $name);
225 };
226 ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
227 if !$elem.is($name, crate::ns::$ns) {
228 return Err(crate::error::Error::ParseError(concat!(
229 "This is not a ",
230 $pretty_name,
231 " element."
232 )));
233 }
234 };
235}
236
237macro_rules! check_ns_only {
238 ($elem:ident, $name:tt, $ns:ident) => {
239 if !$elem.has_ns(crate::ns::$ns) {
240 return Err(crate::error::Error::ParseError(concat!(
241 "This is not a ",
242 $name,
243 " element."
244 )));
245 }
246 };
247}
248
249macro_rules! check_no_children {
250 ($elem:ident, $name:tt) => {
251 for _ in $elem.children() {
252 return Err(crate::error::Error::ParseError(concat!(
253 "Unknown child in ",
254 $name,
255 " element."
256 )));
257 }
258 };
259}
260
261macro_rules! check_no_attributes {
262 ($elem:ident, $name:tt) => {
263 for _ in $elem.attrs() {
264 return Err(crate::error::Error::ParseError(concat!(
265 "Unknown attribute in ",
266 $name,
267 " element."
268 )));
269 }
270 };
271}
272
273macro_rules! check_no_unknown_attributes {
274 ($elem:ident, $name:tt, [$($attr:tt),*]) => (
275 for (_attr, _) in $elem.attrs() {
276 $(
277 if _attr == $attr {
278 continue;
279 }
280 )*
281 return Err(crate::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
282 }
283 );
284}
285
286macro_rules! generate_empty_element {
287 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
288 $(#[$meta])*
289 #[derive(Debug, Clone)]
290 pub struct $elem;
291
292 impl ::try_from::TryFrom<::minidom::Element> for $elem {
293 type Err = crate::error::Error;
294
295 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
296 check_self!(elem, $name, $ns);
297 check_no_children!(elem, $name);
298 check_no_attributes!(elem, $name);
299 Ok($elem)
300 }
301 }
302
303 impl From<$elem> for ::minidom::Element {
304 fn from(_: $elem) -> ::minidom::Element {
305 ::minidom::Element::builder($name)
306 .ns(crate::ns::$ns)
307 .build()
308 }
309 }
310 );
311}
312
313macro_rules! generate_id {
314 ($(#[$meta:meta])* $elem:ident) => (
315 $(#[$meta])*
316 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
317 pub struct $elem(pub String);
318 impl ::std::str::FromStr for $elem {
319 type Err = crate::error::Error;
320 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
321 // TODO: add a way to parse that differently when needed.
322 Ok($elem(String::from(s)))
323 }
324 }
325 impl ::minidom::IntoAttributeValue for $elem {
326 fn into_attribute_value(self) -> Option<String> {
327 Some(self.0)
328 }
329 }
330 );
331}
332
333macro_rules! generate_elem_id {
334 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
335 $(#[$meta])*
336 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
337 pub struct $elem(pub String);
338 impl ::std::str::FromStr for $elem {
339 type Err = crate::error::Error;
340 fn from_str(s: &str) -> Result<$elem, crate::error::Error> {
341 // TODO: add a way to parse that differently when needed.
342 Ok($elem(String::from(s)))
343 }
344 }
345 impl ::try_from::TryFrom<::minidom::Element> for $elem {
346 type Err = crate::error::Error;
347 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
348 check_self!(elem, $name, $ns);
349 check_no_children!(elem, $name);
350 check_no_attributes!(elem, $name);
351 // TODO: add a way to parse that differently when needed.
352 Ok($elem(elem.text()))
353 }
354 }
355 impl From<$elem> for ::minidom::Element {
356 fn from(elem: $elem) -> ::minidom::Element {
357 ::minidom::Element::builder($name)
358 .ns(crate::ns::$ns)
359 .append(elem.0)
360 .build()
361 }
362 }
363 );
364}
365
366macro_rules! start_decl {
367 (Vec, $type:ty) => (
368 Vec<$type>
369 );
370 (Option, $type:ty) => (
371 Option<$type>
372 );
373 (Required, $type:ty) => (
374 $type
375 );
376}
377
378macro_rules! start_parse_elem {
379 ($temp:ident: Vec) => {
380 let mut $temp = Vec::new();
381 };
382 ($temp:ident: Option) => {
383 let mut $temp = None;
384 };
385 ($temp:ident: Required) => {
386 let mut $temp = None;
387 };
388}
389
390macro_rules! do_parse {
391 ($elem:ident, Element) => {
392 $elem.clone()
393 };
394 ($elem:ident, String) => {
395 $elem.text()
396 };
397 ($elem:ident, $constructor:ident) => {
398 $constructor::try_from($elem.clone())?
399 };
400}
401
402macro_rules! do_parse_elem {
403 ($temp:ident: Vec = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
404 $temp.push(do_parse!($elem, $constructor));
405 };
406 ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
407 if $temp.is_some() {
408 return Err(crate::error::Error::ParseError(concat!(
409 "Element ",
410 $parent_name,
411 " must not have more than one ",
412 $name,
413 " child."
414 )));
415 }
416 $temp = Some(do_parse!($elem, $constructor));
417 };
418 ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
419 if $temp.is_some() {
420 return Err(crate::error::Error::ParseError(concat!(
421 "Element ",
422 $parent_name,
423 " must not have more than one ",
424 $name,
425 " child."
426 )));
427 }
428 $temp = Some(do_parse!($elem, $constructor));
429 };
430}
431
432macro_rules! finish_parse_elem {
433 ($temp:ident: Vec = $name:tt, $parent_name:tt) => {
434 $temp
435 };
436 ($temp:ident: Option = $name:tt, $parent_name:tt) => {
437 $temp
438 };
439 ($temp:ident: Required = $name:tt, $parent_name:tt) => {
440 $temp.ok_or(crate::error::Error::ParseError(concat!(
441 "Missing child ",
442 $name,
443 " in ",
444 $parent_name,
445 " element."
446 )))?
447 };
448}
449
450macro_rules! generate_serialiser {
451 ($parent:ident, $elem:ident, Required, String, ($name:tt, $ns:ident)) => {
452 ::minidom::Element::builder($name)
453 .ns(crate::ns::$ns)
454 .append($parent.$elem)
455 .build()
456 };
457 ($parent:ident, $elem:ident, Option, String, ($name:tt, $ns:ident)) => {
458 $parent.$elem.map(|elem| {
459 ::minidom::Element::builder($name)
460 .ns(crate::ns::$ns)
461 .append(elem)
462 .build()
463 })
464 };
465 ($parent:ident, $elem:ident, $_:ident, $constructor:ident, ($name:tt, $ns:ident)) => {
466 $parent.$elem
467 };
468}
469
470macro_rules! generate_element {
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, attributes: [$($(#[$attr_meta:meta])* $attr:ident: $attr_type:ty = $attr_name:tt => $attr_action:tt),+]) => (
475 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [$($(#[$attr_meta])* $attr: $attr_type = $attr_name => $attr_action),*], children: []);
476 );
477 ($(#[$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),*]) => (
478 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], children: [$($(#[$child_meta])* $child_ident: $coucou<$child_type> = ($child_name, $child_ns) => $child_constructor),*]);
479 );
480 ($(#[$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),*]) => (
481 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),*]);
482 );
483 ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident, text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
484 generate_element!($(#[$meta])* $elem, $name, $ns, attributes: [], 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),+], text: ($(#[$text_meta:meta])* $text_ident:ident: $codec:ident < $text_type:ty >)) => (
487 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>));
488 );
489 ($(#[$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 >))*) => (
490 $(#[$meta])*
491 #[derive(Debug, Clone)]
492 pub struct $elem {
493 $(
494 $(#[$attr_meta])*
495 pub $attr: $attr_type,
496 )*
497 $(
498 $(#[$child_meta])*
499 pub $child_ident: start_decl!($coucou, $child_type),
500 )*
501 $(
502 $(#[$text_meta])*
503 pub $text_ident: $text_type,
504 )*
505 }
506
507 impl ::try_from::TryFrom<::minidom::Element> for $elem {
508 type Err = crate::error::Error;
509
510 fn try_from(elem: ::minidom::Element) -> Result<$elem, crate::error::Error> {
511 check_self!(elem, $name, $ns);
512 check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
513 $(
514 start_parse_elem!($child_ident: $coucou);
515 )*
516 for _child in elem.children() {
517 $(
518 if _child.is($child_name, crate::ns::$child_ns) {
519 do_parse_elem!($child_ident: $coucou = $child_constructor => _child, $child_name, $name);
520 continue;
521 }
522 )*
523 return Err(crate::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
524 }
525 Ok($elem {
526 $(
527 $attr: get_attr!(elem, $attr_name, $attr_action),
528 )*
529 $(
530 $child_ident: finish_parse_elem!($child_ident: $coucou = $child_name, $name),
531 )*
532 $(
533 $text_ident: $codec::decode(&elem.text())?,
534 )*
535 })
536 }
537 }
538
539 impl From<$elem> for ::minidom::Element {
540 fn from(elem: $elem) -> ::minidom::Element {
541 ::minidom::Element::builder($name)
542 .ns(crate::ns::$ns)
543 $(
544 .attr($attr_name, elem.$attr)
545 )*
546 $(
547 .append(generate_serialiser!(elem, $child_ident, $coucou, $child_constructor, ($child_name, $child_ns)))
548 )*
549 $(
550 .append($codec::encode(&elem.$text_ident))
551 )*
552 .build()
553 }
554 }
555 );
556}
557
558#[cfg(test)]
559macro_rules! assert_size (
560 ($t:ty, $sz:expr) => (
561 assert_eq!(::std::mem::size_of::<$t>(), $sz);
562 );
563);