diet_xml/
builder.rs

1// use crate::schema::XmlSchema;
2use crate::schema::XmlSchema;
3use std::fmt::Write;
4use std::collections::HashMap;
5
6#[derive(Debug)]
7struct Element {
8        position: Vec<usize>,
9        value: String,       
10        cdata: bool,
11         
12}
13
14
15pub(crate) struct Builder {
16    elements: Vec<Element>,
17    schema: XmlSchema,
18    current_key: Vec<usize>,
19    //lookup  String value of key, must be unique to (no_element, no_unique_key)
20    key_list: HashMap::<(usize,String),usize>,
21    key_count: usize,
22    pub xml_output: String,
23    attribute_list: HashMap::<(usize,usize),String>,
24
25    headers: Vec<String>,
26    ind_original_header: bool,
27
28    
29    
30}
31
32pub struct ChainFromAdd<'a>
33{
34    builder: &'a mut Builder,
35    no_element: usize,
36    no_key: usize
37
38
39}
40
41impl<'a> ChainFromAdd<'a> {
42    pub(crate) fn attributes(self, attributes: &[(&str, &str)]) {
43        // Build all attributes from the pair values
44        let mut all_attributes = String::new();
45        for &(att, value) in attributes {
46            write!(all_attributes, " {}=\"{}\"", att, value).unwrap();
47        }
48
49        match self.builder.attribute_list.get(&(self.no_element, self.no_key)) {
50            Some(value) => {
51                if &all_attributes != value {
52                    panic!("Tried to add a second set of attributes to the same key/element");
53                }
54                // Same attributes added again are just ignored and okay
55            }
56            None => {
57                self.builder
58                    .attribute_list
59                    .insert((self.no_element, self.no_key), all_attributes.clone());
60            }
61        }
62    }
63    
64    pub fn attribute<V: ToString>(self, name: &str, value: V) {
65        // Build a single attribute as a pair, accepting any value that implements ToString
66        let mut all_attributes = String::new();
67        let value_quoted = format!("\"{}\"", value.to_string());
68        let combined = format!(" {}={}", name, value_quoted);
69        all_attributes.push_str(&combined);
70        match self.builder.attribute_list.get(&(self.no_element, self.no_key)) {
71            Some(existing) => {
72                if &all_attributes != existing {
73                    panic!("Tried to add a second set of attributes to the same key/element");
74                }
75                // same added again is just ignored and ok
76            },
77            None => {
78                self.builder.attribute_list.insert((self.no_element, self.no_key), all_attributes.clone());
79            }
80        }
81    }
82
83    pub fn cdata(self) -> Self {
84       
85        self.builder.elements.last_mut().unwrap().cdata = true;
86        self
87    }
88}
89
90
91
92
93
94
95
96
97
98
99impl Builder {
100
101
102    
103    pub(crate) fn clear_headers(&mut self) {
104        self.headers.clear();
105    }
106
107    pub(crate) fn custom_header(&mut self, headers: &str) {
108        if self.ind_original_header == true
109        {       self.headers.clear(); }
110        self.headers.push(format!("<?{}?>", headers));
111        self.ind_original_header = false;
112    }
113
114
115
116     pub(crate) fn new() -> Self
117    {   
118       
119        Self {
120         elements: vec![Element { position: Vec::new(), value: "".to_string(), cdata: false }],
121         schema:  XmlSchema::new(),
122         current_key: Vec::new(),
123         key_list: HashMap::new(),
124         key_count: 0,
125         xml_output: String::new(),
126         attribute_list: HashMap::new(),
127         headers: vec!["<?xml version=\"1.0\" encoding=\"UTF-8\"?>".to_string()],
128            ind_original_header: true,
129        }
130    }
131
132    pub(crate) fn set_schema(&mut self,txt_schema: &str)
133    {
134        self.schema.set_schema(txt_schema);
135        self.schema.parse_schema();
136        self.current_key.resize(self.schema.element_no_lookup.len(), 0);
137    }
138
139    pub(crate) fn get_position(&self,nm_element: &str) -> &Vec<usize>
140    {
141     self.schema.element_no_lookup
142    .get(nm_element)
143    .expect("Tried to add an element that does not exist in the schema")
144    }
145
146
147        
148 #[allow(unused_must_use)]   
149    pub(crate) fn set_key(&mut self, nm_element: &str, txt_key: &str) -> ChainFromAdd {
150        let position = self.get_position(nm_element);
151        let &no_element = position.last().unwrap();
152        let returned_key: usize;
153        if let Some(&existing) = self.key_list.get(&(no_element, txt_key.to_string())) {
154            self.current_key[no_element] = existing;
155            returned_key = existing;
156        } else {
157            self.key_count += 1;
158            self.key_list.insert((no_element, txt_key.to_string()), self.key_count);
159            self.current_key[no_element] = self.key_count;
160            returned_key = self.key_count;
161        }
162        ChainFromAdd { builder: self, no_element, no_key: returned_key }
163    }
164
165      
166
167    pub(crate) fn clear_key(&mut self)
168    {
169        self.current_key.fill(0)  ;
170    }
171
172    #[allow(unused_must_use)]
173    pub(crate) fn add_element(&mut self, nm_element: &str, value_element: &str) -> ChainFromAdd {
174        self.key_count += 1;
175        let key_count = self.key_count;
176        let final_value = if value_element.is_empty() {
177            " ".to_string()
178        } else {
179            value_element.to_string()
180        };
181        let (positition_and_key, element_number) = {
182            let position = self.get_position(nm_element);
183            (create_position(&position, &self.current_key), *position.last().unwrap())
184        };
185        self.elements.push(Element { position: positition_and_key, value: final_value, cdata: false });
186        ChainFromAdd { builder: self, no_element: element_number, no_key: key_count }
187    }
188
189    // combines position and key into a combined alternative Vec, this is what is sorted at teh end to create the final output
190
191    pub(crate) fn build_xml(&mut self)
192    {
193    
194    for i in &self.headers {
195        write!(self.xml_output, "{}\n", i).unwrap();
196    }
197
198     for i in &mut self.schema.element_names {
199    *i = i.split('!').next().unwrap().to_string();
200    }
201        // add dummy row at end to enable last lags to be closed off
202        // sort so element are all groups together , ordered by elements and keys
203        self.elements.sort_unstable_by(|a, b| a.position.cmp(&b.position));
204        self.elements.push(Element { position: Vec::new(), value: "".to_string(), cdata: false });
205
206        let mut opening_tags: Vec<(usize,usize,usize)> = Vec::new();
207for n in 1..self.elements.len() {
208    let last = &self.elements[n - 1];
209    let current =  &self.elements[n];
210   // println!("Last element is: {:?}",last);
211   // println!("Current element is: {:?}",current);
212
213    let len = last.position.len().max(current.position.len());
214
215    // used to stored information for opening tags on pass through closing tags
216    // (no_element, no_key, i (depth))
217    
218    opening_tags.clear();
219    
220    for i in (0..len/2).rev()  {
221    
222    
223    let l = (last.position.get(2*i),last.position.get(2*i+1));     
224    let c = (current.position.get(2*i),current.position.get(2*i+1));    
225
226   // println!("Last pair is: {:?}",l);
227  //  println!("Current pair is: {:?}",c);
228
229    if l != c && l.0 != None {
230   
231        let elem_name = &self.schema.element_names[*l.0.unwrap()];
232    
233        let indent = if i == last.position.len()/2 - 1 {
234    "".to_string()
235} else {
236    "  ".repeat(i)
237};
238          
239   
240       
241         write!(self.xml_output, "{}</{}>\n", indent, elem_name).unwrap();
242   // print!("{}", open_tag);
243  
244    } 
245        //record opening tags on same pass through
246        if l != c && c.0 != None {  opening_tags.push((*c.0.unwrap(),*c.1.unwrap(),i)) ;   }    
247  
248    } 
249
250        // opening tags need to open from lowest level upwards so we need to iterate in reverse
251
252       for &n in opening_tags.iter().skip(1).rev() {
253  
254      let elem_name = &self.schema.element_names[n.0];
255
256
257     // fetch any attribute associated with the element,key combination else
258     let attribute = self.attribute_list.get(&(n.0,n.1)) ;
259
260     
261
262    let open_tag = format!("{}<{}{}>\n", "  ".repeat(n.2),  elem_name, attribute.unwrap_or(&"".to_string()));
263     self.xml_output.push_str(&open_tag);
264   // print!("{}", open_tag);
265}
266
267// After printing all opening tags, print the value (if any)
268// the check is necessary due to the last dummy element, maybe change this later on
269if !current.value.is_empty() {
270    let elem_name = &self.schema.element_names[opening_tags[0].0];
271    let attribute = &self.attribute_list.get(&(opening_tags[0].0,opening_tags[0].1))  ;
272
273    if self.elements[n].cdata {
274        write!(
275            self.xml_output,
276            "{}<{}{}><![CDATA[{}]]>",
277            "  ".repeat(opening_tags.first().unwrap().2),
278            elem_name,
279            attribute.unwrap_or(&"".to_string()),
280            self.elements[n].value
281            
282        ).unwrap();
283    } else {
284        write!(
285            self.xml_output,
286            "{}<{}{}>{}",
287            "  ".repeat(opening_tags.first().unwrap().2),
288            elem_name,
289            attribute.unwrap_or(&"".to_string()),
290            escape_xml(&self.elements[n].value)
291            
292        ).unwrap();
293    }
294  // self.xml_output.push_str(&open_tag);
295   // print!("{}", open_tag);
296}
297
298
299    }
300
301
302    }
303}
304
305
306fn escape_xml(input: &str) -> String {
307    let mut escaped = String::with_capacity(input.len());
308    for c in input.chars() {
309        match c {
310            '&' => escaped.push_str("&amp;"),
311            '<' => escaped.push_str("&lt;"),
312            '>' => escaped.push_str("&gt;"),
313            '"' => escaped.push_str("&quot;"),
314            '\'' => escaped.push_str("&apos;"),
315            _ => escaped.push(c),
316        }
317    }
318    escaped
319}
320
321
322
323
324 fn create_position(position: &Vec<usize>, key: &Vec<usize>) -> Vec<usize> {
325    let len = position.len().max(key.len());
326    let mut combined = Vec::with_capacity(len * 2);
327    for i in position {
328        
329        let key_val = *key.get(*i).unwrap();
330        // Combine however you want; here, for example, just sum:
331        combined.push(*i);
332        combined.push(key_val);
333        // Or if you want to keep both, you could push one or the other, etc.
334    }
335    combined
336}
337
338
339
340
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345/*/
346   //#[test]
347   // changed functionality invalid test
348    fn test_create_position()
349    {
350        //placehodler
351        // update this test now an key list of all elements is used rather than
352        // just level depth keys
353        let position: Vec<usize> = [1,2,3,4,5,6].to_vec(); 
354        let key: Vec<usize> = [0,1,2,3,4].to_vec();
355
356        let combined  =  create_position(&position, &key);
357
358        assert_eq!(combined,[1,0,2,1,3,2,4,3,5,4,6,0].to_vec() )
359
360    }
361*/
362    #[test]
363    fn test_set_key()
364    {
365        let mut xb: Builder = Builder::new();
366        xb.set_schema(
367            "<root>
368                        <g1></g1>
369                        <g2><g3><g4></g4></g3></g2></root>");
370        
371           for i in  &xb.schema.element_no_lookup
372        {
373            println!("{:?}",i);
374        }
375        xb.set_key("g2", "1");     
376        println!("{:?}",xb.current_key);
377        xb.set_key("g2", "1");
378        println!("{:?}",xb.current_key);
379        xb.set_key("g2", "2");
380        println!("{:?}",xb.current_key);
381
382        xb.set_key("g1", "1");     
383        println!("{:?}",xb.current_key);
384        xb.set_key("g1", "1");
385        println!("{:?}",xb.current_key);
386        xb.set_key("g1", "2");
387        println!("{:?}",xb.current_key);
388
389        xb.set_key("g2", "3");
390        println!("{:?}",xb.current_key);
391        xb.set_key("g2", "4");
392        println!("{:?}",xb.current_key);
393
394        xb.set_key("g4", "1");
395        println!("{:?}",xb.current_key);
396
397        xb.set_key("root", "0");
398        println!("{:?}",xb.current_key);
399
400        xb.add_element("g4", "999");
401        println!("{:?}",xb.elements[0] );
402
403        xb.clear_key();
404        println!("{:?}",xb.current_key);
405
406
407        xb.build_xml();
408       
409
410
411
412    }
413
414
415}