use crate::{utils::HashMap, Document, Node};
use super::Element;
#[derive(Debug, Clone, PartialEq, Eq)]
enum NewNodes {
Element(ElementBuilder),
Text(String),
Comment(String),
CData(String),
PI(String),
}
impl NewNodes {
fn push_to(self, doc: &mut Document, parent: Element) {
let result = match self {
NewNodes::Element(elem) => {
let elem = elem.finish(doc);
parent.push_child(doc, Node::Element(elem))
}
NewNodes::Text(text) => parent.push_child(doc, Node::Text(text)),
NewNodes::Comment(text) => parent.push_child(doc, Node::Comment(text)),
NewNodes::CData(text) => parent.push_child(doc, Node::CData(text)),
NewNodes::PI(text) => parent.push_child(doc, Node::PI(text)),
};
if let Err(e) = result {
panic!("Illegal Parameter put in ElementBuilder: {:?}", e);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElementBuilder {
full_name: String,
attributes: HashMap<String, String>,
namespace_decls: HashMap<String, String>,
content: Vec<NewNodes>,
}
macro_rules! push_node {
(
$(
$(#[$docs:meta])*
$fn_name:ident => $node:ident
),*
) => {
$(
$(#[$docs])*
pub fn $fn_name<S: Into<String>>(self, text: S) -> Self {
self.push_node(NewNodes::$node(text.into()))
}
)*
};
}
impl ElementBuilder {
pub fn new(full_name: impl Into<String>) -> ElementBuilder {
ElementBuilder::new_with_capacities(full_name, 0, 0, 0)
}
pub fn new_with_capacities(
full_name: impl Into<String>,
attribute_capacity: usize,
namespace_capacity: usize,
content_capacity: usize,
) -> ElementBuilder {
ElementBuilder {
full_name: full_name.into(),
attributes: HashMap::with_capacity(attribute_capacity),
namespace_decls: HashMap::with_capacity(namespace_capacity),
content: Vec::with_capacity(content_capacity),
}
}
pub fn prefix(mut self, prefix: &str) -> Self {
let (_, name) = Element::separate_prefix_name(&self.full_name);
if prefix.is_empty() {
self.full_name = name.to_string();
} else {
self.full_name = format!("{}{}", prefix, name);
}
self
}
pub fn attribute<S, T>(mut self, name: S, value: T) -> Self
where
S: Into<String>,
T: Into<String>,
{
self.attributes.insert(name.into(), value.into());
self
}
pub fn namespace_decl<S, T>(mut self, prefix: S, namespace: T) -> Self
where
S: Into<String>,
T: Into<String>,
{
self.namespace_decls.insert(prefix.into(), namespace.into());
self
}
fn push_node(mut self, node: NewNodes) -> Self {
self.content.push(node);
self
}
push_node![
add_text => Text,
add_comment => Comment,
add_cdata => CData,
add_pi => PI
];
pub fn add_element(mut self, elem: ElementBuilder) -> Self {
self.content.push(NewNodes::Element(elem));
self
}
pub fn create_element<F>(self, name: impl Into<String>, f: F) -> Self
where
F: FnOnce(ElementBuilder) -> ElementBuilder,
{
let builder = f(ElementBuilder::new(name));
self.add_element(builder)
}
#[must_use]
pub fn finish(self, doc: &mut Document) -> Element {
let Self {
full_name,
attributes,
namespace_decls,
content,
} = self;
let elem = Element::with_data(doc, full_name, attributes, namespace_decls);
for node in content {
node.push_to(doc, elem);
}
elem
}
pub fn push_to(self, doc: &mut Document, parent: Element) -> Element {
let elem = self.finish(doc);
parent
.push_child_element(doc, elem)
.expect("Illegal Parameter put in ElementBuilder");
elem
}
pub fn push_to_root_node(self, doc: &mut Document) -> Element {
let elem = self.finish(doc);
doc.push_root_node(Node::Element(elem))
.expect("Illegal Parameter put in ElementBuilder");
elem
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Document;
fn start() -> Document {
crate::utils::tests::setup_logger();
Document::new()
}
#[test]
fn test_element_builder() -> anyhow::Result<()> {
let mut doc = start();
let element = ElementBuilder::new("root")
.attribute("id", "main")
.attribute("class", "main")
.create_element("tests", |new| {
new.create_element("child", |new| new.add_text("Hello"))
})
.add_comment("This is a comment")
.push_to_root_node(&mut doc);
let new_element = ElementBuilder::new("hello")
.add_text("world")
.finish(&mut doc);
element.push_child(&mut doc, new_element)?;
let to_string = doc.write_str()?;
println!("{}", to_string);
Ok(())
}
}