use crate::schema::XmlSchema;
use std::fmt::Write;
use std::collections::HashMap;
#[derive(Debug)]
struct Element {
position: Vec<usize>,
value: String,
cdata: bool,
}
pub(crate) struct Builder {
elements: Vec<Element>,
schema: XmlSchema,
current_key: Vec<usize>,
key_list: HashMap::<(usize,String),usize>,
key_count: usize,
pub xml_output: String,
attribute_list: HashMap::<(usize,usize),String>,
headers: Vec<String>,
ind_original_header: bool,
}
pub struct ChainFromAdd<'a>
{
builder: &'a mut Builder,
no_element: usize,
no_key: usize
}
impl<'a> ChainFromAdd<'a> {
pub(crate) fn attributes(self, attributes: &[(&str, &str)]) {
let mut all_attributes = String::new();
for &(att, value) in attributes {
write!(all_attributes, " {}=\"{}\"", att, value).unwrap();
}
match self.builder.attribute_list.get(&(self.no_element, self.no_key)) {
Some(value) => {
if &all_attributes != value {
panic!("Tried to add a second set of attributes to the same key/element");
}
}
None => {
self.builder
.attribute_list
.insert((self.no_element, self.no_key), all_attributes.clone());
}
}
}
pub fn attribute<V: ToString>(self, name: &str, value: V) {
let mut all_attributes = String::new();
let value_quoted = format!("\"{}\"", value.to_string());
let combined = format!(" {}={}", name, value_quoted);
all_attributes.push_str(&combined);
match self.builder.attribute_list.get(&(self.no_element, self.no_key)) {
Some(existing) => {
if &all_attributes != existing {
panic!("Tried to add a second set of attributes to the same key/element");
}
},
None => {
self.builder.attribute_list.insert((self.no_element, self.no_key), all_attributes.clone());
}
}
}
pub fn cdata(self) -> Self {
self.builder.elements.last_mut().unwrap().cdata = true;
self
}
}
impl Builder {
pub(crate) fn clear_headers(&mut self) {
self.headers.clear();
}
pub(crate) fn custom_header(&mut self, headers: &str) {
if self.ind_original_header == true
{ self.headers.clear(); }
self.headers.push(format!("<?{}?>", headers));
self.ind_original_header = false;
}
pub(crate) fn new() -> Self
{
Self {
elements: vec![Element { position: Vec::new(), value: "".to_string(), cdata: false }],
schema: XmlSchema::new(),
current_key: Vec::new(),
key_list: HashMap::new(),
key_count: 0,
xml_output: String::new(),
attribute_list: HashMap::new(),
headers: vec!["<?xml version=\"1.0\" encoding=\"UTF-8\"?>".to_string()],
ind_original_header: true,
}
}
pub(crate) fn set_schema(&mut self,txt_schema: &str)
{
self.schema.set_schema(txt_schema);
self.schema.parse_schema();
self.current_key.resize(self.schema.element_no_lookup.len(), 0);
}
pub(crate) fn get_position(&self,nm_element: &str) -> &Vec<usize>
{
self.schema.element_no_lookup
.get(nm_element)
.expect("Tried to add an element that does not exist in the schema")
}
#[allow(unused_must_use)]
pub(crate) fn set_key(&mut self, nm_element: &str, txt_key: &str) -> ChainFromAdd {
let position = self.get_position(nm_element);
let &no_element = position.last().unwrap();
let returned_key: usize;
if let Some(&existing) = self.key_list.get(&(no_element, txt_key.to_string())) {
self.current_key[no_element] = existing;
returned_key = existing;
} else {
self.key_count += 1;
self.key_list.insert((no_element, txt_key.to_string()), self.key_count);
self.current_key[no_element] = self.key_count;
returned_key = self.key_count;
}
ChainFromAdd { builder: self, no_element, no_key: returned_key }
}
pub(crate) fn clear_key(&mut self)
{
self.current_key.fill(0) ;
}
#[allow(unused_must_use)]
pub(crate) fn add_element(&mut self, nm_element: &str, value_element: &str) -> ChainFromAdd {
self.key_count += 1;
let key_count = self.key_count;
let final_value = if value_element.is_empty() {
" ".to_string()
} else {
value_element.to_string()
};
let (positition_and_key, element_number) = {
let position = self.get_position(nm_element);
(create_position(&position, &self.current_key), *position.last().unwrap())
};
self.elements.push(Element { position: positition_and_key, value: final_value, cdata: false });
ChainFromAdd { builder: self, no_element: element_number, no_key: key_count }
}
pub(crate) fn build_xml(&mut self)
{
for i in &self.headers {
write!(self.xml_output, "{}\n", i).unwrap();
}
for i in &mut self.schema.element_names {
*i = i.split('!').next().unwrap().to_string();
}
self.elements.sort_unstable_by(|a, b| a.position.cmp(&b.position));
self.elements.push(Element { position: Vec::new(), value: "".to_string(), cdata: false });
let mut opening_tags: Vec<(usize,usize,usize)> = Vec::new();
for n in 1..self.elements.len() {
let last = &self.elements[n - 1];
let current = &self.elements[n];
let len = last.position.len().max(current.position.len());
opening_tags.clear();
for i in (0..len/2).rev() {
let l = (last.position.get(2*i),last.position.get(2*i+1));
let c = (current.position.get(2*i),current.position.get(2*i+1));
if l != c && l.0 != None {
let elem_name = &self.schema.element_names[*l.0.unwrap()];
let indent = if i == last.position.len()/2 - 1 {
"".to_string()
} else {
" ".repeat(i)
};
write!(self.xml_output, "{}</{}>\n", indent, elem_name).unwrap();
}
if l != c && c.0 != None { opening_tags.push((*c.0.unwrap(),*c.1.unwrap(),i)) ; }
}
for &n in opening_tags.iter().skip(1).rev() {
let elem_name = &self.schema.element_names[n.0];
let attribute = self.attribute_list.get(&(n.0,n.1)) ;
let open_tag = format!("{}<{}{}>\n", " ".repeat(n.2), elem_name, attribute.unwrap_or(&"".to_string()));
self.xml_output.push_str(&open_tag);
}
if !current.value.is_empty() {
let elem_name = &self.schema.element_names[opening_tags[0].0];
let attribute = &self.attribute_list.get(&(opening_tags[0].0,opening_tags[0].1)) ;
if self.elements[n].cdata {
write!(
self.xml_output,
"{}<{}{}><![CDATA[{}]]>",
" ".repeat(opening_tags.first().unwrap().2),
elem_name,
attribute.unwrap_or(&"".to_string()),
self.elements[n].value
).unwrap();
} else {
write!(
self.xml_output,
"{}<{}{}>{}",
" ".repeat(opening_tags.first().unwrap().2),
elem_name,
attribute.unwrap_or(&"".to_string()),
escape_xml(&self.elements[n].value)
).unwrap();
}
}
}
}
}
fn escape_xml(input: &str) -> String {
let mut escaped = String::with_capacity(input.len());
for c in input.chars() {
match c {
'&' => escaped.push_str("&"),
'<' => escaped.push_str("<"),
'>' => escaped.push_str(">"),
'"' => escaped.push_str("""),
'\'' => escaped.push_str("'"),
_ => escaped.push(c),
}
}
escaped
}
fn create_position(position: &Vec<usize>, key: &Vec<usize>) -> Vec<usize> {
let len = position.len().max(key.len());
let mut combined = Vec::with_capacity(len * 2);
for i in position {
let key_val = *key.get(*i).unwrap();
combined.push(*i);
combined.push(key_val);
}
combined
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_key()
{
let mut xb: Builder = Builder::new();
xb.set_schema(
"<root>
<g1></g1>
<g2><g3><g4></g4></g3></g2></root>");
for i in &xb.schema.element_no_lookup
{
println!("{:?}",i);
}
xb.set_key("g2", "1");
println!("{:?}",xb.current_key);
xb.set_key("g2", "1");
println!("{:?}",xb.current_key);
xb.set_key("g2", "2");
println!("{:?}",xb.current_key);
xb.set_key("g1", "1");
println!("{:?}",xb.current_key);
xb.set_key("g1", "1");
println!("{:?}",xb.current_key);
xb.set_key("g1", "2");
println!("{:?}",xb.current_key);
xb.set_key("g2", "3");
println!("{:?}",xb.current_key);
xb.set_key("g2", "4");
println!("{:?}",xb.current_key);
xb.set_key("g4", "1");
println!("{:?}",xb.current_key);
xb.set_key("root", "0");
println!("{:?}",xb.current_key);
xb.add_element("g4", "999");
println!("{:?}",xb.elements[0] );
xb.clear_key();
println!("{:?}",xb.current_key);
xb.build_xml();
}
}