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}