petgraph_graphml/lib.rs
1//! [](https://docs.rs/petgraph-graphml/)
2//! [](https://crates.io/crates/petgraph-graphml/)
3//! [](https://github.com/jonasbb/petgraph-graphml)
4//! [](https://codecov.io/gh/jonasbb/petgraph-graphml)
5//!
6//! ---
7//!
8//! This crate extends [petgraph][] with [GraphML][graphmlwebsite] output support.
9//!
10//! This crate exports a single type [`GraphMl`] which combines a build-pattern for configuration and provides creating strings ([`GraphMl::to_string`]) and writing to writers ([`GraphMl::to_writer`]).
11//!
12//! # Usage
13//!
14//! Add this to your `Cargo.toml`:
15//!
16//! ```toml
17//! [dependencies]
18//! petgraph-graphml = "5.0.0"
19//! ```
20//!
21//! # Example
22//!
23//! For a simple graph like  this is the generated GraphML output.
24//!
25//! ```
26//! # use petgraph::Graph;
27//! # use petgraph_graphml::GraphMl;
28//! # fn make_graph() -> Graph<u32, ()> {
29//! # let mut graph = Graph::new();
30//! # let n0 = graph.add_node(0);
31//! # let n1 = graph.add_node(1);
32//! # let n2 = graph.add_node(2);
33//! # graph.update_edge(n0, n1, ());
34//! # graph.update_edge(n1, n2, ());
35//! # graph
36//! # }
37//! # fn main() {
38//! let graph = make_graph();
39//! // Configure output settings
40//! // Enable pretty printing and exporting of node weights.
41//! // Use the Display implementation of NodeWeights for exporting them.
42//! let graphml = GraphMl::new(&graph)
43//! .pretty_print(true)
44//! .export_node_weights_display();
45//!
46//! assert_eq!(
47//! graphml.to_string(),
48//! r#"<?xml version="1.0" encoding="UTF-8"?>
49//! <graphml xmlns="http://graphml.graphdrawing.org/xmlns">
50//! <key id="weight" for="node" attr.name="weight" attr.type="string" />
51//! <graph edgedefault="directed">
52//! <node id="n0">
53//! <data key="weight">0</data>
54//! </node>
55//! <node id="n1">
56//! <data key="weight">1</data>
57//! </node>
58//! <node id="n2">
59//! <data key="weight">2</data>
60//! </node>
61//! <edge id="e0" source="n0" target="n1" />
62//! <edge id="e1" source="n1" target="n2" />
63//! </graph>
64//! </graphml>"#
65//! );
66//! # }
67//! ```
68//!
69//! [`GraphMl`]: https://docs.rs/petgraph-graphml/*/petgraph_graphml/struct.GraphMl.html
70//! [`GraphMl::to_string`]: https://docs.rs/petgraph-graphml/*/petgraph_graphml/struct.GraphMl.html#method.to_string
71//! [`GraphMl::to_writer`]: https://docs.rs/petgraph-graphml/*/petgraph_graphml/struct.GraphMl.html#method.to_writer
72//! [graphmlwebsite]: http://graphml.graphdrawing.org/
73//! [petgraph]: https://docs.rs/petgraph/
74
75#![deny(
76 missing_debug_implementations,
77 missing_copy_implementations,
78 missing_docs,
79 trivial_casts,
80 trivial_numeric_casts,
81 unused_extern_crates,
82 unused_import_braces,
83 unused_qualifications,
84 variant_size_differences
85)]
86#![allow(unknown_lints, clippy::return_self_not_must_use)]
87#![doc(html_root_url = "https://docs.rs/petgraph-graphml/5.0.0")]
88
89use petgraph::visit::{
90 EdgeRef, GraphProp, IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef,
91};
92use std::borrow::Cow;
93use std::collections::HashSet;
94use std::fmt::{self, Debug, Display};
95use std::io::{self, Cursor, Write};
96use xml::common::XmlVersion;
97use xml::writer::events::XmlEvent;
98use xml::writer::{Error as XmlError, EventWriter, Result as WriterResult};
99use xml::EmitterConfig;
100
101static NAMESPACE_URL: &str = "http://graphml.graphdrawing.org/xmlns";
102
103#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
104struct Attribute {
105 name: Cow<'static, str>,
106 for_: For,
107}
108
109#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
110enum For {
111 Node,
112 Edge,
113}
114
115impl For {
116 fn to_str(self) -> &'static str {
117 match self {
118 For::Node => "node",
119 For::Edge => "edge",
120 }
121 }
122}
123
124type PrintWeights<W> = dyn for<'a> Fn(&'a W) -> Vec<(Cow<'static, str>, Cow<'a, str>)>;
125
126/// GraphML output printer
127///
128/// See the [main crate documentation](index.html) for usage instructions and examples.
129pub struct GraphMl<G>
130where
131 G: IntoEdgeReferences,
132 G: IntoNodeReferences,
133{
134 graph: G,
135 pretty_print: bool,
136 export_edges: Option<Box<PrintWeights<G::EdgeWeight>>>,
137 export_nodes: Option<Box<PrintWeights<G::NodeWeight>>>,
138}
139
140impl<G> GraphMl<G>
141where
142 G: GraphProp,
143 G: IntoNodeReferences,
144 G: IntoEdgeReferences,
145 G: NodeIndexable,
146{
147 /// Create a new GraphML printer for the graph.
148 pub fn new(graph: G) -> Self {
149 Self {
150 graph,
151 pretty_print: true,
152 export_edges: None,
153 export_nodes: None,
154 }
155 }
156
157 /// Enable or disable pretty printing of the XML.
158 ///
159 /// Pretty printing enables linebreaks and indentation.
160 pub fn pretty_print(mut self, state: bool) -> Self {
161 self.pretty_print = state;
162 self
163 }
164
165 /// Export the edge weights to GraphML.
166 ///
167 /// This uses the [`Display`] implementation of the edge weight type.
168 /// The attribute name defaults to "weight".
169 ///
170 /// Once set this option cannot be disabled anymore.
171 ///
172 /// [`Display`]: Display
173 pub fn export_edge_weights_display(self) -> Self
174 where
175 G::EdgeWeight: Display,
176 {
177 self.export_edge_weights(Box::new(|edge| {
178 vec![("weight".into(), edge.to_string().into())]
179 }))
180 }
181
182 /// Export the edge weights to GraphML.
183 ///
184 /// This uses a custom conversion function.
185 /// Each edge can be converted into an arbitrary number of attributes.
186 /// Each attribute is a key-value pair, represented as tuple.
187 ///
188 /// Once set this option cannot be disabled anymore.
189 ///
190 /// # Example
191 ///
192 /// A custom print function for the type `(String, u32)`.
193 /// It will create two attributes "str attr" and "int attr" containing the string and integer part.
194 ///
195 /// ```
196 /// # use petgraph::Graph;
197 /// # use petgraph_graphml::GraphMl;
198 /// # fn make_graph() -> Graph<(), (String, u32)> {
199 /// # Graph::new()
200 /// # }
201 /// let graph = make_graph();
202 /// let graphml = GraphMl::new(&graph).export_edge_weights(Box::new(|edge| {
203 /// let &(ref s, i) = edge;
204 /// vec![
205 /// ("str attr".into(), s[..].into()),
206 /// ("int attr".into(), i.to_string().into()),
207 /// ]
208 /// }));
209 /// ```
210 ///
211 /// Currently only string attribute types are supported.
212 pub fn export_edge_weights(mut self, edge_weight: Box<PrintWeights<G::EdgeWeight>>) -> Self {
213 self.export_edges = Some(edge_weight);
214 self
215 }
216
217 /// Export the node weights to GraphML.
218 ///
219 /// This uses the [`Display`] implementation of the node weight type.
220 /// The attribute name defaults to "weight".
221 ///
222 /// Once set this option cannot be disabled anymore.
223 ///
224 /// [`Display`]: Display
225 pub fn export_node_weights_display(self) -> Self
226 where
227 G::NodeWeight: Display,
228 {
229 self.export_node_weights(Box::new(|node| {
230 vec![("weight".into(), node.to_string().into())]
231 }))
232 }
233
234 /// Export the node weights to GraphML.
235 ///
236 /// This uses a custom conversion function.
237 /// Each node can be converted into an arbitrary number of attributes.
238 /// Each attribute is a key-value pair, represented as tuple.
239 ///
240 /// Once set this option cannot be disabled anymore.
241 ///
242 /// # Example
243 ///
244 /// A custom print function for the type `(String, u32)`.
245 /// It will create two attributes "str attr" and "int attr" containing the string and integer part.
246 ///
247 /// ```
248 /// # use petgraph::Graph;
249 /// # use petgraph_graphml::GraphMl;
250 /// # fn make_graph() -> Graph<(String, u32), ()> {
251 /// # Graph::new()
252 /// # }
253 /// let graph = make_graph();
254 /// let graphml = GraphMl::new(&graph).export_node_weights(Box::new(|node| {
255 /// let &(ref s, i) = node;
256 /// vec![
257 /// ("str attr".into(), s[..].into()),
258 /// ("int attr".into(), i.to_string().into()),
259 /// ]
260 /// }));
261 /// ```
262 ///
263 /// Currently only string attribute types are supported.
264 pub fn export_node_weights(mut self, node_weight: Box<PrintWeights<G::NodeWeight>>) -> Self {
265 self.export_nodes = Some(node_weight);
266 self
267 }
268
269 /// Write the GraphML file to the given writer.
270 pub fn to_writer<W>(&self, writer: W) -> io::Result<()>
271 where
272 W: Write,
273 {
274 let mut writer = EventWriter::new_with_config(
275 writer,
276 EmitterConfig::new().perform_indent(self.pretty_print),
277 );
278 match self.emit_graphml(&mut writer) {
279 Ok(()) => Ok(()),
280 Err(XmlError::Io(ioerror)) => Err(ioerror),
281 _ => panic!(""),
282 }
283 }
284
285 fn emit_graphml<W>(&self, writer: &mut EventWriter<W>) -> WriterResult<()>
286 where
287 W: Write,
288 {
289 // XML/GraphML boilerplate
290 writer.write(XmlEvent::StartDocument {
291 version: XmlVersion::Version10,
292 encoding: Some("UTF-8"),
293 standalone: None,
294 })?;
295 writer.write(XmlEvent::start_element("graphml").attr("xmlns", NAMESPACE_URL))?;
296
297 // First pass to extract keys
298 // Emit <key> tags for all the attributes
299 self.emit_keys(writer)?;
300
301 // emit graph with nodes/edges and possibly weights
302 self.emit_graph(writer)?;
303
304 writer.write(XmlEvent::end_element())?; // end graphml
305 Ok(())
306 }
307
308 fn extract_attributes(&self) -> HashSet<Attribute> {
309 // Store information about the attributes for nodes and edges.
310 // We cannot know in advance what the attribute names will be, so we just keep track of what gets emitted.
311 let mut attributes: HashSet<Attribute> = HashSet::new();
312
313 // Harvest Node Keys
314 for node in self.graph.node_references() {
315 if let Some(ref node_labels) = self.export_nodes {
316 for (name, _) in node_labels(node.weight()) {
317 attributes.insert(Attribute {
318 name,
319 for_: For::Node,
320 });
321 }
322 }
323 }
324
325 // Harvest Edge Keys
326 for edge in self.graph.edge_references() {
327 if let Some(ref edge_labels) = self.export_edges {
328 for (name, _) in edge_labels(edge.weight()) {
329 attributes.insert(Attribute {
330 name,
331 for_: For::Edge,
332 });
333 }
334 }
335 }
336
337 attributes
338 }
339
340 // Emit an attribute for either node or edge
341 fn emit_attribute<W>(
342 &self,
343 writer: &mut EventWriter<W>,
344 name: Cow<'static, str>,
345 data: &str,
346 ) -> WriterResult<()>
347 where
348 W: Write,
349 {
350 writer.write(XmlEvent::start_element("data").attr("key", &name))?;
351 writer.write(XmlEvent::characters(data))?;
352 writer.write(XmlEvent::end_element()) // end data
353 }
354
355 fn emit_graph<W>(&self, writer: &mut EventWriter<W>) -> WriterResult<()>
356 where
357 W: Write,
358 {
359 // convenience function to turn a NodeId into a String
360 let node2str_id = |node: G::NodeId| -> String { format!("n{}", self.graph.to_index(node)) };
361
362 // Each graph needs a default edge type
363 writer.write(XmlEvent::start_element("graph").attr(
364 "edgedefault",
365 if self.graph.is_directed() {
366 "directed"
367 } else {
368 "undirected"
369 },
370 ))?;
371
372 // Emit nodes
373 for node in self.graph.node_references() {
374 writer.write(XmlEvent::start_element("node").attr("id", &node2str_id(node.id())))?;
375 // Print weights
376 if let Some(ref node_labels) = self.export_nodes {
377 let data = node_labels(node.weight());
378 for (name, data) in data {
379 self.emit_attribute(writer, name, &data)?;
380 }
381 }
382 writer.write(XmlEvent::end_element())?; // end node
383 }
384
385 // Emit edges
386 for (i, edge) in self.graph.edge_references().enumerate() {
387 writer.write(
388 XmlEvent::start_element("edge")
389 .attr("id", &format!("e{i}"))
390 .attr("source", &node2str_id(edge.source()))
391 .attr("target", &node2str_id(edge.target())),
392 )?;
393 // Print weights
394 if let Some(ref edge_labels) = self.export_edges {
395 let data = edge_labels(edge.weight());
396 for (name, data) in data {
397 self.emit_attribute(writer, name, &data)?;
398 }
399 }
400 writer.write(XmlEvent::end_element())?; // end edge
401 }
402 writer.write(XmlEvent::end_element()) // end graph
403 }
404
405 fn emit_keys<W>(&self, writer: &mut EventWriter<W>) -> WriterResult<()>
406 where
407 W: Write,
408 {
409 for attr in self.extract_attributes() {
410 writer.write(
411 XmlEvent::start_element("key")
412 .attr("id", &attr.name)
413 .attr("for", attr.for_.to_str())
414 .attr("attr.name", &attr.name)
415 .attr("attr.type", "string"),
416 )?;
417 writer.write(XmlEvent::end_element())?; // end key
418 }
419 Ok(())
420 }
421}
422
423impl<G> Debug for GraphMl<G>
424where
425 G: Debug,
426 G: IntoEdgeReferences,
427 G: IntoNodeReferences,
428{
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 f.debug_struct("GraphMl")
431 .field("graph", &self.graph)
432 .field("pretty_print", &self.pretty_print)
433 .field("export_edges", &self.export_edges.is_some())
434 .field("export_nodes", &self.export_nodes.is_some())
435 .finish()
436 }
437}
438
439impl<G> Display for GraphMl<G>
440where
441 G: Debug,
442 G: IntoEdgeReferences,
443 G: IntoNodeReferences,
444 G: GraphProp,
445 G: NodeIndexable,
446{
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 let mut buff = Cursor::new(Vec::new());
449 self.to_writer(&mut buff)
450 .expect("Writing to a Cursor should never create IO errors.");
451 let s = String::from_utf8(buff.into_inner()).unwrap();
452 write!(f, "{}", &s)
453 }
454}