如下是DOT语言的简明语法定义。
终端以粗体显示,非终端以斜体显示。文本字符使用单引号包含。括号(和)指明一些必要的组合。方括号[和]表示一些可选项。竖线|把一些备选方法分割开。
graph : [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}'
stmt_list : [ stmt [ ';' ] stmt_list ]
stmt : node_stmt
| edge_stmt
| attr_stmt
| ID '=' ID
| subgraph
attr_stmt : (graph | node | edge) attr_list
attr_list : '[' [ a_list ] ']' [ attr_list ]
a_list : ID '=' ID [ (';' | ',') ] [ a_list ]
edge_stmt : (node_id | subgraph) edgeRHS [ attr_list ]
edgeRHS : edgeop (node_id | subgraph) [ edgeRHS ]
node_stmt : node_id [ attr_list ]
node_id : ID [ port ]
port : ':' ID [ ':' compass_pt ]
| ':' compass_pt
subgraph : [ subgraph [ ID ] ] '{' stmt_list '}'
compass_pt : (n | ne | e | se | s | sw | w | nw | c | _)
需要注意的是,关键字node, edge, graph, digraph, subgraph和strict是忽略大小写的。另外,那些给出的指示符值并不是关键字,因此这些字符串在别处可以作为普通标识符来使用。反过来说,解析器会接受任何标识符。
ID可以是如下任何一种类型:
- 任意字符类型的字母 ([a-zA-Z\200-\377]) , 下划线 ('_') 数字 ([0-9]), 不能以数字开头;
- 一个数字 [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? );
- 双引号 ("...")包裹的字符串可能包含独立的转义引号 (")1;
- HTML 字符串 (<...>).
ID就是一个字符串。前面的两个例子上没有加引号只是为了简单起见。事实上, abc_2 和 "abc_2", 或者 2.34 和 "2.34"这两种表达方式并没有什么区别。
显而易见,如果我们要把一个关键词作为ID,那么就要加上引号。需要注意的是,在HTML语言中,尖括号必须成对的出现,并且换行符和其他标准空字符是可以使用呢的。另外,HTML语言的内容必须符合标准的XML, 因此在原文中使用XML中的特殊字符时需要进行转义,比如 ", &, <, 和 > 。
带引号的内容和HTML代码都会被作为一个单元识别出来。所以被识别在这个单元内部的任何内容都会被认为是他们的一部分。
edgeop(边操作)使用 -> 表明有向图边 , -- 表明无向图边。
我们支持C++的注释风格:使用 /* */ 和 //. 同时,一行以#号开头的句子也会被认为是C预处理器的输出内容而不被识别。 (我理解是#也是注释)
分号和逗号有助于提高可读性,但不是必需的。此外,可以在终端之间插入任何的空白字符。
另一个为了可读性方便的案例是,DOT允许在双引号包裹的字符串内跨越多个物理行,只要你紧接在换行符前面使用标准C约定的反斜杠作为识别。
另外,双引号包裹的字符串可以使用‘+’来进行连接。由于HTML字符串允许内部有换行符,因此该语言不允许使用转移换行符和连接运算符。
子图和集合
在Graphviz中,子图有三个作用。子图可以用来表示图结构,标示某些点和边可以组合在一起。这是子图常用的方法,指明图组件的语义信息。其次,它还可以为边提供一个简便的缩写。在边的状态中,子图可以同时在边操作符的左边和右边。当这种状态的时候,会为左边的每个节点和右边的每个节点之间都创建一条表。例如:
A -> {B C}
相当于
A -> B
A -> C
第二点,子图可以为设置属性提供一些上下文。比如说,子图可以设定蓝色作为所有节点产生时的默认颜色。这里有一个更有意思的栗子:
subgraph {
rank = same; A; B; C;
}
这个子图定义了ABC三个节点置于同一个等级。
子图的第三个作用是在使用布局引擎对图像进行布局时进行干预。如果子图的名字被定义为cluster,那么Graphviz会识别到这是一个特殊的cluster子图。在使用这个方法时,布局引擎会把这个cluster下面的所有节点画在一起,并用一个矩形框把它们括在一起。不过需要注意的是,cluster子图并不是一个标准的DOT语句,它只是为了适配部分布局引擎的特殊需求而单独约定的。
语法和语义要点
在该语言中,一个图要么被指定为一个有向图,要么被指定为一个无向图。在语义上来说,有向图需要在每一条边上都设定一个方向或者两个方向,而无向图的边则设定为没有方向。在语法上来说,一个有向图的边要用->,一个无向图的边要用--来表述。有向图和无向图需要在定义图形的时候就指定,作为一个默认属性。在有向图中,一条线默认会有一个箭头指向头部节点,在无向图中则不会有任何箭头。
图也可以被定义为严谨图,禁止创建多边对象,这意味着在任何两点之间只能有一条边。对于无向图,两个节点之间只能有一条线连接。在紧接着的边声明中,可以使用这两个事先声明的点来唯一标识这一条边,并且可以使用边定义中的任何属性。例如下面这样
strict graph {
a -- b
a -- b
b -- a [color=blue]
}
通过上述定义,我们就可以在点a和b之间创建一条蓝色的边。
如果使用节点,边或图形语句或未附加到节点或边的属性赋值定义默认属性,则之后定义的任何相应类型的对象将继承此属性值。
这种状态将一直维持下去,除非这个值被赋予了一个新的值,那么从这个新的节点开始,后面都会使用新的值。已经定义但没有赋值的对象会有一个空字符串值。
值得注意的是,一个子图在被定义的时候就会继承父节点的属性。这个功能很有用,比如你在根图就制定了一个字体,在整个图上的子图都会使用这个字体。但是对于某些属性,是不适用这个功能的。比如在根图上加了一个标签,大概率上来说这个标签都不需要加在所有的子图上。所以不必在图的顶点就列出所有属性,然后在子图上加以修改。可以简单的进行处理,那就是当你需要的时候再加上这个属性。
如果一条边属于一个集合,那么它的端点也属于这个集合。因此,边的放置会影响布局,因为集合有时候是递归布局的。
子图和集合需要一些限制。首先,一个图和所有子图共享一个命名空间,所以所有的子图都需要有一个唯一的命名。其次,尽管节点可以属于任何子图,但是当节点是某个集合的边或节点的子集的时候,该集合应该是有严格的等级的。
字符编码
DOT语言支持ASCII字符编码字符集。在引用的字符中,普通引用和类HTML引用可能包含非ASCII编码字符。在大多数情况下,这些字符都不会被解析。它们会被当做标识符或者字符值来进行传递。其中有一类比较特殊的,即标签(Labels),需要用来在前端展示,需要在软件需求的长度范围内,并使用合适的字体。因此,对于这部分需要知道使用了什么字符编码格式。
DOT语言默认是UTF-8编码。也可以使用Latin1 (ISO-8859-1)编码,但是需要使用 charset
来指明。对于其他的字符编码比如iconv,需要转换成上面的两种。
在labels中避免非法字符的另外一种方法是在使用特殊字符时包进HTML实体中。在识别标签的时候,这些实体被转换成底层标签。table这个表里记录了所有支持的实体,连带它们的Unicode值,标准字形和HTML实体名。因此,要将小写的希腊语beta包含在字符串中,可以使用ascii编码&beta;。通常,应该只使用输出字符集中允许的实体,并且字体中只有一个字形。
- 在DOT中的引用字符串中,唯一的转义字符是双引号(")。也就是说,在引用的字符串中,dyad \“被转换为”;所有其他字符保持不变。尤其是\仍然\。布局引擎可以应用其他转义序列。
- 在2.30版本之前,该语言允许在HTML字符串之外的任何地方使用转义换行符。新的语义识别器很难实现这个功能,同时因为这个功能的用处也不是很大,我们将此功能限制为仅限于使用双引号的字符串,这样在需要的时候也可以发挥一点作用。