Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.6.0"
[deps]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"

Expand Down
192 changes: 192 additions & 0 deletions src/GraphML/GraphML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,114 @@ using Graphs: AbstractGraphFormat

import Graphs: loadgraph, loadgraphs, savegraph

using MetaGraphs

export GraphMLFormat


# TODO: implement writing a dict of graphs

struct GraphMLFormat <: AbstractGraphFormat end

@enum GraphMLAttributesDomain atgraph atnode atedge atall
const graphMLAttributesDomain = Dict("graph" => atgraph,
"node" => atnode,
"edge" => atedge,
"all" => atall)

@enum GraphlMLAttributesType atboolean atint atlong atfloat atdouble atstring
const graphMLAttributesType = Dict("int" => Int,
"boolean" => Bool,
"long" => Int128,
"float" => Float64,
"double" => Float64,
"string" => String)

struct AttrKey{T}
id::String
name::String
domain::GraphMLAttributesDomain
type::Type{T}
default::Union{T,Nothing}
end

function _get_key_props(doc::EzXML.Document)
ns = namespace(doc.root)
keynodes = findall("//x:key", doc.root, ["x"=>ns])
keyprops = Dict{String,AttrKey}()
for keynode in keynodes
attrtype = graphMLAttributesType[strip(keynode["attr.type"])]
keyadded = false
for childnode in EzXML.eachnode(keynode)
if EzXML.nodename(childnode) == "default"
defaultcontent = strip(nodecontent(childnode))
keyprops[keynode["id"]] = AttrKey(keynode["id"], keynode["attr.name"], graphMLAttributesDomain[keynode["for"]], attrtype, attrtype == String ? defaultcontent : parse(attrtype, defaultcontent) )
keyadded = true
end
end
if !keyadded
keyprops[keynode["id"]] = AttrKey(keynode["id"], keynode["attr.name"], graphMLAttributesDomain[keynode["for"]], attrtype, nothing )
end
end
return keyprops
end

function _loadmetagraph_fromnode(graphnode::EzXML.Node, keyprops::Dict{String, AttrKey})
ns = namespace(graphnode)
gr = graphnode["edgedefault"] == "directed" ? MetaDiGraph() : MetaGraph()
set_indexing_prop!(gr, :id)
defaults = [v for v in values(keyprops) if getfield(v,:default) !== nothing && getfield(v,:domain) == atnode]
for (i,node) in enumerate(findall("x:node", graphnode, ["x"=>ns]))
add_vertex!(gr)
set_prop!(gr, i, :id, node["id"])
for def in defaults
set_prop!(gr, i, Symbol(def.name), def.default)
end
for data in findall("x:data", node, ["x"=>ns])
set_prop!(gr, i, Symbol(keyprops[data["key"]].name), keyprops[data["key"]].type == String ? nodecontent(data) : parse(keyprops[data["key"]].type, nodecontent(data)))
end
end

defaults = [v for v in values(keyprops) if getfield(v,:default) !== nothing && getfield(v,:domain) == atedge]
for edge in findall("x:edge", graphnode, ["x"=>ns])
srcnode = gr[edge["source"],:id]
trgnode = gr[edge["target"],:id]
add_edge!(gr, srcnode, trgnode)
set_prop!(gr, srcnode, trgnode, :id, edge["id"])
for def in defaults
set_prop!(gr, srcnode, trgnode, Symbol(def.name), def.default)
end
for data in findall("x:data", edge, ["x"=>ns])
set_prop!(gr, srcnode, trgnode, Symbol(keyprops[data["key"]].name), keyprops[data["key"]].type == String ? strip(nodecontent(data)) : parse(keyprops[data["key"]].type, nodecontent(data)))
end
end
return gr
end

function loadmetagraphml(io::IO, gname::String)
doc = readxml(io)
ns = namespace(doc.root)
keyprops = _get_key_props(doc)


graphnodes = findall("//x:graph", doc.root, ["x"=>ns])
for graphnode in graphnodes
if graphnode["id"] == gname
return _loadmetagraph_fromnode(graphnode, keyprops)
end
end
end
function loadmetagraphml_mult(io::IO)
doc = readxml(io)
ns = namespace(doc.root)
keyprops = _get_key_props(doc)

graphnodes = findall("//x:graph", doc.root, ["x"=>ns])

graphs = Dict(graphnode["id"] => _loadmetagraph_fromnode(graphnode, keyprops)
for graphnode in graphnodes)
end

function _graphml_read_one_graph(reader::EzXML.StreamReader, isdirected::Bool)
nodes = Dict{String,Int}()
xedges = Vector{Graphs.Edge}()
Expand Down Expand Up @@ -129,10 +230,101 @@ end
savegraphml(io::IO, g::Graphs.AbstractGraph, gname::String) =
savegraphml_mult(io, Dict(gname => g))

function _get_attr_type(mg::AbstractMetaGraph, attr, forel)
if forel == atnode
els = vertices(mg)
elseif forel == atedge
els = edges(mg)
end
for el in els
has_prop(mg, el, attr) && return typeof(get_prop(mg, el, attr))
end
end

function savemetagraphml_mult(io::IO, dgr::Dict{String, T}) where T<:AbstractMetaGraph
xdoc = XMLDocument()
xroot = setroot!(xdoc, ElementNode("graphml"))
xroot["xmlns"] = "http://graphml.graphdrawing.org/xmlns"
xroot["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance"
xroot["xsi:schemaLocation"] = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"

#adds keys
attrforellist = Vector{Tuple{Symbol, GraphMLAttributesDomain}}()
for mg in values(dgr)
vattrs = Set(v for keyset in keys.(values(mg.vprops)) for v in keyset)
eattrs = Set(v for keyset in keys.(values(mg.eprops)) for v in keyset)
for (attr, forel) in Iterators.flatten([zip(vattrs, Iterators.cycle([atnode])),
zip(eattrs, Iterators.cycle([atedge]))])
(attr, forel) in attrforellist && continue
push!(attrforellist, (attr, forel))
xkey = addelement!(xroot, "key")
xkey["attr.name"] = string(attr)
xkey["attr.type"] = first(Iterators.filter(x -> x.second == _get_attr_type(mg, attr, forel), graphMLAttributesType)).first
xkey["for"] = first(Iterators.filter(x -> x.second == forel, graphMLAttributesDomain)).first
xkey["id"] = string(attr)
end
end

for (gname, mg) in dgr
xg = addelement!(xroot, "graph")
xg["id"] = gname
xg["edgedefault"] = is_directed(mg) ? "directed" : "undirected"

for i in 1:nv(mg)
xv = addelement!(xg, "node")
if has_prop(mg, i, :id)
xv["id"] = get_prop(mg, i, :id)
else
xv["id"] = "n$(i-1)"
end
for (k,v) in props(mg, i)
k == :id && continue
xel = addelement!(xv, "data", string(v))
xel["key"] = k
end
end

m = 0
for e in Graphs.edges(mg)
xe = addelement!(xg, "edge")

if has_prop(mg, e, :id)
xe["id"] = get_prop(mg, e, :id)
else
xe["id"] = "e$(m)"
end

if has_prop(mg, src(e), :id)
xe["source"] = get_prop(mg, src(e), :id)
else
xe["source"] = "n$(src(e)-1)"
end
if has_prop(mg, dst(e), :id)
xe["target"] = get_prop(mg, dst(e), :id)
else
xe["target"] = "n$(dst(e)-1)"
end

for (k,v) in props(mg, e)
k == :id && continue
xel = addelement!(xe, "data", string(v))
xel["key"] = k
end
m += 1
end
end
prettyprint(io, xdoc)
return 1
end

loadgraph(io::IO, gname::String, ::GraphMLFormat, ::MGFormat) = loadmetagraphml(io, gname)
loadgraphs(io::IO, ::GraphMLFormat, ::MGFormat) = loadmetagraphml_mult(io)
loadgraph(io::IO, gname::String, ::GraphMLFormat) = loadgraphml(io, gname)
loadgraphs(io::IO, ::GraphMLFormat) = loadgraphml_mult(io)

savegraph(io::IO, g::AbstractMetaGraph, gname::String, ::GraphMLFormat) = savemetagraphml_mult(io, Dict(gname => g))
savegraph(io::IO, g::AbstractGraph, gname::String, ::GraphMLFormat) = savegraphml(io, g, gname)
savegraph(io::IO, d::Dict, ::GraphMLFormat) = savegraphml_mult(io, d)
savegraph(io::IO, d::Dict{String, T}, ::GraphMLFormat) where T<:AbstractMetaGraph = savemetagraphml_mult(io, d)

end # module
55 changes: 54 additions & 1 deletion test/GraphML/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,69 @@
using Test
using EzXML
using GraphIO.GraphML
using Graphs, MetaGraphs

@testset "GraphML" begin
for g in values(allgraphs)
readback_test(GraphMLFormat(), g, testfail=true)
end
fname = joinpath(testdir, "testdata", "warngraph.graphml")

@test_logs (:warn, "Skipping unknown node 'warnnode' - further warnings will be suppressed") match_mode=:any loadgraphs(fname, GraphMLFormat())
@test_logs (:warn, "Skipping unknown XML element 'warnelement' - further warnings will be suppressed") match_mode=:any loadgraph(fname, "graph", GraphMLFormat())
d = loadgraphs(fname, GraphMLFormat())
write_test(GraphMLFormat(), d)
end

function test_read_metagraph(dmg)
for v in vertices(dmg)
if get_prop(dmg, v, :id) == "N6"
@test get_prop(dmg, v, :VertexLabel) == "N6"
@test get_prop(dmg, v, :xcoord) == 170
@test get_prop(dmg, v, :ycoord) == 0
end
end
for e in edges(dmg)
if get_prop(dmg, e, :id) == "N0-N3"
@test get_prop(dmg, e, :LinkCapacity) == 100
end
end
end

@testset "GraphML-MGFormat" begin
# single graph
fname = joinpath(testdir, "testdata", "complex.graphml")
mg = open(fname, "r") do io
loadgraph(io, "main-graph", GraphMLFormat(), MGFormat())
end
test_read_metagraph(mg)

# re-read must be equal
ftname = joinpath(testdir, "testdata", "complex_main-graph_write.graphml")
savegraph(ftname, mg, "main-graph", GraphMLFormat())
mg2 = open(ftname, "r") do io
loadgraph(io, "main-graph", GraphMLFormat(), MGFormat())
end
@test mg == mg2 && mg.vprops == mg2.vprops && mg.eprops == mg2.eprops
rm(ftname)

# multiple graphs
dmg = open(fname, "r") do io
loadgraphs(io, GraphMLFormat(), MGFormat())
end

@test length(dmg) == 2
test_read_metagraph(dmg["main-graph"])

# re-read must be equal
ftname = joinpath(testdir, "testdata", "complex_write.graphml")
savegraph(ftname, mg, GraphMLFormat())
dmg2 = open(ftname, "r") do io
loadgraphs(io, GraphMLFormat(), MGFormat())
end
for (dmg_g, dmg2_g) in zip(values(dmg), values(dmg2))
@test dmg_g == dmg2_g && dmg_g.vprops == dmg2_g.vprops && dmg_g.eprops == dmg2_g.eprops
end
rm(ftname)
end

Loading