/rust/registry/src/index.crates.io-1949cf8c6b5b557f/quick-xml-0.29.0/src/writer.rs
Line | Count | Source |
1 | | //! Contains high-level interface for an events-based XML emitter. |
2 | | |
3 | | use std::io::Write; |
4 | | |
5 | | use crate::encoding::UTF8_BOM; |
6 | | use crate::errors::Result; |
7 | | use crate::events::{attributes::Attribute, BytesCData, BytesStart, BytesText, Event}; |
8 | | |
9 | | #[cfg(feature = "async-tokio")] |
10 | | mod async_tokio; |
11 | | |
12 | | /// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] or [`tokio::io::AsyncWrite`] implementor. |
13 | | /// |
14 | | /// # Examples |
15 | | /// |
16 | | /// ``` |
17 | | /// # use pretty_assertions::assert_eq; |
18 | | /// use quick_xml::events::{Event, BytesEnd, BytesStart}; |
19 | | /// use quick_xml::reader::Reader; |
20 | | /// use quick_xml::writer::Writer; |
21 | | /// use std::io::Cursor; |
22 | | /// |
23 | | /// let xml = r#"<this_tag k1="v1" k2="v2"><child>text</child></this_tag>"#; |
24 | | /// let mut reader = Reader::from_str(xml); |
25 | | /// reader.trim_text(true); |
26 | | /// let mut writer = Writer::new(Cursor::new(Vec::new())); |
27 | | /// loop { |
28 | | /// match reader.read_event() { |
29 | | /// Ok(Event::Start(e)) if e.name().as_ref() == b"this_tag" => { |
30 | | /// |
31 | | /// // crates a new element ... alternatively we could reuse `e` by calling |
32 | | /// // `e.into_owned()` |
33 | | /// let mut elem = BytesStart::new("my_elem"); |
34 | | /// |
35 | | /// // collect existing attributes |
36 | | /// elem.extend_attributes(e.attributes().map(|attr| attr.unwrap())); |
37 | | /// |
38 | | /// // copy existing attributes, adds a new my-key="some value" attribute |
39 | | /// elem.push_attribute(("my-key", "some value")); |
40 | | /// |
41 | | /// // writes the event to the writer |
42 | | /// assert!(writer.write_event(Event::Start(elem)).is_ok()); |
43 | | /// }, |
44 | | /// Ok(Event::End(e)) if e.name().as_ref() == b"this_tag" => { |
45 | | /// assert!(writer.write_event(Event::End(BytesEnd::new("my_elem"))).is_ok()); |
46 | | /// }, |
47 | | /// Ok(Event::Eof) => break, |
48 | | /// // we can either move or borrow the event to write, depending on your use-case |
49 | | /// Ok(e) => assert!(writer.write_event(e).is_ok()), |
50 | | /// Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), |
51 | | /// } |
52 | | /// } |
53 | | /// |
54 | | /// let result = writer.into_inner().into_inner(); |
55 | | /// let expected = r#"<my_elem k1="v1" k2="v2" my-key="some value"><child>text</child></my_elem>"#; |
56 | | /// assert_eq!(result, expected.as_bytes()); |
57 | | /// ``` |
58 | | #[derive(Clone)] |
59 | | pub struct Writer<W> { |
60 | | /// underlying writer |
61 | | writer: W, |
62 | | indent: Option<Indentation>, |
63 | | } |
64 | | |
65 | | impl<W> Writer<W> { |
66 | | /// Creates a `Writer` from a generic writer. |
67 | 0 | pub fn new(inner: W) -> Writer<W> { |
68 | 0 | Writer { |
69 | 0 | writer: inner, |
70 | 0 | indent: None, |
71 | 0 | } |
72 | 0 | } |
73 | | |
74 | | /// Creates a `Writer` with configured indents from a generic writer. |
75 | 0 | pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer<W> { |
76 | 0 | Writer { |
77 | 0 | writer: inner, |
78 | 0 | indent: Some(Indentation::new(indent_char, indent_size)), |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | /// Consumes this `Writer`, returning the underlying writer. |
83 | 0 | pub fn into_inner(self) -> W { |
84 | 0 | self.writer |
85 | 0 | } |
86 | | |
87 | | /// Get a mutable reference to the underlying writer. |
88 | 0 | pub fn get_mut(&mut self) -> &mut W { |
89 | 0 | &mut self.writer |
90 | 0 | } |
91 | | |
92 | | /// Get a reference to the underlying writer. |
93 | 0 | pub fn get_ref(&self) -> &W { |
94 | 0 | &self.writer |
95 | 0 | } |
96 | | } |
97 | | |
98 | | impl<W: Write> Writer<W> { |
99 | | /// Write a [Byte-Order-Mark] character to the document. |
100 | | /// |
101 | | /// # Example |
102 | | /// |
103 | | /// ```rust |
104 | | /// # use quick_xml::Result; |
105 | | /// # fn main() -> Result<()> { |
106 | | /// use quick_xml::events::{BytesStart, BytesText, Event}; |
107 | | /// use quick_xml::writer::Writer; |
108 | | /// use quick_xml::Error; |
109 | | /// use std::io::Cursor; |
110 | | /// |
111 | | /// let mut buffer = Vec::new(); |
112 | | /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
113 | | /// |
114 | | /// writer.write_bom()?; |
115 | | /// writer |
116 | | /// .create_element("empty") |
117 | | /// .with_attribute(("attr1", "value1")) |
118 | | /// .write_empty() |
119 | | /// .expect("failure"); |
120 | | /// |
121 | | /// assert_eq!( |
122 | | /// std::str::from_utf8(&buffer).unwrap(), |
123 | | /// "\u{FEFF}<empty attr1=\"value1\"/>" |
124 | | /// ); |
125 | | /// # Ok(()) |
126 | | /// # } |
127 | | /// ``` |
128 | | /// [Byte-Order-Mark]: https://unicode.org/faq/utf_bom.html#BOM |
129 | 0 | pub fn write_bom(&mut self) -> Result<()> { |
130 | 0 | self.write(UTF8_BOM) |
131 | 0 | } |
132 | | |
133 | | /// Writes the given event to the underlying writer. |
134 | 0 | pub fn write_event<'a, E: AsRef<Event<'a>>>(&mut self, event: E) -> Result<()> { |
135 | 0 | let mut next_should_line_break = true; |
136 | 0 | let result = match *event.as_ref() { |
137 | 0 | Event::Start(ref e) => { |
138 | 0 | let result = self.write_wrapped(b"<", e, b">"); |
139 | 0 | if let Some(i) = self.indent.as_mut() { |
140 | 0 | i.grow(); |
141 | 0 | } |
142 | 0 | result |
143 | | } |
144 | 0 | Event::End(ref e) => { |
145 | 0 | if let Some(i) = self.indent.as_mut() { |
146 | 0 | i.shrink(); |
147 | 0 | } |
148 | 0 | self.write_wrapped(b"</", e, b">") |
149 | | } |
150 | 0 | Event::Empty(ref e) => self.write_wrapped(b"<", e, b"/>"), |
151 | 0 | Event::Text(ref e) => { |
152 | 0 | next_should_line_break = false; |
153 | 0 | self.write(e) |
154 | | } |
155 | 0 | Event::Comment(ref e) => self.write_wrapped(b"<!--", e, b"-->"), |
156 | 0 | Event::CData(ref e) => { |
157 | 0 | next_should_line_break = false; |
158 | 0 | self.write(b"<![CDATA[")?; |
159 | 0 | self.write(e)?; |
160 | 0 | self.write(b"]]>") |
161 | | } |
162 | 0 | Event::Decl(ref e) => self.write_wrapped(b"<?", e, b"?>"), |
163 | 0 | Event::PI(ref e) => self.write_wrapped(b"<?", e, b"?>"), |
164 | 0 | Event::DocType(ref e) => self.write_wrapped(b"<!DOCTYPE ", e, b">"), |
165 | 0 | Event::Eof => Ok(()), |
166 | | }; |
167 | 0 | if let Some(i) = self.indent.as_mut() { |
168 | 0 | i.should_line_break = next_should_line_break; |
169 | 0 | } |
170 | 0 | result |
171 | 0 | } |
172 | | |
173 | | /// Writes bytes |
174 | | #[inline] |
175 | 0 | pub(crate) fn write(&mut self, value: &[u8]) -> Result<()> { |
176 | 0 | self.writer.write_all(value).map_err(Into::into) |
177 | 0 | } |
178 | | |
179 | | #[inline] |
180 | 0 | fn write_wrapped(&mut self, before: &[u8], value: &[u8], after: &[u8]) -> Result<()> { |
181 | 0 | if let Some(ref i) = self.indent { |
182 | 0 | if i.should_line_break { |
183 | 0 | self.writer.write_all(b"\n")?; |
184 | 0 | self.writer.write_all(i.current())?; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | self.write(before)?; |
188 | 0 | self.write(value)?; |
189 | 0 | self.write(after)?; |
190 | 0 | Ok(()) |
191 | 0 | } |
192 | | |
193 | | /// Manually write a newline and indentation at the proper level. |
194 | | /// |
195 | | /// This can be used when the heuristic to line break and indent after any |
196 | | /// [`Event`] apart from [`Text`] fails such as when a [`Start`] occurs directly |
197 | | /// after [`Text`]. |
198 | | /// |
199 | | /// This method will do nothing if `Writer` was not constructed with [`new_with_indent`]. |
200 | | /// |
201 | | /// [`Text`]: Event::Text |
202 | | /// [`Start`]: Event::Start |
203 | | /// [`new_with_indent`]: Self::new_with_indent |
204 | 0 | pub fn write_indent(&mut self) -> Result<()> { |
205 | 0 | if let Some(ref i) = self.indent { |
206 | 0 | self.writer.write_all(b"\n")?; |
207 | 0 | self.writer.write_all(i.current())?; |
208 | 0 | } |
209 | 0 | Ok(()) |
210 | 0 | } |
211 | | |
212 | | /// Provides a simple, high-level API for writing XML elements. |
213 | | /// |
214 | | /// Returns an [`ElementWriter`] that simplifies setting attributes and writing |
215 | | /// content inside the element. |
216 | | /// |
217 | | /// # Example |
218 | | /// |
219 | | /// ```rust |
220 | | /// # use quick_xml::Result; |
221 | | /// # fn main() -> Result<()> { |
222 | | /// use quick_xml::events::{BytesStart, BytesText, Event}; |
223 | | /// use quick_xml::writer::Writer; |
224 | | /// use quick_xml::Error; |
225 | | /// use std::io::Cursor; |
226 | | /// |
227 | | /// let mut writer = Writer::new(Cursor::new(Vec::new())); |
228 | | /// |
229 | | /// // writes <tag attr1="value1"/> |
230 | | /// writer.create_element("tag") |
231 | | /// .with_attribute(("attr1", "value1")) // chain `with_attribute()` calls to add many attributes |
232 | | /// .write_empty()?; |
233 | | /// |
234 | | /// // writes <tag attr1="value1" attr2="value2">with some text inside</tag> |
235 | | /// writer.create_element("tag") |
236 | | /// .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()) // or add attributes from an iterator |
237 | | /// .write_text_content(BytesText::new("with some text inside"))?; |
238 | | /// |
239 | | /// // writes <tag><fruit quantity="0">apple</fruit><fruit quantity="1">orange</fruit></tag> |
240 | | /// writer.create_element("tag") |
241 | | /// .write_inner_content(|writer| { |
242 | | /// let fruits = ["apple", "orange"]; |
243 | | /// for (quant, item) in fruits.iter().enumerate() { |
244 | | /// writer |
245 | | /// .create_element("fruit") |
246 | | /// .with_attribute(("quantity", quant.to_string().as_str())) |
247 | | /// .write_text_content(BytesText::new(item))?; |
248 | | /// } |
249 | | /// Ok(()) |
250 | | /// })?; |
251 | | /// # Ok(()) |
252 | | /// # } |
253 | | /// ``` |
254 | | #[must_use] |
255 | 0 | pub fn create_element<'a, N>(&'a mut self, name: &'a N) -> ElementWriter<W> |
256 | 0 | where |
257 | 0 | N: 'a + AsRef<str> + ?Sized, |
258 | | { |
259 | 0 | ElementWriter { |
260 | 0 | writer: self, |
261 | 0 | start_tag: BytesStart::new(name.as_ref()), |
262 | 0 | } |
263 | 0 | } |
264 | | } |
265 | | |
266 | | /// A struct to write an element. Contains methods to add attributes and inner |
267 | | /// elements to the element |
268 | | pub struct ElementWriter<'a, W: Write> { |
269 | | writer: &'a mut Writer<W>, |
270 | | start_tag: BytesStart<'a>, |
271 | | } |
272 | | |
273 | | impl<'a, W: Write> ElementWriter<'a, W> { |
274 | | /// Adds an attribute to this element. |
275 | 0 | pub fn with_attribute<'b, I>(mut self, attr: I) -> Self |
276 | 0 | where |
277 | 0 | I: Into<Attribute<'b>>, |
278 | | { |
279 | 0 | self.start_tag.push_attribute(attr); |
280 | 0 | self |
281 | 0 | } |
282 | | |
283 | | /// Add additional attributes to this element using an iterator. |
284 | | /// |
285 | | /// The yielded items must be convertible to [`Attribute`] using `Into`. |
286 | 0 | pub fn with_attributes<'b, I>(mut self, attributes: I) -> Self |
287 | 0 | where |
288 | 0 | I: IntoIterator, |
289 | 0 | I::Item: Into<Attribute<'b>>, |
290 | | { |
291 | 0 | self.start_tag.extend_attributes(attributes); |
292 | 0 | self |
293 | 0 | } |
294 | | |
295 | | /// Write some text inside the current element. |
296 | 0 | pub fn write_text_content(self, text: BytesText) -> Result<&'a mut Writer<W>> { |
297 | 0 | self.writer |
298 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
299 | 0 | self.writer.write_event(Event::Text(text))?; |
300 | 0 | self.writer |
301 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
302 | 0 | Ok(self.writer) |
303 | 0 | } |
304 | | |
305 | | /// Write a CData event `<![CDATA[...]]>` inside the current element. |
306 | 0 | pub fn write_cdata_content(self, text: BytesCData) -> Result<&'a mut Writer<W>> { |
307 | 0 | self.writer |
308 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
309 | 0 | self.writer.write_event(Event::CData(text))?; |
310 | 0 | self.writer |
311 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
312 | 0 | Ok(self.writer) |
313 | 0 | } |
314 | | |
315 | | /// Write a processing instruction `<?...?>` inside the current element. |
316 | 0 | pub fn write_pi_content(self, text: BytesText) -> Result<&'a mut Writer<W>> { |
317 | 0 | self.writer |
318 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
319 | 0 | self.writer.write_event(Event::PI(text))?; |
320 | 0 | self.writer |
321 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
322 | 0 | Ok(self.writer) |
323 | 0 | } |
324 | | |
325 | | /// Write an empty (self-closing) tag. |
326 | 0 | pub fn write_empty(self) -> Result<&'a mut Writer<W>> { |
327 | 0 | self.writer.write_event(Event::Empty(self.start_tag))?; |
328 | 0 | Ok(self.writer) |
329 | 0 | } |
330 | | |
331 | | /// Create a new scope for writing XML inside the current element. |
332 | 0 | pub fn write_inner_content<F>(self, closure: F) -> Result<&'a mut Writer<W>> |
333 | 0 | where |
334 | 0 | F: FnOnce(&mut Writer<W>) -> Result<()>, |
335 | | { |
336 | 0 | self.writer |
337 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
338 | 0 | closure(self.writer)?; |
339 | 0 | self.writer |
340 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
341 | 0 | Ok(self.writer) |
342 | 0 | } |
343 | | } |
344 | | |
345 | | #[derive(Clone)] |
346 | | pub(crate) struct Indentation { |
347 | | should_line_break: bool, |
348 | | indent_char: u8, |
349 | | indent_size: usize, |
350 | | indents: Vec<u8>, |
351 | | indents_len: usize, |
352 | | } |
353 | | |
354 | | impl Indentation { |
355 | 0 | pub fn new(indent_char: u8, indent_size: usize) -> Self { |
356 | 0 | Self { |
357 | 0 | should_line_break: false, |
358 | 0 | indent_char, |
359 | 0 | indent_size, |
360 | 0 | indents: vec![indent_char; 128], |
361 | 0 | indents_len: 0, |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | /// Increase indentation by one level |
366 | 0 | pub fn grow(&mut self) { |
367 | 0 | self.indents_len += self.indent_size; |
368 | 0 | if self.indents_len > self.indents.len() { |
369 | 0 | self.indents.resize(self.indents_len, self.indent_char); |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | /// Decrease indentation by one level. Do nothing, if level already zero |
374 | 0 | pub fn shrink(&mut self) { |
375 | 0 | self.indents_len = self.indents_len.saturating_sub(self.indent_size); |
376 | 0 | } |
377 | | |
378 | | /// Returns indent string for current level |
379 | 0 | pub fn current(&self) -> &[u8] { |
380 | 0 | &self.indents[..self.indents_len] |
381 | 0 | } |
382 | | } |
383 | | |
384 | | #[cfg(test)] |
385 | | mod indentation { |
386 | | use super::*; |
387 | | use crate::events::*; |
388 | | use pretty_assertions::assert_eq; |
389 | | |
390 | | #[test] |
391 | | fn self_closed() { |
392 | | let mut buffer = Vec::new(); |
393 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
394 | | |
395 | | let tag = BytesStart::new("self-closed") |
396 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
397 | | writer |
398 | | .write_event(Event::Empty(tag)) |
399 | | .expect("write tag failed"); |
400 | | |
401 | | assert_eq!( |
402 | | std::str::from_utf8(&buffer).unwrap(), |
403 | | r#"<self-closed attr1="value1" attr2="value2"/>"# |
404 | | ); |
405 | | } |
406 | | |
407 | | #[test] |
408 | | fn empty_paired() { |
409 | | let mut buffer = Vec::new(); |
410 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
411 | | |
412 | | let start = BytesStart::new("paired") |
413 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
414 | | let end = start.to_end(); |
415 | | writer |
416 | | .write_event(Event::Start(start.clone())) |
417 | | .expect("write start tag failed"); |
418 | | writer |
419 | | .write_event(Event::End(end)) |
420 | | .expect("write end tag failed"); |
421 | | |
422 | | assert_eq!( |
423 | | std::str::from_utf8(&buffer).unwrap(), |
424 | | r#"<paired attr1="value1" attr2="value2"> |
425 | | </paired>"# |
426 | | ); |
427 | | } |
428 | | |
429 | | #[test] |
430 | | fn paired_with_inner() { |
431 | | let mut buffer = Vec::new(); |
432 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
433 | | |
434 | | let start = BytesStart::new("paired") |
435 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
436 | | let end = start.to_end(); |
437 | | let inner = BytesStart::new("inner"); |
438 | | |
439 | | writer |
440 | | .write_event(Event::Start(start.clone())) |
441 | | .expect("write start tag failed"); |
442 | | writer |
443 | | .write_event(Event::Empty(inner)) |
444 | | .expect("write inner tag failed"); |
445 | | writer |
446 | | .write_event(Event::End(end)) |
447 | | .expect("write end tag failed"); |
448 | | |
449 | | assert_eq!( |
450 | | std::str::from_utf8(&buffer).unwrap(), |
451 | | r#"<paired attr1="value1" attr2="value2"> |
452 | | <inner/> |
453 | | </paired>"# |
454 | | ); |
455 | | } |
456 | | |
457 | | #[test] |
458 | | fn paired_with_text() { |
459 | | let mut buffer = Vec::new(); |
460 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
461 | | |
462 | | let start = BytesStart::new("paired") |
463 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
464 | | let end = start.to_end(); |
465 | | let text = BytesText::new("text"); |
466 | | |
467 | | writer |
468 | | .write_event(Event::Start(start.clone())) |
469 | | .expect("write start tag failed"); |
470 | | writer |
471 | | .write_event(Event::Text(text)) |
472 | | .expect("write text failed"); |
473 | | writer |
474 | | .write_event(Event::End(end)) |
475 | | .expect("write end tag failed"); |
476 | | |
477 | | assert_eq!( |
478 | | std::str::from_utf8(&buffer).unwrap(), |
479 | | r#"<paired attr1="value1" attr2="value2">text</paired>"# |
480 | | ); |
481 | | } |
482 | | |
483 | | #[test] |
484 | | fn mixed_content() { |
485 | | let mut buffer = Vec::new(); |
486 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
487 | | |
488 | | let start = BytesStart::new("paired") |
489 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
490 | | let end = start.to_end(); |
491 | | let text = BytesText::new("text"); |
492 | | let inner = BytesStart::new("inner"); |
493 | | |
494 | | writer |
495 | | .write_event(Event::Start(start.clone())) |
496 | | .expect("write start tag failed"); |
497 | | writer |
498 | | .write_event(Event::Text(text)) |
499 | | .expect("write text failed"); |
500 | | writer |
501 | | .write_event(Event::Empty(inner)) |
502 | | .expect("write inner tag failed"); |
503 | | writer |
504 | | .write_event(Event::End(end)) |
505 | | .expect("write end tag failed"); |
506 | | |
507 | | assert_eq!( |
508 | | std::str::from_utf8(&buffer).unwrap(), |
509 | | r#"<paired attr1="value1" attr2="value2">text<inner/> |
510 | | </paired>"# |
511 | | ); |
512 | | } |
513 | | |
514 | | #[test] |
515 | | fn nested() { |
516 | | let mut buffer = Vec::new(); |
517 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
518 | | |
519 | | let start = BytesStart::new("paired") |
520 | | .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()); |
521 | | let end = start.to_end(); |
522 | | let inner = BytesStart::new("inner"); |
523 | | |
524 | | writer |
525 | | .write_event(Event::Start(start.clone())) |
526 | | .expect("write start 1 tag failed"); |
527 | | writer |
528 | | .write_event(Event::Start(start.clone())) |
529 | | .expect("write start 2 tag failed"); |
530 | | writer |
531 | | .write_event(Event::Empty(inner)) |
532 | | .expect("write inner tag failed"); |
533 | | writer |
534 | | .write_event(Event::End(end.clone())) |
535 | | .expect("write end tag 2 failed"); |
536 | | writer |
537 | | .write_event(Event::End(end)) |
538 | | .expect("write end tag 1 failed"); |
539 | | |
540 | | assert_eq!( |
541 | | std::str::from_utf8(&buffer).unwrap(), |
542 | | r#"<paired attr1="value1" attr2="value2"> |
543 | | <paired attr1="value1" attr2="value2"> |
544 | | <inner/> |
545 | | </paired> |
546 | | </paired>"# |
547 | | ); |
548 | | } |
549 | | |
550 | | #[test] |
551 | | fn element_writer_empty() { |
552 | | let mut buffer = Vec::new(); |
553 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
554 | | |
555 | | writer |
556 | | .create_element("empty") |
557 | | .with_attribute(("attr1", "value1")) |
558 | | .with_attribute(("attr2", "value2")) |
559 | | .write_empty() |
560 | | .expect("failure"); |
561 | | |
562 | | assert_eq!( |
563 | | std::str::from_utf8(&buffer).unwrap(), |
564 | | r#"<empty attr1="value1" attr2="value2"/>"# |
565 | | ); |
566 | | } |
567 | | |
568 | | #[test] |
569 | | fn element_writer_text() { |
570 | | let mut buffer = Vec::new(); |
571 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
572 | | |
573 | | writer |
574 | | .create_element("paired") |
575 | | .with_attribute(("attr1", "value1")) |
576 | | .with_attribute(("attr2", "value2")) |
577 | | .write_text_content(BytesText::new("text")) |
578 | | .expect("failure"); |
579 | | |
580 | | assert_eq!( |
581 | | std::str::from_utf8(&buffer).unwrap(), |
582 | | r#"<paired attr1="value1" attr2="value2">text</paired>"# |
583 | | ); |
584 | | } |
585 | | |
586 | | #[test] |
587 | | fn element_writer_nested() { |
588 | | let mut buffer = Vec::new(); |
589 | | let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
590 | | |
591 | | writer |
592 | | .create_element("outer") |
593 | | .with_attribute(("attr1", "value1")) |
594 | | .with_attribute(("attr2", "value2")) |
595 | | .write_inner_content(|writer| { |
596 | | let fruits = ["apple", "orange", "banana"]; |
597 | | for (quant, item) in fruits.iter().enumerate() { |
598 | | writer |
599 | | .create_element("fruit") |
600 | | .with_attribute(("quantity", quant.to_string().as_str())) |
601 | | .write_text_content(BytesText::new(item))?; |
602 | | } |
603 | | writer |
604 | | .create_element("inner") |
605 | | .write_inner_content(|writer| { |
606 | | writer.create_element("empty").write_empty()?; |
607 | | Ok(()) |
608 | | })?; |
609 | | |
610 | | Ok(()) |
611 | | }) |
612 | | .expect("failure"); |
613 | | |
614 | | assert_eq!( |
615 | | std::str::from_utf8(&buffer).unwrap(), |
616 | | r#"<outer attr1="value1" attr2="value2"> |
617 | | <fruit quantity="0">apple</fruit> |
618 | | <fruit quantity="1">orange</fruit> |
619 | | <fruit quantity="2">banana</fruit> |
620 | | <inner> |
621 | | <empty/> |
622 | | </inner> |
623 | | </outer>"# |
624 | | ); |
625 | | } |
626 | | } |