Skip to main content

xso_proc/field/
element.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 untyped `minidom::Element`
8//! children.
9//!
10//! In particular, it provides the `#[xml(element)]` implementation.
11
12use proc_macro2::{Span, TokenStream};
13use quote::quote;
14use syn::*;
15
16use crate::common::GenericsInfo;
17use crate::error_message::{self, ParentRef};
18use crate::meta::{AmountConstraint, Flag};
19use crate::scope::{AsItemsScope, FromEventsScope};
20use crate::types::{
21    as_xml_iter_fn, default_fn, element_ty, from_events_fn, from_xml_builder_ty,
22    into_iterator_into_iter_fn, into_iterator_item_ty, into_iterator_iter_ty, item_iter_ty,
23    option_ty, ref_ty,
24};
25
26use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
27
28pub(super) struct ElementField {
29    /// Flag indicating whether the value should be defaulted if the
30    /// child is absent.
31    pub(super) default_: Flag,
32
33    /// Number of child elements allowed.
34    pub(super) amount: AmountConstraint,
35}
36
37impl Field for ElementField {
38    fn make_builder_part(
39        &self,
40        scope: &FromEventsScope,
41        _generics: &mut GenericsInfo,
42        container_name: &ParentRef,
43        member: &Member,
44        ty: &Type,
45    ) -> Result<FieldBuilderPart> {
46        let element_ty = match self.amount {
47            AmountConstraint::FixedSingle(_) => ty.clone(),
48            AmountConstraint::Any(_) => into_iterator_item_ty(ty.clone()),
49        };
50
51        let FromEventsScope {
52            ref substate_result,
53            ..
54        } = scope;
55
56        let from_events = from_events_fn(element_ty.clone());
57
58        let extra_defs = TokenStream::default();
59        let field_access = scope.access_field(member);
60
61        let default_fn = default_fn(ty.clone());
62        let builder = from_xml_builder_ty(element_ty.clone());
63
64        match self.amount {
65            AmountConstraint::FixedSingle(_) => {
66                let missing_msg = error_message::on_missing_child(container_name, member);
67                let on_absent = match self.default_ {
68                    Flag::Absent => quote! {
69                        return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
70                    },
71                    Flag::Present(_) => {
72                        quote! { #default_fn() }
73                    }
74                };
75                Ok(FieldBuilderPart::Nested {
76                    extra_defs,
77                    value: FieldTempInit {
78                        init: quote! { ::core::option::Option::None },
79                        ty: option_ty(ty.clone()),
80                    },
81                    matcher: NestedMatcher::Selective(quote! {
82                        if #field_access.is_some() {
83                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
84                        } else {
85                            #from_events(name, attrs, ctx)
86                        }
87                    }),
88                    builder,
89                    collect: quote! {
90                        #field_access = ::core::option::Option::Some(#substate_result);
91                    },
92                    finalize: quote! {
93                        match #field_access {
94                            ::core::option::Option::Some(value) => value,
95                            ::core::option::Option::None => #on_absent,
96                        }
97                    },
98                })
99            }
100            AmountConstraint::Any(_) => Ok(FieldBuilderPart::Nested {
101                extra_defs,
102                value: FieldTempInit {
103                    init: quote! { #default_fn() },
104                    ty: ty.clone(),
105                },
106                matcher: NestedMatcher::Fallback(quote! {
107                    #builder::new(name, attrs)
108                }),
109                builder,
110                collect: quote! {
111                    <#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]);
112                },
113                finalize: quote! {
114                    #field_access
115                },
116            }),
117        }
118    }
119
120    fn make_iterator_part(
121        &self,
122        scope: &AsItemsScope,
123        _generics: &mut GenericsInfo,
124        _container_name: &ParentRef,
125        bound_name: &Ident,
126        _member: &Member,
127        ty: &Type,
128    ) -> Result<FieldIteratorPart> {
129        let AsItemsScope { ref lifetime, .. } = scope;
130
131        let item_ty = match self.amount {
132            AmountConstraint::FixedSingle(_) => ty.clone(),
133            AmountConstraint::Any(_) => {
134                // This should give us the type of element stored in the
135                // collection.
136                into_iterator_item_ty(ty.clone())
137            }
138        };
139
140        let element_ty = element_ty(Span::call_site());
141        let iter_ty = item_iter_ty(element_ty.clone(), lifetime.clone());
142        let element_iter = into_iterator_iter_ty(ref_ty(ty.clone(), lifetime.clone()));
143        let into_iter = into_iterator_into_iter_fn(ref_ty(ty.clone(), lifetime.clone()));
144
145        let state_ty = Type::Tuple(TypeTuple {
146            paren_token: token::Paren::default(),
147            elems: [element_iter, option_ty(iter_ty.clone())]
148                .into_iter()
149                .collect(),
150        });
151
152        let extra_defs = TokenStream::default();
153        let as_xml_iter = as_xml_iter_fn(item_ty.clone());
154        let init = quote! { #as_xml_iter(#bound_name)? };
155        let iter_ty = item_iter_ty(item_ty.clone(), lifetime.clone());
156
157        match self.amount {
158            AmountConstraint::FixedSingle(_) => Ok(FieldIteratorPart::Content {
159                extra_defs,
160                value: FieldTempInit { init, ty: iter_ty },
161                generator: quote! {
162                    #bound_name.next().transpose()
163                },
164            }),
165            AmountConstraint::Any(_) => Ok(FieldIteratorPart::Content {
166                extra_defs,
167                value: FieldTempInit {
168                    init: quote! {
169                        (#into_iter(#bound_name), ::core::option::Option::None)
170                    },
171                    ty: state_ty,
172                },
173                generator: quote! {
174                    loop {
175                        if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
176                            if let ::core::option::Option::Some(item) = current.next() {
177                                break ::core::option::Option::Some(item).transpose();
178                            }
179                        }
180                        if let ::core::option::Option::Some(item) = #bound_name.0.next() {
181                            #bound_name.1 = ::core::option::Option::Some(
182                                <#element_ty as ::xso::AsXml>::as_xml_iter(item)?
183                            );
184                        } else {
185                            break ::core::result::Result::Ok(::core::option::Option::None)
186                        }
187                    }
188                },
189            }),
190        }
191    }
192}