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}
609