graph/encoding/dot: quote attributes on output
Created by: mewmew
What are you trying to do?
I have several attributes that I marshal back and forth between DOT graph files. To encode these attributes the encoding.Attributer
interface is implemented.
However, to correctly encode attributes containing spaces (or special characters), the attributes have to be escaped correctly. Given that encoding.Attributer is a "generic" interface, the output format is not always known, and using a double quoted string may be correct for say DOT files, but perhaps not another format.
What did you try?
Run the following program to produce a DOT file.
package main
import (
"fmt"
"sort"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/encoding/dot"
"gonum.org/v1/gonum/graph/simple"
)
func main() {
g := NewGraph()
u := g.NewNode().(*Node)
u.attrs["label"] = "hello world"
u.attrs["fillstyle"] = "filled"
u.attrs["color"] = "red"
g.AddNode(u)
v := g.NewNode().(*Node)
g.AddNode(v)
buf, err := dot.Marshal(g, "", "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
}
type Graph struct {
*simple.DirectedGraph
}
func NewGraph() *Graph {
return &Graph{DirectedGraph: simple.NewDirectedGraph()}
}
func (g *Graph) NewNode() graph.Node {
return &Node{
Node: g.DirectedGraph.NewNode(),
attrs: make(map[string]string),
}
}
// Node is a graph node with attributes.
type Node struct {
graph.Node
attrs map[string]string
}
// Attributes implements the encoding.Attributer interface.
func (n *Node) Attributes() []encoding.Attribute {
var keys []string
for key := range n.attrs {
keys = append(keys, key)
}
sort.Strings(keys)
var attrs []encoding.Attribute
for _, key := range keys {
attr := encoding.Attribute{Key: key, Value: n.attrs[key]}
attrs = append(attrs, attr)
}
return attrs
}
The following DOT file is produced.
strict digraph {
// Node definitions.
0 [
color=red
fillstyle=filled
label=hello world
];
1;
}
However, it contains a syntax error, as the value of the label attribute contains a space and is not quoted.
This gives the following error, using dot
to parse the file:
$ dot -Tpng -o a.png a.dot
Error: a.dot: syntax error in line 7 near ']'
How does Gonum not allow you to achieve your goal?
It works fine as it is, and users could simply use strconv.Quote
in their own code to handle escaping.
// Attributes implements the encoding.Attributer interface.
func (n *Node) Attributes() []encoding.Attribute {
var keys []string
for key := range n.attrs {
keys = append(keys, key)
}
sort.Strings(keys)
var attrs []encoding.Attribute
for _, key := range keys {
- attr := encoding.Attribute{Key: key, Value: n.attrs[key]}
+ attr := encoding.Attribute{Key: key, Value: strconv.Quote(n.attrs[key])}
attrs = append(attrs, attr)
}
return attrs
}
However, it may be a better idea to move this logic into the specific encoding package (e.g. encoding/dot), as further described below.
What version of Go and Gonum are you using?
u@x1 ~/g/s/g/v/gonum> go version go version devel +0d6a2d5f9a Fri Jan 4 02:40:56 2019 +0000 linux/amd64
Gonum rev 75c5f13f.
Is this feature absent from the current master?
Yes.
Are you able to help contribute the feature?
Sure, as long as we determine the right course of action.
Proposal
When marshaling attributes, encode attribute values if needed to conform to the output syntax. E.g. if the value contains a space, add double quotes.
This may be specific to the various output formats, so each output package (e.g. dot) would implement this in the context of that formats syntax and escaping rules.
The benefit of this approach, is that users don't have to be aware of the escaping rules of the underlying file formats, and they get to keep their data in a "clean" and unencoded representation.