netcrab/export/
dot.rs

1use crate::petri_net::PetriNet;
2
3const MAX_TOKENS_AS_DOT: usize = 5;
4
5impl PetriNet {
6    /// Converts the net to a string in DOT format and returns it.
7    ///
8    /// # Errors
9    ///
10    /// If the writer fails to write the contents of the net, then an error is returned.
11    pub fn to_dot_string(&self) -> Result<String, std::io::Error> {
12        let mut writer = Vec::new();
13        self.to_dot(&mut writer)?;
14        String::from_utf8(writer).map_err(|_|
15            // This error could only be due to a bug, map it to a more standard error type.
16            std::io::Error::new(
17                std::io::ErrorKind::Other,
18                "Could not convert the string to UTF-8",
19            ))
20    }
21
22    /// Converts the net to the dot format.
23    /// Writes the output to a trait object which implements `std::io::Write`.
24    ///
25    /// # Errors
26    ///
27    /// If the writer fails to write the contents of the net, then an error is returned.
28    pub fn to_dot<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
29    where
30        T: std::io::Write,
31    {
32        writer.write_all(b"digraph petrinet {\n")?;
33        self.write_dot_places(writer)?;
34        self.write_dot_transitions(writer)?;
35        self.write_dot_arcs(writer)?;
36        writer.write_all(b"}\n")?;
37        Ok(())
38    }
39
40    /// Writes the lines that define the places
41    /// to a trait object which implements `std::io::Write`.
42    fn write_dot_places<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
43    where
44        T: std::io::Write,
45    {
46        for (place_ref, place) in self.places_iter() {
47            let label = Self::sanitize_string(place_ref.label());
48            let marking = Self::marking_to_string(place.marking());
49            let line =
50                format!("    {label} [shape=\"circle\" xlabel=\"{label}\" label=\"{marking}\"];\n");
51            writer.write_all(line.as_bytes())?;
52        }
53        Ok(())
54    }
55
56    /// Writes the lines that define the transitions
57    /// to a trait object which implements `std::io::Write`.
58    fn write_dot_transitions<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
59    where
60        T: std::io::Write,
61    {
62        for (transition_ref, _) in self.transitions_iter() {
63            let label = Self::sanitize_string(transition_ref.label());
64            let line = format!("    {label} [shape=\"box\" xlabel=\"\" label=\"{label}\"];\n");
65            writer.write_all(line.as_bytes())?;
66        }
67        Ok(())
68    }
69
70    /// Writes the lines that define the arcs
71    /// to a trait object which implements `std::io::Write`.
72    fn write_dot_arcs<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
73    where
74        T: std::io::Write,
75    {
76        let arcs = self.find_arcs_place_transition();
77        for (place_ref, transition_ref) in arcs {
78            let line = format!(
79                "    {} -> {};\n",
80                Self::sanitize_string(place_ref.label()),
81                Self::sanitize_string(transition_ref.label())
82            );
83            writer.write_all(line.as_bytes())?;
84        }
85
86        let arcs = self.find_arcs_transition_place();
87        for (transition_ref, place_ref) in arcs {
88            let line = format!(
89                "    {} -> {};\n",
90                Self::sanitize_string(transition_ref.label()),
91                Self::sanitize_string(place_ref.label()),
92            );
93            writer.write_all(line.as_bytes())?;
94        }
95
96        Ok(())
97    }
98
99    /// Converts the label if present to a valid `String`.
100    /// Removes newlines and quotes from the label.
101    ///
102    /// Using escape sequences it is possible to achieve special behavior.
103    /// [More info](https://graphviz.org/docs/attr-types/escString/)
104    fn sanitize_string(string: &str) -> String {
105        string.replace('\n', "").replace('\"', "\\\"")
106    }
107
108    /// Convert the marking to a valid string.
109    fn marking_to_string(marking: usize) -> String {
110        match marking {
111            0 => String::new(),
112            1..=MAX_TOKENS_AS_DOT => "•".repeat(marking),
113            _ => marking.to_string(),
114        }
115    }
116}
117
118#[cfg(test)]
119mod dot_tests {
120    use super::*;
121    use crate::export::test_export_examples::*;
122    use crate::net_creator::*;
123
124    #[test]
125    fn dot_string_empty_net() {
126        let net = PetriNet::new();
127        let result = net.to_dot_string();
128        assert!(result.is_ok());
129        assert_eq!(result.unwrap(), DOT_STRING_EMPTY_NET);
130    }
131
132    #[test]
133    fn dot_string_only_empty_places_net() {
134        let (net, _, _) = create_basic_unconnected_net(5, 0);
135        let result = net.to_dot_string();
136
137        assert!(result.is_ok());
138        assert_eq!(result.unwrap(), DOT_STRING_ONLY_EMPTY_PLACES_NET);
139    }
140
141    #[test]
142    fn dot_string_marked_places_net() {
143        let mut net = PetriNet::new();
144        let p1 = net.add_place("P1");
145        let p2 = net.add_place("P2");
146        let p3 = net.add_place("P3");
147        let p4 = net.add_place("P4");
148        let p5 = net.add_place("P5");
149
150        assert!(net.add_token(&p1, 5).is_ok());
151        assert!(net.add_token(&p2, 6).is_ok());
152        assert!(net.add_token(&p3, 3).is_ok());
153        assert!(net.add_token(&p4, 2).is_ok());
154        assert!(net.add_token(&p5, 1).is_ok());
155        let result = net.to_dot_string();
156
157        assert!(result.is_ok());
158        assert_eq!(result.unwrap(), DOT_STRING_MARKED_PLACES_NET);
159    }
160
161    #[test]
162    fn dot_string_only_empty_transitions_net() {
163        let (net, _, _) = create_basic_unconnected_net(0, 5);
164        let result = net.to_dot_string();
165
166        assert!(result.is_ok());
167        assert_eq!(result.unwrap(), DOT_STRING_ONLY_EMPTY_TRANSITIONS_NET);
168    }
169
170    #[test]
171    fn dot_string_net_with_chain_topology() {
172        let (net, _, _) = create_net_chain_topology(3);
173        let result = net.to_dot_string();
174
175        assert!(result.is_ok());
176        assert_eq!(result.unwrap(), DOT_STRING_NET_WITH_CHAIN_TOPOLOPY);
177    }
178
179    #[test]
180    fn dot_string_net_with_loop_topology() {
181        let (net, _, _) = create_net_loop_topology();
182        let result = net.to_dot_string();
183
184        assert!(result.is_ok());
185        assert_eq!(result.unwrap(), DOT_STRING_NET_WITH_LOOP_TOPOLOGY);
186    }
187}