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