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
20 def __init__(self
, path
, root_program
= None, dome_data
= None, do_load
= 1):
21 "If root_program is given, then no data is loaded (used for lock_and_copy)."
22 self
.uri
= 'Prog.dome'
24 self
.namespaces
= Namespaces
.Namespaces()
27 from Ft
.Xml
.InputSource
import InputSourceFactory
28 isrc
= InputSourceFactory()
29 dome_data
= nonvalParse(isrc
.fromUri(dome_data
))
37 if do_load
and (path
.endswith('.html') or path
.endswith('.htm')):
38 doc
= self
.load_html(path
)
39 if not doc
and not root_program
:
40 from Ft
.Xml
.InputSource
import InputSourceFactory
41 isrc
= InputSourceFactory()
43 doc
= nonvalParse(isrc
.fromUri(path
))
46 rox
.report_exception()
48 doc
= implementation
.createDocument(None, 'root', None)
49 root
= doc
.documentElement
51 self
.root_program
= None
54 from Program
import Program
, load_dome_program
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
== 'namespaces':
60 elif x
.localName
== 'dome-program':
61 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 self
.doc
= implementation
.createDocument(None, 'xslt', None)
76 src
= self
.doc
.createElementNS(None, 'Source')
78 src
.appendChild(self
.import_with_ns(dome_data
.documentElement
))
79 self
.doc
.documentElement
.appendChild(src
)
80 self
.doc
.documentElement
.appendChild(self
.doc
.createElementNS(None, 'Result'))
88 self
.root_program
= root_program
90 if not self
.root_program
:
91 self
.root_program
= Program('Root')
94 self
.doc
= implementation
.createDocument(None, 'root', None)
96 node
= self
.import_with_ns(data_to_load
)
97 self
.doc
.replaceChild(node
, self
.doc
.documentElement
)
100 self
.views
= [] # Notified when something changes
101 self
.locks
= {} # Node -> number of locks
103 self
.hidden
= {} # Node -> Message
104 self
.hidden_code
= {} # XPath to generate Message, if any
106 def load_ns(self
, node
):
107 assert node
.localName
== 'namespaces'
108 assert node
.namespaceURI
== constants
.DOME_NS
109 for x
in node
.childNodes
:
110 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
111 if x
.localName
!= 'ns': continue
112 if x
.namespaceURI
!= constants
.DOME_NS
: continue
114 self
.namespaces
.ensure_ns(x
.getAttributeNS(None, 'prefix'),
115 x
.getAttributeNS(None, 'uri'))
117 def import_with_ns(self
, node
):
118 """Return a copy of node for this model. All namespaces used in the subtree
119 will have been added to the global namespaces list. Prefixes will have been changed
120 as required to avoid conflicts."""
122 def ns_clone(node
, clone
):
123 if node
.nodeType
!= Node
.ELEMENT_NODE
:
124 return doc
.importNode(node
, 1)
125 if node
.namespaceURI
:
126 prefix
= self
.namespaces
.ensure_ns(node
.prefix
, node
.namespaceURI
)
127 new
= doc
.createElementNS(node
.namespaceURI
,
128 prefix
+ ':' + node
.localName
)
130 new
= doc
.createElementNS(None, node
.localName
)
131 for a
in node
.attributes
.values():
132 if a
.namespaceURI
== XMLNS_NAMESPACE
: continue
133 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
134 for k
in node
.childNodes
:
135 new
.appendChild(clone(k
, clone
))
137 new
= ns_clone(node
, ns_clone
)
140 def clear_undo(self
):
141 # Pop an (op_number, function) off one of these and call the function to
142 # move forwards or backwards in the undo history.
147 # Each series of suboperations in an undo stack which are part of a single
148 # user op will have the same number...
152 #print "GC freed:", gc.collect()
153 #print "Garbage", gc.garbage
155 def lock(self
, node
):
156 """Prevent removal of this node (or any ancestor)."""
157 #print "Locking", node.nodeName
159 self
.locks
[node
] = self
.get_locks(node
) + 1
161 self
.lock(node
.parentNode
)
163 def unlock(self
, node
):
164 """Reverse the effect of lock(). Must call unlock the same number
165 of times as lock to fully unlock the node."""
166 l
= self
.get_locks(node
)
168 self
.locks
[node
] = l
- 1
170 del self
.locks
[node
] # Or get a memory leak...
171 if node
== self
.doc
.documentElement
:
173 self
.unlock(node
.parentNode
)
176 raise Exception('unlock(%s): Node not locked!' % node
)
178 self
.unlock(node
.parentNode
)
180 def get_locks(self
, node
):
182 return self
.locks
[node
]
186 def lock_and_copy(self
, node
):
187 """Locks 'node' in the current model and returns a new model
188 with a copy of the subtree."""
189 if self
.get_locks(node
):
190 raise Exception("Can't enter locked node!")
191 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
192 copy
= m
.import_with_ns(node
)
194 m
.replace_node(root
, copy
)
199 "Increment the user_op counter. Undo will undo every operation between"
204 "Return the true root node (not a view root)"
205 return self
.doc
.documentElement
207 def add_view(self
, view
):
209 "'update_all(subtree) - called when a major change occurs."
210 #print "New view:", view
211 self
.views
.append(view
)
213 def remove_view(self
, view
):
214 #print "Removing view", view
215 self
.views
.remove(view
)
216 #print "Now:", self.views
217 # XXX: clears undo on enter!
221 def update_all(self
, node
):
222 "Called when 'node' has been updated."
223 "'node' is still in the document, so deleting or replacing"
224 "a node calls this on the parent."
228 def update_replace(self
, old
, new
):
229 "Called when 'old' is replaced by 'new'."
231 v
.update_replace(old
, new
)
233 def strip_space(self
, node
= None):
235 node
= self
.doc
.documentElement
237 if node
.nodeType
== Node
.TEXT_NODE
:
238 #node.data = node.data.strip()
240 # node.parentNode.removeChild(node)
241 if not node
.data
.strip():
242 node
.parentNode
.removeChild(node
)
244 for k
in node
.childNodes
[:]:
250 def normalise(self
, node
):
251 old
= node
.cloneNode(1)
253 self
.add_undo(lambda: self
.replace_node(node
, old
))
254 self
.update_all(node
)
256 def convert_to_element(self
, node
):
257 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
259 new
= self
.doc
.createElementNS(None, node
.data
.strip())
260 self
.replace_node(node
, new
)
263 def convert_to_text(self
, node
):
264 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
265 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
266 if node
.nodeType
== Node
.ELEMENT_NODE
:
267 new
= self
.doc
.createTextNode(node
.localName
)
269 new
= self
.doc
.createTextNode(node
.data
)
270 self
.replace_node(node
, new
)
273 def convert_to_comment(self
, node
):
274 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
276 new
= self
.doc
.createComment(node
.data
)
277 self
.replace_node(node
, new
)
280 def remove_ns(self
, node
):
281 print "remove_ns: Shouldn't need this now!"
284 nss
= GetAllNs(node
.parentNode
)
285 dns
= nss
.get(None, None)
286 create
= node
.ownerDocument
.createElementNS
287 # clone is an argument to fix a wierd gc bug in python2.2
288 def ns_clone(node
, clone
):
289 if node
.nodeType
!= Node
.ELEMENT_NODE
:
290 return node
.cloneNode(1)
291 new
= create(dns
, node
.nodeName
)
292 for a
in node
.attributes
.values():
293 if a
.localName
== 'xmlns' and a
.prefix
is None:
294 print "Removing xmlns attrib on", node
296 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
297 for k
in node
.childNodes
:
298 new
.appendChild(clone(k
, clone
))
300 new
= ns_clone(node
, ns_clone
)
301 self
.replace_node(node
, new
)
304 def set_name(self
, node
, namespace
, name
):
305 if self
.get_locks(node
):
306 raise Exception('Attempt to set name on locked node %s' % node
)
308 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
309 self
.replace_shallow(node
, new
)
312 def replace_shallow(self
, old
, new
):
313 """Replace old with new, keeping the old children."""
314 assert not new
.childNodes
315 assert not new
.parentNode
317 old_name
= old
.nodeName
318 old_ns
= old
.namespaceURI
320 kids
= old
.childNodes
[:]
321 attrs
= old
.attributes
.values()
322 parent
= old
.parentNode
323 [ old
.removeChild(k
) for k
in kids
]
324 parent
.replaceChild(new
, old
)
325 [ new
.appendChild(k
) for k
in kids
]
326 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
328 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
330 self
.update_replace(old
, new
)
333 if __main__
.no_gui_mode
:
334 def add_undo(self
, fn
):
337 def add_undo(self
, fn
):
338 self
.undo_stack
.append((self
.user_op
, fn
))
339 if not self
.doing_undo
:
342 def set_data(self
, node
, data
):
345 self
.add_undo(lambda: self
.set_data(node
, old_data
))
346 self
.update_all(node
)
348 def replace_node(self
, old
, new
):
349 if self
.get_locks(old
):
350 raise Exception('Attempt to replace locked node %s' % old
)
351 old
.parentNode
.replaceChild(new
, old
)
352 self
.add_undo(lambda: self
.replace_node(new
, old
))
354 self
.update_replace(old
, new
)
356 def delete_shallow(self
, node
):
357 """Replace node with its contents"""
358 kids
= node
.childNodes
[:]
359 next
= node
.nextSibling
360 parent
= node
.parentNode
361 for n
in kids
+ [node
]:
362 if self
.get_locks(n
):
363 raise Exception('Attempt to move/delete locked node %s' % n
)
365 self
.delete_internal(k
)
366 self
.delete_internal(node
)
368 self
.insert_before_interal(next
, k
, parent
)
369 self
.update_all(parent
)
371 def delete_nodes(self
, nodes
):
372 #print "Deleting", nodes
374 if self
.get_locks(n
):
375 raise Exception('Attempt to delete locked node %s' % n
)
378 self
.delete_internal(n
)
381 def delete_internal(self
, node
):
382 "Doesn't update display."
383 next
= node
.nextSibling
384 parent
= node
.parentNode
385 parent
.removeChild(node
)
386 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
388 def insert_before_interal(self
, node
, new
, parent
):
389 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
390 "of parent's children."
391 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
392 #assert parent.nodeType == Node.ELEMENT_NODE
393 parent
.insertBefore(new
, node
)
394 self
.add_undo(lambda: self
.delete_nodes([new
]))
397 if not self
.undo_stack
:
398 raise Exception('Nothing to undo')
400 assert not self
.doing_undo
402 uop
= self
.undo_stack
[-1][0]
404 # Swap stacks so that the undo actions will populate the redo stack...
405 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
408 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
409 self
.redo_stack
[-1][1]()
410 self
.redo_stack
.pop()
412 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
416 if not self
.redo_stack
:
417 raise Exception('Nothing to redo')
419 uop
= self
.redo_stack
[-1][0]
422 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
423 self
.redo_stack
[-1][1]()
424 self
.redo_stack
.pop()
428 def insert(self
, node
, new
, index
= 0):
429 if len(node
.childNodes
) > index
:
430 self
.insert_before(node
.childNodes
[index
], new
)
432 self
.insert_before(None, new
, parent
= node
)
434 def insert_after(self
, node
, new
):
435 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
437 def insert_before(self
, node
, new
, parent
= None):
438 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
439 "of parent's children. New may be a node, a list or a fragment."
441 parent
= node
.parentNode
442 if type(new
) != list:
443 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
447 for n
in new
: self
.insert_before_interal(node
, n
, parent
)
448 self
.update_all(parent
)
450 def split_qname(self
, node
, name
):
452 namespaceURI
= XMLNS_NAMESPACE
455 prefix
, localName
= name
.split(':')
456 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
460 return namespaceURI
, localName
462 def set_attrib(self
, node
, name
, value
, with_update
= 1):
463 """Set an attribute's value. If value is None, remove the attribute.
464 Returns the new attribute node, or None if removing."""
465 namespaceURI
, localName
= self
.split_qname(node
, name
)
467 if node
.hasAttributeNS(namespaceURI
, localName
):
468 old
= node
.getAttributeNS(namespaceURI
, localName
)
471 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
473 node
.setAttributeNS(namespaceURI
, name
, value
)
475 node
.removeAttributeNS(namespaceURI
, localName
)
477 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
480 self
.update_all(node
)
482 if localName
== 'xmlns':
484 return node
.attributes
[(namespaceURI
, localName
)]
486 def prefix_to_namespace(self
, node
, prefix
):
487 "Using attributes for namespaces was too confusing. Keep a global list instead."
488 if prefix
is None: return None
490 return self
.namespaces
.uri
[prefix
]
492 raise Exception("Namespace '%s' is not defined. Choose "
493 "View->Show namespaces from the popup menu to set it." % prefix
)
495 "Use the xmlns attributes to workout the namespace."
497 if nss
.has_key(prefix
):
498 return nss
[prefix
] or None
500 if prefix
== 'xmlns':
501 return XMLNS_NAMESPACE
502 raise Exception("No such namespace prefix '%s'" % prefix
)
506 def get_base_uri(self
, node
):
507 """Go up through the parents looking for a uri attribute.
508 If there isn't one, use the document's URI."""
510 if node
.nodeType
== Node
.DOCUMENT_NODE
:
512 if node
.hasAttributeNS(None, 'uri'):
513 return node
.getAttributeNS(None, 'uri')
514 node
= node
.parentNode
517 def load_html(self
, path
):
518 """Load this HTML file and return the new document."""
519 data
= file(path
).read()
520 data
= support
.to_html_doc(data
)
521 doc
= support
.parse_data(data
, path
)
522 self
.strip_space(doc
)