├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── src ├── builder.rs ├── lib.rs ├── utils.rs ├── xml.rs ├── xmlcontent.rs ├── xmlelement.rs ├── xmlerror.rs └── xmlversion.rs └── tests └── tests.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build-debug: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Build 18 | run: cargo build --verbose 19 | 20 | build-release: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Build 25 | run: cargo build --verbose --release 26 | 27 | clippy: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - run: rustup component add clippy 32 | - uses: actions-rs/clippy-check@v1 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | args: --all-features 36 | 37 | tests: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Run tests 42 | run: cargo test --verbose -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["LIAUD Corentin "] 3 | categories = ["data-structures"] 4 | description = "Easy and highly-configurable XML builder/writer" 5 | edition = "2021" 6 | keywords = ["xml"] 7 | license = "MIT" 8 | name = "xml-builder" 9 | readme = "README.md" 10 | repository = "https://github.com/cocool97/xml-builder" 11 | version = "0.5.4" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xml-builder 2 | 3 | [![Documentation](https://docs.rs/xml-builder/badge.svg)](https://docs.rs/xml-builder) 4 | [![Latest version](https://img.shields.io/crates/v/xml-builder.svg)](https://crates.io/crates/xml-builder) 5 | [![dependency status](https://deps.rs/repo/github/cocool97/xml-builder/status.svg)](https://deps.rs/repo/github/cocool97/xml-builder) 6 | 7 | This crate allows you to easily create an XML file in a short time by building a highly-configurable object tree. 8 | 9 | ## Main advantages 10 | 11 | This crate offers many advantages over other XML-building crates : 12 | 13 | * Fast and easy XML documents creation 14 | * Low size, suits fine for embedeed systems 15 | * Does not depend on other crates 16 | * Highly configurable 17 | * No unsafe code, it integrates the `#![forbid(unsafe_code)]` lint directive 18 | 19 | ## Main features 20 | 21 | Using this crate can bring you many useful features : 22 | 23 | * Element attributes sorting 24 | * XML indentation, or not 25 | * Custom XML versions 26 | * Custom XML encodings 27 | 28 | ## Usage 29 | 30 | To use this crate you just need to add this to your `Cargo.toml` file: 31 | 32 | ```toml 33 | [dependencies] 34 | xml-builder = "*" 35 | ``` 36 | 37 | ## Examples 38 | 39 | ```rust 40 | use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; 41 | 42 | let mut xml = XMLBuilder::new() 43 | .version(XMLVersion::XML1_1) 44 | .encoding("UTF-8".into()) 45 | .build(); 46 | 47 | let mut house = XMLElement::new("house"); 48 | house.add_attribute("rooms", "2"); 49 | 50 | for i in 1..=2 { 51 | let mut room = XMLElement::new("room"); 52 | room.add_attribute("number", &i.to_string()); 53 | room.add_text(format!("This is room number {}", i)).unwrap(); 54 | house.add_child(room).unwrap(); 55 | } 56 | 57 | xml.set_root_element(house); 58 | 59 | let mut writer: Vec = Vec::new(); 60 | xml.generate(&mut writer).unwrap(); 61 | ``` 62 | 63 | This following XML content will then be displayed: 64 | 65 | ```xml 66 | 67 | 68 | This is room number 1 69 | This is room number 2 70 | 71 | ``` 72 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{XMLVersion, XML}; 2 | 3 | /// Builder structure used to generate a custom XML structure. 4 | pub struct XMLBuilder { 5 | /// The XML version to set for the document. 6 | /// 7 | /// Defaults to `XML1.0`. 8 | version: XMLVersion, 9 | 10 | /// The encoding to set for the document. 11 | /// 12 | /// Defaults to `UTF-8`. 13 | encoding: String, 14 | 15 | /// XML standalone attribute. 16 | /// 17 | /// A `None` value indicates no displaying. 18 | /// 19 | /// Defaults to `None` 20 | standalone: Option, 21 | 22 | /// Whether we want to indentate the document. 23 | /// 24 | /// Defaults to `true`. 25 | indent: bool, 26 | 27 | /// Whether the XML attributes should be sorted or not. 28 | /// 29 | /// Defaults to `false`. 30 | sort_attributes: bool, 31 | 32 | /// Whether we want to break lines or not. 33 | /// 34 | /// Defaults to `true`. 35 | break_lines: bool, 36 | 37 | /// Whether we want to expand empty tags or not. 38 | /// 39 | /// Defaults to `false`. 40 | expand_empty_tags: bool, 41 | } 42 | 43 | impl Default for XMLBuilder { 44 | fn default() -> Self { 45 | Self { 46 | version: XMLVersion::XML1_0, 47 | encoding: "UTF-8".into(), 48 | standalone: None, 49 | indent: true, 50 | sort_attributes: false, 51 | break_lines: true, 52 | expand_empty_tags: false, 53 | } 54 | } 55 | } 56 | 57 | impl XMLBuilder { 58 | /// Builds a new XMLBuilder 59 | pub fn new() -> Self { 60 | Self::default() 61 | } 62 | 63 | /// Sets the XML version attribute field. 64 | /// 65 | /// # Arguments 66 | /// 67 | /// `version` - An enum value representing the new version to use for the XML. 68 | pub fn version(mut self, version: XMLVersion) -> Self { 69 | self.version = version; 70 | 71 | self 72 | } 73 | 74 | /// Sets the XML encoding attribute field. 75 | /// 76 | /// # Arguments 77 | /// 78 | /// `encoding` - A String representing the encoding to use for the document. 79 | pub fn encoding(mut self, encoding: String) -> Self { 80 | self.encoding = encoding; 81 | 82 | self 83 | } 84 | 85 | /// Sets the standalone attribute for this XML document. 86 | pub fn standalone(mut self, standalone: Option) -> Self { 87 | self.standalone = standalone; 88 | 89 | self 90 | } 91 | 92 | /// Sets the XML indentation. 93 | pub fn indent(mut self, indent: bool) -> Self { 94 | self.indent = indent; 95 | 96 | self 97 | } 98 | 99 | /// Enables attributes sorting. 100 | pub fn sort_attributes(mut self, sort: bool) -> Self { 101 | self.sort_attributes = sort; 102 | 103 | self 104 | } 105 | 106 | /// Sets whether to break lines. 107 | pub fn break_lines(mut self, break_lines: bool) -> Self { 108 | self.break_lines = break_lines; 109 | 110 | self 111 | } 112 | 113 | /// Sets whether to expand empty tags. 114 | pub fn expand_empty_tags(mut self, expand_empty_tags: bool) -> Self { 115 | self.expand_empty_tags = expand_empty_tags; 116 | 117 | self 118 | } 119 | 120 | /// Builds a new XML structure by consuming self. 121 | pub fn build(self) -> XML { 122 | XML::new( 123 | self.version, 124 | self.encoding, 125 | self.standalone, 126 | self.indent, 127 | self.sort_attributes, 128 | self.break_lines, 129 | self.expand_empty_tags, 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "lib"] 2 | #![forbid(unsafe_code)] 3 | #![forbid(missing_docs)] 4 | #![doc = include_str!("../README.md")] 5 | 6 | mod builder; 7 | mod utils; 8 | mod xml; 9 | mod xmlcontent; 10 | mod xmlelement; 11 | mod xmlerror; 12 | mod xmlversion; 13 | 14 | pub use builder::XMLBuilder; 15 | pub use xml::XML; 16 | pub use xmlelement::XMLElement; 17 | pub use xmlerror::{Result, XMLError}; 18 | pub use xmlversion::XMLVersion; 19 | 20 | use utils::escape_str; 21 | use xmlcontent::XMLElementContent; 22 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn escape_str(input: &str) -> String { 2 | input 3 | .to_owned() 4 | .replace('&', "&") 5 | .replace('"', """) 6 | .replace('\'', "'") 7 | .replace('<', "<") 8 | .replace('>', ">") 9 | } 10 | -------------------------------------------------------------------------------- /src/xml.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{Result, XMLElement, XMLVersion}; 4 | 5 | /// Structure representing a XML document. 6 | /// It must be used to create a XML document. 7 | pub struct XML { 8 | /// The XML version to set for the document. 9 | /// 10 | /// Defaults to `XML1.0`. 11 | version: XMLVersion, 12 | 13 | /// The encoding to set for the document. 14 | /// 15 | /// Defaults to `UTF-8`. 16 | encoding: String, 17 | 18 | /// XML standalone attribute. 19 | /// 20 | /// A `None` value indicates no displaying. 21 | /// 22 | /// Defaults to `None` 23 | standalone: Option, 24 | 25 | /// Whether the XML attributes should be sorted or not. 26 | /// 27 | /// Defaults to `false`. 28 | sort_attributes: bool, 29 | 30 | /// Whether we want to indentate the document. 31 | /// 32 | /// Defaults to `true`. 33 | indent: bool, 34 | 35 | /// Whether we want to break lines or not. 36 | /// 37 | /// Defaults to `true`. 38 | break_lines: bool, 39 | 40 | /// Whether we want to expand empty tags or not. 41 | /// 42 | /// Defaults to `false`. 43 | expand_empty_tags: bool, 44 | 45 | /// The root XML element. 46 | root: Option, 47 | } 48 | 49 | impl XML { 50 | pub(crate) fn new( 51 | version: XMLVersion, 52 | encoding: String, 53 | standalone: Option, 54 | indent: bool, 55 | sort_attributes: bool, 56 | break_lines: bool, 57 | expand_empty_tags: bool, 58 | ) -> Self { 59 | Self { 60 | version, 61 | encoding, 62 | standalone, 63 | indent, 64 | sort_attributes, 65 | break_lines, 66 | expand_empty_tags, 67 | root: None, 68 | } 69 | } 70 | 71 | /// Sets the XML document root element. 72 | /// 73 | /// # Arguments 74 | /// 75 | /// `element` - An XMLElement qualified as root for the XML document. 76 | pub fn set_root_element(&mut self, element: XMLElement) { 77 | self.root = Some(element); 78 | } 79 | 80 | /// Generates an XML document into the specified `Writer`. 81 | /// 82 | /// Consumes the XML object. 83 | pub fn generate(self, mut writer: W) -> Result<()> { 84 | let standalone_attribute = match self.standalone { 85 | Some(_) => r#" standalone="yes""#.to_string(), 86 | None => String::default(), 87 | }; 88 | let suffix = match self.break_lines { 89 | true => "\n", 90 | false => "", 91 | }; 92 | 93 | write!( 94 | writer, 95 | r#"{}"#, 96 | self.version, self.encoding, standalone_attribute, suffix 97 | )?; 98 | 99 | // And then XML elements if present... 100 | if let Some(elem) = &self.root { 101 | elem.render( 102 | &mut writer, 103 | self.sort_attributes, 104 | self.indent, 105 | self.break_lines, 106 | self.expand_empty_tags, 107 | )?; 108 | } 109 | 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/xmlcontent.rs: -------------------------------------------------------------------------------- 1 | use crate::XMLElement; 2 | 3 | /// An enum value representing the types of XML contents 4 | pub(crate) enum XMLElementContent { 5 | /// No XML content. 6 | Empty, 7 | 8 | /// The content is a list of XML elements. 9 | Elements(Vec), 10 | 11 | /// The content is a textual string. 12 | Text(String), 13 | } 14 | -------------------------------------------------------------------------------- /src/xmlelement.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{escape_str, Result, XMLElementContent, XMLError}; 4 | 5 | /// Structure representing an XML element field. 6 | pub struct XMLElement { 7 | /// The name of the XML element. 8 | name: String, 9 | 10 | /// A list of tuple representing (key, value) attributes. 11 | attributes: Vec<(String, String)>, 12 | 13 | /// A boolean representing whether we want attributes to be sorted. 14 | /// 15 | /// If not set, defaults to the root's `XMLELement`. 16 | sort_attributes: Option, 17 | 18 | /// The content of this XML element. 19 | content: XMLElementContent, 20 | } 21 | 22 | impl XMLElement { 23 | /// Instantiates a new XMLElement object. 24 | /// 25 | /// # Arguments 26 | /// 27 | /// * `name` - A string slice that holds the name of the XML element. 28 | pub fn new(name: &str) -> Self { 29 | XMLElement { 30 | name: name.into(), 31 | attributes: Vec::new(), 32 | sort_attributes: None, 33 | content: XMLElementContent::Empty, 34 | } 35 | } 36 | 37 | /// Enables attributes sorting. 38 | pub fn enable_attributes_sorting(&mut self) { 39 | self.sort_attributes = Some(true); 40 | } 41 | 42 | /// Disables attributes sorting. 43 | pub fn disable_attributes_sorting(&mut self) { 44 | self.sort_attributes = Some(false); 45 | } 46 | 47 | /// Adds the given name/value attribute to the XMLElement. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// * `name` - A string slice that holds the name of the attribute 52 | /// * `value` - A string slice that holds the value of the attribute 53 | pub fn add_attribute(&mut self, name: &str, value: &str) { 54 | self.attributes.push((name.into(), escape_str(value))); 55 | } 56 | 57 | /// Adds a new XMLElement child object to the references XMLElement. 58 | /// 59 | /// Raises `XMLError` if trying to add a child to a text XMLElement. 60 | /// 61 | /// # Arguments 62 | /// 63 | /// * `element` - A XMLElement object to add as child 64 | pub fn add_child(&mut self, element: XMLElement) -> Result<()> { 65 | match self.content { 66 | XMLElementContent::Empty => { 67 | self.content = XMLElementContent::Elements(vec![element]); 68 | } 69 | XMLElementContent::Elements(ref mut e) => { 70 | e.push(element); 71 | } 72 | XMLElementContent::Text(_) => { 73 | return Err(XMLError::InsertError( 74 | "Cannot insert child inside an element with text".into(), 75 | )) 76 | } 77 | }; 78 | 79 | Ok(()) 80 | } 81 | 82 | /// Adds text content to a XMLElement object. 83 | /// 84 | /// Raises `XMLError` if trying to add text to a non-empty object. 85 | /// 86 | /// # Arguments 87 | /// 88 | /// * `text` - A string containing the text to add to the object 89 | pub fn add_text(&mut self, text: String) -> Result<()> { 90 | match self.content { 91 | XMLElementContent::Empty => { 92 | self.content = XMLElementContent::Text(text); 93 | } 94 | _ => { 95 | return Err(XMLError::InsertError( 96 | "Cannot insert text in a non-empty element".into(), 97 | )) 98 | } 99 | }; 100 | 101 | Ok(()) 102 | } 103 | 104 | /// Internal method rendering attribute list to a String. 105 | /// 106 | /// # Arguments 107 | /// 108 | /// * `should_sort` - A boolean indicating whether we should sort these atttibutes. 109 | fn attributes_as_string(&self, should_sort: bool) -> String { 110 | if self.attributes.is_empty() { 111 | String::default() 112 | } else { 113 | let mut attributes = self.attributes.clone(); 114 | 115 | // Giving priority to the element boolean, and taking the global xml if not set 116 | let should_sort_attributes = self.sort_attributes.unwrap_or(should_sort); 117 | 118 | if should_sort_attributes { 119 | attributes.sort(); 120 | } 121 | 122 | let mut result = String::new(); 123 | 124 | for (k, v) in &attributes { 125 | result = format!(r#"{} {}="{}""#, result, k, v); 126 | } 127 | result 128 | } 129 | } 130 | 131 | /// Renders an XMLElement object into the specified writer implementing Write trait. 132 | /// 133 | /// Does not take ownership of the object. 134 | /// 135 | /// # Arguments 136 | /// 137 | /// * `writer` - An object to render the referenced XMLElement to 138 | pub fn render( 139 | &self, 140 | writer: &mut W, 141 | should_sort: bool, 142 | should_indent: bool, 143 | should_break_lines: bool, 144 | should_expand_empty_tags: bool, 145 | ) -> Result<()> { 146 | self.render_level( 147 | writer, 148 | 0, 149 | should_sort, 150 | should_indent, 151 | should_break_lines, 152 | should_expand_empty_tags, 153 | ) 154 | } 155 | 156 | /// Internal method rendering and indenting a XMLELement object 157 | /// 158 | /// # Arguments 159 | /// 160 | /// * `writer` - An object to render the referenced XMLElement to 161 | /// * `level` - An usize representing the depth of the XML tree. Used to indent the object. 162 | fn render_level( 163 | &self, 164 | writer: &mut W, 165 | level: usize, 166 | should_sort: bool, 167 | should_indent: bool, 168 | should_break_lines: bool, 169 | should_expand_empty_tags: bool, 170 | ) -> Result<()> { 171 | let indent = match should_indent { 172 | true => "\t".repeat(level), 173 | false => "".into(), 174 | }; 175 | let suffix = match should_break_lines { 176 | true => "\n", 177 | false => "", 178 | }; 179 | 180 | let attributes = self.attributes_as_string(should_sort); 181 | 182 | match &self.content { 183 | XMLElementContent::Empty => match should_expand_empty_tags { 184 | true => { 185 | write!( 186 | writer, 187 | "{}<{}{}>{}", 188 | indent, self.name, attributes, self.name, suffix 189 | )?; 190 | } 191 | false => { 192 | write!( 193 | writer, 194 | "{}<{}{} />{}", 195 | indent, self.name, attributes, suffix 196 | )?; 197 | } 198 | }, 199 | XMLElementContent::Elements(elements) => { 200 | write!(writer, "{}<{}{}>{}", indent, self.name, attributes, suffix)?; 201 | for elem in elements { 202 | elem.render_level( 203 | writer, 204 | level + 1, 205 | should_sort, 206 | should_indent, 207 | should_break_lines, 208 | should_expand_empty_tags, 209 | )?; 210 | } 211 | write!(writer, "{}{}", indent, self.name, suffix)?; 212 | } 213 | XMLElementContent::Text(text) => { 214 | write!( 215 | writer, 216 | "{}<{}{}>{}{}", 217 | indent, self.name, attributes, text, self.name, suffix 218 | )?; 219 | } 220 | }; 221 | 222 | Ok(()) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/xmlerror.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::{Debug, Display}; 3 | 4 | /// Custom `Result` type thrown by this crate. 5 | pub type Result = std::result::Result; 6 | 7 | /// Error type thrown by this crate 8 | pub enum XMLError { 9 | /// Thrown when the given element cannot be inserted into the XML object tree. 10 | InsertError(String), 11 | /// Thrown when the given `Writer` cannot be written to. 12 | IOError(String), 13 | } 14 | 15 | impl From for XMLError { 16 | fn from(e: std::io::Error) -> Self { 17 | XMLError::IOError(e.to_string()) 18 | } 19 | } 20 | 21 | impl Debug for XMLError { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | match self { 24 | XMLError::InsertError(e) => write!(f, "Error encountered during insertion: {}", e), 25 | XMLError::IOError(e) => write!(f, "Error encountered during write: {}", e), 26 | } 27 | } 28 | } 29 | 30 | impl Display for XMLError { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{self:?}") 33 | } 34 | } 35 | 36 | impl Error for XMLError {} 37 | -------------------------------------------------------------------------------- /src/xmlversion.rs: -------------------------------------------------------------------------------- 1 | /// Enum representing all currently available XML versions. 2 | pub enum XMLVersion { 3 | /// XML version 1.0. First definition in 1998. 4 | XML1_0, 5 | 6 | /// XML version 1.1. First definition in 2004. 7 | XML1_1, 8 | } 9 | 10 | impl std::fmt::Display for XMLVersion { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | match self { 13 | XMLVersion::XML1_0 => write!(f, "1.0"), 14 | XMLVersion::XML1_1 => write!(f, "1.1"), 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; 2 | 3 | #[test] 4 | fn test_xml_default_creation() { 5 | let xml = XMLBuilder::new().build(); 6 | let writer = std::io::sink(); 7 | xml.generate(writer).unwrap(); 8 | } 9 | 10 | #[test] 11 | fn test_xml_file_write() { 12 | let xml = XMLBuilder::new().build(); 13 | 14 | let mut writer: Vec = Vec::new(); 15 | xml.generate(&mut writer).unwrap(); 16 | } 17 | 18 | #[test] 19 | fn test_xml_version() { 20 | let xml = XMLBuilder::new().version(XMLVersion::XML1_1).build(); 21 | 22 | let mut writer: Vec = Vec::new(); 23 | xml.generate(&mut writer).unwrap(); 24 | 25 | let expected = "\n"; 26 | let res = std::str::from_utf8(&writer).unwrap(); 27 | 28 | assert_eq!(res, expected, "Both values does not match..."); 29 | } 30 | 31 | #[test] 32 | fn test_xml_encoding() { 33 | let xml = XMLBuilder::new().encoding("UTF-16".into()).build(); 34 | 35 | let mut writer: Vec = Vec::new(); 36 | xml.generate(&mut writer).unwrap(); 37 | 38 | let expected = "\n"; 39 | let res = std::str::from_utf8(&writer).unwrap(); 40 | 41 | assert_eq!(res, expected, "Both values does not match..."); 42 | } 43 | 44 | #[test] 45 | fn test_indent() { 46 | let mut xml = XMLBuilder::new().indent(false).build(); 47 | 48 | let mut root = XMLElement::new("root"); 49 | let first_element_inside = XMLElement::new("indentation"); 50 | let second_element_inside = XMLElement::new("indentation"); 51 | 52 | root.add_child(first_element_inside).unwrap(); 53 | root.add_child(second_element_inside).unwrap(); 54 | 55 | xml.set_root_element(root); 56 | 57 | let mut writer: Vec = Vec::new(); 58 | xml.generate(&mut writer).unwrap(); 59 | 60 | let expected = " 61 | 62 | 63 | 64 | \n"; 65 | let res = std::str::from_utf8(&writer).unwrap(); 66 | 67 | assert_eq!(res, expected, "Both values does not match..."); 68 | } 69 | 70 | #[test] 71 | fn test_line_breaks() { 72 | let mut xml = XMLBuilder::new().break_lines(false).build(); 73 | 74 | let mut root = XMLElement::new("root"); 75 | let element = XMLElement::new("element"); 76 | 77 | root.add_child(element).unwrap(); 78 | xml.set_root_element(root); 79 | 80 | let mut writer: Vec = Vec::new(); 81 | xml.generate(&mut writer).unwrap(); 82 | 83 | let expected = "\t"; 84 | let res = std::str::from_utf8(&writer).unwrap(); 85 | 86 | assert_eq!(res, expected, "Both values does not match..."); 87 | } 88 | 89 | #[test] 90 | fn test_expand_empty_tags() { 91 | let mut xml = XMLBuilder::new().expand_empty_tags(true).build(); 92 | 93 | let mut root = XMLElement::new("root"); 94 | let element = XMLElement::new("element"); 95 | 96 | root.add_child(element).unwrap(); 97 | 98 | xml.set_root_element(root); 99 | 100 | let mut writer: Vec = Vec::new(); 101 | xml.generate(&mut writer).unwrap(); 102 | 103 | let expected = 104 | "\n\n\t\n\n"; 105 | let res = std::str::from_utf8(&writer).unwrap(); 106 | 107 | assert_eq!(res, expected, "Both values does not match..."); 108 | } 109 | 110 | #[test] 111 | fn test_xml_version_1_0() { 112 | let xml = XMLBuilder::new().version(XMLVersion::XML1_0).build(); 113 | 114 | let mut writer: Vec = Vec::new(); 115 | xml.generate(&mut writer).unwrap(); 116 | 117 | let expected = "\n"; 118 | let res = std::str::from_utf8(&writer).unwrap(); 119 | 120 | assert_eq!(res, expected, "Both values does not match..."); 121 | } 122 | 123 | #[test] 124 | fn test_xml_version_1_1() { 125 | let xml = XMLBuilder::new().version(XMLVersion::XML1_1).build(); 126 | 127 | let mut writer: Vec = Vec::new(); 128 | xml.generate(&mut writer).unwrap(); 129 | 130 | let expected = "\n"; 131 | let res = std::str::from_utf8(&writer).unwrap(); 132 | 133 | assert_eq!(res, expected, "Both values does not match..."); 134 | } 135 | 136 | #[test] 137 | #[should_panic] 138 | fn test_panic_child_for_text_element() { 139 | let xml = XMLBuilder::new().build(); 140 | 141 | let mut xml_child = XMLElement::new("panic"); 142 | xml_child 143 | .add_text("This should panic right after this...".into()) 144 | .unwrap(); 145 | 146 | let xml_child2 = XMLElement::new("sorry"); 147 | xml_child.add_child(xml_child2).unwrap(); 148 | 149 | xml.generate(std::io::stdout()).unwrap(); 150 | } 151 | 152 | #[test] 153 | fn test_complex_xml() { 154 | let mut xml = XMLBuilder::new() 155 | .version(XMLVersion::XML1_1) 156 | .encoding("UTF-8".into()) 157 | .build(); 158 | 159 | let mut house = XMLElement::new("house"); 160 | house.add_attribute("rooms", "2"); 161 | 162 | for i in 1..=2 { 163 | let mut room = XMLElement::new("room"); 164 | room.add_attribute("number", &i.to_string()); 165 | room.add_text(format!("This is room number {}", i)).unwrap(); 166 | 167 | house.add_child(room).unwrap(); 168 | } 169 | 170 | xml.set_root_element(house); 171 | 172 | let mut writer: Vec = Vec::new(); 173 | xml.generate(&mut writer).unwrap(); 174 | 175 | let expected = " 176 | 177 | \tThis is room number 1 178 | \tThis is room number 2 179 | \n"; 180 | let res = std::str::from_utf8(&writer).unwrap(); 181 | 182 | assert_eq!(res, expected, "Both values does not match...") 183 | } 184 | 185 | // Here the `sort` attribute is set to the root, so everything should be sorted 186 | #[test] 187 | fn test_complex_sorted_root_xml() { 188 | let mut xml = XMLBuilder::new() 189 | .sort_attributes(true) 190 | .version(XMLVersion::XML1_1) 191 | .encoding("UTF-8".into()) 192 | .build(); 193 | 194 | let mut house = XMLElement::new("house"); 195 | house.add_attribute("rooms", "2"); 196 | 197 | for i in 1..=2 { 198 | let mut room = XMLElement::new("room"); 199 | room.add_attribute("size", &(i * 27).to_string()); 200 | room.add_attribute("number", &i.to_string()); 201 | room.add_text(format!("This is room number {}", i)).unwrap(); 202 | 203 | house.add_child(room).unwrap(); 204 | } 205 | 206 | xml.set_root_element(house); 207 | 208 | let mut writer: Vec = Vec::new(); 209 | xml.generate(&mut writer).unwrap(); 210 | 211 | let expected = " 212 | 213 | \tThis is room number 1 214 | \tThis is room number 2 215 | \n"; 216 | let res = std::str::from_utf8(&writer).unwrap(); 217 | 218 | assert_eq!(res, expected, "Both values does not match...") 219 | } 220 | 221 | // Here the `sort` attribute is set to the an element only, so everything should not be sorted 222 | #[test] 223 | fn test_complex_sorted_element_xml() { 224 | let mut xml = XMLBuilder::new() 225 | .version(XMLVersion::XML1_1) 226 | .encoding("UTF-8".into()) 227 | .standalone(Some(true)) 228 | .build(); 229 | 230 | let mut house = XMLElement::new("house"); 231 | house.add_attribute("rooms", "2"); 232 | 233 | for i in 1..=2 { 234 | let mut room = XMLElement::new("room"); 235 | room.add_attribute("size", &(i * 27).to_string()); 236 | room.add_attribute("city", ["Paris", "LA"][i - 1]); 237 | room.add_attribute("number", &i.to_string()); 238 | room.add_text(format!("This is room number {}", i)).unwrap(); 239 | 240 | if i % 2 == 0 { 241 | room.enable_attributes_sorting(); 242 | } 243 | 244 | house.add_child(room).unwrap(); 245 | } 246 | 247 | xml.set_root_element(house); 248 | 249 | let mut writer: Vec = Vec::new(); 250 | xml.generate(&mut writer).unwrap(); 251 | 252 | let expected = " 253 | 254 | \tThis is room number 1 255 | \tThis is room number 2 256 | \n"; 257 | 258 | let res = std::str::from_utf8(&writer).unwrap(); 259 | 260 | assert_eq!(res, expected, "Both values does not match...") 261 | } 262 | --------------------------------------------------------------------------------