Skip to main content

deser_xml/
lib.rs

1//! XML support for deser.
2//!
3//! ```rust
4//! use deser::{Deserialize, Serialize};
5//!
6//! #[derive(Debug, Deserialize, Serialize, PartialEq)]
7//! struct Link {
8//!     #[deser(rename = "@href")]
9//!     href: String,
10//!     #[deser(rename = "@rel")]
11//!     rel: Option<String>,
12//! }
13//!
14//! #[derive(Debug, Deserialize, Serialize, PartialEq)]
15//! #[deser(rename = "feed")]
16//! struct Feed {
17//!     title: String,
18//!     link: Vec<Link>,
19//!     count: u32,
20//! }
21//!
22//! let feed: Feed = deser_xml::from_str(r#"
23//!     <feed>
24//!       <title>Example</title>
25//!       <link href="/a"/>
26//!       <count>3</count>
27//!       <link href="/b" rel="self"/>
28//!     </feed>
29//! "#).unwrap();
30//! assert_eq!(feed.title, "Example");
31//! assert_eq!(feed.link.len(), 2);
32//! assert_eq!(feed.link[1].rel.as_deref(), Some("self"));
33//!
34//! assert_eq!(
35//!     deser_xml::to_string(&feed).unwrap(),
36//!     "<feed><title>Example</title><link href=\"/a\"/>\
37//!      <link href=\"/b\" rel=\"self\"/><count>3</count></feed>"
38//! );
39//! ```
40//!
41//! # Data Model
42//!
43//! The document is the value of its root element (the name of the root
44//! element is not checked, see [`Root`]).  An element is:
45//!
46//! * **Its text** if it has neither attributes nor child elements:
47//!   `<count>3</count>` is `"3"` and `<empty/>` is `""`.  Like the values of
48//!   query strings, text is passed on as
49//!   [lexical atom](deser_core::Atom::Lexical) that the type it's
50//!   deserialized into parses.  An empty element is `None` for optionals of
51//!   types that do not accept the empty text.
52//! * **A map** otherwise.  Attributes are entries whose key has the
53//!   [attribute prefix](DeserializerConfig::attribute_prefix) (`@href`),
54//!   child elements are entries with their name and text is an entry with
55//!   the [text key](DeserializerConfig::text_key) (`$text`).  Whitespace
56//!   between child elements is not text.
57//!
58//! | XML                                   | deser                                   |
59//! |---------------------------------------|-----------------------------------------|
60//! | `<a>1</a>`                            | `"1"`                                   |
61//! | `<a/>`                                | `""`                                    |
62//! | `<a href="x"/>`                       | `{"@href": "x"}`                        |
63//! | `<a href="x">y</a>`                   | `{"@href": "x", "$text": "y"}`          |
64//! | `<a><b>1</b><c>2</c></a>`             | `{"b": "1", "c": "2"}`                  |
65//! | `<a><b>1</b><c/><b>2</b></a>`         | `{"b": "1", "c": "", "b": "2"}`         |
66//! | `<p>x <b>y</b> z</p>`                 | `{"$text": "x ", "b": "y", "$text": " z"}` |
67//!
68//! Maps are [multimaps](deser_core::ContainerShape::with_multimap) whose
69//! order is [significant](deser_core::Order::Significant): an element can
70//! have more than one child with the same name.  Fields and map values
71//! that are collections (like `Vec<T>`) collect all of them, also if other
72//! elements are between them.  A single child element is a collection of
73//! one value and a missing one an empty collection.  For other types
74//! [`DeserializerConfig::duplicate_keys`] decides.
75//!
76//! Whether an element is text or a map depends on the document, the type
77//! it's deserialized into decides what it wants (the text key is the
78//! [key of the content](deser_core::de::ContentKey)):
79//!
80//! * An element with attributes for a type that expects text (like a
81//!   `u32`) is its text, the attributes are skipped:
82//!   `<count unit="m">3</count>` is `3` for a `u32`.
83//! * An element that is only text for a type that expects a map (like a
84//!   struct) is a map with the text under the text key: `<price>3</price>`
85//!   is `{"$text": "3"}` for a struct and an empty element an empty map.
86//!
87//! ```rust
88//! #[derive(deser::Deserialize)]
89//! struct Price {
90//!     #[deser(rename = "@currency")]
91//!     currency: Option<String>,
92//!     #[deser(rename = "$text")]
93//!     amount: f64,
94//! }
95//!
96//! #[derive(deser::Deserialize)]
97//! struct Item {
98//!     price: Vec<Price>,
99//!     weight: f64,
100//! }
101//!
102//! let item: Item = deser_xml::from_str(r#"
103//!     <item>
104//!       <price currency="EUR">3</price>
105//!       <price>4</price>
106//!       <weight unit="kg">1.5</weight>
107//!     </item>
108//! "#).unwrap();
109//! assert_eq!(item.price[0].currency.as_deref(), Some("EUR"));
110//! assert_eq!(item.price[1].amount, 4.0);
111//! assert_eq!(item.weight, 1.5);
112//! ```
113//!
114//! The order of text and child elements is kept by [`Mixed`], which is
115//! for mixed content (`<p>x <b>y</b> z</p>`): every text and child
116//! element is a value, typically of an enum whose variants are named after
117//! the elements:
118//!
119//! ```rust
120//! use deser::Deserialize;
121//! use deser_xml::Mixed;
122//!
123//! #[derive(Debug, Deserialize, PartialEq)]
124//! enum Inline {
125//!     #[deser(rename = "$text")]
126//!     Text(String),
127//!     #[deser(rename = "b")]
128//!     Bold(String),
129//! }
130//!
131//! #[derive(Deserialize)]
132//! struct Paragraph {
133//!     #[deser(rename = "@class")]
134//!     class: Option<String>,
135//!     #[deser(flatten)]
136//!     content: Mixed<Inline>,
137//! }
138//!
139//! let p: Paragraph =
140//!     deser_xml::from_str(r#"<p class="x">x <b>y</b> z</p>"#).unwrap();
141//! assert_eq!(p.content.0, [
142//!     Inline::Text("x ".into()),
143//!     Inline::Bold("y".into()),
144//!     Inline::Text(" z".into()),
145//! ]);
146//! ```
147//!
148//! Enums work like elsewhere: an externally tagged enum is an element with
149//! one child (`<shape><circle r="1"/></shape>`), unit variants are text,
150//! internally tagged enums can use an attribute as tag
151//! (`#[deser(tag = "@type")]`).
152//!
153//! Names are passed on as written (`atom:link`), namespace declarations
154//! (`xmlns` attributes) are not data.  The name of the root element and
155//! the namespaces declared on it are not part of the value either, they are
156//! captured by [`Root`].  Values that capture event data (such as
157//! [`Recording`](deser_core::de::Recording)) keep the root element and the
158//! namespace declarations of all elements, so they are written again where
159//! they were.  Namespaces can be given prefixes
160//! that are used regardless of the prefixes of the document (see
161//! [`DeserializerConfig::namespaces`]) or be
162//! [resolved](DeserializerConfig::resolve_namespaces) into names like
163//! `{http://www.w3.org/2005/Atom}title` (see [`qname!`] and
164//! [`namespace!`]), which the serializer writes with prefixes.
165//! CDATA sections are text, the
166//! predefined entities (`&amp;`, ...) and character references are
167//! resolved.  Entities of document types are never expanded.
168//!
169//! Serializing works the other way around (see [`SerializerConfig`]).
170//!
171//! # Pretty Printing
172//!
173//! By default the output is a single line.  [`SerializerConfig::pretty`]
174//! (or [`indent`](SerializerConfig::indent)) writes child elements on
175//! lines of their own, but only where the whitespace is not text: the text
176//! of elements is kept as it is and mixed content stays on a single line.
177//!
178//! ```rust
179//! use deser_xml::{Indent, SerializerConfig};
180//!
181//! #[derive(deser::Serialize)]
182//! #[deser(rename = "item")]
183//! struct Item {
184//!     name: &'static str,
185//!     tag: Vec<&'static str>,
186//! }
187//!
188//! const PRETTY: SerializerConfig =
189//!     SerializerConfig::new().pretty(Indent::Spaces(2));
190//! let item = Item { name: "x", tag: vec!["a", "b"] };
191//! assert_eq!(
192//!     PRETTY.to_string(&item).unwrap(),
193//!     "<item>\n  <name>x</name>\n  <tag>a</tag>\n  <tag>b</tag>\n</item>"
194//! );
195//! ```
196//!
197//! # Streams
198//!
199//! Documents are read from a [`Read`](std::io::Read) with
200//! [`from_reader`] and written to a [`Write`](std::io::Write) with
201//! [`to_writer`].  The configurations also create readers and writers of
202//! [`deser::io`](deser_core::io) ([`DeserializerConfig::reader`] and
203//! [`SerializerConfig::writer`]), the stream serializer ([`Serializer`])
204//! and deserializer ([`StreamDeserializer`]) work with other kinds of IO
205//! too (for instance async runtimes with `deser-tokio`).  A stream holds a
206//! single document.  The reader is read to
207//! the end before the document is parsed.  The output is written while the
208//! value is serialized: an element is only held back until no more
209//! attributes can come for it, which for structs is known from their fields
210//! and for maps is their end.
211//!
212//! ```rust
213//! # #[cfg(feature = "io")] {
214//! #[derive(deser::Serialize, deser::Deserialize)]
215//! #[deser(rename = "feed")]
216//! struct Feed {
217//!     entry: Vec<String>,
218//! }
219//!
220//! let feed = Feed { entry: (0..100).map(|x| x.to_string()).collect() };
221//! let mut out = Vec::new();
222//! deser_xml::to_writer(&mut out, &feed).unwrap();
223//! let read: Feed = deser_xml::from_reader(&out[..]).unwrap();
224//! assert_eq!(read.entry.len(), 100);
225//! # }
226//! ```
227//!
228//! # Features
229//!
230//! * `io` (enabled by default): reading and writing streams of the
231//!   standard library, see [streams](#streams).
232//!
233//! # Limitations
234//!
235//! This crate is an early version.  The input has to be UTF-8.
236#![doc(html_logo_url = "https://raw.githubusercontent.com/mitsuhiko/deser/main/artwork/logo.svg")]
237#![deny(missing_docs)]
238
239mod de;
240mod mixed;
241mod root;
242mod ser;
243mod stream;
244
245pub use self::de::{Deserializer, DeserializerConfig, from_slice, from_str};
246pub use self::mixed::{KeepWhitespace, Mixed, SkipWhitespace, Whitespace};
247pub use self::root::Root;
248#[cfg(feature = "io")]
249pub use self::ser::to_writer;
250pub use self::ser::{Indent, Serializer, SerializerConfig, to_string};
251pub use self::stream::StreamDeserializer;
252#[cfg(feature = "io")]
253pub use self::stream::from_reader;
254
255/// Writes a name in a namespace as `{uri}local`.
256///
257/// This is the notation for names in namespaces (see
258/// [`DeserializerConfig::resolve_namespaces`]).  With `@` in front it's
259/// the name of an attribute with the default
260/// [attribute prefix](DeserializerConfig::attribute_prefix).  The name is
261/// a literal, so it can be used for `rename`:
262///
263/// ```
264/// use deser_xml::qname;
265///
266/// #[derive(deser::Deserialize)]
267/// struct Link {
268///     #[deser(rename = qname!(@ "http://www.w3.org/1999/xlink", "href"))]
269///     href: String,
270/// }
271///
272/// assert_eq!(qname!("urn:x", "a"), "{urn:x}a");
273/// assert_eq!(qname!(@ "urn:x", "a"), "@{urn:x}a");
274/// ```
275#[macro_export]
276macro_rules! qname {
277    (@ $uri:literal, $local:literal) => {
278        concat!("@{", $uri, "}", $local)
279    };
280    ($uri:literal, $local:literal) => {
281        concat!("{", $uri, "}", $local)
282    };
283}
284
285/// Defines a macro that writes names in a namespace.
286///
287/// `namespace!(atom = "http://www.w3.org/2005/Atom")` defines `atom!` so
288/// that `atom!("title")` is [`qname!("http://www.w3.org/2005/Atom",
289/// "title")`](qname), `atom!(@ "href")` the attribute and `atom!()` the
290/// URI.  The names of the macros are the prefixes of the namespaces in
291/// [`prefixes!`].  Like all macros defined by macros, they can be used
292/// after the invocation in the same module and its children.
293///
294/// ```
295/// deser_xml::namespace!(atom = "http://www.w3.org/2005/Atom");
296///
297/// #[derive(deser::Deserialize)]
298/// struct Entry {
299///     #[deser(rename = atom!("title"))]
300///     title: String,
301///     #[deser(rename = atom!(@ "lang"))]
302///     lang: Option<String>,
303/// }
304///
305/// assert_eq!(atom!("title"), "{http://www.w3.org/2005/Atom}title");
306/// assert_eq!(atom!(), "http://www.w3.org/2005/Atom");
307/// ```
308#[macro_export]
309macro_rules! namespace {
310    ($($name:ident = $uri:literal),+ $(,)?) => {
311        $($crate::__namespace!($name, $uri, $);)+
312    };
313}
314
315#[doc(hidden)]
316#[macro_export]
317// rustfmt indents the inner macro further on every run
318#[rustfmt::skip]
319macro_rules! __namespace {
320    ($name:ident, $uri:literal, $d:tt) => {
321        #[allow(unused_macros)]
322        macro_rules! $name {
323            () => {
324                $uri
325            };
326            (@ $d local:literal) => {
327                $crate::qname!(@ $uri, $d local)
328            };
329            ($d local:literal) => {
330                $crate::qname!($uri, $d local)
331            };
332        }
333    };
334}
335
336/// Writes the prefixes of namespaces defined by [`namespace!`].
337///
338/// Every namespace has the name of its macro as prefix unless another one
339/// is given with `as`.  The result is the table for
340/// [`SerializerConfig::namespaces`] and
341/// [`DeserializerConfig::namespaces`]:
342///
343/// ```
344/// use deser_xml::{DeserializerConfig, SerializerConfig, prefixes};
345///
346/// deser_xml::namespace!(
347///     atom = "http://www.w3.org/2005/Atom",
348///     dc = "http://purl.org/dc/elements/1.1/",
349///     xlink = "http://www.w3.org/1999/xlink",
350/// );
351///
352/// const PREFIXES: &[(&str, &str)] =
353///     prefixes![atom as "", dc, xlink as "xl"];
354/// assert_eq!(PREFIXES, [
355///     ("", "http://www.w3.org/2005/Atom"),
356///     ("dc", "http://purl.org/dc/elements/1.1/"),
357///     ("xl", "http://www.w3.org/1999/xlink"),
358/// ]);
359///
360/// const WRITE: SerializerConfig =
361///     SerializerConfig::new().namespaces(PREFIXES);
362/// const READ: DeserializerConfig =
363///     DeserializerConfig::new().namespaces(PREFIXES);
364/// ```
365#[macro_export]
366macro_rules! prefixes {
367    ($($name:ident $(as $prefix:literal)?),* $(,)?) => {
368        &[$(($crate::__prefix!($name $(, $prefix)?), $name!())),*]
369    };
370}
371
372#[doc(hidden)]
373#[macro_export]
374macro_rules! __prefix {
375    ($name:ident) => {
376        stringify!($name)
377    };
378    ($name:ident, $prefix:literal) => {
379        $prefix
380    };
381}
382
383/// The names of the special keys and the prefixes of namespaces.
384///
385/// The deserializer publishes them in the state for [`Mixed`].
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub(crate) struct Names {
388    pub(crate) attribute_prefix: &'static str,
389    pub(crate) text_key: &'static str,
390    pub(crate) namespaces: &'static [(&'static str, &'static str)],
391}
392
393impl Names {
394    pub(crate) const fn new() -> Names {
395        Names {
396            attribute_prefix: "@",
397            text_key: "$text",
398            namespaces: &[],
399        }
400    }
401}
402
403impl Default for Names {
404    fn default() -> Names {
405        Names::new()
406    }
407}