Switch searching over to new namespace system.
[dom-editor.git] / Dome / Program.py
blob96da7f76f325128378e194ccec7bd0444690b91e
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 action[2] != 'unused':
55 print "Converting search namespaces..."
56 for p, u in action[2].iteritems():
57 print "Convert", p, u
58 new_prefix = ns.ensure_ns(p, u)
59 action[1] = action[1].replace(p + ':',
60 new_prefix + ':')
61 action[2] = 'unused'
62 op = Op(action)
63 elif op_node.localName == 'block':
64 op = load(op_node, block)
65 else:
66 if op_node.nodeType == Node.ELEMENT_NODE and op_node.localName != 'fail':
67 print "** WARNING ** Unknown op:", op_node
68 continue
70 try:
71 dx = int(float(op_node.getAttributeNS(None, 'dx')))
72 if dx:
73 op.dx = dx
74 dy = int(float(op_node.getAttributeNS(None, 'dy')))
75 if dy:
76 op.dy = dy
77 except:
78 pass
80 node_id = op_node.getAttributeNS(None, 'id')
81 if node_id:
82 id_hash[node_id] = op
84 prev.link_to(op, exit)
85 exit = 'next'
86 prev = op
88 if op_node.localName == 'block':
89 # Block nodes have a special failure child
90 for x in op_node.childNodes:
91 if x.localName == 'fail':
92 _load(x, op, 'fail')
93 break
94 else:
95 # If the new node has children then they are the failure case
96 _load(op_node, op, 'fail')
98 link = op_node.getAttributeNS(None, 'target_fail')
99 if link:
100 to_link.append((op, 'fail', link))
101 link = op_node.getAttributeNS(None, 'target_next')
102 if link:
103 to_link.append((op, 'next', link))
104 _load(node, block.start, 'next')
105 for (op, exit, child) in to_link:
106 try:
107 to = id_hash[child]
108 except:
109 print "**** Not adding link to unknown ID ****"
110 else:
111 op.link_to(to, exit)
112 return block
114 def load_dome_program(prog, ns):
115 "prog should be a DOM 'dome-program' node. ns will be updated"
116 import Namespaces
117 assert isinstance(ns, Namespaces.Namespaces)
118 #print "Loading", prog
119 if prog.localName != 'dome-program':
120 raise Exception('Not a DOME program: %s!' % prog)
122 new = Program(str(prog.getAttributeNS(None, 'name')))
124 #print "Loading '%s'..." % new.name
125 done_update = 0
127 for node in prog.childNodes:
128 if node.localName == 'node' and not done_update:
129 print "*** Converting from old format ***"
130 new.code = load(prog, new)
131 done_update = 1
132 if node.localName == 'block':
133 assert not done_update
134 new.code = load(node, new, ns)
135 if node.localName == 'dome-program':
136 new.add_sub(load_dome_program(node))
138 new.modified = 0
139 return new
141 class Program:
142 "A program contains a code Block and any number of sub-programs."
143 def __init__(self, name):
144 assert '/' not in name
146 self.code = Block(self)
148 self.name = name
149 self.subprograms = {}
150 self.watchers = []
151 self.parent = None
152 self.modified = 0
154 def get_path(self):
155 path = ""
156 p = self
157 while p:
158 path = p.name + '/' + path
159 p = p.parent
160 return path[:-1]
162 def changed(self, op = None):
163 self.modified = 1
164 if self.parent:
165 self.parent.changed(op)
166 else:
167 for w in self.watchers:
168 w.program_changed(op)
170 def tree_changed(self):
171 self.modified = 1
172 if self.parent:
173 self.parent.tree_changed()
174 else:
175 for w in self.watchers:
176 w.prog_tree_changed()
178 def add_sub(self, prog):
179 if prog.parent:
180 raise Exception('%s already has a parent program!' % prog.name)
181 if self.subprograms.has_key(prog.name):
182 raise Exception('%s already has a child called %s!' %
183 (self.name, prog.name))
184 prog.parent = self
185 self.subprograms[prog.name] = prog
186 self.tree_changed()
188 def remove_sub(self, prog):
189 if prog.parent != self:
190 raise Exception('%s is no child of mime!' % prog)
191 prog.parent = None
192 del self.subprograms[prog.name]
193 self.tree_changed()
195 def rename(self, name):
196 p = self.parent
197 if p:
198 if p.subprograms.has_key(name):
199 raise Exception('%s already has a child called %s!' % (p.name, name))
200 p.remove_sub(self)
201 self.name = name
202 if p:
203 p.add_sub(self)
204 else:
205 self.tree_changed()
207 def to_xml(self, doc):
208 node = doc.createElementNS(DOME_NS, 'dome:dome-program')
209 node.setAttributeNS(None, 'name', self.name)
211 node.appendChild(self.code.to_xml(doc))
213 # Keep them in the same order to help with diffs...
214 progs = self.subprograms.keys()
215 progs.sort()
217 for name in progs:
218 p = self.subprograms[name]
219 node.appendChild(p.to_xml(doc))
221 return node
223 def __str__(self):
224 return "Program(%s)" % self.name
226 class Op:
227 "Each node in a chain is an Op. There is no graphical stuff in here."
229 def __init__(self, action = None):
230 "Creates a new node (can be linked into another node later)"
231 if not action:
232 action = ['Start']
233 else:
234 action = list(action)
235 self.parent = None
236 self.action = action
237 self.next = None
238 self.fail = None
239 self.prev = [] # First parent is used for rendering as a tree
240 self.dx, self.dy = (0, 0)
242 def set_parent(self, parent):
243 if self.parent == parent:
244 return
245 if parent and self.parent:
246 raise Exception('Already got a parent!')
247 nearby = self.prev[:]
248 if self.next:
249 nearby.append(self.next)
250 if self.fail:
251 nearby.append(self.fail)
252 self.parent = parent
253 [x.set_parent(parent) for x in nearby if x.parent is not parent]
255 def changed(self, op = None):
256 if hasattr(self, 'cached_code'):
257 del self.cached_code
258 print "(remove cached code)"
259 self.parent.changed(op or self)
261 def swap_nf(self):
262 assert self.action[0] != 'Start'
263 self.next, self.fail = (self.fail, self.next)
264 self.changed()
266 def link_to(self, child, exit):
267 # Create a link from this exit to this child Op
268 # Can't link both exits to the same node (bad for tree-walking code in List)
269 assert self.action[0] != 'Start' or exit == 'next'
270 assert child.action[0] != 'Start'
271 assert child is not self
273 if (exit == 'next' and self.fail == child) or \
274 (exit == 'fail' and self.next == child):
275 raise Exception("Can't link both exits (of %s) to the same node!" % self)
277 #print "Link %s:%s -> %s" % (self, exit, child)
279 if child.parent and child.parent is not self.parent:
280 raise Exception('%s is from a different parent (%s vs %s)!' %
281 (child, child.parent, self.parent))
282 # If we already have something on this exit, and the new node has a
283 # clear next exit, move the rest of the chain there.
284 child.set_parent(self.parent)
285 current = getattr(self, exit)
286 if current:
287 if child.next:
288 raise Exception('%s already has a next exit' % child)
289 primary = current.prev[0] == self
290 self.unlink(exit, may_delete = 0)
291 child.link_to(current, 'next')
292 if primary:
293 # We were the primary parent, so we must be again...
294 current.prev.remove(child)
295 current.prev.insert(0, child)
296 child.prev.append(self)
297 setattr(self, exit, child)
298 self.changed()
300 def unlink(self, exit, may_delete = 1):
301 "Remove link from us to child"
302 assert exit in ['next', 'fail']
303 self._unlink(exit, may_delete)
304 self.changed()
306 def _unlink(self, exit, may_delete = 1):
307 child = getattr(self, exit)
308 if not child:
309 raise Exception('%s has no child on exit %s' % (self, exit))
310 if self not in child.prev:
311 raise Exception('Internal error: %s not my child!' % child)
313 child.prev.remove(self) # Only remove one copy
314 setattr(self, exit, None)
316 for x in child.prev: print x
318 if may_delete and not child.prev:
319 # There is no way to reach this child now, so unlink its children.
320 child.parent = None
321 if child.next:
322 child._unlink('next')
323 if child.fail:
324 child._unlink('fail')
326 def del_node(self):
327 """Remove this node. It there is exactly one out-going arc and one incoming one,
328 join them together. Error if:"
329 - There are multiple out-going arcs
330 - There is a single out-going arc but multiple parents"""
331 if self.next and self.fail:
332 raise Exception("Can't delete a node with both fail and next exits in use.")
333 if not self.prev:
334 raise Exception("Can't delete a Start node!")
336 prog = self.parent
338 # Find the chain to preserve (can't have both set here)
339 if self.next:
340 exit = 'next'
341 elif self.fail:
342 exit = 'fail'
343 else:
344 exit = None
346 if exit:
347 if len(self.prev) != 1:
348 raise Exception("Deleted node-chain must have a single link in")
349 prev = self.prev[0]
350 preserve = getattr(self, exit)
351 self.unlink(exit, may_delete = 0)
352 if prev.next == self:
353 exit = 'next'
354 else:
355 exit = 'fail'
357 # Remove all links to us
358 for p in self.prev[:]:
359 if p.next == self:
360 p.unlink('next')
361 if p.fail == self:
362 p.unlink('fail')
364 # Exit is now our parent's exit that leads to us...
365 if exit:
366 # Relink following nodes to our (single) parent
367 prev.link_to(preserve, exit)
369 assert not self.prev
370 assert not self.next
371 assert not self.fail
372 doc = self.to_doc()
373 self.action = None
374 self.parent = None
376 prog.changed()
377 return doc
379 def to_doc(self):
380 from Ft.Xml.cDomlette import implementation
381 doc = implementation.createDocument(DOME_NS, 'dome:dome-program', None)
382 self.to_xml_int(doc.documentElement)
383 return doc
385 def to_xml(self, doc):
386 node = doc.createElementNS(DOME_NS, 'dome:node')
387 node.setAttributeNS(None, 'action', `self.action`)
388 node.setAttributeNS(None, 'dx', str(self.dx))
389 node.setAttributeNS(None, 'dy', str(self.dy))
390 return node
392 def to_xml_int(self, parent):
393 """Adds a chain of <Node> elements to 'parent'. Links only followed when node is
394 first parent."""
395 node = self.to_xml(parent.ownerDocument)
396 parent.appendChild(node)
398 if len(self.prev) > 1:
399 node.setAttributeNS(None, 'id', str(id(self)))
401 def add_link(op, parent):
402 node = parent.ownerDocument.createElementNS(DOME_NS, 'dome:link')
403 parent.appendChild(node)
404 node.setAttributeNS(None, 'target', str(id(op)))
406 if self.fail:
407 if self.fail.prev[0] == self:
408 if isinstance(self, Block):
409 fail = parent.ownerDocument.createElementNS(DOME_NS, 'dome:fail')
410 self.fail.to_xml_int(fail)
411 node.appendChild(fail)
412 else:
413 self.fail.to_xml_int(node)
414 else:
415 node.setAttributeNS(None, 'target_fail', str(id(self.fail)))
416 if self.next:
417 if self.next.prev[0] == self:
418 self.next.to_xml_int(parent)
419 else:
420 node.setAttributeNS(None, 'target_next', str(id(self.next)))
422 def __str__(self):
423 return "{" + `self.action` + "}"
425 def get_program(self):
426 p = self.parent
427 while p and not isinstance(p, Program):
428 p = p.parent
429 return p
431 class Block(Op):
432 """A Block is an Op which contains a group of Ops."""
434 def __init__(self, parent):
435 Op.__init__(self, action = ['Block'])
436 self.parent = parent
437 self.start = Op()
438 self.start.parent = self
439 self.foreach = 0
440 self.enter = 0
441 self.restore = 0
442 self.comment = ''
444 def set_start(self, start):
445 assert not start.prev
447 start.set_parent(self)
448 self.start = start
449 self.changed()
451 def is_toplevel(self):
452 return not isinstance(self.parent, Block)
454 def link_to(self, child, exit):
455 assert not self.is_toplevel()
456 Op.link_to(self, child, exit)
458 def to_xml(self, doc):
459 node = doc.createElementNS(DOME_NS, 'dome:block')
460 node.setAttributeNS(None, 'foreach', str(self.foreach))
461 node.setAttributeNS(None, 'enter', str(self.enter))
462 node.setAttributeNS(None, 'restore', str(self.restore))
463 node.setAttributeNS(None, 'comment', str(self.comment))
464 assert not self.start.fail
465 if self.start.next:
466 self.start.next.to_xml_int(node)
467 return node
469 def toggle_restore(self):
470 self.restore = not self.restore
471 self.changed()
473 def toggle_enter(self):
474 self.enter = not self.enter
475 self.changed()
477 def toggle_foreach(self):
478 self.foreach = not self.foreach
479 self.changed()
481 def set_comment(self, comment):
482 self.comment = comment
483 self.changed()