Skip to main content

xso_proc/field/
text.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 text content.
8//!
9//! In particular, it provides the `#[xml(text)]` implementation.
10
11use proc_macro2::Span;
12use quote::{quote, quote_spanned};
13use syn::{spanned::Spanned, *};
14
15use crate::common::GenericsInfo;
16use crate::error_message::ParentRef;
17use crate::scope::{AsItemsScope, FromEventsScope};
18use crate::types::{
19    as_xml_text_fn, as_xml_text_trait, from_xml_text_fn, from_xml_text_trait, phantom_fn_ret_ty,
20    string_ty, text_codec_decode_fn, text_codec_encode_fn,
21};
22
23use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
24
25/// The field maps to the character data of the element.
26pub(super) struct TextField {
27    /// Optional codec to use
28    pub(super) codec: Option<Expr>,
29}
30
31impl Field for TextField {
32    fn make_builder_part(
33        &self,
34        scope: &FromEventsScope,
35        generics: &mut GenericsInfo,
36        _container_name: &ParentRef,
37        member: &Member,
38        ty: &Type,
39    ) -> Result<FieldBuilderPart> {
40        let FromEventsScope { ref text, .. } = scope;
41        let field_access = scope.access_field(member);
42        let finalize = match self.codec {
43            Some(ref codec) => {
44                let span = codec.span();
45                let decode = text_codec_decode_fn(ty.clone(), span);
46                quote_spanned! { span=>
47                    #decode(&#codec, #field_access.0)?
48                }
49            }
50            None => {
51                generics.add_bound_if_generic(ty, from_xml_text_trait(Span::call_site()));
52                let from_xml_text = from_xml_text_fn(ty.clone());
53                quote! { #from_xml_text(#field_access.0)? }
54            }
55        };
56
57        // We need a PhantomData here in case that `ty` is generic. Since we
58        // cannot really and reliably know *whether* `ty` is generic, we play
59        // it safe here.
60        // NOTE: this may be removable once we automatically provide trait
61        // bounds, because for that we may also need to know what's generic and
62        // what is not.
63        let ty = Type::Tuple(TypeTuple {
64            paren_token: token::Paren::default(),
65            elems: [string_ty(Span::call_site()), phantom_fn_ret_ty(ty.clone())]
66                .into_iter()
67                .collect(),
68        });
69
70        Ok(FieldBuilderPart::Text {
71            value: FieldTempInit {
72                init: quote! { (::xso::exports::alloc::string::String::new(), core::marker::PhantomData) },
73                ty,
74            },
75            collect: quote! {
76                #field_access.0.push_str(#text.as_str());
77            },
78            finalize,
79        })
80    }
81
82    fn make_iterator_part(
83        &self,
84        _scope: &AsItemsScope,
85        generics: &mut GenericsInfo,
86        _container_name: &ParentRef,
87        bound_name: &Ident,
88        _member: &Member,
89        ty: &Type,
90    ) -> Result<FieldIteratorPart> {
91        let generator = match self.codec {
92            Some(ref codec) => {
93                let span = codec.span();
94                let encode = text_codec_encode_fn(ty.clone(), span);
95                // NOTE: We need to fudge the span of `bound_name` here,
96                // because its span points outside the macro (the identifier
97                // of the field), which means that quote_spanned will not
98                // override it, which would make the error message ugly.
99                let mut bound_name = bound_name.clone();
100                bound_name.set_span(span);
101                quote_spanned! { span=> #encode(&#codec, #bound_name)? }
102            }
103            None => {
104                generics.add_bound_if_generic(ty, as_xml_text_trait(Span::call_site()));
105                let as_xml_text = as_xml_text_fn(ty.clone());
106                quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) }
107            }
108        };
109
110        Ok(FieldIteratorPart::Text { generator })
111    }
112
113    fn captures_text(&self) -> bool {
114        true
115    }
116}