fdg_sim/
gml.rs

1//!
2//! Here's an example graph:
3//! ```ignore
4//! graph [
5//!   node [
6//!     id 0
7//!     label "8"
8//!   ]
9//!   node [
10//!     id 1
11//!     label "3"
12//!   ]
13//!   node [
14//!     id 2
15//!     label "5"
16//!   ]
17//!   node [
18//!     id 3
19//!     label "6"
20//!   ]
21//!   node [
22//!     id 4
23//!     label "2"
24//!   ]
25//!   node [
26//!     id 5
27//!     label "4"
28//!   ]
29//!   node [
30//!     id 6
31//!     label "7"
32//!   ]
33//!   node [
34//!     id 7
35//!     label "1"
36//!   ]
37//!   edge [
38//!     source 7
39//!     target 4
40//!   ]
41//!   edge [
42//!     source 4
43//!     target 1
44//!   ]
45//!   edge [
46//!     source 1
47//!     target 5
48//!   ]
49//!   edge [
50//!     source 5
51//!     target 7
52//!   ]
53//!   edge [
54//!     source 2
55//!     target 3
56//!   ]
57//!   edge [
58//!     source 3
59//!     target 6
60//!   ]
61//!   edge [
62//!     source 6
63//!     target 0
64//!   ]
65//!   edge [
66//!     source 0
67//!     target 2
68//!   ]
69//!   edge [
70//!     source 7
71//!     target 2
72//!   ]
73//!   edge [
74//!     source 4
75//!     target 3
76//!   ]
77//!   edge [
78//!     source 1
79//!     target 6
80//!   ]
81//!   edge [
82//!     source 5
83//!     target 0
84//!   ]
85//! ]
86//! ```
87
88use core::fmt;
89use std::{collections::HashMap, error::Error};
90
91use crate::{ForceGraph, ForceGraphHelper};
92use petgraph::graph::NodeIndex;
93use regex::Regex;
94
95/// Possible errors returned by the functions in the module.
96#[derive(Clone, Debug)]
97pub enum GmlParseError {
98    GraphStructure,
99    NoNodes,
100    IdNotNumber,
101    NoId,
102    NoSource,
103    NoTarget,
104    SourceNotNumber,
105    TargetNotNumber,
106    InvalidSource(usize),
107    InvalidTarget(usize),
108    RegexError(String),
109}
110
111impl fmt::Display for GmlParseError {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self {
114            Self::GraphStructure => {
115                write!(f, "Graph must be structured as \"graph [ [CONTENT] ]\"")
116            }
117            Self::NoNodes => write!(f, "Graph include nodes"),
118            Self::IdNotNumber => write!(f, "Node ids must be a number"),
119            Self::NoId => write!(f, "Nodes must have an id"),
120            Self::NoSource => write!(f, "Edges must have a source"),
121            Self::NoTarget => write!(f, "Edges must have a target"),
122            Self::SourceNotNumber => write!(f, "Edge sources must be numbers"),
123            Self::TargetNotNumber => write!(f, "Edge targets must be numbers"),
124            Self::InvalidSource(s) => write!(f, "Edge source {s} not found in nodes"),
125            Self::InvalidTarget(s) => write!(f, "Edge target {s} not found in nodes"),
126            Self::RegexError(err) => write!(f, "Regex Error: {err}"),
127        }
128    }
129}
130
131impl Error for GmlParseError {}
132
133/// Get a [`ForceGraph`] from a gml string.
134pub fn graph_from_gml(gml: impl AsRef<str>) -> Result<ForceGraph<(), ()>, GmlParseError> {
135    let gml = gml.as_ref();
136
137    let mut graph = ForceGraph::default();
138    let mut indices: HashMap<usize, NodeIndex<u32>> = HashMap::new();
139
140    // overall graph structure
141    let content = match Regex::new(r"graph\s\[([\d\D]+)\]") {
142        Ok(r) => match r.captures(gml) {
143            Some(x) => x[1].to_string(),
144            None => return Err(GmlParseError::GraphStructure),
145        },
146        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
147    };
148
149    let nodes: Vec<String> = match Regex::new(r"node\s\[([^]]+)\]") {
150        Ok(r) => r
151            .captures_iter(&content)
152            .map(|x| x[1].to_string())
153            .collect(),
154        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
155    };
156
157    if nodes.is_empty() {
158        return Err(GmlParseError::NoNodes);
159    }
160
161    let id_regex = match Regex::new(r"\sid\s(\d)") {
162        Ok(r) => r,
163        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
164    };
165
166    let label_regex = match Regex::new(r##"\slabel\s"([^]]+)""##) {
167        Ok(r) => r,
168        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
169    };
170
171    for node in nodes {
172        let id = match id_regex.captures(&node).map(|x| x[1].to_string()) {
173            Some(id) => id,
174            None => return Err(GmlParseError::NoId),
175        };
176
177        let id: usize = match id.parse() {
178            Ok(id) => id,
179            Err(_) => return Err(GmlParseError::IdNotNumber),
180        };
181
182        let label: String = label_regex
183            .captures(&node)
184            .map(|x| x[1].to_string())
185            .unwrap_or_default();
186
187        indices.insert(id, graph.add_force_node(label, ()));
188    }
189
190    let edges: Vec<String> = match Regex::new(r"edge\s\[([^]]+)\]") {
191        Ok(r) => r
192            .captures_iter(&content)
193            .map(|x| x[1].to_string())
194            .collect(),
195        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
196    };
197
198    let source_regex = match Regex::new(r"\ssource\s(\d)") {
199        Ok(r) => r,
200        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
201    };
202
203    let target_regex = match Regex::new(r"\starget\s(\d)") {
204        Ok(r) => r,
205        Err(err) => return Err(GmlParseError::RegexError(err.to_string())),
206    };
207
208    for edge in edges {
209        let source_str = match source_regex.captures(&edge).map(|x| x[1].to_string()) {
210            Some(source) => source,
211            None => return Err(GmlParseError::NoSource),
212        };
213
214        let target_str = match target_regex.captures(&edge).map(|x| x[1].to_string()) {
215            Some(target) => target,
216            None => return Err(GmlParseError::NoTarget),
217        };
218
219        let source: usize = match source_str.parse() {
220            Ok(source) => source,
221            Err(_) => return Err(GmlParseError::SourceNotNumber),
222        };
223
224        let target: usize = match target_str.parse() {
225            Ok(target) => target,
226            Err(_) => return Err(GmlParseError::TargetNotNumber),
227        };
228
229        let source_idx = match indices.get(&source) {
230            Some(idx) => *idx,
231            None => return Err(GmlParseError::InvalidSource(source)),
232        };
233
234        let target_idx = match indices.get(&target) {
235            Some(idx) => *idx,
236            None => return Err(GmlParseError::InvalidSource(target)),
237        };
238
239        graph.add_edge(source_idx, target_idx, ());
240    }
241
242    Ok(graph)
243}
244
245/// Create a gml string from a [`ForceGraph`].
246pub fn graph_to_gml<N, E>(graph: &ForceGraph<N, E>) -> String {
247    let mut final_str = String::new();
248
249    final_str.push_str("graph [\n");
250
251    for id in graph.node_indices() {
252        let label = &graph[id].name;
253
254        final_str.push_str(&format!(
255            "  node [\n    id {}\n    label \"{}\"\n  ]\n",
256            id.index(),
257            label
258        ));
259    }
260
261    for edge in graph.edge_indices() {
262        let (source, target) = match graph.edge_endpoints(edge) {
263            Some(x) => x,
264            None => continue,
265        };
266
267        final_str.push_str(&format!(
268            "  edge [\n    source {}\n    target {}\n  ]\n",
269            source.index(),
270            target.index()
271        ));
272    }
273
274    final_str.push_str("]");
275
276    final_str
277}