+//!
+//! NOTE: The behaviour is not symmetric by default. `None` will be serialized as
+//! `optional=""`. This behaviour is consistent across serde crates. You should add
+//! `#[serde(skip_serializing_if = "Option::is_none")]` attribute to the field to
+//! skip `None`s.
+//!
//!
//!
//!
@@ -454,9 +462,15 @@
//! When the XML element is present, type `T` will be deserialized from an
//! element (which is a string or a multi-mapping -- i.e. mapping which can have
//! duplicated keys).
-//!
+//!
//!
-//! Currently some edge cases exists described in the issue [#497].
+//! NOTE: The behaviour is not symmetric by default. `None` will be serialized as
+//! ` `. This behaviour is consistent across serde crates. You should add
+//! `#[serde(skip_serializing_if = "Option::is_none")]` attribute to the field to
+//! skip `None`s.
+//!
+//! NOTE: Deserializer will automatically handle a [`xsi:nil`] attribute and set field to `None`.
+//! For more info see [Mapping of `xsi:nil`](#mapping-of-xsinil).
//!
//!
//!
@@ -1312,6 +1326,65 @@
//!
//!
//!
+//! Mapping of `xsi:nil`
+//! ====================
+//!
+//! quick-xml supports handling of [`xsi:nil`] special attribute. When field of optional
+//! type is mapped to the XML element which have `xsi:nil="true"` set, or if that attribute
+//! is placed on parent XML element, the deserializer will call [`Visitor::visit_none`]
+//! and skip XML element corresponding to a field.
+//!
+//! Examples:
+//!
+//! ```
+//! # use pretty_assertions::assert_eq;
+//! # use serde::Deserialize;
+//! #[derive(Deserialize, Debug, PartialEq)]
+//! struct TypeWithOptionalField {
+//! element: Option
,
+//! }
+//!
+//! assert_eq!(
+//! TypeWithOptionalField {
+//! element: None,
+//! },
+//! quick_xml::de::from_str("
+//!
+//! Content is skiped because of xsi:nil='true'
+//!
+//! ").unwrap(),
+//! );
+//! ```
+//!
+//! You can capture attributes from the optional type, because ` xsi:nil="true"` elements can have
+//! attributes:
+//! ```
+//! # use pretty_assertions::assert_eq;
+//! # use serde::Deserialize;
+//! #[derive(Deserialize, Debug, PartialEq)]
+//! struct TypeWithOptionalField {
+//! #[serde(rename = "@attribute")]
+//! attribute: usize,
+//!
+//! element: Option,
+//! non_optional: String,
+//! }
+//!
+//! assert_eq!(
+//! TypeWithOptionalField {
+//! attribute: 42,
+//! element: None,
+//! non_optional: "Note, that non-optional fields will be deserialized as usual".to_string(),
+//! },
+//! quick_xml::de::from_str("
+//!
+//! Content is skiped because of xsi:nil='true'
+//! Note, that non-optional fields will be deserialized as usual
+//!
+//! ").unwrap(),
+//! );
+//! ```
+//!
//! Generate Rust types from XML
//! ============================
//!
@@ -1820,7 +1893,7 @@
//! [`overlapped-lists`]: ../index.html#overlapped-lists
//! [specification]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition
//! [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with
-//! [#497]: https://github.com/tafia/quick-xml/issues/497
+//! [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil
//! [`Serializer::serialize_unit_variant`]: serde::Serializer::serialize_unit_variant
//! [`Deserializer::deserialize_enum`]: serde::Deserializer::deserialize_enum
//! [`SeError::Unsupported`]: crate::errors::serialize::SeError::Unsupported
@@ -2016,7 +2089,7 @@ use crate::{
errors::Error,
events::{BytesCData, BytesEnd, BytesStart, BytesText, Event},
name::QName,
- reader::Reader,
+ reader::NsReader,
utils::CowRef,
};
use serde::de::{
@@ -2415,7 +2488,7 @@ where
/// # use pretty_assertions::assert_eq;
/// use serde::Deserialize;
/// use quick_xml::de::Deserializer;
- /// use quick_xml::Reader;
+ /// use quick_xml::NsReader;
///
/// #[derive(Deserialize)]
/// struct SomeStruct {
@@ -2432,7 +2505,7 @@ where
/// let err = SomeStruct::deserialize(&mut de);
/// assert!(err.is_err());
///
- /// let reader: &Reader<_> = de.get_ref().get_ref();
+ /// let reader: &NsReader<_> = de.get_ref().get_ref();
///
/// assert_eq!(reader.error_position(), 28);
/// assert_eq!(reader.buffer_position(), 41);
@@ -2534,6 +2607,22 @@ where
}
}
+ #[inline]
+ fn last_peeked(&self) -> &DeEvent<'de> {
+ #[cfg(feature = "overlapped-lists")]
+ {
+ self.read
+ .front()
+ .expect("`Deserializer::peek()` should be called")
+ }
+ #[cfg(not(feature = "overlapped-lists"))]
+ {
+ self.peek
+ .as_ref()
+ .expect("`Deserializer::peek()` should be called")
+ }
+ }
+
fn next(&mut self) -> Result, DeError> {
// Replay skipped or peeked events
#[cfg(feature = "overlapped-lists")]
@@ -2764,6 +2853,14 @@ where
}
self.reader.read_to_end(name)
}
+
+ fn skip_next_tree(&mut self) -> Result<(), DeError> {
+ let DeEvent::Start(start) = self.next()? else {
+ unreachable!("Only call this if the next event is a start event")
+ };
+ let name = start.name();
+ self.read_to_end(name)
+ }
}
impl<'de> Deserializer<'de, SliceReader<'de>> {
@@ -2783,7 +2880,7 @@ where
/// Create new deserializer that will borrow data from the specified string
/// and use specified entity resolver.
pub fn from_str_with_resolver(source: &'de str, entity_resolver: E) -> Self {
- let mut reader = Reader::from_str(source);
+ let mut reader = NsReader::from_str(source);
let config = reader.config_mut();
config.expand_empty_elements = true;
@@ -2826,7 +2923,7 @@ where
/// will borrow instead of copy. If you have `&[u8]` which is known to represent
/// UTF-8, you can decode it first before using [`from_str`].
pub fn with_resolver(reader: R, entity_resolver: E) -> Self {
- let mut reader = Reader::from_reader(reader);
+ let mut reader = NsReader::from_reader(reader);
let config = reader.config_mut();
config.expand_empty_elements = true;
@@ -2945,9 +3042,16 @@ where
where
V: Visitor<'de>,
{
- match self.peek()? {
+ // We cannot use result of `peek()` directly because of borrow checker
+ let _ = self.peek()?;
+ match self.last_peeked() {
DeEvent::Text(t) if t.is_empty() => visitor.visit_none(),
DeEvent::Eof => visitor.visit_none(),
+ // if the `xsi:nil` attribute is set to true we got a none value
+ DeEvent::Start(start) if self.reader.reader.has_nil_attr(&start) => {
+ self.skip_next_tree()?;
+ visitor.visit_none()
+ }
_ => visitor.visit_some(self),
}
}
@@ -3071,6 +3175,12 @@ pub trait XmlRead<'i> {
/// A copy of the reader's decoder used to decode strings.
fn decoder(&self) -> Decoder;
+
+ /// Checks if the `start` tag has a [`xsi:nil`] attribute. This method ignores
+ /// any errors in attributes.
+ ///
+ /// [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil
+ fn has_nil_attr(&self, start: &BytesStart) -> bool;
}
/// XML input source that reads from a std::io input stream.
@@ -3078,7 +3188,7 @@ pub trait XmlRead<'i> {
/// You cannot create it, it is created automatically when you call
/// [`Deserializer::from_reader`]
pub struct IoReader {
- reader: Reader,
+ reader: NsReader,
start_trimmer: StartTrimmer,
buf: Vec,
}
@@ -3091,7 +3201,7 @@ impl IoReader {
/// use serde::Deserialize;
/// use std::io::Cursor;
/// use quick_xml::de::Deserializer;
- /// use quick_xml::Reader;
+ /// use quick_xml::NsReader;
///
/// #[derive(Deserialize)]
/// struct SomeStruct {
@@ -3108,12 +3218,12 @@ impl IoReader {
/// let err = SomeStruct::deserialize(&mut de);
/// assert!(err.is_err());
///
- /// let reader: &Reader> = de.get_ref().get_ref();
+ /// let reader: &NsReader> = de.get_ref().get_ref();
///
/// assert_eq!(reader.error_position(), 28);
/// assert_eq!(reader.buffer_position(), 41);
/// ```
- pub const fn get_ref(&self) -> &Reader {
+ pub const fn get_ref(&self) -> &NsReader {
&self.reader
}
}
@@ -3140,6 +3250,10 @@ impl<'i, R: BufRead> XmlRead<'i> for IoReader {
fn decoder(&self) -> Decoder {
self.reader.decoder()
}
+
+ fn has_nil_attr(&self, start: &BytesStart) -> bool {
+ start.attributes().has_nil(&self.reader)
+ }
}
/// XML input source that reads from a slice of bytes and can borrow from it.
@@ -3147,7 +3261,7 @@ impl<'i, R: BufRead> XmlRead<'i> for IoReader {
/// You cannot create it, it is created automatically when you call
/// [`Deserializer::from_str`].
pub struct SliceReader<'de> {
- reader: Reader<&'de [u8]>,
+ reader: NsReader<&'de [u8]>,
start_trimmer: StartTrimmer,
}
@@ -3158,7 +3272,7 @@ impl<'de> SliceReader<'de> {
/// # use pretty_assertions::assert_eq;
/// use serde::Deserialize;
/// use quick_xml::de::Deserializer;
- /// use quick_xml::Reader;
+ /// use quick_xml::NsReader;
///
/// #[derive(Deserialize)]
/// struct SomeStruct {
@@ -3175,12 +3289,12 @@ impl<'de> SliceReader<'de> {
/// let err = SomeStruct::deserialize(&mut de);
/// assert!(err.is_err());
///
- /// let reader: &Reader<&[u8]> = de.get_ref().get_ref();
+ /// let reader: &NsReader<&[u8]> = de.get_ref().get_ref();
///
/// assert_eq!(reader.error_position(), 28);
/// assert_eq!(reader.buffer_position(), 41);
/// ```
- pub const fn get_ref(&self) -> &Reader<&'de [u8]> {
+ pub const fn get_ref(&self) -> &NsReader<&'de [u8]> {
&self.reader
}
}
@@ -3205,6 +3319,10 @@ impl<'de> XmlRead<'de> for SliceReader<'de> {
fn decoder(&self) -> Decoder {
self.reader.decoder()
}
+
+ fn has_nil_attr(&self, start: &BytesStart) -> bool {
+ start.attributes().has_nil(&self.reader)
+ }
}
#[cfg(test)]
@@ -3781,12 +3899,12 @@ mod tests {
"#;
let mut reader1 = IoReader {
- reader: Reader::from_reader(s.as_bytes()),
+ reader: NsReader::from_reader(s.as_bytes()),
start_trimmer: StartTrimmer::default(),
buf: Vec::new(),
};
let mut reader2 = SliceReader {
- reader: Reader::from_str(s),
+ reader: NsReader::from_str(s),
start_trimmer: StartTrimmer::default(),
};
@@ -3812,7 +3930,7 @@ mod tests {
"#;
let mut reader = SliceReader {
- reader: Reader::from_str(s),
+ reader: NsReader::from_str(s),
start_trimmer: StartTrimmer::default(),
};
diff --git a/src/events/attributes.rs b/src/events/attributes.rs
index dd35c4d5..c5be1b3c 100644
--- a/src/events/attributes.rs
+++ b/src/events/attributes.rs
@@ -5,8 +5,9 @@
use crate::encoding::Decoder;
use crate::errors::Result as XmlResult;
use crate::escape::{escape, resolve_predefined_entity, unescape_with};
-use crate::name::QName;
-use crate::utils::{is_whitespace, write_byte_string, write_cow_string, Bytes};
+use crate::name::{LocalName, Namespace, QName};
+use crate::reader::NsReader;
+use crate::utils::{is_whitespace, Bytes};
use std::fmt::{self, Debug, Display, Formatter};
use std::iter::FusedIterator;
@@ -96,15 +97,50 @@ impl<'a> Attribute<'a> {
Cow::Owned(s) => Ok(s.into()),
}
}
+
+ /// If attribute value [represents] valid boolean values, returns `Some`, otherwise returns `None`.
+ ///
+ /// The valid boolean representations are only `"true"`, `"false"`, `"1"`, and `"0"`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_assertions::assert_eq;
+ /// use quick_xml::events::attributes::Attribute;
+ ///
+ /// let attr = Attribute::from(("attr", "false"));
+ /// assert_eq!(attr.as_bool(), Some(false));
+ ///
+ /// let attr = Attribute::from(("attr", "0"));
+ /// assert_eq!(attr.as_bool(), Some(false));
+ ///
+ /// let attr = Attribute::from(("attr", "true"));
+ /// assert_eq!(attr.as_bool(), Some(true));
+ ///
+ /// let attr = Attribute::from(("attr", "1"));
+ /// assert_eq!(attr.as_bool(), Some(true));
+ ///
+ /// let attr = Attribute::from(("attr", "bot bool"));
+ /// assert_eq!(attr.as_bool(), None);
+ /// ```
+ ///
+ /// [represents]: https://www.w3.org/TR/xmlschema11-2/#boolean
+ #[inline]
+ pub fn as_bool(&self) -> Option {
+ match self.value.as_ref() {
+ b"1" | b"true" => Some(true),
+ b"0" | b"false" => Some(false),
+ _ => None,
+ }
+ }
}
impl<'a> Debug for Attribute<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Attribute {{ key: ")?;
- write_byte_string(f, self.key.as_ref())?;
- write!(f, ", value: ")?;
- write_cow_string(f, &self.value)?;
- write!(f, " }}")
+ f.debug_struct("Attribute")
+ .field("key", &Bytes(self.key.as_ref()))
+ .field("value", &Bytes(&self.value))
+ .finish()
}
}
@@ -196,7 +232,7 @@ impl<'a> From> for Attribute<'a> {
/// The duplicate check can be turned off by calling [`with_checks(false)`].
///
/// [`with_checks(false)`]: Self::with_checks
-#[derive(Clone, Debug)]
+#[derive(Clone)]
pub struct Attributes<'a> {
/// Slice of `BytesStart` corresponding to attributes
bytes: &'a [u8],
@@ -234,6 +270,90 @@ impl<'a> Attributes<'a> {
self.state.check_duplicates = val;
self
}
+
+ /// Checks if the current tag has a [`xsi:nil`] attribute. This method ignores any errors in
+ /// attributes.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use pretty_assertions::assert_eq;
+ /// use quick_xml::events::Event;
+ /// use quick_xml::name::QName;
+ /// use quick_xml::reader::NsReader;
+ ///
+ /// let mut reader = NsReader::from_str("
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ");
+ /// reader.config_mut().trim_text(true);
+ ///
+ /// macro_rules! check {
+ /// ($reader:expr, $name:literal, $value:literal) => {
+ /// let event = match $reader.read_event().unwrap() {
+ /// Event::Empty(e) => e,
+ /// e => panic!("Unexpected event {:?}", e),
+ /// };
+ /// assert_eq!(
+ /// (event.name(), event.attributes().has_nil(&$reader)),
+ /// (QName($name.as_bytes()), $value),
+ /// );
+ /// };
+ /// }
+ ///
+ /// let root = match reader.read_event().unwrap() {
+ /// Event::Start(e) => e,
+ /// e => panic!("Unexpected event {:?}", e),
+ /// };
+ /// assert_eq!(root.attributes().has_nil(&reader), false);
+ ///
+ /// // definitely true
+ /// check!(reader, "true", true);
+ /// // definitely false
+ /// check!(reader, "false", false);
+ /// // absence of the attribute means that attribute is not set
+ /// check!(reader, "none", false);
+ /// // attribute not bound to the correct namespace
+ /// check!(reader, "non-xsi", false);
+ /// // attributes without prefix not bound to any namespace
+ /// check!(reader, "unbound-nil", false);
+ /// // prefix can be any while it is bound to the correct namespace
+ /// check!(reader, "another-xmlns", true);
+ /// ```
+ ///
+ /// [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil
+ pub fn has_nil(&mut self, reader: &NsReader) -> bool {
+ use crate::name::ResolveResult::*;
+
+ self.any(|attr| {
+ if let Ok(attr) = attr {
+ match reader.resolve_attribute(attr.key) {
+ (
+ Bound(Namespace(b"http://www.w3.org/2001/XMLSchema-instance")),
+ LocalName(b"nil"),
+ ) => attr.as_bool().unwrap_or_default(),
+ _ => false,
+ }
+ } else {
+ false
+ }
+ })
+ }
+}
+
+impl<'a> Debug for Attributes<'a> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("Attributes")
+ .field("bytes", &Bytes(&self.bytes))
+ .field("state", &self.state)
+ .finish()
+ }
}
impl<'a> Iterator for Attributes<'a> {
diff --git a/src/name.rs b/src/name.rs
index b7a8de16..6bed83a8 100644
--- a/src/name.rs
+++ b/src/name.rs
@@ -200,7 +200,7 @@ impl<'a> AsRef<[u8]> for QName<'a> {
/// [local (unqualified) name]: https://www.w3.org/TR/xml-names11/#dt-localname
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde-types", derive(serde::Deserialize, serde::Serialize))]
-pub struct LocalName<'a>(&'a [u8]);
+pub struct LocalName<'a>(pub(crate) &'a [u8]);
impl<'a> LocalName<'a> {
/// Converts this name to an internal slice representation.
#[inline(always)]
diff --git a/src/reader/async_tokio.rs b/src/reader/async_tokio.rs
index eaf56f26..c5e1eaaa 100644
--- a/src/reader/async_tokio.rs
+++ b/src/reader/async_tokio.rs
@@ -119,7 +119,8 @@ impl Reader {
mut buf: &'b mut Vec,
) -> Result> {
read_event_impl!(
- self, buf,
+ self,
+ buf,
TokioAdapter(&mut self.reader),
read_until_close_async,
await
@@ -181,7 +182,16 @@ impl Reader {
end: QName<'n>,
buf: &mut Vec,
) -> Result {
- Ok(read_to_end!(self, end, buf, read_event_into_async, { buf.clear(); }, await))
+ Ok(read_to_end!(
+ self,
+ end,
+ buf,
+ read_event_into_async,
+ {
+ buf.clear();
+ },
+ await
+ ))
}
/// Private function to read until `>` is found. This function expects that
@@ -410,7 +420,8 @@ mod test {
read_until_close_async,
TokioAdapter,
&mut Vec::new(),
- async, await
+ async,
+ await
);
#[test]
diff --git a/tests/serde-de-xsi.rs b/tests/serde-de-xsi.rs
new file mode 100644
index 00000000..4db9e605
--- /dev/null
+++ b/tests/serde-de-xsi.rs
@@ -0,0 +1,704 @@
+//! Tests for ensure behavior of `xsi:nil` handling.
+//!
+//! We want to threat element with `xsi:nil="true"` as `None` in optional contexts.
+use quick_xml::se::to_string;
+use quick_xml::DeError;
+
+use serde::{Deserialize, Serialize};
+
+mod serde_helpers;
+use serde_helpers::from_str;
+
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+struct Foo {
+ elem: String,
+}
+
+macro_rules! assert_error_matches {
+ ($res: expr, $err: pat) => {
+ assert!(
+ matches!($res, Err($err)),
+ concat!("Expected `", stringify!($err), "`, but got `{:?}`"),
+ $res
+ );
+ };
+}
+
+mod top_level_option {
+ use super::*;
+
+ mod empty {
+ use super::*;
+
+ /// Without `xsi:nil="true"` tags in optional contexts are always considered as having
+ /// `Some` value, but because we do not have `tag` element, deserialization failed
+ #[test]
+ fn none() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str::>(xml), DeError::Custom(_));
+ }
+
+ /// When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ /// so just `nil="true"` does not mean that `xsi:nil` is set
+ mod no_prefix {
+ use super::*;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+
+ /// Check canonical prefix
+ mod xsi {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+
+ /// Check other prefix to be sure that we not process only canonical prefixes
+ mod ns0 {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+ }
+
+ /// We have no place to store attribute of the element, so the behavior must be the same
+ /// as without attributes.
+ mod with_attr {
+ use super::*;
+
+ #[test]
+ fn none() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+
+ /// When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ /// so just `nil="true"` does not mean that `xsi:nil` is set
+ mod no_prefix {
+ use super::*;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+
+ /// Check canonical prefix
+ mod xsi {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+
+ /// Check other prefix to be sure that we not process only canonical prefixes
+ mod ns0 {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+ assert_error_matches!(from_str:: >(xml), DeError::Custom(_));
+ }
+ }
+ }
+
+ mod with_element {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn none() {
+ let xml = r#"Foo "#;
+ assert_eq!(
+ from_str:: >(xml).unwrap(),
+ Some(Foo { elem: "Foo".into() })
+ );
+ }
+
+ /// When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ /// so just `nil="true"` does not mean that `xsi:nil` is set
+ mod no_prefix {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#"Foo "#;
+ assert_eq!(
+ from_str:: >(xml).unwrap(),
+ Some(Foo { elem: "Foo".into() })
+ );
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#"Foo "#;
+ assert_eq!(
+ from_str:: >(xml).unwrap(),
+ Some(Foo { elem: "Foo".into() })
+ );
+ }
+ }
+
+ /// Check canonical prefix
+ mod xsi {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#"Foo "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#"Foo "#;
+ assert_eq!(
+ from_str:: >(xml).unwrap(),
+ Some(Foo { elem: "Foo".into() })
+ );
+ }
+ }
+
+ /// Check other prefix to be sure that we not process only canonical prefixes
+ mod ns0 {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let xml = r#"Foo "#;
+ assert_eq!(from_str:: >(xml).unwrap(), None);
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#"Foo "#;
+ assert_eq!(
+ from_str:: >(xml).unwrap(),
+ Some(Foo { elem: "Foo".into() })
+ );
+ }
+ }
+ }
+}
+
+mod as_field {
+ use super::*;
+
+ /// According to the [specification], `xsi:nil` controls only ability to (not) have nested
+ /// elements, but it does not applied to attributes. Due to that we ensure, that attributes
+ /// are still can be accessed.
+ ///
+ /// [specification]: https://www.w3.org/TR/xmlschema11-1/#Instance_Document_Constructions
+ #[derive(Debug, Deserialize, PartialEq, Serialize)]
+ struct AnyName {
+ #[serde(rename = "@attr")]
+ attr: Option,
+
+ elem: Option,
+ }
+
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct Root {
+ foo: AnyName,
+ }
+
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct Bar {
+ foo: Option,
+ }
+
+ macro_rules! check {
+ (
+ $name:ident,
+ $true_xml:literal,
+ $false_xml:literal,
+ $se_xml:literal,
+ $attr:expr,
+ ) => {
+ mod $name {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let value = AnyName {
+ attr: $attr,
+ elem: None,
+ };
+
+ assert_eq!(to_string(&value).unwrap(), $se_xml);
+ assert_eq!(from_str::($true_xml).unwrap(), value);
+ }
+
+ #[test]
+ fn false_() {
+ let value = AnyName {
+ attr: $attr,
+ elem: None,
+ };
+
+ assert_eq!(to_string(&value).unwrap(), $se_xml);
+ assert_eq!(from_str::($false_xml).unwrap(), value);
+ }
+ }
+ };
+ }
+
+ mod empty {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ /// Without `xsi:nil="true"` tags in optional contexts are always considered as having
+ /// `Some` value, but because we do not have `tag` element, deserialization failed
+ #[test]
+ fn none() {
+ let value = AnyName {
+ attr: None,
+ elem: None,
+ };
+
+ assert_eq!(
+ to_string(&value).unwrap(),
+ r#" "#
+ );
+ assert_eq!(
+ from_str::(" ").unwrap(),
+ AnyName {
+ attr: None,
+ elem: None,
+ }
+ );
+ }
+
+ // When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ // so just `nil="true"` does not mean that `xsi:nil` is set. But because `AnyName` is empty
+ // there anyway nothing inside, so all fields will be set to `None`
+ check!(
+ no_prefix,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ None,
+ );
+
+ // Check canonical prefix
+ check!(
+ xsi,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ None,
+ );
+
+ // Check other prefix to be sure that we do not process only canonical prefixes
+ check!(
+ ns0,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ None,
+ );
+
+ mod nested {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn none() {
+ let xml =
+ r#" "#;
+
+ assert_error_matches!(from_str::(xml), DeError::Custom(_));
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: None,
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+
+ assert_eq!(from_str::(xml).unwrap(), Bar { foo: None });
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: None,
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+
+ assert_error_matches!(from_str::(xml), DeError::Custom(_));
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: None,
+ },
+ }
+ );
+ }
+ }
+ }
+
+ /// We have no place to store attribute of the element, so the behavior must be the same
+ /// as without attributes.
+ mod with_attr {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn none() {
+ let value = AnyName {
+ attr: Some("value".into()),
+ elem: None,
+ };
+
+ assert_eq!(
+ to_string(&value).unwrap(),
+ r#" "#
+ );
+ assert_eq!(
+ from_str::(r#" "#).unwrap(),
+ value
+ );
+ }
+
+ // When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ // so just `nil="true"` does not mean that `xsi:nil` is set. But because `AnyName` is empty
+ // there anyway nothing inside, so all element fields will be set to `None`
+ check!(
+ no_prefix,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ Some("value".into()),
+ );
+
+ // Check canonical prefix
+ check!(
+ xsi,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ Some("value".into()),
+ );
+
+ // Check other prefix to be sure that we do not process only canonical prefixes
+ check!(
+ ns0,
+ r#" "#,
+ r#" "#,
+ r#" "#,
+ Some("value".into()),
+ );
+
+ mod nested {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn none() {
+ let xml = r#" "#;
+
+ // Without `xsi:nil="true"` is mapped to `foo` field,
+ // but failed to deserialzie because of missing required tag
+ assert_error_matches!(from_str::(xml), DeError::Custom(_));
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: Some("value".into()),
+ elem: None,
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn true_() {
+ let xml = r#" "#;
+
+ assert_eq!(from_str::(xml).unwrap(), Bar { foo: None });
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: Some("value".into()),
+ elem: None,
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#" "#;
+
+ // With `xsi:nil="false"` is mapped to `foo` field,
+ // but failed to deserialzie because of missing required tag
+ assert_error_matches!(from_str::(xml), DeError::Custom(_));
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: Some("value".into()),
+ elem: None,
+ },
+ }
+ );
+ }
+ }
+ }
+
+ mod with_element {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ macro_rules! check {
+ (
+ $name:ident,
+
+ $de_true_xml:literal,
+ $se_true_xml:literal,
+
+ $de_false_xml:literal,
+ $se_false_xml:literal,
+ ) => {
+ mod $name {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let value = AnyName {
+ attr: None,
+ // Becase `nil=true``, element deserialized as `None`
+ elem: None,
+ };
+
+ assert_eq!(to_string(&value).unwrap(), $se_true_xml);
+ assert_eq!(from_str::($de_true_xml).unwrap(), value);
+ }
+
+ #[test]
+ fn false_() {
+ let value = AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ };
+
+ assert_eq!(to_string(&value).unwrap(), $se_false_xml);
+ assert_eq!(from_str::($de_false_xml).unwrap(), value);
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn none() {
+ let value = AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ };
+
+ assert_eq!(
+ to_string(&value).unwrap(),
+ r#"Foo "#
+ );
+ assert_eq!(
+ from_str::(r#"Foo "#).unwrap(),
+ value
+ );
+ }
+
+ /// When prefix is not defined, attributes not bound to any namespace (unlike elements),
+ /// so just `nil="true"` does not mean that `xsi:nil` is set
+ mod no_prefix {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn true_() {
+ let se_xml = r#"Foo "#;
+ let de_xml = r#"Foo "#;
+
+ let value = AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ };
+
+ assert_eq!(to_string(&value).unwrap(), se_xml);
+ assert_eq!(from_str::(de_xml).unwrap(), value);
+ }
+
+ #[test]
+ fn false_() {
+ let se_xml = r#"Foo "#;
+ let de_xml = r#"Foo "#;
+
+ let value = AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ };
+
+ assert_eq!(to_string(&value).unwrap(), se_xml);
+ assert_eq!(from_str::(de_xml).unwrap(), value);
+ }
+ }
+
+ // Check canonical prefix
+ check!(
+ xsi,
+ r#"Foo "#,
+ r#" "#,
+ r#"Foo "#,
+ r#"Foo "#,
+ );
+
+ // Check other prefix to be sure that we do not process only canonical prefixes
+ check!(
+ ns0,
+ r#"Foo "#,
+ r#" "#,
+ r#"Foo "#,
+ r#"Foo "#,
+ );
+
+ mod nested {
+ use super::*;
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn none() {
+ let xml = r#"Foo "#;
+
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Bar {
+ foo: Some(Foo { elem: "Foo".into() }),
+ }
+ );
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn true_() {
+ let xml = r#"Foo "#;
+
+ assert_eq!(from_str::(xml).unwrap(), Bar { foo: None });
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: None,
+ },
+ }
+ );
+ }
+
+ #[test]
+ fn false_() {
+ let xml = r#"Foo "#;
+
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Bar {
+ foo: Some(Foo { elem: "Foo".into() }),
+ }
+ );
+ assert_eq!(
+ from_str::(xml).unwrap(),
+ Root {
+ foo: AnyName {
+ attr: None,
+ elem: Some("Foo".into()),
+ },
+ }
+ );
+ }
+ }
+ }
+}
diff --git a/tests/serde-issues.rs b/tests/serde-issues.rs
index bac7d416..7826669d 100644
--- a/tests/serde-issues.rs
+++ b/tests/serde-issues.rs
@@ -218,6 +218,23 @@ fn issue429() {
);
}
+/// Regression test for https://github.com/tafia/quick-xml/issues/497.
+#[test]
+fn issue497() {
+ #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
+ struct Player {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ spawn_forced: Option,
+ }
+ let data = Player { spawn_forced: None };
+
+ let deserialize_buffer = to_string(&data).unwrap();
+ dbg!(&deserialize_buffer);
+
+ let p: Player = from_reader(deserialize_buffer.as_bytes()).unwrap();
+ assert_eq!(p, data);
+}
+
/// Regression test for https://github.com/tafia/quick-xml/issues/500.
#[test]
fn issue500() {