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
18 def get_xslt_source(doc
, dome_data
):
19 print "get_xslt_source", dome_data
20 src
= doc
.createElementNS(None, 'Source')
22 src
.appendChild(support
.import_with_ns(doc
, dome_data
.documentElement
))
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'
30 self
.namespaces
= Namespaces
.Namespaces()
33 from Ft
.Xml
.InputSource
import InputSourceFactory
34 isrc
= InputSourceFactory()
35 dome_data
= nonvalParse(isrc
.fromUri(dome_data
))
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
))
50 doc
= implementation
.createDocument(None, 'root', None)
51 root
= doc
.documentElement
53 self
.root_program
= None
56 from Program
import Program
, load_dome_program
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
:
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'):
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
)
84 self
.root_program
= root_program
86 if not self
.root_program
:
87 self
.root_program
= Program('Root')
90 self
.doc
= implementation
.createDocument(None, 'root', None)
92 node
= self
.import_with_ns(data_to_load
)
93 self
.doc
.replaceChild(node
, self
.doc
.documentElement
)
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."""
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
)
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
))
119 new
= ns_clone(node
, ns_clone
)
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.
129 # Each series of suboperations in an undo stack which are part of a single
130 # user op will have the same number...
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
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
)
149 self
.locks
[node
] = l
- 1
151 del self
.locks
[node
] # Or get a memory leak...
152 if node
== self
.doc
.documentElement
:
154 self
.unlock(node
.parentNode
)
157 raise Exception('unlock(%s): Node not locked!' % node
)
159 self
.unlock(node
.parentNode
)
161 def get_locks(self
, node
):
163 return self
.locks
[node
]
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
)
175 m
.replace_node(root
, copy
)
180 "Increment the user_op counter. Undo will undo every operation between"
185 "Return the true root node (not a view root)"
186 return self
.doc
.documentElement
188 def add_view(self
, view
):
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
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."
208 def update_replace(self
, old
, new
):
209 "Called when 'old' is replaced by 'new'."
211 v
.update_replace(old
, new
)
213 def strip_space(self
, node
= None):
215 node
= self
.doc
.documentElement
217 if node
.nodeType
== Node
.TEXT_NODE
:
218 #node.data = node.data.strip()
220 # node.parentNode.removeChild(node)
221 if not node
.data
.strip():
222 node
.parentNode
.removeChild(node
)
224 for k
in node
.childNodes
[:]:
230 def normalise(self
, node
):
231 old
= node
.cloneNode(1)
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
,
239 new
= self
.doc
.createElementNS(None, node
.data
.strip())
240 self
.replace_node(node
, 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
)
249 new
= self
.doc
.createTextNode(node
.data
)
250 self
.replace_node(node
, new
)
253 def convert_to_comment(self
, node
):
254 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
256 new
= self
.doc
.createComment(node
.data
)
257 self
.replace_node(node
, 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
273 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
274 for k
in node
.childNodes
:
275 new
.appendChild(clone(k
, clone
))
277 new
= ns_clone(node
, ns_clone
)
278 self
.replace_node(node
, 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
)
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
)
310 if __main__
.no_gui_mode
:
311 def add_undo(self
, fn
):
314 def add_undo(self
, fn
):
315 self
.undo_stack
.append((self
.user_op
, fn
))
316 if not self
.doing_undo
:
319 def set_data(self
, node
, 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
)
342 self
.delete_internal(k
)
343 self
.delete_internal(node
)
345 self
.insert_before_interal(next
, k
, parent
)
346 self
.update_all(parent
)
348 def delete_nodes(self
, nodes
):
349 #print "Deleting", nodes
351 if self
.get_locks(n
):
352 raise Exception('Attempt to delete locked node %s' % n
)
355 self
.delete_internal(n
)
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
]))
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
)
385 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
386 self
.redo_stack
[-1][1]()
387 self
.redo_stack
.pop()
389 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
393 if not self
.redo_stack
:
394 raise Exception('Nothing to redo')
396 uop
= self
.redo_stack
[-1][0]
399 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
400 self
.redo_stack
[-1][1]()
401 self
.redo_stack
.pop()
405 def insert(self
, node
, new
, index
= 0):
406 if len(node
.childNodes
) > index
:
407 self
.insert_before(node
.childNodes
[index
], new
)
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."
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
)
423 self
.insert_before_interal(node
, new
, parent
)
424 self
.update_all(parent
)
426 def split_qname(self
, node
, name
):
428 namespaceURI
= XMLNS_NAMESPACE
431 prefix
, localName
= name
.split(':')
432 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
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
)
447 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
449 node
.setAttributeNS(namespaceURI
, name
, value
)
451 node
.removeAttributeNS(namespaceURI
, localName
)
453 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
456 self
.update_all(node
)
458 if localName
== 'xmlns':
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."
465 return self
.namespaces
.uri
[prefix
]
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."
472 if nss
.has_key(prefix
):
473 return nss
[prefix
] or None
475 if prefix
== 'xmlns':
476 return XMLNS_NAMESPACE
477 raise Exception("No such namespace prefix '%s'" % prefix
)
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."""
485 if node
.nodeType
== Node
.DOCUMENT_NODE
:
487 if node
.hasAttributeNS(None, 'uri'):
488 return node
.getAttributeNS(None, 'uri')
489 node
= node
.parentNode
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
)