diff options
author | seancarroll <seanc28@gmail.com> | 2021-06-14 22:13:04 -0500 |
---|---|---|
committer | cos <cos> | 2021-06-15 09:07:48 +0200 |
commit | a3fd9c4da77ceef1d35bc530bc4bce42ab4337aa (patch) | |
tree | 743e0407c61928983d201c2ff10b993bbdeab55b | |
parent | 1f1661263c88cce03b6baf27b23f859b6f072c53 (diff) | |
download | dotavious-a3fd9c4da77ceef1d35bc530bc4bce42ab4337aa.zip |
Bugfix: Allow ID Strings
A user, Martin, reached out about a bug with IDs specifically with
quoting. Dotavious initially just used the string provided by caller
however this is problematic for a few different scenarios amongst others
1. Strings with spaces
2. Double-quoted strings
3. Strings with non-alphanumerical content
Details about IDs can be found at https://graphviz.org/doc/info/lang.html
This commit conditional formats IDs based on the following
1. Wrap string in quotes if containing non alpha-numerical characters
2. Escape quotes found in string
My rationale for conditionally formatting is mostly around keeping
previous behavior intact. Quote from the link above
An ID is just a string; the lack of quote characters in the first two forms is just for simplicity.
There is no semantic difference between abc_2 and "abc_2", or between 2.34 and "2.34"
Given these IDs are semantically the same I wanted to keep the same
formatting which is unquoted.
NOTE: This does not address HTML strings as IDs which needs to be
addressed at a later date.
-rw-r--r-- | src/dot.rs | 39 | ||||
-rw-r--r-- | tests/dot.rs | 52 |
2 files changed, 84 insertions, 7 deletions
@@ -53,7 +53,7 @@ impl<'a> Dot<'a> { write!(w, "{}{}", strict, &graph.graph_type())?; if let Some(id) = &graph.id { - write!(w, r#" "{}""#, escape_doublequotes(id))?; + write!(w, " {}", format_id(id))?; } writeln!(w, " {{")?; @@ -185,11 +185,11 @@ impl<'a> Dot<'a> { write!( w, - r#"{}"{}" {} "{}""#, + r#"{}{} {} {}"#, get_indentation(indentation_level), - escape_doublequotes(&edge_source), + format_id(&edge_source), edge_op, - escape_doublequotes(&edge_source) + format_id(&edge_target) )?; write!(w, "{}", fmt_attributes(&edge.attributes))?; writeln!(w, ";") @@ -638,7 +638,7 @@ impl<'a> Node<'a> { impl<'a> DotString<'a> for Node<'a> { fn dot_string(&self) -> Cow<'a, str> { - let mut dot_string = format!(r#""{}""#, escape_doublequotes(&self.id)); + let mut dot_string = format!("{}", format_id(&self.id)); dot_string.push_str(fmt_attributes(&self.attributes).as_str()); dot_string.push_str(";"); dot_string.into() @@ -966,6 +966,31 @@ fn get_indentation(indentation_level: usize) -> String { INDENT.repeat(indentation_level) } -fn escape_doublequotes(val: &String) -> String { - val.chars().map(|c| if c == '"' { format!("\\{}", c) } else { format!("{}", c)}).collect() +fn is_alphanum(val: &String) -> bool { + for byte in val.bytes() { + if !((byte >= b'a' && byte <= b'z') || (byte >= b'A' && byte <= b'Z') || + (byte >= b'0' && byte <= b'9') || byte == b'_' || byte >= 128) + { + return false; + } + } + true +} + +// According to https://graphviz.org/doc/info/lang.html we should wrap any strings containing +// non-alphanumerical characters, as escape double quotes within those strings. +// This probably needs to be more robust but I think for now it fixes a but around double-quoted +// strings +fn format_id(val: &String) -> String { + if is_alphanum(val) { + val.to_string() + } else { + format!("\"{}\"", val.chars().map( + |c| if c == '"' { + format!("\\{}", c) + } else { + format!("{}", c) + }).collect::<String>() + ) + } } diff --git a/tests/dot.rs b/tests/dot.rs index bf960a9..9d944a2 100644 --- a/tests/dot.rs +++ b/tests/dot.rs @@ -63,6 +63,36 @@ digraph { } #[test] +fn quotted_id() { + let quotted_id = r#"Earvin "Magic" Johnson"#; + let g = GraphBuilder::new_named_directed(quotted_id) + .build() + .unwrap(); + let r = test_input(g); + assert_eq!( + r.unwrap(), + r#"digraph "Earvin \"Magic\" Johnson" { +} +"# + ); +} + +#[test] +fn id_with_space() { + let id = "A Graph"; + let g = GraphBuilder::new_named_directed(id) + .build() + .unwrap(); + let r = test_input(g); + assert_eq!( + r.unwrap(), + r#"digraph "A Graph" { +} +"# + ); +} + +#[test] fn empty_digraph() { let g = GraphBuilder::new_named_directed("empty_graph") .build() @@ -198,6 +228,28 @@ fn single_edge() { } #[test] +fn format_edges() { + let g = GraphBuilder::new_named_directed("format_edges") + .add_node(Node::new(r#"Earvin "Magic" Johnson"#)) + .add_node(Node::new("A Graph")) + .add_edge(Edge::new(r#"Earvin "Magic" Johnson"#, "A Graph")) + .build() + .unwrap(); + + let r = test_input(g); + + assert_eq!( + r.unwrap(), + r#"digraph format_edges { + "Earvin \"Magic\" Johnson"; + "A Graph"; + "Earvin \"Magic\" Johnson" -> "A Graph"; +} +"# + ); +} + +#[test] fn single_edge_with_style() { let edge = EdgeBuilder::new("N0", "N1") .style(EdgeStyle::Bold) |