1 from __future__
import nested_scopes
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
19 def __init__(self
, path
, root_program
= None, dome_data
= None, do_load
= 1):
20 "If root_program is given, then no data is loaded (used for lock_and_copy)."
21 self
.uri
= 'Prog.dome'
23 self
.namespaces
= Namespaces
.Namespaces()
26 from Ft
.Xml
.InputSource
import InputSourceFactory
27 isrc
= InputSourceFactory()
28 dome_data
= nonvalParse(isrc
.fromUri(dome_data
))
36 if do_load
and (path
.endswith('.html') or path
.endswith('.htm')):
37 doc
= self
.load_html(path
)
38 if not doc
and not root_program
:
39 from Ft
.Xml
.InputSource
import InputSourceFactory
40 isrc
= InputSourceFactory()
41 doc
= nonvalParse(isrc
.fromUri(path
))
43 doc
= implementation
.createDocument(None, 'root', None)
44 root
= doc
.documentElement
46 self
.root_program
= None
49 from Program
import Program
, load_dome_program
51 if root
.namespaceURI
== constants
.DOME_NS
and root
.localName
== 'dome':
52 for x
in root
.childNodes
:
53 if x
.namespaceURI
== constants
.DOME_NS
:
54 if x
.localName
== 'dome-program':
55 self
.root_program
= load_dome_program(x
,
57 elif x
.localName
== 'dome-data':
58 for y
in x
.childNodes
:
59 if y
.nodeType
== Node
.ELEMENT_NODE
:
62 data_to_load
= dome_data
.documentElement
63 elif (root
.namespaceURI
== constants
.XSLT_NS
and
64 root
.localName
in ['stylesheet', 'transform']) or \
65 root
.hasAttributeNS(constants
.XSLT_NS
, 'version'):
67 self
.root_program
= xslt
.import_sheet(doc
)
68 x
= implementation
.createDocument(None, 'xslt', None)
69 data_to_load
= x
.documentElement
70 src
= doc
.createElementNS(None, 'Source')
72 # TODO: import_with_ns?
73 src
.appendChild(self
.import_with_ns(doc
,
74 dome_data
.documentElement
))
75 data_to_load
.appendChild(x
.createElementNS(None, 'Result'))
76 data_to_load
.appendChild(src
)
82 self
.root_program
= root_program
84 if not self
.root_program
:
85 self
.root_program
= Program('Root')
88 self
.doc
= implementation
.createDocument(None, 'root', None)
90 node
= self
.import_with_ns(data_to_load
)
91 self
.doc
.replaceChild(node
, self
.doc
.documentElement
)
94 self
.views
= [] # Notified when something changes
95 self
.locks
= {} # Node -> number of locks
97 def import_with_ns(self
, node
):
98 """Return a copy of node for this model. All namespaces used in the subtree
99 will have been added to the global namespaces list. Prefixes will have been changed
100 as required to avoid conflicts."""
102 def ns_clone(node
, clone
):
103 if node
.nodeType
!= Node
.ELEMENT_NODE
:
104 return doc
.importNode(node
, 1)
105 if node
.namespaceURI
:
106 prefix
= self
.namespaces
.ensure_ns(node
.prefix
, node
.namespaceURI
)
107 new
= doc
.createElementNS(node
.namespaceURI
,
108 prefix
+ ':' + node
.localName
)
110 new
= doc
.createElementNS(None, node
.localName
)
111 for a
in node
.attributes
.values():
112 if a
.namespaceURI
== XMLNS_NAMESPACE
: continue
113 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
114 for k
in node
.childNodes
:
115 new
.appendChild(clone(k
, clone
))
117 new
= ns_clone(node
, ns_clone
)
120 def clear_undo(self
):
121 # Pop an (op_number, function) off one of these and call the function to
122 # move forwards or backwards in the undo history.
127 # Each series of suboperations in an undo stack which are part of a single
128 # user op will have the same number...
132 #print "GC freed:", gc.collect()
133 #print "Garbage", gc.garbage
135 def lock(self
, node
):
136 """Prevent removal of this node (or any ancestor)."""
137 #print "Locking", node.nodeName
138 self
.locks
[node
] = self
.get_locks(node
) + 1
140 self
.lock(node
.parentNode
)
142 def unlock(self
, node
):
143 """Reverse the effect of lock(). Must call unlock the same number
144 of times as lock to fully unlock the node."""
145 l
= self
.get_locks(node
)
147 self
.locks
[node
] = l
- 1
149 del self
.locks
[node
] # Or get a memory leak...
150 if node
== self
.doc
.documentElement
:
152 self
.unlock(node
.parentNode
)
155 raise Exception('unlock(%s): Node not locked!' % node
)
157 self
.unlock(node
.parentNode
)
159 def get_locks(self
, node
):
161 return self
.locks
[node
]
165 def lock_and_copy(self
, node
):
166 """Locks 'node' in the current model and returns a new model
167 with a copy of the subtree."""
168 if self
.get_locks(node
):
169 raise Exception("Can't enter locked node!")
170 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
171 copy
= self
.doc
.importNode(node
, 1)
173 m
.replace_node(root
, copy
)
178 "Increment the user_op counter. Undo will undo every operation between"
183 "Return the true root node (not a view root)"
184 return self
.doc
.documentElement
186 def add_view(self
, view
):
188 "'update_all(subtree) - called when a major change occurs."
189 #print "New view:", view
190 self
.views
.append(view
)
192 def remove_view(self
, view
):
193 #print "Removing view", view
194 self
.views
.remove(view
)
195 #print "Now:", self.views
199 def update_all(self
, node
):
200 "Called when 'node' has been updated."
201 "'node' is still in the document, so deleting or replacing"
202 "a node calls this on the parent."
206 def update_replace(self
, old
, new
):
207 "Called when 'old' is replaced by 'new'."
209 v
.update_replace(old
, new
)
211 def strip_space(self
, node
= None):
213 node
= self
.doc
.documentElement
215 if node
.nodeType
== Node
.TEXT_NODE
:
216 #node.data = node.data.strip()
218 # node.parentNode.removeChild(node)
219 if not node
.data
.strip():
220 node
.parentNode
.removeChild(node
)
222 for k
in node
.childNodes
[:]:
228 def normalise(self
, node
):
229 old
= node
.cloneNode(1)
231 self
.add_undo(lambda: self
.replace_node(node
, old
))
232 self
.update_all(node
)
234 def convert_to_element(self
, node
):
235 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
237 new
= self
.doc
.createElementNS(None, node
.data
.strip())
238 self
.replace_node(node
, new
)
241 def convert_to_text(self
, node
):
242 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
243 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
244 if node
.nodeType
== Node
.ELEMENT_NODE
:
245 new
= self
.doc
.createTextNode(node
.localName
)
247 new
= self
.doc
.createTextNode(node
.data
)
248 self
.replace_node(node
, new
)
251 def convert_to_comment(self
, node
):
252 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
254 new
= self
.doc
.createComment(node
.data
)
255 self
.replace_node(node
, new
)
258 def remove_ns(self
, node
):
259 print "remove_ns: Shouldn't need this now!"
262 nss
= GetAllNs(node
.parentNode
)
263 dns
= nss
.get(None, None)
264 create
= node
.ownerDocument
.createElementNS
265 # clone is an argument to fix a wierd gc bug in python2.2
266 def ns_clone(node
, clone
):
267 if node
.nodeType
!= Node
.ELEMENT_NODE
:
268 return node
.cloneNode(1)
269 new
= create(dns
, node
.nodeName
)
270 for a
in node
.attributes
.values():
271 if a
.localName
== 'xmlns' and a
.prefix
is None:
272 print "Removing xmlns attrib on", node
274 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
275 for k
in node
.childNodes
:
276 new
.appendChild(clone(k
, clone
))
278 new
= ns_clone(node
, ns_clone
)
279 self
.replace_node(node
, new
)
282 def set_name(self
, node
, namespace
, name
):
283 if self
.get_locks(node
):
284 raise Exception('Attempt to set name on locked node %s' % node
)
286 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
287 self
.replace_shallow(node
, new
)
290 def replace_shallow(self
, old
, new
):
291 """Replace old with new, keeping the old children."""
292 assert not new
.childNodes
293 assert not new
.parentNode
295 old_name
= old
.nodeName
296 old_ns
= old
.namespaceURI
298 kids
= old
.childNodes
[:]
299 attrs
= old
.attributes
.values()
300 parent
= old
.parentNode
301 [ old
.removeChild(k
) for k
in kids
]
302 parent
.replaceChild(new
, old
)
303 [ new
.appendChild(k
) for k
in kids
]
304 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
306 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
308 self
.update_replace(old
, new
)
311 if __main__
.no_gui_mode
:
312 def add_undo(self
, fn
):
315 def add_undo(self
, fn
):
316 self
.undo_stack
.append((self
.user_op
, fn
))
317 if not self
.doing_undo
:
320 def set_data(self
, node
, data
):
323 self
.add_undo(lambda: self
.set_data(node
, old_data
))
324 self
.update_all(node
)
326 def replace_node(self
, old
, new
):
327 if self
.get_locks(old
):
328 raise Exception('Attempt to replace locked node %s' % old
)
329 old
.parentNode
.replaceChild(new
, old
)
330 self
.add_undo(lambda: self
.replace_node(new
, old
))
332 self
.update_replace(old
, new
)
334 def delete_shallow(self
, node
):
335 """Replace node with its contents"""
336 kids
= node
.childNodes
[:]
337 next
= node
.nextSibling
338 parent
= node
.parentNode
339 for n
in kids
+ [node
]:
340 if self
.get_locks(n
):
341 raise Exception('Attempt to move/delete locked node %s' % n
)
343 self
.delete_internal(k
)
344 self
.delete_internal(node
)
346 self
.insert_before_interal(next
, k
, parent
)
347 self
.update_all(parent
)
349 def delete_nodes(self
, nodes
):
350 #print "Deleting", nodes
352 if self
.get_locks(n
):
353 raise Exception('Attempt to delete locked node %s' % n
)
356 self
.delete_internal(n
)
359 def delete_internal(self
, node
):
360 "Doesn't update display."
361 next
= node
.nextSibling
362 parent
= node
.parentNode
363 parent
.removeChild(node
)
364 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
366 def insert_before_interal(self
, node
, new
, parent
):
367 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
368 "of parent's children."
369 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
370 assert parent
.nodeType
== Node
.ELEMENT_NODE
371 parent
.insertBefore(new
, node
)
372 self
.add_undo(lambda: self
.delete_nodes([new
]))
375 if not self
.undo_stack
:
376 raise Exception('Nothing to undo')
378 assert not self
.doing_undo
380 uop
= self
.undo_stack
[-1][0]
382 # Swap stacks so that the undo actions will populate the redo stack...
383 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
386 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
387 self
.redo_stack
[-1][1]()
388 self
.redo_stack
.pop()
390 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
394 if not self
.redo_stack
:
395 raise Exception('Nothing to redo')
397 uop
= self
.redo_stack
[-1][0]
400 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
401 self
.redo_stack
[-1][1]()
402 self
.redo_stack
.pop()
406 def insert(self
, node
, new
, index
= 0):
407 if len(node
.childNodes
) > index
:
408 self
.insert_before(node
.childNodes
[index
], new
)
410 self
.insert_before(None, new
, parent
= node
)
412 def insert_after(self
, node
, new
):
413 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
415 def insert_before(self
, node
, new
, parent
= None):
416 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
417 "of parent's children."
419 parent
= node
.parentNode
420 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
421 for n
in new
.childNodes
[:]:
422 self
.insert_before_interal(node
, n
, parent
)
424 self
.insert_before_interal(node
, new
, parent
)
425 self
.update_all(parent
)
427 def split_qname(self
, node
, name
):
429 namespaceURI
= XMLNS_NAMESPACE
432 prefix
, localName
= name
.split(':')
433 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
437 return namespaceURI
, localName
439 def set_attrib(self
, node
, name
, value
, with_update
= 1):
440 """Set an attribute's value. If value is None, remove the attribute.
441 Returns the new attribute node, or None if removing."""
442 namespaceURI
, localName
= self
.split_qname(node
, name
)
444 if node
.hasAttributeNS(namespaceURI
, localName
):
445 old
= node
.getAttributeNS(namespaceURI
, localName
)
448 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
450 node
.setAttributeNS(namespaceURI
, name
, value
)
452 node
.removeAttributeNS(namespaceURI
, localName
)
454 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
457 self
.update_all(node
)
459 if localName
== 'xmlns':
461 return node
.attributes
[(namespaceURI
, localName
)]
463 def prefix_to_namespace(self
, node
, prefix
):
464 "Using attributes for namespaces was too confusing. Keep a global list instead."
465 if prefix
is None: return None
467 return self
.namespaces
.uri
[prefix
]
469 raise Exception("Namespace '%s' is not defined. Choose "
470 "View->Show namespaces from the popup menu to set it." % prefix
)
472 "Use the xmlns attributes to workout the namespace."
474 if nss
.has_key(prefix
):
475 return nss
[prefix
] or None
477 if prefix
== 'xmlns':
478 return XMLNS_NAMESPACE
479 raise Exception("No such namespace prefix '%s'" % prefix
)
483 def get_base_uri(self
, node
):
484 """Go up through the parents looking for a uri attribute.
485 If there isn't one, use the document's URI."""
487 if node
.nodeType
== Node
.DOCUMENT_NODE
:
489 if node
.hasAttributeNS(None, 'uri'):
490 return node
.getAttributeNS(None, 'uri')
491 node
= node
.parentNode
494 def load_html(self
, path
):
495 """Load this HTML file and return the new document."""
496 data
= file(path
).read()
497 data
= support
.to_html_doc(data
)
498 doc
= support
.parse_data(data
, path
)
499 self
.strip_space(doc
)