Initial Import
git-svn-id: http://pydot.googlecode.com/svn/trunk@2 06aa9b79-7134-0410-ae7e-c1cd3e483e87
This commit is contained in:
parent
00b0eac19c
commit
ab4cd5ddf0
29
README
Normal file
29
README
Normal file
@ -0,0 +1,29 @@
|
||||
pydot - Python interface to Graphviz's Dot language
|
||||
Ero Carrera (c) 2004
|
||||
ero@dkbza.org
|
||||
|
||||
This code is distributed under the MIT license.
|
||||
|
||||
Requirements:
|
||||
-------------
|
||||
|
||||
pyparsing: pydot requires the pyparsing module in order to be
|
||||
able to load DOT files.
|
||||
|
||||
GraphViz: is needed in order to render the graphs into any of
|
||||
the plethora of output formats supported.
|
||||
|
||||
Installation:
|
||||
-------------
|
||||
|
||||
Should suffice with doing:
|
||||
|
||||
python setup.py install
|
||||
|
||||
Needless to say, no installation is needed just to use the module. A mere:
|
||||
|
||||
import pydot
|
||||
|
||||
should do it, provided that the directory containing the modules is on Python
|
||||
module search path.
|
||||
|
380
dot_parser.py
Normal file
380
dot_parser.py
Normal file
@ -0,0 +1,380 @@
|
||||
# -*- coding: Latin-1 -*-
|
||||
"""Graphviz's dot language parser.
|
||||
|
||||
The dotparser parses graphviz files in dot and dot files and transforms them
|
||||
into a class representation defined by pydot.
|
||||
|
||||
The module needs pyparsing (tested with version 1.2.2) and pydot (tested with 0.9.10)
|
||||
|
||||
Author: Michael Krause <michael@krause-software.de>
|
||||
"""
|
||||
|
||||
__author__ = 'Michael Krause'
|
||||
__license__ = 'MIT'
|
||||
|
||||
import sys
|
||||
import glob
|
||||
import pydot
|
||||
import re
|
||||
|
||||
from pyparsing import __version__ as pyparsing_version
|
||||
from pyparsing import Literal, CaselessLiteral, Word, \
|
||||
Upcase, OneOrMore, ZeroOrMore, Forward, NotAny, \
|
||||
delimitedList, oneOf, Group, Optional, Combine, \
|
||||
alphas, nums, restOfLine, cStyleComment, nums, \
|
||||
alphanums, printables, empty, quotedString, \
|
||||
ParseException, ParseResults, CharsNotIn, _noncomma,\
|
||||
dblQuotedString
|
||||
|
||||
|
||||
class P_AttrList:
|
||||
def __init__(self, toks):
|
||||
self.attrs = {}
|
||||
i = 0
|
||||
while i < len(toks):
|
||||
attrname = toks[i]
|
||||
attrvalue = toks[i+1]
|
||||
self.attrs[attrname] = attrvalue
|
||||
i += 2
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r)" % (self.__class__.__name__, self.attrs)
|
||||
|
||||
|
||||
class DefaultStatement(P_AttrList):
|
||||
def __init__(self, default_type, attrs):
|
||||
self.default_type = default_type
|
||||
self.attrs = attrs
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s, %r)" % \
|
||||
(self.__class__.__name__, self.default_type, self.attrs)
|
||||
|
||||
|
||||
def push_top_graph_stmt(str, loc, toks):
|
||||
attrs = {}
|
||||
g = None
|
||||
|
||||
for element in toks:
|
||||
if isinstance(element, ParseResults) or \
|
||||
isinstance(element, tuple) or \
|
||||
isinstance(element, list):
|
||||
|
||||
element = element[0]
|
||||
|
||||
if element == 'strict':
|
||||
attrs['strict'] = True
|
||||
elif element in ['graph', 'digraph']:
|
||||
attrs['graph_type'] = element
|
||||
elif type(element) == type(''):
|
||||
attrs['graph_name'] = element
|
||||
elif isinstance(element, pydot.Graph):
|
||||
g = pydot.Graph(**attrs)
|
||||
g.__dict__.update(element.__dict__)
|
||||
for e in g.get_edge_list():
|
||||
e.parent_graph = g
|
||||
for e in g.get_node_list():
|
||||
e.parent_graph = g
|
||||
for e in g.get_subgraph_list():
|
||||
e.set_graph_parent(g)
|
||||
|
||||
elif isinstance(element, P_AttrList):
|
||||
attrs.update(element.attrs)
|
||||
else:
|
||||
raise ValueError, "Unknown element statement: %r " % element
|
||||
|
||||
if g is not None:
|
||||
g.__dict__.update(attrs)
|
||||
return g
|
||||
|
||||
|
||||
def add_defaults(element, defaults):
|
||||
d = element.__dict__
|
||||
for key, value in defaults.items():
|
||||
if not d.get(key):
|
||||
d[key] = value
|
||||
|
||||
|
||||
def add_elements(g, toks, defaults_graph=None, defaults_node=None, defaults_edge=None):
|
||||
|
||||
if defaults_graph is None:
|
||||
defaults_graph = {}
|
||||
if defaults_node is None:
|
||||
defaults_node = {}
|
||||
if defaults_edge is None:
|
||||
defaults_edge = {}
|
||||
|
||||
for element in toks:
|
||||
if isinstance(element, pydot.Graph):
|
||||
add_defaults(element, defaults_graph)
|
||||
g.add_subgraph(element)
|
||||
elif isinstance(element, pydot.Node):
|
||||
add_defaults(element, defaults_node)
|
||||
g.add_node(element)
|
||||
elif isinstance(element, pydot.Edge):
|
||||
add_defaults(element, defaults_edge)
|
||||
g.add_edge(element)
|
||||
elif isinstance(element, ParseResults):
|
||||
for e in element:
|
||||
add_elements(g, [e], defaults_graph, defaults_node, defaults_edge)
|
||||
elif isinstance(element, DefaultStatement):
|
||||
if element.default_type == 'graph':
|
||||
default_graph_attrs = pydot.Node('graph')
|
||||
default_graph_attrs.__dict__.update(element.attrs)
|
||||
g.add_node(default_graph_attrs)
|
||||
defaults_graph.update(element.attrs)
|
||||
g.__dict__.update(element.attrs)
|
||||
elif element.default_type == 'node':
|
||||
default_node_attrs = pydot.Node('node')
|
||||
default_node_attrs.__dict__.update(element.attrs)
|
||||
g.add_node(default_node_attrs)
|
||||
# defaults_node.update(element.attrs)
|
||||
elif element.default_type == 'edge':
|
||||
default_edge_attrs = pydot.Node('edge')
|
||||
default_edge_attrs.__dict__.update(element.attrs)
|
||||
g.add_node(default_edge_attrs)
|
||||
defaults_edge.update(element.attrs)
|
||||
else:
|
||||
raise ValueError, "Unknown DefaultStatement: %s " % element.default_type
|
||||
elif isinstance(element, P_AttrList):
|
||||
g.__dict__.update(element.attrs)
|
||||
else:
|
||||
raise ValueError, "Unknown element statement: %r " % element
|
||||
|
||||
|
||||
def push_graph_stmt(str, loc, toks):
|
||||
g = pydot.Subgraph()
|
||||
add_elements(g, toks)
|
||||
return g
|
||||
|
||||
|
||||
def push_subgraph_stmt(str, loc, toks):
|
||||
|
||||
for e in toks:
|
||||
if len(e)==3:
|
||||
g = e[2]
|
||||
g.set_name(e[1])
|
||||
if len(e)==1:
|
||||
e[0].set_name('')
|
||||
return e[0]
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def push_default_stmt(str, loc, toks):
|
||||
# The pydot class instances should be marked as
|
||||
# default statements to be inherited by actual
|
||||
# graphs, nodes and edges.
|
||||
# print "push_default_stmt", toks
|
||||
default_type = toks[0][0]
|
||||
if len(toks) > 1:
|
||||
attrs = toks[1].attrs
|
||||
else:
|
||||
attrs = {}
|
||||
|
||||
if default_type in ['graph', 'node', 'edge']:
|
||||
return DefaultStatement(default_type, attrs)
|
||||
else:
|
||||
raise ValueError, "Unknown default statement: %r " % toks
|
||||
|
||||
|
||||
def push_attr_list(str, loc, toks):
|
||||
p = P_AttrList(toks)
|
||||
return p
|
||||
|
||||
|
||||
def get_port(node):
|
||||
|
||||
if len(node)>1:
|
||||
if isinstance(node[1], ParseResults):
|
||||
if len(node[1][0])==2:
|
||||
if node[1][0][0]==':':
|
||||
return node[1][0][1]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def do_node_ports(n_prev, n_next):
|
||||
port = get_port(n_prev)
|
||||
if port is not None:
|
||||
n_prev_port = ':'+port
|
||||
else:
|
||||
n_prev_port = ''
|
||||
|
||||
port = get_port(n_next)
|
||||
if port is not None:
|
||||
n_next_port = ':'+port
|
||||
else:
|
||||
n_next_port = ''
|
||||
|
||||
return (n_prev_port, n_next_port)
|
||||
|
||||
|
||||
def push_edge_stmt(str, loc, toks):
|
||||
|
||||
tok_attrs = [a for a in toks if isinstance(a, P_AttrList)]
|
||||
attrs = {}
|
||||
for a in tok_attrs:
|
||||
attrs.update(a.attrs)
|
||||
|
||||
e = []
|
||||
n_prev = toks[0]
|
||||
if isinstance(toks[2][0], pydot.Graph):
|
||||
n_next_list = [[n.get_name(),] for n in toks[2][0].get_node_list()]
|
||||
for n_next in [n for n in n_next_list]:
|
||||
n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
|
||||
e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
|
||||
else:
|
||||
for n_next in [n for n in tuple(toks)[2::2]]:
|
||||
n_prev_port, n_next_port = do_node_ports(n_prev, n_next)
|
||||
e.append(pydot.Edge(n_prev[0]+n_prev_port, n_next[0]+n_next_port, **attrs))
|
||||
n_prev = n_next
|
||||
|
||||
return e
|
||||
|
||||
|
||||
def push_node_stmt(str, loc, toks):
|
||||
|
||||
if len(toks) == 2:
|
||||
attrs = toks[1].attrs
|
||||
else:
|
||||
attrs = {}
|
||||
|
||||
node_name = toks[0]
|
||||
if isinstance(node_name, list) or isinstance(node_name, tuple):
|
||||
if len(node_name)>0:
|
||||
node_name = node_name[0]
|
||||
|
||||
n = pydot.Node('"'+node_name+'"', **attrs)
|
||||
return n
|
||||
|
||||
|
||||
def strip_quotes( s, l, t ):
|
||||
return [ t[0].strip('"') ]
|
||||
|
||||
|
||||
graphparser = None
|
||||
def graph_definition():
|
||||
global graphparser
|
||||
|
||||
if not graphparser:
|
||||
# punctuation
|
||||
colon = Literal(":")
|
||||
lbrace = Literal("{")
|
||||
rbrace = Literal("}")
|
||||
lbrack = Literal("[")
|
||||
rbrack = Literal("]")
|
||||
lparen = Literal("(")
|
||||
rparen = Literal(")")
|
||||
equals = Literal("=")
|
||||
comma = Literal(",")
|
||||
dot = Literal(".")
|
||||
slash = Literal("/")
|
||||
bslash = Literal("\\")
|
||||
star = Literal("*")
|
||||
semi = Literal(";")
|
||||
at = Literal("@")
|
||||
minus = Literal("-")
|
||||
|
||||
# keywords
|
||||
strict_ = Literal("strict")
|
||||
graph_ = Literal("graph")
|
||||
digraph_ = Literal("digraph")
|
||||
subgraph_ = Literal("subgraph")
|
||||
node_ = Literal("node")
|
||||
edge_ = Literal("edge")
|
||||
|
||||
|
||||
# token definitions
|
||||
|
||||
identifier = Word(alphanums + "_" ).setName("identifier")
|
||||
|
||||
double_quoted_string = dblQuotedString
|
||||
|
||||
alphastring_ = OneOrMore(CharsNotIn(_noncomma))
|
||||
|
||||
ID = (identifier | double_quoted_string.setParseAction(strip_quotes) |\
|
||||
alphastring_).setName("ID")
|
||||
|
||||
html_text = Combine(Literal("<<") + OneOrMore(CharsNotIn(",]")))
|
||||
|
||||
float_number = Combine(Optional(minus) + \
|
||||
OneOrMore(Word(nums + "."))).setName("float_number")
|
||||
|
||||
righthand_id = (float_number | ID | html_text).setName("righthand_id")
|
||||
|
||||
port_angle = (at + ID).setName("port_angle")
|
||||
|
||||
port_location = (Group(colon + ID) | \
|
||||
Group(colon + lparen + ID + comma + ID + rparen)).setName("port_location")
|
||||
|
||||
port = (Group(port_location + Optional(port_angle)) | \
|
||||
Group(port_angle + Optional(port_location))).setName("port")
|
||||
|
||||
node_id = (ID + Optional(port))
|
||||
a_list = OneOrMore(ID + Optional(equals.suppress() + righthand_id) + \
|
||||
Optional(comma.suppress())).setName("a_list")
|
||||
|
||||
attr_list = OneOrMore(lbrack.suppress() + Optional(a_list) + \
|
||||
rbrack.suppress()).setName("attr_list")
|
||||
|
||||
attr_stmt = (Group(graph_ | node_ | edge_) + attr_list).setName("attr_stmt")
|
||||
|
||||
edgeop = (Literal("--") | Literal("->")).setName("edgeop")
|
||||
|
||||
stmt_list = Forward()
|
||||
graph_stmt = Group(lbrace.suppress() + Optional(stmt_list) + \
|
||||
rbrace.suppress()).setName("graph_stmt")
|
||||
|
||||
subgraph = (Group(Optional(subgraph_ + Optional(ID)) + graph_stmt) | \
|
||||
Group(subgraph_ + ID)).setName("subgraph")
|
||||
|
||||
edgeRHS = OneOrMore(edgeop + Group(node_id | subgraph))
|
||||
|
||||
edge_stmt = Group(node_id | subgraph) + edgeRHS + Optional(attr_list)
|
||||
|
||||
node_stmt = (node_id + Optional(attr_list) + Optional(semi.suppress())).setName("node_stmt")
|
||||
|
||||
assignment = (ID + equals.suppress() + righthand_id).setName("assignment")
|
||||
stmt = (assignment | edge_stmt | attr_stmt | subgraph | node_stmt).setName("stmt")
|
||||
stmt_list << OneOrMore(stmt + Optional(semi.suppress()))
|
||||
|
||||
graphparser = (Optional(strict_) + Group((graph_ | digraph_)) + \
|
||||
Optional(ID) + graph_stmt).setResultsName("graph")
|
||||
|
||||
singleLineComment = "//" + restOfLine
|
||||
|
||||
|
||||
# actions
|
||||
|
||||
graphparser.ignore(singleLineComment)
|
||||
graphparser.ignore(cStyleComment)
|
||||
|
||||
assignment.setParseAction(push_attr_list)
|
||||
a_list.setParseAction(push_attr_list)
|
||||
edge_stmt.setParseAction(push_edge_stmt)
|
||||
node_stmt.setParseAction(push_node_stmt)
|
||||
attr_stmt.setParseAction(push_default_stmt)
|
||||
|
||||
subgraph.setParseAction(push_subgraph_stmt)
|
||||
graph_stmt.setParseAction(push_graph_stmt)
|
||||
graphparser.setParseAction(push_top_graph_stmt)
|
||||
|
||||
|
||||
return graphparser
|
||||
|
||||
|
||||
def parse_dot_data(data):
|
||||
try:
|
||||
data = data.replace('\\\n', '')
|
||||
graphparser = graph_definition()
|
||||
if pyparsing_version >= '1.2':
|
||||
graphparser.parseWithTabs()
|
||||
tokens = graphparser.parseString(data)
|
||||
graph = tokens.graph
|
||||
return graph
|
||||
except ParseException, err:
|
||||
print err.line
|
||||
print " "*(err.column-1) + "^"
|
||||
print err
|
||||
return None
|
23
setup.py
Normal file
23
setup.py
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
import pydot
|
||||
|
||||
setup( name = 'pydot',
|
||||
version = pydot.__version__,
|
||||
description = 'Python interface to Graphiz\'s Dot',
|
||||
author = 'Ero Carrera',
|
||||
author_email = 'ero@dkbza.org',
|
||||
url = 'http://dkbza.org/pydot.html',
|
||||
license = 'MIT',
|
||||
platforms = ["any"],
|
||||
classifiers = ['Development Status :: 5 - Production/Stable', \
|
||||
'Intended Audience :: Science/Research', \
|
||||
'License :: OSI Approved :: MIT License',\
|
||||
'Natural Language :: English', \
|
||||
'Operating System :: OS Independent', \
|
||||
'Programming Language :: Python', \
|
||||
'Topic :: Scientific/Engineering :: Visualization',\
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'],
|
||||
long_description = "\n".join(pydot.__doc__.split('\n')),
|
||||
py_modules = ['pydot', 'dot_parser'])
|
Loading…
Reference in New Issue
Block a user