xso: re-implement #[xml(lang)] on top of #[xml(attribute)]
This gives us all the goodies of `default`, `type_` and `codec` without having to duplicate lots of code (and I think the `match`-iness of the new macro code is still within limits). However, we still keep them as separate `#[xml(..)]` attributes, because their semantics are very different and it is sensible to make them stand out. skip-changelog, because `#[xml(lang)]` was introduced in this version.
This commit is contained in:
parent
341d49fecf
commit
67f3ffaced
6 changed files with 198 additions and 138 deletions
|
|
@ -8,11 +8,14 @@
|
|||
//!
|
||||
//! In particular, it provides the `#[xml(attribute)]` implementation.
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::error_message::{self, ParentRef};
|
||||
use crate::meta::{Flag, NameRef, NamespaceRef, QNameRef};
|
||||
use crate::meta::{Flag, NameRef, NamespaceRef, QNameRef, XMLNS_XML};
|
||||
use crate::scope::{AsItemsScope, FromEventsScope};
|
||||
use crate::types::{
|
||||
as_optional_xml_text_fn, default_fn, from_xml_text_fn, text_codec_decode_fn,
|
||||
|
|
@ -21,13 +24,51 @@ use crate::types::{
|
|||
|
||||
use super::{Field, FieldBuilderPart, FieldIteratorPart, FieldTempInit};
|
||||
|
||||
/// Subtype for attribute-matching fields.
|
||||
pub(super) enum AttributeFieldKind {
|
||||
/// Matches any attribute
|
||||
Generic {
|
||||
/// The optional XML namespace of the attribute.
|
||||
xml_namespace: Option<NamespaceRef>,
|
||||
|
||||
/// The XML name of the attribute.
|
||||
xml_name: NameRef,
|
||||
},
|
||||
|
||||
/// Matches `xml:lang`
|
||||
XmlLang,
|
||||
}
|
||||
|
||||
impl AttributeFieldKind {
|
||||
fn matcher(&self) -> (Cow<'_, Option<NamespaceRef>>, Cow<'_, NameRef>) {
|
||||
match self {
|
||||
Self::Generic {
|
||||
ref xml_namespace,
|
||||
ref xml_name,
|
||||
} => (Cow::Borrowed(xml_namespace), Cow::Borrowed(xml_name)),
|
||||
Self::XmlLang => (
|
||||
Cow::Owned(Some(NamespaceRef::fudge(XMLNS_XML, Span::call_site()))),
|
||||
Cow::Owned(NameRef::fudge(
|
||||
rxml_validation::NcName::try_from("lang").unwrap(),
|
||||
Span::call_site(),
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn qname_ref(&self) -> QNameRef {
|
||||
let (namespace, name) = self.matcher();
|
||||
QNameRef {
|
||||
namespace: namespace.into_owned(),
|
||||
name: Some(name.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The field maps to an attribute.
|
||||
pub(super) struct AttributeField {
|
||||
/// The optional XML namespace of the attribute.
|
||||
pub(super) xml_namespace: Option<NamespaceRef>,
|
||||
|
||||
/// The XML name of the attribute.
|
||||
pub(super) xml_name: NameRef,
|
||||
/// Subtype
|
||||
pub(super) kind: AttributeFieldKind,
|
||||
|
||||
/// Flag indicating whether the value should be defaulted if the
|
||||
/// attribute is absent.
|
||||
|
|
@ -47,16 +88,29 @@ impl Field for AttributeField {
|
|||
) -> Result<FieldBuilderPart> {
|
||||
let FromEventsScope { ref attrs, .. } = scope;
|
||||
let ty = ty.clone();
|
||||
let xml_namespace = &self.xml_namespace;
|
||||
let xml_name = &self.xml_name;
|
||||
|
||||
let missing_msg = error_message::on_missing_attribute(container_name, member);
|
||||
let fetch = match self.kind {
|
||||
AttributeFieldKind::Generic {
|
||||
ref xml_namespace,
|
||||
ref xml_name,
|
||||
} => {
|
||||
let xml_namespace = match xml_namespace {
|
||||
Some(v) => v.to_token_stream(),
|
||||
None => quote! {
|
||||
::xso::exports::rxml::Namespace::none()
|
||||
},
|
||||
};
|
||||
|
||||
let xml_namespace = match xml_namespace {
|
||||
Some(v) => v.to_token_stream(),
|
||||
None => quote! {
|
||||
::xso::exports::rxml::Namespace::none()
|
||||
},
|
||||
quote! {
|
||||
#attrs.remove(#xml_namespace, #xml_name)
|
||||
}
|
||||
}
|
||||
|
||||
AttributeFieldKind::XmlLang => {
|
||||
quote! {
|
||||
ctx.language().map(::xso::exports::alloc::borrow::ToOwned::to_owned)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let finalize = match self.codec {
|
||||
|
|
@ -72,6 +126,7 @@ impl Field for AttributeField {
|
|||
}
|
||||
};
|
||||
|
||||
let missing_msg = error_message::on_missing_attribute(container_name, member);
|
||||
let on_absent = match self.default_ {
|
||||
Flag::Absent => quote! {
|
||||
return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into())
|
||||
|
|
@ -87,7 +142,7 @@ impl Field for AttributeField {
|
|||
Ok(FieldBuilderPart::Init {
|
||||
value: FieldTempInit {
|
||||
init: quote! {
|
||||
match #attrs.remove(#xml_namespace, #xml_name).map(#finalize).transpose()? {
|
||||
match #fetch.map(#finalize).transpose()? {
|
||||
::core::option::Option::Some(v) => v,
|
||||
::core::option::Option::None => #on_absent,
|
||||
}
|
||||
|
|
@ -105,13 +160,13 @@ impl Field for AttributeField {
|
|||
_member: &Member,
|
||||
ty: &Type,
|
||||
) -> Result<FieldIteratorPart> {
|
||||
let xml_namespace = match self.xml_namespace {
|
||||
let (xml_namespace, xml_name) = self.kind.matcher();
|
||||
let xml_namespace = match xml_namespace.as_ref() {
|
||||
Some(ref v) => quote! { ::xso::exports::rxml::Namespace::from(#v) },
|
||||
None => quote! {
|
||||
::xso::exports::rxml::Namespace::NONE
|
||||
},
|
||||
};
|
||||
let xml_name = &self.xml_name;
|
||||
|
||||
let generator = match self.codec {
|
||||
Some(ref codec) => {
|
||||
|
|
@ -136,9 +191,6 @@ impl Field for AttributeField {
|
|||
}
|
||||
|
||||
fn captures_attribute(&self) -> Option<QNameRef> {
|
||||
Some(QNameRef {
|
||||
namespace: self.xml_namespace.clone(),
|
||||
name: Some(self.xml_name.clone()),
|
||||
})
|
||||
Some(self.kind.qname_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue