use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, LitStr, ExprPath};
use crate::types::{Field, Type};
pub fn write(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStream {
let write_attributes = fields.iter().filter_map(|field| match field {
Field::Attribute { tag, bind, ty, with, .. } => Some(write_attrs(tag, bind, ty, with, &ele_name)),
_ => None,
});
let write_text = fields.iter().filter_map(|field| match field {
Field::Text {
bind, ty, with, is_cdata, ..
} => Some(write_text(tag, bind, ty, with, &ele_name, *is_cdata)),
_ => None,
});
let write_flatten_text = fields.iter().filter_map(|field| match field {
Field::FlattenText {
tag,
bind,
ty,
with,
is_cdata,
..
} => Some(write_flatten_text(tag, bind, ty, with, &ele_name, *is_cdata)),
_ => None,
});
let write_child = fields.iter().filter_map(|field| match field {
Field::Child { bind, ty, .. } => Some(write_child(bind, ty, &ele_name)),
_ => None,
});
let is_leaf_element = fields
.iter()
.all(|field| matches!(field, Field::Attribute { .. }));
let is_text_element = fields
.iter()
.any(|field| matches!(field, Field::Text { .. }));
let can_self_close = fields.iter().all(|field| match field {
Field::Child { ty, .. } | Field::FlattenText { ty, .. } => ty.is_vec() || ty.is_option(),
_ => true,
});
let content_is_empty = fields.iter().filter_map(|field| match field {
Field::Child { ty, bind, .. } | Field::FlattenText { ty, bind, .. } => {
if ty.is_vec() {
Some(quote! { #bind.is_empty() })
} else if ty.is_option() {
Some(quote! { #bind.is_none() })
} else {
None
}
}
_ => None,
});
let write_element_end = if is_leaf_element {
quote! { writer.write_element_end_empty()?; }
} else if is_text_element {
quote! { #( #write_text )* }
} else {
quote! {
if #can_self_close #( && #content_is_empty )* {
writer.write_element_end_empty()?;
} else {
writer.write_element_end_open()?;
#( #write_child )*
#( #write_flatten_text )*
writer.write_element_end_close(#tag)?;
}
}
};
quote! {
::hard_xml::log_start_writing!(#ele_name);
writer.write_element_start(#tag)?;
#( #write_attributes )*
#write_element_end
::hard_xml::log_finish_writing!(#ele_name);
}
}
fn write_attrs(tag: &LitStr, name: &Ident, ty: &Type, with: &Option<ExprPath>, ele_name: &TokenStream) -> TokenStream {
let to_str = to_str(ty, with);
if ty.is_vec() {
panic!("`attr` attribute doesn't support Vec.");
} else if ty.is_option() {
quote! {
::hard_xml::log_start_writing_field!(#ele_name, #name);
if let Some(__value) = #name {
writer.write_attribute(#tag, #to_str)?;
}
::hard_xml::log_finish_writing_field!(#ele_name, #name);
}
} else {
quote! {
::hard_xml::log_start_writing_field!(#ele_name, #name);
let __value = #name;
writer.write_attribute(#tag, #to_str)?;
::hard_xml::log_finish_writing_field!(#ele_name, #name);
}
}
}
fn write_child(name: &Ident, ty: &Type, ele_name: &TokenStream) -> TokenStream {
match ty {
Type::OptionT(_) => quote! {
::hard_xml::log_start_writing_field!(#ele_name, #name);
if let Some(ref ele) = #name {
ele.to_writer(&mut writer)?;
}
::hard_xml::log_finish_writing_field!(#ele_name, #name);
},
Type::VecT(_) => quote! {
::hard_xml::log_start_writing_field!(#ele_name, #name);
for ele in #name {
ele.to_writer(&mut writer)?;
}
::hard_xml::log_finish_writing_field!(#ele_name, #name);
},
Type::T(_) => quote! {
::hard_xml::log_start_writing_field!(#ele_name, #name);
#name.to_writer(&mut writer)?;
::hard_xml::log_finish_writing_field!(#ele_name, #name);
},
_ => panic!("`child` attribute only supports Vec<T>, Option<T> and T."),
}
}
fn write_text(
tag: &LitStr,
name: &Ident,
ty: &Type,
with: &Option<ExprPath>,
ele_name: &TokenStream,
is_cdata: bool,
) -> TokenStream {
let to_str = to_str(ty, with);
let wrtie_fn = if is_cdata {
quote!(write_cdata_text)
} else {
quote!(write_text)
};
quote! {
writer.write_element_end_open()?;
::hard_xml::log_start_writing_field!(#ele_name, #name);
let __value = &#name;
writer.#wrtie_fn(#to_str)?;
::hard_xml::log_finish_writing_field!(#ele_name, #name);
writer.write_element_end_close(#tag)?;
}
}
fn write_flatten_text(
tag: &LitStr,
name: &Ident,
ty: &Type,
with: &Option<ExprPath>,
ele_name: &TokenStream,
is_cdata: bool,
) -> TokenStream {
let to_str = to_str(ty, with);
if ty.is_vec() {
quote! {
::hard_xml::log_finish_writing_field!(#ele_name, #name);
for __value in #name {
writer.write_flatten_text(#tag, #to_str, #is_cdata)?;
}
::hard_xml::log_finish_writing_field!(#ele_name, #name);
}
} else if ty.is_option() {
quote! {
::hard_xml::log_finish_writing_field!(#ele_name, #name);
if let Some(__value) = #name {
writer.write_flatten_text(#tag, #to_str, #is_cdata)?;
}
::hard_xml::log_finish_writing_field!(#ele_name, #name);
}
} else {
quote! {
::hard_xml::log_finish_writing_field!(#ele_name, #name);
let __value = &#name;
writer.write_flatten_text(#tag, #to_str, #is_cdata)?;
::hard_xml::log_finish_writing_field!(#ele_name, #name);
}
}
}
fn to_str(ty: &Type, with: &Option<ExprPath>) -> TokenStream {
if let Some(with_mod) = with {
return quote! {
{
let r: ::hard_xml::XmlResult<_> = #with_mod::to_xml(&__value);
std::convert::AsRef::<str>::as_ref(&r?)
}
};
}
match &ty {
Type::CowStr | Type::OptionCowStr | Type::VecCowStr => {
quote! { __value }
}
Type::Bool | Type::OptionBool | Type::VecBool => quote! {
match __value {
true => "true",
false => "false"
}
},
Type::T(_) | Type::OptionT(_) | Type::VecT(_) => {
quote! { &format!("{}", __value) }
}
}
}