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
:
134 # Also add explicit namespaces (for type="xsd:foo", etc)
135 prefix
= self
.namespaces
.ensure_ns(a
.localName
, a
.value
)
137 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
138 for k
in node
.childNodes
:
139 new
.appendChild(clone(k
, clone
))
141 new
= ns_clone(node
, ns_clone
)
144 def clear_undo(self
):
145 # Pop an (op_number, function) off one of these and call the function to
146 # move forwards or backwards in the undo history.
151 # Each series of suboperations in an undo stack which are part of a single
152 # user op will have the same number...
156 #print "GC freed:", gc.collect()
157 #print "Garbage", gc.garbage
159 def lock(self
, node
):
160 """Prevent removal of this node (or any ancestor)."""
161 #print "Locking", node.nodeName
163 self
.locks
[node
] = self
.get_locks(node
) + 1
165 self
.lock(node
.parentNode
)
167 def unlock(self
, node
):
168 """Reverse the effect of lock(). Must call unlock the same number
169 of times as lock to fully unlock the node."""
170 l
= self
.get_locks(node
)
172 self
.locks
[node
] = l
- 1
174 del self
.locks
[node
] # Or get a memory leak...
175 if node
== self
.doc
.documentElement
:
177 self
.unlock(node
.parentNode
)
180 raise Exception('unlock(%s): Node not locked!' % node
)
182 self
.unlock(node
.parentNode
)
184 def get_locks(self
, node
):
186 return self
.locks
[node
]
190 def lock_and_copy(self
, node
):
191 """Locks 'node' in the current model and returns a new model
192 with a copy of the subtree."""
193 if self
.get_locks(node
):
194 raise Exception("Can't enter locked node!")
195 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
196 copy
= m
.import_with_ns(node
)
198 m
.replace_node(root
, copy
)
203 "Increment the user_op counter. Undo will undo every operation between"
208 "Return the true root node (not a view root)"
209 return self
.doc
.documentElement
211 def add_view(self
, view
):
213 "'update_all(subtree) - called when a major change occurs."
214 #print "New view:", view
215 self
.views
.append(view
)
217 def remove_view(self
, view
):
218 #print "Removing view", view
219 self
.views
.remove(view
)
220 #print "Now:", self.views
221 # XXX: clears undo on enter!
225 def update_all(self
, node
):
226 "Called when 'node' has been updated."
227 "'node' is still in the document, so deleting or replacing"
228 "a node calls this on the parent."
232 def update_replace(self
, old
, new
):
233 "Called when 'old' is replaced by 'new'."
235 v
.update_replace(old
, new
)
237 def strip_space(self
, node
= None):
239 node
= self
.doc
.documentElement
241 if node
.nodeType
== Node
.TEXT_NODE
:
242 #node.data = node.data.strip()
244 # node.parentNode.removeChild(node)
245 if not node
.data
.strip():
246 node
.parentNode
.removeChild(node
)
248 for k
in node
.childNodes
[:]:
254 def normalise(self
, node
):
255 old
= node
.cloneNode(1)
257 self
.add_undo(lambda: self
.replace_node(node
, old
))
258 self
.update_all(node
)
260 def convert_to_element(self
, node
):
261 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
263 new
= self
.doc
.createElementNS(None, node
.data
.strip())
264 self
.replace_node(node
, new
)
267 def convert_to_text(self
, node
):
268 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
269 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
270 if node
.nodeType
== Node
.ELEMENT_NODE
:
271 new
= self
.doc
.createTextNode(node
.localName
)
273 new
= self
.doc
.createTextNode(node
.data
)
274 self
.replace_node(node
, new
)
277 def convert_to_comment(self
, node
):
278 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
280 new
= self
.doc
.createComment(node
.data
)
281 self
.replace_node(node
, new
)
284 def remove_ns(self
, node
):
285 print "remove_ns: Shouldn't need this now!"
288 nss
= GetAllNs(node
.parentNode
)
289 dns
= nss
.get(None, None)
290 create
= node
.ownerDocument
.createElementNS
291 # clone is an argument to fix a wierd gc bug in python2.2
292 def ns_clone(node
, clone
):
293 if node
.nodeType
!= Node
.ELEMENT_NODE
:
294 return node
.cloneNode(1)
295 new
= create(dns
, node
.nodeName
)
296 for a
in node
.attributes
.values():
297 if a
.localName
== 'xmlns' and a
.prefix
is None:
298 print "Removing xmlns attrib on", node
300 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
301 for k
in node
.childNodes
:
302 new
.appendChild(clone(k
, clone
))
304 new
= ns_clone(node
, ns_clone
)
305 self
.replace_node(node
, new
)
308 def set_name(self
, node
, namespace
, name
):
309 if self
.get_locks(node
):
310 raise Exception('Attempt to set name on locked node %s' % node
)
312 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
313 self
.replace_shallow(node
, new
)
316 def replace_shallow(self
, old
, new
):
317 """Replace old with new, keeping the old children."""
318 assert not new
.childNodes
319 assert not new
.parentNode
321 old_name
= old
.nodeName
322 old_ns
= old
.namespaceURI
324 kids
= old
.childNodes
[:]
325 attrs
= old
.attributes
.values()
326 parent
= old
.parentNode
327 [ old
.removeChild(k
) for k
in kids
]
328 parent
.replaceChild(new
, old
)
329 [ new
.appendChild(k
) for k
in kids
]
330 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
332 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
334 self
.update_replace(old
, new
)
337 if __main__
.no_gui_mode
:
338 def add_undo(self
, fn
):
341 def add_undo(self
, fn
):
342 self
.undo_stack
.append((self
.user_op
, fn
))
343 if not self
.doing_undo
:
346 def set_data(self
, node
, data
):
349 self
.add_undo(lambda: self
.set_data(node
, old_data
))
350 self
.update_all(node
)
352 def replace_node(self
, old
, new
):
353 if self
.get_locks(old
):
354 raise Exception('Attempt to replace locked node %s' % old
)
355 old
.parentNode
.replaceChild(new
, old
)
356 self
.add_undo(lambda: self
.replace_node(new
, old
))
358 self
.update_replace(old
, new
)
360 def delete_shallow(self
, node
):
361 """Replace node with its contents"""
362 kids
= node
.childNodes
[:]
363 next
= node
.nextSibling
364 parent
= node
.parentNode
365 for n
in kids
+ [node
]:
366 if self
.get_locks(n
):
367 raise Exception('Attempt to move/delete locked node %s' % n
)
369 self
.delete_internal(k
)
370 self
.delete_internal(node
)
372 self
.insert_before_interal(next
, k
, parent
)
373 self
.update_all(parent
)
375 def delete_nodes(self
, nodes
):
376 #print "Deleting", nodes
378 if self
.get_locks(n
):
379 raise Exception('Attempt to delete locked node %s' % n
)
382 self
.delete_internal(n
)
385 def delete_internal(self
, node
):
386 "Doesn't update display."
387 next
= node
.nextSibling
388 parent
= node
.parentNode
389 parent
.removeChild(node
)
390 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
392 def insert_before_interal(self
, node
, new
, parent
):
393 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
394 "of parent's children."
395 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
396 #assert parent.nodeType == Node.ELEMENT_NODE
397 parent
.insertBefore(new
, node
)
398 self
.add_undo(lambda: self
.delete_nodes([new
]))
401 if not self
.undo_stack
:
402 raise Exception('Nothing to undo')
404 assert not self
.doing_undo
406 uop
= self
.undo_stack
[-1][0]
408 # Swap stacks so that the undo actions will populate the redo stack...
409 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
412 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
413 self
.redo_stack
[-1][1]()
414 self
.redo_stack
.pop()
416 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
420 if not self
.redo_stack
:
421 raise Exception('Nothing to redo')
423 uop
= self
.redo_stack
[-1][0]
426 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
427 self
.redo_stack
[-1][1]()
428 self
.redo_stack
.pop()
432 def insert(self
, node
, new
, index
= 0):
433 if len(node
.childNodes
) > index
:
434 self
.insert_before(node
.childNodes
[index
], new
)
436 self
.insert_before(None, new
, parent
= node
)
438 def insert_after(self
, node
, new
):
439 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
441 def insert_before(self
, node
, new
, parent
= None):
442 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
443 "of parent's children. New may be a node, a list or a fragment."
445 parent
= node
.parentNode
446 if type(new
) != list:
447 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
451 for n
in new
: self
.insert_before_interal(node
, n
, parent
)
452 self
.update_all(parent
)
454 def split_qname(self
, node
, name
):
456 namespaceURI
= XMLNS_NAMESPACE
459 prefix
, localName
= name
.split(':')
460 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
464 return namespaceURI
, localName
466 def set_attrib(self
, node
, name
, value
, with_update
= 1):
467 """Set an attribute's value. If value is None, remove the attribute.
468 Returns the new attribute node, or None if removing."""
469 namespaceURI
, localName
= self
.split_qname(node
, name
)
471 if namespaceURI
== XMLNS_NAMESPACE
:
472 raise Exception("Attempt to set namespace attribute '%s'.\n\n"
473 "Use View->Show Namespaces to edit namespaces." % name
)
475 if node
.hasAttributeNS(namespaceURI
, localName
):
476 old
= node
.getAttributeNS(namespaceURI
, localName
)
479 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
481 node
.setAttributeNS(namespaceURI
, name
, value
)
483 node
.removeAttributeNS(namespaceURI
, localName
)
485 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
488 self
.update_all(node
)
490 if localName
== 'xmlns':
492 return node
.attributes
[(namespaceURI
, localName
)]
494 def prefix_to_namespace(self
, node
, prefix
):
495 "Using attributes for namespaces was too confusing. Keep a global list instead."
496 if prefix
is None: return None
498 return self
.namespaces
.uri
[prefix
]
500 raise Exception("Namespace '%s' is not defined. Choose "
501 "View->Show namespaces from the popup menu to set it." % prefix
)
503 "Use the xmlns attributes to workout the namespace."
505 if nss
.has_key(prefix
):
506 return nss
[prefix
] or None
508 if prefix
== 'xmlns':
509 return XMLNS_NAMESPACE
510 raise Exception("No such namespace prefix '%s'" % prefix
)
514 def get_base_uri(self
, node
):
515 """Go up through the parents looking for a uri attribute.
516 If there isn't one, use the document's URI."""
518 if node
.nodeType
== Node
.DOCUMENT_NODE
:
520 if node
.hasAttributeNS(None, 'uri'):
521 return node
.getAttributeNS(None, 'uri')
522 node
= node
.parentNode
525 def load_html(self
, path
):
526 """Load this HTML file and return the new document."""
527 data
= file(path
).read()
528 data
= support
.to_html_doc(data
)
529 doc
= support
.parse_data(data
, path
)
530 self
.strip_space(doc
)