xso_proc/field/
flag.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
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
7//! This module concerns the processing of flag-style children.
8//!
9//! In particular, it provides the `#[xml(flag)]` implementation.
10
11use proc_macro2::{Span, TokenStream};
12use quote::quote;
13use syn::*;
14
15use crate::error_message::{FieldName, ParentRef};
16use crate::meta::{NameRef, NamespaceRef};
17use crate::scope::{AsItemsScope, FromEventsScope};
18use crate::types::{bool_ty, empty_builder_ty, u8_ty};
19
20use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
21
22/// The field maps to a child element, the presence of which is represented as boolean.
23pub(super) struct FlagField {
24    /// The XML namespace of the child element.
25    pub(super) xml_namespace: NamespaceRef,
26
27    /// The XML name of the child element.
28    pub(super) xml_name: NameRef,
29}
30
31impl Field for FlagField {
32    fn make_builder_part(
33        &self,
34        scope: &FromEventsScope,
35        container_name: &ParentRef,
36        member: &Member,
37        _ty: &Type,
38    ) -> Result<FieldBuilderPart> {
39        let field_access = scope.access_field(member);
40
41        let unknown_attr_err = format!(
42            "Unknown attribute in flag child {} in {}.",
43            FieldName(&member),
44            container_name
45        );
46        let unknown_child_err = format!(
47            "Unknown child in flag child {} in {}.",
48            FieldName(&member),
49            container_name
50        );
51        let unknown_text_err = format!(
52            "Unexpected text in flag child {} in {}.",
53            FieldName(&member),
54            container_name
55        );
56
57        let xml_namespace = &self.xml_namespace;
58        let xml_name = &self.xml_name;
59
60        Ok(FieldBuilderPart::Nested {
61            extra_defs: TokenStream::new(),
62            value: FieldTempInit {
63                ty: bool_ty(Span::call_site()),
64                init: quote! { false },
65            },
66            matcher: NestedMatcher::Selective(quote! {
67                if name.0 == #xml_namespace && name.1 == #xml_name {
68                    ::xso::fromxml::Empty {
69                        attributeerr: #unknown_attr_err,
70                        childerr: #unknown_child_err,
71                        texterr: #unknown_text_err,
72                    }.start(attrs).map_err(
73                        ::xso::error::FromEventsError::Invalid
74                    )
75                } else {
76                    ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
77                        name,
78                        attrs,
79                    })
80                }
81            }),
82            builder: empty_builder_ty(Span::call_site()),
83            collect: quote! {
84                #field_access = true;
85            },
86            finalize: quote! {
87                #field_access
88            },
89        })
90    }
91
92    fn make_iterator_part(
93        &self,
94        _scope: &AsItemsScope,
95        _container_name: &ParentRef,
96        bound_name: &Ident,
97        _member: &Member,
98        _ty: &Type,
99    ) -> Result<FieldIteratorPart> {
100        let xml_namespace = &self.xml_namespace;
101        let xml_name = &self.xml_name;
102
103        Ok(FieldIteratorPart::Content {
104            extra_defs: TokenStream::new(),
105            value: FieldTempInit {
106                init: quote! {
107                    if *#bound_name {
108                        3
109                    } else {
110                        1
111                    }
112                },
113                ty: u8_ty(Span::call_site()),
114            },
115            generator: quote! {
116                {
117                    // using wrapping_sub will make the match below crash
118                    // with unreachable!() in case we messed up somewhere.
119                    #bound_name = #bound_name.wrapping_sub(1);
120                    match #bound_name {
121                        0 => ::core::result::Result::<_, ::xso::error::Error>::Ok(::core::option::Option::None),
122                        1 => ::core::result::Result::Ok(::core::option::Option::Some(
123                            ::xso::Item::ElementFoot
124                        )),
125                        2 => ::core::result::Result::Ok(::core::option::Option::Some(
126                            ::xso::Item::ElementHeadStart(
127                                ::xso::exports::rxml::Namespace::from(#xml_namespace),
128                                ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
129                            )
130                        )),
131                        _ => unreachable!(),
132                    }
133                }
134            },
135        })
136    }
137}