xml-builder 0.5.2

Easy and highly-configurable XML builder/writer
Documentation
use std::io::Write;

use crate::{escape_str, Result, XMLElementContent, XMLError};

/// Structure representing an XML element field.
pub struct XMLElement {
    /// The name of the XML element.
    name: String,

    /// A list of tuple representing (key, value) attributes.
    attributes: Vec<(String, String)>,

    /// A boolean representing whether we want attributes to be sorted.
    ///
    /// If not set, defaults to the root's `XMLELement`.
    sort_attributes: Option<bool>,

    /// The content of this XML element.
    content: XMLElementContent,
}

impl XMLElement {
    /// Instantiates a new XMLElement object.
    ///
    /// # Arguments
    ///
    /// * `name` - A string slice that holds the name of the XML element.
    pub fn new(name: &str) -> Self {
        XMLElement {
            name: name.into(),
            attributes: Vec::new(),
            sort_attributes: None,
            content: XMLElementContent::Empty,
        }
    }

    /// Enables attributes sorting.
    pub fn enable_attributes_sorting(&mut self) {
        self.sort_attributes = Some(true);
    }

    /// Disables attributes sorting.
    pub fn disable_attributes_sorting(&mut self) {
        self.sort_attributes = Some(false);
    }

    /// Adds the given name/value attribute to the XMLElement.
    ///
    /// # Arguments
    ///
    /// * `name` - A string slice that holds the name of the attribute
    /// * `value` - A string slice that holds the value of the attribute
    pub fn add_attribute(&mut self, name: &str, value: &str) {
        self.attributes.push((name.into(), escape_str(value)));
    }

    /// 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: XMLElement) -> Result<()> {
        match self.content {
            XMLElementContent::Empty => {
                self.content = XMLElementContent::Elements(vec![element]);
            }
            XMLElementContent::Elements(ref mut e) => {
                e.push(element);
            }
            XMLElementContent::Text(_) => {
                return Err(XMLError::InsertError(
                    "Cannot insert child inside an element with text".into(),
                ))
            }
        };

        Ok(())
    }

    /// Adds text content to a XMLElement object.
    ///
    /// Raises `XMLError` if trying to add text to a non-empty object.
    ///
    /// # Arguments
    ///
    /// * `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);
            }
            _ => {
                return Err(XMLError::InsertError(
                    "Cannot insert text in a non-empty element".into(),
                ))
            }
        };

        Ok(())
    }

    /// Internal method rendering attribute list to a String.
    ///
    /// # Arguments
    ///
    /// * `should_sort` - A boolean indicating whether we should sort these atttibutes.
    fn attributes_as_string(&self, should_sort: bool) -> String {
        if self.attributes.is_empty() {
            String::default()
        } else {
            let mut attributes = self.attributes.clone();

            // Giving priority to the element boolean, and taking the global xml if not set
            let should_sort_attributes = self.sort_attributes.unwrap_or(should_sort);

            if should_sort_attributes {
                attributes.sort();
            }

            let mut result = String::new();

            for (k, v) in &attributes {
                result = format!(r#"{} {}="{}""#, result, k, v);
            }
            result
        }
    }

    /// Renders an XMLElement object into the specified writer implementing Write trait.
    ///
    /// Does not take ownership of the object.
    ///
    /// # Arguments
    ///
    /// * `writer` - An object to render the referenced XMLElement to
    pub fn render<W: Write>(
        &self,
        writer: &mut W,
        should_sort: bool,
        should_indent: bool,
    ) -> Result<()> {
        self.render_level(writer, 0, should_sort, should_indent)
    }

    /// Internal method rendering and indenting a XMLELement object
    ///
    /// # Arguments
    ///
    /// * `writer` - An object to render the referenced XMLElement to
    /// * `level` - An usize representing the depth of the XML tree. Used to indent the object.
    fn render_level<W: Write>(
        &self,
        writer: &mut W,
        level: usize,
        should_sort: bool,
        should_indent: bool,
    ) -> Result<()> {
        let indent = match should_indent {
            true => "\t".repeat(level),
            false => "".into(),
        };

        let attributes = self.attributes_as_string(should_sort);

        match &self.content {
            XMLElementContent::Empty => {
                writeln!(writer, "{}<{}{} />", indent, self.name, attributes)?;
            }
            XMLElementContent::Elements(elements) => {
                writeln!(writer, "{}<{}{}>", indent, self.name, attributes)?;
                for elem in elements {
                    elem.render_level(writer, level + 1, should_sort, should_indent)?;
                }
                writeln!(writer, "{}</{}>", indent, self.name)?;
            }
            XMLElementContent::Text(text) => {
                writeln!(
                    writer,
                    "{}<{}{}>{}</{}>",
                    indent, self.name, attributes, text, self.name
                )?;
            }
        };

        Ok(())
    }
}