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