Skip to main content

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