-
Notifications
You must be signed in to change notification settings - Fork 0
/
tree2graph.py
179 lines (152 loc) · 5.01 KB
/
tree2graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""
tree2graph.py
Copyright (c) 2009, Robert Sesek <http://www.bluestatic.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""
Tree2Graph:
This program will create an OmniGraffle 5 Professional document given a
tree. The tree structure can have an arbitrary number of nodes and should be
in the format described below.
Usage:
python tree2graph.py < tree.txt > tree.graffle
./some-binary | python tree2graph.y > tree.graffle
"""
import plistlib
import sys
"""
FORMAT OF TREE INPUT:
The tree is unserialized from a new-line separated text file. Each line
corresponds to a node. The format of the line is as follows:
<number of children>:<node value>\n
The parser will look at <number of children> and will take that many lines
following the current one as the child values for the nodes. This should be
generated by a pre-order traversal of the tree. Pseudocode:
SerializeTree(TreeNode n) {
print n.children.Count()
print ':'
print n.value
print '\n'
foreach (TreeNode c in n.children) {
SerializeTree(c)
}
}
The result, on stdout, will be an OmniGraffle 5 Plist document.
"""
class TreeNode(object):
"""A tree node."""
def __init__(self, value):
super(TreeNode, self).__init__()
self.value = value
self.children = list()
self.num_children = 0
self.parent = None
def _ParseNode(string):
"""Parses a stringified node into a TreeNode object."""
last = len(string) - 1
if string[last] == '\n':
string = string[0:last]
pos = string.find(':')
node = TreeNode(string[pos+1:])
node.num_children = int(string[0:pos])
return node
def _HelpReadTree(parent, nlist):
"""Recursively joins children with their parents."""
if parent.num_children == 0:
return
for i in range(0, parent.num_children):
child = nlist.pop(0)
parent.children.append(child)
_HelpReadTree(child, nlist)
def ReadTree(flat_nodes):
"""Reads a list of flattened, serialized nodes and creates a tree with a
TreeNode root."""
nodes = map(_ParseNode, flat_nodes)
root = nodes[0]
_HelpReadTree(root, nodes[1:])
return root
def _CreateGraffleNode(node):
"""Creates a node for an OmniGraffle document."""
global gid
gid += 1
graf = dict()
graf['ID'] = gid
graf['Class'] = "ShapedGraphic"
graf['Shape'] = "Circle"
graf['Text'] = {"Text": "{\\rtf1\\ansi " + node.value + "}"}
graf['Bounds'] = "{{0,0},{50,50}}"
return graf
def _CreateGraffleLink(target, dest):
"""Creates a OmniGraffle link between to graffle nodes (created via
_CreateGraffleLink())."""
global gid
gid += 1
graf = dict()
graf['ID'] = gid
graf['Class'] = "LineGraphic"
graf['Head'] = {"ID": target['ID']}
graf['Tail'] = {"ID": dest['ID']}
graf['Style'] = {
"stroke": {
'HeadArrow' : "FilledArrow",
'LineType' : 1,
'TailArrow' : '0'
}
}
return graf
def _CreateGraffleFromNode(node, parent_graf, graphics):
"""Creates a OmniGraffle node from TreeNode |node|, and attaches it to the
parent Graffle node |parent_graph|."""
# Create a Graffle node for this node.
graf = _CreateGraffleNode(node)
graphics.append(graf)
# If we're not the root, then attach this new Graffle node to the parent.
if parent_graf != None:
graphics.append(_CreateGraffleLink(graf, parent_graf))
# Go over each child, repeating this process.
for child in node.children:
_CreateGraffleFromNode(child, graf, graphics)
def CreateGraffle(root):
"""Creates a dictionary, representing an OmniGraffle document."""
global gid
gid = 3
graphics = list()
_CreateGraffleFromNode(root, None, graphics)
doc = dict()
doc['AutoAdjust'] = True
doc['BackgroundGraphic'] = {
"Class" : "SolidGraphic",
"ID" : 2,
"Bounds" : "{{0,0},{1000,1000}}"
}
doc['GraphicsList'] = graphics
doc['CanvasOrigin'] = "{0,0}"
doc['GraphDocumentVersion'] = 6
doc['OutlineStyle'] = "Basic"
doc['PageBreaks'] = "NO"
doc['LayoutInfo'] = {
'Animate' : "NO",
'AutoLayout' : 2,
'LineLength' : 0.5,
'circoMinDist' : 15,
'circoSeparation' : 0,
'layoutEngine' : "dot"
}
return doc
def Main():
"""Reads from stdin the serialized tree structure and assembles a tree."""
nodes = sys.stdin.readlines()
root = ReadTree(nodes)
graffle = CreateGraffle(root)
plistlib.writePlist(graffle, sys.stdout)
if __name__ == '__main__':
Main()