xso-proc: Add support for the codec field on attribute meta

This allows a custom TextCodec to be used for encoding and decoding the
attribute’s value, instead of FromXmlText and AsOptionalXmlText.
This commit is contained in:
Emmanuel Gil Peyrot 2025-01-25 16:21:22 +01:00
commit 01a0c51a2f
6 changed files with 114 additions and 5 deletions

View file

@ -14,7 +14,10 @@ use syn::*;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, NameRef, NamespaceRef};
use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{as_optional_xml_text_fn, default_fn, from_xml_text_fn};
use crate::types::{
as_optional_xml_text_fn, default_fn, from_xml_text_fn, text_codec_decode_fn,
text_codec_encode_fn,
};
use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
@ -29,6 +32,9 @@ pub(super) struct AttributeField {
/// Flag indicating whether the value should be defaulted if the
/// attribute is absent.
pub(super) default_: Flag,
/// Optional codec to use.
pub(super) codec: Option<Expr>,
}
impl Field for AttributeField {
@ -53,7 +59,18 @@ impl Field for AttributeField {
},
};
let from_xml_text = from_xml_text_fn(ty.clone());
let finalize = match self.codec {
Some(ref codec) => {
let decode = text_codec_decode_fn(ty.clone());
quote! {
|value| #decode(&#codec, value)
}
}
None => {
let from_xml_text = from_xml_text_fn(ty.clone());
quote! { #from_xml_text }
}
};
let on_absent = match self.default_ {
Flag::Absent => quote! {
@ -70,7 +87,7 @@ impl Field for AttributeField {
Ok(FieldBuilderPart::Init {
value: FieldTempInit {
init: quote! {
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
match #attrs.remove(#xml_namespace, #xml_name).map(#finalize).transpose()? {
::core::option::Option::Some(v) => v,
::core::option::Option::None => #on_absent,
}
@ -96,11 +113,20 @@ impl Field for AttributeField {
};
let xml_name = &self.xml_name;
let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
let generator = match self.codec {
Some(ref codec) => {
let encode = text_codec_encode_fn(ty.clone());
quote! { #encode(&#codec, #bound_name)? }
}
None => {
let as_optional_xml_text = as_optional_xml_text_fn(ty.clone());
quote! { #as_optional_xml_text(#bound_name)? }
}
};
Ok(FieldIteratorPart::Header {
generator: quote! {
#as_optional_xml_text(#bound_name)?.map(|#bound_name| ::xso::Item::Attribute(
#generator.map(|#bound_name| ::xso::Item::Attribute(
#xml_namespace,
::std::borrow::Cow::Borrowed(#xml_name),
#bound_name,

View file

@ -253,6 +253,7 @@ fn new_field(
qname: QNameRef { namespace, name },
default_,
type_,
codec,
} => {
let xml_name = default_name(span, name, field_ident)?;
@ -270,6 +271,7 @@ fn new_field(
xml_name,
xml_namespace: namespace,
default_,
codec,
}))
}

View file

@ -689,6 +689,9 @@ pub(crate) enum XmlFieldMeta {
/// An explicit type override, only usable within extracts.
type_: Option<Type>,
/// The path to the optional codec type.
codec: Option<Expr>,
},
/// `#[xml(text)]`
@ -787,12 +790,14 @@ impl XmlFieldMeta {
},
default_: Flag::Absent,
type_: None,
codec: None,
})
} else if meta.input.peek(syn::token::Paren) {
// full syntax
let mut qname = QNameRef::default();
let mut default_ = Flag::Absent;
let mut type_ = None;
let mut codec = None;
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
if default_.is_set() {
@ -806,6 +811,23 @@ impl XmlFieldMeta {
}
type_ = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("codec") {
if codec.is_some() {
return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
}
let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
// See the comment at the top of text_from_meta() below for why we
// do this.
let lookahead = meta.input.lookahead1();
if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
if let Some(helpful_error) = helpful_error {
let mut e = lookahead.error();
e.combine(helpful_error);
return Err(e);
}
}
codec = Some(new_codec);
Ok(())
} else {
match qname.parse_incremental_from_meta(meta)? {
None => Ok(()),
@ -818,6 +840,7 @@ impl XmlFieldMeta {
qname,
default_,
type_,
codec,
})
} else {
// argument-less syntax
@ -826,6 +849,7 @@ impl XmlFieldMeta {
qname: QNameRef::default(),
default_: Flag::Absent,
type_: None,
codec: None,
})
}
}