Skip to content

Commit

Permalink
Quote properties on edges too (#40)
Browse files Browse the repository at this point in the history
* Quote properties on edges too

* Bump version

---------

Co-authored-by: Guillaume Dalle <[email protected]>
  • Loading branch information
gabriel-fallen and gdalle authored Sep 13, 2023
1 parent 01d4dac commit 42a2f26
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 101 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "MetaGraphs"
uuid = "626554b9-1ddb-594c-aa3c-2596fe9399a5"
version = "0.7.2"
version = "0.8.0"

[deps]
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
Expand Down
38 changes: 26 additions & 12 deletions src/persistence.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ end
loadgraph(fn::AbstractString, gname::String, ::MGFormat) = loadmg(fn)
savegraph(fn::AbstractString, g::AbstractMetaGraph) = savemg(fn, g)

# escaping unescaped quotation marks
# i.e. replacing `"`` with `\"` while leaving `\"` as is
escape_quotes(s::AbstractString) = replace(s, r"([^\\])\"" => s"\1\\\\\"")

# According to the DOT language specification https://graphviz.org/doc/info/lang.html
# we can quote everyhthing that's not an XML/HTML literal
function quote_prop(p::AbstractString)
if occursin(r"<+.*>+$", p)
# The label is an HTML string, no additional quotes here.
return p
else
return "\"" * escape_quotes(p) * "\""
end
end
# if the property value is _not_ a string it cannot be XML/HTML literal, so just put it in quotes
quote_prop(p::Any) = "\"" * escape_quotes(string(p)) * "\""
# NOTE: down there I only quote property _values_. DOT allows quoting property _names_ too
# I don't do that as long as names are Symbols and can't have spaces and commas and stuff.
# That will break if someone uses a DOT keyword as a property name, as they must be quoted.

function savedot(io::IO, g::AbstractMetaGraph)

if is_directed(g)
Expand All @@ -27,24 +47,18 @@ function savedot(io::IO, g::AbstractMetaGraph)
end

for p in props(g)
write(io, "$(p[1])=$(p[2]);\n")
write(io, "$(p[1])=$(quote_prop(p[2]));\n")
end

for v in vertices(g)
write(io, "$v")
if length(props(g, v)) > 0
write(io, " [ ")
end
for p in props(g, v)
key = p[1]
if key .=== :label && occursin(r"<+.*>+$", p[2])
# The label is an HTML string, no additional quotes here.
write(io, "$key=$(p[2]),")
else
write(io, "$key=\"$(p[2])\",")

for p in props(g, v)
write(io, "$(p[1])=$(quote_prop(p[2])), ")
end
end
if length(props(g, v)) > 0

write(io, "];")
end
write(io, "\n")
Expand All @@ -53,7 +67,7 @@ function savedot(io::IO, g::AbstractMetaGraph)
for e in edges(g)
write(io, "$(src(e)) $dash $(dst(e)) [ ")
for p in props(g,e)
write(io, "$(p[1])=$(p[2]), ")
write(io, "$(p[1])=$(quote_prop(p[2])), ")
end
write(io, "]\n")
end
Expand Down
108 changes: 54 additions & 54 deletions test/diagram_ref.dot
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
digraph G {
pack=true;
1 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="weather",label="NOAA\nWeather",shape="record",];
2 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="cost",label="Vaccination\nCost",shape="record",];
3 [ color="orange",style="filled",penwidth="2.0",fillcolor="#dddddd",name="demo",label="Census\nDemographics",shape="record",];
4 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="fed",label="Fed Forcast",shape="record",];
5 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="epi",label="SIR",shape="record",];
6 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="ode",label="ODE Solver",shape="record",];
7 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="rate",label="{Transition\nRate}",shape="record",];
8 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="unit",label="Unit",shape="record",];
9 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="personper",label="Person/s",shape="record",];
10 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="person",label="Person",shape="record",];
11 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="second",label="second (s)",shape="record",];
12 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="dollars",label="$",shape="record",];
13 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="inf",label="Infection\nRate",shape="record",];
14 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="mort",label="Mortality\nRate",shape="record",];
15 [ color="#66AA55",style="filled",penwidth="2.0",fillcolor="#dddddd",name="birth",label="Birth\nRate",shape="record",];
16 [ color="#DD1133",style="filled",penwidth="2.0",fillcolor="#dddddd",name="twenty",label="0.2 Persons/s",shape="record",];
17 [ color="#DD1133",style="filled",penwidth="2.0",fillcolor="#dddddd",name="thirty",label="0.3 Persons/s",shape="record",];
18 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="ind",label="Individual\nContact\nModel",shape="record",];
19 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="temp",label="Temperature",shape="record",];
20 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="age",label="Age",shape="record",];
21 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="dGDP",label="Economic Growth",shape="record",];
22 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="cases",label="Flu\nCases",shape="record",];
23 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="prices",label="Vacc\nPrice",shape="record",];
24 [ color="#5DADE2",style="filled",penwidth="2.0",fillcolor="#dddddd",name="regres",label="Regression",shape="record",];
25 [ color="#000000",style="filled",penwidth="2.0",fillcolor="#dddddd",name="html",label=<<U><B>Title </B></U> <BR/> Some text.>,shape="record",];
1 -> 19 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
3 -> 15 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
3 -> 20 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
4 -> 21 [ color=missing, dir=none, penwidth=missing, style=missing, ]
5 -> 22 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
6 -> 5 [ color=missing, dir=none, penwidth=missing, style=missing, ]
7 -> 9 [ color=missing, dir=none, penwidth=missing, style=missing, ]
9 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ]
10 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ]
11 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ]
12 -> 2 [ color=missing, dir=none, penwidth=missing, style=missing, ]
12 -> 8 [ color=missing, dir=none, penwidth=missing, style=missing, ]
13 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ]
13 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
14 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ]
14 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
15 -> 7 [ color=black, dir=none, penwidth=2.0, style=dashed, ]
15 -> 18 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
16 -> 14 [ color=missing, dir=none, penwidth=missing, style=missing, ]
17 -> 13 [ color=missing, dir=none, penwidth=missing, style=missing, ]
18 -> 5 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
19 -> 13 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
20 -> 14 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
21 -> 23 [ color=missing, dir=none, penwidth=missing, style=missing, ]
22 -> 24 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
23 -> 24 [ color=missing, dir=none, penwidth=missing, style=missing, ]
24 -> 2 [ color=orange, dir=none, penwidth=4.0, style=solid, ]
25 -> 4 [ color=black, dir=none, penwidth=2.0, style=solid, ]
pack="true";
1 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="weather", label="NOAA\nWeather", shape="record", ];
2 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="cost", label="Vaccination\nCost", shape="record", ];
3 [ color="orange", style="filled", penwidth="2.0", fillcolor="#dddddd", name="demo", label="Census\nDemographics", shape="record", ];
4 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="fed", label="Fed Forcast", shape="record", ];
5 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="epi", label="SIR", shape="record", ];
6 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="ode", label="ODE Solver", shape="record", ];
7 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="rate", label="{Transition\nRate}", shape="record", ];
8 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="unit", label="Unit", shape="record", ];
9 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="personper", label="Person/s", shape="record", ];
10 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="person", label="Person", shape="record", ];
11 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="second", label="second (s)", shape="record", ];
12 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="dollars", label="$", shape="record", ];
13 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="inf", label="Infection\nRate", shape="record", ];
14 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="mort", label="Mortality\nRate", shape="record", ];
15 [ color="#66AA55", style="filled", penwidth="2.0", fillcolor="#dddddd", name="birth", label="Birth\nRate", shape="record", ];
16 [ color="#DD1133", style="filled", penwidth="2.0", fillcolor="#dddddd", name="twenty", label="0.2 Persons/s", shape="record", ];
17 [ color="#DD1133", style="filled", penwidth="2.0", fillcolor="#dddddd", name="thirty", label="0.3 Persons/s", shape="record", ];
18 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="ind", label="Individual\n\"Contact\"\nModel", shape="record", ];
19 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="temp", label="Temperature", shape="record", ];
20 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="age", label="Age", shape="record", ];
21 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="dGDP", label="Economic Growth", shape="record", ];
22 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="cases", label="Flu\nCases", shape="record", ];
23 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="prices", label="Vacc\nPrice", shape="record", ];
24 [ color="#5DADE2", style="filled", penwidth="2.0", fillcolor="#dddddd", name="regres", label="Regression", shape="record", ];
25 [ color="#000000", style="filled", penwidth="2.0", fillcolor="#dddddd", name="html", label=<<U><B>Title </B></U> <BR/> Some text.>, shape="record", ];
1 -> 19 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
3 -> 15 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
3 -> 20 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
4 -> 21 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
5 -> 22 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
6 -> 5 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
7 -> 9 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
9 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
10 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
11 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
12 -> 2 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
12 -> 8 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
13 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ]
13 -> 18 [ color="orange", dir="none", penwidth="4.0", label="one, other", style="solid", ]
14 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ]
14 -> 18 [ color="orange", dir="none", penwidth="4.0", label="cause", style="solid", ]
15 -> 7 [ color="black", dir="none", penwidth="2.0", label="", style="dashed", ]
15 -> 18 [ color="orange", dir="none", penwidth="4.0", label="one, other, and third one", style="solid", ]
16 -> 14 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
17 -> 13 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
18 -> 5 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
19 -> 13 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
20 -> 14 [ color="orange", dir="none", penwidth="4.0", label="", style="solid", ]
21 -> 23 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
22 -> 24 [ color="orange", dir="none", penwidth="4.0", label="with single \" here", style="solid", ]
23 -> 24 [ color="missing", dir="none", penwidth="missing", label="", style="missing", ]
24 -> 2 [ color="orange", dir="none", penwidth="4.0", label="with \"quoted\" word", style="solid", ]
25 -> 4 [ color="black", dir="none", penwidth="2.0", label="", style="solid", ]
}
69 changes: 35 additions & 34 deletions test/dotformat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@ using Test
using Graphs

property_set=[
(src="thirty",dst="inf",color="missing",penwidth="missing",style="missing",),
(src="twenty",dst="mort",color="missing",penwidth="missing",style="missing",),
(src="rate",dst="personper",color="missing",penwidth="missing",style="missing",),
(src="person",dst="unit",color="missing",penwidth="missing",style="missing",),
(src="personper",dst="unit",color="missing",penwidth="missing",style="missing",),
(src="second",dst="unit",color="missing",penwidth="missing",style="missing",),
(src="dollars",dst="unit",color="missing",penwidth="missing",style="missing",),
(src="dGDP",dst="prices",color="missing",penwidth="missing",style="missing",),
(src="prices",dst="regres",color="missing",penwidth="missing",style="missing",),
(src="fed",dst="dGDP",color="missing",penwidth="missing",style="missing",),
(src="ode",dst="epi",color="missing",penwidth="missing",style="missing",),
(src="dollars",dst="cost",color="missing",penwidth="missing",style="missing",),
(src="mort",dst="rate",color="black",penwidth="2.0",style="dashed",),
(src="inf",dst="rate",color="black",penwidth="2.0",style="dashed",),
(src="birth",dst="rate",color="black",penwidth="2.0",style="dashed",),
(src="mort",dst="ind",color="orange",penwidth="4.0",style="solid",),
(src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",),
(src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",),
(src="inf",dst="ind",color="orange",penwidth="4.0",style="solid",),
(src="birth",dst="ind",color="orange",penwidth="4.0",style="solid",),
(src="temp",dst="inf",color="orange",penwidth="4.0",style="solid",),
(src="age",dst="mort",color="orange",penwidth="4.0",style="solid",),
(src="demo",dst="birth",color="orange",penwidth="4.0",style="solid",),
(src="epi",dst="cases",color="orange",penwidth="4.0",style="solid",),
(src="cases",dst="regres",color="orange",penwidth="4.0",style="solid",),
(src="weather",dst="temp",color="orange",penwidth="4.0",style="solid",),
(src="demo",dst="age",color="orange",penwidth="4.0",style="solid",),
(src="regres",dst="cost",color="orange",penwidth="4.0",style="solid",),
(src="html",dst="fed",color="black",penwidth="2.0",style="solid",)
(src="thirty",dst="inf",color="missing",penwidth="missing",style="missing",label=""),
(src="twenty",dst="mort",color="missing",penwidth="missing",style="missing",label="",),
(src="rate",dst="personper",color="missing",penwidth="missing",style="missing",label="",),
(src="person",dst="unit",color="missing",penwidth="missing",style="missing",label="",),
(src="personper",dst="unit",color="missing",penwidth="missing",style="missing",label="",),
(src="second",dst="unit",color="missing",penwidth="missing",style="missing",label="",),
(src="dollars",dst="unit",color="missing",penwidth="missing",style="missing",label="",),
(src="dGDP",dst="prices",color="missing",penwidth="missing",style="missing",label="",),
(src="prices",dst="regres",color="missing",penwidth="missing",style="missing",label="",),
(src="fed",dst="dGDP",color="missing",penwidth="missing",style="missing",label="",),
(src="ode",dst="epi",color="missing",penwidth="missing",style="missing",label="",),
(src="dollars",dst="cost",color="missing",penwidth="missing",style="missing",label="",),
(src="mort",dst="rate",color="black",penwidth="2.0",style="dashed",label="",),
(src="inf",dst="rate",color="black",penwidth="2.0",style="dashed",label="",),
(src="birth",dst="rate",color="black",penwidth="2.0",style="dashed",label="",),
(src="mort",dst="ind",color="orange",penwidth="4.0",style="solid",label="cause",),
(src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",label="two words",),
(src="ind",dst="epi",color="orange",penwidth="4.0",style="solid",label="",),
(src="inf",dst="ind",color="orange",penwidth="4.0",style="solid",label="one, other",),
(src="birth",dst="ind",color="orange",penwidth="4.0",style="solid",label="one, other, and third one",),
(src="temp",dst="inf",color="orange",penwidth="4.0",style="solid",label="",),
(src="age",dst="mort",color="orange",penwidth="4.0",style="solid",label="",),
(src="demo",dst="birth",color="orange",penwidth="4.0",style="solid",label="",),
(src="epi",dst="cases",color="orange",penwidth="4.0",style="solid",label="",),
(src="cases",dst="regres",color="orange",penwidth="4.0",style="solid",label="with single \" here",),
(src="weather",dst="temp",color="orange",penwidth="4.0",style="solid",label="",),
(src="demo",dst="age",color="orange",penwidth="4.0",style="solid",label="",),
(src="regres",dst="cost",color="orange",penwidth="4.0",style="solid",label="with \"quoted\" word",),
(src="html",dst="fed",color="black",penwidth="2.0",style="solid",label="",)
]

# name, label, color
Expand All @@ -53,7 +53,7 @@ vprops = [
("birth","Birth\\nRate","#66AA55"),
("twenty","0.2 Persons/s","#DD1133"),
("thirty","0.3 Persons/s","#DD1133"),
("ind","Individual\\nContact\\nModel","#5DADE2"),
("ind","Individual\\n\"Contact\"\\nModel","#5DADE2"),
("temp","Temperature","#5DADE2"),
("age","Age","#5DADE2"),
("dGDP","Economic Growth","#5DADE2"),
Expand All @@ -79,17 +79,18 @@ set_indexing_prop!(g, :name)
# add edges
for prop in property_set
src, dst = g[prop.src, :name], g[prop.dst, :name]
add_edge!(g, src,dst)
add_edge!(g, src, dst)
set_prop!(g, src, dst, :color, prop.color)
set_prop!(g, src, dst, :penwidth, prop.penwidth)
set_prop!(g, src, dst, :style, prop.style)
set_prop!(g, src, dst, :label, prop.label)
end

# set global edge properties
for e in edges(g)
set_prop!(g, e, :dir, :none)
end
# set global vertex properties
# set global vertex properties
for v in vertices(g)
set_prop!(g, v, :shape, :record)
set_prop!(g, v, :style, :filled)
Expand All @@ -112,7 +113,7 @@ end
# - <...> : OK
# - both : NOK ("< or >" as bounding characters, resulting HTML in file will not be parsed correctly by dot.)
quote_regex = r"label\s*=\s*\"(?:[^\"\\]|\\.)*\"" # source: https://stackoverflow.com/questions/249791/regex-for-quoted-string-with-escaping-quotes
html_regex = r"label\s*=\s*<.*>"
html_regex = r"label\s*=\s*<.*>"
invalid_quote_regex = r"label\s*=\s*\"<(?:[^\"\\]|\\.)*>\""
for line in eachline(fp)
test_val = false
Expand All @@ -126,7 +127,7 @@ end
# no worries, proper HTML surrounding brackets found.
test_val = true
end
@test test_val
@test test_val
end
end

Expand Down

0 comments on commit 42a2f26

Please sign in to comment.