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 def load_ns(self
, node
):
104 assert node
.localName
== 'namespaces'
105 assert node
.namespaceURI
== constants
.DOME_NS
106 for x
in node
.childNodes
:
107 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
108 if x
.localName
!= 'ns': continue
109 if x
.namespaceURI
!= constants
.DOME_NS
: continue
111 self
.namespaces
.ensure_ns(x
.getAttributeNS(None, 'prefix'),
112 x
.getAttributeNS(None, 'uri'))
114 def import_with_ns(self
, node
):
115 """Return a copy of node for this model. All namespaces used in the subtree
116 will have been added to the global namespaces list. Prefixes will have been changed
117 as required to avoid conflicts."""
119 def ns_clone(node
, clone
):
120 if node
.nodeType
!= Node
.ELEMENT_NODE
:
121 return doc
.importNode(node
, 1)
122 if node
.namespaceURI
:
123 prefix
= self
.namespaces
.ensure_ns(node
.prefix
, node
.namespaceURI
)
124 new
= doc
.createElementNS(node
.namespaceURI
,
125 prefix
+ ':' + node
.localName
)
127 new
= doc
.createElementNS(None, node
.localName
)
128 for a
in node
.attributes
.values():
129 if a
.namespaceURI
== XMLNS_NAMESPACE
: continue
130 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
131 for k
in node
.childNodes
:
132 new
.appendChild(clone(k
, clone
))
134 new
= ns_clone(node
, ns_clone
)
137 def clear_undo(self
):
138 # Pop an (op_number, function) off one of these and call the function to
139 # move forwards or backwards in the undo history.
144 # Each series of suboperations in an undo stack which are part of a single
145 # user op will have the same number...
149 #print "GC freed:", gc.collect()
150 #print "Garbage", gc.garbage
152 def lock(self
, node
):
153 """Prevent removal of this node (or any ancestor)."""
154 #print "Locking", node.nodeName
156 self
.locks
[node
] = self
.get_locks(node
) + 1
158 self
.lock(node
.parentNode
)
160 def unlock(self
, node
):
161 """Reverse the effect of lock(). Must call unlock the same number
162 of times as lock to fully unlock the node."""
163 l
= self
.get_locks(node
)
165 self
.locks
[node
] = l
- 1
167 del self
.locks
[node
] # Or get a memory leak...
168 if node
== self
.doc
.documentElement
:
170 self
.unlock(node
.parentNode
)
173 raise Exception('unlock(%s): Node not locked!' % node
)
175 self
.unlock(node
.parentNode
)
177 def get_locks(self
, node
):
179 return self
.locks
[node
]
183 def lock_and_copy(self
, node
):
184 """Locks 'node' in the current model and returns a new model
185 with a copy of the subtree."""
186 if self
.get_locks(node
):
187 raise Exception("Can't enter locked node!")
188 m
= Model(self
.get_base_uri(node
), root_program
= self
.root_program
, do_load
= 0)
189 copy
= m
.import_with_ns(node
)
191 m
.replace_node(root
, copy
)
196 "Increment the user_op counter. Undo will undo every operation between"
201 "Return the true root node (not a view root)"
202 return self
.doc
.documentElement
204 def add_view(self
, view
):
206 "'update_all(subtree) - called when a major change occurs."
207 #print "New view:", view
208 self
.views
.append(view
)
210 def remove_view(self
, view
):
211 #print "Removing view", view
212 self
.views
.remove(view
)
213 #print "Now:", self.views
217 def update_all(self
, node
):
218 "Called when 'node' has been updated."
219 "'node' is still in the document, so deleting or replacing"
220 "a node calls this on the parent."
224 def update_replace(self
, old
, new
):
225 "Called when 'old' is replaced by 'new'."
227 v
.update_replace(old
, new
)
229 def strip_space(self
, node
= None):
231 node
= self
.doc
.documentElement
233 if node
.nodeType
== Node
.TEXT_NODE
:
234 #node.data = node.data.strip()
236 # node.parentNode.removeChild(node)
237 if not node
.data
.strip():
238 node
.parentNode
.removeChild(node
)
240 for k
in node
.childNodes
[:]:
246 def normalise(self
, node
):
247 old
= node
.cloneNode(1)
249 self
.add_undo(lambda: self
.replace_node(node
, old
))
250 self
.update_all(node
)
252 def convert_to_element(self
, node
):
253 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
255 new
= self
.doc
.createElementNS(None, node
.data
.strip())
256 self
.replace_node(node
, new
)
259 def convert_to_text(self
, node
):
260 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
261 Node
.TEXT_NODE
, Node
.ELEMENT_NODE
)
262 if node
.nodeType
== Node
.ELEMENT_NODE
:
263 new
= self
.doc
.createTextNode(node
.localName
)
265 new
= self
.doc
.createTextNode(node
.data
)
266 self
.replace_node(node
, new
)
269 def convert_to_comment(self
, node
):
270 assert node
.nodeType
in (Node
.COMMENT_NODE
, Node
.PROCESSING_INSTRUCTION_NODE
,
272 new
= self
.doc
.createComment(node
.data
)
273 self
.replace_node(node
, new
)
276 def remove_ns(self
, node
):
277 print "remove_ns: Shouldn't need this now!"
280 nss
= GetAllNs(node
.parentNode
)
281 dns
= nss
.get(None, None)
282 create
= node
.ownerDocument
.createElementNS
283 # clone is an argument to fix a wierd gc bug in python2.2
284 def ns_clone(node
, clone
):
285 if node
.nodeType
!= Node
.ELEMENT_NODE
:
286 return node
.cloneNode(1)
287 new
= create(dns
, node
.nodeName
)
288 for a
in node
.attributes
.values():
289 if a
.localName
== 'xmlns' and a
.prefix
is None:
290 print "Removing xmlns attrib on", node
292 new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
)
293 for k
in node
.childNodes
:
294 new
.appendChild(clone(k
, clone
))
296 new
= ns_clone(node
, ns_clone
)
297 self
.replace_node(node
, new
)
300 def set_name(self
, node
, namespace
, name
):
301 if self
.get_locks(node
):
302 raise Exception('Attempt to set name on locked node %s' % node
)
304 new
= node
.ownerDocument
.createElementNS(namespace
, name
)
305 self
.replace_shallow(node
, new
)
308 def replace_shallow(self
, old
, new
):
309 """Replace old with new, keeping the old children."""
310 assert not new
.childNodes
311 assert not new
.parentNode
313 old_name
= old
.nodeName
314 old_ns
= old
.namespaceURI
316 kids
= old
.childNodes
[:]
317 attrs
= old
.attributes
.values()
318 parent
= old
.parentNode
319 [ old
.removeChild(k
) for k
in kids
]
320 parent
.replaceChild(new
, old
)
321 [ new
.appendChild(k
) for k
in kids
]
322 [ new
.setAttributeNS(a
.namespaceURI
, a
.name
, a
.value
) for a
in attrs
]
324 self
.add_undo(lambda: self
.replace_shallow(new
, old
))
326 self
.update_replace(old
, new
)
329 if __main__
.no_gui_mode
:
330 def add_undo(self
, fn
):
333 def add_undo(self
, fn
):
334 self
.undo_stack
.append((self
.user_op
, fn
))
335 if not self
.doing_undo
:
338 def set_data(self
, node
, data
):
341 self
.add_undo(lambda: self
.set_data(node
, old_data
))
342 self
.update_all(node
)
344 def replace_node(self
, old
, new
):
345 if self
.get_locks(old
):
346 raise Exception('Attempt to replace locked node %s' % old
)
347 old
.parentNode
.replaceChild(new
, old
)
348 self
.add_undo(lambda: self
.replace_node(new
, old
))
350 self
.update_replace(old
, new
)
352 def delete_shallow(self
, node
):
353 """Replace node with its contents"""
354 kids
= node
.childNodes
[:]
355 next
= node
.nextSibling
356 parent
= node
.parentNode
357 for n
in kids
+ [node
]:
358 if self
.get_locks(n
):
359 raise Exception('Attempt to move/delete locked node %s' % n
)
361 self
.delete_internal(k
)
362 self
.delete_internal(node
)
364 self
.insert_before_interal(next
, k
, parent
)
365 self
.update_all(parent
)
367 def delete_nodes(self
, nodes
):
368 #print "Deleting", nodes
370 if self
.get_locks(n
):
371 raise Exception('Attempt to delete locked node %s' % n
)
374 self
.delete_internal(n
)
377 def delete_internal(self
, node
):
378 "Doesn't update display."
379 next
= node
.nextSibling
380 parent
= node
.parentNode
381 parent
.removeChild(node
)
382 self
.add_undo(lambda: self
.insert_before(next
, node
, parent
= parent
))
384 def insert_before_interal(self
, node
, new
, parent
):
385 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
386 "of parent's children."
387 assert new
.nodeType
!= Node
.DOCUMENT_FRAGMENT_NODE
388 assert parent
.nodeType
== Node
.ELEMENT_NODE
389 parent
.insertBefore(new
, node
)
390 self
.add_undo(lambda: self
.delete_nodes([new
]))
393 if not self
.undo_stack
:
394 raise Exception('Nothing to undo')
396 assert not self
.doing_undo
398 uop
= self
.undo_stack
[-1][0]
400 # Swap stacks so that the undo actions will populate the redo stack...
401 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
404 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
405 self
.redo_stack
[-1][1]()
406 self
.redo_stack
.pop()
408 (self
.undo_stack
, self
.redo_stack
) = (self
.redo_stack
, self
.undo_stack
)
412 if not self
.redo_stack
:
413 raise Exception('Nothing to redo')
415 uop
= self
.redo_stack
[-1][0]
418 while self
.redo_stack
and self
.redo_stack
[-1][0] == uop
:
419 self
.redo_stack
[-1][1]()
420 self
.redo_stack
.pop()
424 def insert(self
, node
, new
, index
= 0):
425 if len(node
.childNodes
) > index
:
426 self
.insert_before(node
.childNodes
[index
], new
)
428 self
.insert_before(None, new
, parent
= node
)
430 def insert_after(self
, node
, new
):
431 self
.insert_before(node
.nextSibling
, new
, parent
= node
.parentNode
)
433 def insert_before(self
, node
, new
, parent
= None):
434 "Insert 'new' before 'node'. If 'node' is None then insert at the end"
435 "of parent's children."
437 parent
= node
.parentNode
438 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
439 for n
in new
.childNodes
[:]:
440 self
.insert_before_interal(node
, n
, parent
)
442 self
.insert_before_interal(node
, new
, parent
)
443 self
.update_all(parent
)
445 def split_qname(self
, node
, name
):
447 namespaceURI
= XMLNS_NAMESPACE
450 prefix
, localName
= name
.split(':')
451 namespaceURI
= self
.prefix_to_namespace(node
, prefix
)
455 return namespaceURI
, localName
457 def set_attrib(self
, node
, name
, value
, with_update
= 1):
458 """Set an attribute's value. If value is None, remove the attribute.
459 Returns the new attribute node, or None if removing."""
460 namespaceURI
, localName
= self
.split_qname(node
, name
)
462 if node
.hasAttributeNS(namespaceURI
, localName
):
463 old
= node
.getAttributeNS(namespaceURI
, localName
)
466 #print "Set (%s,%s) = %s" % (namespaceURI, name, value)
468 node
.setAttributeNS(namespaceURI
, name
, value
)
470 node
.removeAttributeNS(namespaceURI
, localName
)
472 self
.add_undo(lambda: self
.set_attrib(node
, name
, old
))
475 self
.update_all(node
)
477 if localName
== 'xmlns':
479 return node
.attributes
[(namespaceURI
, localName
)]
481 def prefix_to_namespace(self
, node
, prefix
):
482 "Using attributes for namespaces was too confusing. Keep a global list instead."
483 if prefix
is None: return None
485 return self
.namespaces
.uri
[prefix
]
487 raise Exception("Namespace '%s' is not defined. Choose "
488 "View->Show namespaces from the popup menu to set it." % prefix
)
490 "Use the xmlns attributes to workout the namespace."
492 if nss
.has_key(prefix
):
493 return nss
[prefix
] or None
495 if prefix
== 'xmlns':
496 return XMLNS_NAMESPACE
497 raise Exception("No such namespace prefix '%s'" % prefix
)
501 def get_base_uri(self
, node
):
502 """Go up through the parents looking for a uri attribute.
503 If there isn't one, use the document's URI."""
505 if node
.nodeType
== Node
.DOCUMENT_NODE
:
507 if node
.hasAttributeNS(None, 'uri'):
508 return node
.getAttributeNS(None, 'uri')
509 node
= node
.parentNode
512 def load_html(self
, path
):
513 """Load this HTML file and return the new document."""
514 data
= file(path
).read()
515 data
= support
.to_html_doc(data
)
516 doc
= support
.parse_data(data
, path
)
517 self
.strip_space(doc
)