Set background colour when hiding/showing.
[dom-editor.git] / Dome / View.py
blobf2a189c5de7f27e9457b45862522bb1a8aee3ef2
1 from __future__ import nested_scopes
3 from rox import alert
5 import GDK
6 from support import *
7 from xml.dom import Node, XMLNS_NAMESPACE
8 from Ft.Xml import XPath
9 from Ft.Xml.XPath import FT_EXT_NAMESPACE, Context
10 from Ft.Xml.cDomlette import implementation
11 from Ft.Xml.Domlette import PrettyPrint
13 import os, re, string, types, sys
14 import urlparse
15 from StringIO import StringIO
17 from Program import Op, Block
18 from Beep import Beep
20 import time
21 import urllib, urllib2
22 import traceback
24 from constants import *
26 def elements(node):
27 out = []
28 for x in node.childNodes:
29 if x.nodeType == Node.ELEMENT_NODE:
30 out.append(x)
31 return out
33 normal_chars = string.letters + string.digits + "-"
35 fast_global = re.compile('//([-A-Za-z][-A-Za-z0-9]*:)?[-A-Za-z][-A-Za-z0-9]*$')
37 def fix_broken_html(data):
38 """Pre-parse the data before sending to tidy to fix really really broken
39 stuff (eg, MS Word output). Returns None if data is OK"""
40 if data.find('<o:p>') == -1:
41 return # Doesn't need fixing?
42 import re
43 data = data.replace('<o:p></o:p>', '')
44 data = re.sub('<!\[[^]]*\]>', '', data)
45 return data
47 def to_html(data):
48 (r, w) = os.pipe()
49 child = os.fork()
50 fixed = fix_broken_html(data)
51 if child == 0:
52 # We are the child
53 try:
54 os.close(r)
55 os.dup2(w, 1)
56 os.close(w)
57 if fixed:
58 tin = os.popen('tidy --force-output yes -q -utf8 -asxml 2>/dev/null', 'w')
59 else:
60 tin = os.popen('tidy --force-output yes -q -asxml 2>/dev/null', 'w')
61 tin.write(fixed or data)
62 tin.close()
63 finally:
64 os._exit(0)
65 os.close(w)
67 data = os.fdopen(r).read()
68 os.waitpid(child, 0)
70 return data
72 # An view contains:
73 # - A ref to a DOM document
74 # - A set of current nodes
75 # - A root node
76 # - A chroot stack
77 # It does not have any display code. It does contain code to perform actions
78 # (actions affect the document AND the view state).
80 # These actions can be repeated using '.'
81 record_again = [
82 "do_global",
83 "subst",
84 "python",
85 "ask",
86 "yank",
87 "shallow_yank",
88 "delete_node",
89 "delete_node_no_clipboard",
90 "delete_shallow",
91 "play",
92 "map",
93 "change_node",
94 "add_node",
95 "suck",
96 "http_post",
97 "put_before",
98 "put_after",
99 "put_replace",
100 "put_as_child",
101 "yank_value",
102 "yank_attribs",
103 "paste_attribs",
104 "compare",
105 "fail",
106 "do_pass",
107 "attribute",
108 "set_attrib",
109 "add_attrib",
110 "soap_send",
111 "show_canvas",
112 "show_html",
113 "select_dups",
114 "select_region",
117 def same(a, b):
118 "Recursivly compare two nodes."
119 if a.nodeType != b.nodeType or a.nodeName != b.nodeName:
120 return FALSE
121 if a.nodeValue != b.nodeValue:
122 return FALSE
123 aks = a.childNodes
124 bks = b.childNodes
125 if len(aks) != len(bks):
126 return FALSE
127 for (ak, bk) in map(None, aks, bks):
128 if not same(ak, bk):
129 return FALSE
130 return TRUE
132 class InProgress(Exception):
133 "Throw this if the operation will complete later..."
134 class Done(Exception):
135 "Thrown when the chain is completed successfully"
137 class View:
138 def __init__(self, model, callback_handlers = None):
139 """callback_handlers is an (idle_add, idle_remove) tuple"""
140 self.root = None
141 self.displays = []
142 self.lists = []
143 self.single_step = 1 # 0 = Play 1 = Step-into 2 = Step-over
144 self.model = None
145 self.chroots = [] # (model, node, marked)
146 self.foreach_stack = [] # (block, [nodes], restore-nodes, restore-marks)
147 self.current_nodes = []
148 self.clipboard = None
149 self.current_attrib = None
150 self.marked = {}
152 if not callback_handlers:
153 from rox import g
154 self.idle_add, self.idle_remove = g.idle_add, g.idle_remove
155 else:
156 self.idle_add, self.idle_remove = callback_handlers
158 self.exec_point = None # None, or (Op, Exit)
159 self.rec_point = None # None, or (Op, Exit)
160 self.op_in_progress = None
161 self.idle_cb = 0
162 self.callback_on_return = None # Called when there are no more Ops...
163 self.in_callback = 0 # (not the above callback - this is the playback one)
164 self.innermost_failure = None
165 self.call_on_done = None # Called when there is nowhere to return to
166 self.exec_stack = [] # Ops we are inside (display use only)
168 self.breakpoints = {} # (op, exit) keys, values don't matter
169 self.current_nodes = []
170 self.set_model(model)
172 def get_current(self):
173 if len(self.current_nodes) == 1:
174 return self.current_nodes[0]
175 raise Exception('This operation required exactly one selected node!')
177 def set_model(self, model):
178 assert not self.marked
179 if self.root:
180 self.move_to([])
181 self.model.unlock(self.root)
182 self.root = None
183 if self.model:
184 self.model.remove_view(self)
185 self.model.root_program.watchers.remove(self)
186 self.model = model
187 self.model.root_program.watchers.append(self)
188 model.add_view(self)
189 self.set_display_root(self.model.get_root())
190 self.move_to(self.root)
192 def running(self):
193 return self.idle_cb != 0 or self.in_callback
195 def run_new(self, callback = None):
196 "Reset the playback system (stack, step-mode and point)."
197 "Call callback(exit) when execution finishes."
198 if self.idle_cb:
199 self.idle_remove(self.idle_cb)
200 self.idle_cb = 0
201 self.single_step = 0
202 self.innermost_failure = None
203 self.call_on_done = callback
204 self.callback_on_return = None
205 while self.exec_stack:
206 self.pop_stack()
207 self.reset_foreach_stack()
208 self.status_changed()
209 self.update_stack()
211 def reset_foreach_stack(self):
212 for block, nodes, restore, mark in self.foreach_stack:
213 if mark:
214 print "reset_foreach_stack: unlocking %d nodes" % len(mark)
215 [self.model.unlock(x) for x in mark]
216 self.foreach_stack = []
218 def push_stack(self, op):
219 if not isinstance(op, Op):
220 raise Exception('push_stack: not an Op', op)
221 self.exec_stack.append(op)
222 self.update_stack(op)
224 def pop_stack(self):
225 op = self.exec_stack.pop()
226 self.update_stack(op)
228 def update_stack(self, op = None):
229 "Called when exec_stack or foreach_stack changes."
230 for l in self.lists:
231 l.update_stack(op)
233 def set_exec(self, pos):
234 if self.op_in_progress:
235 raise Exception("Operation in progress...")
236 if pos is not None:
237 assert isinstance(pos[0], Op)
238 assert pos[1] in ['next', 'fail']
239 self.exec_point = pos
240 #if pos:
241 #print "set_exec: %s:%s" % pos
242 for l in self.lists:
243 l.update_points()
245 def set_rec(self, pos):
246 self.rec_point = pos
247 for l in self.lists:
248 l.update_points()
249 self.status_changed()
251 def record_at_point(self):
252 if not self.exec_point:
253 alert("No current point!")
254 return
255 self.set_rec(self.exec_point)
256 self.set_exec(None)
258 def stop_recording(self):
259 if self.rec_point:
260 self.set_exec(self.rec_point)
261 self.set_rec(None)
262 else:
263 alert("Not recording!")
265 def may_record(self, action):
266 "Perform and, possibly, record this action"
267 rec = self.rec_point
269 if rec:
270 print "RECORD:", rec, action
271 (op, old_exit) = rec
272 if action == ['enter']:
273 new_op = Block(op.parent)
274 new_op.toggle_enter()
275 if len(self.current_nodes) > 1:
276 new_op.toggle_foreach()
277 else:
278 new_op = Op(action)
279 op.link_to(new_op, old_exit)
280 self.set_exec(rec)
281 try:
282 self.do_one_step()
283 except InProgress:
284 if isinstance(new_op, Block):
285 self.set_rec((new_op.start, 'next'))
286 else:
287 self.set_rec((new_op, 'next'))
288 return
289 play_op, exit = self.exec_point
290 # (do_one_step may have stopped recording)
291 if self.rec_point:
292 self.set_rec((new_op, exit))
293 self.set_exec(None)
294 return
296 exit = 'next'
297 try:
298 self.do_action(action)
299 except InProgress:
300 pass
301 except Beep:
302 from rox import g
303 g.gdk.beep()
304 (type, val, tb) = sys.exc_info()
305 #if not val.may_record:
306 # return 0
307 exit = 'fail'
308 except Done:
309 raise
310 except:
311 rox.report_exception()
312 raise
314 def add_display(self, display):
315 "Calls move_from(old_node) when we move and update_all() on updates."
316 self.displays.append(display)
317 #print "Added:", self.displays
319 def remove_display(self, display):
320 self.displays.remove(display)
321 #print "Removed, now:", self.displays
322 if not self.displays:
323 self.delete()
325 def update_replace(self, old, new):
326 if old == self.root:
327 self.root = new
328 if old in self.current_nodes:
329 self.model.lock(new)
330 self.model.unlock(old)
331 self.current_nodes.remove(old)
332 self.current_nodes.append(new)
333 self.update_all(new.parentNode)
334 else:
335 self.update_all(new.parentNode)
337 def has_ancestor(self, node, ancestor):
338 while node != ancestor:
339 node = node.parentNode
340 if not node:
341 return FALSE
342 return TRUE
344 def update_all(self, node):
345 for display in self.displays:
346 display.update_all(node)
348 def delete(self):
349 #print "View deleted"
350 self.model.root_program.watchers.remove(self)
351 self.move_to([])
352 for l in self.lists:
353 l.destroy()
354 self.model.unlock(self.root)
355 self.root = None
356 self.model.remove_view(self)
357 self.model = None
359 # 'nodes' may be either a node or a list of nodes.
360 # (duplicates will be removed)
361 # If it's a single node, then an 'attrib' node may also be specified
362 def move_to(self, nodes, attrib = None):
363 if self.current_nodes == nodes:
364 return
366 if attrib and attrib.nodeType != Node.ATTRIBUTE_NODE:
367 raise Exception('attrib not of type ATTRIBUTE_NODE!')
369 if type(nodes) != types.ListType:
370 assert nodes
371 nodes = [nodes]
373 if len(nodes) > 1:
374 # Remove duplicates
375 map = {}
376 old = nodes
377 nodes = []
378 for n in old:
379 if n not in map:
380 map[n] = None
381 nodes.append(n)
382 #if len(old) != len(nodes):
383 # print "(move_to: attempt to set duplicate nodes)"
385 old_nodes = self.current_nodes
386 self.current_nodes = nodes
388 for node in self.current_nodes:
389 self.model.lock(node)
390 for node in old_nodes:
391 self.model.unlock(node)
393 self.current_attrib = attrib
395 for display in self.displays:
396 display.move_from(old_nodes)
398 def move_prev_sib(self):
399 if self.get_current() == self.root or not self.get_current().previousSibling:
400 raise Beep
401 self.move_to(self.get_current().previousSibling)
403 def move_next_sib(self):
404 if self.get_current() == self.root or not self.get_current().nextSibling:
405 raise Beep
406 self.move_to(self.get_current().nextSibling)
408 def move_left(self):
409 new = []
410 for n in self.current_nodes:
411 if n == self.root:
412 raise Beep
413 p = n.parentNode
414 if p not in new:
415 new.append(p)
416 self.move_to(new)
418 def move_right(self):
419 new = []
420 for n in self.current_nodes:
421 kids = n.childNodes
422 if kids:
423 new.append(kids[0])
424 else:
425 raise Beep
426 self.move_to(new)
428 def move_home(self):
429 self.move_to(self.root)
431 def move_end(self):
432 if not self.get_current().childNodes:
433 raise Beep
434 node = self.get_current().childNodes[0]
435 while node.nextSibling:
436 node = node.nextSibling
437 self.move_to(node)
439 def set_display_root(self, root):
440 self.model.lock(root)
441 if self.root:
442 self.model.unlock(self.root)
443 self.root = root
444 self.update_all(root)
446 def enter(self):
447 """Change the display root to a COPY of the selected node.
448 Call Leave to check changes back in."""
449 node = self.get_current()
450 if node is self.root:
451 raise Beep # Locking problems if this happens...
452 if self.model.doc is not node.ownerDocument:
453 raise Exception('Current node not in view!')
454 self.move_to([])
455 self.set_marked([])
457 new_model = self.model.lock_and_copy(node)
458 self.chroots.append((self.model, node, self.marked))
459 self.set_model(new_model)
460 self.update_stack()
462 def leave(self):
463 """Undo the effect of the last chroot()."""
464 if not self.chroots:
465 raise Beep
467 self.set_marked([])
468 self.move_to([])
469 model = self.model
471 (old_model, old_node, old_marked) = self.chroots.pop()
472 self.update_stack()
474 copy = old_model.doc.importNode(self.model.get_root(), 1)
475 old_model.unlock(old_node)
476 old_model.replace_node(old_node, copy)
477 self.set_model(old_model)
478 self.move_to([copy])
479 self.set_marked(old_marked.keys())
481 if not model.views:
482 model.undo_stack = None
483 model.__dict__ = {}
484 del model
485 import gc
486 gc.collect()
488 def do_action(self, action):
489 "'action' is a tuple (function, arg1, arg2, ...)"
490 "Performs the action. Returns if action completes, or raises "
491 "InProgress if not (will call resume() later)."
492 if action[0] in record_again:
493 self.last_action = action
494 elif action[0] == 'again':
495 action = self.last_action
496 fn = getattr(self, action[0])
497 exit = 'next'
498 #print "DO:", action[0]
499 self.model.mark()
500 try:
501 new = apply(fn, action[1:])
502 except InProgress:
503 raise
504 except Beep:
505 if not self.op_in_progress:
506 raise
507 exit = 'fail'
508 new = None
509 except:
510 if not self.op_in_progress:
511 raise
512 traceback.print_exc()
513 exit = 'fail'
514 new = None
516 if self.op_in_progress:
517 op = self.op_in_progress
518 self.set_oip(None)
519 self.set_exec((op, exit))
520 if new:
521 self.move_to(new)
523 def breakpoint(self):
524 if self.breakpoints.has_key(self.exec_point):
525 return 1
526 op = self.exec_point[0]
527 if op.parent.start == op and op.next == None:
528 return 1 # Empty program
529 return 0
531 def do_one_step(self):
532 "Execute the next op after exec_point, then:"
533 "- position the point on one of the exits return."
534 "- if there is no op to perform, call callback_on_return() or raise Done."
535 "- if the operation is started but not complete, raise InProgress and "
536 " arrange to resume() later."
537 if self.op_in_progress:
538 alert("Already executing something.")
539 raise Done()
540 if not self.exec_point:
541 alert("No current playback point.")
542 raise Done()
543 (op, exit) = self.exec_point
545 if self.single_step == 0 and self.breakpoint():
546 print "Hit a breakpoint! At " + time.ctime(time.time())
547 if self.rec_point:
548 self.set_rec(None)
549 self.single_step = 1
550 for l in self.lists:
551 l.show_prog(op.get_program())
552 return
554 next = getattr(op, exit)
555 try:
556 if next:
557 self.set_oip(next)
558 self.do_action(next.action) # May raise InProgress
559 return
561 if exit == 'fail' and not self.innermost_failure:
562 #print "Setting innermost_failure on", op
563 self.innermost_failure = op
565 # If we're in a block, try exiting from it...
566 if isinstance(op.parent, Block):
567 if self.start_block_iteration(op.parent, continuing = exit):
568 return # Looping...
569 if not op.parent.is_toplevel():
570 self.set_exec((op.parent, exit))
571 return
572 except Done:
573 print "(skipped a whole program!)"
574 if self.callback_on_return:
575 cb = self.callback_on_return
576 self.callback_on_return = None
577 cb()
578 else:
579 raise Done()
581 def set_oip(self, op):
582 #print "set_oip:", self.exec_point
583 if op:
584 self.set_exec(None)
585 self.op_in_progress = op
586 for l in self.lists:
587 l.update_points()
589 def fast_global(self, name):
590 "Search for nodes with this name anywhere under the root (//name)"
591 #print "Fast global", name
592 if ':' in name:
593 (prefix, localName) = string.split(name, ':', 1)
594 else:
595 (prefix, localName) = (None, name)
596 if self.current_nodes:
597 src = self.current_nodes[-1]
598 else:
599 src = self.root
600 namespaceURI = self.model.prefix_to_namespace(src, prefix)
601 select = []
602 def add(node):
603 if node.nodeType != Node.ELEMENT_NODE:
604 return
605 if node.localName == localName and node.namespaceURI == namespaceURI:
606 select.append(node)
607 map(add, node.childNodes)
608 add(self.root)
609 self.move_to(select)
611 # Actions...
613 def do_global(self, pattern):
614 if len(self.current_nodes) != 1:
615 self.move_to(self.root)
616 if pattern[:2] == '//':
617 if fast_global.match(pattern):
618 self.fast_global(pattern[2:])
619 return
621 assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
622 try:
623 code = self.op_in_progress.cached_code
624 except:
625 from Ft.Xml.XPath import XPathParser
626 code = XPathParser.new().parse(self.macro_pattern(pattern))
627 if self.op_in_progress and pattern.find('@CURRENT@') == -1:
628 self.op_in_progress.cached_code = code
630 ns = {}
631 if not ns:
632 ns = GetAllNs(self.current_nodes[0])
633 ns['ext'] = FT_EXT_NAMESPACE
634 #print "ns is", ns
635 c = Context.Context(self.get_current(), processorNss = ns)
636 #print code
637 nodes = code.evaluate(c)
638 assert type(nodes) == list
640 #don't select the document itself!
641 for n in nodes: assert n.parentNode
643 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
644 #print "Found", nodes
645 self.move_to(nodes)
647 def select_region(self, path, ns = None):
648 if len(self.current_nodes) == 0:
649 raise Beep
650 src = self.current_nodes[-1]
651 if not ns:
652 ns = GetAllNs(src)
653 ns['ext'] = FT_EXT_NAMESPACE
654 c = Context.Context(src, [src], processorNss = ns)
655 rt = XPath.Evaluate(path, context = c)
656 node = None
657 for x in rt:
658 if not self.has_ancestor(x, self.root):
659 print "[ skipping search result above root ]"
660 continue
661 if not node:
662 node = x
663 if not node:
664 print "*** Search for '%s' in select_region failed" % path
665 print " (namespaces were '%s')" % ns
666 raise Beep
667 if node.parentNode != src.parentNode:
668 print "Nodes must have same parent!"
669 raise Beep
670 on = 0
671 selected = []
672 for n in src.parentNode.childNodes:
673 was_on = on
674 if n is src or n is node:
675 on = not was_on
676 if on or was_on:
677 selected.append(n)
678 self.move_to(selected)
680 def macro_pattern(self, pattern):
681 """Do the @CURRENT@ substitution for an XPath"""
682 if len(self.current_nodes) != 1:
683 return pattern
684 node = self.get_current()
685 if node.nodeType == Node.TEXT_NODE:
686 current = node.data
687 else:
688 if self.current_attrib:
689 current = self.current_attrib.value
690 else:
691 current = node.nodeName
692 pattern = pattern.replace('@CURRENT@', current)
693 #print "Searching for", pattern
694 return pattern
696 def do_search(self, pattern, ns = None, toggle = FALSE):
697 if len(self.current_nodes) == 0:
698 src = self.root
699 else:
700 src = self.current_nodes[-1]
702 # May be from a text_search...
703 #assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
704 try:
705 code = self.op_in_progress.cached_code
706 except:
707 from Ft.Xml.XPath import XPathParser
708 code = XPathParser.new().parse(self.macro_pattern(pattern))
709 if self.op_in_progress and pattern.find('@CURRENT@') == -1:
710 self.op_in_progress.cached_code = code
712 if not ns:
713 ns = GetAllNs(src)
714 ns['ext'] = FT_EXT_NAMESPACE
715 c = Context.Context(src, [src], processorNss = ns)
717 rt = code.evaluate(c)
718 node = None
719 for x in rt:
720 if not self.has_ancestor(x, self.root):
721 print "[ skipping search result above root ]"
722 continue
723 if not node:
724 node = x
725 #if self.node_to_line[x] > self.current_line:
726 #node = x
727 #break
728 if not node:
729 #print "*** Search for '%s' failed" % pattern
730 #print " (namespaces were '%s')" % ns
731 raise Beep
732 if toggle:
733 new = self.current_nodes[:]
734 if node in new:
735 new.remove(node)
736 else:
737 new.append(node)
738 self.move_to(new)
739 else:
740 self.move_to(node)
742 def do_text_search(self, pattern):
743 pattern = self.macro_pattern(pattern)
744 return self.do_search("//text()[ext:match('%s')]" % pattern)
746 def subst(self, replace, with):
747 "re search and replace on the current node"
748 nodes = self.current_nodes[:]
749 check = len(nodes) == 1
750 a = self.current_attrib
751 if a:
752 new, num = re.subn(replace, with, a.value)
753 if not num:
754 raise Beep
755 a = self.model.set_attrib(nodes[0], a.name, new)
756 self.move_to(nodes[0], a)
757 else:
758 self.move_to([])
759 final = []
760 for n in nodes:
761 if n.nodeType == Node.TEXT_NODE:
762 old = n.data.replace('\n', ' ')
763 new, num = re.subn(replace, with, old)
764 if check and not num:
765 self.move_to(n)
766 raise Beep
767 self.model.set_data(n, new)
768 final.append(n)
769 elif n.nodeType == Node.ELEMENT_NODE:
770 old = str(n.nodeName)
771 new, num = re.subn(replace, with, old)
772 if check and not num:
773 self.move_to(n)
774 raise Beep
775 new_ns, x = self.model.split_qname(n, new)
776 final.append(self.model.set_name(n, new_ns, new))
777 else:
778 self.move_to(n)
779 raise Beep
780 self.move_to(final)
782 def python(self, expr):
783 "Replace node with result of expr(old_value)"
784 if self.get_current().nodeType == Node.TEXT_NODE:
785 vars = {'x': self.get_current().data, 're': re, 'sub': re.sub, 'string': string}
786 result = eval(expr, vars)
787 new = self.python_to_node(result)
788 node = self.get_current()
789 self.move_to([])
790 self.model.replace_node(node, new)
791 self.move_to(new)
792 else:
793 raise Beep
795 def resume(self, exit = 'next'):
796 "After raising InProgress, call this to start moving again."
797 if self.op_in_progress:
798 op = self.op_in_progress
799 self.set_oip(None)
800 self.set_exec((op, exit))
801 if not self.single_step:
802 self.sched()
803 self.status_changed()
804 else:
805 print "(nothing to resume)"
807 def ask(self, q):
808 def ask_cb(result, self = self):
809 if result is None:
810 exit = 'fail'
811 else:
812 self.clipboard = self.model.doc.createTextNode(result)
813 exit = 'next'
814 self.resume(exit)
815 from GetArg import GetArg
816 box = GetArg('Input:', ask_cb, [q], destroy_return = 1)
817 raise InProgress
819 def python_to_node(self, data):
820 "Convert a python data structure into a tree and return the root."
821 if type(data) == types.ListType:
822 list = self.model.doc.createElementNS(DOME_NS, 'dome:list')
823 list.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:dome', DOME_NS)
824 for x in data:
825 list.appendChild(self.python_to_node(x))
826 return list
827 return self.model.doc.createTextNode(str(data))
829 def yank(self, deep = 1):
830 if self.current_attrib:
831 a = self.current_attrib
833 self.clipboard = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
834 self.clipboard.appendChild(self.model.doc.createTextNode(a.value))
835 else:
836 self.clipboard = self.model.doc.createDocumentFragment()
837 for n in self.current_nodes:
838 c = n.cloneNode(deep)
839 #print n, "->", c
840 self.clipboard.appendChild(c)
842 #print "Clip now", self.clipboard
844 def shallow_yank(self):
845 self.yank(0)
847 def delete_shallow(self):
848 nodes = self.current_nodes[:]
849 if not nodes:
850 return
851 if self.root in nodes:
852 raise Beep
853 self.shallow_yank()
854 self.move_to([])
855 for n in nodes:
856 self.model.delete_shallow(n)
857 self.move_home()
859 def delete_node_no_clipboard(self):
860 self.delete_node(yank = 0)
862 def delete_node(self, yank = 1):
863 nodes = self.current_nodes[:]
864 if not nodes:
865 return
866 if yank:
867 self.yank()
868 if self.current_attrib:
869 ca = self.current_attrib
870 self.current_attrib = None
871 self.model.set_attrib(self.get_current(), ca.name, None)
872 return
873 if self.root in nodes:
874 raise Beep
875 self.move_to([])
876 new = [x.parentNode for x in nodes]
877 self.move_to(new)
878 self.model.delete_nodes(nodes)
880 def undo(self):
881 nodes = self.current_nodes[:]
882 self.move_to([])
883 self.model.unlock(self.root)
884 try:
885 self.model.undo()
886 finally:
887 self.model.lock(self.root)
888 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
890 def redo(self):
891 nodes = self.current_nodes[:]
892 self.move_to([])
893 self.model.unlock(self.root)
894 try:
895 self.model.redo()
896 finally:
897 self.model.lock(self.root)
898 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
900 def default_done(self, exit):
901 "Called when execution of a program returns. op_in_progress has been "
902 "restored - move to the exit."
903 #print "default_done(%s)" % exit
904 if self.op_in_progress:
905 op = self.op_in_progress
906 self.set_oip(None)
907 self.set_exec((op, exit))
908 else:
909 print "No operation to return to!"
910 c = self.call_on_done
911 if c:
912 self.call_on_done = None
913 c(exit)
914 elif exit == 'fail':
915 self.jump_to_innermost_failure()
916 raise Done()
918 def jump_to_innermost_failure(self):
919 assert self.innermost_failure != None
921 print "Returning to innermost failure:", self.innermost_failure
922 self.set_exec((self.innermost_failure, 'fail'))
923 for l in self.lists:
924 if hasattr(l, 'set_innermost_failure'):
925 l.set_innermost_failure(self.innermost_failure)
927 def play(self, name, done = None):
928 "Play this macro. When it returns, restore the current op_in_progress (if any)"
929 "and call done(exit). Default for done() moves exec_point."
930 "done() is called from do_one_step() - usual rules apply."
932 prog = self.name_to_prog(name)
933 self.innermost_failure = None
935 if not done:
936 done = self.default_done
938 def cbor(self = self, op = self.op_in_progress, done = done,
939 name = name,
940 old_cbor = self.callback_on_return,
941 old_ss = self.single_step):
942 "We're in do_one_step..."
944 #print "Return from '%s'..." % name
946 if old_ss == 2 and self.single_step == 0:
947 self.single_step = old_ss
948 self.callback_on_return = old_cbor
950 o, exit = self.exec_point
951 if op:
952 #print "Resume op '%s' (%s)" % (op.program.name, op)
953 self.pop_stack()
954 self.set_oip(op)
955 return done(exit)
957 self.callback_on_return = cbor
959 if self.single_step == 2:
960 self.single_step = 0
962 if self.op_in_progress:
963 self.push_stack(self.op_in_progress)
964 self.set_oip(None)
965 self.play_block(prog.code)
966 self.sched()
967 self.status_changed()
968 raise InProgress
970 def start_block_iteration(self, block, continuing = None):
971 "True if we are going to run the block, False to exit the loop"
972 "Continuing is 'next' or 'fail' if we reached the end of the block."
973 #print "Start interation"
974 if not self.foreach_stack:
975 raise Done
976 stack_block, nodes_list, restore, old_mark = self.foreach_stack[-1]
977 if stack_block != block:
978 self.reset_foreach_stack()
979 self.update_stack()
980 raise Exception("Reached the end of a block we never entered")
982 if continuing:
983 if block.enter:
984 self.leave()
985 if block.foreach:
986 restore.extend(self.current_nodes)
987 if continuing == 'fail':
988 print "Error in block; exiting early in program", block.get_program()
989 if old_mark:
990 [self.model.unlock(x) for x in old_mark]
991 self.foreach_stack.pop()
992 self.update_stack()
993 return 0
994 while nodes_list and nodes_list[0].parentNode == None:
995 print "Skipping deleted node", nodes_list[0]
996 del nodes_list[0]
998 if not nodes_list:
999 self.foreach_stack.pop()
1000 self.update_stack()
1001 if block.foreach:
1002 nodes = filter(lambda x: self.has_ancestor(x, self.root), restore)
1003 self.move_to(nodes)
1004 if old_mark is not None:
1005 self.set_marked(old_mark)
1006 [self.model.unlock(x) for x in old_mark]
1007 return 0 # Nothing left to do
1008 nodes = nodes_list[0]
1009 del nodes_list[0]
1010 self.move_to(nodes)
1012 if nodes_list:
1013 print "[ %d after this ]" % len(nodes_list),
1014 sys.stdout.flush()
1016 if block.enter:
1017 self.enter()
1018 self.set_exec((block.start, 'next'))
1019 return 1
1021 def play_block(self, block):
1022 assert isinstance(block, Block)
1023 #print "Enter Block!"
1024 if block.foreach:
1025 list = self.current_nodes[:]
1026 else:
1027 list = [self.current_nodes[:]] # List of one item, containing everything
1029 if block.restore:
1030 marks = self.marked.copy()
1031 [self.model.lock(x) for x in marks]
1032 else:
1033 marks = None
1034 self.foreach_stack.append((block, list, [], marks))
1036 self.update_stack()
1037 if not self.start_block_iteration(block):
1038 # No nodes selected...
1039 if not block.is_toplevel():
1040 self.set_exec((block, 'next'))
1041 else:
1042 self.set_oip(None)
1043 self.set_exec((block.start, 'next'))
1044 raise Done
1046 def Block(self):
1047 assert self.op_in_progress
1048 oip = self.op_in_progress
1049 self.set_oip(None)
1050 self.play_block(oip)
1051 if not self.single_step:
1052 self.sched()
1053 raise InProgress
1055 def sched(self):
1056 if self.op_in_progress:
1057 raise Exception("Operation in progress")
1058 if self.idle_cb:
1059 raise Exception("Already playing!")
1060 self.idle_cb = self.idle_add(self.play_callback)
1062 def play_callback(self):
1063 self.idle_remove(self.idle_cb)
1064 self.idle_cb = 0
1065 try:
1066 self.in_callback = 1
1067 try:
1068 self.do_one_step()
1069 finally:
1070 self.in_callback = 0
1071 except Done:
1072 (op, exit) = self.exec_point
1073 if exit == 'fail' and self.innermost_failure:
1074 self.jump_to_innermost_failure()
1075 print "Done, at " + time.ctime(time.time())
1076 self.run_new()
1077 return 0
1078 except InProgress:
1079 #print "InProgress"
1080 return 0
1081 except:
1082 type, val, tb = sys.exc_info()
1083 list = traceback.extract_tb(tb)
1084 stack = traceback.format_list(list[-2:])
1085 ex = traceback.format_exception_only(type, val) + ['\n\n'] + stack
1086 traceback.print_exception(type, val, tb)
1087 print "Error in do_one_step(): stopping playback"
1088 node = self.op_in_progress
1089 self.set_oip(None)
1090 if node:
1091 self.set_exec((node, 'fail'))
1092 self.status_changed()
1093 return 0
1094 if self.op_in_progress or self.single_step:
1095 self.status_changed()
1096 return 0
1097 self.sched()
1098 return 0
1100 def status_changed(self):
1101 for display in self.displays:
1102 if hasattr(display, 'update_state'):
1103 display.update_state()
1105 def map(self, name):
1106 print "Map", name
1108 nodes = self.current_nodes[:]
1109 if not nodes:
1110 print "map of nothing: skipping..."
1111 return
1112 inp = [nodes, None] # Nodes, next
1113 def next(exit = exit, self = self, name = name, inp = inp):
1114 "This is called while in do_one_step() - normal rules apply."
1115 nodes, next = inp
1116 print "[ %d to go ]" % len(nodes),
1117 sys.stdout.flush()
1118 if exit == 'fail':
1119 print "Map: nodes remaining, but an error occurred..."
1120 return self.default_done(exit)
1121 while nodes and nodes[0].parentNode == None:
1122 print "Skipping deleted node", nodes[0]
1123 del nodes[0]
1124 if not nodes:
1125 return self.default_done(exit)
1126 self.move_to(nodes[0])
1127 del nodes[0]
1128 if not nodes:
1129 next = None
1130 #print "Map: calling play (%d after this)" % len(nodes)
1131 self.play(name, done = next) # Should raise InProgress
1132 if nodes is self.current_nodes:
1133 raise Exception("Slice failed!")
1134 inp[1] = next
1135 next('next')
1137 def name_to_prog(self, name):
1138 comps = string.split(name, '/')
1139 prog = self.model.root_program
1140 if prog.name != comps[0]:
1141 raise Exception("No such program as '%s'!" % name)
1142 del comps[0]
1143 while comps:
1144 prog = prog.subprograms[comps[0]]
1145 del comps[0]
1146 return prog
1148 def change_node(self, new_data):
1149 nodes = self.current_nodes
1150 if not nodes:
1151 return
1152 self.move_to([])
1153 if nodes[0].nodeType == Node.ELEMENT_NODE:
1154 # Slow, so do this here, even if vaguely incorrect...
1155 assert ' ' not in new_data
1156 if ':' in new_data:
1157 (prefix, localName) = string.split(new_data, ':', 1)
1158 else:
1159 (prefix, localName) = (None, new_data)
1160 namespaceURI = self.model.prefix_to_namespace(nodes[0], prefix)
1161 out = []
1162 for node in nodes:
1163 if node is self.root:
1164 self.model.unlock(self.root)
1165 new = self.model.set_name(node, namespaceURI, new_data)
1166 self.model.lock(new)
1167 self.root = new
1168 else:
1169 new = self.model.set_name(node, namespaceURI, new_data)
1170 out.append(new)
1171 self.move_to(out)
1172 else:
1173 for node in nodes:
1174 self.model.set_data(node, new_data)
1175 self.move_to(nodes)
1177 def add_node(self, where, data):
1178 cur = self.get_current()
1179 if where[1] == 'e':
1180 if ':' in data:
1181 (prefix, localName) = string.split(data, ':', 1)
1182 else:
1183 (prefix, localName) = (None, data)
1184 namespaceURI = self.model.prefix_to_namespace(self.get_current(), prefix)
1185 new = self.model.doc.createElementNS(namespaceURI, data)
1186 else:
1187 new = self.model.doc.createTextNode(data)
1189 try:
1190 if where[0] == 'i':
1191 self.model.insert_before(cur, new)
1192 elif where[0] == 'a':
1193 self.model.insert_after(cur, new)
1194 elif where[0] == 'e':
1195 self.model.insert_before(None, new, parent = cur)
1196 else:
1197 self.model.insert(cur, new)
1198 except:
1199 raise Beep
1201 self.move_to(new)
1203 def request_from_node(self, node, attrib):
1204 """Return a urllib2.Request object. If attrib is set then the URI is
1205 taken from that, otherwise search for a good attribute."""
1206 uri = None
1207 if node.nodeType == Node.TEXT_NODE:
1208 uri = node.nodeValue
1209 else:
1210 if attrib:
1211 uri = attrib.value
1212 elif node.hasAttributeNS(None, 'uri'):
1213 uri = node.getAttributeNS(None, 'uri')
1214 else:
1215 for attr in node.attributes.keys():
1216 uri = node.attributes[attr].value
1217 if uri.find('//') != -1 or uri.find('.htm') != -1:
1218 break
1219 if not uri:
1220 print "Can't suck", node, "(no uri attribute found)"
1221 raise Beep
1222 if uri.find('//') == -1:
1223 base = self.model.get_base_uri(node)
1224 #print "Relative URI..."
1225 if base:
1226 #print "Base URI is:", base, "add", uri
1227 uri = urlparse.urljoin(base, uri)
1228 else:
1229 pass
1230 #print "Warning: Can't find 'uri' attribute!"
1231 request = urllib2.Request(uri)
1233 return request
1235 def http_post(self):
1236 node = self.get_current()
1237 attrs = node.attributes
1238 post = []
1239 request = self.request_from_node(node, self.current_attrib)
1240 for (ns,name) in attrs.keys():
1241 if ns is not None: continue
1242 value = str(attrs[(ns, name)].value)
1243 if name.startswith('header-'):
1244 request.add_header(str(name)[7:], value)
1245 else:
1246 post.append((str(name), value))
1248 request.add_data(urllib.urlencode(post))
1249 node = self.suck_node(node, request)
1250 if node:
1251 self.move_to(node)
1253 def suck(self, md5_only = 0):
1254 nodes = self.current_nodes[:]
1255 attrib = self.current_attrib
1256 self.move_to([])
1257 final = []
1258 for x in nodes:
1259 request = self.request_from_node(x, attrib)
1260 try:
1261 new = self.suck_node(x, request, md5_only = md5_only)
1262 finally:
1263 self.move_to(x)
1264 final.append(new)
1265 self.move_to(final)
1267 def suck_md5(self):
1268 self.suck(md5_only = 1)
1270 def suck_node(self, node, request, md5_only = 0):
1271 """Load the resource specified by request and replace 'node' with the
1272 sucked data."""
1273 uri = request.get_full_url()
1274 if uri.startswith('file:///'):
1275 print "Loading", uri
1277 assert not request.has_data()
1278 stream = open(uri[7:])
1279 # (could read the mod time here...)
1280 last_mod = None
1281 else:
1282 print "Sucking", uri
1284 if request.has_data():
1285 print "POSTING", request.get_data()
1286 stream = urllib2.urlopen(request)
1287 headers = stream.info().headers
1288 last_mod = None
1289 for x in headers:
1290 if x.lower().startswith('last-modified:'):
1291 last_mod = x[14:].strip()
1292 break
1294 current_last_mod = node.getAttributeNS(None, 'last-modified')
1295 if current_last_mod and last_mod:
1296 if current_last_mod == last_mod:
1297 self.model.set_attrib(node, 'modified', None)
1298 print "not modified => not sucking!\n"
1299 return
1301 print "Fetching page contents..."
1302 data = stream.read()
1303 print "got data... tidying..."
1305 if data.startswith('<?xml'):
1306 pass
1307 else:
1308 data = to_html(data)
1310 old_md5 = node.getAttributeNS(None, 'md5_sum')
1312 import md5
1313 new_md5 = md5.new(data).hexdigest()
1315 if old_md5 and new_md5 == old_md5:
1316 self.model.set_attrib(node, 'modified', None)
1317 print "MD5 sums match => not parsing!"
1318 return node
1320 if md5_only:
1321 # This is a nasty hack left in for backwards compat.
1322 self.model.set_attrib(node, 'md5_sum', new_md5)
1323 return node
1325 print "parsing...",
1327 from Ft.Xml.InputSource import InputSourceFactory
1328 from Ft.Xml.cDomlette import nonvalParse
1329 isrc = InputSourceFactory()
1331 try:
1332 root = nonvalParse(isrc.fromString(data, uri))
1333 #ext.StripHtml(root)
1334 except:
1335 type, val, tb = sys.exc_info()
1336 traceback.print_exception(type, val, tb)
1337 print "parsing failed!"
1338 print "Data was:"
1339 print data
1340 #rox.report_exception()
1341 raise Beep
1342 else:
1343 print "parse OK...",
1345 new = node.ownerDocument.importNode(root.documentElement, 1)
1346 new.setAttributeNS(None, 'uri', uri)
1348 if last_mod:
1349 new.setAttributeNS(None, 'last-modified', last_mod)
1350 new.setAttributeNS(None, 'modified', 'yes')
1351 new.setAttributeNS(None, 'md5_sum', new_md5)
1353 self.move_to([])
1354 if node == self.root:
1355 self.model.unlock(self.root)
1356 self.model.replace_node(self.root, new)
1357 self.model.strip_space(new)
1358 self.model.lock(new)
1359 self.root = new
1360 else:
1361 self.model.replace_node(node, new)
1362 self.model.strip_space(new)
1364 print "Loaded."
1365 return new
1367 def put_before(self):
1368 node = self.get_current()
1369 if self.clipboard == None:
1370 raise Beep
1371 new = self.clipboard.cloneNode(1)
1372 try:
1373 self.model.insert_before(node, new)
1374 except:
1375 raise Beep
1377 def put_after(self):
1378 node = self.get_current()
1379 if self.clipboard == None:
1380 raise Beep
1381 new = self.clipboard.cloneNode(1)
1382 self.model.insert_after(node, new)
1384 def put_replace(self):
1385 node = self.get_current()
1386 if self.clipboard == None:
1387 print "No clipboard!"
1388 raise Beep
1389 if self.current_attrib:
1390 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1391 value = self.clipboard.childNodes[0].data
1392 else:
1393 value = self.clipboard.data
1394 a = self.current_attrib
1395 value = value.replace('\n', ' ')
1396 a = self.model.set_attrib(node, a.name, value)
1397 self.move_to(node, a)
1398 return
1399 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1400 if len(self.clipboard.childNodes) != 1:
1401 print "Multiple nodes in clipboard!"
1402 raise Beep
1403 new = self.clipboard.childNodes[0].cloneNode(1)
1404 else:
1405 new = self.clipboard.cloneNode(1)
1406 if new.nodeType != Node.ELEMENT_NODE:
1407 raise Beep
1408 self.move_to([])
1409 try:
1410 if node == self.root:
1411 self.model.unlock(self.root)
1412 try:
1413 self.model.replace_node(self.root, new)
1414 self.root = new
1415 finally:
1416 self.model.lock(self.root)
1417 else:
1418 self.model.replace_node(node, new)
1419 self.move_to(new)
1420 except:
1421 type, val, tb = sys.exc_info()
1422 traceback.print_exception(type, val, tb)
1423 print "Replace failed!"
1424 raise Beep
1426 def put_as_child_end(self):
1427 self.put_as_child(end = 1)
1429 def put_as_child(self, end = 0):
1430 node = self.get_current()
1431 if self.clipboard == None:
1432 raise Beep
1433 new = self.clipboard.cloneNode(1)
1434 if new.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1435 to = []
1436 for n in new.childNodes:
1437 to.append(n)
1438 else:
1439 to = new
1440 try:
1441 if end:
1442 self.model.insert_before(None, new, parent = node)
1443 else:
1444 self.model.insert(node, new, index = 0)
1445 except:
1446 raise Beep
1448 self.move_to(to)
1450 def yank_value(self):
1451 if not self.current_attrib:
1452 raise Beep
1453 value = self.current_attrib.value
1454 self.clipboard = self.model.doc.createTextNode(value)
1455 #print "Clip now", self.clipboard
1457 def yank_attribs(self, name = None):
1458 if name:
1459 print "yank_attribs: DEPRECATED -- use Yank instead!"
1460 self.clipboard = self.model.doc.createDocumentFragment()
1461 if name:
1462 if not self.get_current().hasAttributeNS(None, name):
1463 raise Beep
1464 attribs = [self.get_current().getAttributeNodeNS(None, name)]
1465 else:
1466 attribs = []
1467 dict = self.get_current().attributes
1468 for a in dict.keys():
1469 attribs.append(dict[a])
1471 # Make sure the attributes always come out in the same order
1472 # (helps with macros).
1473 def by_name(a, b):
1474 diff = cmp(a.name, b.name)
1475 if diff == 0:
1476 diff = cmp(a.namespaceURI, b.namespaceURI)
1477 return diff
1479 attribs.sort(by_name)
1480 for a in attribs:
1481 n = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
1482 n.appendChild(self.model.doc.createTextNode(a.value))
1483 self.clipboard.appendChild(n)
1484 #print "Clip now", self.clipboard
1486 def paste_attribs(self):
1487 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1488 attribs = self.clipboard.childNodes
1489 else:
1490 attribs = [self.clipboard]
1491 new = []
1492 for a in attribs:
1493 try:
1494 new.append((a.nodeName, a.childNodes[0].data))
1495 except:
1496 raise Beep
1497 for node in self.current_nodes:
1498 # XXX: Set NS attribs first...
1499 for (name, value) in new:
1500 self.model.set_attrib(node, name, value)
1502 def compare(self):
1503 "Ensure that all selected nodes have the same value."
1504 if len(self.current_nodes) < 2:
1505 raise Beep # Not enough nodes!
1506 base = self.current_nodes[0]
1507 for n in self.current_nodes[1:]:
1508 if not same(base, n):
1509 raise Beep(may_record = 1)
1511 def fail(self):
1512 raise Beep(may_record = 1)
1514 def do_pass(self):
1515 pass
1517 def fail_if(self, xpath):
1518 """Evaluate xpath as a boolean, and fail if true."""
1519 src = self.get_current()
1520 ns = GetAllNs(src)
1521 ns['ext'] = FT_EXT_NAMESPACE
1522 c = Context.Context(src.parentNode, [src.parentNode], processorNss = ns)
1524 rt = XPath.Evaluate(xpath, context = c)
1525 #print "Got", rt
1526 if src in rt:
1527 raise Beep(may_record = 1)
1529 def attribute(self, namespace = None, attrib = ''):
1530 node = self.get_current()
1532 if attrib == '':
1533 self.move_to(node)
1534 return
1536 if attrib == 'xmlns':
1537 attrib = None
1538 #print "(ns, attrib)", `namespace`, attrib
1540 a = node.attributes.get((namespace, attrib), None)
1542 if a:
1543 self.move_to(node, a)
1544 else:
1545 print "No such attribute"
1546 raise Beep()
1548 def set_attrib(self, value):
1549 a = self.current_attrib
1550 if not a:
1551 raise Beep()
1552 node = self.get_current()
1553 a = self.model.set_attrib(node, a.name, value)
1554 self.move_to(node, a)
1556 def add_attrib(self, UNUSED, name, value = ''):
1557 node = self.get_current()
1558 a = self.model.set_attrib(node, name, value)
1559 self.move_to(node, a)
1561 def parse_data(self, data, path):
1562 """Convert and XML document into a DOM Document."""
1563 from Ft.Xml.InputSource import InputSourceFactory
1564 from Ft.Xml.cDomlette import nonvalParse
1565 isrc = InputSourceFactory()
1567 try:
1568 doc = nonvalParse(isrc.fromString(data, path))
1569 except:
1570 type, val, tb = sys.exc_info()
1571 traceback.print_exception(type, val, tb)
1572 print "parsing failed!"
1573 print "Data was:"
1574 print data
1575 #rox.report_exception()
1576 raise Beep
1577 else:
1578 print "parse OK...",
1579 return doc
1581 def set_root_from_doc(self, doc):
1582 new = self.root.ownerDocument.importNode(doc.documentElement, 1)
1584 if self.root:
1585 self.model.unlock(self.root)
1586 self.move_to([])
1587 self.model.replace_node(self.root, new)
1588 self.model.lock(new)
1589 self.root = new
1590 self.move_to(self.root)
1592 def load_html(self, path):
1593 "Replace root with contents of this HTML file."
1594 print "Reading HTML..."
1595 data = file(path).read()
1596 data = to_html(data)
1597 doc = self.parse_data(data, path)
1598 #doc = ext.StripHtml(doc)
1599 self.set_root_from_doc(doc)
1601 def load_xml(self, path):
1602 "Replace root with contents of this XML (or Dome) file."
1603 print "Reading XML..."
1604 data = file(path).read()
1605 doc = self.parse_data(data, path)
1606 self.set_root_from_doc(doc)
1608 def load_node(self, root):
1609 new = self.model.doc.importNode(root, 1)
1611 self.model.strip_space(new)
1613 if self.root:
1614 self.model.unlock(self.root)
1615 self.move_to([])
1616 self.model.replace_node(self.root, new)
1617 self.model.lock(new)
1618 self.root = new
1619 self.move_to(self.root)
1621 def select_dups(self):
1622 node = self.get_current()
1623 select = []
1624 for n in node.parentNode.childNodes:
1625 if n is node:
1626 continue
1627 if same(node, n):
1628 select.append(n)
1629 self.move_to(select)
1631 def select_marked_region(self, attr = "unused"):
1632 select = []
1633 if len(self.marked) != 1:
1634 print "Must be exactly one marked node!"
1635 raise Beep()
1636 if len(self.current_nodes) != 1:
1637 print "Must be exactly one selected node!"
1638 raise Beep()
1639 import Path
1640 a = Path.path_to(self.get_current())
1641 b = Path.path_to(self.marked.keys()[0])
1643 while a and b and a[0] == b[0]:
1644 del a[0]
1645 del b[0]
1647 if a and b:
1648 select = []
1649 s = 0
1650 a = a[0]
1651 b = b[0]
1652 for x in a.parentNode.childNodes:
1653 if x == a:
1654 s = not s
1655 elif x == b:
1656 s = not s
1657 if s:
1658 select.append(x)
1659 self.move_to(select)
1660 else:
1661 print "One node is a parent of the other!"
1662 raise Beep()
1664 def show_html(self):
1665 from HTML import HTML
1666 HTML(self.model, self.get_current()).show()
1668 def show_canvas(self):
1669 from Canvas import Canvas
1670 Canvas(self, self.get_current()).show()
1672 def toggle_hidden(self):
1673 nodes = self.current_nodes[:]
1674 self.move_to([])
1675 for node in nodes:
1676 if node.hasAttributeNS(None, 'hidden'):
1677 new = None
1678 else:
1679 new = 'yes'
1680 self.model.set_attrib(node, 'hidden', new, with_update = 0)
1681 self.model.update_all(self.root)
1682 self.move_to(nodes)
1684 def soap_send(self):
1685 copy = node_to_xml(self.get_current())
1686 env = copy.documentElement
1688 if env.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/':
1689 alert("Not a SOAP-ENV:Envelope (bad namespace)")
1690 raise Done()
1691 if env.localName != 'Envelope':
1692 alert("Not a SOAP-ENV:Envelope (bad local name)")
1693 raise Done()
1695 if len(env.childNodes) != 2:
1696 alert("SOAP-ENV:Envelope must have one header and one body")
1697 raise Done()
1699 kids = elements(env)
1700 head = kids[0]
1701 body = kids[1]
1703 if head.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1704 head.localName != 'Head':
1705 alert("First child must be a SOAP-ENV:Head element")
1706 raise Done()
1708 if body.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1709 body.localName != 'Body':
1710 alert("Second child must be a SOAP-ENV:Body element")
1711 raise Done()
1713 sft = None
1714 for header in elements(head):
1715 if header.namespaceURI == DOME_NS and header.localName == 'soap-forward-to':
1716 sft = header
1717 break
1718 print header.namespaceURI
1719 print header.localName
1721 if not sft:
1722 alert("Head must contain a dome:soap-forward-to element")
1723 raise Done()
1725 dest = sft.childNodes[0].data
1726 parent = sft.parentNode
1727 if len(elements(parent)) == 1:
1728 sft = parent
1729 parent = sft.parentNode # Delete the whole header
1730 parent.removeChild(sft)
1732 import httplib, urlparse
1734 (scheme, addr, path, p, q, f) = urlparse.urlparse(dest, allow_fragments = 0)
1735 if scheme != 'http':
1736 alert("SOAP is only supported for 'http:' -- sorry!")
1737 raise Done()
1739 stream = StrGrab()
1740 PrettyPrint(copy, stream = stream)
1741 message = stream.data
1743 conn = httplib.HTTP(addr)
1744 conn.putrequest("POST", path)
1745 conn.putheader('Content-Type', 'text/xml; charset="utf-8"')
1746 conn.putheader('Content-Length', str(len(message)))
1747 conn.putheader('SOAPAction', '')
1748 conn.endheaders()
1749 conn.send(message)
1750 (code, r_mess, r_headers) = conn.getreply()
1752 reply = conn.getfile().read()
1753 print "Got:\n", reply
1755 reader = PyExpat.Reader() # XXX
1756 new_doc = reader.fromString(reply)
1757 print new_doc
1759 new = self.model.doc.importNode(new_doc.documentElement, 1)
1761 self.model.strip_space(new)
1763 old = self.get_current()
1764 self.move_to([])
1765 self.model.replace_node(old, new)
1766 self.move_to(new)
1768 def program_changed(self, changed_op):
1769 print "Check points..."
1770 if self.rec_point:
1771 (op, exit) = self.rec_point
1772 if not op.parent:
1773 print "Lost rec_point"
1774 self.rec_point = None
1775 if self.exec_point:
1776 (op, exit) = self.exec_point
1777 if not op.parent:
1778 print "Lost exec_point"
1779 self.exec_point = None
1780 for l in self.lists:
1781 l.update_points()
1782 self.status_changed()
1784 def prog_tree_changed(self):
1785 pass
1787 def export_all(self):
1788 doc = implementation.createDocument(DOME_NS, 'dome', None)
1789 node = self.model.root_program.to_xml(doc)
1790 doc.documentElement.appendChild(node)
1791 node = doc.createElementNS(DOME_NS, 'dome-data')
1792 doc.documentElement.appendChild(node)
1794 if self.chroots:
1795 print "*** WARNING: Saving from a chroot!"
1796 model = self.model
1797 data = doc.importNode(model.doc.documentElement, 1)
1798 node.appendChild(data)
1800 return doc
1802 def blank_all(self):
1803 doc = implementation.createDocument(None, 'root', None)
1804 self.move_home()
1805 self.clipboard = self.model.doc.createElementNS(None, 'root')
1806 self.put_replace()
1808 def mark_switch(self):
1809 new = self.marked.keys()
1810 self.set_marked(self.current_nodes)
1811 self.move_to(new)
1813 def set_marked(self, new):
1814 update = self.marked
1815 for x in self.marked.keys():
1816 self.model.unlock(x)
1817 self.marked = {}
1818 for x in new:
1819 self.model.lock(x)
1820 self.marked[x] = None
1821 update[x] = None
1822 update = update.keys()
1823 for display in self.displays:
1824 display.marked_changed(update)
1826 def mark_selection(self):
1827 self.set_marked(self.current_nodes)
1829 def clear_mark(self):
1830 self.set_marked([])
1832 def normalise(self):
1833 self.model.normalise(self.get_current())
1835 def remove_ns(self):
1836 nodes = self.current_nodes[:]
1837 self.move_to([])
1838 nodes = map(self.model.remove_ns, nodes)
1839 self.move_to(nodes)
1841 def convert_to_text(self):
1842 nodes = self.current_nodes[:]
1843 self.move_to([])
1844 nodes = map(self.model.convert_to_text, nodes)
1845 self.move_to(nodes)
1847 class StrGrab:
1848 data = ''
1850 def write(self, str):
1851 self.data += str