netcrab/export/
pnml.rs

1use crate::petri_net::PetriNet;
2use xml::writer::{EmitterConfig, EventWriter, Result as XmlResult, XmlEvent};
3
4const XML_PNML_DEFAULT_NAMESPACE: &str = "http://www.pnml.org/version-2009/grammar/pnml";
5const XML_PNML_DEFAULT_GRAMMAR: &str = "http://www.pnml.org/version-2009/grammar/ptnet";
6
7impl PetriNet {
8    /// Converts the net to a string in PNML format and returns it.
9    ///
10    /// # Errors
11    ///
12    /// If the writer fails to write the contents of the net, then an error is returned.
13    pub fn to_pnml_string(&self) -> Result<String, std::io::Error> {
14        let mut writer = Vec::new();
15        self.to_pnml(&mut writer)?;
16        String::from_utf8(writer).map_err(|_|
17            // This error could only be due to a bug, map it to a more standard error type.
18            std::io::Error::new(
19                std::io::ErrorKind::Other,
20                "Could not convert the string to UTF-8",
21            ))
22    }
23
24    /// Converts the net to the PNML format.
25    /// Writes the output to a trait object which implements `std::io::Write`.
26    ///
27    /// # Errors
28    ///
29    /// If the writer fails to write the contents of the net, then an error is returned.
30    pub fn to_pnml<T>(&self, writer: &mut T) -> Result<(), std::io::Error>
31    where
32        T: std::io::Write,
33    {
34        self.write_pnml(writer).map_err(|_| {
35            // Map the XML error of the library to a more standard error type
36            // to stay consistent with the other export formats.
37            std::io::Error::new(
38                std::io::ErrorKind::Other,
39                "Could not convert the net to PNML",
40            )
41        })
42    }
43
44    /// Writes the net to the PNML format.
45    ///
46    /// # Errors
47    ///
48    /// If the XML writer fails to write the contents of the net, then an XML error is returned.
49    fn write_pnml<T>(&self, writer: &mut T) -> XmlResult<()>
50    where
51        T: std::io::Write,
52    {
53        let mut xml_writer = EmitterConfig::new()
54            .perform_indent(true)
55            .create_writer(writer);
56
57        // Initialize general properties of the XML.
58        xml_writer.write(XmlEvent::start_element("pnml").default_ns(XML_PNML_DEFAULT_NAMESPACE))?;
59        xml_writer.write(
60            XmlEvent::start_element("net")
61                .attr("id", "net0")
62                .attr("type", XML_PNML_DEFAULT_GRAMMAR),
63        )?;
64        xml_writer.write(XmlEvent::start_element("page").attr("id", "page0"))?;
65
66        self.write_pnml_places(&mut xml_writer)?;
67        self.write_pnml_transitions(&mut xml_writer)?;
68        self.write_pnml_arcs(&mut xml_writer)?;
69
70        // Close the tags of the general properties of the XML.
71        xml_writer.write(XmlEvent::end_element())?;
72        xml_writer.write(XmlEvent::end_element())?;
73        xml_writer.write(XmlEvent::end_element())?;
74        Ok(())
75    }
76
77    /// Writes the XML elements that define the places
78    /// to an instance of `xml::writer::Writer`.
79    fn write_pnml_places<T>(&self, writer: &mut EventWriter<T>) -> XmlResult<()>
80    where
81        T: std::io::Write,
82    {
83        for (place_ref, place) in self.places_iter() {
84            let place_xml_element = XmlEvent::start_element("place").attr("id", place_ref.label());
85            writer.write(place_xml_element)?;
86            Self::label_to_pnml(place_ref.label(), writer)?;
87            Self::marking_to_pnml(place.marking(), writer)?;
88            writer.write(XmlEvent::end_element())?;
89        }
90        Ok(())
91    }
92
93    /// Writes the XML elements that define the transitions
94    /// to an instance of `xml::writer::Writer`.
95    fn write_pnml_transitions<T>(&self, writer: &mut EventWriter<T>) -> XmlResult<()>
96    where
97        T: std::io::Write,
98    {
99        for (transition_ref, _) in self.transitions_iter() {
100            let transition_xml_element =
101                XmlEvent::start_element("transition").attr("id", transition_ref.label());
102            writer.write(transition_xml_element)?;
103            Self::label_to_pnml(transition_ref.label(), writer)?;
104            writer.write(XmlEvent::end_element())?;
105        }
106        Ok(())
107    }
108
109    /// Writes the XML elements that define the arcs
110    /// to an instance of `xml::writer::Writer`.
111    fn write_pnml_arcs<T>(&self, writer: &mut EventWriter<T>) -> XmlResult<()>
112    where
113        T: std::io::Write,
114    {
115        let arcs = self.find_arcs_place_transition();
116        for (place_ref, transition_ref) in arcs {
117            Self::write_arc(place_ref.label(), transition_ref.label(), writer)?;
118        }
119
120        let arcs = self.find_arcs_transition_place();
121        for (transition_ref, place_ref) in arcs {
122            Self::write_arc(transition_ref.label(), place_ref.label(), writer)?;
123        }
124
125        Ok(())
126    }
127
128    /// Writes a single arc in the net as a XML node
129    /// as required by the PNML standard.
130    fn write_arc<T>(
131        source: &String,
132        dest: &String,
133        xml_writer: &mut EventWriter<T>,
134    ) -> XmlResult<()>
135    where
136        T: std::io::Write,
137    {
138        let arc_label = format!("({source}, {dest})");
139        let start_element = XmlEvent::start_element("arc")
140            .attr("source", source)
141            .attr("target", dest)
142            .attr("id", &arc_label);
143        xml_writer.write(start_element)?;
144        Self::label_to_pnml(&arc_label, xml_writer)?;
145        xml_writer.write(XmlEvent::start_element("inscription"))?;
146        xml_writer.write(XmlEvent::start_element("text"))?;
147        // Weights in arcs are not supported for now.
148        xml_writer.write(XmlEvent::Characters("1"))?;
149        xml_writer.write(XmlEvent::end_element())?;
150        xml_writer.write(XmlEvent::end_element())?;
151        xml_writer.write(XmlEvent::end_element())?;
152        Ok(())
153    }
154
155    /// Writes the label of a place or transition as a XML node
156    /// as required by the PNML standard.
157    fn label_to_pnml<T>(name: &str, xml_writer: &mut EventWriter<T>) -> XmlResult<()>
158    where
159        T: std::io::Write,
160    {
161        xml_writer.write(XmlEvent::start_element("name"))?;
162        xml_writer.write(XmlEvent::start_element("text"))?;
163        xml_writer.write(XmlEvent::Characters(name))?;
164        xml_writer.write(XmlEvent::end_element())?;
165        xml_writer.write(XmlEvent::end_element())?;
166        Ok(())
167    }
168
169    /// Writes the marking of a place as a XML node
170    /// as required by the PNML standard.
171    fn marking_to_pnml<T>(marking: usize, xml_writer: &mut EventWriter<T>) -> XmlResult<()>
172    where
173        T: std::io::Write,
174    {
175        if marking == 0 {
176            return Ok(());
177        }
178        xml_writer.write(XmlEvent::start_element("initialMarking"))?;
179        xml_writer.write(XmlEvent::start_element("text"))?;
180        xml_writer.write(XmlEvent::Characters(&marking.to_string()))?;
181        xml_writer.write(XmlEvent::end_element())?;
182        xml_writer.write(XmlEvent::end_element())?;
183        Ok(())
184    }
185}
186
187#[cfg(test)]
188mod pnml_tests {
189    use super::*;
190    use crate::export::test_export_examples::*;
191    use crate::net_creator::*;
192
193    #[test]
194    fn pnml_string_empty_net() {
195        let net = PetriNet::new();
196        let result = net.to_pnml_string();
197
198        assert!(result.is_ok());
199        assert_eq!(result.unwrap(), PNML_STRING_EMPTY_NET);
200    }
201
202    #[test]
203    fn pnml_string_only_empty_places_net() {
204        let (net, _, _) = create_basic_unconnected_net(5, 0);
205        let result = net.to_pnml_string();
206
207        assert!(result.is_ok());
208        assert_eq!(result.unwrap(), PNML_STRING_ONLY_EMPTY_PLACES_NET);
209    }
210
211    #[test]
212    fn pnml_string_marked_places_net() {
213        let mut net = PetriNet::new();
214        let p1 = net.add_place("P1");
215        let p2 = net.add_place("P2");
216        let p3 = net.add_place("P3");
217        let p4 = net.add_place("P4");
218        let p5 = net.add_place("P5");
219
220        assert!(net.add_token(&p1, 5).is_ok());
221        assert!(net.add_token(&p2, 6).is_ok());
222        assert!(net.add_token(&p3, 3).is_ok());
223        assert!(net.add_token(&p4, 2).is_ok());
224        assert!(net.add_token(&p5, 1).is_ok());
225        let result = net.to_pnml_string();
226
227        assert!(result.is_ok());
228        assert_eq!(result.unwrap(), PNML_STRING_MARKED_PLACES_NET);
229    }
230
231    #[test]
232    fn pnml_string_only_empty_transitions_net() {
233        let (net, _, _) = create_basic_unconnected_net(0, 5);
234        let result = net.to_pnml_string();
235
236        assert!(result.is_ok());
237        assert_eq!(result.unwrap(), PNML_STRING_ONLY_EMPTY_TRANSITIONS_NET);
238    }
239
240    #[test]
241    fn pnml_string_net_with_chain_topology() {
242        let (net, _, _) = create_net_chain_topology(3);
243        let result = net.to_pnml_string();
244
245        assert!(result.is_ok());
246        assert_eq!(result.unwrap(), PNML_STRING_NET_WITH_CHAIN_TOPOLOPY);
247    }
248
249    #[test]
250    fn pnml_string_net_with_loop_topology() {
251        let (net, _, _) = create_net_loop_topology();
252        let result = net.to_pnml_string();
253
254        assert!(result.is_ok());
255        assert_eq!(result.unwrap(), PNML_STRING_NET_WITH_LOOP_TOPOLOGY);
256    }
257}