xso_proc/field/
attribute.rs1use proc_macro2::Span;
12use quote::{quote, quote_spanned, ToTokens};
13use syn::{spanned::Spanned, *};
14
15use std::borrow::Cow;
16
17use crate::common::GenericsInfo;
18use crate::error_message::{self, ParentRef};
19use crate::meta::{Flag, NameRef, NamespaceRef, QNameRef, XMLNS_XML};
20use crate::scope::{AsItemsScope, FromEventsScope};
21use crate::types::{
22 as_optional_xml_text_fn, as_optional_xml_text_trait, default_fn, from_xml_text_fn,
23 from_xml_text_trait, text_codec_decode_fn, text_codec_encode_fn,
24};
25
26use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
27
28pub(super) enum AttributeFieldKind {
30 Generic {
32 xml_namespace: Option<NamespaceRef>,
34
35 xml_name: NameRef,
37 },
38
39 XmlLang,
41}
42
43impl AttributeFieldKind {
44 fn matcher(&self) -> (Cow<'_, Option<NamespaceRef>>, Cow<'_, NameRef>) {
45 match self {
46 Self::Generic {
47 ref xml_namespace,
48 ref xml_name,
49 } => (Cow::Borrowed(xml_namespace), Cow::Borrowed(xml_name)),
50 Self::XmlLang => (
51 Cow::Owned(Some(NamespaceRef::fudge(XMLNS_XML, Span::call_site()))),
52 Cow::Owned(NameRef::fudge(
53 rxml_validation::NcName::try_from("lang").unwrap(),
54 Span::call_site(),
55 )),
56 ),
57 }
58 }
59
60 fn qname_ref(&self) -> QNameRef {
61 let (namespace, name) = self.matcher();
62 QNameRef {
63 namespace: namespace.into_owned(),
64 name: Some(name.into_owned()),
65 }
66 }
67}
68
69pub(super) struct AttributeField {
71 pub(super) kind: AttributeFieldKind,
73
74 pub(super) default_: Flag,
77
78 pub(super) codec: Option<Expr>,
80}
81
82impl Field for AttributeField {
83 fn make_builder_part(
84 &self,
85 scope: &FromEventsScope,
86 generics: &mut GenericsInfo,
87 container_name: &ParentRef,
88 member: &Member,
89 ty: &Type,
90 ) -> Result<FieldBuilderPart> {
91 let FromEventsScope { ref attrs, .. } = scope;
92 let ty = ty.clone();
93
94 let fetch = match self.kind {
95 AttributeFieldKind::Generic {
96 ref xml_namespace,
97 ref xml_name,
98 } => {
99 let xml_namespace = match xml_namespace {
100 Some(v) => v.to_token_stream(),
101 None => quote! {
102 ::xso::exports::rxml::Namespace::none()
103 },
104 };
105
106 quote! {
107 #attrs.remove(#xml_namespace, #xml_name)
108 }
109 }
110
111 AttributeFieldKind::XmlLang => {
112 quote! {
113 ctx.language().map(::xso::exports::alloc::borrow::ToOwned::to_owned)
114 }
115 }
116 };
117
118 let finalize = match self.codec {
119 Some(ref codec) => {
120 let span = codec.span();
121 let decode = text_codec_decode_fn(ty.clone(), span);
122 quote_spanned! { span=>
123 |value| #decode(&#codec, value)
124 }
125 }
126 None => {
127 generics.add_bound_if_generic(&ty, from_xml_text_trait(Span::call_site()));
128 let from_xml_text = from_xml_text_fn(ty.clone());
129 quote! { #from_xml_text }
130 }
131 };
132
133 let missing_msg = error_message::on_missing_attribute(container_name, member);
134 let on_absent = match self.default_ {
135 Flag::Absent => quote! {
136 return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
137 },
138 Flag::Present(_) => {
139 let default_ = default_fn(ty.clone());
140 quote! {
141 #default_()
142 }
143 }
144 };
145
146 Ok(FieldBuilderPart::Init {
147 value: FieldTempInit {
148 init: quote! {
149 match #fetch.map(#finalize).transpose()? {
150 ::core::option::Option::Some(v) => v,
151 ::core::option::Option::None => #on_absent,
152 }
153 },
154 ty: ty.clone(),
155 },
156 })
157 }
158
159 fn make_iterator_part(
160 &self,
161 _scope: &AsItemsScope,
162 generics: &mut GenericsInfo,
163 _container_name: &ParentRef,
164 bound_name: &Ident,
165 _member: &Member,
166 ty: &Type,
167 ) -> Result<FieldIteratorPart> {
168 let (xml_namespace, xml_name) = self.kind.matcher();
169 let xml_namespace = match xml_namespace.as_ref() {
170 Some(ref v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
171 None => quote! {
172 ::xso::exports::rxml::Namespace::NONE
173 },
174 };
175
176 let generator = match self.codec {
177 Some(ref codec) => {
178 let span = codec.span();
179 let encode = text_codec_encode_fn(ty.clone(), span);
180 let mut bound_name = bound_name.clone();
185 bound_name.set_span(span);
186 quote_spanned! { span=> #encode(&#codec, #bound_name)? }
187 }
188 None => {
189 generics.add_bound_if_generic(ty, as_optional_xml_text_trait(Span::call_site()));
190 let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
191 quote! { #as_optional_xml_text(#bound_name)? }
192 }
193 };
194
195 Ok(FieldIteratorPart::Header {
196 generator: quote! {
197 #generator.map(|#bound_name| ::xso::Item::Attribute(
198 #xml_namespace,
199 ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
200 #bound_name,
201 ));
202 },
203 })
204 }
205
206 fn captures_attribute(&self) -> Option<QNameRef> {
207 Some(self.kind.qname_ref())
208 }
209}