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