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 x
= implementation
.createDocument(None, 'xslt', None)
75 data_to_load
= x
.documentElement
76 src
= doc
.createElementNS(None, 'Source')
78 # TODO: import_with_ns?
79 src
.appendChild(self
.import_with_ns(doc
,
80 dome_data
.documentElement
))
81 data_to_load
.appendChild(x
.createElementNS(None, 'Result'))
82 data_to_load
.appendChild(src
)
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
220 def update_all(self
, node
):
221 "Called when 'node' has been updated."
222 "'node' is still in the document, so deleting or replacing"
223 "a node calls this on the parent."
227 def update_replace(self
, old
, new
):
228 "Called when 'old' is replaced by 'new'."
230 v
.update_replace(old
, new
)
232 def strip_space(self
, node
= None):
234 node
= self
.doc
.documentElement
236 if node
.nodeType
== Node
.TEXT_NODE
:
237 #node.data = node.data.strip()
239 # node.parentNode.removeChild(node)
240 if not node
.data
.strip():
241 node
.parentNode
.removeChild(node
)
243 for k
in node
.childNodes
[:]:
249 def normalise(self
, node
):
250 old
= node
.cloneNode(1)
252 self
.add_undo(lambda: self
.replace_node(node
, old
))
253 self
.update_all(node
)
255 def convert_to_element(self
, node
):
256 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
258 new
= self
.doc
.createElementNS(None, node
.data
.strip())
259 self
.replace_node(node
, new
)
262 def convert_to_text(self
, node
):
263 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
264 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
265 if node
.nodeType
== Node
.ELEMENT_NODE
:
266 new
= self
.doc
.createTextNode(node
.localName
)
268 new
= self
.doc
.createTextNode(node
.data
)
269 self
.replace_node(node
, new
)
272 def convert_to_comment(self
, node
):
273 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
275 new
= self
.doc
.createComment(node
.data
)
276 self
.replace_node(node
, new
)
279 def remove_ns(self
, node
):
280 print "remove_ns: Shouldn't need this now!"
283 nss
= GetAllNs(node
.parentNode
)
284 dns
= nss
.get(None, None)
285 create
= node
.ownerDocument
.createElementNS
286 # clone is an argument to fix a wierd gc bug in python2.2
287 def ns_clone(node
, clone
):
288 if node
.nodeType
!= Node
.ELEMENT_NODE
:
289 return node
.cloneNode(1)
290 new
= create(dns
, node
.nodeName
)
291 for a
in node
.attributes
.values():
292 if a
.localName
== 'xmlns' and a
.prefix
is None:
293 print "Removing xmlns attrib on", node
295 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
296 for k
in node
.childNodes
:
297 new
.appendChild(clone(k
, clone
))
299 new
= ns_clone(node
, ns_clone
)
300 self
.replace_node(node
, new
)
303 def set_name(self
, node
, namespace
, name
):
304 if self
.get_locks(node
):
305 raise Exception('Attempt to set name on locked node %s' % node
)
307 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
308 self
.replace_shallow(node
, new
)
311 def replace_shallow(self
, old
, new
):
312 """Replace old with new, keeping the old children."""
313 assert not new
.childNodes
314 assert not new
.parentNode
316 old_name
= old
.nodeName
317 old_ns
= old
.namespaceURI
319 kids
= old
.childNodes
[:]
320 attrs
= old
.attributes
.values()
321 parent
= old
.parentNode
322 [ old
.removeChild(k
) for k
in kids
]
323 parent
.replaceChild(new
, old
)
324 [ new
.appendChild(k
) for k
in kids
]
325 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
327 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
329 self
.update_replace(old
, new
)
332 if __main__
.no_gui_mode
:
333 def add_undo(self
, fn
):
336 def add_undo(self
, fn
):
337 self
.undo_stack
.append((self
.user_op
, fn
))
338 if not self
.doing_undo
:
341 def set_data(self
, node
, data
):
344 self
.add_undo(lambda: self
.set_data(node
, old_data
))
345 self
.update_all(node
)
347 def replace_node(self
, old
, new
):
348 if self
.get_locks(old
):
349 raise Exception('Attempt to replace locked node %s' % old
)
350 old
.parentNode
.replaceChild(new
, old
)
351 self
.add_undo(lambda: self
.replace_node(new
, old
))
353 self
.update_replace(old
, new
)
355 def delete_shallow(self
, node
):
356 """Replace node with its contents"""
357 kids
= node
.childNodes
[:]
358 next
= node
.nextSibling
359 parent
= node
.parentNode
360 for n
in kids
+ [node
]:
361 if self
.get_locks(n
):
362 raise Exception('Attempt to move/delete locked node %s' % n
)
364 self
.delete_internal(k
)
365 self
.delete_internal(node
)
367 self
.insert_before_interal(next
, k
, parent
)
368 self
.update_all(parent
)
370 def delete_nodes(self
, nodes
):
371 #print "Deleting", nodes
373 if self
.get_locks(n
):
374 raise Exception('Attempt to delete locked node %s' % n
)
377 self
.delete_internal(n
)
380 def delete_internal(self
, node
):
381 "Doesn't update display."
382 next
= node
.nextSibling
383 parent
= node
.parentNode
384 parent
.removeChild(node
)
385 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
387 def insert_before_interal(self
, node
, new
, parent
):
388 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
389 "of parent's children."
390 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
391 #assert parent.nodeType == Node.ELEMENT_NODE
392 parent
.insertBefore(new
, node
)
393 self
.add_undo(lambda: self
.delete_nodes([new
]))
396 if not self
.undo_stack
:
397 raise Exception('Nothing to undo')
399 assert not self
.doing_undo
401 uop
= self
.undo_stack
[-1][0]
403 # Swap stacks so that the undo actions will populate the redo stack...
404 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
407 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
408 self
.redo_stack
[-1][1]()
409 self
.redo_stack
.pop()
411 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
415 if not self
.redo_stack
:
416 raise Exception('Nothing to redo')
418 uop
= self
.redo_stack
[-1][0]
421 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
422 self
.redo_stack
[-1][1]()
423 self
.redo_stack
.pop()
427 def insert(self
, node
, new
, index
= 0):
428 if len(node
.childNodes
) > index
:
429 self
.insert_before(node
.childNodes
[index
], new
)
431 self
.insert_before(None, new
, parent
= node
)
433 def insert_after(self
, node
, new
):
434 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
436 def insert_before(self
, node
, new
, parent
= None):
437 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
438 "of parent's children."
440 parent
= node
.parentNode
441 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
442 for n
in new
.childNodes
[:]:
443 self
.insert_before_interal(node
, n
, parent
)
445 self
.insert_before_interal(node
, new
, parent
)
446 self
.update_all(parent
)
448 def split_qname(self
, node
, name
):
450 namespaceURI
= XMLNS_NAMESPACE
453 prefix
, localName
= name
.split(':')
454 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
458 return namespaceURI
, localName
460 def set_attrib(self
, node
, name
, value
, with_update
= 1):
461 """Set an attribute's value. If value is None, remove the attribute.
462 Returns the new attribute node, or None if removing."""
463 namespaceURI
, localName
= self
.split_qname(node
, name
)
465 if node
.hasAttributeNS(namespaceURI
, localName
):
466 old
= node
.getAttributeNS(namespaceURI
, localName
)
469 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
471 node
.setAttributeNS(namespaceURI
, name
, value
)
473 node
.removeAttributeNS(namespaceURI
, localName
)
475 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
478 self
.update_all(node
)
480 if localName
== 'xmlns':
482 return node
.attributes
[(namespaceURI
, localName
)]
484 def prefix_to_namespace(self
, node
, prefix
):
485 "Using attributes for namespaces was too confusing. Keep a global list instead."
486 if prefix
is None: return None
488 return self
.namespaces
.uri
[prefix
]
490 raise Exception("Namespace '%s' is not defined. Choose "
491 "View->Show namespaces from the popup menu to set it." % prefix
)
493 "Use the xmlns attributes to workout the namespace."
495 if nss
.has_key(prefix
):
496 return nss
[prefix
] or None
498 if prefix
== 'xmlns':
499 return XMLNS_NAMESPACE
500 raise Exception("No such namespace prefix '%s'" % prefix
)
504 def get_base_uri(self
, node
):
505 """Go up through the parents looking for a uri attribute.
506 If there isn't one, use the document's URI."""
508 if node
.nodeType
== Node
.DOCUMENT_NODE
:
510 if node
.hasAttributeNS(None, 'uri'):
511 return node
.getAttributeNS(None, 'uri')
512 node
= node
.parentNode
515 def load_html(self
, path
):
516 """Load this HTML file and return the new document."""
517 data
= file(path
).read()
518 data
= support
.to_html_doc(data
)
519 doc
= support
.parse_data(data
, path
)
520 self
.strip_space(doc
)