Change name according to save type.
[dom-editor.git] / Dome / Model.py
blob9685e5269db48076f2753aeb752f61f0ddd7d613
1 from __future__ import nested_scopes
3 # An model contains:
4 # - A DOM document
5 # - The undo history
6 # - The root program
7 # All changes to the DOM must go through here.
8 # Notification to views of changes is done.
10 from Ft.Xml.cDomlette import implementation, nonvalParse
11 from Ft.Xml.Domlette import GetAllNs
12 from Ft.Xml import XMLNS_NAMESPACE
14 from xml.dom import Node
15 import string
16 import support
17 from Beep import Beep
19 def get_xslt_source(doc, dome_data):
20 print "get_xslt_source", dome_data
21 src = doc.createElementNS(None, 'Source')
22 if file:
23 src.appendChild(support.import_with_ns(doc, dome_data.documentElement))
24 return src
26 class Model:
27 def __init__(self, path, root_program = None, dome_data = None):
28 "If root_program is given, then no data is loaded (used for lock_and_copy)."
29 self.uri = 'Prog.dome'
31 if dome_data:
32 from Ft.Xml.InputSource import InputSourceFactory
33 isrc = InputSourceFactory()
34 dome_data = nonvalParse(isrc.fromUri(dome_data))
36 self.clear_undo()
38 doc = None
39 if path:
40 if path != '-':
41 self.uri = path
42 if not root_program:
43 from Ft.Xml.InputSource import InputSourceFactory
44 isrc = InputSourceFactory()
45 doc = nonvalParse(isrc.fromUri(path))
46 if not doc:
47 doc = implementation.createDocument(None, 'root', None)
48 root = doc.documentElement
50 self.root_program = None
51 data_to_load = None
53 from Program import Program, load_dome_program
54 import constants
55 if root.namespaceURI == constants.DOME_NS and root.localName == 'dome':
56 for x in root.childNodes:
57 if x.namespaceURI == constants.DOME_NS:
58 if x.localName == 'dome-program':
59 self.root_program = load_dome_program(x)
60 elif x.localName == 'dome-data':
61 for y in x.childNodes:
62 if y.nodeType == Node.ELEMENT_NODE:
63 data_to_load = y
64 if dome_data:
65 data_to_load = dome_data.documentElement
66 elif (root.namespaceURI == constants.XSLT_NS and
67 root.localName in ['stylesheet', 'transform']) or \
68 root.hasAttributeNS(constants.XSLT_NS, 'version'):
69 import xslt
70 self.root_program = xslt.import_sheet(doc)
71 x = implementation.createDocument(None, 'xslt', None)
72 data_to_load = x.documentElement
73 src = get_xslt_source(x, dome_data)
74 data_to_load.appendChild(x.createElementNS(None, 'Result'))
75 data_to_load.appendChild(src)
76 dome_data = None
77 else:
78 data_to_load = root
80 if root_program:
81 self.root_program = root_program
82 else:
83 if not self.root_program:
84 self.root_program = Program('Root')
86 if data_to_load:
87 self.doc = implementation.createDocument(None, 'root', None)
88 if not root_program:
89 node = support.import_with_ns(self.doc, data_to_load)
90 self.doc.replaceChild(node, self.doc.documentElement)
91 self.strip_space()
93 self.views = [] # Notified when something changes
94 self.locks = {} # Node -> number of locks
96 def clear_undo(self):
97 # Pop an (op_number, function) off one of these and call the function to
98 # move forwards or backwards in the undo history.
99 self.undo_stack = []
100 self.redo_stack = []
101 self.doing_undo = 0
103 # Each series of suboperations in an undo stack which are part of a single
104 # user op will have the same number...
105 self.user_op = 1
107 #import gc
108 #print "GC freed:", gc.collect()
109 #print "Garbage", gc.garbage
111 def lock(self, node):
112 """Prevent removal of this node (or any ancestor)."""
113 #print "Locking", node.nodeName
114 self.locks[node] = self.get_locks(node) + 1
115 if node.parentNode:
116 self.lock(node.parentNode)
118 def unlock(self, node):
119 """Reverse the effect of lock(). Must call unlock the same number
120 of times as lock to fully unlock the node."""
121 l = self.get_locks(node)
122 if l > 1:
123 self.locks[node] = l - 1
124 elif l == 1:
125 del self.locks[node] # Or get a memory leak...
126 if node == self.doc.documentElement:
127 if node.parentNode:
128 self.unlock(node.parentNode)
129 return
130 else:
131 raise Exception('unlock(%s): Node not locked!' % node)
132 if node.parentNode:
133 self.unlock(node.parentNode)
135 def get_locks(self, node):
136 try:
137 return self.locks[node]
138 except KeyError:
139 return 0
141 def lock_and_copy(self, node):
142 """Locks 'node' in the current model and returns a new model
143 with a copy of the subtree."""
144 if self.get_locks(node):
145 raise Exception("Can't enter locked node!")
146 m = Model(self.get_base_uri(node), root_program = self.root_program)
147 copy = support.import_with_ns(m.doc, node)
148 root = m.get_root()
149 m.replace_node(root, copy)
150 self.lock(node)
151 return m
153 def mark(self):
154 "Increment the user_op counter. Undo will undo every operation between"
155 "two marks."
156 self.user_op += 1
158 def get_root(self):
159 "Return the true root node (not a view root)"
160 return self.doc.documentElement
162 def add_view(self, view):
163 "'view' provides:"
164 "'update_all(subtree) - called when a major change occurs."
165 #print "New view:", view
166 self.views.append(view)
168 def remove_view(self, view):
169 #print "Removing view", view
170 self.views.remove(view)
171 #print "Now:", self.views
172 if not self.views:
173 self.clear_undo()
175 def update_all(self, node):
176 "Called when 'node' has been updated."
177 "'node' is still in the document, so deleting or replacing"
178 "a node calls this on the parent."
179 for v in self.views:
180 v.update_all(node)
182 def update_replace(self, old, new):
183 "Called when 'old' is replaced by 'new'."
184 for v in self.views:
185 v.update_replace(old, new)
187 def strip_space(self, node = None):
188 if not node:
189 node = self.doc.documentElement
190 if node.nodeType == Node.TEXT_NODE:
191 node.data = string.strip(node.data)
192 if node.data == '':
193 node.parentNode.removeChild(node)
194 else:
195 for k in node.childNodes[:]:
196 self.strip_space(k)
198 # Changes
200 def normalise(self, node):
201 old = node.cloneNode(1)
202 node.normalize()
203 self.add_undo(lambda: self.replace_node(node, old))
204 self.update_all(node)
206 def convert_to_text(self, node):
207 assert node.nodeType == Node.COMMENT_NODE
208 new = self.doc.createTextNode(node.data)
209 self.replace_node(node, new)
210 return new
212 def remove_ns(self, node):
213 nss = GetAllNs(node.parentNode)
214 dns = nss.get(None, None)
215 create = node.ownerDocument.createElementNS
216 def ns_clone(node):
217 if node.nodeType != Node.ELEMENT_NODE:
218 return node.cloneNode(1)
219 new = create(dns, node.nodeName)
220 for a in node.attributes.values():
221 if a.localName == 'xmlns' and a.prefix is None:
222 print "Removing xmlns attrib on", node
223 continue
224 new.setAttributeNS(a.namespaceURI, a.name, a.value)
225 #print "Now", new
226 for k in node.childNodes:
227 new.appendChild(ns_clone(k))
228 return new
229 new = ns_clone(node)
230 self.replace_node(node, new)
231 return new
233 def set_name(self, node, namespace, name):
234 if self.get_locks(node):
235 raise Exception('Attempt to set name on locked node %s' % node)
237 new = node.ownerDocument.createElementNS(namespace, name)
238 self.replace_shallow(node, new)
239 return new
241 def replace_shallow(self, old, new):
242 """Replace old with new, keeping the old children."""
243 assert not new.childNodes
244 assert not new.parentNode
246 old_name = old.nodeName
247 old_ns = old.namespaceURI
249 kids = old.childNodes[:]
250 attrs = old.attributes.values()
251 parent = old.parentNode
252 [ old.removeChild(k) for k in kids ]
253 parent.replaceChild(new, old)
254 [ new.appendChild(k) for k in kids ]
255 [ new.setAttributeNS(a.namespaceURI, a.name, a.value) for a in attrs ]
257 self.add_undo(lambda: self.replace_shallow(new, old))
259 self.update_replace(old, new)
261 import __main__
262 if __main__.no_gui_mode:
263 def add_undo(self, fn):
264 pass
265 else:
266 def add_undo(self, fn):
267 self.undo_stack.append((self.user_op, fn))
268 if not self.doing_undo:
269 self.redo_stack = []
271 def set_data(self, node, data):
272 old_data = node.data
273 node.data = data
274 self.add_undo(lambda: self.set_data(node, old_data))
275 self.update_all(node)
277 def replace_node(self, old, new):
278 if self.get_locks(old):
279 raise Exception('Attempt to replace locked node %s' % old)
280 old.parentNode.replaceChild(new, old)
281 self.add_undo(lambda: self.replace_node(new, old))
283 self.update_replace(old, new)
285 def delete_shallow(self, node):
286 """Replace node with its contents"""
287 kids = node.childNodes[:]
288 next = node.nextSibling
289 parent = node.parentNode
290 for n in kids + [node]:
291 if self.get_locks(n):
292 raise Exception('Attempt to move/delete locked node %s' % n)
293 for k in kids:
294 self.delete_internal(k)
295 self.delete_internal(node)
296 for k in kids:
297 self.insert_before_interal(next, k, parent)
298 self.update_all(parent)
300 def delete_nodes(self, nodes):
301 #print "Deleting", nodes
302 for n in nodes:
303 if self.get_locks(n):
304 raise Exception('Attempt to delete locked node %s' % n)
305 for n in nodes:
306 p = n.parentNode
307 self.delete_internal(n)
308 self.update_all(p)
310 def delete_internal(self, node):
311 "Doesn't update display."
312 next = node.nextSibling
313 parent = node.parentNode
314 parent.removeChild(node)
315 self.add_undo(lambda: self.insert_before(next, node, parent = parent))
317 def insert_before_interal(self, node, new, parent):
318 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
319 "of parent's children."
320 assert new.nodeType != Node.DOCUMENT_FRAGMENT_NODE
321 assert parent.nodeType == Node.ELEMENT_NODE
322 parent.insertBefore(new, node)
323 self.add_undo(lambda: self.delete_nodes([new]))
325 def undo(self):
326 if not self.undo_stack:
327 raise Exception('Nothing to undo')
329 assert not self.doing_undo
331 uop = self.undo_stack[-1][0]
333 # Swap stacks so that the undo actions will populate the redo stack...
334 (self.undo_stack, self.redo_stack) = (self.redo_stack, self.undo_stack)
335 self.doing_undo = 1
336 try:
337 while self.redo_stack and self.redo_stack[-1][0] == uop:
338 self.redo_stack[-1][1]()
339 self.redo_stack.pop()
340 finally:
341 (self.undo_stack, self.redo_stack) = (self.redo_stack, self.undo_stack)
342 self.doing_undo = 0
344 def redo(self):
345 if not self.redo_stack:
346 raise Exception('Nothing to redo')
348 uop = self.redo_stack[-1][0]
349 self.doing_undo = 1
350 try:
351 while self.redo_stack and self.redo_stack[-1][0] == uop:
352 self.redo_stack[-1][1]()
353 self.redo_stack.pop()
354 finally:
355 self.doing_undo = 0
357 def insert(self, node, new, index = 0):
358 if len(node.childNodes) > index:
359 self.insert_before(node.childNodes[index], new)
360 else:
361 self.insert_before(None, new, parent = node)
363 def insert_after(self, node, new):
364 self.insert_before(node.nextSibling, new, parent = node.parentNode)
366 def insert_before(self, node, new, parent = None):
367 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
368 "of parent's children."
369 if not parent:
370 parent = node.parentNode
371 if new.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
372 for n in new.childNodes[:]:
373 self.insert_before_interal(node, n, parent)
374 else:
375 self.insert_before_interal(node, new, parent)
376 self.update_all(parent)
378 def split_qname(self, node, name):
379 if name == 'xmlns':
380 namespaceURI = XMLNS_NAMESPACE
381 localName = name
382 elif ':' in name:
383 prefix, localName = string.split(name, ':')
384 namespaceURI = self.prefix_to_namespace(node, prefix)
385 else:
386 namespaceURI = None
387 localName = name
388 return namespaceURI, localName
390 def set_attrib(self, node, name, value, with_update = 1):
391 """Set an attribute's value. If value is None, remove the attribute.
392 Returns the new attribute node, or None if removing."""
393 namespaceURI, localName = self.split_qname(node, name)
395 if node.hasAttributeNS(namespaceURI, localName):
396 old = node.getAttributeNS(namespaceURI, localName)
397 else:
398 old = None
399 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
400 if value != None:
401 node.setAttributeNS(namespaceURI, name, value)
402 else:
403 node.removeAttributeNS(namespaceURI, localName)
405 self.add_undo(lambda: self.set_attrib(node, name, old))
407 if with_update:
408 self.update_all(node)
409 if value != None:
410 if localName == 'xmlns':
411 localName = None
412 return node.attributes[(namespaceURI, localName)]
414 def prefix_to_namespace(self, node, prefix):
415 "Use the xmlns attributes to workout the namespace."
416 nss = GetAllNs(node)
417 if nss.has_key(prefix):
418 return nss[prefix] or None
419 if prefix:
420 if prefix == 'xmlns':
421 return XMLNS_NAMESPACE
422 raise Exception("No such namespace prefix '%s'" % prefix)
423 else:
424 return None
426 def get_base_uri(self, node):
427 """Go up through the parents looking for a uri attribute.
428 If there isn't one, use the document's URI."""
429 while node:
430 if node.nodeType == Node.DOCUMENT_NODE:
431 return self.uri
432 if node.hasAttributeNS(None, 'uri'):
433 return node.getAttributeNS(None, 'uri')
434 node = node.parentNode
435 return None