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