Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

### New Features

- [#907]: Allow to serialize and deserialize structs which contains only one `$text` field
as an attribute value or a text content value.

### Bug Fixes

### Misc Changes
Expand All @@ -25,6 +28,7 @@
of `NsReader`. Use `.resolver().bindings()` and `.resolver().resolve()` methods instead.
- [#913]: `Attributes::has_nil` now accepts `NamespaceResolver` instead of `Reader<R>`.

[#907]: https://github.com/tafia/quick-xml/pull/907
[#908]: https://github.com/tafia/quick-xml/pull/908
[#913]: https://github.com/tafia/quick-xml/pull/913

Expand Down
55 changes: 43 additions & 12 deletions src/de/simple_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
//! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp
//! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition

use crate::de::Text;
use crate::de::{Text, TEXT_KEY};
use crate::encoding::Decoder;
use crate::errors::serialize::DeError;
use crate::escape::unescape;
use crate::utils::{trim_xml_spaces, CowRef};
use memchr::memchr;
use serde::de::value::UnitDeserializer;
use serde::de::value::{MapDeserializer, SeqAccessDeserializer, UnitDeserializer};
use serde::de::{
DeserializeSeed, Deserializer, EnumAccess, IntoDeserializer, SeqAccess, VariantAccess, Visitor,
};
Expand Down Expand Up @@ -477,6 +477,17 @@ impl<'de, 'a> SeqAccess<'de> for ListIter<'de, 'a> {
}
}

/// serde<=1.0.213 does not implement `IntoDeserializer` for `SeqAccessDeserializer`,
/// which is required for `MapDeserializer`, so implement it for our type
impl<'de, 'a> IntoDeserializer<'de, DeError> for ListIter<'de, 'a> {
type Deserializer = SeqAccessDeserializer<Self>;

#[inline]
fn into_deserializer(self) -> Self::Deserializer {
SeqAccessDeserializer::new(self)
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

/// A deserializer for an xml probably escaped and encoded value of XSD [simple types].
Expand Down Expand Up @@ -605,6 +616,19 @@ impl<'de, 'a> SimpleTypeDeserializer<'de, 'a> {
},
})
}

#[inline]
fn as_seq_access(&self) -> Result<ListIter<'de, '_>, DeError> {
let content = match self.decode()? {
CowRef::Input(s) => Content::Input(s),
CowRef::Slice(s) => Content::Slice(s),
CowRef::Owned(s) => Content::Owned(s, 0),
};
Ok(ListIter {
content: Some(content),
escaped: self.escaped,
})
}
}

impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> {
Expand Down Expand Up @@ -685,15 +709,7 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> {
where
V: Visitor<'de>,
{
let content = match self.decode()? {
CowRef::Input(s) => Content::Input(s),
CowRef::Slice(s) => Content::Slice(s),
CowRef::Owned(s) => Content::Owned(s, 0),
};
visitor.visit_seq(ListIter {
content: Some(content),
escaped: self.escaped,
})
visitor.visit_seq(self.as_seq_access()?)
}

/// Representation of tuples the same as [sequences][Self::deserialize_seq].
Expand All @@ -720,7 +736,22 @@ impl<'de, 'a> Deserializer<'de> for SimpleTypeDeserializer<'de, 'a> {
}

unsupported!(deserialize_map);
unsupported!(deserialize_struct(&'static str, &'static [&'static str]));

fn deserialize_struct<V>(
self,
_name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if fields == [TEXT_KEY] {
let seq = self.as_seq_access()?;
return visitor.visit_map(MapDeserializer::new(std::iter::once((TEXT_KEY, seq))));
}
self.deserialize_str(visitor)
}

fn deserialize_enum<V>(
self,
Expand Down
55 changes: 44 additions & 11 deletions src/se/simple_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
//! [simple types]: https://www.w3schools.com/xml/el_simpletype.asp
//! [as defined]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition

use crate::de::TEXT_KEY;
use crate::escape::escape_char;
use crate::se::text::TextSerializer;
use crate::se::{QuoteLevel, SeError};
use crate::utils::CDataIterator;
use serde::ser::{
Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct,
Impossible, Serialize, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct,
SerializeTupleVariant, Serializer,
};
use std::fmt::{self, Write};
Expand Down Expand Up @@ -480,7 +482,7 @@ impl<W: Write> Serializer for SimpleTypeSerializer<W> {
type SerializeTupleStruct = SimpleSeq<W>;
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
type SerializeMap = Impossible<Self::Ok, Self::Error>;
type SerializeStruct = Impossible<Self::Ok, Self::Error>;
type SerializeStruct = SimpleSeq<W>;
type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;

write_primitive!();
Expand Down Expand Up @@ -561,18 +563,18 @@ impl<W: Write> Serializer for SimpleTypeSerializer<W> {
))
}

#[inline]
fn serialize_struct(
self,
name: &'static str,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Err(SeError::Unsupported(
format!(
"cannot serialize struct `{}` as an attribute or text content value",
name
)
.into(),
))
Ok(SimpleSeq {
writer: self.writer,
target: self.target,
level: self.level,
is_empty: true,
})
}

fn serialize_struct_variant(
Expand Down Expand Up @@ -679,6 +681,37 @@ impl<W: Write> SerializeTupleVariant for SimpleSeq<W> {
}
}

impl<W: Write> SerializeStruct for SimpleSeq<W> {
type Ok = W;
type Error = SeError;

fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Self::Error> {
if self.is_empty && key == TEXT_KEY {
let ser = TextSerializer(SimpleTypeSerializer {
writer: &mut self.writer,
target: self.target,
level: self.level,
});
value.serialize(ser)?;
self.is_empty = false;
Ok(())
} else {
Err(SeError::Unsupported(
format!("only struct which contains exactly one `{TEXT_KEY}` field may be serialized as an attribute or text content value").into(),
))
}
}

#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
SerializeSeq::end(self)
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
Expand Down Expand Up @@ -1260,7 +1293,7 @@ mod tests {
err!(map: BTreeMap::from([(1, 2), (3, 4)])
=> Unsupported("cannot serialize map as an attribute or text content value"));
err!(struct_: Struct { key: "answer", val: 42 }
=> Unsupported("cannot serialize struct `Struct` as an attribute or text content value"));
=> Unsupported("only struct which contains exactly one `$text` field may be serialized as an attribute or text content value"));
err!(enum_struct: Enum::Struct { key: "answer", val: 42 }
=> Unsupported("cannot serialize enum struct variant `Enum::Struct` as an attribute or text content value"));
}
Expand Down
47 changes: 47 additions & 0 deletions tests/serde-issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,50 @@ fn issue888() {
}
);
}

/// Regression test for https://github.com/tafia/quick-xml/issues/906.
#[test]
fn issue906() {
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct AsElement {
#[serde(rename = "a-list")]
a_list: TextContent,
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct AsAttribute {
#[serde(rename = "@a-list")]
a_list: TextContent,
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct TextContent {
#[serde(rename = "$text")]
content: Vec<String>,
#[serde(skip)]
ignored: (),
}

let foo = AsElement {
a_list: TextContent {
content: vec!["A".to_string(), "B".to_string()],
ignored: (),
},
};
let bar = AsAttribute {
a_list: TextContent {
content: vec!["A".to_string(), "B".to_string()],
ignored: (),
},
};

let buffer = to_string_with_root("test", &foo).unwrap();
assert_eq!(buffer, "<test><a-list>A B</a-list></test>");
let foo2: AsElement = from_str(&buffer).unwrap();
assert_eq!(foo2, foo);

let buffer = to_string_with_root("test", &bar).unwrap();
assert_eq!(buffer, "<test a-list=\"A B\"/>");
let bar2: AsAttribute = from_str(&buffer).unwrap();
assert_eq!(bar2, bar);
}