Removed all the MD5 suck checking stuff.
[dom-editor.git] / Dome / Program.py
blobc4799a8d4d0af5edb1524fc0c7a788891e8f3219
1 from __future__ import nested_scopes
2 import string
3 from constants import DOME_NS
4 from xml.dom import Node
6 def el_named(node, name):
7 for n in node.childNodes:
8 if n.localName == name:
9 return n
10 return None
12 # Converts a DOM <block> node to a Block object.
13 def load(node, parent, ns):
14 #assert node.localName == 'block'
16 block = Block(parent)
17 prev = block.start
18 try:
19 if int(node.getAttributeNS(None, 'foreach')):
20 block.toggle_foreach()
21 if int(node.getAttributeNS(None, 'enter')):
22 block.toggle_enter()
23 if node.hasAttributeNS(None, 'restore'):
24 if int(node.getAttributeNS(None, 'restore')):
25 block.toggle_restore()
26 comment = node.getAttributeNS(None, 'comment')
27 block.set_comment(comment)
28 except:
29 pass
31 id_hash = {} # id from file -> Op
32 to_link = []
33 def _load(chain, prev, exit):
34 for op_node in chain.childNodes:
35 if str(op_node.localName) == 'node':
36 attr = op_node.getAttributeNS(None, 'action')
37 action = eval(str(attr))
38 if action[0] == 'Start':
39 print "Skipping Start"
40 continue
41 if action[0] == 'chroot':
42 action[0] = 'enter'
43 elif action[0] == 'unchroot':
44 action[0] = 'leave'
45 #elif action[0] == 'set_attrib':
46 # if action[3] == '':
47 # action = ('add_attrib', action[1], action[2])
48 # else:
49 # action = ('set_attrib', action[3])
50 elif action[0] == 'playback':
51 action[0] = 'map'
52 elif action[0] == 'add_attrib':
53 action[1] = "UNUSED"
54 elif action[0] == 'do_search' and type(action[-1]) is dict:
55 print "Converting search namespaces..."
56 for p, u in action[-1].iteritems():
57 print "Convert", p, u
58 old = p
59 if p.startswith('_'): p = None
60 p = ns.ensure_ns(p, u)
61 action[1] = action[1].replace(old + ':',
62 p + ':')
63 action[2] = 'unused'
64 op = Op(action)
65 elif op_node.localName == 'block':
66 op = load(op_node, block, ns)
67 else:
68 if op_node.nodeType == Node.ELEMENT_NODE and op_node.localName != 'fail':
69 print "** WARNING ** Unknown op:", op_node
70 continue
72 try:
73 dx = int(float(op_node.getAttributeNS(None, 'dx')))
74 if dx:
75 op.dx = dx
76 dy = int(float(op_node.getAttributeNS(None, 'dy')))
77 if dy:
78 op.dy = dy
79 except:
80 pass
82 node_id = op_node.getAttributeNS(None, 'id')
83 if node_id:
84 id_hash[node_id] = op
86 prev.link_to(op, exit)
87 exit = 'next'
88 prev = op
90 if op_node.localName == 'block':
91 # Block nodes have a special failure child
92 for x in op_node.childNodes:
93 if x.localName == 'fail':
94 _load(x, op, 'fail')
95 break
96 else:
97 # If the new node has children then they are the failure case
98 _load(op_node, op, 'fail')
100 link = op_node.getAttributeNS(None, 'target_fail')
101 if link:
102 to_link.append((op, 'fail', link))
103 link = op_node.getAttributeNS(None, 'target_next')
104 if link:
105 to_link.append((op, 'next', link))
106 _load(node, block.start, 'next')
107 for (op, exit, child) in to_link:
108 try:
109 to = id_hash[child]
110 except:
111 print "**** Not adding link to unknown ID ****"
112 else:
113 op.link_to(to, exit)
114 return block
116 def load_dome_program(prog, ns):
117 "prog should be a DOM 'dome-program' node. ns will be updated"
118 import Namespaces
119 assert isinstance(ns, Namespaces.Namespaces)
120 #print "Loading", prog
121 if prog.localName != 'dome-program':
122 raise Exception('Not a DOME program: %s!' % prog)
124 new = Program(str(prog.getAttributeNS(None, 'name')))
126 #print "Loading '%s'..." % new.name
127 done_update = 0
129 for node in prog.childNodes:
130 if node.localName == 'node' and not done_update:
131 print "*** Converting from old format ***"
132 new.code = load(prog, new, ns)
133 done_update = 1
134 if node.localName == 'block':
135 assert not done_update
136 new.code = load(node, new, ns)
137 if node.localName == 'dome-program':
138 new.add_sub(load_dome_program(node, ns))
140 new.modified = 0
141 return new
143 class Program:
144 "A program contains a code Block and any number of sub-programs."
145 def __init__(self, name):
146 assert '/' not in name
148 self.code = Block(self)
150 self.name = name
151 self.subprograms = {}
152 self.watchers = []
153 self.parent = None
154 self.modified = 0
156 def get_path(self):
157 path = ""
158 p = self
159 while p:
160 path = p.name + '/' + path
161 p = p.parent
162 return path[:-1]
164 def changed(self, op = None):
165 self.modified = 1
166 if self.parent:
167 self.parent.changed(op)
168 else:
169 for w in self.watchers:
170 w.program_changed(op)
172 def tree_changed(self):
173 self.modified = 1
174 if self.parent:
175 self.parent.tree_changed()
176 else:
177 for w in self.watchers:
178 w.prog_tree_changed()
180 def add_sub(self, prog):
181 if prog.parent:
182 raise Exception('%s already has a parent program!' % prog.name)
183 if self.subprograms.has_key(prog.name):
184 raise Exception('%s already has a child called %s!' %
185 (self.name, prog.name))
186 prog.parent = self
187 self.subprograms[prog.name] = prog
188 self.tree_changed()
190 def remove_sub(self, prog):
191 if prog.parent != self:
192 raise Exception('%s is no child of mime!' % prog)
193 prog.parent = None
194 del self.subprograms[prog.name]
195 self.tree_changed()
197 def rename(self, name):
198 p = self.parent
199 if p:
200 if p.subprograms.has_key(name):
201 raise Exception('%s already has a child called %s!' % (p.name, name))
202 p.remove_sub(self)
203 self.name = name
204 if p:
205 p.add_sub(self)
206 else:
207 self.tree_changed()
209 def to_xml(self, doc):
210 node = doc.createElementNS(DOME_NS, 'dome:dome-program')
211 node.setAttributeNS(None, 'name', self.name)
213 node.appendChild(self.code.to_xml(doc))
215 # Keep them in the same order to help with diffs...
216 progs = self.subprograms.keys()
217 progs.sort()
219 for name in progs:
220 p = self.subprograms[name]
221 node.appendChild(p.to_xml(doc))
223 return node
225 def __str__(self):
226 return "Program(%s)" % self.name
228 class Op:
229 "Each node in a chain is an Op. There is no graphical stuff in here."
231 def __init__(self, action = None):
232 "Creates a new node (can be linked into another node later)"
233 if not action:
234 action = ['Start']
235 else:
236 action = list(action)
237 self.parent = None
238 self.action = action
239 self.next = None
240 self.fail = None
241 self.prev = [] # First parent is used for rendering as a tree
242 self.dx, self.dy = (0, 0)
244 def set_parent(self, parent):
245 if self.parent == parent:
246 return
247 if parent and self.parent:
248 raise Exception('Already got a parent!')
249 nearby = self.prev[:]
250 if self.next:
251 nearby.append(self.next)
252 if self.fail:
253 nearby.append(self.fail)
254 self.parent = parent
255 [x.set_parent(parent) for x in nearby if x.parent is not parent]
257 def changed(self, op = None):
258 if hasattr(self, 'cached_code'):
259 del self.cached_code
260 print "(remove cached code)"
261 self.parent.changed(op or self)
263 def swap_nf(self):
264 assert self.action[0] != 'Start'
265 self.next, self.fail = (self.fail, self.next)
266 self.changed()
268 def link_to(self, child, exit):
269 # Create a link from this exit to this child Op
270 # Can't link both exits to the same node (bad for tree-walking code in List)
271 assert self.action[0] != 'Start' or exit == 'next'
272 assert child.action[0] != 'Start'
273 assert child is not self
275 if (exit == 'next' and self.fail == child) or \
276 (exit == 'fail' and self.next == child):
277 raise Exception("Can't link both exits (of %s) to the same node!" % self)
279 #print "Link %s:%s -> %s" % (self, exit, child)
281 if child.parent and child.parent is not self.parent:
282 raise Exception('%s is from a different parent (%s vs %s)!' %
283 (child, child.parent, self.parent))
284 # If we already have something on this exit, and the new node has a
285 # clear next exit, move the rest of the chain there.
286 child.set_parent(self.parent)
287 current = getattr(self, exit)
288 if current:
289 if child.next:
290 raise Exception('%s already has a next exit' % child)
291 primary = current.prev[0] == self
292 self.unlink(exit, may_delete = 0)
293 child.link_to(current, 'next')
294 if primary:
295 # We were the primary parent, so we must be again...
296 current.prev.remove(child)
297 current.prev.insert(0, child)
298 child.prev.append(self)
299 setattr(self, exit, child)
300 self.changed()
302 def unlink(self, exit, may_delete = 1):
303 "Remove link from us to child"
304 assert exit in ['next', 'fail']
305 self._unlink(exit, may_delete)
306 self.changed()
308 def _unlink(self, exit, may_delete = 1):
309 child = getattr(self, exit)
310 if not child:
311 raise Exception('%s has no child on exit %s' % (self, exit))
312 if self not in child.prev:
313 raise Exception('Internal error: %s not my child!' % child)
315 child.prev.remove(self) # Only remove one copy
316 setattr(self, exit, None)
318 for x in child.prev: print x
320 if may_delete and not child.prev:
321 # There is no way to reach this child now, so unlink its children.
322 child.parent = None
323 if child.next:
324 child._unlink('next')
325 if child.fail:
326 child._unlink('fail')
328 def del_node(self):
329 """Remove this node. It there is exactly one out-going arc and one incoming one,
330 join them together. Error if:"
331 - There are multiple out-going arcs
332 - There is a single out-going arc but multiple parents"""
333 if self.next and self.fail:
334 raise Exception("Can't delete a node with both fail and next exits in use.")
335 if not self.prev:
336 raise Exception("Can't delete a Start node!")
338 prog = self.parent
340 # Find the chain to preserve (can't have both set here)
341 if self.next:
342 exit = 'next'
343 elif self.fail:
344 exit = 'fail'
345 else:
346 exit = None
348 if exit:
349 if len(self.prev) != 1:
350 raise Exception("Deleted node-chain must have a single link in")
351 prev = self.prev[0]
352 preserve = getattr(self, exit)
353 self.unlink(exit, may_delete = 0)
354 if prev.next == self:
355 exit = 'next'
356 else:
357 exit = 'fail'
359 # Remove all links to us
360 for p in self.prev[:]:
361 if p.next == self:
362 p.unlink('next')
363 if p.fail == self:
364 p.unlink('fail')
366 # Exit is now our parent's exit that leads to us...
367 if exit:
368 # Relink following nodes to our (single) parent
369 prev.link_to(preserve, exit)
371 assert not self.prev
372 assert not self.next
373 assert not self.fail
374 doc = self.to_doc()
375 self.action = None
376 self.parent = None
378 prog.changed()
379 return doc
381 def to_doc(self):
382 from Ft.Xml.cDomlette import implementation
383 doc = implementation.createDocument(DOME_NS, 'dome:dome-program', None)
384 self.to_xml_int(doc.documentElement)
385 return doc
387 def to_xml(self, doc):
388 node = doc.createElementNS(DOME_NS, 'dome:node')
389 node.setAttributeNS(None, 'action', `self.action`)
390 node.setAttributeNS(None, 'dx', str(self.dx))
391 node.setAttributeNS(None, 'dy', str(self.dy))
392 return node
394 def to_xml_int(self, parent):
395 """Adds a chain of <Node> elements to 'parent'. Links only followed when node is
396 first parent."""
397 node = self.to_xml(parent.ownerDocument)
398 parent.appendChild(node)
400 if len(self.prev) > 1:
401 node.setAttributeNS(None, 'id', str(id(self)))
403 def add_link(op, parent):
404 node = parent.ownerDocument.createElementNS(DOME_NS, 'dome:link')
405 parent.appendChild(node)
406 node.setAttributeNS(None, 'target', str(id(op)))
408 if self.fail:
409 if self.fail.prev[0] == self:
410 if isinstance(self, Block):
411 fail = parent.ownerDocument.createElementNS(DOME_NS, 'dome:fail')
412 self.fail.to_xml_int(fail)
413 node.appendChild(fail)
414 else:
415 self.fail.to_xml_int(node)
416 else:
417 node.setAttributeNS(None, 'target_fail', str(id(self.fail)))
418 if self.next:
419 if self.next.prev[0] == self:
420 self.next.to_xml_int(parent)
421 else:
422 node.setAttributeNS(None, 'target_next', str(id(self.next)))
424 def __str__(self):
425 return "{" + `self.action` + "}"
427 def get_program(self):
428 p = self.parent
429 while p and not isinstance(p, Program):
430 p = p.parent
431 return p
433 class Block(Op):
434 """A Block is an Op which contains a group of Ops."""
436 def __init__(self, parent):
437 Op.__init__(self, action = ['Block'])
438 self.parent = parent
439 self.start = Op()
440 self.start.parent = self
441 self.foreach = 0
442 self.enter = 0
443 self.restore = 0
444 self.comment = ''
446 def set_start(self, start):
447 assert not start.prev
449 start.set_parent(self)
450 self.start = start
451 self.changed()
453 def is_toplevel(self):
454 return not isinstance(self.parent, Block)
456 def link_to(self, child, exit):
457 assert not self.is_toplevel()
458 Op.link_to(self, child, exit)
460 def to_xml(self, doc):
461 node = doc.createElementNS(DOME_NS, 'dome:block')
462 node.setAttributeNS(None, 'foreach', str(self.foreach))
463 node.setAttributeNS(None, 'enter', str(self.enter))
464 node.setAttributeNS(None, 'restore', str(self.restore))
465 node.setAttributeNS(None, 'comment', str(self.comment))
466 assert not self.start.fail
467 if self.start.next:
468 self.start.next.to_xml_int(node)
469 return node
471 def toggle_restore(self):
472 self.restore = not self.restore
473 self.changed()
475 def toggle_enter(self):
476 self.enter = not self.enter
477 self.changed()
479 def toggle_foreach(self):
480 self.foreach = not self.foreach
481 self.changed()
483 def set_comment(self, comment):
484 self.comment = comment
485 self.changed()