From ff290a5c3df4285a38ed3e8e84032206e0edb22c Mon Sep 17 00:00:00 2001 From: Artemiy Date: Fri, 19 Jan 2024 14:35:29 +0300 Subject: [PATCH] Add self-closed tag support for `to xml` (#11577) # Description This PR closes #11524 Add `to xml --self-closed` flag to output empty tags as self close. For example: ![image](https://github.com/nushell/nushell/assets/17511668/bdf040f7-8ac1-4e8b-80bb-0043d7cec7f9) # User-Facing Changes New `to xml` flag `--self-closed`. # Tests + Formatting Added new example for `to xml` command and new test for self-closed tags. --- crates/nu-command/src/formats/to/xml.rs | 52 ++++++++++++++----- .../tests/format_conversions/xml.rs | 19 +++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index 34bcaebfa1..f455f2e4f3 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -34,6 +34,11 @@ impl Command for ToXml { "Only escape mandatory characters in text and attributes", Some('p'), ) + .switch( + "self-closed", + "Output empty tags as self closing", + Some('s'), + ) .category(Category::Formats) } @@ -77,6 +82,13 @@ Additionally any field which is: empty record, empty list or null, can be omitte result: Some(Value::test_string( r#""'"# )) + }, + Example { + description: "Save space using self-closed tags", + example: r#"{tag: root content: [[tag]; [a] [b] [c]]} | to xml --self-closed"#, + result: Some(Value::test_string( + r#""# + )) } ] } @@ -95,8 +107,9 @@ Additionally any field which is: empty record, empty list or null, can be omitte let head = call.head; let indent: Option> = call.get_flag(engine_state, stack, "indent")?; let partial_escape = call.has_flag(engine_state, stack, "partial-escape")?; + let self_closed = call.has_flag(engine_state, stack, "self-closed")?; - let job = Job::new(indent, partial_escape); + let job = Job::new(indent, partial_escape, self_closed); let input = input.try_expand_range()?; job.run(input, head) } @@ -105,10 +118,11 @@ Additionally any field which is: empty record, empty list or null, can be omitte struct Job { writer: quick_xml::Writer>>, partial_escape: bool, + self_closed: bool, } impl Job { - fn new(indent: Option>, partial_escape: bool) -> Self { + fn new(indent: Option>, partial_escape: bool, self_closed: bool) -> Self { let writer = indent.as_ref().map_or_else( || quick_xml::Writer::new(Cursor::new(Vec::new())), |p| quick_xml::Writer::new_with_indent(Cursor::new(Vec::new()), b' ', p.item as usize), @@ -117,6 +131,7 @@ impl Job { Self { writer, partial_escape, + self_closed, } } @@ -424,12 +439,18 @@ impl Job { }); } + let self_closed = self.self_closed && children.is_empty(); let attributes = Self::parse_attributes(attrs)?; - let mut open_tag_event = BytesStart::new(tag.clone()); - self.add_attributes(&mut open_tag_event, &attributes); + let mut open_tag = BytesStart::new(tag.clone()); + self.add_attributes(&mut open_tag, &attributes); + let open_tag_event = if self_closed { + Event::Empty(open_tag) + } else { + Event::Start(open_tag) + }; self.writer - .write_event(Event::Start(open_tag_event)) + .write_event(open_tag_event) .map_err(|_| ShellError::CantConvert { to_type: "XML".to_string(), from_type: Type::Record(vec![]).to_string(), @@ -441,15 +462,18 @@ impl Job { .into_iter() .try_for_each(|child| self.write_xml_entry(child, false))?; - let close_tag_event = BytesEnd::new(tag); - self.writer - .write_event(Event::End(close_tag_event)) - .map_err(|_| ShellError::CantConvert { - to_type: "XML".to_string(), - from_type: Type::Record(vec![]).to_string(), - span: entry_span, - help: Some("Failure writing tag to xml".into()), - }) + if !self_closed { + let close_tag_event = Event::End(BytesEnd::new(tag)); + self.writer + .write_event(close_tag_event) + .map_err(|_| ShellError::CantConvert { + to_type: "XML".to_string(), + from_type: Type::Record(vec![]).to_string(), + span: entry_span, + help: Some("Failure writing tag to xml".into()), + })?; + } + Ok(()) } fn parse_attributes(attrs: Record) -> Result, ShellError> { diff --git a/crates/nu-command/tests/format_conversions/xml.rs b/crates/nu-command/tests/format_conversions/xml.rs index 37ff49875a..cf84371e45 100644 --- a/crates/nu-command/tests/format_conversions/xml.rs +++ b/crates/nu-command/tests/format_conversions/xml.rs @@ -91,3 +91,22 @@ fn to_xml_pi_comment_not_escaped() { )); assert_eq!(actual.out, r#"&?>"#); } + +#[test] +fn to_xml_self_closed() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + { + tag: root + content: [ + [tag attributes content]; + [a null null] + [b {e: r} null] + [c {t: y} []] + ] + } | to xml --self-closed + "# + )); + assert_eq!(actual.out, r#""#); +}