diff --git a/Cargo.toml b/Cargo.toml index 683b378..c8ca67d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["ImJeremyHe"] edition = "2018" name = "xmlserde" -version = "0.13.0" +version = "0.13.1" license = "MIT" description = "useful tool for serializing and deserializing xml" repository = "https://github.com/ImJeremyHe/xmlserde" @@ -13,4 +13,4 @@ readme = "README.md" quick-xml = { version = "0.40", features = ["serialize"] } [dev-dependencies] -xmlserde_derives = { path = "./derives", version = "0.13.0" } +xmlserde_derives = { path = "./derives", version = "0.13.1" } diff --git a/README.md b/README.md index d283583..cc54b1c 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ You can check the detail of usage in the `workbook` directory or [here](https:// `xmlserde` offers a range of macros that should suffice for most use cases. To utilize them, you need to include the following crates in your `Cargo.toml` file: ```toml -xmlserde = 0.10 -xmlserde_derives = 0.10 +xmlserde = 0.13 +xmlserde_derives = 0.13 ``` ### Deserialize diff --git a/derives/Cargo.toml b/derives/Cargo.toml index a94f2db..8d646c6 100644 --- a/derives/Cargo.toml +++ b/derives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xmlserde_derives" -version = "0.13.0" +version = "0.13.1" description = "macros that help xmlserde serde the xml files" authors = ["ImJeremyHe"] license = "MIT" diff --git a/derives/src/de.rs b/derives/src/de.rs index 34b9524..bc1a153 100644 --- a/derives/src/de.rs +++ b/derives/src/de.rs @@ -171,11 +171,12 @@ pub fn get_de_struct_impl_block(container: Container) -> proc_macro2::TokenStrea let sfc_branch = sfc_match_branch(self_closed_children); let ident = &container.original.ident; let (impl_generics, type_generics, where_clause) = container.original.generics.split_for_impl(); - let text_branch = { + let (text_init, text_branch, text_finalize) = { if let Some(t) = text { - Some(text_match_branch(t)) + let (init, branch, finalize) = text_match_branch(t); + (init, Some(branch), Some(finalize)) } else { - None + (quote! {}, None, None) } }; let get_root = if let Some(r) = &container.root { @@ -237,6 +238,7 @@ pub fn get_de_struct_impl_block(container: Container) -> proc_macro2::TokenStrea let mut buf = Vec::::new(); use ::xmlserde::quick_xml::events::Event; #vec_init + #text_init if _is_empty_ {} else { loop { match _reader_.read_event_into(&mut buf) { @@ -255,6 +257,7 @@ pub fn get_de_struct_impl_block(container: Container) -> proc_macro2::TokenStrea } } } + #text_finalize #result_untagged_structs Self { #result @@ -619,12 +622,21 @@ fn attr_match_branch(field: StructField) -> proc_macro2::TokenStream { } } -fn text_match_branch(field: StructField) -> proc_macro2::TokenStream { +/// Returns `(init, branch, finalize)`: +/// * `init` declares the accumulator before the read loop, +/// * `branch` are the match arms handling `Text` and `GeneralRef` events, +/// * `finalize` deserializes the accumulated text and assigns the field. +fn text_match_branch( + field: StructField, +) -> ( + proc_macro2::TokenStream, + proc_macro2::TokenStream, + proc_macro2::TokenStream, +) { if !matches!(field.ty, EleType::Text) { panic!("") } let ident = field.original.ident.as_ref().expect("should have idnet"); - // let t = &field.original.ty; let (t, is_opt) = match field.generic { Generic::Vec(_) => panic!("text element should not be Vec"), Generic::Opt(ty) => (ty, true), @@ -635,22 +647,44 @@ fn text_match_branch(field: StructField) -> proc_macro2::TokenStream { } else { quote! {#ident = __v;} }; - quote! { + + let init = quote! { + let mut __text_buf: Option = None; + }; + + let branch = quote! { Ok(Event::Text(__s)) => { - use ::xmlserde::{XmlValue, XmlDeserialize}; let __decoded = __s.decode().expect("decode text"); - let __r = ::xmlserde::quick_xml::escape::unescape(&__decoded).expect("unescape text"); - match #t::deserialize(&__r) { + __text_buf.get_or_insert_with(String::new).push_str(&__decoded); + }, + Ok(Event::GeneralRef(__r)) => { + let __buf = __text_buf.get_or_insert_with(String::new); + if let Some(__ch) = __r.resolve_char_ref().expect("resolve char ref") { + __buf.push(__ch); + } else { + let __name = __r.decode().expect("decode entity"); + let __resolved = ::xmlserde::quick_xml::escape::resolve_predefined_entity(&__name) + .expect("resolve predefined entity"); + __buf.push_str(__resolved); + } + }, + }; + + let finalize = quote! { + if let Some(__text) = __text_buf { + use ::xmlserde::{XmlValue, XmlDeserialize}; + match #t::deserialize(&__text) { Ok(__v) => { - // #ident = v; #tt }, Err(_) => { panic!("deserialize failed in text element") } } - }, - } + } + }; + + (init, branch, finalize) } fn untag_text_enum_branches(untags: &[StructField]) -> proc_macro2::TokenStream { diff --git a/tests/lib.rs b/tests/lib.rs index bfa0738..2fe1004 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -915,6 +915,23 @@ mod tests { assert_eq!(c.value, "&foo'bar\"baz"); } + #[test] + fn test_unescape_xml_entities_in_text() { + #[derive(Debug, XmlDeserialize)] + #[xmlserde(root = b"root")] + struct Root { + #[xmlserde(name = b"f", ty = "text")] + f: String, + } + + let xml = r#"_xlfn.DISPIMG("ID_72CA26DEC13E452487646D77B0F1058F",1)"#; + let root = xml_deserialize_from_str::(xml).unwrap(); + assert_eq!( + root.f, + "_xlfn.DISPIMG(\"ID_72CA26DEC13E452487646D77B0F1058F\",1)" + ); + } + #[test] fn test_unescape_xml_entities_in_opt_attr() { #[derive(Debug, XmlDeserialize)]