gonum.org/v1/gonum@v0.14.0/graph/encoding/dot/decode.go (about) 1 // Copyright ©2017 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package dot 6 7 import ( 8 "fmt" 9 "strconv" 10 "strings" 11 12 "gonum.org/v1/gonum/graph" 13 "gonum.org/v1/gonum/graph/encoding" 14 "gonum.org/v1/gonum/graph/formats/dot" 15 "gonum.org/v1/gonum/graph/formats/dot/ast" 16 "gonum.org/v1/gonum/graph/internal/set" 17 ) 18 19 // AttributeSetters is implemented by graph values that can set global 20 // DOT attributes. 21 type AttributeSetters interface { 22 // DOTAttributeSetters returns the global attribute setters. 23 DOTAttributeSetters() (graph, node, edge encoding.AttributeSetter) 24 } 25 26 // DOTIDSetter is implemented by types that can set a DOT ID. 27 type DOTIDSetter interface { 28 SetDOTID(id string) 29 } 30 31 // PortSetter is implemented by graph.Edge and graph.Line that can set 32 // the DOT port and compass directions of an edge. 33 type PortSetter interface { 34 // SetFromPort sets the From port and 35 // compass direction of the receiver. 36 SetFromPort(port, compass string) error 37 38 // SetToPort sets the To port and compass 39 // direction of the receiver. 40 SetToPort(port, compass string) error 41 } 42 43 // Unmarshal parses the Graphviz DOT-encoded data and stores the result in dst. 44 // If the number of graphs encoded in data is not one, an error is returned and 45 // dst will hold the first graph in data. 46 // 47 // Attributes and IDs are unquoted during unmarshalling if appropriate. 48 func Unmarshal(data []byte, dst encoding.Builder) error { 49 file, err := dot.ParseBytes(data) 50 if err != nil { 51 return err 52 } 53 err = copyGraph(dst, file.Graphs[0]) 54 if err == nil && len(file.Graphs) != 1 { 55 err = fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs)) 56 } 57 return err 58 } 59 60 // UnmarshalMulti parses the Graphviz DOT-encoded data as a multigraph and 61 // stores the result in dst. 62 // If the number of graphs encoded in data is not one, an error is returned and 63 // dst will hold the first graph in data. 64 // 65 // Attributes and IDs are unquoted during unmarshalling if appropriate. 66 func UnmarshalMulti(data []byte, dst encoding.MultiBuilder) error { 67 file, err := dot.ParseBytes(data) 68 if err != nil { 69 return err 70 } 71 err = copyMultigraph(dst, file.Graphs[0]) 72 if err == nil && len(file.Graphs) != 1 { 73 err = fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs)) 74 } 75 return err 76 } 77 78 // copyGraph copies the nodes and edges from the Graphviz AST source graph to 79 // the destination graph. Edge direction is maintained if present. 80 func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) { 81 defer func() { 82 switch e := recover().(type) { 83 case nil: 84 case error: 85 err = e 86 default: 87 panic(e) 88 } 89 }() 90 gen := &simpleGraph{ 91 generator: generator{ 92 directed: src.Directed, 93 ids: make(map[string]graph.Node), 94 }, 95 } 96 if dst, ok := dst.(DOTIDSetter); ok { 97 dst.SetDOTID(unquoteID(src.ID)) 98 } 99 if a, ok := dst.(AttributeSetters); ok { 100 gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTAttributeSetters() 101 } 102 for _, stmt := range src.Stmts { 103 gen.addStmt(dst, stmt) 104 } 105 return err 106 } 107 108 // copyMultigraph copies the nodes and edges from the Graphviz AST source graph to 109 // the destination graph. Edge direction is maintained if present. 110 func copyMultigraph(dst encoding.MultiBuilder, src *ast.Graph) (err error) { 111 defer func() { 112 switch e := recover().(type) { 113 case nil: 114 case error: 115 err = e 116 default: 117 panic(e) 118 } 119 }() 120 gen := &multiGraph{ 121 generator: generator{ 122 directed: src.Directed, 123 ids: make(map[string]graph.Node), 124 }, 125 } 126 if dst, ok := dst.(DOTIDSetter); ok { 127 dst.SetDOTID(unquoteID(src.ID)) 128 } 129 if a, ok := dst.(AttributeSetters); ok { 130 gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTAttributeSetters() 131 } 132 for _, stmt := range src.Stmts { 133 gen.addStmt(dst, stmt) 134 } 135 return err 136 } 137 138 // A generator keeps track of the information required for generating a Gonum 139 // graph from a dot AST graph. 140 type generator struct { 141 // Directed graph. 142 directed bool 143 // Map from dot AST node ID to Gonum node. 144 ids map[string]graph.Node 145 // Nodes processed within the context of a subgraph, that is to be used as a 146 // vertex of an edge. 147 subNodes []graph.Node 148 // Stack of start indices into the subgraph node slice. The top element 149 // corresponds to the start index of the active (or inner-most) subgraph. 150 subStart []int 151 // graphAttr, nodeAttr and edgeAttr are global graph attributes. 152 graphAttr, nodeAttr, edgeAttr encoding.AttributeSetter 153 } 154 155 // node returns the Gonum node corresponding to the given dot AST node ID, 156 // generating a new such node if none exist. 157 func (gen *generator) node(dst graph.NodeAdder, id string) graph.Node { 158 if n, ok := gen.ids[id]; ok { 159 return n 160 } 161 n := dst.NewNode() 162 if n, ok := n.(DOTIDSetter); ok { 163 n.SetDOTID(unquoteID(id)) 164 } 165 dst.AddNode(n) 166 gen.ids[id] = n 167 // Check if within the context of a subgraph, that is to be used as a vertex 168 // of an edge. 169 if gen.isInSubgraph() { 170 // Append node processed within the context of a subgraph, that is to be 171 // used as a vertex of an edge 172 gen.appendSubgraphNode(n) 173 } 174 return n 175 } 176 177 type simpleGraph struct{ generator } 178 179 // addStmt adds the given statement to the graph. 180 func (gen *simpleGraph) addStmt(dst encoding.Builder, stmt ast.Stmt) { 181 switch stmt := stmt.(type) { 182 case *ast.NodeStmt: 183 n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter) 184 if !ok { 185 return 186 } 187 for _, attr := range stmt.Attrs { 188 a := encoding.Attribute{ 189 Key: unquoteID(attr.Key), 190 Value: unquoteID(attr.Val), 191 } 192 if err := n.SetAttribute(a); err != nil { 193 panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s): %v", a.Key, a.Value, err)) 194 } 195 } 196 case *ast.EdgeStmt: 197 gen.addEdgeStmt(dst, stmt) 198 case *ast.AttrStmt: 199 var n encoding.AttributeSetter 200 var dst string 201 switch stmt.Kind { 202 case ast.GraphKind: 203 if gen.graphAttr == nil { 204 return 205 } 206 n = gen.graphAttr 207 dst = "graph" 208 case ast.NodeKind: 209 if gen.nodeAttr == nil { 210 return 211 } 212 n = gen.nodeAttr 213 dst = "node" 214 case ast.EdgeKind: 215 if gen.edgeAttr == nil { 216 return 217 } 218 n = gen.edgeAttr 219 dst = "edge" 220 default: 221 panic("unreachable") 222 } 223 for _, attr := range stmt.Attrs { 224 a := encoding.Attribute{ 225 Key: unquoteID(attr.Key), 226 Value: unquoteID(attr.Val), 227 } 228 if err := n.SetAttribute(a); err != nil { 229 panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s): %v", dst, a.Key, a.Value, err)) 230 } 231 } 232 case *ast.Attr: 233 // ignore. 234 case *ast.Subgraph: 235 for _, stmt := range stmt.Stmts { 236 gen.addStmt(dst, stmt) 237 } 238 default: 239 panic(fmt.Sprintf("unknown statement type %T", stmt)) 240 } 241 } 242 243 // basicEdge is an edge without the Reverse method to 244 // allow satisfaction by both graph.Edge and graph.Line. 245 type basicEdge interface { 246 From() graph.Node 247 To() graph.Node 248 } 249 250 // applyPortsToEdge applies the available port metadata from an ast.Edge 251 // to a graph.Edge 252 func applyPortsToEdge(from ast.Vertex, to *ast.Edge, edge basicEdge) { 253 if ps, isPortSetter := edge.(PortSetter); isPortSetter { 254 if n, vertexIsNode := from.(*ast.Node); vertexIsNode { 255 if n.Port != nil { 256 err := ps.SetFromPort(unquoteID(n.Port.ID), n.Port.CompassPoint.String()) 257 if err != nil { 258 panic(fmt.Errorf("unable to unmarshal edge port (:%s:%s)", n.Port.ID, n.Port.CompassPoint.String())) 259 } 260 } 261 } 262 263 if n, vertexIsNode := to.Vertex.(*ast.Node); vertexIsNode { 264 if n.Port != nil { 265 err := ps.SetToPort(unquoteID(n.Port.ID), n.Port.CompassPoint.String()) 266 if err != nil { 267 panic(fmt.Errorf("unable to unmarshal edge DOT port (:%s:%s)", n.Port.ID, n.Port.CompassPoint.String())) 268 } 269 } 270 } 271 } 272 } 273 274 // addEdgeStmt adds the given edge statement to the graph. 275 func (gen *simpleGraph) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) { 276 fs := gen.addVertex(dst, stmt.From) 277 ts := gen.addEdge(dst, stmt.To, stmt.Attrs) 278 defer func() { 279 switch e := recover().(type) { 280 case nil: 281 // Do nothing. 282 case error: 283 panic(e) 284 default: 285 panic(fmt.Errorf("panic setting edge: %v", e)) 286 } 287 }() 288 for _, f := range fs { 289 for _, t := range ts { 290 edge := dst.NewEdge(f, t) 291 dst.SetEdge(edge) 292 applyPortsToEdge(stmt.From, stmt.To, edge) 293 addEdgeAttrs(edge, stmt.Attrs) 294 } 295 } 296 } 297 298 // addVertex adds the given vertex to the graph, and returns its set of nodes. 299 func (gen *simpleGraph) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node { 300 switch v := v.(type) { 301 case *ast.Node: 302 n := gen.node(dst, v.ID) 303 return []graph.Node{n} 304 case *ast.Subgraph: 305 gen.pushSubgraph() 306 for _, stmt := range v.Stmts { 307 gen.addStmt(dst, stmt) 308 } 309 return gen.popSubgraph() 310 default: 311 panic(fmt.Sprintf("unknown vertex type %T", v)) 312 } 313 } 314 315 // addEdge adds the given edge to the graph, and returns its set of nodes. 316 func (gen *simpleGraph) addEdge(dst encoding.Builder, to *ast.Edge, attrs []*ast.Attr) []graph.Node { 317 if !gen.directed && to.Directed { 318 panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex)) 319 } 320 fs := gen.addVertex(dst, to.Vertex) 321 if to.To != nil { 322 ts := gen.addEdge(dst, to.To, attrs) 323 for _, f := range fs { 324 for _, t := range ts { 325 edge := dst.NewEdge(f, t) 326 dst.SetEdge(edge) 327 applyPortsToEdge(to.Vertex, to.To, edge) 328 addEdgeAttrs(edge, attrs) 329 } 330 } 331 } 332 return fs 333 } 334 335 // pushSubgraph pushes the node start index of the active subgraph onto the 336 // stack. 337 func (gen *generator) pushSubgraph() { 338 gen.subStart = append(gen.subStart, len(gen.subNodes)) 339 } 340 341 // popSubgraph pops the node start index of the active subgraph from the stack, 342 // and returns the nodes processed since. 343 func (gen *generator) popSubgraph() []graph.Node { 344 // Get nodes processed since the subgraph became active. 345 start := gen.subStart[len(gen.subStart)-1] 346 // TODO: Figure out a better way to store subgraph nodes, so that duplicates 347 // may not occur. 348 nodes := unique(gen.subNodes[start:]) 349 // Remove subgraph from stack. 350 gen.subStart = gen.subStart[:len(gen.subStart)-1] 351 if len(gen.subStart) == 0 { 352 // Remove subgraph nodes when the bottom-most subgraph has been processed. 353 gen.subNodes = gen.subNodes[:0] 354 } 355 return nodes 356 } 357 358 // unique returns the set of unique nodes contained within ns. 359 func unique(ns []graph.Node) []graph.Node { 360 var nodes []graph.Node 361 seen := make(set.Int64s) 362 for _, n := range ns { 363 id := n.ID() 364 if seen.Has(id) { 365 // skip duplicate node 366 continue 367 } 368 seen.Add(id) 369 nodes = append(nodes, n) 370 } 371 return nodes 372 } 373 374 // isInSubgraph reports whether the active context is within a subgraph, that is 375 // to be used as a vertex of an edge. 376 func (gen *generator) isInSubgraph() bool { 377 return len(gen.subStart) > 0 378 } 379 380 // appendSubgraphNode appends the given node to the slice of nodes processed 381 // within the context of a subgraph. 382 func (gen *generator) appendSubgraphNode(n graph.Node) { 383 gen.subNodes = append(gen.subNodes, n) 384 } 385 386 type multiGraph struct{ generator } 387 388 // addStmt adds the given statement to the multigraph. 389 func (gen *multiGraph) addStmt(dst encoding.MultiBuilder, stmt ast.Stmt) { 390 switch stmt := stmt.(type) { 391 case *ast.NodeStmt: 392 n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter) 393 if !ok { 394 return 395 } 396 for _, attr := range stmt.Attrs { 397 a := encoding.Attribute{ 398 Key: unquoteID(attr.Key), 399 Value: unquoteID(attr.Val), 400 } 401 if err := n.SetAttribute(a); err != nil { 402 panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s): %v", a.Key, a.Value, err)) 403 } 404 } 405 case *ast.EdgeStmt: 406 gen.addEdgeStmt(dst, stmt) 407 case *ast.AttrStmt: 408 var n encoding.AttributeSetter 409 var dst string 410 switch stmt.Kind { 411 case ast.GraphKind: 412 if gen.graphAttr == nil { 413 return 414 } 415 n = gen.graphAttr 416 dst = "graph" 417 case ast.NodeKind: 418 if gen.nodeAttr == nil { 419 return 420 } 421 n = gen.nodeAttr 422 dst = "node" 423 case ast.EdgeKind: 424 if gen.edgeAttr == nil { 425 return 426 } 427 n = gen.edgeAttr 428 dst = "edge" 429 default: 430 panic("unreachable") 431 } 432 for _, attr := range stmt.Attrs { 433 a := encoding.Attribute{ 434 Key: unquoteID(attr.Key), 435 Value: unquoteID(attr.Val), 436 } 437 if err := n.SetAttribute(a); err != nil { 438 panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s): %v", dst, a.Key, a.Value, err)) 439 } 440 } 441 case *ast.Attr: 442 // ignore. 443 case *ast.Subgraph: 444 for _, stmt := range stmt.Stmts { 445 gen.addStmt(dst, stmt) 446 } 447 default: 448 panic(fmt.Sprintf("unknown statement type %T", stmt)) 449 } 450 } 451 452 // addEdgeStmt adds the given edge statement to the multigraph. 453 func (gen *multiGraph) addEdgeStmt(dst encoding.MultiBuilder, stmt *ast.EdgeStmt) { 454 fs := gen.addVertex(dst, stmt.From) 455 ts := gen.addLine(dst, stmt.To, stmt.Attrs) 456 for _, f := range fs { 457 for _, t := range ts { 458 edge := dst.NewLine(f, t) 459 dst.SetLine(edge) 460 applyPortsToEdge(stmt.From, stmt.To, edge) 461 addEdgeAttrs(edge, stmt.Attrs) 462 } 463 } 464 } 465 466 // addVertex adds the given vertex to the multigraph, and returns its set of nodes. 467 func (gen *multiGraph) addVertex(dst encoding.MultiBuilder, v ast.Vertex) []graph.Node { 468 switch v := v.(type) { 469 case *ast.Node: 470 n := gen.node(dst, v.ID) 471 return []graph.Node{n} 472 case *ast.Subgraph: 473 gen.pushSubgraph() 474 for _, stmt := range v.Stmts { 475 gen.addStmt(dst, stmt) 476 } 477 return gen.popSubgraph() 478 default: 479 panic(fmt.Sprintf("unknown vertex type %T", v)) 480 } 481 } 482 483 // addLine adds the given edge to the multigraph, and returns its set of nodes. 484 func (gen *multiGraph) addLine(dst encoding.MultiBuilder, to *ast.Edge, attrs []*ast.Attr) []graph.Node { 485 if !gen.directed && to.Directed { 486 panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex)) 487 } 488 fs := gen.addVertex(dst, to.Vertex) 489 if to.To != nil { 490 ts := gen.addLine(dst, to.To, attrs) 491 for _, f := range fs { 492 for _, t := range ts { 493 edge := dst.NewLine(f, t) 494 dst.SetLine(edge) 495 applyPortsToEdge(to.Vertex, to.To, edge) 496 addEdgeAttrs(edge, attrs) 497 } 498 } 499 } 500 return fs 501 } 502 503 // addEdgeAttrs adds the attributes to the given edge. 504 func addEdgeAttrs(edge basicEdge, attrs []*ast.Attr) { 505 e, ok := edge.(encoding.AttributeSetter) 506 if !ok { 507 return 508 } 509 for _, attr := range attrs { 510 a := encoding.Attribute{ 511 Key: unquoteID(attr.Key), 512 Value: unquoteID(attr.Val), 513 } 514 if err := e.SetAttribute(a); err != nil { 515 panic(fmt.Errorf("unable to unmarshal edge DOT attribute (%s=%s): %v", a.Key, a.Value, err)) 516 } 517 } 518 } 519 520 // unquoteID unquotes the given string if needed in the context of an ID. If s 521 // is not already quoted the original string is returned. 522 func unquoteID(s string) string { 523 // To make round-trips idempotent, don't unquote quoted HTML-like strings 524 // 525 // /^"<.*>"$/ 526 if len(s) >= 4 && strings.HasPrefix(s, `"<`) && strings.HasSuffix(s, `>"`) { 527 return s 528 } 529 // Unquote quoted string if possible. 530 if t, err := strconv.Unquote(s); err == nil { 531 return t 532 } 533 // On error, either s is not quoted or s is quoted but contains invalid 534 // characters, in both cases we return the original string rather than 535 // panicking. 536 return s 537 }