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::ParentRef;
17use crate::scope::{AsItemsScope, FromEventsScope};
18use crate::types::{
19 default_fn, element_ty, from_xml_builder_ty, into_iterator_into_iter_fn, into_iterator_iter_ty,
20 item_iter_ty, option_ty, ref_ty,
21};
22
23use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit, NestedMatcher};
24
25pub(super) struct ElementField;
26
27impl Field for ElementField {
28 fn make_builder_part(
29 &self,
30 scope: &FromEventsScope,
31 _container_name: &ParentRef,
32 member: &Member,
33 ty: &Type,
34 ) -> Result<FieldBuilderPart> {
35 let FromEventsScope {
36 ref substate_result,
37 ..
38 } = scope;
39 let field_access = scope.access_field(member);
40
41 let element_ty = element_ty(Span::call_site());
42 let default_fn = default_fn(ty.clone());
43 let builder = from_xml_builder_ty(element_ty.clone());
44
45 Ok(FieldBuilderPart::Nested {
46 extra_defs: TokenStream::default(),
47 value: FieldTempInit {
48 init: quote! { #default_fn() },
49 ty: ty.clone(),
50 },
51 matcher: NestedMatcher::Fallback(quote! {
52 #builder::new(name, attrs)
53 }),
54 builder,
55 collect: quote! {
56 <#ty as ::core::iter::Extend::<#element_ty>>::extend(&mut #field_access, [#substate_result]);
57 },
58 finalize: quote! {
59 #field_access
60 },
61 })
62 }
63
64 fn make_iterator_part(
65 &self,
66 scope: &AsItemsScope,
67 _container_name: &ParentRef,
68 bound_name: &Ident,
69 _member: &Member,
70 ty: &Type,
71 ) -> Result<FieldIteratorPart> {
72 let AsItemsScope { ref lifetime, .. } = scope;
73
74 let element_ty = element_ty(Span::call_site());
75 let iter_ty = item_iter_ty(element_ty.clone(), lifetime.clone());
76 let element_iter = into_iterator_iter_ty(ref_ty(ty.clone(), lifetime.clone()));
77 let into_iter = into_iterator_into_iter_fn(ref_ty(ty.clone(), lifetime.clone()));
78
79 let state_ty = Type::Tuple(TypeTuple {
80 paren_token: token::Paren::default(),
81 elems: [element_iter, option_ty(iter_ty)].into_iter().collect(),
82 });
83
84 Ok(FieldIteratorPart::Content {
85 extra_defs: TokenStream::default(),
86 value: FieldTempInit {
87 init: quote! {
88 (#into_iter(#bound_name), ::core::option::Option::None)
89 },
90 ty: state_ty,
91 },
92 generator: quote! {
93 loop {
94 if let ::core::option::Option::Some(current) = #bound_name.1.as_mut() {
95 if let ::core::option::Option::Some(item) = current.next() {
96 break ::core::option::Option::Some(item).transpose();
97 }
98 }
99 if let ::core::option::Option::Some(item) = #bound_name.0.next() {
100 #bound_name.1 = ::core::option::Option::Some(
101 <#element_ty as ::xso::AsXml>::as_xml_iter(item)?
102 );
103 } else {
104 break ::core::result::Result::Ok(::core::option::Option::None)
105 }
106 }
107 },
108 })
109 }
110}