Skip to content

Commit ea6a118

Browse files
areleuMingun
authored andcommitted
Implement API to add newlines between attributes when writing XML.
1 parent 8fc158a commit ea6a118

File tree

3 files changed

+222
-4
lines changed

3 files changed

+222
-4
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ to get an offset of the error position. For `SyntaxError`s the range
3030
- [#362]: Added `BytesCData::minimal_escape()` which escapes only `&` and `<`.
3131
- [#362]: Added `Serializer::set_quote_level()` which allow to set desired level of escaping.
3232
- [#705]: Added `NsReader::prefixes()` to list all the prefixes currently declared.
33+
- [#275]: Added `ElementWriter::new_line()` which enables pretty printing elements with multiple attributes.
3334

3435
### Bug Fixes
3536

@@ -61,6 +62,7 @@ to get an offset of the error position. For `SyntaxError`s the range
6162
- [#689]: `buffer_position()` now always report the position the parser last seen.
6263
To get an error position use `error_position()`.
6364

65+
[#275]: https://github.com/tafia/quick-xml/issues/275
6466
[#362]: https://github.com/tafia/quick-xml/issues/362
6567
[#513]: https://github.com/tafia/quick-xml/issues/513
6668
[#622]: https://github.com/tafia/quick-xml/issues/622

src/events/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,16 @@ impl<'a> BytesStart<'a> {
251251
bytes.push(b'"');
252252
}
253253

254+
/// Adds new line in existing element
255+
pub fn push_newline(&mut self) {
256+
self.buf.to_mut().push(b'\n');
257+
}
258+
259+
/// Adds indentation bytes in existing element
260+
pub(crate) fn push_indent(&mut self, indent: &[u8]) {
261+
self.buf.to_mut().extend_from_slice(indent);
262+
}
263+
254264
/// Remove all attributes from the ByteStart
255265
pub fn clear_attributes(&mut self) -> &mut BytesStart<'a> {
256266
self.buf.to_mut().truncate(self.name_len);

src/writer.rs

Lines changed: 210 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ impl<W> Writer<W> {
150150
ElementWriter {
151151
writer: self,
152152
start_tag: BytesStart::new(name.as_ref()),
153+
state: AttributeIndent::NoneAttributesWritten,
153154
}
154155
}
155156
}
@@ -335,11 +336,23 @@ impl<W: Write> Writer<W> {
335336
}
336337
}
337338

339+
/// Track indent inside elements state
340+
#[derive(Debug)]
341+
enum AttributeIndent {
342+
/// Initial state
343+
NoneAttributesWritten,
344+
/// Keep indent that should be used if `new_line()` would be called
345+
SomeAttributesWritten(usize),
346+
/// Write specified indent before writing attribute in `with_attribute()`
347+
Indent(usize),
348+
}
349+
338350
/// A struct to write an element. Contains methods to add attributes and inner
339351
/// elements to the element
340352
pub struct ElementWriter<'a, W> {
341353
writer: &'a mut Writer<W>,
342354
start_tag: BytesStart<'a>,
355+
state: AttributeIndent,
343356
}
344357

345358
impl<'a, W> ElementWriter<'a, W> {
@@ -348,6 +361,26 @@ impl<'a, W> ElementWriter<'a, W> {
348361
where
349362
I: Into<Attribute<'b>>,
350363
{
364+
if let Some(i) = self.writer.indent.as_mut() {
365+
let next_indent = match self.state {
366+
// Neither .new_line() or .with_attribute() yet called
367+
// If newline inside attributes will be requested, we should indent them
368+
// by the length of tag name and one space
369+
AttributeIndent::NoneAttributesWritten => self.start_tag.name().as_ref().len() + 1,
370+
// .new_line() was not called, but .with_attribute() was.
371+
// use the previously calculated indent
372+
AttributeIndent::SomeAttributesWritten(indent) => indent,
373+
AttributeIndent::Indent(indent) => {
374+
// Indent was requested by previous call to .new_line(), write it
375+
// New line was already written
376+
self.start_tag.push_indent(i.additional(indent));
377+
indent
378+
}
379+
};
380+
// Save the indent that we should use next time when .new_line() be called
381+
self.state = AttributeIndent::SomeAttributesWritten(next_indent);
382+
}
383+
// write attribute
351384
self.start_tag.push_attribute(attr);
352385
self
353386
}
@@ -363,6 +396,64 @@ impl<'a, W> ElementWriter<'a, W> {
363396
self.start_tag.extend_attributes(attributes);
364397
self
365398
}
399+
400+
/// Push a new line inside an element.
401+
///
402+
/// # Examples
403+
///
404+
/// The following code
405+
///
406+
/// ```rust
407+
/// # use quick_xml::writer::Writer;
408+
/// let mut buffer = Vec::new();
409+
/// let mut writer = Writer::new_with_indent(&mut buffer, b' ', 2);
410+
/// writer
411+
/// .create_element("element")
412+
/// //.new_line() (1)
413+
/// .with_attribute("first", "1")
414+
/// .with_attribute("second", "2")
415+
/// .new_line()
416+
/// .with_attribute("third", "3")
417+
/// .with_attribute("fourth", "4")
418+
/// //.new_line() (2)
419+
/// .write_empty();
420+
/// ```
421+
/// will produce the following XMLs:
422+
/// ```xml
423+
/// <!-- result of the code above -->
424+
/// <element first="1" second="2"
425+
/// third="3" fourth="4"/>
426+
///
427+
/// <!-- if uncomment only (1) - indent depends on indentation
428+
/// settings - 2 spaces here -->
429+
/// <element
430+
/// first="1" second="2"
431+
/// third="3" fourth="4"/>
432+
///
433+
/// <!-- if uncomment only (2) -->
434+
/// <element first="1" second="2"
435+
/// third="3" fourth="4"
436+
/// />
437+
/// ```
438+
pub fn new_line(mut self) -> Self {
439+
if let Some(i) = self.writer.indent.as_mut() {
440+
match self.state {
441+
// .new_line() called just after .create_element().
442+
// Use element indent to additionally indent attributes
443+
AttributeIndent::NoneAttributesWritten => {
444+
self.state = AttributeIndent::Indent(i.indent_size)
445+
}
446+
// .new_line() called when .with_attribute() was called at least once.
447+
// Plan saved indent
448+
AttributeIndent::SomeAttributesWritten(indent) => {
449+
self.state = AttributeIndent::Indent(indent)
450+
}
451+
AttributeIndent::Indent(_) => {}
452+
}
453+
self.start_tag.push_newline();
454+
};
455+
self
456+
}
366457
}
367458

368459
impl<'a, W: Write> ElementWriter<'a, W> {
@@ -458,10 +549,7 @@ impl Indentation {
458549
/// Increase indentation by one level
459550
pub fn grow(&mut self) {
460551
self.current_indent_len += self.indent_size;
461-
if self.current_indent_len > self.indents.len() {
462-
self.indents
463-
.resize(self.current_indent_len, self.indent_char);
464-
}
552+
self.ensure(self.current_indent_len);
465553
}
466554

467555
/// Decrease indentation by one level. Do nothing, if level already zero
@@ -473,6 +561,19 @@ impl Indentation {
473561
pub fn current(&self) -> &[u8] {
474562
&self.indents[..self.current_indent_len]
475563
}
564+
565+
/// Returns indent with current indent plus additional indent
566+
pub fn additional(&mut self, additional_indent: usize) -> &[u8] {
567+
let new_len = self.current_indent_len + additional_indent;
568+
self.ensure(new_len);
569+
&self.indents[..new_len]
570+
}
571+
572+
fn ensure(&mut self, new_len: usize) {
573+
if new_len > self.indents.len() {
574+
self.indents.resize(new_len, self.indent_char);
575+
}
576+
}
476577
}
477578

478579
#[cfg(test)]
@@ -781,4 +882,109 @@ mod indentation {
781882
</outer>"#
782883
);
783884
}
885+
886+
mod in_attributes {
887+
use super::*;
888+
use pretty_assertions::assert_eq;
889+
890+
#[test]
891+
fn newline_first() {
892+
let mut buffer = Vec::new();
893+
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
894+
895+
writer
896+
.create_element("element")
897+
.new_line()
898+
.with_attribute(("first", "1"))
899+
.with_attribute(("second", "2"))
900+
.new_line()
901+
.with_attribute(("third", "3"))
902+
.with_attribute(("fourth", "4"))
903+
.write_empty()
904+
.expect("write tag failed");
905+
906+
assert_eq!(
907+
std::str::from_utf8(&buffer).unwrap(),
908+
r#"<element
909+
first="1" second="2"
910+
third="3" fourth="4"/>"#
911+
);
912+
}
913+
914+
#[test]
915+
fn newline_inside() {
916+
let mut buffer = Vec::new();
917+
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
918+
919+
writer
920+
.create_element("element")
921+
.with_attribute(("first", "1"))
922+
.with_attribute(("second", "2"))
923+
.new_line()
924+
.with_attribute(("third", "3"))
925+
.with_attribute(("fourth", "4"))
926+
.write_empty()
927+
.expect("write tag failed");
928+
929+
assert_eq!(
930+
std::str::from_utf8(&buffer).unwrap(),
931+
r#"<element first="1" second="2"
932+
third="3" fourth="4"/>"#
933+
);
934+
}
935+
936+
#[test]
937+
fn newline_last() {
938+
let mut buffer = Vec::new();
939+
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
940+
941+
writer
942+
.create_element("element")
943+
.new_line()
944+
.with_attribute(("first", "1"))
945+
.with_attribute(("second", "2"))
946+
.new_line()
947+
.with_attribute(("third", "3"))
948+
.with_attribute(("fourth", "4"))
949+
.new_line()
950+
.write_empty()
951+
.expect("write tag failed");
952+
953+
writer
954+
.create_element("element")
955+
.with_attribute(("first", "1"))
956+
.with_attribute(("second", "2"))
957+
.new_line()
958+
.with_attribute(("third", "3"))
959+
.with_attribute(("fourth", "4"))
960+
.new_line()
961+
.write_empty()
962+
.expect("write tag failed");
963+
964+
assert_eq!(
965+
std::str::from_utf8(&buffer).unwrap(),
966+
r#"<element
967+
first="1" second="2"
968+
third="3" fourth="4"
969+
/>
970+
<element first="1" second="2"
971+
third="3" fourth="4"
972+
/>"#
973+
);
974+
}
975+
976+
#[test]
977+
fn long_element_name() {
978+
let mut buffer = Vec::new();
979+
let mut writer = Writer::new_with_indent(&mut buffer, b' ', 4);
980+
981+
writer
982+
.create_element(String::from("x").repeat(128).as_str())
983+
.with_attribute(("first", "1"))
984+
.new_line()
985+
.with_attribute(("second", "2"))
986+
.write_empty()
987+
.expect("Problem with indentation reference");
988+
}
989+
}
784990
}

0 commit comments

Comments
 (0)