use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, LitStr, ExprPath};
use crate::types::{Field, StrictMode, Type};
pub fn read(
tag: &LitStr,
strict: StrictMode,
ele_name: TokenStream,
fields: &[Field],
) -> TokenStream {
let init_fields = fields.iter().map(|field| match field {
Field::Attribute { bind, ty, .. }
| Field::Child { bind, ty, .. }
| Field::FlattenText { bind, ty, .. } => init_value(bind, ty),
Field::Text { bind, .. } => quote! { let #bind; },
});
let return_fields = fields.iter().map(|field| match field {
Field::Attribute {
name,
bind,
ty,
default,
..
}
| Field::Child {
name,
bind,
ty,
default,
..
}
| Field::FlattenText {
name,
bind,
ty,
default,
..
} => return_value(name, bind, ty, *default, &ele_name),
Field::Text { name, bind, ty, .. } => return_value(name, bind, ty, false, &ele_name),
});
let read_attr_fields = fields.iter().filter_map(|field| match field {
Field::Attribute {
bind,
duplicate_last,
name,
tag,
ty,
with,
..
} => Some(read_attrs(tag, bind, name, ty, with, &ele_name, *duplicate_last)),
_ => None,
});
let read_child_fields = fields.iter().filter_map(|field| match field {
Field::Child {
bind,
duplicate_last,
ty,
tags,
name,
..
} => Some(read_children(tags, bind, name, ty, &ele_name, *duplicate_last)),
_ => None,
});
let read_flatten_text_fields = fields.iter().filter_map(|field| match field {
Field::FlattenText {
bind,
duplicate_last,
ty,
with,
tag,
name,
..
} => Some(read_flatten_text(tag, bind, name, ty, with, &ele_name, *duplicate_last)),
_ => None,
});
let read_text_fields = fields.iter().filter_map(|field| match field {
Field::Text { bind, ty, with, name, .. } => Some(read_text(tag, bind, name, ty, with, &ele_name)),
_ => None,
});
let is_text_element = fields
.iter()
.any(|field| matches!(field, Field::Text { .. }));
let return_fields = quote! {
let __res = #ele_name {
#( #return_fields, )*
};
::hard_xml::log_finish_reading!(#ele_name);
return Ok(__res);
};
let unknown_element_handler = if strict.contains(StrictMode::UNKNOWN_ELEMENT) {
quote! {
return Err(XmlError::UnknownField { name: stringify!(#ele_name).to_owned(), field: tag.to_owned() });
}
} else {
quote! {
::hard_xml::log_skip_element!(#ele_name, tag);
reader.next();
reader.read_to_end(tag)?;
}
};
let read_content = if is_text_element {
quote! {
#( #read_text_fields )*
#return_fields
}
} else {
quote! {
if let Token::ElementEnd { end: ElementEnd::Empty, .. } = reader.next().unwrap()? {
#return_fields
}
while let Some(__tag) = reader.find_element_start(Some(#tag))? {
match __tag {
#( #read_child_fields, )*
#( #read_flatten_text_fields, )*
tag => {
#unknown_element_handler
},
}
}
#return_fields
}
};
let unknown_attribute_handler = if strict.contains(StrictMode::UNKNOWN_ATTRIBUTE) {
quote!(return Err(XmlError::UnknownField { name: stringify!(#ele_name).to_owned(), field: key.to_owned()});)
} else {
quote!(::hard_xml::log_skip_attribute!(#ele_name, key);)
};
quote! {
::hard_xml::log_start_reading!(#ele_name);
#( #init_fields )*
reader.read_till_element_start(#tag)?;
while let Some((__key, __value)) = reader.find_attribute()? {
match __key {
#( #read_attr_fields, )*
key => {
#unknown_attribute_handler
},
}
}
#read_content
}
}
fn init_value(name: &Ident, ty: &Type) -> TokenStream {
if ty.is_vec() {
quote! { let mut #name = Vec::new(); }
} else {
quote! { let mut #name = None; }
}
}
fn return_value(
name: &TokenStream,
bind: &Ident,
ty: &Type,
default: bool,
ele_name: &TokenStream,
) -> TokenStream {
if ty.is_vec() || ty.is_option() {
quote! { #name: #bind }
} else if default {
quote! { #name: #bind.unwrap_or_default() }
} else {
quote! {
#name: #bind.ok_or(XmlError::MissingField {
name: stringify!(#ele_name).to_owned(),
field: stringify!(#name).to_owned(),
})?
}
}
}
fn read_attrs(
tag: &LitStr,
bind: &Ident,
name: &TokenStream,
ty: &Type,
with: &Option<ExprPath>,
ele_name: &TokenStream,
duplicate_last: bool,
) -> TokenStream {
let from_str = from_str(ty, with);
if ty.is_vec() {
panic!("`attr` attribute doesn't support Vec.");
} else {
quote! {
#tag => {
::hard_xml::log_start_reading_field!(#ele_name, #name);
if !#duplicate_last && #bind.is_some() {
return ::std::result::Result::Err(::hard_xml::XmlExtendedError::DuplicateAttribute(
(#tag).into(),
).into());
}
#bind = Some(#from_str);
::hard_xml::log_finish_reading_field!(#ele_name, #name);
}
}
}
}
fn read_text(
tag: &LitStr,
bind: &Ident,
name: &TokenStream,
ty: &Type,
with: &Option<ExprPath>,
ele_name: &TokenStream,
) -> TokenStream {
let from_str = from_str(ty, with);
if ty.is_vec() {
panic!("`text` attribute doesn't support Vec.");
} else {
quote! {
::hard_xml::log_start_reading_field!(#ele_name, #name);
let __value = reader.read_text(#tag)?;
#bind = Some(#from_str);
::hard_xml::log_finish_reading_field!(#ele_name, #name);
}
}
}
fn read_children(
tags: &[LitStr],
bind: &Ident,
name: &TokenStream,
ty: &Type,
ele_name: &TokenStream,
duplicate_last: bool,
) -> TokenStream {
let from_reader = match &ty {
Type::VecT(ty) => quote! {
#bind.push(<#ty as ::hard_xml::XmlRead>::from_reader(reader)?);
},
Type::OptionT(ty) | Type::T(ty) => quote! {
if !#duplicate_last && #bind.is_some() {
return ::std::result::Result::Err(::hard_xml::XmlExtendedError::DuplicateElement(
__tag.to_string().into(),
).into());
}
#bind = Some(<#ty as ::hard_xml::XmlRead>::from_reader(reader)?);
},
_ => panic!("`child` attribute only supports Vec<T>, Option<T> and T."),
};
quote! {
#( #tags )|* => {
::hard_xml::log_start_reading_field!(#ele_name, #name);
#from_reader
::hard_xml::log_finish_reading_field!(#ele_name, #name);
}
}
}
fn read_flatten_text(
tag: &LitStr,
bind: &Ident,
name: &TokenStream,
ty: &Type,
with: &Option<ExprPath>,
ele_name: &TokenStream,
duplicate_last: bool,
) -> TokenStream {
let from_str = from_str(ty, with);
let read_text = if ty.is_vec() {
quote! {
let __value = reader.read_text(#tag)?;
#bind.push(#from_str);
}
} else {
quote! {
if !#duplicate_last && #bind.is_some() {
return ::std::result::Result::Err(::hard_xml::XmlExtendedError::DuplicateElement(
(#tag).into(),
).into());
}
let __value = reader.read_text(#tag)?;
#bind = Some(#from_str);
}
};
quote! {
#tag => {
reader.next();
::hard_xml::log_start_reading_field!(#ele_name, #name);
#read_text
::hard_xml::log_finish_reading_field!(#ele_name, #name);
}
}
}
fn from_str(ty: &Type, with: &Option<ExprPath>) -> TokenStream {
if let Some(with_mod) = with {
return quote! {
{
let r: ::hard_xml::XmlResult<_> = #with_mod::from_xml(&__value);
r?
}
};
}
match &ty {
Type::CowStr | Type::OptionCowStr | Type::VecCowStr => quote! { __value },
Type::Bool | Type::OptionBool | Type::VecBool => quote! {
match &*__value {
"t" | "true" | "y" | "yes" | "on" | "1" => true,
"f" | "false" | "n" | "no" | "off" | "0" => false,
_ => <bool as std::str::FromStr>::from_str(&__value).map_err(|e| XmlError::FromStr(e.into()))?
}
},
Type::T(ty) | Type::OptionT(ty) | Type::VecT(ty) => quote! {
<#ty as std::str::FromStr>::from_str(&__value).map_err(|e| XmlError::FromStr(e.into()))?
},
}
}