ilmen_dot_parser/dot_parser/
dot_graph.rs

1use std::{collections::HashMap, fs::{read_to_string, File}, io::Write};
2
3use anyhow::Context;
4use log::{debug, info};
5use crate::dot_parser::attributs::Attributs;
6use super::{attribut::Attribut, edge::Edge, graph_type::GraphType, node::Node, parsing_error::ParsingError};
7
8#[derive(PartialEq,Clone, Eq)]
9#[cfg_attr(
10    feature = "serde",
11    derive(serde::Serialize, serde::Deserialize)
12)]
13pub struct DotGraph {
14    family: GraphType, 
15    nodes: Vec<Node>,
16    edges: Vec<Edge>,
17    sous_graphes: Vec<DotGraph>,
18    attributs: Attributs,
19    name: String
20}
21
22impl Default for DotGraph {
23    fn default() -> Self {
24        Self { 
25            family: GraphType::Graph, 
26            nodes: Default::default(), 
27            edges: Default::default(), 
28            sous_graphes: Default::default(), 
29            attributs: Default::default(), 
30            name: Default::default() }
31    }
32}
33
34impl DotGraph {
35    pub fn graph_from_file(path: &str) -> Result<DotGraph, ParsingError> {
36        info!("Opening graph from: {}", path);
37        let file = read_to_string(path)
38            .with_context(|| format!("Reading file {}", path))?;
39        
40        let cleaned_file = file.lines()
41            .map(|line| line.trim_ascii())
42            .filter(|line| !line.is_empty() || line.starts_with("//"))
43            .collect::<Vec<&str>>()
44            .join("\r\n");
45    
46        DotGraph::try_from(cleaned_file.as_str())
47    }
48
49
50    pub fn new(family: GraphType, nodes: Vec<Node>, edges: Vec<Edge>, sous_graphes: Vec<DotGraph>, attributs: Attributs, name: String) -> Self {
51        DotGraph {
52            family,
53            name,
54            nodes,
55            edges,
56            attributs,
57            sous_graphes
58        }
59    }
60    
61}
62
63
64// Create A graph from a valid DOT content
65impl TryFrom<&str> for DotGraph {
66    type Error = ParsingError;
67    fn try_from(content: &str) -> Result<Self, Self::Error> {
68        let mut cleaned_content = content.lines()
69            .map(clean_line)
70            .filter(|l| !l.is_empty() && !l.starts_with("//"))
71            .collect::<Vec<_>>().join(";")
72            .to_string();
73            
74        Self::create_graph(&mut cleaned_content, None)
75    }
76}
77
78
79impl DotGraph {
80
81    pub fn nodes(&self) ->Vec<Node> {
82        let mut nodes = self.nodes.clone();  
83        
84        nodes
85        .extend(
86            self.sous_graphes.iter().flat_map(|g| g.nodes.clone()));
87
88        nodes
89    }
90
91    pub fn edges(&self) -> Vec<Edge> {
92        let mut edges = self.edges.clone();  
93        edges.extend(self.sous_graphes.iter().flat_map(|g| g.edges.clone()));
94        edges
95    }
96
97    fn create_graph(content: &mut String, parent: Option<GraphType>)  -> Result<DotGraph, ParsingError>{
98        debug!("creating graph from: {}", content);
99        if content.trim().is_empty() {
100            return Ok(DotGraph::default());
101        }
102    
103        let head_and_body = content.split_once("{").ok_or(ParsingError::DefaultError("Pas de corps ?".to_string()))?;
104        let head = head_and_body.0;
105        
106        let type_graph = get_type_graph(head, parent)?;
107        let name = head.split_once(" ").map(|(_gtype,name)| name).unwrap_or("NoName").trim();
108        
109        let mut body = head_and_body.1.to_string();
110
111        let sous_graphes = Self::extract_subgraphes(&mut body, type_graph)?; 
112        body.pop(); // Popping last } for the cleanest body 
113
114        let mut attributs = HashMap::default();
115        let mut nodes =vec![];
116        let mut edges =vec![];
117        let mut default_node_attribute = Attributs::default();
118        let mut default_edge_attribute = Attributs::default();
119        
120        body
121            .split(";")
122            .map(clean_line)
123            .filter(|l| !l.is_empty())
124            .try_for_each(|line| {
125                // Edge if it got the arrow
126                if line.contains(&type_graph.symbol()) {
127                    let edge = Edge::try_from((line, type_graph.symbol().as_str()))?;
128                    edges.push(edge);
129                    return Ok(());
130                } 
131    
132                if line.contains("[") || !line.contains("=") {
133                    let node = Node::try_from(&line.to_string())?;
134                    if node.identifier == "node" {
135                        default_node_attribute=node.attributes
136                    } else if node.identifier == "edge" {
137                        default_edge_attribute=node.attributes
138                    } else {
139                        nodes.push(node);   
140                    }
141                    return Ok(());
142                }
143    
144                let att = Attribut::try_from(line)?;
145                attributs.insert(att.key, att.value);
146                Ok::<(), ParsingError>(())
147            })?;
148    
149            Ok(DotGraph {name: name.to_string(), family: type_graph, sous_graphes, nodes, edges, attributs: Attributs::from(attributs) })
150    }
151    
152    fn extract_subgraphes(body: &mut String, parent: GraphType) -> Result<Vec<DotGraph>, ParsingError> {
153        let mut sous_graphes_position = extract_subgraphes_position(body)?;
154    
155        let sous_graphes = sous_graphes_position
156                .iter()
157                .map(|(start,end)|Self::create_graph(&mut body[*start..*end+1].to_string(), Some(parent)))
158                .collect::<Result<Vec<DotGraph>, ParsingError>>()?;
159    
160        sous_graphes_position.reverse();
161        for i in sous_graphes_position {
162            body.replace_range(i.0..i.1+1, "");
163        }
164
165        Ok(sous_graphes)
166    }
167
168    pub fn name(&self) -> &String {
169        &self.name
170    }
171
172    pub fn write(&self, path: &str) -> Result<(), ParsingError> {
173        let content = self.as_dot_content();
174        let mut file = File::create(path).unwrap();
175        file.write_all(content.as_bytes()).unwrap();
176        Ok(())
177    }
178
179    fn as_dot_content(&self) -> String {
180        let mut content = String::default();
181
182        content = content + &self.family.to_string() + " " + &self.name + " { \r\n";
183
184        let nodes = self.nodes.iter().map(Node::to_string).fold(String::default(), |acc, node| acc +  "\r\n" + &node);   
185        let edges = self.edges.iter().map(Edge::to_string).fold(String::default(), |acc, edge| acc + "\r\n" + &edge);
186
187        content = content + &nodes + "\r\n\r\n" + &edges;
188
189        let subgraphes_string = self.sous_graphes.iter().map(DotGraph::as_dot_content).fold(String::default(), |acc, sous_graph| acc + &sous_graph + "\r\n"); 
190        
191
192        content + "\r\n\r\n" + &subgraphes_string + "\r\n}"
193    }
194}
195
196
197// Get the graph type from the first chars of content
198fn get_type_graph(content: &str, parent: Option<GraphType>) -> Result<GraphType, ParsingError> {
199    if content.starts_with("digraph") {
200        return Ok(GraphType::Digraph);
201    }
202
203    if content.starts_with("graph") {
204        return Ok(GraphType::Graph);
205    }
206
207    if content.starts_with("subgraph") {
208        return parent.ok_or(ParsingError::DefaultError("Should have a parent".to_string()));
209    }
210
211    Err(ParsingError::DefaultError("No graph type detected".to_string()))
212} 
213
214// Removing comments and trimming
215fn clean_line(line: &str) -> &str {
216    line.split_once("//").map(|a|a.0).unwrap_or(line).trim()
217}
218
219fn extract_subgraphes_position(inside_block: &str) -> Result<Vec<(usize, usize)>, ParsingError> {
220
221    let mut remaining = inside_block.to_string();
222
223    let mut sub_graphes_ranges = vec![];
224    let mut stack = 0;
225    while remaining.contains("subgraph"){
226        let start = remaining.find("subgraph").unwrap();
227        let end = next_block_range(&remaining)?.1;
228        sub_graphes_ranges.push((start+stack, end+stack));
229        stack =end+1;
230
231        remaining = remaining.split_at(end+1).1.to_string();
232    } 
233    Ok(sub_graphes_ranges)
234}
235
236fn next_block_range(block: &str) -> Result<(usize, usize), ParsingError>{
237    let mut stack = 0;
238    let mut index = 0;
239
240    let mut range : (Option<usize>, Option<usize>)= (None, None);
241    let mut chars = block.chars();
242    let mut next= chars.next();
243    while next.is_some() {
244        let char = next.unwrap();
245        
246        if char == '{' {
247            stack+=1;
248            if range.0.is_none() {
249                range.0 = Some(index);
250            }
251        }
252
253        if char == '}' {
254            stack -= 1;
255            if stack == 0 {
256                return match range.0 {
257                    Some(start) => Ok((start, index)),
258                    None => Err(ParsingError::DefaultError("Parsing exception error: no starting brackets".to_string()))
259                }
260            }
261            if stack < 0 {
262                return Err(ParsingError::DefaultError("Too many }".to_string()));
263            }
264        } 
265        index +=1;
266        next = chars.next();
267    }
268
269    Err(ParsingError::DefaultError("Missing ending }".to_string()))
270}
271
272
273#[cfg(test)]
274mod tests {
275    use std::{collections::HashMap, vec};
276
277    use super::*;
278
279    
280
281    #[test]
282    fn test_find_ending_pos_combinations() {
283        let combinations :Vec<(&str, (usize,usize))> = vec![
284            ("{test -> a;}", (0,11)),
285            ("{{}}", (0,3)),
286            ("{icitoutvabien}", (0,14)),
287            ("{{{{}}}}", (0,7)),
288            ("{{{{}}}}}", (0,7)),
289            ("graph Test {A;subgraph{D;}A->C}", (11, 30))
290            ];
291            
292        combinations.iter().for_each(|combinaisons| assert_eq!(next_block_range(combinaisons.0).unwrap(), combinaisons.1));
293    }
294
295    #[test]
296    fn test_find_ending_pos_combinations_ko() {
297        let combinations :Vec<(&str, &str)> = vec![
298            ("}test{}", "Too many }"),
299            ("{testt", "Missing ending }"),
300            ("{test{}", "Missing ending }")
301            ];
302            
303        combinations.iter().for_each(|combinaisons| assert_eq!(next_block_range(combinaisons.0).unwrap_err().to_string(), ParsingError::DefaultError(combinaisons.1.to_string()).to_string()));
304
305    } 
306
307
308    #[test]
309    fn graph_try_from() {
310        let input = "digraph Test {A; B [label=test, encore=toto]; A -> B;subgraph{C;D;C->D;}B -> A [label=\"to B\"];value=type;subgraph{C;D;C->D;}A->C;}";
311
312        let result = DotGraph::try_from(input).unwrap();
313        let mut map_attribut = HashMap::new();
314        map_attribut.insert("label".to_string(), "test".to_string());
315        map_attribut.insert("encore".to_string(), "toto".to_string());
316        assert_eq!(result.name, "Test".to_string());
317        assert_eq!(result.nodes, 
318            vec![
319                Node::new("A",Attributs::default()),
320                Node::new("B", Attributs::from(map_attribut))]);
321        assert_eq!(result.edges, 
322            vec![
323                Edge::try_from(("A->B", "->")).unwrap(),
324                Edge::try_from(("B->A[label=\"to B\"", "->")).unwrap(),
325                Edge::try_from(("A->C", "->")).unwrap()]);
326        assert_eq!(result.sous_graphes.len(), 2);
327    }
328
329    #[test]
330    fn graph_try_from_with_new_line_instead_of_semilicons() {
331        let input = "digraph Test {A\r\n B [label=test, encore=toto]; A -> B;subgraph{C;D\r\nC->D;}B -> A [label=\"to B\"]\r\nvalue=type;subgraph{C;D;C->D;}A->C;}";
332
333        let result = DotGraph::try_from(input).unwrap();
334        let mut map_attribut = HashMap::new();
335        map_attribut.insert("encore".to_string(), "toto".to_string());
336        map_attribut.insert("label".to_string(), "test".to_string());
337        assert_eq!(result.name, "Test".to_string());
338        assert_eq!(result.nodes, 
339            vec![
340                Node::new("A",Attributs::default()),
341                Node::new("B", Attributs::from(map_attribut))]);
342        assert_eq!(result.edges, 
343            vec![
344                Edge::try_from(("A->B", "->")).unwrap(),
345                Edge::try_from(("B->A[label=\"to B\"", "->")).unwrap(),
346                Edge::try_from(("A->C", "->")).unwrap()]);
347        assert_eq!(result.sous_graphes.len(), 2);
348    }
349
350
351    #[test]
352    fn extract_subgraphes_position_ok() {
353        let combinations :Vec<(&str,Vec<(usize, usize)>)> = vec![
354            ("subgraph{tetsautres}",vec![(0,19)]),
355            ("another what ?subgraph{tetsautres}", vec![(14,33)]),
356            ("subgraph{} subgraph{}",vec![(0,9), (11,20)]),
357            ("subgraph{E;} subgraph{H;}",vec![(0,11), (13,24)]),
358            ("encore un test subgraph{tetsautres} et au subgraph{ } voila du boulout", vec![(15,34),(42,52)]),
359            ("subgraph{C;D;C->D;}\r\n", vec![(0,18)]),
360            ("no sub grhaph", vec![])
361            ];
362
363        combinations.iter()
364            .for_each(|combinaisons| 
365                {
366                    let result = extract_subgraphes_position(combinaisons.0).unwrap();
367                    assert_eq!(result, combinaisons.1);
368                }
369            );
370    } 
371
372    #[test]
373    fn extract_subgraphes_ok() {
374        let combinations :Vec<(&str,usize, &str)> = vec![
375            ("subgraph{A->B}", 1, ""),
376            ("another what ?subgraph{C->D}", 1, "another what ?"),
377            ("subgraph{E;} subgraph{H;}",2, " "),
378            ("encore un test subgraph {G->D;} et au subgraph {A;} voila du boulout", 2, "encore un test  et au  voila du boulout"),
379            ("subgraph{X;Y;Z->E;}\r\n", 1, "\r\n" ),
380            ];
381
382        combinations.iter()
383            .for_each(|combinaisons| 
384                {
385                    let mut content = combinaisons.0.to_string(); 
386                    let result = DotGraph::extract_subgraphes(&mut content, GraphType::Digraph).unwrap();
387                    assert_eq!(result.len(), combinaisons.1);
388                    assert_eq!(content, combinaisons.2)
389                }
390            );
391    } 
392}