Loading files now adds the namespaces.
[dom-editor.git] / Dome / Model.py
bloba194a15dca45c6edff12aef1fb95520acb77553b
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 support
16 from Beep import Beep
18 def get_xslt_source(doc, dome_data):
19 print "get_xslt_source", dome_data
20 src = doc.createElementNS(None, 'Source')
21 if file:
22 src.appendChild(support.import_with_ns(doc, dome_data.documentElement))
23 return src
25 class Model:
26 def __init__(self, path, root_program = None, dome_data = None, do_load = 1):
27 "If root_program is given, then no data is loaded (used for lock_and_copy)."
28 self.uri = 'Prog.dome'
29 import Namespaces
30 self.namespaces = Namespaces.Namespaces()
32 if dome_data:
33 from Ft.Xml.InputSource import InputSourceFactory
34 isrc = InputSourceFactory()
35 dome_data = nonvalParse(isrc.fromUri(dome_data))
37 self.clear_undo()
39 doc = None
40 if path:
41 if path != '-':
42 self.uri = path
43 if do_load and (path.endswith('.html') or path.endswith('.htm')):
44 doc = self.load_html(path)
45 if not doc and not root_program:
46 from Ft.Xml.InputSource import InputSourceFactory
47 isrc = InputSourceFactory()
48 doc = nonvalParse(isrc.fromUri(path))
49 if not doc:
50 doc = implementation.createDocument(None, 'root', None)
51 root = doc.documentElement
53 self.root_program = None
54 data_to_load = None
56 from Program import Program, load_dome_program
57 import constants
58 if root.namespaceURI == constants.DOME_NS and root.localName == 'dome':
59 for x in root.childNodes:
60 if x.namespaceURI == constants.DOME_NS:
61 if x.localName == 'dome-program':
62 self.root_program = load_dome_program(x)
63 elif x.localName == 'dome-data':
64 for y in x.childNodes:
65 if y.nodeType == Node.ELEMENT_NODE:
66 data_to_load = y
67 if dome_data:
68 data_to_load = dome_data.documentElement
69 elif (root.namespaceURI == constants.XSLT_NS and
70 root.localName in ['stylesheet', 'transform']) or \
71 root.hasAttributeNS(constants.XSLT_NS, 'version'):
72 import xslt
73 self.root_program = xslt.import_sheet(doc)
74 x = implementation.createDocument(None, 'xslt', None)
75 data_to_load = x.documentElement
76 src = get_xslt_source(x, dome_data)
77 data_to_load.appendChild(x.createElementNS(None, 'Result'))
78 data_to_load.appendChild(src)
79 dome_data = None
80 else:
81 data_to_load = root
83 if root_program:
84 self.root_program = root_program
85 else:
86 if not self.root_program:
87 self.root_program = Program('Root')
89 if data_to_load:
90 self.doc = implementation.createDocument(None, 'root', None)
91 if not root_program:
92 node = self.import_with_ns(data_to_load)
93 self.doc.replaceChild(node, self.doc.documentElement)
94 self.strip_space()
96 self.views = [] # Notified when something changes
97 self.locks = {} # Node -> number of locks
99 def import_with_ns(self, node):
100 """Return a copy of node for this model. All namespaces used in the subtree
101 will have been added to the global namespaces list. Prefixes will have been changed
102 as required to avoid conflicts."""
103 doc = self.doc
104 def ns_clone(node, clone):
105 if node.nodeType != Node.ELEMENT_NODE:
106 return doc.importNode(node, 1)
107 if node.namespaceURI:
108 prefix = self.namespaces.ensure_ns(node.prefix, node.namespaceURI)
109 new = doc.createElementNS(node.namespaceURI,
110 prefix + ':' + node.localName)
111 else:
112 new = doc.createElementNS(None, node.localName)
113 for a in node.attributes.values():
114 if a.namespaceURI == XMLNS_NAMESPACE: continue
115 new.setAttributeNS(a.namespaceURI, a.name, a.value)
116 for k in node.childNodes:
117 new.appendChild(clone(k, clone))
118 return new
119 new = ns_clone(node, ns_clone)
120 return new
122 def clear_undo(self):
123 # Pop an (op_number, function) off one of these and call the function to
124 # move forwards or backwards in the undo history.
125 self.undo_stack = []
126 self.redo_stack = []
127 self.doing_undo = 0
129 # Each series of suboperations in an undo stack which are part of a single
130 # user op will have the same number...
131 self.user_op = 1
133 #import gc
134 #print "GC freed:", gc.collect()
135 #print "Garbage", gc.garbage
137 def lock(self, node):
138 """Prevent removal of this node (or any ancestor)."""
139 #print "Locking", node.nodeName
140 self.locks[node] = self.get_locks(node) + 1
141 if node.parentNode:
142 self.lock(node.parentNode)
144 def unlock(self, node):
145 """Reverse the effect of lock(). Must call unlock the same number
146 of times as lock to fully unlock the node."""
147 l = self.get_locks(node)
148 if l > 1:
149 self.locks[node] = l - 1
150 elif l == 1:
151 del self.locks[node] # Or get a memory leak...
152 if node == self.doc.documentElement:
153 if node.parentNode:
154 self.unlock(node.parentNode)
155 return
156 else:
157 raise Exception('unlock(%s): Node not locked!' % node)
158 if node.parentNode:
159 self.unlock(node.parentNode)
161 def get_locks(self, node):
162 try:
163 return self.locks[node]
164 except KeyError:
165 return 0
167 def lock_and_copy(self, node):
168 """Locks 'node' in the current model and returns a new model
169 with a copy of the subtree."""
170 if self.get_locks(node):
171 raise Exception("Can't enter locked node!")
172 m = Model(self.get_base_uri(node), root_program = self.root_program, do_load = 0)
173 copy = support.import_with_ns(m.doc, node)
174 root = m.get_root()
175 m.replace_node(root, copy)
176 self.lock(node)
177 return m
179 def mark(self):
180 "Increment the user_op counter. Undo will undo every operation between"
181 "two marks."
182 self.user_op += 1
184 def get_root(self):
185 "Return the true root node (not a view root)"
186 return self.doc.documentElement
188 def add_view(self, view):
189 "'view' provides:"
190 "'update_all(subtree) - called when a major change occurs."
191 #print "New view:", view
192 self.views.append(view)
194 def remove_view(self, view):
195 #print "Removing view", view
196 self.views.remove(view)
197 #print "Now:", self.views
198 if not self.views:
199 self.clear_undo()
201 def update_all(self, node):
202 "Called when 'node' has been updated."
203 "'node' is still in the document, so deleting or replacing"
204 "a node calls this on the parent."
205 for v in self.views:
206 v.update_all(node)
208 def update_replace(self, old, new):
209 "Called when 'old' is replaced by 'new'."
210 for v in self.views:
211 v.update_replace(old, new)
213 def strip_space(self, node = None):
214 if not node:
215 node = self.doc.documentElement
216 def ss(node):
217 if node.nodeType == Node.TEXT_NODE:
218 #node.data = node.data.strip()
219 #if node.data == '':
220 # node.parentNode.removeChild(node)
221 if not node.data.strip():
222 node.parentNode.removeChild(node)
223 else:
224 for k in node.childNodes[:]:
225 ss(k)
226 ss(node)
228 # Changes
230 def normalise(self, node):
231 old = node.cloneNode(1)
232 node.normalize()
233 self.add_undo(lambda: self.replace_node(node, old))
234 self.update_all(node)
236 def convert_to_element(self, node):
237 assert node.nodeType in (Node.COMMENT_NODE, Node.PROCESSING_INSTRUCTION_NODE,
238 Node.TEXT_NODE)
239 new = self.doc.createElementNS(None, node.data.strip())
240 self.replace_node(node, new)
241 return new
243 def convert_to_text(self, node):
244 assert node.nodeType in (Node.COMMENT_NODE, Node.PROCESSING_INSTRUCTION_NODE,
245 Node.TEXT_NODE, Node.ELEMENT_NODE)
246 if node.nodeType == Node.ELEMENT_NODE:
247 new = self.doc.createTextNode(node.localName)
248 else:
249 new = self.doc.createTextNode(node.data)
250 self.replace_node(node, new)
251 return new
253 def convert_to_comment(self, node):
254 assert node.nodeType in (Node.COMMENT_NODE, Node.PROCESSING_INSTRUCTION_NODE,
255 Node.TEXT_NODE)
256 new = self.doc.createComment(node.data)
257 self.replace_node(node, new)
258 return new
260 def remove_ns(self, node):
261 nss = GetAllNs(node.parentNode)
262 dns = nss.get(None, None)
263 create = node.ownerDocument.createElementNS
264 # clone is an argument to fix a wierd gc bug in python2.2
265 def ns_clone(node, clone):
266 if node.nodeType != Node.ELEMENT_NODE:
267 return node.cloneNode(1)
268 new = create(dns, node.nodeName)
269 for a in node.attributes.values():
270 if a.localName == 'xmlns' and a.prefix is None:
271 print "Removing xmlns attrib on", node
272 continue
273 new.setAttributeNS(a.namespaceURI, a.name, a.value)
274 for k in node.childNodes:
275 new.appendChild(clone(k, clone))
276 return new
277 new = ns_clone(node, ns_clone)
278 self.replace_node(node, new)
279 return new
281 def set_name(self, node, namespace, name):
282 if self.get_locks(node):
283 raise Exception('Attempt to set name on locked node %s' % node)
285 new = node.ownerDocument.createElementNS(namespace, name)
286 self.replace_shallow(node, new)
287 return new
289 def replace_shallow(self, old, new):
290 """Replace old with new, keeping the old children."""
291 assert not new.childNodes
292 assert not new.parentNode
294 old_name = old.nodeName
295 old_ns = old.namespaceURI
297 kids = old.childNodes[:]
298 attrs = old.attributes.values()
299 parent = old.parentNode
300 [ old.removeChild(k) for k in kids ]
301 parent.replaceChild(new, old)
302 [ new.appendChild(k) for k in kids ]
303 [ new.setAttributeNS(a.namespaceURI, a.name, a.value) for a in attrs ]
305 self.add_undo(lambda: self.replace_shallow(new, old))
307 self.update_replace(old, new)
309 import __main__
310 if __main__.no_gui_mode:
311 def add_undo(self, fn):
312 pass
313 else:
314 def add_undo(self, fn):
315 self.undo_stack.append((self.user_op, fn))
316 if not self.doing_undo:
317 self.redo_stack = []
319 def set_data(self, node, data):
320 old_data = node.data
321 node.data = data
322 self.add_undo(lambda: self.set_data(node, old_data))
323 self.update_all(node)
325 def replace_node(self, old, new):
326 if self.get_locks(old):
327 raise Exception('Attempt to replace locked node %s' % old)
328 old.parentNode.replaceChild(new, old)
329 self.add_undo(lambda: self.replace_node(new, old))
331 self.update_replace(old, new)
333 def delete_shallow(self, node):
334 """Replace node with its contents"""
335 kids = node.childNodes[:]
336 next = node.nextSibling
337 parent = node.parentNode
338 for n in kids + [node]:
339 if self.get_locks(n):
340 raise Exception('Attempt to move/delete locked node %s' % n)
341 for k in kids:
342 self.delete_internal(k)
343 self.delete_internal(node)
344 for k in kids:
345 self.insert_before_interal(next, k, parent)
346 self.update_all(parent)
348 def delete_nodes(self, nodes):
349 #print "Deleting", nodes
350 for n in nodes:
351 if self.get_locks(n):
352 raise Exception('Attempt to delete locked node %s' % n)
353 for n in nodes:
354 p = n.parentNode
355 self.delete_internal(n)
356 self.update_all(p)
358 def delete_internal(self, node):
359 "Doesn't update display."
360 next = node.nextSibling
361 parent = node.parentNode
362 parent.removeChild(node)
363 self.add_undo(lambda: self.insert_before(next, node, parent = parent))
365 def insert_before_interal(self, node, new, parent):
366 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
367 "of parent's children."
368 assert new.nodeType != Node.DOCUMENT_FRAGMENT_NODE
369 assert parent.nodeType == Node.ELEMENT_NODE
370 parent.insertBefore(new, node)
371 self.add_undo(lambda: self.delete_nodes([new]))
373 def undo(self):
374 if not self.undo_stack:
375 raise Exception('Nothing to undo')
377 assert not self.doing_undo
379 uop = self.undo_stack[-1][0]
381 # Swap stacks so that the undo actions will populate the redo stack...
382 (self.undo_stack, self.redo_stack) = (self.redo_stack, self.undo_stack)
383 self.doing_undo = 1
384 try:
385 while self.redo_stack and self.redo_stack[-1][0] == uop:
386 self.redo_stack[-1][1]()
387 self.redo_stack.pop()
388 finally:
389 (self.undo_stack, self.redo_stack) = (self.redo_stack, self.undo_stack)
390 self.doing_undo = 0
392 def redo(self):
393 if not self.redo_stack:
394 raise Exception('Nothing to redo')
396 uop = self.redo_stack[-1][0]
397 self.doing_undo = 1
398 try:
399 while self.redo_stack and self.redo_stack[-1][0] == uop:
400 self.redo_stack[-1][1]()
401 self.redo_stack.pop()
402 finally:
403 self.doing_undo = 0
405 def insert(self, node, new, index = 0):
406 if len(node.childNodes) > index:
407 self.insert_before(node.childNodes[index], new)
408 else:
409 self.insert_before(None, new, parent = node)
411 def insert_after(self, node, new):
412 self.insert_before(node.nextSibling, new, parent = node.parentNode)
414 def insert_before(self, node, new, parent = None):
415 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
416 "of parent's children."
417 if not parent:
418 parent = node.parentNode
419 if new.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
420 for n in new.childNodes[:]:
421 self.insert_before_interal(node, n, parent)
422 else:
423 self.insert_before_interal(node, new, parent)
424 self.update_all(parent)
426 def split_qname(self, node, name):
427 if name == 'xmlns':
428 namespaceURI = XMLNS_NAMESPACE
429 localName = name
430 elif ':' in name:
431 prefix, localName = name.split(':')
432 namespaceURI = self.prefix_to_namespace(node, prefix)
433 else:
434 namespaceURI = None
435 localName = name
436 return namespaceURI, localName
438 def set_attrib(self, node, name, value, with_update = 1):
439 """Set an attribute's value. If value is None, remove the attribute.
440 Returns the new attribute node, or None if removing."""
441 namespaceURI, localName = self.split_qname(node, name)
443 if node.hasAttributeNS(namespaceURI, localName):
444 old = node.getAttributeNS(namespaceURI, localName)
445 else:
446 old = None
447 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
448 if value != None:
449 node.setAttributeNS(namespaceURI, name, value)
450 else:
451 node.removeAttributeNS(namespaceURI, localName)
453 self.add_undo(lambda: self.set_attrib(node, name, old))
455 if with_update:
456 self.update_all(node)
457 if value != None:
458 if localName == 'xmlns':
459 localName = None
460 return node.attributes[(namespaceURI, localName)]
462 def prefix_to_namespace(self, node, prefix):
463 "Using attributes for namespaces was too confusing. Keep a global list instead."
464 try:
465 return self.namespaces.uri[prefix]
466 except KeyError:
467 raise Exception("Namespace '%s' is not defined. Choose "
468 "View->Show namespaces from the popup menu to set it." % prefix)
470 "Use the xmlns attributes to workout the namespace."
471 nss = GetAllNs(node)
472 if nss.has_key(prefix):
473 return nss[prefix] or None
474 if prefix:
475 if prefix == 'xmlns':
476 return XMLNS_NAMESPACE
477 raise Exception("No such namespace prefix '%s'" % prefix)
478 else:
479 return None
481 def get_base_uri(self, node):
482 """Go up through the parents looking for a uri attribute.
483 If there isn't one, use the document's URI."""
484 while node:
485 if node.nodeType == Node.DOCUMENT_NODE:
486 return self.uri
487 if node.hasAttributeNS(None, 'uri'):
488 return node.getAttributeNS(None, 'uri')
489 node = node.parentNode
490 return None
492 def load_html(self, path):
493 """Load this HTML file and return the new document."""
494 data = file(path).read()
495 data = support.to_html_doc(data)
496 doc = support.parse_data(data, path)
497 self.strip_space(doc)
498 return doc