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 /// - " -> "
410 /// - ' -> '
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"'" } else { b""" };
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"<".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}