From 4eec99e49dae565ca6b7973b4342e44eed92106b Mon Sep 17 00:00:00 2001 From: Oror Date: Thu, 19 Mar 2026 13:05:55 +0100 Subject: [PATCH] Added mixed children XML specification allows to have as many children[0], both text and elements, as the user desires. The program should not panic when mixing them. [0]: https://www.w3.org/TR/xml/#NT-content --- src/xmlcontent.rs | 6 ++- src/xmlelement.rs | 97 ++++++++++++++++++++++++++++++++--------------- tests/tests.rs | 26 +++++++++---- 3 files changed, 91 insertions(+), 38 deletions(-) diff --git a/src/xmlcontent.rs b/src/xmlcontent.rs index 64fcc67..e9b6f3c 100644 --- a/src/xmlcontent.rs +++ b/src/xmlcontent.rs @@ -7,7 +7,11 @@ pub enum XMLElementContent { Empty, /// The content is a list of XML elements. - Elements(Vec), + Mixed(Vec), + + /// The content is an XML element. + /// Needs to be boxed to avoid infinite size. + Element(Box), /// The content is a textual string. Text(String), diff --git a/src/xmlelement.rs b/src/xmlelement.rs index a3d1ebd..63baf39 100644 --- a/src/xmlelement.rs +++ b/src/xmlelement.rs @@ -1,6 +1,6 @@ use std::io::Write; -use crate::{Result, XMLElementContent, XMLError, escape_str}; +use crate::{Result, XMLElementContent, escape_str}; /// Structure representing an XML element field. #[derive(Clone)] @@ -58,23 +58,28 @@ impl XMLElement { /// Adds a new `XMLElement` child object to the references `XMLElement`. /// - /// Raises `XMLError` if trying to add a child to a text `XMLElement`. - /// /// # Arguments /// /// * `element` - A `XMLElement` object to add as child pub fn add_child(&mut self, element: Self) -> Result<()> { match self.content { XMLElementContent::Empty => { - self.content = XMLElementContent::Elements(vec![element]); + self.content = XMLElementContent::Element(Box::new(element)) + } + XMLElementContent::Text(ref s) => { + self.content = XMLElementContent::Mixed(vec![ + XMLElementContent::Text(s.clone()), + XMLElementContent::Element(Box::new(element)), + ]); } - XMLElementContent::Elements(ref mut e) => { - e.push(element); + XMLElementContent::Element(ref b) => { + self.content = XMLElementContent::Mixed(vec![ + XMLElementContent::Element(b.clone()), + XMLElementContent::Element(Box::new(element)), + ]); } - XMLElementContent::Text(_) => { - return Err(XMLError::InsertError( - "Cannot insert child inside an element with text".into(), - )); + XMLElementContent::Mixed(ref mut v) => { + v.push(XMLElementContent::Element(Box::new(element))); } } @@ -90,13 +95,21 @@ impl XMLElement { /// * `text` - A string containing the text to add to the object pub fn add_text(&mut self, text: String) -> Result<()> { match self.content { - XMLElementContent::Empty => { - self.content = XMLElementContent::Text(text); + XMLElementContent::Empty => self.content = XMLElementContent::Text(text), + XMLElementContent::Text(ref s) => { + self.content = XMLElementContent::Mixed(vec![ + XMLElementContent::Text(s.clone()), + XMLElementContent::Text(text), + ]); + } + XMLElementContent::Element(ref b) => { + self.content = XMLElementContent::Mixed(vec![ + XMLElementContent::Element(b.clone()), + XMLElementContent::Text(text), + ]); } - _ => { - return Err(XMLError::InsertError( - "Cannot insert text in a non-empty element".into(), - )); + XMLElementContent::Mixed(ref mut v) => { + v.push(XMLElementContent::Text(text)); } } @@ -194,29 +207,53 @@ impl XMLElement { indent, self.name, attributes, suffix )?; } - } - XMLElementContent::Elements(elements) => { + }, + XMLElementContent::Element(element) => { write!(writer, "{}<{}{}>{}", indent, self.name, attributes, suffix)?; - for elem in elements { - elem.render_level( - writer, - level + 1, - should_sort, - should_indent, - should_break_lines, - should_expand_empty_tags, - )?; - } + element.render_level( + writer, + level + 1, + should_sort, + should_indent, + should_break_lines, + should_expand_empty_tags)?; write!(writer, "{}{}", indent, self.name, suffix)?; - } + }, XMLElementContent::Text(text) => { write!( writer, "{}<{}{}>{}{}", indent, self.name, attributes, text, self.name, suffix )?; + }, + XMLElementContent::Mixed(children) => { + let higher_indent = "\t".to_string() + &indent; + write!(writer, "{}<{}{}>{}", indent, self.name, attributes, suffix)?; + for content in children { + match content { + // Should be unreachable + XMLElementContent::Empty => {}, + // Should be unreachable + XMLElementContent::Mixed(_) => {}, + + XMLElementContent::Element(element) => { + element.render_level( + writer, + level + 1, + should_sort, + should_indent, + should_break_lines, + should_expand_empty_tags, + )?; + } + XMLElementContent::Text(text) => { + write!(writer, "{}{}{}", higher_indent, text, suffix)?; + } + }; + } + write!(writer, "{}{}", indent, self.name, suffix)?; } - } + }; Ok(()) } diff --git a/tests/tests.rs b/tests/tests.rs index efa51ff..5279d9b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -133,19 +133,31 @@ fn test_xml_version_1_1() { } #[test] -#[should_panic(expected = "Cannot insert child inside an element with text")] -fn test_panic_child_for_text_element() { - let xml = XMLBuilder::new().build(); +fn test_complex_mixed_children() { + let mut xml = XMLBuilder::new().build(); - let mut xml_child = XMLElement::new("panic"); + let mut xml_child = XMLElement::new("mixed"); xml_child - .add_text("This should panic right after this...".into()) + .add_text("Let's start with a text".into()) .unwrap(); + xml_child.add_attribute("complexity", "much"); - let xml_child2 = XMLElement::new("sorry"); + let xml_child2 = XMLElement::new("element_then"); xml_child.add_child(xml_child2).unwrap(); - xml.generate(std::io::stdout()).unwrap(); + xml.set_root_element(xml_child); + + let mut writer: Vec = Vec::new(); + xml.generate(&mut writer).unwrap(); + + let expected = " + +\tLet's start with a text +\t +\n"; + let res = std::str::from_utf8(&writer).unwrap(); + + assert_eq!(res, expected, "Both values does not match...") } #[test]