xmlwriter/
lib.rs

1/*!
2A simple, streaming, partially-validating XML writer that writes XML data into an internal buffer.
3
4## Features
5
6- A simple, bare-minimum, panic-based API.
7- Non-allocating API. All methods are accepting either `fmt::Display` or `fmt::Arguments`.
8- Nodes auto-closing.
9
10## Example
11
12```rust
13use xmlwriter::*;
14
15let opt = Options {
16    use_single_quote: true,
17    ..Options::default()
18};
19
20let mut w = XmlWriter::new(opt);
21w.start_element("svg");
22w.write_attribute("xmlns", "http://www.w3.org/2000/svg");
23w.write_attribute_fmt("viewBox", format_args!("{} {} {} {}", 0, 0, 128, 128));
24w.start_element("text");
25// We can write any object that implements `fmt::Display`.
26w.write_attribute("x", &10);
27w.write_attribute("y", &20);
28w.write_text_fmt(format_args!("length is {}", 5));
29
30assert_eq!(w.end_document(),
31"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
32    <text x='10' y='20'>
33        length is 5
34    </text>
35</svg>
36");
37```
38*/
39
40#![doc(html_root_url = "https://docs.rs/xmlwriter/0.1.0")]
41
42#![forbid(unsafe_code)]
43#![warn(missing_docs)]
44#![warn(missing_copy_implementations)]
45
46
47use std::fmt::{self, Display};
48use std::io::Write;
49use std::ops::Range;
50
51
52/// An XML node indention.
53#[derive(Clone, Copy, PartialEq, Debug)]
54pub enum Indent {
55    /// Disable indention and new lines.
56    None,
57    /// Indent with spaces. Preferred range is 0..4.
58    Spaces(u8),
59    /// Indent with tabs.
60    Tabs,
61}
62
63/// An XML writing options.
64#[derive(Clone, Copy, Debug)]
65pub struct Options {
66    /// Use single quote marks instead of double quote.
67    ///
68    /// # Examples
69    ///
70    /// Before:
71    ///
72    /// ```text
73    /// <rect fill="red"/>
74    /// ```
75    ///
76    /// After:
77    ///
78    /// ```text
79    /// <rect fill='red'/>
80    /// ```
81    ///
82    /// Default: disabled
83    pub use_single_quote: bool,
84
85    /// Set XML nodes indention.
86    ///
87    /// # Examples
88    ///
89    /// `Indent::None`
90    /// Before:
91    ///
92    /// ```text
93    /// <svg>
94    ///     <rect fill="red"/>
95    /// </svg>
96    /// ```
97    ///
98    /// After:
99    ///
100    /// ```text
101    /// <svg><rect fill="red"/></svg>
102    /// ```
103    ///
104    /// Default: 4 spaces
105    pub indent: Indent,
106
107    /// Set XML attributes indention.
108    ///
109    /// # Examples
110    ///
111    /// `Indent::Spaces(2)`
112    ///
113    /// Before:
114    ///
115    /// ```text
116    /// <svg>
117    ///     <rect fill="red" stroke="black"/>
118    /// </svg>
119    /// ```
120    ///
121    /// After:
122    ///
123    /// ```text
124    /// <svg>
125    ///     <rect
126    ///       fill="red"
127    ///       stroke="black"/>
128    /// </svg>
129    /// ```
130    ///
131    /// Default: `None`
132    pub attributes_indent: Indent,
133}
134
135impl Default for Options {
136    #[inline]
137    fn default() -> Self {
138        Options {
139            use_single_quote: false,
140            indent: Indent::Spaces(4),
141            attributes_indent: Indent::None,
142        }
143    }
144}
145
146
147#[derive(Clone, Copy, PartialEq, Debug)]
148enum State {
149    Empty,
150    Document,
151    Attributes,
152}
153
154struct DepthData {
155    range: Range<usize>,
156    has_children: bool,
157}
158
159
160/// An XML writer.
161pub struct XmlWriter {
162    buf: Vec<u8>,
163    state: State,
164    preserve_whitespaces: bool,
165    depth_stack: Vec<DepthData>,
166    opt: Options,
167}
168
169impl XmlWriter {
170    #[inline]
171    fn from_vec(buf: Vec<u8>, opt: Options) -> Self {
172        XmlWriter {
173            buf,
174            state: State::Empty,
175            preserve_whitespaces: false,
176            depth_stack: Vec::new(),
177            opt,
178        }
179    }
180
181    /// Creates a new `XmlWriter`.
182    #[inline]
183    pub fn new(opt: Options) -> Self {
184        Self::from_vec(Vec::new(), opt)
185    }
186
187    /// Creates a new `XmlWriter` with a specified capacity.
188    #[inline]
189    pub fn with_capacity(capacity: usize, opt: Options) -> Self {
190        Self::from_vec(Vec::with_capacity(capacity), opt)
191    }
192
193    /// Writes an XML declaration.
194    ///
195    /// `<?xml version="1.0" encoding="UTF-8" standalone="no"?>`
196    ///
197    /// # Panics
198    ///
199    /// - When called twice.
200    #[inline(never)]
201    pub fn write_declaration(&mut self) {
202        if self.state != State::Empty {
203            panic!("declaration was already written");
204        }
205
206        // Pretend that we are writing an element.
207        self.state = State::Attributes;
208
209        // <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
210        self.push_str("<?xml");
211        self.write_attribute("version", "1.0");
212        self.write_attribute("encoding", "UTF-8");
213        self.write_attribute("standalone", "no");
214        self.push_str("?>");
215
216        self.state = State::Document;
217    }
218
219    /// Writes a comment string.
220    pub fn write_comment(&mut self, text: &str) {
221        self.write_comment_fmt(format_args!("{}", text));
222    }
223
224    /// Writes a formatted comment.
225    #[inline(never)]
226    pub fn write_comment_fmt(&mut self, fmt: fmt::Arguments) {
227        if self.state == State::Attributes {
228            self.write_open_element();
229        }
230
231        if self.state != State::Empty {
232            self.write_new_line();
233        }
234
235        self.write_node_indent();
236
237        // <!--text-->
238        self.push_str("<!--");
239        self.buf.write_fmt(fmt).unwrap(); // TODO: check content
240        self.push_str("-->");
241
242        if self.state == State::Attributes {
243            self.depth_stack.push(DepthData {
244                range: 0..0,
245                has_children: false,
246            });
247        }
248
249        self.state = State::Document;
250    }
251
252    /// Starts writing a new element.
253    ///
254    /// This method writes only the `<tag-name` part.
255    #[inline(never)]
256    pub fn start_element(&mut self, name: &str) {
257        if self.state == State::Attributes {
258            self.write_open_element();
259        }
260
261        if self.state != State::Empty {
262            self.write_new_line();
263        }
264
265        if !self.preserve_whitespaces {
266            self.write_node_indent();
267        }
268
269        self.push_byte(b'<');
270        let start = self.buf.len();
271        self.push_str(name);
272
273        self.depth_stack.push(DepthData {
274            range: start..self.buf.len(),
275            has_children: false,
276        });
277
278        self.state = State::Attributes;
279    }
280
281    /// Writes an attribute.
282    ///
283    /// Quotes in the value will be escaped.
284    ///
285    /// # Panics
286    ///
287    /// - When called before `start_element()`.
288    /// - When called after `close_element()`.
289    ///
290    /// # Example
291    ///
292    /// ```
293    /// use xmlwriter::*;
294    ///
295    /// let mut w = XmlWriter::new(Options::default());
296    /// w.start_element("svg");
297    /// w.write_attribute("x", "5");
298    /// w.write_attribute("y", &5);
299    /// assert_eq!(w.end_document(), "<svg x=\"5\" y=\"5\"/>\n");
300    /// ```
301    pub fn write_attribute<V: Display + ?Sized>(&mut self, name: &str, value: &V) {
302        self.write_attribute_fmt(name, format_args!("{}", value));
303    }
304
305    /// Writes a formatted attribute value.
306    ///
307    /// Quotes in the value will be escaped.
308    ///
309    /// # Panics
310    ///
311    /// - When called before `start_element()`.
312    /// - When called after `close_element()`.
313    ///
314    /// # Example
315    ///
316    /// ```
317    /// use xmlwriter::*;
318    ///
319    /// let mut w = XmlWriter::new(Options::default());
320    /// w.start_element("rect");
321    /// w.write_attribute_fmt("fill", format_args!("url(#{})", "gradient"));
322    /// assert_eq!(w.end_document(), "<rect fill=\"url(#gradient)\"/>\n");
323    /// ```
324    #[inline(never)]
325    pub fn write_attribute_fmt(&mut self, name: &str, fmt: fmt::Arguments) {
326        if self.state != State::Attributes {
327            panic!("must be called after start_element()");
328        }
329
330        self.write_attribute_prefix(name);
331        let start = self.buf.len();
332        self.buf.write_fmt(fmt).unwrap();
333        self.escape_attribute_value(start);
334        self.write_quote();
335    }
336
337    /// Writes a raw attribute value.
338    ///
339    /// Closure provides a mutable reference to an internal buffer.
340    ///
341    /// **Warning:** this method is an escape hatch for cases when you need to write
342    /// a lot of data very fast.
343    ///
344    /// # Panics
345    ///
346    /// - When called before `start_element()`.
347    /// - When called after `close_element()`.
348    ///
349    /// # Example
350    ///
351    /// ```
352    /// use xmlwriter::*;
353    ///
354    /// let mut w = XmlWriter::new(Options::default());
355    /// w.start_element("path");
356    /// w.write_attribute_raw("d", |buf| buf.extend_from_slice(b"M 10 20 L 30 40"));
357    /// assert_eq!(w.end_document(), "<path d=\"M 10 20 L 30 40\"/>\n");
358    /// ```
359    #[inline(never)]
360    pub fn write_attribute_raw<F>(&mut self, name: &str, f: F)
361        where F: FnOnce(&mut Vec<u8>)
362    {
363        if self.state != State::Attributes {
364            panic!("must be called after start_element()");
365        }
366
367        self.write_attribute_prefix(name);
368        let start = self.buf.len();
369        f(&mut self.buf);
370        self.escape_attribute_value(start);
371        self.write_quote();
372    }
373
374    #[inline(never)]
375    fn write_attribute_prefix(&mut self, name: &str) {
376        if self.opt.attributes_indent == Indent::None {
377            self.push_byte(b' ');
378        } else {
379            self.push_byte(b'\n');
380
381            let depth = self.depth_stack.len();
382            if depth > 0 {
383                self.write_indent(depth - 1, self.opt.indent);
384            }
385
386            self.write_indent(1, self.opt.attributes_indent);
387        }
388
389        self.push_str(name);
390        self.push_byte(b'=');
391        self.write_quote();
392    }
393
394    /// Escapes the attribute value string.
395    ///
396    /// - " -> &quot;
397    /// - ' -> &apos;
398    #[inline(never)]
399    fn escape_attribute_value(&mut self, mut start: usize) {
400        let quote = if self.opt.use_single_quote { b'\'' } else { b'"' };
401        while let Some(idx) = self.buf[start..].iter().position(|c| *c == quote) {
402            let i = start + idx;
403            let s = if self.opt.use_single_quote { b"&apos;" } else { b"&quot;" };
404            self.buf.splice(i..i+1, s.iter().cloned());
405            start = i + 6;
406        }
407    }
408
409    /// Sets the preserve whitespaces flag.
410    ///
411    /// - If set, text nodes will be written as is.
412    /// - If not set, text nodes will be indented.
413    ///
414    /// Can be set at any moment.
415    ///
416    /// # Example
417    ///
418    /// ```
419    /// use xmlwriter::*;
420    ///
421    /// let mut w = XmlWriter::new(Options::default());
422    /// w.start_element("html");
423    /// w.start_element("p");
424    /// w.write_text("text");
425    /// w.end_element();
426    /// w.start_element("p");
427    /// w.set_preserve_whitespaces(true);
428    /// w.write_text("text");
429    /// w.end_element();
430    /// w.set_preserve_whitespaces(false);
431    /// assert_eq!(w.end_document(),
432    /// "<html>
433    ///     <p>
434    ///         text
435    ///     </p>
436    ///     <p>text</p>
437    /// </html>
438    /// ");
439    /// ```
440    pub fn set_preserve_whitespaces(&mut self, preserve: bool) {
441        self.preserve_whitespaces = preserve;
442    }
443
444    /// Writes a text node.
445    ///
446    /// See `write_text_fmt()` for details.
447    pub fn write_text(&mut self, text: &str) {
448        self.write_text_fmt(format_args!("{}", text));
449    }
450
451    /// Writes a formatted text node.
452    ///
453    /// `<` will be escaped.
454    ///
455    /// # Panics
456    ///
457    /// - When called not after `start_element()`.
458    #[inline(never)]
459    pub fn write_text_fmt(&mut self, fmt: fmt::Arguments) {
460        if self.state == State::Empty || self.depth_stack.is_empty() {
461            panic!("must be called after start_element()");
462        }
463
464        if self.state == State::Attributes {
465            self.write_open_element();
466        }
467
468        if self.state != State::Empty {
469            self.write_new_line();
470        }
471
472        self.write_node_indent();
473
474        let start = self.buf.len();
475        self.buf.write_fmt(fmt).unwrap();
476        self.escape_text(start);
477
478        if self.state == State::Attributes {
479            self.depth_stack.push(DepthData {
480                range: 0..0,
481                has_children: false,
482            });
483        }
484
485        self.state = State::Document;
486    }
487
488    fn escape_text(&mut self, mut start: usize) {
489        while let Some(idx) = self.buf[start..].iter().position(|c| *c == b'<') {
490            let i = start + idx;
491            self.buf.splice(i..i+1, b"&lt;".iter().cloned());
492            start = i + 4;
493        }
494    }
495
496    /// Closes an open element.
497    #[inline(never)]
498    pub fn end_element(&mut self) {
499        if let Some(depth) = self.depth_stack.pop() {
500            if depth.has_children {
501                if !self.preserve_whitespaces {
502                    self.write_new_line();
503                    self.write_node_indent();
504                }
505
506                self.push_str("</");
507
508                for i in depth.range {
509                    self.push_byte(self.buf[i]);
510                }
511
512                self.push_byte(b'>');
513            } else {
514                self.push_str("/>");
515            }
516        }
517
518        self.state = State::Document;
519    }
520
521    /// Closes all open elements and returns an internal XML buffer.
522    ///
523    /// # Example
524    ///
525    /// ```
526    /// use xmlwriter::*;
527    ///
528    /// let mut w = XmlWriter::new(Options::default());
529    /// w.start_element("svg");
530    /// w.start_element("g");
531    /// w.start_element("rect");
532    /// assert_eq!(w.end_document(),
533    /// "<svg>
534    ///     <g>
535    ///         <rect/>
536    ///     </g>
537    /// </svg>
538    /// ");
539    /// ```
540    pub fn end_document(mut self) -> String {
541        while !self.depth_stack.is_empty() {
542            self.end_element();
543        }
544
545        self.write_new_line();
546
547        // The only way it can fail is if an invalid data
548        // was written via `write_attribute_raw()`.
549        String::from_utf8(self.buf).unwrap()
550    }
551
552    #[inline]
553    fn push_byte(&mut self, c: u8) {
554        self.buf.push(c);
555    }
556
557    #[inline]
558    fn push_str(&mut self, text: &str) {
559        self.buf.extend_from_slice(text.as_bytes());
560    }
561
562    #[inline]
563    fn get_quote_char(&self) -> u8 {
564        if self.opt.use_single_quote { b'\'' } else { b'"' }
565    }
566
567    #[inline]
568    fn write_quote(&mut self) {
569        self.push_byte(self.get_quote_char());
570    }
571
572    fn write_open_element(&mut self) {
573        if let Some(depth) = self.depth_stack.last_mut() {
574            depth.has_children = true;
575            self.push_byte(b'>');
576
577            self.state = State::Document;
578        }
579    }
580
581    fn write_node_indent(&mut self) {
582        self.write_indent(self.depth_stack.len(), self.opt.indent);
583    }
584
585    fn write_indent(&mut self, depth: usize, indent: Indent) {
586        if indent == Indent::None || self.preserve_whitespaces {
587            return;
588        }
589
590        for _ in 0..depth {
591            match indent {
592                Indent::None => {}
593                Indent::Spaces(n) => {
594                    for _ in 0..n {
595                        self.push_byte(b' ');
596                    }
597                }
598                Indent::Tabs => self.push_byte(b'\t'),
599            }
600        }
601    }
602
603    fn write_new_line(&mut self) {
604        if self.opt.indent != Indent::None && !self.preserve_whitespaces {
605            self.push_byte(b'\n');
606        }
607    }
608}