xso-proc: add support for child elements

This commit is contained in:
Jonas Schäfer 2024-06-29 15:22:52 +02:00
commit 5bd36eccfc
11 changed files with 576 additions and 13 deletions

View file

@ -14,9 +14,10 @@ use rxml_validation::NcName;
use crate::error_message::{self, ParentRef};
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
use crate::scope::FromEventsScope;
use crate::scope::{AsItemsScope, FromEventsScope};
use crate::types::{
as_optional_xml_text_fn, as_xml_text_fn, default_fn, from_xml_text_fn, string_ty,
as_optional_xml_text_fn, as_xml_iter_fn, as_xml_text_fn, default_fn, from_events_fn,
from_xml_builder_ty, from_xml_text_fn, item_iter_ty, option_ty, string_ty,
text_codec_decode_fn, text_codec_encode_fn,
};
@ -58,6 +59,38 @@ pub(crate) enum FieldBuilderPart {
/// temporary value.
finalize: TokenStream,
},
/// Parse a field from child element events.
Nested {
/// Expression and type which initializes a buffer to use during
/// parsing.
value: FieldTempInit,
/// Expression which evaluates to `Result<T, FromEventsError>`,
/// consuming `name: rxml::QName` and `attrs: rxml::AttrMap`.
///
/// `T` must be the type specified in the
/// [`Self::Nested::builder`] field.
matcher: TokenStream,
/// Type implementing `xso::FromEventsBuilder` which parses the child
/// element.
///
/// This type is returned by the expressions in
/// [`matcher`][`Self::Nested::matcher`].
builder: Type,
/// Expression which consumes the value stored in the identifier
/// [`crate::common::FromEventsScope::substate_result`][`FromEventsScope::substate_result`]
/// and somehow collects it into the field declared with
/// [`value`][`Self::Nested::value`].
collect: TokenStream,
/// Expression which consumes the data from the field declared with
/// [`value`][`Self::Nested::value`] and converts it into the field's
/// type.
finalize: TokenStream,
},
}
/// Describe how a struct or enum variant's member is converted to XML data.
@ -80,6 +113,21 @@ pub(crate) enum FieldIteratorPart {
/// String, which is then emitted as text data.
generator: TokenStream,
},
/// The field is emitted as series of items which form a child element.
Content {
/// Expression and type which initializes the nested iterator.
///
/// Note that this is evaluated at construction time of the iterator.
/// Fields of this variant do not get access to their original data,
/// unless they carry it in the contents of this `value`.
value: FieldTempInit,
/// An expression which uses the value (mutably) and evaluates to
/// a Result<Option<Item>, Error>. Once the state returns None, the
/// processing will advance to the next state.
generator: TokenStream,
},
}
/// Specify how the field is mapped to XML.
@ -102,6 +150,9 @@ enum FieldKind {
/// Optional codec to use
codec: Option<Type>,
},
/// The field maps to a child
Child,
}
impl FieldKind {
@ -147,6 +198,8 @@ impl FieldKind {
}
XmlFieldMeta::Text { codec } => Ok(Self::Text { codec }),
XmlFieldMeta::Child => Ok(Self::Child),
}
}
}
@ -287,6 +340,39 @@ impl FieldDef {
finalize,
})
}
FieldKind::Child => {
let FromEventsScope {
ref substate_result,
..
} = scope;
let field_access = scope.access_field(&self.member);
let missing_msg = error_message::on_missing_child(container_name, &self.member);
let from_events = from_events_fn(self.ty.clone());
let from_xml_builder = from_xml_builder_ty(self.ty.clone());
Ok(FieldBuilderPart::Nested {
value: FieldTempInit {
init: quote! { ::std::option::Option::None },
ty: option_ty(self.ty.clone()),
},
matcher: quote! {
#from_events(name, attrs)
},
builder: from_xml_builder,
collect: quote! {
#field_access = ::std::option::Option::Some(#substate_result);
},
finalize: quote! {
match #field_access {
::std::option::Option::Some(value) => value,
::std::option::Option::None => return ::core::result::Result::Err(::xso::error::Error::Other(#missing_msg).into()),
}
},
})
}
}
}
@ -294,7 +380,11 @@ impl FieldDef {
///
/// `bound_name` must be the name to which the field's value is bound in
/// the iterator code.
pub(crate) fn make_iterator_part(&self, bound_name: &Ident) -> Result<FieldIteratorPart> {
pub(crate) fn make_iterator_part(
&self,
scope: &AsItemsScope,
bound_name: &Ident,
) -> Result<FieldIteratorPart> {
match self.kind {
FieldKind::Attribute {
ref xml_name,
@ -335,6 +425,25 @@ impl FieldDef {
Ok(FieldIteratorPart::Text { generator })
}
FieldKind::Child => {
let AsItemsScope { ref lifetime, .. } = scope;
let as_xml_iter = as_xml_iter_fn(self.ty.clone());
let item_iter = item_iter_ty(self.ty.clone(), lifetime.clone());
Ok(FieldIteratorPart::Content {
value: FieldTempInit {
init: quote! {
#as_xml_iter(#bound_name)?
},
ty: item_iter,
},
generator: quote! {
#bound_name.next().transpose()
},
})
}
}
}