|
19 | 19 | //! - [Optional attributes and elements](#optional-attributes-and-elements)
|
20 | 20 | //! - [Choices (`xs:choice` XML Schema type)](#choices-xschoice-xml-schema-type)
|
21 | 21 | //! - [Sequences (`xs:all` and `xs:sequence` XML Schema types)](#sequences-xsall-and-xssequence-xml-schema-types)
|
| 22 | +//! - [Mapping of `xsi:nil`](#mapping-of-xsinil) |
22 | 23 | //! - [Generate Rust types from XML](#generate-rust-types-from-xml)
|
23 | 24 | //! - [Composition Rules](#composition-rules)
|
24 | 25 | //! - [Enum Representations](#enum-representations)
|
|
413 | 414 | //! <any-tag optional=""/> <!-- Some("") -->
|
414 | 415 | //! <any-tag/> <!-- None -->
|
415 | 416 | //! ```
|
| 417 | +//! <div style="background:rgba(120,145,255,0.45);padding:0.75em;"> |
| 418 | +//! |
| 419 | +//! NOTE: The behaviour is not symmetric by default. `None` will be serialized as |
| 420 | +//! `optional=""`. This behaviour is consistent across serde crates. You should add |
| 421 | +//! `#[serde(skip_serializing_if = "Option::is_none")]` attribute to the field to |
| 422 | +//! skip `None`s. |
| 423 | +//! </div> |
416 | 424 | //! </td>
|
417 | 425 | //! </tr>
|
418 | 426 | //! <!-- 7 ===================================================================================== -->
|
|
454 | 462 | //! When the XML element is present, type `T` will be deserialized from an
|
455 | 463 | //! element (which is a string or a multi-mapping -- i.e. mapping which can have
|
456 | 464 | //! duplicated keys).
|
457 |
| -//! <div style="background:rgba(80, 240, 100, 0.20);padding:0.75em;"> |
| 465 | +//! <div style="background:rgba(120,145,255,0.45);padding:0.75em;"> |
458 | 466 | //!
|
459 |
| -//! Currently some edge cases exists described in the issue [#497]. |
| 467 | +//! NOTE: The behaviour is not symmetric by default. `None` will be serialized as |
| 468 | +//! `<optional/>`. This behaviour is consistent across serde crates. You should add |
| 469 | +//! `#[serde(skip_serializing_if = "Option::is_none")]` attribute to the field to |
| 470 | +//! skip `None`s. |
| 471 | +//! |
| 472 | +//! NOTE: Deserializer will automatically handle a [`xsi:nil`] attribute and set field to `None`. |
| 473 | +//! For more info see [Mapping of `xsi:nil`](#mapping-of-xsinil). |
460 | 474 | //! </div>
|
461 | 475 | //! </td>
|
462 | 476 | //! </tr>
|
|
1312 | 1326 | //! </table>
|
1313 | 1327 | //!
|
1314 | 1328 | //!
|
| 1329 | +//! Mapping of `xsi:nil` |
| 1330 | +//! ==================== |
| 1331 | +//! |
| 1332 | +//! quick-xml supports handling of [`xsi:nil`] special attribute. When field of optional |
| 1333 | +//! type is mapped to the XML element which have `xsi:nil="true"` set, or if that attribute |
| 1334 | +//! is placed on parent XML element, the deserializer will call [`Visitor::visit_none`] |
| 1335 | +//! and skip XML element corresponding to a field. |
| 1336 | +//! |
| 1337 | +//! Examples: |
| 1338 | +//! |
| 1339 | +//! ``` |
| 1340 | +//! # use pretty_assertions::assert_eq; |
| 1341 | +//! # use serde::Deserialize; |
| 1342 | +//! #[derive(Deserialize, Debug, PartialEq)] |
| 1343 | +//! struct TypeWithOptionalField { |
| 1344 | +//! element: Option<String>, |
| 1345 | +//! } |
| 1346 | +//! |
| 1347 | +//! assert_eq!( |
| 1348 | +//! TypeWithOptionalField { |
| 1349 | +//! element: None, |
| 1350 | +//! }, |
| 1351 | +//! quick_xml::de::from_str(" |
| 1352 | +//! <any-tag xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> |
| 1353 | +//! <element xsi:nil='true'>Content is skiped because of xsi:nil='true'</element> |
| 1354 | +//! </any-tag> |
| 1355 | +//! ").unwrap(), |
| 1356 | +//! ); |
| 1357 | +//! ``` |
| 1358 | +//! |
| 1359 | +//! You can capture attributes from the optional type, because ` xsi:nil="true"` elements can have |
| 1360 | +//! attributes: |
| 1361 | +//! ``` |
| 1362 | +//! # use pretty_assertions::assert_eq; |
| 1363 | +//! # use serde::Deserialize; |
| 1364 | +//! #[derive(Deserialize, Debug, PartialEq)] |
| 1365 | +//! struct TypeWithOptionalField { |
| 1366 | +//! #[serde(rename = "@attribute")] |
| 1367 | +//! attribute: usize, |
| 1368 | +//! |
| 1369 | +//! element: Option<String>, |
| 1370 | +//! non_optional: String, |
| 1371 | +//! } |
| 1372 | +//! |
| 1373 | +//! assert_eq!( |
| 1374 | +//! TypeWithOptionalField { |
| 1375 | +//! attribute: 42, |
| 1376 | +//! element: None, |
| 1377 | +//! non_optional: "Note, that non-optional fields will be deserialized as usual".to_string(), |
| 1378 | +//! }, |
| 1379 | +//! quick_xml::de::from_str(" |
| 1380 | +//! <any-tag attribute='42' xsi:nil='true' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> |
| 1381 | +//! <element>Content is skiped because of xsi:nil='true'</element> |
| 1382 | +//! <non_optional>Note, that non-optional fields will be deserialized as usual</non_optional> |
| 1383 | +//! </any-tag> |
| 1384 | +//! ").unwrap(), |
| 1385 | +//! ); |
| 1386 | +//! ``` |
| 1387 | +//! |
1315 | 1388 | //! Generate Rust types from XML
|
1316 | 1389 | //! ============================
|
1317 | 1390 | //!
|
|
1820 | 1893 | //! [`overlapped-lists`]: ../index.html#overlapped-lists
|
1821 | 1894 | //! [specification]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition
|
1822 | 1895 | //! [`deserialize_with`]: https://serde.rs/field-attrs.html#deserialize_with
|
1823 |
| -//! [#497]: https://github.com/tafia/quick-xml/issues/497 |
| 1896 | +//! [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil |
1824 | 1897 | //! [`Serializer::serialize_unit_variant`]: serde::Serializer::serialize_unit_variant
|
1825 | 1898 | //! [`Deserializer::deserialize_enum`]: serde::Deserializer::deserialize_enum
|
1826 | 1899 | //! [`SeError::Unsupported`]: crate::errors::serialize::SeError::Unsupported
|
@@ -2534,6 +2607,22 @@ where
|
2534 | 2607 | }
|
2535 | 2608 | }
|
2536 | 2609 |
|
| 2610 | + #[inline] |
| 2611 | + fn last_peeked(&self) -> &DeEvent<'de> { |
| 2612 | + #[cfg(feature = "overlapped-lists")] |
| 2613 | + { |
| 2614 | + self.read |
| 2615 | + .front() |
| 2616 | + .expect("`Deserializer::peek()` should be called") |
| 2617 | + } |
| 2618 | + #[cfg(not(feature = "overlapped-lists"))] |
| 2619 | + { |
| 2620 | + self.peek |
| 2621 | + .as_ref() |
| 2622 | + .expect("`Deserializer::peek()` should be called") |
| 2623 | + } |
| 2624 | + } |
| 2625 | + |
2537 | 2626 | fn next(&mut self) -> Result<DeEvent<'de>, DeError> {
|
2538 | 2627 | // Replay skipped or peeked events
|
2539 | 2628 | #[cfg(feature = "overlapped-lists")]
|
@@ -2764,6 +2853,14 @@ where
|
2764 | 2853 | }
|
2765 | 2854 | self.reader.read_to_end(name)
|
2766 | 2855 | }
|
| 2856 | + |
| 2857 | + fn skip_next_tree(&mut self) -> Result<(), DeError> { |
| 2858 | + let DeEvent::Start(start) = self.next()? else { |
| 2859 | + unreachable!("Only call this if the next event is a start event") |
| 2860 | + }; |
| 2861 | + let name = start.name(); |
| 2862 | + self.read_to_end(name) |
| 2863 | + } |
2767 | 2864 | }
|
2768 | 2865 |
|
2769 | 2866 | impl<'de> Deserializer<'de, SliceReader<'de>> {
|
@@ -2945,9 +3042,16 @@ where
|
2945 | 3042 | where
|
2946 | 3043 | V: Visitor<'de>,
|
2947 | 3044 | {
|
2948 |
| - match self.peek()? { |
| 3045 | + // We cannot use result of `peek()` directly because of borrow checker |
| 3046 | + let _ = self.peek()?; |
| 3047 | + match self.last_peeked() { |
2949 | 3048 | DeEvent::Text(t) if t.is_empty() => visitor.visit_none(),
|
2950 | 3049 | DeEvent::Eof => visitor.visit_none(),
|
| 3050 | + // if the `xsi:nil` attribute is set to true we got a none value |
| 3051 | + DeEvent::Start(start) if self.reader.reader.has_nil_attr(&start) => { |
| 3052 | + self.skip_next_tree()?; |
| 3053 | + visitor.visit_none() |
| 3054 | + } |
2951 | 3055 | _ => visitor.visit_some(self),
|
2952 | 3056 | }
|
2953 | 3057 | }
|
@@ -3071,6 +3175,12 @@ pub trait XmlRead<'i> {
|
3071 | 3175 |
|
3072 | 3176 | /// A copy of the reader's decoder used to decode strings.
|
3073 | 3177 | fn decoder(&self) -> Decoder;
|
| 3178 | + |
| 3179 | + /// Checks if the `start` tag has a [`xsi:nil`] attribute. This method ignores |
| 3180 | + /// any errors in attributes. |
| 3181 | + /// |
| 3182 | + /// [`xsi:nil`]: https://www.w3.org/TR/xmlschema-1/#xsi_nil |
| 3183 | + fn has_nil_attr(&self, start: &BytesStart) -> bool; |
3074 | 3184 | }
|
3075 | 3185 |
|
3076 | 3186 | /// XML input source that reads from a std::io input stream.
|
@@ -3140,6 +3250,10 @@ impl<'i, R: BufRead> XmlRead<'i> for IoReader<R> {
|
3140 | 3250 | fn decoder(&self) -> Decoder {
|
3141 | 3251 | self.reader.decoder()
|
3142 | 3252 | }
|
| 3253 | + |
| 3254 | + fn has_nil_attr(&self, start: &BytesStart) -> bool { |
| 3255 | + start.attributes().has_nil(&self.reader) |
| 3256 | + } |
3143 | 3257 | }
|
3144 | 3258 |
|
3145 | 3259 | /// XML input source that reads from a slice of bytes and can borrow from it.
|
@@ -3205,6 +3319,10 @@ impl<'de> XmlRead<'de> for SliceReader<'de> {
|
3205 | 3319 | fn decoder(&self) -> Decoder {
|
3206 | 3320 | self.reader.decoder()
|
3207 | 3321 | }
|
| 3322 | + |
| 3323 | + fn has_nil_attr(&self, start: &BytesStart) -> bool { |
| 3324 | + start.attributes().has_nil(&self.reader) |
| 3325 | + } |
3208 | 3326 | }
|
3209 | 3327 |
|
3210 | 3328 | #[cfg(test)]
|
|
0 commit comments