graph/encoding/dot: add multigraph serialization and deserialization support

This commit is contained in:
J. Holmes
2018-11-24 14:01:56 -07:00
committed by Dan Kortschak
parent dc5eba8a13
commit b3c4e40467
6 changed files with 791 additions and 37 deletions

View File

@@ -50,6 +50,19 @@ func Unmarshal(data []byte, dst encoding.Builder) error {
return copyGraph(dst, file.Graphs[0])
}
// UnmarshalMulti parses the Graphviz DOT-encoded data as a multigraph and
// stores the result in dst.
func UnmarshalMulti(data []byte, dst encoding.MultiBuilder) error {
file, err := dot.ParseBytes(data)
if err != nil {
return err
}
if len(file.Graphs) != 1 {
return fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs))
}
return copyMultigraph(dst, file.Graphs[0])
}
// copyGraph copies the nodes and edges from the Graphviz AST source graph to
// the destination graph. Edge direction is maintained if present.
func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) {
@@ -62,9 +75,41 @@ func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) {
panic(e)
}
}()
gen := &generator{
directed: src.Directed,
ids: make(map[string]graph.Node),
gen := &simpleGraph{
generator: generator{
directed: src.Directed,
ids: make(map[string]graph.Node),
},
}
if dst, ok := dst.(DOTIDSetter); ok {
dst.SetDOTID(src.ID)
}
if a, ok := dst.(AttributeSetters); ok {
gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTAttributeSetters()
}
for _, stmt := range src.Stmts {
gen.addStmt(dst, stmt)
}
return err
}
// copyMultigraph copies the nodes and edges from the Graphviz AST source graph to
// the destination graph. Edge direction is maintained if present.
func copyMultigraph(dst encoding.MultiBuilder, src *ast.Graph) (err error) {
defer func() {
switch e := recover().(type) {
case nil:
case error:
err = e
default:
panic(e)
}
}()
gen := &multiGraph{
generator: generator{
directed: src.Directed,
ids: make(map[string]graph.Node),
},
}
if dst, ok := dst.(DOTIDSetter); ok {
dst.SetDOTID(src.ID)
@@ -97,7 +142,7 @@ type generator struct {
// node returns the gonum node corresponding to the given dot AST node ID,
// generating a new such node if none exist.
func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
func (gen *generator) node(dst graph.NodeAdder, id string) graph.Node {
if n, ok := gen.ids[id]; ok {
return n
}
@@ -117,8 +162,10 @@ func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
return n
}
type simpleGraph struct{ generator }
// addStmt adds the given statement to the graph.
func (gen *generator) addStmt(dst encoding.Builder, stmt ast.Stmt) {
func (gen *simpleGraph) addStmt(dst encoding.Builder, stmt ast.Stmt) {
switch stmt := stmt.(type) {
case *ast.NodeStmt:
n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter)
@@ -206,7 +253,7 @@ func applyPortsToEdge(from ast.Vertex, to *ast.Edge, edge graph.Edge) {
}
// addEdgeStmt adds the given edge statement to the graph.
func (gen *generator) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
func (gen *simpleGraph) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
fs := gen.addVertex(dst, stmt.From)
ts := gen.addEdge(dst, stmt.To, stmt.Attrs)
for _, f := range fs {
@@ -220,7 +267,7 @@ func (gen *generator) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
}
// addVertex adds the given vertex to the graph, and returns its set of nodes.
func (gen *generator) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node {
func (gen *simpleGraph) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node {
switch v := v.(type) {
case *ast.Node:
n := gen.node(dst, v.ID)
@@ -237,7 +284,7 @@ func (gen *generator) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node
}
// addEdge adds the given edge to the graph, and returns its set of nodes.
func (gen *generator) addEdge(dst encoding.Builder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
func (gen *simpleGraph) addEdge(dst encoding.Builder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
if !gen.directed && to.Directed {
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
}
@@ -307,6 +354,123 @@ func (gen *generator) appendSubgraphNode(n graph.Node) {
gen.subNodes = append(gen.subNodes, n)
}
type multiGraph struct{ generator }
// addStmt adds the given statement to the multigraph.
func (gen *multiGraph) addStmt(dst encoding.MultiBuilder, stmt ast.Stmt) {
switch stmt := stmt.(type) {
case *ast.NodeStmt:
n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter)
if !ok {
return
}
for _, attr := range stmt.Attrs {
a := encoding.Attribute{
Key: attr.Key,
Value: attr.Val,
}
if err := n.SetAttribute(a); err != nil {
panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value))
}
}
case *ast.EdgeStmt:
gen.addEdgeStmt(dst, stmt)
case *ast.AttrStmt:
var n encoding.AttributeSetter
var dst string
switch stmt.Kind {
case ast.GraphKind:
if gen.graphAttr == nil {
return
}
n = gen.graphAttr
dst = "graph"
case ast.NodeKind:
if gen.nodeAttr == nil {
return
}
n = gen.nodeAttr
dst = "node"
case ast.EdgeKind:
if gen.edgeAttr == nil {
return
}
n = gen.edgeAttr
dst = "edge"
default:
panic("unreachable")
}
for _, attr := range stmt.Attrs {
a := encoding.Attribute{
Key: attr.Key,
Value: attr.Val,
}
if err := n.SetAttribute(a); err != nil {
panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value))
}
}
case *ast.Attr:
// ignore.
case *ast.Subgraph:
for _, stmt := range stmt.Stmts {
gen.addStmt(dst, stmt)
}
default:
panic(fmt.Sprintf("unknown statement type %T", stmt))
}
}
// addEdgeStmt adds the given edge statement to the multigraph.
func (gen *multiGraph) addEdgeStmt(dst encoding.MultiBuilder, stmt *ast.EdgeStmt) {
fs := gen.addVertex(dst, stmt.From)
ts := gen.addLine(dst, stmt.To, stmt.Attrs)
for _, f := range fs {
for _, t := range ts {
edge := dst.NewLine(f, t)
dst.SetLine(edge)
applyPortsToEdge(stmt.From, stmt.To, edge)
addEdgeAttrs(edge, stmt.Attrs)
}
}
}
// addVertex adds the given vertex to the multigraph, and returns its set of nodes.
func (gen *multiGraph) addVertex(dst encoding.MultiBuilder, v ast.Vertex) []graph.Node {
switch v := v.(type) {
case *ast.Node:
n := gen.node(dst, v.ID)
return []graph.Node{n}
case *ast.Subgraph:
gen.pushSubgraph()
for _, stmt := range v.Stmts {
gen.addStmt(dst, stmt)
}
return gen.popSubgraph()
default:
panic(fmt.Sprintf("unknown vertex type %T", v))
}
}
// addLine adds the given edge to the multigraph, and returns its set of nodes.
func (gen *multiGraph) addLine(dst encoding.MultiBuilder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
if !gen.directed && to.Directed {
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
}
fs := gen.addVertex(dst, to.Vertex)
if to.To != nil {
ts := gen.addLine(dst, to.To, attrs)
for _, f := range fs {
for _, t := range ts {
edge := dst.NewLine(f, t)
dst.SetLine(edge)
applyPortsToEdge(to.Vertex, to.To, edge)
addEdgeAttrs(edge, attrs)
}
}
}
return fs
}
// addEdgeAttrs adds the attributes to the given edge.
func addEdgeAttrs(edge graph.Edge, attrs []*ast.Attr) {
e, ok := edge.(encoding.AttributeSetter)