xso-proc: add support for parsing text content
This commit is contained in:
parent
92e69cf59f
commit
b0803f831b
7 changed files with 301 additions and 11 deletions
|
@ -380,3 +380,37 @@ fn default_attribute_roundtrip_pp() {
|
||||||
};
|
};
|
||||||
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz' bar='16'/>");
|
roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz' bar='16'/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "text")]
|
||||||
|
struct TextString {
|
||||||
|
#[xml(text)]
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_string_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<TextString>("<text xmlns='urn:example:ns1'>hello world!</text>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
|
||||||
|
#[xml(namespace = NS1, name = "text")]
|
||||||
|
struct TextNonString {
|
||||||
|
#[xml(text)]
|
||||||
|
text: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_non_string_roundtrip() {
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::{
|
||||||
|
option::Option::{None, Some},
|
||||||
|
result::Result::{Err, Ok},
|
||||||
|
};
|
||||||
|
roundtrip_full::<TextNonString>("<text xmlns='urn:example:ns1'>123456</text>");
|
||||||
|
}
|
||||||
|
|
|
@ -56,10 +56,14 @@ impl Compound {
|
||||||
state_prefix: &str,
|
state_prefix: &str,
|
||||||
) -> Result<FromEventsSubmachine> {
|
) -> Result<FromEventsSubmachine> {
|
||||||
let scope = FromEventsScope::new();
|
let scope = FromEventsScope::new();
|
||||||
let FromEventsScope { ref attrs, .. } = scope;
|
let FromEventsScope {
|
||||||
|
ref attrs,
|
||||||
|
ref builder_data_ident,
|
||||||
|
ref text,
|
||||||
|
..
|
||||||
|
} = scope;
|
||||||
|
|
||||||
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
|
let default_state_ident = quote::format_ident!("{}Default", state_prefix);
|
||||||
let builder_data_ident = quote::format_ident!("__data");
|
|
||||||
let builder_data_ty: Type = TypePath {
|
let builder_data_ty: Type = TypePath {
|
||||||
qself: None,
|
qself: None,
|
||||||
path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
|
path: quote::format_ident!("{}Data{}", state_ty_ident, state_prefix).into(),
|
||||||
|
@ -70,6 +74,7 @@ impl Compound {
|
||||||
let mut builder_data_def = TokenStream::default();
|
let mut builder_data_def = TokenStream::default();
|
||||||
let mut builder_data_init = TokenStream::default();
|
let mut builder_data_init = TokenStream::default();
|
||||||
let mut output_cons = TokenStream::default();
|
let mut output_cons = TokenStream::default();
|
||||||
|
let mut text_handler = None;
|
||||||
|
|
||||||
for field in self.fields.iter() {
|
for field in self.fields.iter() {
|
||||||
let member = field.member();
|
let member = field.member();
|
||||||
|
@ -92,9 +97,45 @@ impl Compound {
|
||||||
#member: #builder_data_ident.#builder_field_name,
|
#member: #builder_data_ident.#builder_field_name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldBuilderPart::Text {
|
||||||
|
value: FieldTempInit { ty, init },
|
||||||
|
collect,
|
||||||
|
finalize,
|
||||||
|
} => {
|
||||||
|
if text_handler.is_some() {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
field.member(),
|
||||||
|
"more than one field attempts to collect text data",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder_data_def.extend(quote! {
|
||||||
|
#builder_field_name: #ty,
|
||||||
|
});
|
||||||
|
builder_data_init.extend(quote! {
|
||||||
|
#builder_field_name: #init,
|
||||||
|
});
|
||||||
|
text_handler = Some(quote! {
|
||||||
|
#collect
|
||||||
|
::core::result::Result::Ok(::std::ops::ControlFlow::Break(
|
||||||
|
Self::#default_state_ident { #builder_data_ident }
|
||||||
|
))
|
||||||
|
});
|
||||||
|
output_cons.extend(quote! {
|
||||||
|
#member: #finalize,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let text_handler = match text_handler {
|
||||||
|
Some(v) => v,
|
||||||
|
None => quote! {
|
||||||
|
::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
|
let unknown_attr_err = format!("Unknown attribute in {}.", output_name);
|
||||||
let unknown_child_err = format!("Unknown child in {}.", output_name);
|
let unknown_child_err = format!("Unknown child in {}.", output_name);
|
||||||
|
|
||||||
|
@ -121,8 +162,8 @@ impl Compound {
|
||||||
::xso::exports::rxml::Event::StartElement(..) => {
|
::xso::exports::rxml::Event::StartElement(..) => {
|
||||||
::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
|
::core::result::Result::Err(::xso::error::Error::Other(#unknown_child_err))
|
||||||
}
|
}
|
||||||
::xso::exports::rxml::Event::Text(..) => {
|
::xso::exports::rxml::Event::Text(_, #text) => {
|
||||||
::core::result::Result::Err(::xso::error::Error::Other("Unexpected text content".into()))
|
#text_handler
|
||||||
}
|
}
|
||||||
// we ignore these: a correct parser only generates
|
// we ignore these: a correct parser only generates
|
||||||
// them at document start, and there we want to indeed
|
// them at document start, and there we want to indeed
|
||||||
|
@ -186,10 +227,11 @@ impl Compound {
|
||||||
.with_field(&name_ident, &qname_ty(Span::call_site())),
|
.with_field(&name_ident, &qname_ty(Span::call_site())),
|
||||||
);
|
);
|
||||||
|
|
||||||
for field in self.fields.iter() {
|
for (i, field) in self.fields.iter().enumerate() {
|
||||||
let member = field.member();
|
let member = field.member();
|
||||||
let bound_name = mangle_member(member);
|
let bound_name = mangle_member(member);
|
||||||
let part = field.make_iterator_part(&scope, &bound_name)?;
|
let part = field.make_iterator_part(&scope, &bound_name)?;
|
||||||
|
let state_name = quote::format_ident!("{}Field{}", state_prefix, i);
|
||||||
|
|
||||||
match part {
|
match part {
|
||||||
FieldIteratorPart::Header { setter } => {
|
FieldIteratorPart::Header { setter } => {
|
||||||
|
@ -202,6 +244,30 @@ impl Compound {
|
||||||
});
|
});
|
||||||
states[0].add_field(&bound_name, field.ty());
|
states[0].add_field(&bound_name, field.ty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldIteratorPart::Text { generator } => {
|
||||||
|
// we have to make sure that we carry our data around in
|
||||||
|
// all the previous states.
|
||||||
|
for state in states.iter_mut() {
|
||||||
|
state.add_field(&bound_name, field.ty());
|
||||||
|
}
|
||||||
|
states.push(
|
||||||
|
State::new(state_name)
|
||||||
|
.with_field(&bound_name, field.ty())
|
||||||
|
.with_impl(quote! {
|
||||||
|
::core::option::Option::Some(::xso::exports::rxml::Event::Text(
|
||||||
|
::xso::exports::rxml::parser::EventMetrics::zero(),
|
||||||
|
#generator,
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
destructure.extend(quote! {
|
||||||
|
#member: #bound_name,
|
||||||
|
});
|
||||||
|
start_init.extend(quote! {
|
||||||
|
#bound_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
//! Compound (struct or enum variant) field types
|
//! Compound (struct or enum variant) field types
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{spanned::Spanned, *};
|
use syn::{spanned::Spanned, *};
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ use rxml_validation::NcName;
|
||||||
use crate::error_message::{self, ParentRef};
|
use crate::error_message::{self, ParentRef};
|
||||||
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
use crate::meta::{Flag, NameRef, NamespaceRef, XmlFieldMeta};
|
||||||
use crate::scope::{FromEventsScope, IntoEventsScope};
|
use crate::scope::{FromEventsScope, IntoEventsScope};
|
||||||
use crate::types::{default_fn, from_xml_text_fn, into_optional_xml_text_fn};
|
use crate::types::{
|
||||||
|
default_fn, from_xml_text_fn, into_optional_xml_text_fn, into_xml_text_fn, string_ty,
|
||||||
|
};
|
||||||
|
|
||||||
/// Code slices necessary for declaring and initializing a temporary variable
|
/// Code slices necessary for declaring and initializing a temporary variable
|
||||||
/// for parsing purposes.
|
/// for parsing purposes.
|
||||||
|
@ -40,6 +42,21 @@ pub(crate) enum FieldBuilderPart {
|
||||||
/// element's start event.
|
/// element's start event.
|
||||||
value: FieldTempInit,
|
value: FieldTempInit,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Parse a field from text events.
|
||||||
|
Text {
|
||||||
|
/// Expression and type which initializes a buffer to use during
|
||||||
|
/// parsing.
|
||||||
|
value: FieldTempInit,
|
||||||
|
|
||||||
|
/// Statement which takes text and accumulates it into the temporary
|
||||||
|
/// value declared via `value`.
|
||||||
|
collect: TokenStream,
|
||||||
|
|
||||||
|
/// Expression which evaluates to the field's type, consuming the
|
||||||
|
/// temporary value.
|
||||||
|
finalize: TokenStream,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describe how a struct or enum variant's member is converted to XML data.
|
/// Describe how a struct or enum variant's member is converted to XML data.
|
||||||
|
@ -56,6 +73,13 @@ pub(crate) enum FieldIteratorPart {
|
||||||
/// field's value.
|
/// field's value.
|
||||||
setter: TokenStream,
|
setter: TokenStream,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The field is emitted as text event.
|
||||||
|
Text {
|
||||||
|
/// An expression which consumes the field's value and returns a
|
||||||
|
/// String, which is then emitted as text data.
|
||||||
|
generator: TokenStream,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify how the field is mapped to XML.
|
/// Specify how the field is mapped to XML.
|
||||||
|
@ -72,6 +96,9 @@ enum FieldKind {
|
||||||
// attribute is absent.
|
// attribute is absent.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The field maps to the character data of the element.
|
||||||
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldKind {
|
impl FieldKind {
|
||||||
|
@ -115,6 +142,8 @@ impl FieldKind {
|
||||||
default_,
|
default_,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XmlFieldMeta::Text => Ok(Self::Text),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,7 +244,7 @@ impl FieldDef {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(FieldBuilderPart::Init {
|
Ok(FieldBuilderPart::Init {
|
||||||
value: FieldTempInit {
|
value: FieldTempInit {
|
||||||
init: quote! {
|
init: quote! {
|
||||||
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
|
match #attrs.remove(#xml_namespace, #xml_name).map(#from_xml_text).transpose()? {
|
||||||
|
@ -225,7 +254,26 @@ impl FieldDef {
|
||||||
},
|
},
|
||||||
ty: self.ty.clone(),
|
ty: self.ty.clone(),
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldKind::Text => {
|
||||||
|
let FromEventsScope { ref text, .. } = scope;
|
||||||
|
let field_access = scope.access_field(&self.member);
|
||||||
|
let from_xml_text = from_xml_text_fn(self.ty.clone());
|
||||||
|
|
||||||
|
Ok(FieldBuilderPart::Text {
|
||||||
|
value: FieldTempInit {
|
||||||
|
init: quote! { ::std::string::String::new() },
|
||||||
|
ty: string_ty(Span::call_site()),
|
||||||
|
},
|
||||||
|
collect: quote! {
|
||||||
|
#field_access.push_str(#text.as_str());
|
||||||
|
},
|
||||||
|
finalize: quote! {
|
||||||
|
#from_xml_text(#field_access)?
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,7 +304,7 @@ impl FieldDef {
|
||||||
|
|
||||||
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
|
let into_optional_xml_text = into_optional_xml_text_fn(self.ty.clone());
|
||||||
|
|
||||||
return Ok(FieldIteratorPart::Header {
|
Ok(FieldIteratorPart::Header {
|
||||||
// This is a neat little trick:
|
// This is a neat little trick:
|
||||||
// Option::from(x) converts x to an Option<T> *unless* it
|
// Option::from(x) converts x to an Option<T> *unless* it
|
||||||
// already is an Option<_>.
|
// already is an Option<_>.
|
||||||
|
@ -267,7 +315,17 @@ impl FieldDef {
|
||||||
#bound_name,
|
#bound_name,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldKind::Text => {
|
||||||
|
let into_xml_text = into_xml_text_fn(self.ty.clone());
|
||||||
|
|
||||||
|
Ok(FieldIteratorPart::Text {
|
||||||
|
generator: quote! {
|
||||||
|
#into_xml_text(#bound_name)?
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -294,6 +294,7 @@ fn parse_prefixed_name(
|
||||||
/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
|
/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum XmlFieldMeta {
|
pub(crate) enum XmlFieldMeta {
|
||||||
|
/// `#[xml(attribute)]`, `#[xml(attribute = ..)]` or `#[xml(attribute(..))]`
|
||||||
Attribute {
|
Attribute {
|
||||||
/// The span of the `#[xml(attribute)]` meta from which this was parsed.
|
/// The span of the `#[xml(attribute)]` meta from which this was parsed.
|
||||||
///
|
///
|
||||||
|
@ -309,6 +310,9 @@ pub(crate) enum XmlFieldMeta {
|
||||||
/// The `default` flag.
|
/// The `default` flag.
|
||||||
default_: Flag,
|
default_: Flag,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// `#[xml(text)]`
|
||||||
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XmlFieldMeta {
|
impl XmlFieldMeta {
|
||||||
|
@ -388,11 +392,18 @@ impl XmlFieldMeta {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a `#[xml(text)]` meta.
|
||||||
|
fn text_from_meta(_: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
|
Ok(Self::Text)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse [`Self`] from a nestd meta, switching on the identifier
|
/// Parse [`Self`] from a nestd meta, switching on the identifier
|
||||||
/// of that nested meta.
|
/// of that nested meta.
|
||||||
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
if meta.path.is_ident("attribute") {
|
if meta.path.is_ident("attribute") {
|
||||||
Self::attribute_from_meta(meta)
|
Self::attribute_from_meta(meta)
|
||||||
|
} else if meta.path.is_ident("text") {
|
||||||
|
Self::text_from_meta(meta)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported field meta"))
|
Err(Error::new_spanned(meta.path, "unsupported field meta"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,17 @@ pub(crate) struct FromEventsScope {
|
||||||
/// Accesses the `AttrMap` from code in
|
/// Accesses the `AttrMap` from code in
|
||||||
/// [`crate::field::FieldBuilderPart::Init`].
|
/// [`crate::field::FieldBuilderPart::Init`].
|
||||||
pub(crate) attrs: Ident,
|
pub(crate) attrs: Ident,
|
||||||
|
|
||||||
|
/// Accesses the `String` of a `rxml::Event::Text` event from code in
|
||||||
|
/// [`crate::field::FieldBuilderPart::Text`].
|
||||||
|
pub(crate) text: Ident,
|
||||||
|
|
||||||
|
/// Accesses the builder data during parsing.
|
||||||
|
///
|
||||||
|
/// This should not be used directly outside [`crate::compound`]. Most of
|
||||||
|
/// the time, using [`Self::access_field`] is the correct way to access
|
||||||
|
/// the builder data.
|
||||||
|
pub(crate) builder_data_ident: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromEventsScope {
|
impl FromEventsScope {
|
||||||
|
@ -38,8 +49,27 @@ impl FromEventsScope {
|
||||||
// well-known identifiers from scratch all the time.
|
// well-known identifiers from scratch all the time.
|
||||||
Self {
|
Self {
|
||||||
attrs: Ident::new("attrs", Span::call_site()),
|
attrs: Ident::new("attrs", Span::call_site()),
|
||||||
|
text: Ident::new("__xso_proc_macro_text_data", Span::call_site()),
|
||||||
|
builder_data_ident: Ident::new("__xso_proc_macro_builder_data", Span::call_site()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate an expression which accesses the temporary value for the
|
||||||
|
/// given `member` during parsing.
|
||||||
|
pub(crate) fn access_field(&self, member: &Member) -> Expr {
|
||||||
|
Expr::Field(ExprField {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
base: Box::new(Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: None,
|
||||||
|
path: self.builder_data_ident.clone().into(),
|
||||||
|
})),
|
||||||
|
dot_token: syn::token::Dot {
|
||||||
|
spans: [Span::call_site()],
|
||||||
|
},
|
||||||
|
member: Member::Named(mangle_member(member)),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Container struct for various identifiers used throughout the generator
|
/// Container struct for various identifiers used throughout the generator
|
||||||
|
|
|
@ -155,3 +155,68 @@ pub(crate) fn default_fn(of_ty: Type) -> Expr {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Type`] referring to `::std::string::String`.
|
||||||
|
pub(crate) fn string_ty(span: Span) -> Type {
|
||||||
|
Type::Path(TypePath {
|
||||||
|
qself: None,
|
||||||
|
path: Path {
|
||||||
|
leading_colon: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
segments: [
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("std", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("string", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("String", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`syn::Expr`] referring to
|
||||||
|
/// `<#ty as ::xso::IntoXmlText>::into_xml_text`.
|
||||||
|
pub(crate) fn into_xml_text_fn(ty: Type) -> Expr {
|
||||||
|
let span = ty.span();
|
||||||
|
Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: Some(QSelf {
|
||||||
|
lt_token: syn::token::Lt { spans: [span] },
|
||||||
|
ty: Box::new(ty),
|
||||||
|
position: 2,
|
||||||
|
as_token: Some(syn::token::As { span }),
|
||||||
|
gt_token: syn::token::Gt { spans: [span] },
|
||||||
|
}),
|
||||||
|
path: Path {
|
||||||
|
leading_colon: Some(syn::token::PathSep {
|
||||||
|
spans: [span, span],
|
||||||
|
}),
|
||||||
|
segments: [
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("xso", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("IntoXmlText", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new("into_xml_text", span),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ The following mapping types are defined:
|
||||||
| Type | Description |
|
| Type | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [`attribute`](#attribute-meta) | Map the field to an XML attribute on the struct's element |
|
| [`attribute`](#attribute-meta) | Map the field to an XML attribute on the struct's element |
|
||||||
|
| [`text`](#text-meta) | Map the field to the text content of the struct's element |
|
||||||
|
|
||||||
#### `attribute` meta
|
#### `attribute` meta
|
||||||
|
|
||||||
|
@ -132,3 +133,28 @@ assert_eq!(foo, Foo {
|
||||||
e: "5".to_string(),
|
e: "5".to_string(),
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `text` meta
|
||||||
|
|
||||||
|
The `text` meta causes the field to be mapped to the text content of the
|
||||||
|
element. For `FromXml`, the field's type must implement [`FromXmlText`] and
|
||||||
|
for `IntoXml`, the field's type must implement [`IntoXmlText`].
|
||||||
|
|
||||||
|
The `text` meta supports no options or value.
|
||||||
|
|
||||||
|
##### Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# use xso::FromXml;
|
||||||
|
#[derive(FromXml, Debug, PartialEq)]
|
||||||
|
#[xml(namespace = "urn:example", name = "foo")]
|
||||||
|
struct Foo {
|
||||||
|
#[xml(text)]
|
||||||
|
a: String,
|
||||||
|
};
|
||||||
|
|
||||||
|
let foo: Foo = xso::from_bytes(b"<foo xmlns='urn:example'>hello</foo>").unwrap();
|
||||||
|
assert_eq!(foo, Foo {
|
||||||
|
a: "hello".to_string(),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in a new issue