Skip to main content

Crate deser_xml

Crate deser_xml 

Source
Expand description

XML support for deser.

use deser::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct Link {
    #[deser(rename = "@href")]
    href: String,
    #[deser(rename = "@rel")]
    rel: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[deser(rename = "feed")]
struct Feed {
    title: String,
    link: Vec<Link>,
    count: u32,
}

let feed: Feed = deser_xml::from_str(r#"
    <feed>
      <title>Example</title>
      <link href="/a"/>
      <count>3</count>
      <link href="/b" rel="self"/>
    </feed>
"#).unwrap();
assert_eq!(feed.title, "Example");
assert_eq!(feed.link.len(), 2);
assert_eq!(feed.link[1].rel.as_deref(), Some("self"));

assert_eq!(
    deser_xml::to_string(&feed).unwrap(),
    "<feed><title>Example</title><link href=\"/a\"/>\
     <link href=\"/b\" rel=\"self\"/><count>3</count></feed>"
);

§Data Model

The document is the value of its root element (the name of the root element is not checked, see Root). An element is:

  • Its text if it has neither attributes nor child elements: <count>3</count> is "3" and <empty/> is "". Like the values of query strings, text is passed on as lexical atom that the type it’s deserialized into parses. An empty element is None for optionals of types that do not accept the empty text.
  • A map otherwise. Attributes are entries whose key has the attribute prefix (@href), child elements are entries with their name and text is an entry with the text key ($text). Whitespace between child elements is not text.
XMLdeser
<a>1</a>"1"
<a/>""
<a href="x"/>{"@href": "x"}
<a href="x">y</a>{"@href": "x", "$text": "y"}
<a><b>1</b><c>2</c></a>{"b": "1", "c": "2"}
<a><b>1</b><c/><b>2</b></a>{"b": "1", "c": "", "b": "2"}
<p>x <b>y</b> z</p>{"$text": "x ", "b": "y", "$text": " z"}

Maps are multimaps whose order is significant: an element can have more than one child with the same name. Fields and map values that are collections (like Vec<T>) collect all of them, also if other elements are between them. A single child element is a collection of one value and a missing one an empty collection. For other types the DuplicateKeys policy of the Context decides (repeated elements are an error by default).

Whether an element is text or a map depends on the document, the type it’s deserialized into decides what it wants (the text key is the key of the content):

  • An element with attributes for a type that expects text (like a u32) is its text, the attributes are skipped: <count unit="m">3</count> is 3 for a u32.
  • An element that is only text for a type that expects a map (like a struct) is a map with the text under the text key: <price>3</price> is {"$text": "3"} for a struct and an empty element an empty map.
#[derive(deser::Deserialize)]
struct Price {
    #[deser(rename = "@currency")]
    currency: Option<String>,
    #[deser(rename = "$text")]
    amount: f64,
}

#[derive(deser::Deserialize)]
struct Item {
    price: Vec<Price>,
    weight: f64,
}

let item: Item = deser_xml::from_str(r#"
    <item>
      <price currency="EUR">3</price>
      <price>4</price>
      <weight unit="kg">1.5</weight>
    </item>
"#).unwrap();
assert_eq!(item.price[0].currency.as_deref(), Some("EUR"));
assert_eq!(item.price[1].amount, 4.0);
assert_eq!(item.weight, 1.5);

The order of text and child elements is kept by Mixed, which is for mixed content (<p>x <b>y</b> z</p>): every text and child element is a value, typically of an enum whose variants are named after the elements:

use deser::Deserialize;
use deser_xml::Mixed;

#[derive(Debug, Deserialize, PartialEq)]
enum Inline {
    #[deser(rename = "$text")]
    Text(String),
    #[deser(rename = "b")]
    Bold(String),
}

#[derive(Deserialize)]
struct Paragraph {
    #[deser(rename = "@class")]
    class: Option<String>,
    #[deser(flatten)]
    content: Mixed<Inline>,
}

let p: Paragraph =
    deser_xml::from_str(r#"<p class="x">x <b>y</b> z</p>"#).unwrap();
assert_eq!(p.content.0, [
    Inline::Text("x ".into()),
    Inline::Bold("y".into()),
    Inline::Text(" z".into()),
]);

Enums work like elsewhere: an externally tagged enum is an element with one child (<shape><circle r="1"/></shape>), unit variants are text, internally tagged enums can use an attribute as tag (#[deser(tag = "@type")]).

Names are passed on as written (atom:link), namespace declarations (xmlns attributes) are not data. The name of the root element and the namespaces declared on it are not part of the value either, they are captured by Root. Values that capture event data (such as Recording) keep the root element and the namespace declarations of all elements, so they are written again where they were. Namespaces can be given prefixes that are used regardless of the prefixes of the document (see DeserializerConfig::namespaces) or be resolved into names like {http://www.w3.org/2005/Atom}title (see qname! and namespace!), which the serializer writes with prefixes. CDATA sections are text, the predefined entities (&amp;, …) and character references are resolved. Entities of document types are never expanded.

Serializing works the other way around (see SerializerConfig).

§Pretty Printing

By default the output is a single line. SerializerConfig::set_pretty (or set_indent) writes child elements on lines of their own, but only where the whitespace is not text: the text of elements is kept as it is and mixed content stays on a single line.

use deser_xml::{Indent, SerializerConfig};

#[derive(deser::Serialize)]
#[deser(rename = "item")]
struct Item {
    name: &'static str,
    tag: Vec<&'static str>,
}

const PRETTY: SerializerConfig =
    SerializerConfig::builder().pretty(Indent::Spaces(2)).build();
let item = Item { name: "x", tag: vec!["a", "b"] };
assert_eq!(
    PRETTY.to_string(&item).unwrap(),
    "<item>\n  <name>x</name>\n  <tag>a</tag>\n  <tag>b</tag>\n</item>"
);

§Streams

Documents are read from a Read with from_reader and written to a Write with to_writer. The configurations also create readers and writers of deser::io (DeserializerConfig::reader and SerializerConfig::writer), the stream serializer (Serializer) and deserializer (StreamDeserializer) work with other kinds of IO too (for instance async runtimes with deser-tokio). A stream holds a single document. The reader is read to the end before the document is parsed. The output is written while the value is serialized: an element is only held back until no more attributes can come for it, which for structs is known from their fields and for maps is their end.

#[derive(deser::Serialize, deser::Deserialize)]
#[deser(rename = "feed")]
struct Feed {
    entry: Vec<String>,
}

let feed = Feed { entry: (0..100).map(|x| x.to_string()).collect() };
let mut out = Vec::new();
deser_xml::to_writer(&mut out, &feed).unwrap();
let read: Feed = deser_xml::from_reader(&out[..]).unwrap();
assert_eq!(read.entry.len(), 100);

§Features

  • io (enabled by default): reading and writing streams of the standard library, see streams.
  • speedups (enabled by default): has no effect yet, it exists so that all formats have it.

§Limitations

This crate is an early version. The input has to be UTF-8.

Macros§

namespace
Defines a macro that writes names in a namespace.
prefixes
Writes the prefixes of namespaces defined by namespace!.
qname
Writes a name in a namespace as {uri}local.

Structs§

Deserializer
Deserializes XML documents.
DeserializerConfig
Configures how XML documents are deserialized.
DeserializerConfigBuilder
Builds a DeserializerConfig.
KeepWhitespace
Mixed keeps text that is only whitespace (the default).
Mixed
The content of an element in order: its text and child elements.
Root
A document: the value of the root element with its name and the namespaces declared on it.
Serializer
Serializes values to XML.
SerializerConfig
Configures how values are serialized to XML.
SerializerConfigBuilder
Builds a SerializerConfig.
SkipWhitespace
Mixed leaves out text that is only whitespace.
StreamDeserializer
Reads an XML document from a stream (see deser::stream).

Enums§

Indent
How the output is indented.

Traits§

Whitespace
What Mixed does with text that is only whitespace.

Functions§

from_reader
Deserializes a document from a reader.
from_slice
Deserializes a value from UTF-8 encoded XML.
from_str
Deserializes a value from an XML string.
to_string
Serializes a value to XML with the default configuration.
to_writer
Serializes a value as XML document to a writer.