1 | /*! |
2 | A 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 |
13 | use xmlwriter::*; |
14 | |
15 | let opt = Options { |
16 | use_single_quote: true, |
17 | ..Options::default() |
18 | }; |
19 | |
20 | let mut w = XmlWriter::new(opt); |
21 | w.start_element("svg" ); |
22 | w.write_attribute("xmlns" , "http://www.w3.org/2000/svg" ); |
23 | w.write_attribute_fmt("viewBox" , format_args!("{} {} {} {}" , 0, 0, 128, 128)); |
24 | w.start_element("text" ); |
25 | // We can write any object that implements `fmt::Display`. |
26 | w.write_attribute("x" , &10); |
27 | w.write_attribute("y" , &20); |
28 | w.write_text_fmt(format_args!("length is {}" , 5)); |
29 | |
30 | assert_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 | |
47 | use std::fmt::{self, Display}; |
48 | use std::io::Write; |
49 | use std::ops::Range; |
50 | |
51 | |
52 | /// An XML node indention. |
53 | #[derive (Clone, Copy, PartialEq, Debug)] |
54 | pub 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)] |
65 | pub 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 | |
135 | impl 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)] |
148 | enum State { |
149 | Empty, |
150 | Document, |
151 | Attributes, |
152 | } |
153 | |
154 | struct DepthData { |
155 | range: Range<usize>, |
156 | has_children: bool, |
157 | } |
158 | |
159 | |
160 | /// An XML writer. |
161 | pub struct XmlWriter { |
162 | buf: Vec<u8>, |
163 | state: State, |
164 | preserve_whitespaces: bool, |
165 | depth_stack: Vec<DepthData>, |
166 | opt: Options, |
167 | } |
168 | |
169 | impl 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 | /// - " -> " |
397 | /// - ' -> ' |
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"'" } else { b""" }; |
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"<" .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 | |