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'
31 from Ft
.Xml
.InputSource
import InputSourceFactory
32 isrc
= InputSourceFactory()
33 dome_data
= nonvalParse(isrc
.fromUri(dome_data
))
41 if do_load
and (path
.endswith('.html') or path
.endswith('.htm')):
42 doc
= self
.load_html(path
)
43 if not doc
and not root_program
:
44 from Ft
.Xml
.InputSource
import InputSourceFactory
45 isrc
= InputSourceFactory()
46 doc
= nonvalParse(isrc
.fromUri(path
))
48 doc
= implementation
.createDocument(None, 'root', None)
49 root
= doc
.documentElement
51 self
.root_program
= None
54 from Program
import Program
, load_dome_program
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
== 'dome-program':
60 self
.root_program
= load_dome_program(x
)
61 elif x
.localName
== 'dome-data':
62 for y
in x
.childNodes
:
63 if y
.nodeType
== Node
.ELEMENT_NODE
:
66 data_to_load
= dome_data
.documentElement
67 elif (root
.namespaceURI
== constants
.XSLT_NS
and
68 root
.localName
in ['stylesheet', 'transform']) or \
69 root
.hasAttributeNS(constants
.XSLT_NS
, 'version'):
71 self
.root_program
= xslt
.import_sheet(doc
)
72 x
= implementation
.createDocument(None, 'xslt', None)
73 data_to_load
= x
.documentElement
74 src
= get_xslt_source(x
, dome_data
)
75 data_to_load
.appendChild(x
.createElementNS(None, 'Result'))
76 data_to_load
.appendChild(src
)
82 self
.root_program
= root_program
84 if not self
.root_program
:
85 self
.root_program
= Program('Root')
88 self
.doc
= implementation
.createDocument(None, 'root', None)
90 node
= support
.import_with_ns(self
.doc
, data_to_load
)
91 self
.doc
.replaceChild(node
, self
.doc
.documentElement
)
94 self
.views
= [] # Notified when something changes
95 self
.locks
= {} # Node -> number of locks
98 # Pop an (op_number, function) off one of these and call the function to
99 # move forwards or backwards in the undo history.
104 # Each series of suboperations in an undo stack which are part of a single
105 # user op will have the same number...
109 #print "GC freed:", gc.collect()
110 #print "Garbage", gc.garbage
112 def lock(self
, node
):
113 """Prevent removal of this node (or any ancestor)."""
114 #print "Locking", node.nodeName
115 self
.locks
[node
] = self
.get_locks(node
) + 1
117 self
.lock(node
.parentNode
)
119 def unlock(self
, node
):
120 """Reverse the effect of lock(). Must call unlock the same number
121 of times as lock to fully unlock the node."""
122 l
= self
.get_locks(node
)
124 self
.locks
[node
] = l
- 1
126 del self
.locks
[node
] # Or get a memory leak...
127 if node
== self
.doc
.documentElement
:
129 self
.unlock(node
.parentNode
)
132 raise Exception('unlock(%s): Node not locked!' % node
)
134 self
.unlock(node
.parentNode
)
136 def get_locks(self
, node
):
138 return self
.locks
[node
]
142 def lock_and_copy(self
, node
):
143 """Locks 'node' in the current model and returns a new model
144 with a copy of the subtree."""
145 if self
.get_locks(node
):
146 raise Exception("Can't enter locked node!")
147 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
148 copy
= support
.import_with_ns(m
.doc
, node
)
150 m
.replace_node(root
, copy
)
155 "Increment the user_op counter. Undo will undo every operation between"
160 "Return the true root node (not a view root)"
161 return self
.doc
.documentElement
163 def add_view(self
, view
):
165 "'update_all(subtree) - called when a major change occurs."
166 #print "New view:", view
167 self
.views
.append(view
)
169 def remove_view(self
, view
):
170 #print "Removing view", view
171 self
.views
.remove(view
)
172 #print "Now:", self.views
176 def update_all(self
, node
):
177 "Called when 'node' has been updated."
178 "'node' is still in the document, so deleting or replacing"
179 "a node calls this on the parent."
183 def update_replace(self
, old
, new
):
184 "Called when 'old' is replaced by 'new'."
186 v
.update_replace(old
, new
)
188 def strip_space(self
, node
= None):
190 node
= self
.doc
.documentElement
192 if node
.nodeType
== Node
.TEXT_NODE
:
193 #node.data = node.data.strip()
195 # node.parentNode.removeChild(node)
196 if not node
.data
.strip():
197 node
.parentNode
.removeChild(node
)
199 for k
in node
.childNodes
[:]:
205 def normalise(self
, node
):
206 old
= node
.cloneNode(1)
208 self
.add_undo(lambda: self
.replace_node(node
, old
))
209 self
.update_all(node
)
211 def convert_to_text(self
, node
):
212 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
214 new
= self
.doc
.createTextNode(node
.data
)
215 self
.replace_node(node
, new
)
218 def convert_to_comment(self
, node
):
219 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
221 new
= self
.doc
.createComment(node
.data
)
222 self
.replace_node(node
, new
)
225 def remove_ns(self
, node
):
226 nss
= GetAllNs(node
.parentNode
)
227 dns
= nss
.get(None, None)
228 create
= node
.ownerDocument
.createElementNS
229 # clone is an argument to fix a wierd gc bug in python2.2
230 def ns_clone(node
, clone
):
231 if node
.nodeType
!= Node
.ELEMENT_NODE
:
232 return node
.cloneNode(1)
233 new
= create(dns
, node
.nodeName
)
234 for a
in node
.attributes
.values():
235 if a
.localName
== 'xmlns' and a
.prefix
is None:
236 print "Removing xmlns attrib on", node
238 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
239 for k
in node
.childNodes
:
240 new
.appendChild(clone(k
, clone
))
242 new
= ns_clone(node
, ns_clone
)
243 self
.replace_node(node
, new
)
246 def set_name(self
, node
, namespace
, name
):
247 if self
.get_locks(node
):
248 raise Exception('Attempt to set name on locked node %s' % node
)
250 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
251 self
.replace_shallow(node
, new
)
254 def replace_shallow(self
, old
, new
):
255 """Replace old with new, keeping the old children."""
256 assert not new
.childNodes
257 assert not new
.parentNode
259 old_name
= old
.nodeName
260 old_ns
= old
.namespaceURI
262 kids
= old
.childNodes
[:]
263 attrs
= old
.attributes
.values()
264 parent
= old
.parentNode
265 [ old
.removeChild(k
) for k
in kids
]
266 parent
.replaceChild(new
, old
)
267 [ new
.appendChild(k
) for k
in kids
]
268 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
270 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
272 self
.update_replace(old
, new
)
275 if __main__
.no_gui_mode
:
276 def add_undo(self
, fn
):
279 def add_undo(self
, fn
):
280 self
.undo_stack
.append((self
.user_op
, fn
))
281 if not self
.doing_undo
:
284 def set_data(self
, node
, data
):
287 self
.add_undo(lambda: self
.set_data(node
, old_data
))
288 self
.update_all(node
)
290 def replace_node(self
, old
, new
):
291 if self
.get_locks(old
):
292 raise Exception('Attempt to replace locked node %s' % old
)
293 old
.parentNode
.replaceChild(new
, old
)
294 self
.add_undo(lambda: self
.replace_node(new
, old
))
296 self
.update_replace(old
, new
)
298 def delete_shallow(self
, node
):
299 """Replace node with its contents"""
300 kids
= node
.childNodes
[:]
301 next
= node
.nextSibling
302 parent
= node
.parentNode
303 for n
in kids
+ [node
]:
304 if self
.get_locks(n
):
305 raise Exception('Attempt to move/delete locked node %s' % n
)
307 self
.delete_internal(k
)
308 self
.delete_internal(node
)
310 self
.insert_before_interal(next
, k
, parent
)
311 self
.update_all(parent
)
313 def delete_nodes(self
, nodes
):
314 #print "Deleting", nodes
316 if self
.get_locks(n
):
317 raise Exception('Attempt to delete locked node %s' % n
)
320 self
.delete_internal(n
)
323 def delete_internal(self
, node
):
324 "Doesn't update display."
325 next
= node
.nextSibling
326 parent
= node
.parentNode
327 parent
.removeChild(node
)
328 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
330 def insert_before_interal(self
, node
, new
, parent
):
331 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
332 "of parent's children."
333 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
334 assert parent
.nodeType
== Node
.ELEMENT_NODE
335 parent
.insertBefore(new
, node
)
336 self
.add_undo(lambda: self
.delete_nodes([new
]))
339 if not self
.undo_stack
:
340 raise Exception('Nothing to undo')
342 assert not self
.doing_undo
344 uop
= self
.undo_stack
[-1][0]
346 # Swap stacks so that the undo actions will populate the redo stack...
347 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
350 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
351 self
.redo_stack
[-1][1]()
352 self
.redo_stack
.pop()
354 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
358 if not self
.redo_stack
:
359 raise Exception('Nothing to redo')
361 uop
= self
.redo_stack
[-1][0]
364 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
365 self
.redo_stack
[-1][1]()
366 self
.redo_stack
.pop()
370 def insert(self
, node
, new
, index
= 0):
371 if len(node
.childNodes
) > index
:
372 self
.insert_before(node
.childNodes
[index
], new
)
374 self
.insert_before(None, new
, parent
= node
)
376 def insert_after(self
, node
, new
):
377 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
379 def insert_before(self
, node
, new
, parent
= None):
380 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
381 "of parent's children."
383 parent
= node
.parentNode
384 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
385 for n
in new
.childNodes
[:]:
386 self
.insert_before_interal(node
, n
, parent
)
388 self
.insert_before_interal(node
, new
, parent
)
389 self
.update_all(parent
)
391 def split_qname(self
, node
, name
):
393 namespaceURI
= XMLNS_NAMESPACE
396 prefix
, localName
= name
.split(':')
397 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
401 return namespaceURI
, localName
403 def set_attrib(self
, node
, name
, value
, with_update
= 1):
404 """Set an attribute's value. If value is None, remove the attribute.
405 Returns the new attribute node, or None if removing."""
406 namespaceURI
, localName
= self
.split_qname(node
, name
)
408 if node
.hasAttributeNS(namespaceURI
, localName
):
409 old
= node
.getAttributeNS(namespaceURI
, localName
)
412 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
414 node
.setAttributeNS(namespaceURI
, name
, value
)
416 node
.removeAttributeNS(namespaceURI
, localName
)
418 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
421 self
.update_all(node
)
423 if localName
== 'xmlns':
425 return node
.attributes
[(namespaceURI
, localName
)]
427 def prefix_to_namespace(self
, node
, prefix
):
428 "Use the xmlns attributes to workout the namespace."
430 if nss
.has_key(prefix
):
431 return nss
[prefix
] or None
433 if prefix
== 'xmlns':
434 return XMLNS_NAMESPACE
435 raise Exception("No such namespace prefix '%s'" % prefix
)
439 def get_base_uri(self
, node
):
440 """Go up through the parents looking for a uri attribute.
441 If there isn't one, use the document's URI."""
443 if node
.nodeType
== Node
.DOCUMENT_NODE
:
445 if node
.hasAttributeNS(None, 'uri'):
446 return node
.getAttributeNS(None, 'uri')
447 node
= node
.parentNode
450 def load_html(self
, path
):
451 """Load this HTML file and return the new document."""
452 data
= file(path
).read()
453 data
= support
.to_html_doc(data
)
454 doc
= support
.parse_data(data
, path
)
455 self
.strip_space(doc
)