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