Skip to main content

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