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 print root
.namespaceURI
, root
.localName
56 if root
.namespaceURI
== constants
.DOME_NS
and root
.localName
== 'dome':
57 for x
in root
.childNodes
:
58 if x
.namespaceURI
== constants
.DOME_NS
:
59 if x
.localName
== 'namespaces':
61 elif x
.localName
== 'dome-program':
62 self
.root_program
= load_dome_program(x
,
64 elif x
.localName
== 'dome-data':
65 for y
in x
.childNodes
:
66 if y
.nodeType
== Node
.ELEMENT_NODE
:
69 data_to_load
= dome_data
.documentElement
70 elif (root
.namespaceURI
== constants
.XSLT_NS
and
71 root
.localName
in ['stylesheet', 'transform']) or \
72 root
.hasAttributeNS(constants
.XSLT_NS
, 'version'):
74 self
.root_program
= xslt
.import_sheet(doc
)
75 self
.doc
= implementation
.createDocument(None, 'xslt', None)
77 src
= self
.doc
.createElementNS(None, 'Source')
79 src
.appendChild(self
.import_with_ns(dome_data
.documentElement
))
80 self
.doc
.documentElement
.appendChild(src
)
81 self
.doc
.documentElement
.appendChild(self
.doc
.createElementNS(None, 'Result'))
89 self
.root_program
= root_program
91 if not self
.root_program
:
92 self
.root_program
= Program('Root')
95 self
.doc
= implementation
.createDocument(None, 'root', None)
97 node
= self
.import_with_ns(data_to_load
)
98 self
.doc
.replaceChild(node
, self
.doc
.documentElement
)
101 self
.views
= [] # Notified when something changes
102 self
.locks
= {} # Node -> number of locks
104 self
.hidden
= {} # Node -> Message
105 self
.hidden_code
= {} # XPath to generate Message, if any
107 def load_ns(self
, node
):
108 assert node
.localName
== 'namespaces'
109 assert node
.namespaceURI
== constants
.DOME_NS
110 for x
in node
.childNodes
:
111 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
112 if x
.localName
!= 'ns': continue
113 if x
.namespaceURI
!= constants
.DOME_NS
: continue
115 self
.namespaces
.ensure_ns(x
.getAttributeNS(None, 'prefix'),
116 x
.getAttributeNS(None, 'uri'))
118 def import_with_ns(self
, node
):
119 """Return a copy of node for this model. All namespaces used in the subtree
120 will have been added to the global namespaces list. Prefixes will have been changed
121 as required to avoid conflicts."""
123 def ns_clone(node
, clone
):
124 if node
.nodeType
!= Node
.ELEMENT_NODE
:
125 return doc
.importNode(node
, 1)
126 if node
.namespaceURI
:
127 prefix
= self
.namespaces
.ensure_ns(node
.prefix
, node
.namespaceURI
)
128 new
= doc
.createElementNS(node
.namespaceURI
,
129 prefix
+ ':' + node
.localName
)
131 new
= doc
.createElementNS(None, node
.localName
)
132 for a
in node
.attributes
.values():
133 if a
.namespaceURI
== XMLNS_NAMESPACE
: continue
134 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
135 for k
in node
.childNodes
:
136 new
.appendChild(clone(k
, clone
))
138 new
= ns_clone(node
, ns_clone
)
141 def clear_undo(self
):
142 # Pop an (op_number, function) off one of these and call the function to
143 # move forwards or backwards in the undo history.
148 # Each series of suboperations in an undo stack which are part of a single
149 # user op will have the same number...
153 #print "GC freed:", gc.collect()
154 #print "Garbage", gc.garbage
156 def lock(self
, node
):
157 """Prevent removal of this node (or any ancestor)."""
158 #print "Locking", node.nodeName
160 self
.locks
[node
] = self
.get_locks(node
) + 1
162 self
.lock(node
.parentNode
)
164 def unlock(self
, node
):
165 """Reverse the effect of lock(). Must call unlock the same number
166 of times as lock to fully unlock the node."""
167 l
= self
.get_locks(node
)
169 self
.locks
[node
] = l
- 1
171 del self
.locks
[node
] # Or get a memory leak...
172 if node
== self
.doc
.documentElement
:
174 self
.unlock(node
.parentNode
)
177 raise Exception('unlock(%s): Node not locked!' % node
)
179 self
.unlock(node
.parentNode
)
181 def get_locks(self
, node
):
183 return self
.locks
[node
]
187 def lock_and_copy(self
, node
):
188 """Locks 'node' in the current model and returns a new model
189 with a copy of the subtree."""
190 if self
.get_locks(node
):
191 raise Exception("Can't enter locked node!")
192 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
193 copy
= m
.import_with_ns(node
)
195 m
.replace_node(root
, copy
)
200 "Increment the user_op counter. Undo will undo every operation between"
205 "Return the true root node (not a view root)"
206 return self
.doc
.documentElement
208 def add_view(self
, view
):
210 "'update_all(subtree) - called when a major change occurs."
211 #print "New view:", view
212 self
.views
.append(view
)
214 def remove_view(self
, view
):
215 #print "Removing view", view
216 self
.views
.remove(view
)
217 #print "Now:", self.views
218 # XXX: clears undo on enter!
222 def update_all(self
, node
):
223 "Called when 'node' has been updated."
224 "'node' is still in the document, so deleting or replacing"
225 "a node calls this on the parent."
229 def update_replace(self
, old
, new
):
230 "Called when 'old' is replaced by 'new'."
232 v
.update_replace(old
, new
)
234 def strip_space(self
, node
= None):
236 node
= self
.doc
.documentElement
238 if node
.nodeType
== Node
.TEXT_NODE
:
239 #node.data = node.data.strip()
241 # node.parentNode.removeChild(node)
242 if not node
.data
.strip():
243 node
.parentNode
.removeChild(node
)
245 for k
in node
.childNodes
[:]:
251 def normalise(self
, node
):
252 old
= node
.cloneNode(1)
254 self
.add_undo(lambda: self
.replace_node(node
, old
))
255 self
.update_all(node
)
257 def convert_to_element(self
, node
):
258 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
260 new
= self
.doc
.createElementNS(None, node
.data
.strip())
261 self
.replace_node(node
, new
)
264 def convert_to_text(self
, node
):
265 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
266 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
267 if node
.nodeType
== Node
.ELEMENT_NODE
:
268 new
= self
.doc
.createTextNode(node
.localName
)
270 new
= self
.doc
.createTextNode(node
.data
)
271 self
.replace_node(node
, new
)
274 def convert_to_comment(self
, node
):
275 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
277 new
= self
.doc
.createComment(node
.data
)
278 self
.replace_node(node
, new
)
281 def remove_ns(self
, node
):
282 print "remove_ns: Shouldn't need this now!"
285 nss
= GetAllNs(node
.parentNode
)
286 dns
= nss
.get(None, None)
287 create
= node
.ownerDocument
.createElementNS
288 # clone is an argument to fix a wierd gc bug in python2.2
289 def ns_clone(node
, clone
):
290 if node
.nodeType
!= Node
.ELEMENT_NODE
:
291 return node
.cloneNode(1)
292 new
= create(dns
, node
.nodeName
)
293 for a
in node
.attributes
.values():
294 if a
.localName
== 'xmlns' and a
.prefix
is None:
295 print "Removing xmlns attrib on", node
297 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
298 for k
in node
.childNodes
:
299 new
.appendChild(clone(k
, clone
))
301 new
= ns_clone(node
, ns_clone
)
302 self
.replace_node(node
, new
)
305 def set_name(self
, node
, namespace
, name
):
306 if self
.get_locks(node
):
307 raise Exception('Attempt to set name on locked node %s' % node
)
309 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
310 self
.replace_shallow(node
, new
)
313 def replace_shallow(self
, old
, new
):
314 """Replace old with new, keeping the old children."""
315 assert not new
.childNodes
316 assert not new
.parentNode
318 old_name
= old
.nodeName
319 old_ns
= old
.namespaceURI
321 kids
= old
.childNodes
[:]
322 attrs
= old
.attributes
.values()
323 parent
= old
.parentNode
324 [ old
.removeChild(k
) for k
in kids
]
325 parent
.replaceChild(new
, old
)
326 [ new
.appendChild(k
) for k
in kids
]
327 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
329 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
331 self
.update_replace(old
, new
)
334 if __main__
.no_gui_mode
:
335 def add_undo(self
, fn
):
338 def add_undo(self
, fn
):
339 self
.undo_stack
.append((self
.user_op
, fn
))
340 if not self
.doing_undo
:
343 def set_data(self
, node
, data
):
346 self
.add_undo(lambda: self
.set_data(node
, old_data
))
347 self
.update_all(node
)
349 def replace_node(self
, old
, new
):
350 if self
.get_locks(old
):
351 raise Exception('Attempt to replace locked node %s' % old
)
352 old
.parentNode
.replaceChild(new
, old
)
353 self
.add_undo(lambda: self
.replace_node(new
, old
))
355 self
.update_replace(old
, new
)
357 def delete_shallow(self
, node
):
358 """Replace node with its contents"""
359 kids
= node
.childNodes
[:]
360 next
= node
.nextSibling
361 parent
= node
.parentNode
362 for n
in kids
+ [node
]:
363 if self
.get_locks(n
):
364 raise Exception('Attempt to move/delete locked node %s' % n
)
366 self
.delete_internal(k
)
367 self
.delete_internal(node
)
369 self
.insert_before_interal(next
, k
, parent
)
370 self
.update_all(parent
)
372 def delete_nodes(self
, nodes
):
373 #print "Deleting", nodes
375 if self
.get_locks(n
):
376 raise Exception('Attempt to delete locked node %s' % n
)
379 self
.delete_internal(n
)
382 def delete_internal(self
, node
):
383 "Doesn't update display."
384 next
= node
.nextSibling
385 parent
= node
.parentNode
386 parent
.removeChild(node
)
387 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
389 def insert_before_interal(self
, node
, new
, parent
):
390 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
391 "of parent's children."
392 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
393 #assert parent.nodeType == Node.ELEMENT_NODE
394 parent
.insertBefore(new
, node
)
395 self
.add_undo(lambda: self
.delete_nodes([new
]))
398 if not self
.undo_stack
:
399 raise Exception('Nothing to undo')
401 assert not self
.doing_undo
403 uop
= self
.undo_stack
[-1][0]
405 # Swap stacks so that the undo actions will populate the redo stack...
406 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
409 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
410 self
.redo_stack
[-1][1]()
411 self
.redo_stack
.pop()
413 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
417 if not self
.redo_stack
:
418 raise Exception('Nothing to redo')
420 uop
= self
.redo_stack
[-1][0]
423 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
424 self
.redo_stack
[-1][1]()
425 self
.redo_stack
.pop()
429 def insert(self
, node
, new
, index
= 0):
430 if len(node
.childNodes
) > index
:
431 self
.insert_before(node
.childNodes
[index
], new
)
433 self
.insert_before(None, new
, parent
= node
)
435 def insert_after(self
, node
, new
):
436 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
438 def insert_before(self
, node
, new
, parent
= None):
439 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
440 "of parent's children. New may be a node, a list or a fragment."
442 parent
= node
.parentNode
443 if type(new
) != list:
444 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
448 for n
in new
: self
.insert_before_interal(node
, n
, parent
)
449 self
.update_all(parent
)
451 def split_qname(self
, node
, name
):
453 namespaceURI
= XMLNS_NAMESPACE
456 prefix
, localName
= name
.split(':')
457 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
461 return namespaceURI
, localName
463 def set_attrib(self
, node
, name
, value
, with_update
= 1):
464 """Set an attribute's value. If value is None, remove the attribute.
465 Returns the new attribute node, or None if removing."""
466 namespaceURI
, localName
= self
.split_qname(node
, name
)
468 if namespaceURI
== XMLNS_NAMESPACE
:
469 raise Exception("Attempt to set namespace attribute '%s'.\n\n"
470 "Use View->Show Namespaces to edit namespaces." % name
)
472 if node
.hasAttributeNS(namespaceURI
, localName
):
473 old
= node
.getAttributeNS(namespaceURI
, localName
)
476 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
478 node
.setAttributeNS(namespaceURI
, name
, value
)
480 node
.removeAttributeNS(namespaceURI
, localName
)
482 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
485 self
.update_all(node
)
487 if localName
== 'xmlns':
489 return node
.attributes
[(namespaceURI
, localName
)]
491 def prefix_to_namespace(self
, node
, prefix
):
492 "Using attributes for namespaces was too confusing. Keep a global list instead."
493 if prefix
is None: return None
495 return self
.namespaces
.uri
[prefix
]
497 raise Exception("Namespace '%s' is not defined. Choose "
498 "View->Show namespaces from the popup menu to set it." % prefix
)
500 "Use the xmlns attributes to workout the namespace."
502 if nss
.has_key(prefix
):
503 return nss
[prefix
] or None
505 if prefix
== 'xmlns':
506 return XMLNS_NAMESPACE
507 raise Exception("No such namespace prefix '%s'" % prefix
)
511 def get_base_uri(self
, node
):
512 """Go up through the parents looking for a uri attribute.
513 If there isn't one, use the document's URI."""
515 if node
.nodeType
== Node
.DOCUMENT_NODE
:
517 if node
.hasAttributeNS(None, 'uri'):
518 return node
.getAttributeNS(None, 'uri')
519 node
= node
.parentNode
522 def load_html(self
, path
):
523 """Load this HTML file and return the new document."""
524 data
= file(path
).read()
525 data
= support
.to_html_doc(data
)
526 doc
= support
.parse_data(data
, path
)
527 self
.strip_space(doc
)