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::error_message::ParentRef;
16use crate::scope::{AsItemsScope, FromEventsScope};
17use crate::types::{
18    as_xml_text_fn, from_xml_text_fn, string_ty, text_codec_decode_fn, text_codec_encode_fn,
19};
20
21use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
22
23/// The field maps to the character data of the element.
24pub(super) struct TextField {
25    /// Optional codec to use
26    pub(super) codec: Option<Expr>,
27}
28
29impl Field for TextField {
30    fn make_builder_part(
31        &self,
32        scope: &FromEventsScope,
33        _container_name: &ParentRef,
34        member: &Member,
35        ty: &Type,
36    ) -> Result<FieldBuilderPart> {
37        let FromEventsScope { ref text, .. } = scope;
38        let field_access = scope.access_field(member);
39        let finalize = match self.codec {
40            Some(ref codec) => {
41                let span = codec.span();
42                let decode = text_codec_decode_fn(ty.clone(), span);
43                quote_spanned! { span=>
44                    #decode(&#codec, #field_access)?
45                }
46            }
47            None => {
48                let from_xml_text = from_xml_text_fn(ty.clone());
49                quote! { #from_xml_text(#field_access)? }
50            }
51        };
52
53        Ok(FieldBuilderPart::Text {
54            value: FieldTempInit {
55                init: quote! { ::xso::exports::alloc::string::String::new() },
56                ty: string_ty(Span::call_site()),
57            },
58            collect: quote! {
59                #field_access.push_str(#text.as_str());
60            },
61            finalize,
62        })
63    }
64
65    fn make_iterator_part(
66        &self,
67        _scope: &AsItemsScope,
68        _container_name: &ParentRef,
69        bound_name: &Ident,
70        _member: &Member,
71        ty: &Type,
72    ) -> Result<FieldIteratorPart> {
73        let generator = match self.codec {
74            Some(ref codec) => {
75                let span = codec.span();
76                let encode = text_codec_encode_fn(ty.clone(), span);
77                // NOTE: We need to fudge the span of `bound_name` here,
78                // because its span points outside the macro (the identifier
79                // of the field), which means that quote_spanned will not
80                // override it, which would make the error message ugly.
81                let mut bound_name = bound_name.clone();
82                bound_name.set_span(span);
83                quote_spanned! { span=> #encode(&#codec, #bound_name)? }
84            }
85            None => {
86                let as_xml_text = as_xml_text_fn(ty.clone());
87                quote! { ::core::option::Option::Some(#as_xml_text(#bound_name)?) }
88            }
89        };
90
91        Ok(FieldIteratorPart::Text { generator })
92    }
93
94    fn captures_text(&self) -> bool {
95        true
96    }
97}