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
= support
.import_with_ns(self
.doc
, 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
100 # Pop an (op_number, function) off one of these and call the function to
101 # move forwards or backwards in the undo history.
106 # Each series of suboperations in an undo stack which are part of a single
107 # user op will have the same number...
111 #print "GC freed:", gc.collect()
112 #print "Garbage", gc.garbage
114 def lock(self
, node
):
115 """Prevent removal of this node (or any ancestor)."""
116 #print "Locking", node.nodeName
117 self
.locks
[node
] = self
.get_locks(node
) + 1
119 self
.lock(node
.parentNode
)
121 def unlock(self
, node
):
122 """Reverse the effect of lock(). Must call unlock the same number
123 of times as lock to fully unlock the node."""
124 l
= self
.get_locks(node
)
126 self
.locks
[node
] = l
- 1
128 del self
.locks
[node
] # Or get a memory leak...
129 if node
== self
.doc
.documentElement
:
131 self
.unlock(node
.parentNode
)
134 raise Exception('unlock(%s): Node not locked!' % node
)
136 self
.unlock(node
.parentNode
)
138 def get_locks(self
, node
):
140 return self
.locks
[node
]
144 def lock_and_copy(self
, node
):
145 """Locks 'node' in the current model and returns a new model
146 with a copy of the subtree."""
147 if self
.get_locks(node
):
148 raise Exception("Can't enter locked node!")
149 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
150 copy
= support
.import_with_ns(m
.doc
, node
)
152 m
.replace_node(root
, copy
)
157 "Increment the user_op counter. Undo will undo every operation between"
162 "Return the true root node (not a view root)"
163 return self
.doc
.documentElement
165 def add_view(self
, view
):
167 "'update_all(subtree) - called when a major change occurs."
168 #print "New view:", view
169 self
.views
.append(view
)
171 def remove_view(self
, view
):
172 #print "Removing view", view
173 self
.views
.remove(view
)
174 #print "Now:", self.views
178 def update_all(self
, node
):
179 "Called when 'node' has been updated."
180 "'node' is still in the document, so deleting or replacing"
181 "a node calls this on the parent."
185 def update_replace(self
, old
, new
):
186 "Called when 'old' is replaced by 'new'."
188 v
.update_replace(old
, new
)
190 def strip_space(self
, node
= None):
192 node
= self
.doc
.documentElement
194 if node
.nodeType
== Node
.TEXT_NODE
:
195 #node.data = node.data.strip()
197 # node.parentNode.removeChild(node)
198 if not node
.data
.strip():
199 node
.parentNode
.removeChild(node
)
201 for k
in node
.childNodes
[:]:
207 def normalise(self
, node
):
208 old
= node
.cloneNode(1)
210 self
.add_undo(lambda: self
.replace_node(node
, old
))
211 self
.update_all(node
)
213 def convert_to_element(self
, node
):
214 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
216 new
= self
.doc
.createElementNS(None, node
.data
.strip())
217 self
.replace_node(node
, new
)
220 def convert_to_text(self
, node
):
221 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
222 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
223 if node
.nodeType
== Node
.ELEMENT_NODE
:
224 new
= self
.doc
.createTextNode(node
.localName
)
226 new
= self
.doc
.createTextNode(node
.data
)
227 self
.replace_node(node
, new
)
230 def convert_to_comment(self
, node
):
231 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
233 new
= self
.doc
.createComment(node
.data
)
234 self
.replace_node(node
, new
)
237 def remove_ns(self
, node
):
238 nss
= GetAllNs(node
.parentNode
)
239 dns
= nss
.get(None, None)
240 create
= node
.ownerDocument
.createElementNS
241 # clone is an argument to fix a wierd gc bug in python2.2
242 def ns_clone(node
, clone
):
243 if node
.nodeType
!= Node
.ELEMENT_NODE
:
244 return node
.cloneNode(1)
245 new
= create(dns
, node
.nodeName
)
246 for a
in node
.attributes
.values():
247 if a
.localName
== 'xmlns' and a
.prefix
is None:
248 print "Removing xmlns attrib on", node
250 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
251 for k
in node
.childNodes
:
252 new
.appendChild(clone(k
, clone
))
254 new
= ns_clone(node
, ns_clone
)
255 self
.replace_node(node
, new
)
258 def set_name(self
, node
, namespace
, name
):
259 if self
.get_locks(node
):
260 raise Exception('Attempt to set name on locked node %s' % node
)
262 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
263 self
.replace_shallow(node
, new
)
266 def replace_shallow(self
, old
, new
):
267 """Replace old with new, keeping the old children."""
268 assert not new
.childNodes
269 assert not new
.parentNode
271 old_name
= old
.nodeName
272 old_ns
= old
.namespaceURI
274 kids
= old
.childNodes
[:]
275 attrs
= old
.attributes
.values()
276 parent
= old
.parentNode
277 [ old
.removeChild(k
) for k
in kids
]
278 parent
.replaceChild(new
, old
)
279 [ new
.appendChild(k
) for k
in kids
]
280 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
282 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
284 self
.update_replace(old
, new
)
287 if __main__
.no_gui_mode
:
288 def add_undo(self
, fn
):
291 def add_undo(self
, fn
):
292 self
.undo_stack
.append((self
.user_op
, fn
))
293 if not self
.doing_undo
:
296 def set_data(self
, node
, data
):
299 self
.add_undo(lambda: self
.set_data(node
, old_data
))
300 self
.update_all(node
)
302 def replace_node(self
, old
, new
):
303 if self
.get_locks(old
):
304 raise Exception('Attempt to replace locked node %s' % old
)
305 old
.parentNode
.replaceChild(new
, old
)
306 self
.add_undo(lambda: self
.replace_node(new
, old
))
308 self
.update_replace(old
, new
)
310 def delete_shallow(self
, node
):
311 """Replace node with its contents"""
312 kids
= node
.childNodes
[:]
313 next
= node
.nextSibling
314 parent
= node
.parentNode
315 for n
in kids
+ [node
]:
316 if self
.get_locks(n
):
317 raise Exception('Attempt to move/delete locked node %s' % n
)
319 self
.delete_internal(k
)
320 self
.delete_internal(node
)
322 self
.insert_before_interal(next
, k
, parent
)
323 self
.update_all(parent
)
325 def delete_nodes(self
, nodes
):
326 #print "Deleting", nodes
328 if self
.get_locks(n
):
329 raise Exception('Attempt to delete locked node %s' % n
)
332 self
.delete_internal(n
)
335 def delete_internal(self
, node
):
336 "Doesn't update display."
337 next
= node
.nextSibling
338 parent
= node
.parentNode
339 parent
.removeChild(node
)
340 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
342 def insert_before_interal(self
, node
, new
, parent
):
343 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
344 "of parent's children."
345 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
346 assert parent
.nodeType
== Node
.ELEMENT_NODE
347 parent
.insertBefore(new
, node
)
348 self
.add_undo(lambda: self
.delete_nodes([new
]))
351 if not self
.undo_stack
:
352 raise Exception('Nothing to undo')
354 assert not self
.doing_undo
356 uop
= self
.undo_stack
[-1][0]
358 # Swap stacks so that the undo actions will populate the redo stack...
359 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
362 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
363 self
.redo_stack
[-1][1]()
364 self
.redo_stack
.pop()
366 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
370 if not self
.redo_stack
:
371 raise Exception('Nothing to redo')
373 uop
= self
.redo_stack
[-1][0]
376 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
377 self
.redo_stack
[-1][1]()
378 self
.redo_stack
.pop()
382 def insert(self
, node
, new
, index
= 0):
383 if len(node
.childNodes
) > index
:
384 self
.insert_before(node
.childNodes
[index
], new
)
386 self
.insert_before(None, new
, parent
= node
)
388 def insert_after(self
, node
, new
):
389 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
391 def insert_before(self
, node
, new
, parent
= None):
392 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
393 "of parent's children."
395 parent
= node
.parentNode
396 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
397 for n
in new
.childNodes
[:]:
398 self
.insert_before_interal(node
, n
, parent
)
400 self
.insert_before_interal(node
, new
, parent
)
401 self
.update_all(parent
)
403 def split_qname(self
, node
, name
):
405 namespaceURI
= XMLNS_NAMESPACE
408 prefix
, localName
= name
.split(':')
409 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
413 return namespaceURI
, localName
415 def set_attrib(self
, node
, name
, value
, with_update
= 1):
416 """Set an attribute's value. If value is None, remove the attribute.
417 Returns the new attribute node, or None if removing."""
418 namespaceURI
, localName
= self
.split_qname(node
, name
)
420 if node
.hasAttributeNS(namespaceURI
, localName
):
421 old
= node
.getAttributeNS(namespaceURI
, localName
)
424 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
426 node
.setAttributeNS(namespaceURI
, name
, value
)
428 node
.removeAttributeNS(namespaceURI
, localName
)
430 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
433 self
.update_all(node
)
435 if localName
== 'xmlns':
437 return node
.attributes
[(namespaceURI
, localName
)]
439 def prefix_to_namespace(self
, node
, prefix
):
440 "Use the xmlns attributes to workout the namespace."
442 if nss
.has_key(prefix
):
443 return nss
[prefix
] or None
445 if prefix
== 'xmlns':
446 return XMLNS_NAMESPACE
447 raise Exception("No such namespace prefix '%s'" % prefix
)
451 def get_base_uri(self
, node
):
452 """Go up through the parents looking for a uri attribute.
453 If there isn't one, use the document's URI."""
455 if node
.nodeType
== Node
.DOCUMENT_NODE
:
457 if node
.hasAttributeNS(None, 'uri'):
458 return node
.getAttributeNS(None, 'uri')
459 node
= node
.parentNode
462 def load_html(self
, path
):
463 """Load this HTML file and return the new document."""
464 data
= file(path
).read()
465 data
= support
.to_html_doc(data
)
466 doc
= support
.parse_data(data
, path
)
467 self
.strip_space(doc
)