/rust/registry/src/index.crates.io-1949cf8c6b5b557f/quick-xml-0.41.0/src/writer.rs
Line | Count | Source |
1 | | //! Contains high-level interface for an events-based XML emitter. |
2 | | |
3 | | use std::borrow::Cow; |
4 | | use std::io::{self, Write}; |
5 | | |
6 | | use crate::encoding::UTF8_BOM; |
7 | | use crate::events::{attributes::Attribute, BytesCData, BytesPI, 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 | | #[cfg(feature = "serialize")] |
14 | | use {crate::se::SeError, serde::Serialize}; |
15 | | |
16 | | /// A struct that holds a writer configuration. |
17 | | /// |
18 | | /// Current writer configuration can be retrieved by calling [`Writer::config()`] |
19 | | /// and changed by changing properties of the object returned by a call to |
20 | | /// [`Writer::config_mut()`]. |
21 | | /// |
22 | | /// [`Writer::config()`]: crate::writer::Writer::config |
23 | | /// [`Writer::config_mut()`]: crate::writer::Writer::config_mut |
24 | | #[derive(Debug, Default, Clone, PartialEq, Eq)] |
25 | | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] |
26 | | #[cfg_attr(feature = "serde-types", derive(serde::Deserialize, serde::Serialize))] |
27 | | #[non_exhaustive] |
28 | | pub struct Config { |
29 | | /// Whether to add a space before the closing slash in empty elements. |
30 | | /// According to the [W3C guidelines], this is recommended as for maximum compatibility. |
31 | | /// |
32 | | /// When set to `true`, empty elements will be terminated with "` />`". |
33 | | /// When set to `false`, empty elements will be terminated with "`/>`". |
34 | | /// |
35 | | /// Default: `false` |
36 | | /// |
37 | | /// # Example |
38 | | /// |
39 | | /// ``` |
40 | | /// # use pretty_assertions::assert_eq; |
41 | | /// use quick_xml::reader::Reader; |
42 | | /// use quick_xml::writer::Writer; |
43 | | /// use std::io::Cursor; |
44 | | /// |
45 | | /// let mut writer = Writer::new(Cursor::new(Vec::new())); |
46 | | /// writer.config_mut().add_space_before_slash_in_empty_elements = true; |
47 | | /// |
48 | | /// writer.create_element("tag") |
49 | | /// .with_attribute(("attr1", "value1")) |
50 | | /// .write_empty() |
51 | | /// .unwrap(); |
52 | | /// |
53 | | /// let result = writer.into_inner().into_inner(); |
54 | | /// let expected = r#"<tag attr1="value1" />"#; |
55 | | /// assert_eq!(result, expected.as_bytes()); |
56 | | /// ``` |
57 | | /// |
58 | | /// [W3C guidelines]: https://www.w3.org/TR/xhtml1/#guidelines |
59 | | pub add_space_before_slash_in_empty_elements: bool, |
60 | | } |
61 | | |
62 | | /// XML writer. Writes XML [`Event`]s to a [`std::io::Write`] implementor. |
63 | | /// |
64 | | /// # Examples |
65 | | /// |
66 | | /// ``` |
67 | | /// # use pretty_assertions::assert_eq; |
68 | | /// use quick_xml::events::{Event, BytesEnd, BytesStart}; |
69 | | /// use quick_xml::reader::Reader; |
70 | | /// use quick_xml::writer::Writer; |
71 | | /// use std::io::Cursor; |
72 | | /// |
73 | | /// let xml = r#"<this_tag k1="v1" k2="v2"><child>text</child></this_tag>"#; |
74 | | /// let mut reader = Reader::from_str(xml); |
75 | | /// let mut writer = Writer::new(Cursor::new(Vec::new())); |
76 | | /// loop { |
77 | | /// match reader.read_event() { |
78 | | /// Ok(Event::Start(e)) if e.name().as_ref() == b"this_tag" => { |
79 | | /// |
80 | | /// // creates a new element ... alternatively we could reuse `e` by calling |
81 | | /// // `e.into_owned()` |
82 | | /// let mut elem = BytesStart::new("my_elem"); |
83 | | /// |
84 | | /// // collect existing attributes |
85 | | /// elem.extend_attributes(e.attributes().map(|attr| attr.unwrap())); |
86 | | /// |
87 | | /// // copy existing attributes, adds a new my-key="some value" attribute |
88 | | /// elem.push_attribute(("my-key", "some value")); |
89 | | /// |
90 | | /// // writes the event to the writer |
91 | | /// assert!(writer.write_event(Event::Start(elem)).is_ok()); |
92 | | /// }, |
93 | | /// Ok(Event::End(e)) if e.name().as_ref() == b"this_tag" => { |
94 | | /// assert!(writer.write_event(Event::End(BytesEnd::new("my_elem"))).is_ok()); |
95 | | /// }, |
96 | | /// Ok(Event::Eof) => break, |
97 | | /// // we can either move or borrow the event to write, depending on your use-case |
98 | | /// Ok(e) => assert!(writer.write_event(e.borrow()).is_ok()), |
99 | | /// Err(e) => panic!("Error at position {}: {:?}", reader.error_position(), e), |
100 | | /// } |
101 | | /// } |
102 | | /// |
103 | | /// let result = writer.into_inner().into_inner(); |
104 | | /// let expected = r#"<my_elem k1="v1" k2="v2" my-key="some value"><child>text</child></my_elem>"#; |
105 | | /// assert_eq!(result, expected.as_bytes()); |
106 | | /// ``` |
107 | | #[derive(Clone)] |
108 | | pub struct Writer<W> { |
109 | | /// underlying writer |
110 | | writer: W, |
111 | | |
112 | | /// writer configuration |
113 | | config: Config, |
114 | | |
115 | | /// indentation configuration and state; stored separately from |
116 | | /// other configuration since it also tracks writer state |
117 | | indent: Option<Indentation>, |
118 | | } |
119 | | |
120 | | impl<W> Writer<W> { |
121 | | /// Creates a `Writer` from a generic writer. |
122 | 0 | pub const fn new(inner: W) -> Writer<W> { |
123 | 0 | Writer { |
124 | 0 | writer: inner, |
125 | 0 | config: Config { |
126 | 0 | add_space_before_slash_in_empty_elements: false, |
127 | 0 | }, |
128 | 0 | indent: None, |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | /// Creates a `Writer` with configured indents from a generic writer. |
133 | 0 | pub fn new_with_indent(inner: W, indent_char: u8, indent_size: usize) -> Writer<W> { |
134 | 0 | Writer { |
135 | 0 | writer: inner, |
136 | 0 | config: Config { |
137 | 0 | add_space_before_slash_in_empty_elements: false, |
138 | 0 | }, |
139 | 0 | indent: Some(Indentation::new(indent_char, indent_size)), |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | /// Consumes this `Writer`, returning the underlying writer. |
144 | 0 | pub fn into_inner(self) -> W { |
145 | 0 | self.writer |
146 | 0 | } |
147 | | |
148 | | /// Get a mutable reference to the underlying writer. |
149 | 0 | pub fn get_mut(&mut self) -> &mut W { |
150 | 0 | &mut self.writer |
151 | 0 | } |
152 | | |
153 | | /// Get a reference to the underlying writer. |
154 | 0 | pub const fn get_ref(&self) -> &W { |
155 | 0 | &self.writer |
156 | 0 | } |
157 | | |
158 | | /// Returns reference to the writer configuration |
159 | 0 | pub const fn config(&self) -> &Config { |
160 | 0 | &self.config |
161 | 0 | } |
162 | | |
163 | | /// Returns mutable reference to the writer configuration |
164 | 0 | pub fn config_mut(&mut self) -> &mut Config { |
165 | 0 | &mut self.config |
166 | 0 | } |
167 | | |
168 | | /// Provides a simple, high-level API for writing XML elements. |
169 | | /// |
170 | | /// Returns an [`ElementWriter`] that simplifies setting attributes and writing |
171 | | /// content inside the element. |
172 | | /// |
173 | | /// # Example |
174 | | /// |
175 | | /// ``` |
176 | | /// # use quick_xml::Result; |
177 | | /// # fn main() -> Result<()> { |
178 | | /// use quick_xml::events::{BytesStart, BytesText, Event}; |
179 | | /// use quick_xml::writer::Writer; |
180 | | /// use quick_xml::Error; |
181 | | /// use std::io::Cursor; |
182 | | /// |
183 | | /// let mut writer = Writer::new(Cursor::new(Vec::new())); |
184 | | /// |
185 | | /// // writes <tag attr1="value1"/> |
186 | | /// writer.create_element("tag") |
187 | | /// .with_attribute(("attr1", "value1")) // chain `with_attribute()` calls to add many attributes |
188 | | /// .write_empty()?; |
189 | | /// |
190 | | /// // writes <tag attr1="value1" attr2="value2">with some text inside</tag> |
191 | | /// writer.create_element("tag") |
192 | | /// .with_attributes(vec![("attr1", "value1"), ("attr2", "value2")].into_iter()) // or add attributes from an iterator |
193 | | /// .write_text_content(BytesText::new("with some text inside"))?; |
194 | | /// |
195 | | /// // writes <tag><fruit quantity="0">apple</fruit><fruit quantity="1">orange</fruit></tag> |
196 | | /// writer.create_element("tag") |
197 | | /// // We need to provide error type, because it is not named somewhere explicitly |
198 | | /// .write_inner_content(|writer| { |
199 | | /// let fruits = ["apple", "orange"]; |
200 | | /// for (quant, item) in fruits.iter().enumerate() { |
201 | | /// writer |
202 | | /// .create_element("fruit") |
203 | | /// .with_attribute(("quantity", quant.to_string().as_str())) |
204 | | /// .write_text_content(BytesText::new(item))?; |
205 | | /// } |
206 | | /// Ok(()) |
207 | | /// })?; |
208 | | /// # Ok(()) |
209 | | /// # } |
210 | | /// ``` |
211 | | #[must_use] |
212 | 0 | pub fn create_element<'a, N>(&'a mut self, name: N) -> ElementWriter<'a, W> |
213 | 0 | where |
214 | 0 | N: Into<Cow<'a, str>>, |
215 | | { |
216 | 0 | ElementWriter { |
217 | 0 | writer: self, |
218 | 0 | start_tag: BytesStart::new(name), |
219 | 0 | state: AttributeIndent::NoneAttributesWritten, |
220 | 0 | spaces: Vec::new(), |
221 | 0 | } |
222 | 0 | } |
223 | | } |
224 | | |
225 | | impl<W: Write> Writer<W> { |
226 | | /// Write a [Byte-Order-Mark] character to the document. |
227 | | /// |
228 | | /// # Example |
229 | | /// |
230 | | /// ```rust |
231 | | /// # use quick_xml::Result; |
232 | | /// # fn main() -> Result<()> { |
233 | | /// use quick_xml::events::{BytesStart, BytesText, Event}; |
234 | | /// use quick_xml::writer::Writer; |
235 | | /// use quick_xml::Error; |
236 | | /// use std::io::Cursor; |
237 | | /// |
238 | | /// let mut buffer = Vec::new(); |
239 | | /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
240 | | /// |
241 | | /// writer.write_bom()?; |
242 | | /// writer |
243 | | /// .create_element("empty") |
244 | | /// .with_attribute(("attr1", "value1")) |
245 | | /// .write_empty() |
246 | | /// .expect("failure"); |
247 | | /// |
248 | | /// assert_eq!( |
249 | | /// std::str::from_utf8(&buffer).unwrap(), |
250 | | /// "\u{FEFF}<empty attr1=\"value1\"/>" |
251 | | /// ); |
252 | | /// # Ok(()) |
253 | | /// # } |
254 | | /// ``` |
255 | | /// [Byte-Order-Mark]: https://unicode.org/faq/utf_bom.html#BOM |
256 | 0 | pub fn write_bom(&mut self) -> io::Result<()> { |
257 | 0 | self.write(UTF8_BOM) |
258 | 0 | } |
259 | | |
260 | | /// Writes the given event to the underlying writer. |
261 | 0 | pub fn write_event<'a, E: Into<Event<'a>>>(&mut self, event: E) -> io::Result<()> { |
262 | 0 | let mut next_should_line_break = true; |
263 | 0 | let result = match event.into() { |
264 | 0 | Event::Start(e) => { |
265 | 0 | let result = self.write_wrapped(b"<", &e, b">"); |
266 | 0 | if let Some(i) = self.indent.as_mut() { |
267 | 0 | i.grow(); |
268 | 0 | } |
269 | 0 | result |
270 | | } |
271 | 0 | Event::End(e) => { |
272 | 0 | if let Some(i) = self.indent.as_mut() { |
273 | 0 | i.shrink(); |
274 | 0 | } |
275 | 0 | self.write_wrapped(b"</", &e, b">") |
276 | | } |
277 | 0 | Event::Empty(e) => self.write_wrapped( |
278 | 0 | b"<", |
279 | 0 | &e, |
280 | 0 | if self.config.add_space_before_slash_in_empty_elements { |
281 | 0 | b" />" |
282 | | } else { |
283 | 0 | b"/>" |
284 | | }, |
285 | | ), |
286 | 0 | Event::Text(e) => { |
287 | 0 | next_should_line_break = false; |
288 | 0 | self.write(&e) |
289 | | } |
290 | 0 | Event::Comment(e) => self.write_wrapped(b"<!--", &e, b"-->"), |
291 | 0 | Event::CData(e) => { |
292 | 0 | next_should_line_break = false; |
293 | 0 | self.write(b"<![CDATA[")?; |
294 | 0 | self.write(&e)?; |
295 | 0 | self.write(b"]]>") |
296 | | } |
297 | 0 | Event::Decl(e) => self.write_wrapped(b"<?", &e, b"?>"), |
298 | 0 | Event::PI(e) => self.write_wrapped(b"<?", &e, b"?>"), |
299 | 0 | Event::DocType(e) => self.write_wrapped(b"<!DOCTYPE ", &e, b">"), |
300 | 0 | Event::GeneralRef(e) => self.write_wrapped(b"&", &e, b";"), |
301 | 0 | Event::Eof => Ok(()), |
302 | | }; |
303 | 0 | if let Some(i) = self.indent.as_mut() { |
304 | 0 | i.should_line_break = next_should_line_break; |
305 | 0 | } |
306 | 0 | result |
307 | 0 | } |
308 | | |
309 | | /// Writes bytes |
310 | | #[inline] |
311 | 0 | pub(crate) fn write(&mut self, value: &[u8]) -> io::Result<()> { |
312 | 0 | self.writer.write_all(value) |
313 | 0 | } |
314 | | |
315 | | #[inline] |
316 | 0 | fn write_wrapped(&mut self, before: &[u8], value: &[u8], after: &[u8]) -> io::Result<()> { |
317 | 0 | if let Some(ref i) = self.indent { |
318 | 0 | if i.should_line_break { |
319 | 0 | self.writer.write_all(b"\n")?; |
320 | 0 | self.writer.write_all(i.current())?; |
321 | 0 | } |
322 | 0 | } |
323 | 0 | self.write(before)?; |
324 | 0 | self.write(value)?; |
325 | 0 | self.write(after)?; |
326 | 0 | Ok(()) |
327 | 0 | } |
328 | | |
329 | | /// Manually write a newline and indentation at the proper level. |
330 | | /// |
331 | | /// This can be used when the heuristic to line break and indent after any |
332 | | /// [`Event`] apart from [`Text`] fails such as when a [`Start`] occurs directly |
333 | | /// after [`Text`]. |
334 | | /// |
335 | | /// This method will do nothing if `Writer` was not constructed with [`new_with_indent`]. |
336 | | /// |
337 | | /// [`Text`]: Event::Text |
338 | | /// [`Start`]: Event::Start |
339 | | /// [`new_with_indent`]: Self::new_with_indent |
340 | 0 | pub fn write_indent(&mut self) -> io::Result<()> { |
341 | 0 | if let Some(ref i) = self.indent { |
342 | 0 | self.writer.write_all(b"\n")?; |
343 | 0 | self.writer.write_all(i.current())?; |
344 | 0 | } |
345 | 0 | Ok(()) |
346 | 0 | } |
347 | | |
348 | | /// Write an arbitrary serializable type |
349 | | /// |
350 | | /// Note: If you are attempting to write XML in a non-UTF-8 encoding, this may not |
351 | | /// be safe to use. Rust basic types assume UTF-8 encodings. |
352 | | /// |
353 | | /// ```rust |
354 | | /// # use pretty_assertions::assert_eq; |
355 | | /// # use serde::Serialize; |
356 | | /// # use quick_xml::events::{BytesStart, Event}; |
357 | | /// # use quick_xml::writer::Writer; |
358 | | /// # use quick_xml::se::SeError; |
359 | | /// # fn main() -> Result<(), SeError> { |
360 | | /// #[derive(Debug, PartialEq, Serialize)] |
361 | | /// struct MyData { |
362 | | /// question: String, |
363 | | /// answer: u32, |
364 | | /// } |
365 | | /// |
366 | | /// let data = MyData { |
367 | | /// question: "The Ultimate Question of Life, the Universe, and Everything".into(), |
368 | | /// answer: 42, |
369 | | /// }; |
370 | | /// |
371 | | /// let mut buffer = Vec::new(); |
372 | | /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4); |
373 | | /// |
374 | | /// let start = BytesStart::new("root"); |
375 | | /// let end = start.to_end(); |
376 | | /// |
377 | | /// writer.write_event(Event::Start(start.clone()))?; |
378 | | /// writer.write_serializable("my_data", &data)?; |
379 | | /// writer.write_event(Event::End(end))?; |
380 | | /// |
381 | | /// assert_eq!( |
382 | | /// std::str::from_utf8(&buffer)?, |
383 | | /// r#"<root> |
384 | | /// <my_data> |
385 | | /// <question>The Ultimate Question of Life, the Universe, and Everything</question> |
386 | | /// <answer>42</answer> |
387 | | /// </my_data> |
388 | | /// </root>"# |
389 | | /// ); |
390 | | /// # Ok(()) |
391 | | /// # } |
392 | | /// ``` |
393 | | #[cfg(feature = "serialize")] |
394 | 0 | pub fn write_serializable<T: Serialize>( |
395 | 0 | &mut self, |
396 | 0 | tag_name: &str, |
397 | 0 | content: &T, |
398 | 0 | ) -> Result<(), SeError> { |
399 | | use crate::se::{Indent, Serializer}; |
400 | | |
401 | 0 | self.write_indent()?; |
402 | 0 | let mut fmt = ToFmtWrite(&mut self.writer); |
403 | 0 | let mut serializer = Serializer::with_root(&mut fmt, Some(tag_name))?; |
404 | | |
405 | 0 | if let Some(indent) = &mut self.indent { |
406 | 0 | serializer.set_indent(Indent::Borrow(indent)); |
407 | 0 | } |
408 | | |
409 | 0 | content.serialize(serializer)?; |
410 | | |
411 | 0 | Ok(()) |
412 | 0 | } |
413 | | } |
414 | | |
415 | | /// Track indent inside elements state |
416 | | /// |
417 | | /// ```mermaid |
418 | | /// stateDiagram-v2 |
419 | | /// [*] --> NoneAttributesWritten |
420 | | /// NoneAttributesWritten --> Spaces : .with_attribute() |
421 | | /// NoneAttributesWritten --> WriteConfigured : .new_line() |
422 | | /// |
423 | | /// Spaces --> Spaces : .with_attribute() |
424 | | /// Spaces --> WriteSpaces : .new_line() |
425 | | /// |
426 | | /// WriteSpaces --> Spaces : .with_attribute() |
427 | | /// WriteSpaces --> WriteSpaces : .new_line() |
428 | | /// |
429 | | /// Configured --> Configured : .with_attribute() |
430 | | /// Configured --> WriteConfigured : .new_line() |
431 | | /// |
432 | | /// WriteConfigured --> Configured : .with_attribute() |
433 | | /// WriteConfigured --> WriteConfigured : .new_line() |
434 | | /// ``` |
435 | | #[derive(Debug)] |
436 | | enum AttributeIndent { |
437 | | /// Initial state. `ElementWriter` was just created and no attributes written yet |
438 | | NoneAttributesWritten, |
439 | | /// Write specified count of spaces to indent before writing attribute in `with_attribute()` |
440 | | WriteSpaces(usize), |
441 | | /// Keep space indent that should be used if `new_line()` would be called |
442 | | Spaces(usize), |
443 | | /// Write specified count of indent characters before writing attribute in `with_attribute()` |
444 | | WriteConfigured(usize), |
445 | | /// Keep indent that should be used if `new_line()` would be called |
446 | | Configured(usize), |
447 | | } |
448 | | |
449 | | /// A struct to write an element. Contains methods to add attributes and inner |
450 | | /// elements to the element |
451 | | pub struct ElementWriter<'a, W> { |
452 | | writer: &'a mut Writer<W>, |
453 | | start_tag: BytesStart<'a>, |
454 | | state: AttributeIndent, |
455 | | /// Contains spaces used to write space indents of attributes |
456 | | spaces: Vec<u8>, |
457 | | } |
458 | | |
459 | | impl<'a, W> ElementWriter<'a, W> { |
460 | | /// Adds an attribute to this element. |
461 | 0 | pub fn with_attribute<'b, I>(mut self, attr: I) -> Self |
462 | 0 | where |
463 | 0 | I: Into<Attribute<'b>>, |
464 | | { |
465 | 0 | self.write_attr(attr.into()); |
466 | 0 | self |
467 | 0 | } |
468 | | |
469 | | /// Add additional attributes to this element using an iterator. |
470 | | /// |
471 | | /// The yielded items must be convertible to [`Attribute`] using `Into`. |
472 | 0 | pub fn with_attributes<'b, I>(mut self, attributes: I) -> Self |
473 | 0 | where |
474 | 0 | I: IntoIterator, |
475 | 0 | I::Item: Into<Attribute<'b>>, |
476 | | { |
477 | 0 | let mut iter = attributes.into_iter(); |
478 | 0 | if let Some(attr) = iter.next() { |
479 | 0 | self.write_attr(attr.into()); |
480 | 0 | self.start_tag.extend_attributes(iter); |
481 | 0 | } |
482 | 0 | self |
483 | 0 | } |
484 | | |
485 | | /// Push a new line inside an element between attributes. Note, that this |
486 | | /// method does nothing if [`Writer`] was created without indentation support. |
487 | | /// |
488 | | /// # Examples |
489 | | /// |
490 | | /// The following code |
491 | | /// |
492 | | /// ``` |
493 | | /// # use quick_xml::writer::Writer; |
494 | | /// let mut buffer = Vec::new(); |
495 | | /// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 2); |
496 | | /// writer |
497 | | /// .create_element("element") |
498 | | /// //.new_line() (1) |
499 | | /// .with_attribute(("first", "1")) |
500 | | /// .with_attribute(("second", "2")) |
501 | | /// .new_line() |
502 | | /// .with_attributes([ |
503 | | /// ("third", "3"), |
504 | | /// ("fourth", "4"), |
505 | | /// ]) |
506 | | /// //.new_line() (2) |
507 | | /// .write_empty(); |
508 | | /// ``` |
509 | | /// will produce the following XMLs: |
510 | | /// ```xml |
511 | | /// <!-- result of the code above. Spaces always is used --> |
512 | | /// <element first="1" second="2" |
513 | | /// third="3" fourth="4"/> |
514 | | /// |
515 | | /// <!-- if uncomment only (1) - indent depends on indentation |
516 | | /// settings - 2 spaces here --> |
517 | | /// <element |
518 | | /// first="1" second="2" |
519 | | /// third="3" fourth="4"/> |
520 | | /// |
521 | | /// <!-- if uncomment only (2). Spaces always is used --> |
522 | | /// <element first="1" second="2" |
523 | | /// third="3" fourth="4" |
524 | | /// /> |
525 | | /// ``` |
526 | 0 | pub fn new_line(mut self) -> Self { |
527 | 0 | if let Some(i) = self.writer.indent.as_mut() { |
528 | 0 | match self.state { |
529 | | // .new_line() called just after .create_element(). |
530 | | // Use element indent to additionally indent attributes |
531 | | AttributeIndent::NoneAttributesWritten => { |
532 | 0 | self.state = AttributeIndent::WriteConfigured(i.indent_size) |
533 | | } |
534 | | |
535 | 0 | AttributeIndent::WriteSpaces(_) => {} |
536 | | // .new_line() called when .with_attribute() was called at least once. |
537 | | // The spaces should be used to indent |
538 | | // Plan saved indent |
539 | 0 | AttributeIndent::Spaces(indent) => { |
540 | 0 | self.state = AttributeIndent::WriteSpaces(indent) |
541 | | } |
542 | | |
543 | 0 | AttributeIndent::WriteConfigured(_) => {} |
544 | | // .new_line() called when .with_attribute() was called at least once. |
545 | | // The configured indent characters should be used to indent |
546 | | // Plan saved indent |
547 | 0 | AttributeIndent::Configured(indent) => { |
548 | 0 | self.state = AttributeIndent::WriteConfigured(indent) |
549 | | } |
550 | | } |
551 | 0 | self.start_tag.push_newline(); |
552 | 0 | }; |
553 | 0 | self |
554 | 0 | } |
555 | | |
556 | | /// Writes attribute and maintain indentation state |
557 | 0 | fn write_attr<'b>(&mut self, attr: Attribute<'b>) { |
558 | 0 | if let Some(i) = self.writer.indent.as_mut() { |
559 | | // Save the indent that we should use next time when .new_line() be called |
560 | 0 | self.state = match self.state { |
561 | | // Neither .new_line() or .with_attribute() yet called |
562 | | // If newline inside attributes will be requested, we should indent them |
563 | | // by the length of tag name and +1 for `<` and +1 for one space |
564 | | AttributeIndent::NoneAttributesWritten => { |
565 | 0 | self.start_tag.push_attribute(attr); |
566 | 0 | AttributeIndent::Spaces(self.start_tag.name().as_ref().len() + 2) |
567 | | } |
568 | | |
569 | | // Indent was requested by previous call to .new_line(), write it |
570 | | // New line was already written |
571 | 0 | AttributeIndent::WriteSpaces(indent) => { |
572 | 0 | if self.spaces.len() < indent { |
573 | 0 | self.spaces.resize(indent, b' '); |
574 | 0 | } |
575 | 0 | self.start_tag.push_indent(&self.spaces[..indent]); |
576 | 0 | self.start_tag.push_attr(attr); |
577 | 0 | AttributeIndent::Spaces(indent) |
578 | | } |
579 | | // .new_line() was not called, but .with_attribute() was. |
580 | | // use the previously calculated indent |
581 | 0 | AttributeIndent::Spaces(indent) => { |
582 | 0 | self.start_tag.push_attribute(attr); |
583 | 0 | AttributeIndent::Spaces(indent) |
584 | | } |
585 | | |
586 | | // Indent was requested by previous call to .new_line(), write it |
587 | | // New line was already written |
588 | 0 | AttributeIndent::WriteConfigured(indent) => { |
589 | 0 | self.start_tag.push_indent(i.additional(indent)); |
590 | 0 | self.start_tag.push_attr(attr); |
591 | 0 | AttributeIndent::Configured(indent) |
592 | | } |
593 | | // .new_line() was not called, but .with_attribute() was. |
594 | | // use the previously calculated indent |
595 | 0 | AttributeIndent::Configured(indent) => { |
596 | 0 | self.start_tag.push_attribute(attr); |
597 | 0 | AttributeIndent::Configured(indent) |
598 | | } |
599 | | }; |
600 | 0 | } else { |
601 | 0 | self.start_tag.push_attribute(attr); |
602 | 0 | } |
603 | 0 | } |
604 | | } |
605 | | |
606 | | impl<'a, W: Write> ElementWriter<'a, W> { |
607 | | /// Write some text inside the current element. |
608 | 0 | pub fn write_text_content(self, text: BytesText) -> io::Result<&'a mut Writer<W>> { |
609 | 0 | self.writer |
610 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
611 | 0 | self.writer.write_event(Event::Text(text))?; |
612 | 0 | self.writer |
613 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
614 | 0 | Ok(self.writer) |
615 | 0 | } |
616 | | |
617 | | /// Write a CData event `<![CDATA[...]]>` inside the current element. |
618 | 0 | pub fn write_cdata_content(self, text: BytesCData) -> io::Result<&'a mut Writer<W>> { |
619 | 0 | self.writer |
620 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
621 | 0 | self.writer.write_event(Event::CData(text))?; |
622 | 0 | self.writer |
623 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
624 | 0 | Ok(self.writer) |
625 | 0 | } |
626 | | |
627 | | /// Write a processing instruction `<?...?>` inside the current element. |
628 | 0 | pub fn write_pi_content(self, pi: BytesPI) -> io::Result<&'a mut Writer<W>> { |
629 | 0 | self.writer |
630 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
631 | 0 | self.writer.write_event(Event::PI(pi))?; |
632 | 0 | self.writer |
633 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
634 | 0 | Ok(self.writer) |
635 | 0 | } |
636 | | |
637 | | /// Write an empty (self-closing) tag. |
638 | 0 | pub fn write_empty(self) -> io::Result<&'a mut Writer<W>> { |
639 | 0 | self.writer.write_event(Event::Empty(self.start_tag))?; |
640 | 0 | Ok(self.writer) |
641 | 0 | } |
642 | | |
643 | | /// Create a new scope for writing XML inside the current element. |
644 | 0 | pub fn write_inner_content<F>(self, closure: F) -> io::Result<&'a mut Writer<W>> |
645 | 0 | where |
646 | 0 | F: FnOnce(&mut Writer<W>) -> io::Result<()>, |
647 | | { |
648 | 0 | self.writer |
649 | 0 | .write_event(Event::Start(self.start_tag.borrow()))?; |
650 | 0 | closure(self.writer)?; |
651 | 0 | self.writer |
652 | 0 | .write_event(Event::End(self.start_tag.to_end()))?; |
653 | 0 | Ok(self.writer) |
654 | 0 | } |
655 | | } |
656 | | #[cfg(feature = "serialize")] |
657 | | pub(crate) struct ToFmtWrite<T>(pub T); |
658 | | |
659 | | #[cfg(feature = "serialize")] |
660 | | impl<T> std::fmt::Write for ToFmtWrite<T> |
661 | | where |
662 | | T: std::io::Write, |
663 | | { |
664 | 0 | fn write_str(&mut self, s: &str) -> std::fmt::Result { |
665 | 0 | self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error) |
666 | 0 | } |
667 | | } |
668 | | |
669 | | #[derive(Debug, Clone)] |
670 | | pub(crate) struct Indentation { |
671 | | /// todo: this is an awkward fit as it has no impact on indentation logic, but it is |
672 | | /// only applicable when an indentation exists. Potentially refactor later |
673 | | should_line_break: bool, |
674 | | /// The character code to be used for indentations (e.g. ` ` or `\t`) |
675 | | indent_char: u8, |
676 | | /// How many instances of the indent character ought to be used for each level of indentation |
677 | | indent_size: usize, |
678 | | /// Used as a cache for the bytes used for indentation |
679 | | indents: Vec<u8>, |
680 | | /// The current amount of indentation |
681 | | current_indent_len: usize, |
682 | | } |
683 | | |
684 | | impl Indentation { |
685 | 0 | pub fn new(indent_char: u8, indent_size: usize) -> Self { |
686 | 0 | Self { |
687 | 0 | should_line_break: false, |
688 | 0 | indent_char, |
689 | 0 | indent_size, |
690 | 0 | indents: vec![indent_char; 128], |
691 | 0 | current_indent_len: 0, // invariant - needs to remain less than indents.len() |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | | /// Increase indentation by one level |
696 | 0 | pub fn grow(&mut self) { |
697 | 0 | self.current_indent_len += self.indent_size; |
698 | 0 | self.ensure(self.current_indent_len); |
699 | 0 | } |
700 | | |
701 | | /// Decrease indentation by one level. Do nothing, if level already zero |
702 | 0 | pub fn shrink(&mut self) { |
703 | 0 | self.current_indent_len = self.current_indent_len.saturating_sub(self.indent_size); |
704 | 0 | } |
705 | | |
706 | | /// Returns indent string for current level |
707 | 0 | pub fn current(&self) -> &[u8] { |
708 | 0 | &self.indents[..self.current_indent_len] |
709 | 0 | } |
710 | | |
711 | | /// Returns indent with current indent plus additional indent |
712 | 0 | pub fn additional(&mut self, additional_indent: usize) -> &[u8] { |
713 | 0 | let new_len = self.current_indent_len + additional_indent; |
714 | 0 | self.ensure(new_len); |
715 | 0 | &self.indents[..new_len] |
716 | 0 | } |
717 | | |
718 | 0 | fn ensure(&mut self, new_len: usize) { |
719 | 0 | if self.indents.len() < new_len { |
720 | 0 | self.indents.resize(new_len, self.indent_char); |
721 | 0 | } |
722 | 0 | } |
723 | | } |