petgraph_graphml/
lib.rs

1//! [![docs.rs badge](https://docs.rs/petgraph-graphml/badge.svg)](https://docs.rs/petgraph-graphml/)
2//! [![crates.io badge](https://img.shields.io/crates/v/petgraph-graphml.svg)](https://crates.io/crates/petgraph-graphml/)
3//! [![Rust CI](https://github.com/jonasbb/petgraph-graphml/workflows/Rust%20CI/badge.svg)](https://github.com/jonasbb/petgraph-graphml)
4//! [![codecov](https://codecov.io/gh/jonasbb/petgraph-graphml/branch/master/graph/badge.svg)](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 ![Graph with three nodes and two edges](https://github.com/jonasbb/petgraph-graphml/tree/master/doc/graph.png) 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}