usfmtc 0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usfmtc/__init__.py +379 -0
- usfmtc/diagrams.py +247 -0
- usfmtc/extension.py +79 -0
- usfmtc/grammar.py +461 -0
- usfmtc/parser.py +300 -0
- usfmtc/railroad.py +1320 -0
- usfmtc/reference.py +302 -0
- usfmtc/rnc2rng/__init__.py +13 -0
- usfmtc/rnc2rng/parser.py +741 -0
- usfmtc/rnc2rng/rnctree.py +17 -0
- usfmtc/rnc2rng/serializer.py +255 -0
- usfmtc/sfmparser.py +345 -0
- usfmtc/usfmgenerate.py +156 -0
- usfmtc/usfmparser.py +590 -0
- usfmtc/usjproc.py +88 -0
- usfmtc/usx.rng +2182 -0
- usfmtc/usxgrammar.py +36 -0
- usfmtc/usxmodel.py +520 -0
- usfmtc/usxparser.py +568 -0
- usfmtc/xmlutils.py +139 -0
- usfmtc-0.2.dist-info/LICENSE.md +20 -0
- usfmtc-0.2.dist-info/METADATA +61 -0
- usfmtc-0.2.dist-info/RECORD +26 -0
- usfmtc-0.2.dist-info/WHEEL +5 -0
- usfmtc-0.2.dist-info/entry_points.txt +2 -0
- usfmtc-0.2.dist-info/top_level.txt +1 -0
usfmtc/railroad.py
ADDED
|
@@ -0,0 +1,1320 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import division, unicode_literals
|
|
3
|
+
import sys
|
|
4
|
+
import math as Math
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, ):
|
|
7
|
+
unicode = str
|
|
8
|
+
|
|
9
|
+
# Display constants
|
|
10
|
+
DEBUG = False # if true, writes some debug information into attributes
|
|
11
|
+
VS = 8 # minimum vertical separation between things. For a 3px stroke, must be at least 4
|
|
12
|
+
AR = 10 # radius of arcs
|
|
13
|
+
DIAGRAM_CLASS = 'railroad-diagram' # class to put on the root <svg>
|
|
14
|
+
STROKE_ODD_PIXEL_LENGTH = True # is the stroke width an odd (1px, 3px, etc) pixel length?
|
|
15
|
+
INTERNAL_ALIGNMENT = 'center' # how to align items when they have extra space. left/right/center
|
|
16
|
+
CHAR_WIDTH = 8.5 # width of each monospace character. play until you find the right value for your font
|
|
17
|
+
COMMENT_CHAR_WIDTH = 7 # comments are in smaller text by default
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def e(text):
|
|
21
|
+
import re
|
|
22
|
+
return re.sub(r"[*_\`\[\]<&]", lambda c: "&#{0};".format(ord(c.group(0))), unicode(text))
|
|
23
|
+
|
|
24
|
+
def determineGaps(outer, inner):
|
|
25
|
+
diff = outer - inner
|
|
26
|
+
if diff < 0:
|
|
27
|
+
diff = 0
|
|
28
|
+
if INTERNAL_ALIGNMENT == 'left':
|
|
29
|
+
return 0, diff
|
|
30
|
+
elif INTERNAL_ALIGNMENT == 'right':
|
|
31
|
+
return diff, 0
|
|
32
|
+
else:
|
|
33
|
+
return diff/2, diff/2
|
|
34
|
+
|
|
35
|
+
def doubleenumerate(seq):
|
|
36
|
+
length = len(list(seq))
|
|
37
|
+
for i,item in enumerate(seq):
|
|
38
|
+
yield i, i-length, item
|
|
39
|
+
|
|
40
|
+
def addDebug(el):
|
|
41
|
+
if not DEBUG:
|
|
42
|
+
return
|
|
43
|
+
el.attrs['data-x'] = "{0} w:{1} h:{2}/{3}/{4}".format(type(el).__name__, el.width, el.up, el.height, el.down)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DiagramItem(object):
|
|
48
|
+
def __init__(self, name, attrs=None, text=None):
|
|
49
|
+
self.name = name
|
|
50
|
+
# up = distance it projects above the entry line
|
|
51
|
+
# height = distance between the entry/exit lines
|
|
52
|
+
# down = distance it projects below the exit line
|
|
53
|
+
self.height = 0
|
|
54
|
+
self.attrs = attrs or {}
|
|
55
|
+
self.children = [text] if text else []
|
|
56
|
+
self.needsSpace = False
|
|
57
|
+
|
|
58
|
+
def format(self, x, y, width):
|
|
59
|
+
raise NotImplementedError # Virtual
|
|
60
|
+
|
|
61
|
+
def addTo(self, parent):
|
|
62
|
+
parent.children.append(self)
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def writeSvg(self, write):
|
|
66
|
+
write(u'<{0}'.format(self.name))
|
|
67
|
+
for name, value in sorted(self.attrs.items()):
|
|
68
|
+
write(u' {0}="{1}"'.format(name, e(value)))
|
|
69
|
+
write(u'>')
|
|
70
|
+
if self.name in ["g", "svg"]:
|
|
71
|
+
write(u'\n')
|
|
72
|
+
for child in self.children:
|
|
73
|
+
if isinstance(child, DiagramItem):
|
|
74
|
+
child.writeSvg(write)
|
|
75
|
+
else:
|
|
76
|
+
write(e(child))
|
|
77
|
+
write(u'</{0}>'.format(self.name))
|
|
78
|
+
|
|
79
|
+
def walk(self, cb):
|
|
80
|
+
cb(self)
|
|
81
|
+
|
|
82
|
+
def __eq__(self, other):
|
|
83
|
+
return type(self) == type(other) and self.__dict__ == other.__dict__
|
|
84
|
+
|
|
85
|
+
def __ne__(self, other):
|
|
86
|
+
return not (self == other)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class DiagramMultiContainer(DiagramItem):
|
|
90
|
+
def __init__(self, name, items, attrs=None, text=None, **kw):
|
|
91
|
+
DiagramItem.__init__(self, name, attrs, text)
|
|
92
|
+
self.items = [wrapString(item) for item in items if item is not None]
|
|
93
|
+
for k, v in kw.items():
|
|
94
|
+
setattr(self, k, v)
|
|
95
|
+
|
|
96
|
+
def append(self, item):
|
|
97
|
+
self.items.append(item)
|
|
98
|
+
|
|
99
|
+
def walk(self, cb):
|
|
100
|
+
cb(self)
|
|
101
|
+
for item in self.items:
|
|
102
|
+
item.walk(cb)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Path(DiagramItem):
|
|
106
|
+
def __init__(self, x, y):
|
|
107
|
+
self.x = x
|
|
108
|
+
self.y = y
|
|
109
|
+
DiagramItem.__init__(self, 'path', {'d': 'M %s %s' % (x, y)})
|
|
110
|
+
|
|
111
|
+
def m(self, x, y):
|
|
112
|
+
self.attrs['d'] += ' m {0} {1}'.format(x,y)
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def l(self, x, y):
|
|
116
|
+
self.attrs['d'] += ' l {0} {1}'.format(x,y)
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def h(self, val):
|
|
120
|
+
self.attrs['d'] += ' h {0}'.format(val)
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
def right(self, val):
|
|
124
|
+
return self.h(max(0, val))
|
|
125
|
+
|
|
126
|
+
def left(self, val):
|
|
127
|
+
return self.h(-max(0, val))
|
|
128
|
+
|
|
129
|
+
def v(self, val):
|
|
130
|
+
self.attrs['d'] += ' v {0}'.format(val)
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
def down(self, val):
|
|
134
|
+
return self.v(max(0, val))
|
|
135
|
+
|
|
136
|
+
def up(self, val):
|
|
137
|
+
return self.v(-max(0, val))
|
|
138
|
+
|
|
139
|
+
def arc_8(self, start, dir):
|
|
140
|
+
# 1/8 of a circle
|
|
141
|
+
arc = AR
|
|
142
|
+
s2 = 1/Math.sqrt(2) * arc
|
|
143
|
+
s2inv = (arc - s2)
|
|
144
|
+
path = " a {0} {0} 0 0 {1} ".format(arc, "1" if dir == 'cw' else "0")
|
|
145
|
+
sd = start+dir
|
|
146
|
+
if sd == 'ncw':
|
|
147
|
+
offset = [s2, s2inv]
|
|
148
|
+
elif sd == 'necw':
|
|
149
|
+
offset = [s2inv, s2]
|
|
150
|
+
elif sd == 'ecw':
|
|
151
|
+
offset = [-s2inv, s2]
|
|
152
|
+
elif sd == 'secw':
|
|
153
|
+
offset = [-s2, s2inv]
|
|
154
|
+
elif sd == 'scw':
|
|
155
|
+
offset = [-s2, -s2inv]
|
|
156
|
+
elif sd == 'swcw':
|
|
157
|
+
offset = [-s2inv, -s2]
|
|
158
|
+
elif sd == 'wcw':
|
|
159
|
+
offset = [s2inv, -s2]
|
|
160
|
+
elif sd == 'nwcw':
|
|
161
|
+
offset = [s2, -s2inv]
|
|
162
|
+
elif sd == 'nccw':
|
|
163
|
+
offset = [-s2, s2inv]
|
|
164
|
+
elif sd == 'nwccw':
|
|
165
|
+
offset = [-s2inv, s2]
|
|
166
|
+
elif sd == 'wccw':
|
|
167
|
+
offset = [s2inv, s2]
|
|
168
|
+
elif sd == 'swccw':
|
|
169
|
+
offset = [s2, s2inv]
|
|
170
|
+
elif sd == 'sccw':
|
|
171
|
+
offset = [s2, -s2inv]
|
|
172
|
+
elif sd == 'seccw':
|
|
173
|
+
offset = [s2inv, -s2]
|
|
174
|
+
elif sd == 'eccw':
|
|
175
|
+
offset = [-s2inv, -s2]
|
|
176
|
+
elif sd == 'neccw':
|
|
177
|
+
offset = [-s2, -s2inv]
|
|
178
|
+
|
|
179
|
+
path += " ".join(str(x) for x in offset)
|
|
180
|
+
self.attrs['d'] += path
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def arc(self, sweep):
|
|
184
|
+
x = AR
|
|
185
|
+
y = AR
|
|
186
|
+
if sweep[0] == 'e' or sweep[1] == 'w':
|
|
187
|
+
x *= -1
|
|
188
|
+
if sweep[0] == 's' or sweep[1] == 'n':
|
|
189
|
+
y *= -1
|
|
190
|
+
cw = 1 if sweep == 'ne' or sweep == 'es' or sweep == 'sw' or sweep == 'wn' else 0
|
|
191
|
+
self.attrs['d'] += ' a {0} {0} 0 0 {1} {2} {3}'.format(AR, cw, x, y)
|
|
192
|
+
#self.attrs['d'] += ' l {0} {1}'.format(x, y)
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def format(self):
|
|
197
|
+
self.attrs['d'] += ' h .5'
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def __repr__(self):
|
|
201
|
+
return 'Path(%r, %r)' % (self.x, self.y)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def wrapString(value):
|
|
205
|
+
return value if isinstance(value, DiagramItem) else Terminal(value)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
DEFAULT_STYLE = '''\
|
|
209
|
+
svg.railroad-diagram {
|
|
210
|
+
background-color:hsl(30,20%,95%);
|
|
211
|
+
}
|
|
212
|
+
svg.railroad-diagram path {
|
|
213
|
+
stroke-width:3;
|
|
214
|
+
stroke:black;
|
|
215
|
+
fill:rgba(0,0,0,0);
|
|
216
|
+
}
|
|
217
|
+
svg.railroad-diagram text {
|
|
218
|
+
font:bold 14px monospace;
|
|
219
|
+
text-anchor:middle;
|
|
220
|
+
}
|
|
221
|
+
svg.railroad-diagram text.label{
|
|
222
|
+
text-anchor:start;
|
|
223
|
+
}
|
|
224
|
+
svg.railroad-diagram text.comment{
|
|
225
|
+
font:italic 12px monospace;
|
|
226
|
+
}
|
|
227
|
+
svg.railroad-diagram rect{
|
|
228
|
+
stroke-width:3;
|
|
229
|
+
stroke:black;
|
|
230
|
+
fill:hsl(120,100%,90%);
|
|
231
|
+
}
|
|
232
|
+
svg.railroad-diagram rect.group-box {
|
|
233
|
+
stroke: gray;
|
|
234
|
+
stroke-dasharray: 10 5;
|
|
235
|
+
fill: none;
|
|
236
|
+
}
|
|
237
|
+
'''
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class Style(DiagramItem):
|
|
241
|
+
def __init__(self, css):
|
|
242
|
+
self.name = 'style'
|
|
243
|
+
self.css = css
|
|
244
|
+
self.height = 0
|
|
245
|
+
self.width = 0
|
|
246
|
+
self.needsSpace = False
|
|
247
|
+
|
|
248
|
+
def __repr__(self):
|
|
249
|
+
return 'Style(%r)' % self.css
|
|
250
|
+
|
|
251
|
+
def format(self, x, y, width):
|
|
252
|
+
return self
|
|
253
|
+
|
|
254
|
+
def writeSvg(self, write):
|
|
255
|
+
# Write included stylesheet as CDATA. See https:#developer.mozilla.org/en-US/docs/Web/SVG/Element/style
|
|
256
|
+
cdata = u'/* <![CDATA[ */\n{css}\n/* ]]> */\n'.format(css=self.css)
|
|
257
|
+
write(u'<style>{cdata}</style>'.format(cdata=cdata))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class Diagram(DiagramMultiContainer):
|
|
261
|
+
def __init__(self, *items, **kwargs):
|
|
262
|
+
# Accepts a type=[simple|complex] kwarg
|
|
263
|
+
DiagramMultiContainer.__init__(
|
|
264
|
+
self,
|
|
265
|
+
'svg',
|
|
266
|
+
items,
|
|
267
|
+
{'class': DIAGRAM_CLASS, 'xmlns': "http://www.w3.org/2000/svg",
|
|
268
|
+
'xmlns:xlink': "http://www.w3.org/1999/xlink"}
|
|
269
|
+
)
|
|
270
|
+
self.type = kwargs.get("type", "simple")
|
|
271
|
+
if items and not isinstance(items[0], Start):
|
|
272
|
+
self.items.insert(0, Start(self.type))
|
|
273
|
+
if items and not isinstance(items[-1], End):
|
|
274
|
+
self.items.append(End(self.type))
|
|
275
|
+
self.css = kwargs.get("css", DEFAULT_STYLE)
|
|
276
|
+
if self.css:
|
|
277
|
+
self.items.insert(0, Style(self.css))
|
|
278
|
+
self.up = 0
|
|
279
|
+
self.down = 0
|
|
280
|
+
self.height = 0
|
|
281
|
+
self.width = 0
|
|
282
|
+
for item in self.items:
|
|
283
|
+
if isinstance(item, Style):
|
|
284
|
+
continue
|
|
285
|
+
self.width += item.width + (20 if item.needsSpace else 0)
|
|
286
|
+
self.up = max(self.up, item.up - self.height)
|
|
287
|
+
self.height += item.height
|
|
288
|
+
self.down = max(self.down - item.height, item.down)
|
|
289
|
+
if self.items[0].needsSpace:
|
|
290
|
+
self.width -= 10
|
|
291
|
+
if self.items[-1].needsSpace:
|
|
292
|
+
self.width -= 10
|
|
293
|
+
self.formatted = False
|
|
294
|
+
|
|
295
|
+
def __repr__(self):
|
|
296
|
+
if self.css:
|
|
297
|
+
items = ', '.join(map(repr, self.items[2:-1]))
|
|
298
|
+
else:
|
|
299
|
+
items = ', '.join(map(repr, self.items[1:-1]))
|
|
300
|
+
pieces = [] if not items else [items]
|
|
301
|
+
if self.css != DEFAULT_STYLE:
|
|
302
|
+
pieces.append('css=%r' % self.css)
|
|
303
|
+
if self.type != 'simple':
|
|
304
|
+
pieces.append('type=%r' % self.type)
|
|
305
|
+
return 'Diagram(%s)' % ', '.join(pieces)
|
|
306
|
+
|
|
307
|
+
def format(self, paddingTop=20, paddingRight=None, paddingBottom=None, paddingLeft=None):
|
|
308
|
+
if paddingRight is None:
|
|
309
|
+
paddingRight = paddingTop
|
|
310
|
+
if paddingBottom is None:
|
|
311
|
+
paddingBottom = paddingTop
|
|
312
|
+
if paddingLeft is None:
|
|
313
|
+
paddingLeft = paddingRight
|
|
314
|
+
x = paddingLeft
|
|
315
|
+
y = paddingTop + self.up
|
|
316
|
+
g = DiagramItem('g')
|
|
317
|
+
if STROKE_ODD_PIXEL_LENGTH:
|
|
318
|
+
g.attrs['transform'] = 'translate(.5 .5)'
|
|
319
|
+
for item in self.items:
|
|
320
|
+
if item.needsSpace:
|
|
321
|
+
Path(x, y).h(10).addTo(g)
|
|
322
|
+
x += 10
|
|
323
|
+
item.format(x, y, item.width).addTo(g)
|
|
324
|
+
x += item.width
|
|
325
|
+
y += item.height
|
|
326
|
+
if item.needsSpace:
|
|
327
|
+
Path(x, y).h(10).addTo(g)
|
|
328
|
+
x += 10
|
|
329
|
+
self.attrs['width'] = self.width + paddingLeft + paddingRight
|
|
330
|
+
self.attrs['height'] = self.up + self.height + self.down + paddingTop + paddingBottom
|
|
331
|
+
self.attrs['viewBox'] = "0 0 {width} {height}".format(**self.attrs)
|
|
332
|
+
g.addTo(self)
|
|
333
|
+
self.formatted = True
|
|
334
|
+
return self
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def writeSvg(self, write):
|
|
338
|
+
if not self.formatted:
|
|
339
|
+
self.format()
|
|
340
|
+
return DiagramItem.writeSvg(self, write)
|
|
341
|
+
|
|
342
|
+
def parseCSSGrammar(self, text):
|
|
343
|
+
token_patterns = {
|
|
344
|
+
'keyword': r"[\w-]+\(?",
|
|
345
|
+
'type': r"<[\w-]+(\(\))?>",
|
|
346
|
+
'char': r"[/,()]",
|
|
347
|
+
'literal': r"'(.)'",
|
|
348
|
+
'openbracket': r"\[",
|
|
349
|
+
'closebracket': r"\]",
|
|
350
|
+
'closebracketbang': r"\]!",
|
|
351
|
+
'bar': r"\|",
|
|
352
|
+
'doublebar': r"\|\|",
|
|
353
|
+
'doubleand': r"&&",
|
|
354
|
+
'multstar': r"\*",
|
|
355
|
+
'multplus': r"\+",
|
|
356
|
+
'multhash': r"#",
|
|
357
|
+
'multnum1': r"{\s*(\d+)\s*}",
|
|
358
|
+
'multnum2': r"{\s*(\d+)\s*,\s*(\d*)\s*}",
|
|
359
|
+
'multhashnum1': r"#{\s*(\d+)\s*}",
|
|
360
|
+
'multhashnum2': r"{\s*(\d+)\s*,\s*(\d*)\s*}"
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class Sequence(DiagramMultiContainer):
|
|
365
|
+
def __init__(self, *items, **kw):
|
|
366
|
+
DiagramMultiContainer.__init__(self, 'g', items, **kw)
|
|
367
|
+
self.needsSpace = True
|
|
368
|
+
self.up = 0
|
|
369
|
+
self.down = 0
|
|
370
|
+
self.height = 0
|
|
371
|
+
self.width = 0
|
|
372
|
+
for item in self.items:
|
|
373
|
+
self.width += item.width + (20 if item.needsSpace else 0)
|
|
374
|
+
self.up = max(self.up, item.up - self.height)
|
|
375
|
+
self.height += item.height
|
|
376
|
+
self.down = max(self.down - item.height, item.down)
|
|
377
|
+
if self.items[0].needsSpace:
|
|
378
|
+
self.width -= 10
|
|
379
|
+
if self.items[-1].needsSpace:
|
|
380
|
+
self.width -= 10
|
|
381
|
+
addDebug(self)
|
|
382
|
+
|
|
383
|
+
def __repr__(self):
|
|
384
|
+
items = ', '.join(map(repr, self.items))
|
|
385
|
+
return 'Sequence(%s)' % items
|
|
386
|
+
|
|
387
|
+
def format(self, x, y, width):
|
|
388
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
389
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
390
|
+
Path(x+leftGap+self.width, y+self.height).h(rightGap).addTo(self)
|
|
391
|
+
x += leftGap
|
|
392
|
+
for i,item in enumerate(self.items):
|
|
393
|
+
if item.needsSpace and i > 0:
|
|
394
|
+
Path(x, y).h(10).addTo(self)
|
|
395
|
+
x += 10
|
|
396
|
+
item.format(x, y, item.width).addTo(self)
|
|
397
|
+
x += item.width
|
|
398
|
+
y += item.height
|
|
399
|
+
if item.needsSpace and i < len(self.items)-1:
|
|
400
|
+
Path(x, y).h(10).addTo(self)
|
|
401
|
+
x += 10
|
|
402
|
+
return self
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class Stack(DiagramMultiContainer):
|
|
406
|
+
def __init__(self, *items):
|
|
407
|
+
DiagramMultiContainer.__init__(self, 'g', items)
|
|
408
|
+
self.needsSpace = True
|
|
409
|
+
self.width = max(item.width + (20 if item.needsSpace else 0) for item in self.items)
|
|
410
|
+
# pretty sure that space calc is totes wrong
|
|
411
|
+
if len(self.items) > 1:
|
|
412
|
+
self.width += AR*2
|
|
413
|
+
self.up = self.items[0].up
|
|
414
|
+
self.down = self.items[-1].down
|
|
415
|
+
self.height = 0
|
|
416
|
+
last = len(self.items) - 1
|
|
417
|
+
for i,item in enumerate(self.items):
|
|
418
|
+
self.height += item.height
|
|
419
|
+
if i > 0:
|
|
420
|
+
self.height += max(AR*2, item.up + VS)
|
|
421
|
+
if i < last:
|
|
422
|
+
self.height += max(AR*2, item.down + VS)
|
|
423
|
+
addDebug(self)
|
|
424
|
+
|
|
425
|
+
def __repr__(self):
|
|
426
|
+
items = ', '.join(repr(item) for item in self.items)
|
|
427
|
+
return 'Stack(%s)' % items
|
|
428
|
+
|
|
429
|
+
def format(self, x, y, width):
|
|
430
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
431
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
432
|
+
x += leftGap
|
|
433
|
+
xInitial = x
|
|
434
|
+
if len(self.items) > 1:
|
|
435
|
+
Path(x, y).h(AR).addTo(self)
|
|
436
|
+
x += AR
|
|
437
|
+
innerWidth = self.width - AR*2
|
|
438
|
+
else:
|
|
439
|
+
innerWidth = self.width
|
|
440
|
+
for i,item in enumerate(self.items):
|
|
441
|
+
item.format(x, y, innerWidth).addTo(self)
|
|
442
|
+
x += innerWidth
|
|
443
|
+
y += item.height
|
|
444
|
+
if i != len(self.items)-1:
|
|
445
|
+
(Path(x,y)
|
|
446
|
+
.arc('ne').down(max(0, item.down + VS - AR*2))
|
|
447
|
+
.arc('es').left(innerWidth)
|
|
448
|
+
.arc('nw').down(max(0, self.items[i+1].up + VS - AR*2))
|
|
449
|
+
.arc('ws').addTo(self))
|
|
450
|
+
y += max(item.down + VS, AR*2) + max(self.items[i+1].up + VS, AR*2)
|
|
451
|
+
x = xInitial + AR
|
|
452
|
+
if len(self.items) > 1:
|
|
453
|
+
Path(x, y).h(AR).addTo(self)
|
|
454
|
+
x += AR
|
|
455
|
+
Path(x, y).h(rightGap).addTo(self)
|
|
456
|
+
return self
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class OptionalSequence(DiagramMultiContainer):
|
|
460
|
+
def __new__(cls, *items, **kw):
|
|
461
|
+
if len(items) <= 1:
|
|
462
|
+
return Sequence(*items, **kw)
|
|
463
|
+
else:
|
|
464
|
+
return super(OptionalSequence, cls).__new__(cls)
|
|
465
|
+
|
|
466
|
+
def __init__(self, *items, **kw):
|
|
467
|
+
DiagramMultiContainer.__init__(self, 'g', items)
|
|
468
|
+
self.inline = kw.get('inline', False)
|
|
469
|
+
self.needsSpace = False
|
|
470
|
+
self.width = 0
|
|
471
|
+
self.up = 0
|
|
472
|
+
self.height = sum(item.height for item in self.items)
|
|
473
|
+
self.down = self.items[0].down
|
|
474
|
+
heightSoFar = 0
|
|
475
|
+
for i,item in enumerate(self.items):
|
|
476
|
+
self.up = max(self.up, max((0 if self.inline else AR * 2), item.up + VS) - heightSoFar)
|
|
477
|
+
heightSoFar += item.height
|
|
478
|
+
if i > 0:
|
|
479
|
+
self.down = max(self.height + self.down, heightSoFar + max((0 if self.inline else AR*2), item.down + VS)) - self.height
|
|
480
|
+
itemWidth = item.width + (10 if item.needsSpace else 0)
|
|
481
|
+
if i == 0:
|
|
482
|
+
self.width += AR + max(itemWidth, AR)
|
|
483
|
+
else:
|
|
484
|
+
self.width += AR*2 + max(itemWidth, AR) + AR
|
|
485
|
+
if self.inlines:
|
|
486
|
+
self.width += AR * 2
|
|
487
|
+
addDebug(self)
|
|
488
|
+
|
|
489
|
+
def __repr__(self):
|
|
490
|
+
items = ', '.join(repr(item) for item in self.items)
|
|
491
|
+
return 'OptionalSequence(%s)' % items
|
|
492
|
+
|
|
493
|
+
def format(self, x, y, width):
|
|
494
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
495
|
+
Path(x, y).right(leftGap).addTo(self)
|
|
496
|
+
Path(x + leftGap + self.width, y + self.height).right(rightGap).addTo(self)
|
|
497
|
+
x += leftGap
|
|
498
|
+
upperLineY = y - self.up
|
|
499
|
+
last = len(self.items) - 1
|
|
500
|
+
for i,item in enumerate(self.items):
|
|
501
|
+
itemSpace = 10 if item.needsSpace else 0
|
|
502
|
+
itemWidth = item.width + itemSpace
|
|
503
|
+
if i == 0:
|
|
504
|
+
if not self.inline:
|
|
505
|
+
# Upper skip
|
|
506
|
+
(Path(x,y)
|
|
507
|
+
.arc('se')
|
|
508
|
+
.up(y - upperLineY - AR*2)
|
|
509
|
+
.arc('wn')
|
|
510
|
+
.right(itemWidth - AR)
|
|
511
|
+
.arc('ne')
|
|
512
|
+
.down(y + item.height - upperLineY - AR*2)
|
|
513
|
+
.arc('ws')
|
|
514
|
+
.addTo(self))
|
|
515
|
+
else:
|
|
516
|
+
DiagramItem('path', attrs={
|
|
517
|
+
"d": "M {x} {y} h -26 a 4 4 0 0 0 -4 4 v 12 a 4 4 0 0 0 4 4 h 26 z".format(x=x+30, y=y-10),
|
|
518
|
+
"class": "diagram-text"
|
|
519
|
+
}).addTo(text)
|
|
520
|
+
DiagramItem('text', text="?", attrs={
|
|
521
|
+
"x": x + 15,
|
|
522
|
+
"y": y + 4,
|
|
523
|
+
"class": "diagram-text"
|
|
524
|
+
}).addTo(text)
|
|
525
|
+
|
|
526
|
+
# Straight line
|
|
527
|
+
(Path(x, y)
|
|
528
|
+
.right(itemSpace + AR)
|
|
529
|
+
.addTo(self))
|
|
530
|
+
item.format(x + itemSpace + AR, y, item.width).addTo(self)
|
|
531
|
+
x += itemWidth + AR
|
|
532
|
+
y += item.height
|
|
533
|
+
elif i < last:
|
|
534
|
+
if not self.inline:
|
|
535
|
+
# Upper skip
|
|
536
|
+
(Path(x, upperLineY)
|
|
537
|
+
.right(AR*2 + max(itemWidth, AR) + AR)
|
|
538
|
+
.arc('ne')
|
|
539
|
+
.down(y - upperLineY + item.height - AR*2)
|
|
540
|
+
.arc('ws')
|
|
541
|
+
.addTo(self))
|
|
542
|
+
# Straight line
|
|
543
|
+
(Path(x,y)
|
|
544
|
+
.right(AR*2)
|
|
545
|
+
.addTo(self))
|
|
546
|
+
item.format(x + AR*2, y, item.width).addTo(self)
|
|
547
|
+
(Path(x + item.width + AR*2, y + item.height)
|
|
548
|
+
.right(itemSpace + AR)
|
|
549
|
+
.addTo(self))
|
|
550
|
+
if not self.inline:
|
|
551
|
+
# Lower skip
|
|
552
|
+
(Path(x,y)
|
|
553
|
+
.arc('ne')
|
|
554
|
+
.down(item.height + max(item.down + VS, AR*2) - AR*2)
|
|
555
|
+
.arc('ws')
|
|
556
|
+
.right(itemWidth - AR)
|
|
557
|
+
.arc('se')
|
|
558
|
+
.up(item.down + VS - AR*2)
|
|
559
|
+
.arc('wn')
|
|
560
|
+
.addTo(self))
|
|
561
|
+
x += AR*2 + max(itemWidth, AR) + AR
|
|
562
|
+
y += item.height
|
|
563
|
+
else:
|
|
564
|
+
# Straight line
|
|
565
|
+
(Path(x, y)
|
|
566
|
+
.right(AR*2)
|
|
567
|
+
.addTo(self))
|
|
568
|
+
item.format(x + AR*2, y, item.width).addTo(self)
|
|
569
|
+
(Path(x + AR*2 + item.width, y + item.height)
|
|
570
|
+
.right(itemSpace + AR)
|
|
571
|
+
.addTo(self))
|
|
572
|
+
if not self.inline:
|
|
573
|
+
# Lower skip
|
|
574
|
+
(Path(x,y)
|
|
575
|
+
.arc('ne')
|
|
576
|
+
.down(item.height + max(item.down + VS, AR*2) - AR*2)
|
|
577
|
+
.arc('ws')
|
|
578
|
+
.right(itemWidth - AR)
|
|
579
|
+
.arc('se')
|
|
580
|
+
.up(item.down + VS - AR*2)
|
|
581
|
+
.arc('wn')
|
|
582
|
+
.addTo(self))
|
|
583
|
+
return self
|
|
584
|
+
|
|
585
|
+
class AlternatingSequence(DiagramMultiContainer):
|
|
586
|
+
def __new__(cls, *items):
|
|
587
|
+
if len(items) == 2:
|
|
588
|
+
return super(AlternatingSequence, cls).__new__(cls)
|
|
589
|
+
else:
|
|
590
|
+
raise Exception("AlternatingSequence takes exactly two arguments got " + len(items))
|
|
591
|
+
|
|
592
|
+
def __init__(self, *items):
|
|
593
|
+
DiagramMultiContainer.__init__(self, 'g', items)
|
|
594
|
+
self.needsSpace = False
|
|
595
|
+
|
|
596
|
+
arc = AR
|
|
597
|
+
vert = VS
|
|
598
|
+
first = self.items[0]
|
|
599
|
+
second = self.items[1]
|
|
600
|
+
|
|
601
|
+
arcX = 1 / Math.sqrt(2) * arc * 2
|
|
602
|
+
arcY = (1 - 1 / Math.sqrt(2)) * arc * 2
|
|
603
|
+
crossY = max(arc, vert)
|
|
604
|
+
crossX = (crossY - arcY) + arcX
|
|
605
|
+
|
|
606
|
+
firstOut = max(arc + arc, crossY/2 + arc + arc, crossY/2 + vert + first.down)
|
|
607
|
+
self.up = firstOut + first.height + first.up
|
|
608
|
+
|
|
609
|
+
secondIn = max(arc + arc, crossY/2 + arc + arc, crossY/2 + vert + second.up)
|
|
610
|
+
self.down = secondIn + second.height + second.down
|
|
611
|
+
|
|
612
|
+
self.height = 0
|
|
613
|
+
|
|
614
|
+
firstWidth = (20 if first.needsSpace else 0) + first.width
|
|
615
|
+
secondWidth = (20 if second.needsSpace else 0) + second.width
|
|
616
|
+
self.width = 2*arc + max(firstWidth, crossX, secondWidth) + 2*arc
|
|
617
|
+
addDebug(self)
|
|
618
|
+
|
|
619
|
+
def __repr__(self):
|
|
620
|
+
items = ', '.join(repr(item) for item in self.items)
|
|
621
|
+
return 'AlternatingSequence(%s)' % items
|
|
622
|
+
|
|
623
|
+
def format(self, x, y, width):
|
|
624
|
+
arc = AR
|
|
625
|
+
gaps = determineGaps(width, self.width)
|
|
626
|
+
Path(x,y).right(gaps[0]).addTo(self)
|
|
627
|
+
x += gaps[0]
|
|
628
|
+
Path(x+self.width, y).right(gaps[1]).addTo(self)
|
|
629
|
+
# bounding box
|
|
630
|
+
# Path(x+gaps[0], y).up(self.up).right(self.width).down(self.up+self.down).left(self.width).up(self.down).addTo(self)
|
|
631
|
+
first = self.items[0]
|
|
632
|
+
second = self.items[1]
|
|
633
|
+
|
|
634
|
+
# top
|
|
635
|
+
firstIn = self.up - first.up
|
|
636
|
+
firstOut = self.up - first.up - first.height
|
|
637
|
+
Path(x,y).arc('se').up(firstIn-2*arc).arc('wn').addTo(self)
|
|
638
|
+
first.format(x + 2*arc, y - firstIn, self.width - 4*arc).addTo(self)
|
|
639
|
+
Path(x + self.width - 2*arc, y - firstOut).arc('ne').down(firstOut - 2*arc).arc('ws').addTo(self)
|
|
640
|
+
|
|
641
|
+
# bottom
|
|
642
|
+
secondIn = self.down - second.down - second.height
|
|
643
|
+
secondOut = self.down - second.down
|
|
644
|
+
Path(x,y).arc('ne').down(secondIn - 2*arc).arc('ws').addTo(self)
|
|
645
|
+
second.format(x + 2*arc, y + secondIn, self.width - 4*arc).addTo(self)
|
|
646
|
+
Path(x + self.width - 2*arc, y + secondOut).arc('se').up(secondOut - 2*arc).arc('wn').addTo(self)
|
|
647
|
+
|
|
648
|
+
# crossover
|
|
649
|
+
arcX = 1 / Math.sqrt(2) * arc * 2
|
|
650
|
+
arcY = (1 - 1 / Math.sqrt(2)) * arc * 2
|
|
651
|
+
crossY = max(arc, VS)
|
|
652
|
+
crossX = (crossY - arcY) + arcX
|
|
653
|
+
crossBar = (self.width - 4*arc - crossX)/2
|
|
654
|
+
(Path(x+arc, y - crossY/2 - arc).arc('ws').right(crossBar)
|
|
655
|
+
.arc_8('n', 'cw').l(crossX - arcX, crossY - arcY).arc_8('sw', 'ccw')
|
|
656
|
+
.right(crossBar).arc('ne').addTo(self))
|
|
657
|
+
(Path(x+arc, y + crossY/2 + arc).arc('wn').right(crossBar)
|
|
658
|
+
.arc_8('s', 'ccw').l(crossX - arcX, -(crossY - arcY)).arc_8('nw', 'cw')
|
|
659
|
+
.right(crossBar).arc('se').addTo(self))
|
|
660
|
+
|
|
661
|
+
return self
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
class Choice(DiagramMultiContainer):
|
|
665
|
+
def __init__(self, default, *items, **kw):
|
|
666
|
+
DiagramMultiContainer.__init__(self, 'g', items, **kw)
|
|
667
|
+
assert default < len(items)
|
|
668
|
+
self.default = default
|
|
669
|
+
self.width = AR * 4 + max(item.width for item in self.items)
|
|
670
|
+
self.up = self.items[0].up
|
|
671
|
+
self.down = self.items[-1].down
|
|
672
|
+
self.height = self.items[default].height
|
|
673
|
+
for i, item in enumerate(self.items):
|
|
674
|
+
if i in [default-1, default+1]:
|
|
675
|
+
arcs = AR*2
|
|
676
|
+
else:
|
|
677
|
+
arcs = AR
|
|
678
|
+
if i < default:
|
|
679
|
+
self.up += max(arcs, item.height + item.down + VS + self.items[i+1].up)
|
|
680
|
+
elif i == default:
|
|
681
|
+
self.down += arcs
|
|
682
|
+
self.up += arcs
|
|
683
|
+
else:
|
|
684
|
+
self.down += max(arcs, item.up + VS + self.items[i-1].down + self.items[i-1].height)
|
|
685
|
+
self.down -= self.items[default].height # already counted in self.height
|
|
686
|
+
self.down += self.items[-1].height
|
|
687
|
+
self.needsSpace = True
|
|
688
|
+
addDebug(self)
|
|
689
|
+
|
|
690
|
+
def __repr__(self):
|
|
691
|
+
items = ', '.join(repr(item) for item in self.items)
|
|
692
|
+
return 'Choice(%r, %s)' % (self.default, items)
|
|
693
|
+
|
|
694
|
+
def format(self, x, y, width):
|
|
695
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
696
|
+
noclose = getattr(self, 'noclose', 0)
|
|
697
|
+
# noclose: 1 = no entry above curve, 2 = no exit curve, 4 = no entry below curve?
|
|
698
|
+
|
|
699
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
700
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
701
|
+
if (noclose & 2) == 0:
|
|
702
|
+
Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)
|
|
703
|
+
x += leftGap
|
|
704
|
+
|
|
705
|
+
innerWidth = self.width - AR * 4
|
|
706
|
+
default = self.items[self.default]
|
|
707
|
+
|
|
708
|
+
# Do the elements that curve above
|
|
709
|
+
above = self.items[:self.default][::-1]
|
|
710
|
+
if above:
|
|
711
|
+
distanceFromY = max(
|
|
712
|
+
AR * 2,
|
|
713
|
+
default.up
|
|
714
|
+
+ VS
|
|
715
|
+
+ above[0].down
|
|
716
|
+
+ above[0].height)
|
|
717
|
+
for i,ni,item in doubleenumerate(above):
|
|
718
|
+
Path(x, y).arc('se').up(distanceFromY - AR * 2).arc('wn').addTo(self)
|
|
719
|
+
item.format(x + AR * 2, y - distanceFromY,
|
|
720
|
+
innerWidth if (noclose & 1) == 0 else 0).addTo(self)
|
|
721
|
+
if (noclose & 1) == 0:
|
|
722
|
+
Path(x + AR * 2 + innerWidth, y - distanceFromY + item.height).arc('ne') \
|
|
723
|
+
.down(distanceFromY - item.height + default.height - AR*2).arc('ws').addTo(self)
|
|
724
|
+
if ni < -1:
|
|
725
|
+
distanceFromY += max(
|
|
726
|
+
AR,
|
|
727
|
+
item.up
|
|
728
|
+
+ VS
|
|
729
|
+
+ above[i+1].down
|
|
730
|
+
+ above[i+1].height)
|
|
731
|
+
|
|
732
|
+
# Do the straight-line path.
|
|
733
|
+
Path(x, y).right(AR * 2).addTo(self)
|
|
734
|
+
self.items[self.default].format(x + AR * 2, y,
|
|
735
|
+
innerWidth if (noclose & 2) == 0 else 0).addTo(self)
|
|
736
|
+
if (noclose & 2) == 0:
|
|
737
|
+
Path(x + AR * 2 + innerWidth, y+self.height).right(AR * 2).addTo(self)
|
|
738
|
+
|
|
739
|
+
# Do the elements that curve below
|
|
740
|
+
below = self.items[self.default + 1:]
|
|
741
|
+
if below:
|
|
742
|
+
distanceFromY = max(
|
|
743
|
+
AR * 2,
|
|
744
|
+
default.height
|
|
745
|
+
+ default.down
|
|
746
|
+
+ VS
|
|
747
|
+
+ below[0].up)
|
|
748
|
+
for i, item in enumerate(below):
|
|
749
|
+
Path(x, y).arc('ne').down(distanceFromY - AR * 2).arc('ws').addTo(self)
|
|
750
|
+
item.format(x + AR * 2, y + distanceFromY,
|
|
751
|
+
innerWidth if (noclose & 4) == 0 else 0).addTo(self)
|
|
752
|
+
if (noclose & 4) == 0:
|
|
753
|
+
Path(x + AR * 2 + innerWidth, y + distanceFromY + item.height).arc('se') \
|
|
754
|
+
.up(distanceFromY - AR * 2 + item.height - default.height).arc('wn').addTo(self)
|
|
755
|
+
distanceFromY += max(
|
|
756
|
+
AR,
|
|
757
|
+
item.height
|
|
758
|
+
+ item.down
|
|
759
|
+
+ VS
|
|
760
|
+
+ (below[i + 1].up if i+1 < len(below) else 0))
|
|
761
|
+
return self
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
class MultipleChoice(DiagramMultiContainer):
|
|
765
|
+
def __init__(self, default, type, *items):
|
|
766
|
+
DiagramMultiContainer.__init__(self, 'g', items)
|
|
767
|
+
assert 0 <= default < len(items)
|
|
768
|
+
assert type in ["any", "all"]
|
|
769
|
+
self.default = default
|
|
770
|
+
self.type = type
|
|
771
|
+
self.needsSpace = True
|
|
772
|
+
self.innerWidth = max(item.width for item in self.items)
|
|
773
|
+
self.width = 30 + AR + self.innerWidth + AR + 20
|
|
774
|
+
self.up = self.items[0].up
|
|
775
|
+
self.down = self.items[-1].down
|
|
776
|
+
self.height = self.items[default].height
|
|
777
|
+
for i, item in enumerate(self.items):
|
|
778
|
+
if i in [default-1, default+1]:
|
|
779
|
+
minimum = 10 + AR
|
|
780
|
+
else:
|
|
781
|
+
minimum = AR
|
|
782
|
+
if i < default:
|
|
783
|
+
self.up += max(minimum, item.height + item.down + VS + self.items[i+1].up)
|
|
784
|
+
elif i == default:
|
|
785
|
+
continue
|
|
786
|
+
else:
|
|
787
|
+
self.down += max(minimum, item.up + VS + self.items[i-1].down + self.items[i-1].height)
|
|
788
|
+
self.down -= self.items[default].height # already counted in self.height
|
|
789
|
+
addDebug(self)
|
|
790
|
+
|
|
791
|
+
def __repr__(self):
|
|
792
|
+
items = ', '.join(map(repr, self.items))
|
|
793
|
+
return 'MultipleChoice(%r, %r, %s)' % (self.default, self.type, items)
|
|
794
|
+
|
|
795
|
+
def format(self, x, y, width):
|
|
796
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
797
|
+
|
|
798
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
799
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
800
|
+
Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)
|
|
801
|
+
x += leftGap
|
|
802
|
+
|
|
803
|
+
default = self.items[self.default]
|
|
804
|
+
|
|
805
|
+
# Do the elements that curve above
|
|
806
|
+
above = self.items[:self.default][::-1]
|
|
807
|
+
if above:
|
|
808
|
+
distanceFromY = max(
|
|
809
|
+
10 + AR,
|
|
810
|
+
default.up
|
|
811
|
+
+ VS
|
|
812
|
+
+ above[0].down
|
|
813
|
+
+ above[0].height)
|
|
814
|
+
for i,ni,item in doubleenumerate(above):
|
|
815
|
+
(Path(x + 30, y)
|
|
816
|
+
.up(distanceFromY - AR)
|
|
817
|
+
.arc('wn')
|
|
818
|
+
.addTo(self))
|
|
819
|
+
item.format(x + 30 + AR, y - distanceFromY, self.innerWidth).addTo(self)
|
|
820
|
+
(Path(x + 30 + AR + self.innerWidth, y - distanceFromY + item.height)
|
|
821
|
+
.arc('ne')
|
|
822
|
+
.down(distanceFromY - item.height + default.height - AR - 10)
|
|
823
|
+
.addTo(self))
|
|
824
|
+
if ni < -1:
|
|
825
|
+
distanceFromY += max(
|
|
826
|
+
AR,
|
|
827
|
+
item.up
|
|
828
|
+
+ VS
|
|
829
|
+
+ above[i+1].down
|
|
830
|
+
+ above[i+1].height)
|
|
831
|
+
|
|
832
|
+
# Do the straight-line path.
|
|
833
|
+
Path(x + 30, y).right(AR).addTo(self)
|
|
834
|
+
self.items[self.default].format(x + 30 + AR, y, self.innerWidth).addTo(self)
|
|
835
|
+
Path(x + 30 + AR + self.innerWidth, y + self.height).right(AR).addTo(self)
|
|
836
|
+
|
|
837
|
+
# Do the elements that curve below
|
|
838
|
+
below = self.items[self.default + 1:]
|
|
839
|
+
if below:
|
|
840
|
+
distanceFromY = max(
|
|
841
|
+
10 + AR,
|
|
842
|
+
default.height
|
|
843
|
+
+ default.down
|
|
844
|
+
+ VS
|
|
845
|
+
+ below[0].up)
|
|
846
|
+
for i, item in enumerate(below):
|
|
847
|
+
(Path(x+30, y)
|
|
848
|
+
.down(distanceFromY - AR)
|
|
849
|
+
.arc('ws')
|
|
850
|
+
.addTo(self))
|
|
851
|
+
item.format(x + 30 + AR, y + distanceFromY, self.innerWidth).addTo(self)
|
|
852
|
+
(Path(x + 30 + AR + self.innerWidth, y + distanceFromY + item.height)
|
|
853
|
+
.arc('se')
|
|
854
|
+
.up(distanceFromY - AR + item.height - default.height - 10)
|
|
855
|
+
.addTo(self))
|
|
856
|
+
distanceFromY += max(
|
|
857
|
+
AR,
|
|
858
|
+
item.height
|
|
859
|
+
+ item.down
|
|
860
|
+
+ VS
|
|
861
|
+
+ (below[i + 1].up if i+1 < len(below) else 0))
|
|
862
|
+
text = DiagramItem('g', attrs={"class": "diagram-text"}).addTo(self)
|
|
863
|
+
DiagramItem('title', text="take one or more branches, once each, in any order" if self.type=="any" else "take all branches, once each, in any order").addTo(text)
|
|
864
|
+
DiagramItem('path', attrs={
|
|
865
|
+
"d": "M {x} {y} h -26 a 4 4 0 0 0 -4 4 v 12 a 4 4 0 0 0 4 4 h 26 z".format(x=x+30, y=y-10),
|
|
866
|
+
"class": "diagram-text"
|
|
867
|
+
}).addTo(text)
|
|
868
|
+
DiagramItem('text', text="1+" if self.type=="any" else "all", attrs={
|
|
869
|
+
"x": x + 15,
|
|
870
|
+
"y": y + 4,
|
|
871
|
+
"class": "diagram-text"
|
|
872
|
+
}).addTo(text)
|
|
873
|
+
DiagramItem('path', attrs={
|
|
874
|
+
"d": "M {x} {y} h 16 a 4 4 0 0 1 4 4 v 12 a 4 4 0 0 1 -4 4 h -16 z".format(x=x+self.width-20, y=y-10),
|
|
875
|
+
"class": "diagram-text"
|
|
876
|
+
}).addTo(text)
|
|
877
|
+
DiagramItem('text', text=u"↺", attrs={
|
|
878
|
+
"x": x + self.width - 10,
|
|
879
|
+
"y": y + 4,
|
|
880
|
+
"class": "diagram-arrow"
|
|
881
|
+
}).addTo(text)
|
|
882
|
+
return self
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
class HorizontalChoice(DiagramMultiContainer):
|
|
886
|
+
def __new__(cls, *items, **kw):
|
|
887
|
+
if len(items) <= 1:
|
|
888
|
+
return Sequence(*items)
|
|
889
|
+
else:
|
|
890
|
+
return super(HorizontalChoice, cls).__new__(cls)
|
|
891
|
+
|
|
892
|
+
def __init__(self, *items, **kw):
|
|
893
|
+
DiagramMultiContainer.__init__(self, 'g', items, **kw)
|
|
894
|
+
allButLast = self.items[:-1]
|
|
895
|
+
middles = self.items[1:-1]
|
|
896
|
+
first = self.items[0]
|
|
897
|
+
last = self.items[-1]
|
|
898
|
+
self.needsSpace = False
|
|
899
|
+
|
|
900
|
+
self.width = (AR # starting track
|
|
901
|
+
+ AR*2 * (len(self.items)-1) # inbetween tracks
|
|
902
|
+
+ sum(x.width + (20 if x.needsSpace else 0) for x in self.items) #items
|
|
903
|
+
+ (AR if last.height > 0 else 0) # needs space to curve up
|
|
904
|
+
+ AR) #ending track
|
|
905
|
+
|
|
906
|
+
# Always exits at entrance height
|
|
907
|
+
self.height = 0
|
|
908
|
+
|
|
909
|
+
# All but the last have a track running above them
|
|
910
|
+
self._upperTrack = max(
|
|
911
|
+
AR*2,
|
|
912
|
+
VS,
|
|
913
|
+
max(x.up for x in allButLast) + VS
|
|
914
|
+
)
|
|
915
|
+
self.up = max(self._upperTrack, last.up)
|
|
916
|
+
|
|
917
|
+
# All but the first have a track running below them
|
|
918
|
+
# Last either straight-lines or curves up, so has different calculation
|
|
919
|
+
self._lowerTrack = max(
|
|
920
|
+
VS,
|
|
921
|
+
max(x.height+max(x.down+VS, AR*2) for x in middles) if middles else 0,
|
|
922
|
+
last.height + last.down + VS
|
|
923
|
+
)
|
|
924
|
+
if first.height < self._lowerTrack:
|
|
925
|
+
# Make sure there's at least 2*AR room between first exit and lower track
|
|
926
|
+
self._lowerTrack = max(self._lowerTrack, first.height + AR*2)
|
|
927
|
+
self.down = max(self._lowerTrack, first.height + first.down)
|
|
928
|
+
|
|
929
|
+
addDebug(self)
|
|
930
|
+
|
|
931
|
+
def format(self, x, y, width):
|
|
932
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
933
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
934
|
+
noclose = getattr(self, 'noclose', 0) != 0
|
|
935
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
936
|
+
Path(x + leftGap + self.width, y + self.height).h(rightGap).addTo(self)
|
|
937
|
+
x += leftGap
|
|
938
|
+
|
|
939
|
+
first = self.items[0]
|
|
940
|
+
last = self.items[-1]
|
|
941
|
+
|
|
942
|
+
# upper track
|
|
943
|
+
upperSpan = (sum(x.width+(20 if x.needsSpace and not noclose else 0) for x in self.items[:-1])
|
|
944
|
+
+ (len(self.items) - 2) * AR*2
|
|
945
|
+
- AR)
|
|
946
|
+
(Path(x,y)
|
|
947
|
+
.arc('se')
|
|
948
|
+
.up(self._upperTrack - AR*2)
|
|
949
|
+
.arc('wn')
|
|
950
|
+
.h(upperSpan)
|
|
951
|
+
.addTo(self))
|
|
952
|
+
|
|
953
|
+
# lower track
|
|
954
|
+
if not noclose:
|
|
955
|
+
lowerSpan = (sum(x.width+(20 if x.needsSpace else 0) for x in self.items[1:])
|
|
956
|
+
+ (len(self.items) - 2) * AR*2
|
|
957
|
+
+ (AR if last.height > 0 else 0)
|
|
958
|
+
- AR)
|
|
959
|
+
lowerStart = x + AR + first.width+(20 if first.needsSpace else 0) + AR*2
|
|
960
|
+
(Path(lowerStart, y+self._lowerTrack)
|
|
961
|
+
.h(lowerSpan)
|
|
962
|
+
.arc('se')
|
|
963
|
+
.up(self._lowerTrack - AR*2)
|
|
964
|
+
.arc('wn')
|
|
965
|
+
.addTo(self))
|
|
966
|
+
|
|
967
|
+
# Items
|
|
968
|
+
for [i, item] in enumerate(self.items):
|
|
969
|
+
# input track
|
|
970
|
+
if i == 0:
|
|
971
|
+
(Path(x,y)
|
|
972
|
+
.h(AR)
|
|
973
|
+
.addTo(self))
|
|
974
|
+
x += AR
|
|
975
|
+
else:
|
|
976
|
+
(Path(x, y - self._upperTrack)
|
|
977
|
+
.arc('ne')
|
|
978
|
+
.v(self._upperTrack - AR*2)
|
|
979
|
+
.arc('ws')
|
|
980
|
+
.addTo(self))
|
|
981
|
+
x += AR*2
|
|
982
|
+
|
|
983
|
+
# item
|
|
984
|
+
itemWidth = item.width + (20 if item.needsSpace and not noclose else 0)
|
|
985
|
+
item.format(x, y, itemWidth).addTo(self)
|
|
986
|
+
x += itemWidth
|
|
987
|
+
|
|
988
|
+
# output track
|
|
989
|
+
if not noclose:
|
|
990
|
+
if i == len(self.items)-1:
|
|
991
|
+
if item.height == 0:
|
|
992
|
+
(Path(x,y)
|
|
993
|
+
.h(AR)
|
|
994
|
+
.addTo(self))
|
|
995
|
+
else:
|
|
996
|
+
(Path(x,y+item.height)
|
|
997
|
+
.arc('se')
|
|
998
|
+
.addTo(self))
|
|
999
|
+
elif i == 0 and item.height > self._lowerTrack:
|
|
1000
|
+
# Needs to arc up to meet the lower track, not down.
|
|
1001
|
+
if item.height - self._lowerTrack >= AR*2:
|
|
1002
|
+
(Path(x, y+item.height)
|
|
1003
|
+
.arc('se')
|
|
1004
|
+
.v(self._lowerTrack - item.height + AR*2)
|
|
1005
|
+
.arc('wn')
|
|
1006
|
+
.addTo(self))
|
|
1007
|
+
else:
|
|
1008
|
+
# Not enough space to fit two arcs
|
|
1009
|
+
# so just bail and draw a straight line for now.
|
|
1010
|
+
(Path(x, y+item.height)
|
|
1011
|
+
.l(AR*2, self._lowerTrack - item.height)
|
|
1012
|
+
.addTo(self))
|
|
1013
|
+
else:
|
|
1014
|
+
(Path(x, y+item.height)
|
|
1015
|
+
.arc('ne')
|
|
1016
|
+
.v(self._lowerTrack - item.height - AR*2)
|
|
1017
|
+
.arc('ws')
|
|
1018
|
+
.addTo(self))
|
|
1019
|
+
return self
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def Optional(item, skip=False):
|
|
1023
|
+
return Choice(0 if skip else 1, Skip(), item)
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
class OneOrMore(DiagramItem):
|
|
1027
|
+
def __init__(self, item=None, repeat=None):
|
|
1028
|
+
DiagramItem.__init__(self, 'g')
|
|
1029
|
+
if item is not None:
|
|
1030
|
+
self.item = wrapString(item)
|
|
1031
|
+
repeat = repeat or Skip()
|
|
1032
|
+
self.rep = wrapString(repeat)
|
|
1033
|
+
self.width = max(self.item.width, self.rep.width) + AR * 2
|
|
1034
|
+
self.height = self.item.height
|
|
1035
|
+
self.up = self.item.up
|
|
1036
|
+
self.down = max(
|
|
1037
|
+
AR * 2,
|
|
1038
|
+
self.item.down + VS + self.rep.up + self.rep.height + self.rep.down)
|
|
1039
|
+
self.needsSpace = True
|
|
1040
|
+
addDebug(self)
|
|
1041
|
+
|
|
1042
|
+
def append(self, item):
|
|
1043
|
+
self.item = wrapString(item)
|
|
1044
|
+
|
|
1045
|
+
def format(self, x, y, width):
|
|
1046
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
1047
|
+
|
|
1048
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
1049
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
1050
|
+
Path(x + leftGap + self.width, y +self.height).h(rightGap).addTo(self)
|
|
1051
|
+
x += leftGap
|
|
1052
|
+
|
|
1053
|
+
# Draw item
|
|
1054
|
+
Path(x, y).right(AR).addTo(self)
|
|
1055
|
+
self.item.format(x + AR, y, self.width - AR * 2).addTo(self)
|
|
1056
|
+
Path(x + self.width - AR, y + self.height).right(AR).addTo(self)
|
|
1057
|
+
|
|
1058
|
+
# Draw repeat arc
|
|
1059
|
+
distanceFromY = max(AR*2, self.item.height + self.item.down + VS + self.rep.up)
|
|
1060
|
+
Path(x + AR, y).arc('nw').down(distanceFromY - AR * 2) \
|
|
1061
|
+
.arc('ws').addTo(self)
|
|
1062
|
+
self.rep.format(x + AR, y + distanceFromY, self.width - AR*2).addTo(self)
|
|
1063
|
+
Path(x + self.width - AR, y + distanceFromY + self.rep.height).arc('se') \
|
|
1064
|
+
.up(distanceFromY - AR * 2 + self.rep.height - self.item.height).arc('en').addTo(self)
|
|
1065
|
+
|
|
1066
|
+
return self
|
|
1067
|
+
|
|
1068
|
+
def walk(self, cb):
|
|
1069
|
+
cb(self)
|
|
1070
|
+
self.item.walk(cb)
|
|
1071
|
+
self.rep.walk(cb)
|
|
1072
|
+
|
|
1073
|
+
def __repr__(self):
|
|
1074
|
+
return 'OneOrMore(%r, repeat=%r)' % (self.item, self.rep)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def ZeroOrMore(item=None, repeat=None, skip=False):
|
|
1078
|
+
result = Optional(OneOrMore(item, repeat), skip)
|
|
1079
|
+
return result
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
class Group(DiagramItem):
|
|
1083
|
+
def __init__(self, item, label):
|
|
1084
|
+
DiagramItem.__init__(self, 'g')
|
|
1085
|
+
self.item = wrapString(item)
|
|
1086
|
+
if isinstance(label, DiagramItem):
|
|
1087
|
+
self.label = label
|
|
1088
|
+
elif label:
|
|
1089
|
+
self.label = Comment(label)
|
|
1090
|
+
else:
|
|
1091
|
+
self.label = None
|
|
1092
|
+
|
|
1093
|
+
self.width = max(
|
|
1094
|
+
self.item.width + (20 if self.item.needsSpace else 0),
|
|
1095
|
+
self.label.width if self.label else 0,
|
|
1096
|
+
AR*2)
|
|
1097
|
+
self.height = self.item.height
|
|
1098
|
+
self.boxUp = max(self.item.up + VS, AR)
|
|
1099
|
+
self.up = self.boxUp
|
|
1100
|
+
if self.label:
|
|
1101
|
+
self.up += self.label.up + self.label.height + self.label.down
|
|
1102
|
+
self.down = max(self.item.down + VS, AR)
|
|
1103
|
+
self.needsSpace = True
|
|
1104
|
+
addDebug(self)
|
|
1105
|
+
|
|
1106
|
+
def format(self, x, y, width):
|
|
1107
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
1108
|
+
Path(x,y).h(leftGap).addTo(self)
|
|
1109
|
+
Path(x+leftGap+self.width,y+self.height).h(rightGap).addTo(self)
|
|
1110
|
+
x += leftGap
|
|
1111
|
+
|
|
1112
|
+
DiagramItem('rect', {
|
|
1113
|
+
"x":x,
|
|
1114
|
+
"y":y-self.boxUp,
|
|
1115
|
+
"width":self.width,
|
|
1116
|
+
"height":self.boxUp + self.height + self.down,
|
|
1117
|
+
"rx": AR,
|
|
1118
|
+
"ry": AR,
|
|
1119
|
+
'class':'group-box',
|
|
1120
|
+
}).addTo(self)
|
|
1121
|
+
|
|
1122
|
+
self.item.format(x,y,self.width).addTo(self)
|
|
1123
|
+
if self.label:
|
|
1124
|
+
self.label.format(
|
|
1125
|
+
x,
|
|
1126
|
+
y-(self.boxUp+self.label.down+self.label.height),
|
|
1127
|
+
self.label.width).addTo(self)
|
|
1128
|
+
|
|
1129
|
+
return self
|
|
1130
|
+
|
|
1131
|
+
def walk(self, cb):
|
|
1132
|
+
cb(self)
|
|
1133
|
+
self.item.walk(cb)
|
|
1134
|
+
self.label.walk(cb)
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
class Start(DiagramItem):
|
|
1138
|
+
def __init__(self, type="simple", label=None):
|
|
1139
|
+
DiagramItem.__init__(self, 'g')
|
|
1140
|
+
if label:
|
|
1141
|
+
self.width = max(20, len(label) * CHAR_WIDTH + 10)
|
|
1142
|
+
else:
|
|
1143
|
+
self.width = 20
|
|
1144
|
+
self.up = 10
|
|
1145
|
+
self.down = 10
|
|
1146
|
+
self.type = type
|
|
1147
|
+
self.label = label
|
|
1148
|
+
addDebug(self)
|
|
1149
|
+
|
|
1150
|
+
def format(self, x, y, _width):
|
|
1151
|
+
path = Path(x, y-10)
|
|
1152
|
+
if self.type == "complex":
|
|
1153
|
+
path.down(20).m(0, -10).right(self.width).addTo(self)
|
|
1154
|
+
else:
|
|
1155
|
+
path.down(20).m(10, -20).down(20).m(-10, -10).right(self.width).addTo(self)
|
|
1156
|
+
if self.label:
|
|
1157
|
+
DiagramItem('text', attrs={"x":x, "y":y-15, "style":"text-anchor:start"}, text=self.label).addTo(self)
|
|
1158
|
+
return self
|
|
1159
|
+
|
|
1160
|
+
def __repr__(self):
|
|
1161
|
+
return 'Start(type=%r, label=%r)' % (self.type, self.label)
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
class End(DiagramItem):
|
|
1165
|
+
def __init__(self, type="simple"):
|
|
1166
|
+
DiagramItem.__init__(self, 'path')
|
|
1167
|
+
self.width = 20
|
|
1168
|
+
self.up = 10
|
|
1169
|
+
self.down = 10
|
|
1170
|
+
self.type = type
|
|
1171
|
+
addDebug(self)
|
|
1172
|
+
|
|
1173
|
+
def format(self, x, y, _width):
|
|
1174
|
+
if self.type == "simple":
|
|
1175
|
+
self.attrs['d'] = 'M {0} {1} h 20 m -10 -10 v 20 m 10 -20 v 20'.format(x, y)
|
|
1176
|
+
elif self.type == "complex":
|
|
1177
|
+
self.attrs['d'] = 'M {0} {1} h 20 m 0 -10 v 20'.format(x, y)
|
|
1178
|
+
return self
|
|
1179
|
+
|
|
1180
|
+
def __repr__(self):
|
|
1181
|
+
return 'End(type=%r)' % self.type
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
class Terminal(DiagramItem):
|
|
1185
|
+
def __init__(self, text, href=None, title=None, cls=""):
|
|
1186
|
+
DiagramItem.__init__(self, 'g', {'class': ' '.join(['terminal', cls])})
|
|
1187
|
+
self.text = text
|
|
1188
|
+
self.href = href
|
|
1189
|
+
self.title = title
|
|
1190
|
+
self.cls = cls
|
|
1191
|
+
self.width = len(text) * CHAR_WIDTH + 20
|
|
1192
|
+
self.up = 11
|
|
1193
|
+
self.down = 11
|
|
1194
|
+
self.needsSpace = True
|
|
1195
|
+
addDebug(self)
|
|
1196
|
+
|
|
1197
|
+
def __repr__(self):
|
|
1198
|
+
return 'Terminal(%r, href=%r, title=%r)' % (self.text, self.href, self.title)
|
|
1199
|
+
|
|
1200
|
+
def format(self, x, y, width):
|
|
1201
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
1202
|
+
|
|
1203
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
1204
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
1205
|
+
Path(x + leftGap + self.width, y).h(rightGap).addTo(self)
|
|
1206
|
+
|
|
1207
|
+
DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width,
|
|
1208
|
+
'height': self.up + self.down, 'rx': 10, 'ry': 10}).addTo(self)
|
|
1209
|
+
text = DiagramItem('text', {'x': x + leftGap + self.width / 2, 'y': y + 4}, self.text)
|
|
1210
|
+
if self.href is not None:
|
|
1211
|
+
a = DiagramItem('a', {'xlink:href':self.href}, text).addTo(self)
|
|
1212
|
+
#text.addTo(a)
|
|
1213
|
+
else:
|
|
1214
|
+
text.addTo(self)
|
|
1215
|
+
if self.title is not None:
|
|
1216
|
+
DiagramItem('title', {}, self.title).addTo(self)
|
|
1217
|
+
return self
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
class NonTerminal(DiagramItem):
|
|
1221
|
+
def __init__(self, text, href=None, title=None, cls=""):
|
|
1222
|
+
DiagramItem.__init__(self, 'g', {'class': ' '.join(['non-terminal', cls])})
|
|
1223
|
+
self.text = text
|
|
1224
|
+
self.href = href
|
|
1225
|
+
self.title = title
|
|
1226
|
+
self.cls = cls
|
|
1227
|
+
self.width = len(text) * CHAR_WIDTH + 20
|
|
1228
|
+
self.up = 11
|
|
1229
|
+
self.down = 11
|
|
1230
|
+
self.needsSpace = True
|
|
1231
|
+
addDebug(self)
|
|
1232
|
+
|
|
1233
|
+
def __repr__(self):
|
|
1234
|
+
return 'NonTerminal(%r, href=%r, title=%r)' % (self.text, self.href, self.title)
|
|
1235
|
+
|
|
1236
|
+
def format(self, x, y, width):
|
|
1237
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
1238
|
+
|
|
1239
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
1240
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
1241
|
+
Path(x + leftGap + self.width, y).h(rightGap).addTo(self)
|
|
1242
|
+
|
|
1243
|
+
DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width,
|
|
1244
|
+
'height': self.up + self.down}).addTo(self)
|
|
1245
|
+
text = DiagramItem('text', {'x': x + leftGap + self.width / 2, 'y': y + 4}, self.text)
|
|
1246
|
+
if self.href is not None:
|
|
1247
|
+
a = DiagramItem('a', {'xlink:href':self.href}, text).addTo(self)
|
|
1248
|
+
text.addTo(a)
|
|
1249
|
+
else:
|
|
1250
|
+
text.addTo(self)
|
|
1251
|
+
if self.title is not None:
|
|
1252
|
+
DiagramItem('title', {}, self.title).addTo(self)
|
|
1253
|
+
return self
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
class Comment(DiagramItem):
|
|
1257
|
+
def __init__(self, text, href=None, title=None, cls=""):
|
|
1258
|
+
DiagramItem.__init__(self, 'g', {'class': ' '.join(['non-terminal', cls])})
|
|
1259
|
+
self.text = text
|
|
1260
|
+
self.href = href
|
|
1261
|
+
self.title = title
|
|
1262
|
+
self.cls = cls
|
|
1263
|
+
self.width = len(text) * COMMENT_CHAR_WIDTH + 10
|
|
1264
|
+
self.up = 8
|
|
1265
|
+
self.down = 8
|
|
1266
|
+
self.needsSpace = True
|
|
1267
|
+
addDebug(self)
|
|
1268
|
+
|
|
1269
|
+
def __repr__(self):
|
|
1270
|
+
return 'Comment(%r, href=%r, title=%r)' % (self.text, self.href, self.title)
|
|
1271
|
+
|
|
1272
|
+
def format(self, x, y, width):
|
|
1273
|
+
leftGap, rightGap = determineGaps(width, self.width)
|
|
1274
|
+
|
|
1275
|
+
# Hook up the two sides if self is narrower than its stated width.
|
|
1276
|
+
Path(x, y).h(leftGap).addTo(self)
|
|
1277
|
+
Path(x + leftGap + self.width, y).h(rightGap).addTo(self)
|
|
1278
|
+
|
|
1279
|
+
text = DiagramItem('text', {'x': x + leftGap + self.width / 2, 'y': y + 5, 'class': 'comment'}, self.text)
|
|
1280
|
+
if self.href is not None:
|
|
1281
|
+
a = DiagramItem('a', {'xlink:href':self.href}, text).addTo(self)
|
|
1282
|
+
text.addTo(a)
|
|
1283
|
+
else:
|
|
1284
|
+
text.addTo(self)
|
|
1285
|
+
if self.title is not None:
|
|
1286
|
+
DiagramItem('title', {}, self.title).addTo(self)
|
|
1287
|
+
return self
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
class Skip(DiagramItem):
|
|
1291
|
+
def __init__(self):
|
|
1292
|
+
DiagramItem.__init__(self, 'g')
|
|
1293
|
+
self.width = 0
|
|
1294
|
+
self.up = 0
|
|
1295
|
+
self.down = 0
|
|
1296
|
+
addDebug(self)
|
|
1297
|
+
|
|
1298
|
+
def format(self, x, y, width):
|
|
1299
|
+
Path(x, y).right(width).addTo(self)
|
|
1300
|
+
return self
|
|
1301
|
+
|
|
1302
|
+
def __repr__(self):
|
|
1303
|
+
return 'Skip()'
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
if __name__ == '__main__':
|
|
1307
|
+
def add(name, diagram):
|
|
1308
|
+
sys.stdout.write('<h1>{0}</h1>\n'.format(e(name)))
|
|
1309
|
+
diagram.writeSvg(sys.stdout.write)
|
|
1310
|
+
sys.stdout.write('\n')
|
|
1311
|
+
|
|
1312
|
+
import sys
|
|
1313
|
+
sys.stdout.write("<!doctype html><title>Test</title><body>")
|
|
1314
|
+
exec(open('test.py').read())
|
|
1315
|
+
sys.stdout.write('''
|
|
1316
|
+
<style>
|
|
1317
|
+
.blue text { fill: blue; }
|
|
1318
|
+
</style>
|
|
1319
|
+
''')
|
|
1320
|
+
sys.stdout.write('</body></html>')
|