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  }