summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorseancarroll <seanc28@gmail.com>2021-06-14 22:13:04 -0500
committercos <cos>2021-06-15 09:07:48 +0200
commita3fd9c4da77ceef1d35bc530bc4bce42ab4337aa (patch)
tree743e0407c61928983d201c2ff10b993bbdeab55b
parent1f1661263c88cce03b6baf27b23f859b6f072c53 (diff)
downloaddotavious-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.rs39
-rw-r--r--tests/dot.rs52
2 files changed, 84 insertions, 7 deletions
diff --git a/src/dot.rs b/src/dot.rs
index c211bdb..f328e1a 100644
--- a/src/dot.rs
+++ b/src/dot.rs
@@ -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)