Remove double warning.
[dom-editor.git] / Dome / View.py
blobb68eb8e4fd2c073b45837306d8d680dd402db916
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()
313 def add_display(self, display):
314 "Calls move_from(old_node) when we move and update_all() on updates."
315 self.displays.append(display)
316 #print "Added:", self.displays
318 def remove_display(self, display):
319 self.displays.remove(display)
320 #print "Removed, now:", self.displays
321 if not self.displays:
322 self.delete()
324 def update_replace(self, old, new):
325 if old == self.root:
326 self.root = new
327 if old in self.current_nodes:
328 self.model.lock(new)
329 self.model.unlock(old)
330 self.current_nodes.remove(old)
331 self.current_nodes.append(new)
332 self.update_all(new.parentNode)
333 else:
334 self.update_all(new.parentNode)
336 def has_ancestor(self, node, ancestor):
337 while node != ancestor:
338 node = node.parentNode
339 if not node:
340 return FALSE
341 return TRUE
343 def update_all(self, node):
344 for display in self.displays:
345 display.update_all(node)
347 def delete(self):
348 #print "View deleted"
349 self.model.root_program.watchers.remove(self)
350 self.move_to([])
351 for l in self.lists:
352 l.destroy()
353 self.model.unlock(self.root)
354 self.root = None
355 self.model.remove_view(self)
356 self.model = None
358 # 'nodes' may be either a node or a list of nodes.
359 # (duplicates will be removed)
360 # If it's a single node, then an 'attrib' node may also be specified
361 def move_to(self, nodes, attrib = None):
362 if self.current_nodes == nodes:
363 return
365 if attrib and attrib.nodeType != Node.ATTRIBUTE_NODE:
366 raise Exception('attrib not of type ATTRIBUTE_NODE!')
368 if type(nodes) != types.ListType:
369 assert nodes
370 nodes = [nodes]
372 if len(nodes) > 1:
373 # Remove duplicates
374 map = {}
375 old = nodes
376 nodes = []
377 for n in old:
378 if n not in map:
379 map[n] = None
380 nodes.append(n)
381 #if len(old) != len(nodes):
382 # print "(move_to: attempt to set duplicate nodes)"
384 old_nodes = self.current_nodes
385 self.current_nodes = nodes
387 for node in self.current_nodes:
388 self.model.lock(node)
389 for node in old_nodes:
390 self.model.unlock(node)
392 self.current_attrib = attrib
394 for display in self.displays:
395 display.move_from(old_nodes)
397 def move_prev_sib(self):
398 if self.get_current() == self.root or not self.get_current().previousSibling:
399 raise Beep
400 self.move_to(self.get_current().previousSibling)
402 def move_next_sib(self):
403 if self.get_current() == self.root or not self.get_current().nextSibling:
404 raise Beep
405 self.move_to(self.get_current().nextSibling)
407 def move_left(self):
408 new = []
409 for n in self.current_nodes:
410 if n == self.root:
411 raise Beep
412 p = n.parentNode
413 if p not in new:
414 new.append(p)
415 self.move_to(new)
417 def move_right(self):
418 new = []
419 for n in self.current_nodes:
420 kids = n.childNodes
421 if kids:
422 new.append(kids[0])
423 else:
424 raise Beep
425 self.move_to(new)
427 def move_home(self):
428 self.move_to(self.root)
430 def move_end(self):
431 if not self.get_current().childNodes:
432 raise Beep
433 node = self.get_current().childNodes[0]
434 while node.nextSibling:
435 node = node.nextSibling
436 self.move_to(node)
438 def set_display_root(self, root):
439 self.model.lock(root)
440 if self.root:
441 self.model.unlock(self.root)
442 self.root = root
443 self.update_all(root)
445 def enter(self):
446 """Change the display root to a COPY of the selected node.
447 Call Leave to check changes back in."""
448 node = self.get_current()
449 if node is self.root:
450 raise Beep # Locking problems if this happens...
451 if self.model.doc is not node.ownerDocument:
452 raise Exception('Current node not in view!')
453 self.move_to([])
454 self.set_marked([])
456 new_model = self.model.lock_and_copy(node)
457 self.chroots.append((self.model, node, self.marked))
458 self.set_model(new_model)
459 self.update_stack()
461 def leave(self):
462 """Undo the effect of the last chroot()."""
463 if not self.chroots:
464 raise Beep
466 self.set_marked([])
467 self.move_to([])
468 model = self.model
470 (old_model, old_node, old_marked) = self.chroots.pop()
471 self.update_stack()
473 copy = old_model.doc.importNode(self.model.get_root(), 1)
474 old_model.unlock(old_node)
475 old_model.replace_node(old_node, copy)
476 self.set_model(old_model)
477 self.move_to([copy])
478 self.set_marked(old_marked.keys())
480 if not model.views:
481 model.undo_stack = None
482 model.__dict__ = {}
483 del model
484 import gc
485 gc.collect()
487 def do_action(self, action):
488 "'action' is a tuple (function, arg1, arg2, ...)"
489 "Performs the action. Returns if action completes, or raises "
490 "InProgress if not (will call resume() later)."
491 if action[0] in record_again:
492 self.last_action = action
493 elif action[0] == 'again':
494 action = self.last_action
495 fn = getattr(self, action[0])
496 exit = 'next'
497 #print "DO:", action[0]
498 self.model.mark()
499 try:
500 new = apply(fn, action[1:])
501 except InProgress:
502 raise
503 except Beep:
504 if not self.op_in_progress:
505 raise
506 exit = 'fail'
507 new = None
508 except:
509 if not self.op_in_progress:
510 raise
511 traceback.print_exc()
512 exit = 'fail'
513 new = None
515 if self.op_in_progress:
516 op = self.op_in_progress
517 self.set_oip(None)
518 self.set_exec((op, exit))
519 if new:
520 self.move_to(new)
522 def breakpoint(self):
523 if self.breakpoints.has_key(self.exec_point):
524 return 1
525 op = self.exec_point[0]
526 if op.parent.start == op and op.next == None:
527 return 1 # Empty program
528 return 0
530 def do_one_step(self):
531 "Execute the next op after exec_point, then:"
532 "- position the point on one of the exits return."
533 "- if there is no op to perform, call callback_on_return() or raise Done."
534 "- if the operation is started but not complete, raise InProgress and "
535 " arrange to resume() later."
536 if self.op_in_progress:
537 alert("Already executing something.")
538 raise Done()
539 if not self.exec_point:
540 alert("No current playback point.")
541 raise Done()
542 (op, exit) = self.exec_point
544 if self.single_step == 0 and self.breakpoint():
545 print "Hit a breakpoint! At " + time.ctime(time.time())
546 if self.rec_point:
547 self.set_rec(None)
548 self.single_step = 1
549 for l in self.lists:
550 l.show_prog(op.get_program())
551 return
553 next = getattr(op, exit)
554 try:
555 if next:
556 self.set_oip(next)
557 self.do_action(next.action) # May raise InProgress
558 return
560 if exit == 'fail' and not self.innermost_failure:
561 #print "Setting innermost_failure on", op
562 self.innermost_failure = op
564 # If we're in a block, try exiting from it...
565 if isinstance(op.parent, Block):
566 if self.start_block_iteration(op.parent, continuing = exit):
567 return # Looping...
568 if not op.parent.is_toplevel():
569 self.set_exec((op.parent, exit))
570 return
571 except Done:
572 print "(skipped a whole program!)"
573 if self.callback_on_return:
574 cb = self.callback_on_return
575 self.callback_on_return = None
576 cb()
577 else:
578 raise Done()
580 def set_oip(self, op):
581 #print "set_oip:", self.exec_point
582 if op:
583 self.set_exec(None)
584 self.op_in_progress = op
585 for l in self.lists:
586 l.update_points()
588 def fast_global(self, name):
589 "Search for nodes with this name anywhere under the root (//name)"
590 #print "Fast global", name
591 if ':' in name:
592 (prefix, localName) = string.split(name, ':', 1)
593 else:
594 (prefix, localName) = (None, name)
595 if self.current_nodes:
596 src = self.current_nodes[-1]
597 else:
598 src = self.root
599 namespaceURI = self.model.prefix_to_namespace(src, prefix)
600 select = []
601 def add(node):
602 if node.nodeType != Node.ELEMENT_NODE:
603 return
604 if node.localName == localName and node.namespaceURI == namespaceURI:
605 select.append(node)
606 map(add, node.childNodes)
607 add(self.root)
608 self.move_to(select)
610 # Actions...
612 def do_global(self, pattern):
613 if len(self.current_nodes) != 1:
614 self.move_to(self.root)
615 if pattern[:2] == '//':
616 if fast_global.match(pattern):
617 self.fast_global(pattern[2:])
618 return
620 assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
621 try:
622 code = self.op_in_progress.cached_code
623 except:
624 from Ft.Xml.XPath import XPathParser
625 code = XPathParser.new().parse(self.macro_pattern(pattern))
626 if self.op_in_progress and pattern.find('@CURRENT@') == -1:
627 self.op_in_progress.cached_code = code
629 ns = {}
630 if not ns:
631 ns = GetAllNs(self.current_nodes[0])
632 ns['ext'] = FT_EXT_NAMESPACE
633 #print "ns is", ns
634 c = Context.Context(self.get_current(), processorNss = ns)
635 #print code
636 nodes = code.evaluate(c)
637 assert type(nodes) == list
639 #don't select the document itself!
640 for n in nodes: assert n.parentNode
642 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
643 #print "Found", nodes
644 self.move_to(nodes)
646 def select_region(self, path, ns = None):
647 if len(self.current_nodes) == 0:
648 raise Beep
649 src = self.current_nodes[-1]
650 if not ns:
651 ns = GetAllNs(src)
652 ns['ext'] = FT_EXT_NAMESPACE
653 c = Context.Context(src, [src], processorNss = ns)
654 rt = XPath.Evaluate(path, context = c)
655 node = None
656 for x in rt:
657 if not self.has_ancestor(x, self.root):
658 print "[ skipping search result above root ]"
659 continue
660 if not node:
661 node = x
662 if not node:
663 print "*** Search for '%s' in select_region failed" % path
664 print " (namespaces were '%s')" % ns
665 raise Beep
666 if node.parentNode != src.parentNode:
667 print "Nodes must have same parent!"
668 raise Beep
669 on = 0
670 selected = []
671 for n in src.parentNode.childNodes:
672 was_on = on
673 if n is src or n is node:
674 on = not was_on
675 if on or was_on:
676 selected.append(n)
677 self.move_to(selected)
679 def macro_pattern(self, pattern):
680 """Do the @CURRENT@ substitution for an XPath"""
681 if len(self.current_nodes) != 1:
682 return pattern
683 node = self.get_current()
684 if node.nodeType == Node.TEXT_NODE:
685 current = node.data
686 else:
687 if self.current_attrib:
688 current = self.current_attrib.value
689 else:
690 current = node.nodeName
691 pattern = pattern.replace('@CURRENT@', current)
692 #print "Searching for", pattern
693 return pattern
695 def do_search(self, pattern, ns = None, toggle = FALSE):
696 if len(self.current_nodes) == 0:
697 src = self.root
698 else:
699 src = self.current_nodes[-1]
701 # May be from a text_search...
702 #assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
703 try:
704 code = self.op_in_progress.cached_code
705 except:
706 from Ft.Xml.XPath import XPathParser
707 code = XPathParser.new().parse(self.macro_pattern(pattern))
708 if self.op_in_progress and pattern.find('@CURRENT@') == -1:
709 self.op_in_progress.cached_code = code
711 if not ns:
712 ns = GetAllNs(src)
713 ns['ext'] = FT_EXT_NAMESPACE
714 c = Context.Context(src, [src], processorNss = ns)
716 rt = code.evaluate(c)
717 node = None
718 for x in rt:
719 if not self.has_ancestor(x, self.root):
720 print "[ skipping search result above root ]"
721 continue
722 if not node:
723 node = x
724 #if self.node_to_line[x] > self.current_line:
725 #node = x
726 #break
727 if not node:
728 #print "*** Search for '%s' failed" % pattern
729 #print " (namespaces were '%s')" % ns
730 raise Beep
731 if toggle:
732 new = self.current_nodes[:]
733 if node in new:
734 new.remove(node)
735 else:
736 new.append(node)
737 self.move_to(new)
738 else:
739 self.move_to(node)
741 def do_text_search(self, pattern):
742 pattern = self.macro_pattern(pattern)
743 return self.do_search("//text()[ext:match('%s')]" % pattern)
745 def subst(self, replace, with):
746 "re search and replace on the current node"
747 nodes = self.current_nodes[:]
748 check = len(nodes) == 1
749 a = self.current_attrib
750 if a:
751 new, num = re.subn(replace, with, a.value)
752 if not num:
753 raise Beep
754 a = self.model.set_attrib(nodes[0], a.name, new)
755 self.move_to(nodes[0], a)
756 else:
757 self.move_to([])
758 final = []
759 for n in nodes:
760 if n.nodeType == Node.TEXT_NODE:
761 old = n.data.replace('\n', ' ')
762 new, num = re.subn(replace, with, old)
763 if check and not num:
764 self.move_to(n)
765 raise Beep
766 self.model.set_data(n, new)
767 final.append(n)
768 elif n.nodeType == Node.ELEMENT_NODE:
769 old = str(n.nodeName)
770 new, num = re.subn(replace, with, old)
771 if check and not num:
772 self.move_to(n)
773 raise Beep
774 new_ns, x = self.model.split_qname(n, new)
775 final.append(self.model.set_name(n, new_ns, new))
776 else:
777 self.move_to(n)
778 raise Beep
779 self.move_to(final)
781 def python(self, expr):
782 "Replace node with result of expr(old_value)"
783 if self.get_current().nodeType == Node.TEXT_NODE:
784 vars = {'x': self.get_current().data, 're': re, 'sub': re.sub, 'string': string}
785 result = eval(expr, vars)
786 new = self.python_to_node(result)
787 node = self.get_current()
788 self.move_to([])
789 self.model.replace_node(node, new)
790 self.move_to(new)
791 else:
792 raise Beep
794 def resume(self, exit = 'next'):
795 "After raising InProgress, call this to start moving again."
796 if self.op_in_progress:
797 op = self.op_in_progress
798 self.set_oip(None)
799 self.set_exec((op, exit))
800 if not self.single_step:
801 self.sched()
802 self.status_changed()
803 else:
804 print "(nothing to resume)"
806 def ask(self, q):
807 def ask_cb(result, self = self):
808 if result is None:
809 exit = 'fail'
810 else:
811 self.clipboard = self.model.doc.createTextNode(result)
812 exit = 'next'
813 self.resume(exit)
814 from GetArg import GetArg
815 box = GetArg('Input:', ask_cb, [q], destroy_return = 1)
816 raise InProgress
818 def python_to_node(self, data):
819 "Convert a python data structure into a tree and return the root."
820 if type(data) == types.ListType:
821 list = self.model.doc.createElementNS(DOME_NS, 'dome:list')
822 list.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:dome', DOME_NS)
823 for x in data:
824 list.appendChild(self.python_to_node(x))
825 return list
826 return self.model.doc.createTextNode(str(data))
828 def yank(self, deep = 1):
829 if self.current_attrib:
830 a = self.current_attrib
832 self.clipboard = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
833 self.clipboard.appendChild(self.model.doc.createTextNode(a.value))
834 else:
835 self.clipboard = self.model.doc.createDocumentFragment()
836 for n in self.current_nodes:
837 c = n.cloneNode(deep)
838 #print n, "->", c
839 self.clipboard.appendChild(c)
841 #print "Clip now", self.clipboard
843 def shallow_yank(self):
844 self.yank(0)
846 def delete_shallow(self):
847 nodes = self.current_nodes[:]
848 if not nodes:
849 return
850 if self.root in nodes:
851 raise Beep
852 self.shallow_yank()
853 self.move_to([])
854 for n in nodes:
855 self.model.delete_shallow(n)
856 self.move_home()
858 def delete_node_no_clipboard(self):
859 self.delete_node(yank = 0)
861 def delete_node(self, yank = 1):
862 nodes = self.current_nodes[:]
863 if not nodes:
864 return
865 if yank:
866 self.yank()
867 if self.current_attrib:
868 ca = self.current_attrib
869 self.current_attrib = None
870 self.model.set_attrib(self.get_current(), ca.name, None)
871 return
872 if self.root in nodes:
873 raise Beep
874 self.move_to([])
875 new = [x.parentNode for x in nodes]
876 self.move_to(new)
877 self.model.delete_nodes(nodes)
879 def undo(self):
880 nodes = self.current_nodes[:]
881 self.move_to([])
882 self.model.unlock(self.root)
883 try:
884 self.model.undo()
885 finally:
886 self.model.lock(self.root)
887 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
889 def redo(self):
890 nodes = self.current_nodes[:]
891 self.move_to([])
892 self.model.unlock(self.root)
893 try:
894 self.model.redo()
895 finally:
896 self.model.lock(self.root)
897 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
899 def default_done(self, exit):
900 "Called when execution of a program returns. op_in_progress has been "
901 "restored - move to the exit."
902 #print "default_done(%s)" % exit
903 if self.op_in_progress:
904 op = self.op_in_progress
905 self.set_oip(None)
906 self.set_exec((op, exit))
907 else:
908 print "No operation to return to!"
909 c = self.call_on_done
910 if c:
911 self.call_on_done = None
912 c(exit)
913 elif exit == 'fail':
914 self.jump_to_innermost_failure()
915 raise Done()
917 def jump_to_innermost_failure(self):
918 assert self.innermost_failure != None
920 print "Returning to innermost failure:", self.innermost_failure
921 self.set_exec((self.innermost_failure, 'fail'))
922 for l in self.lists:
923 if hasattr(l, 'set_innermost_failure'):
924 l.set_innermost_failure(self.innermost_failure)
926 def play(self, name, done = None):
927 "Play this macro. When it returns, restore the current op_in_progress (if any)"
928 "and call done(exit). Default for done() moves exec_point."
929 "done() is called from do_one_step() - usual rules apply."
931 prog = self.name_to_prog(name)
932 self.innermost_failure = None
934 if not done:
935 done = self.default_done
937 def cbor(self = self, op = self.op_in_progress, done = done,
938 name = name,
939 old_cbor = self.callback_on_return,
940 old_ss = self.single_step):
941 "We're in do_one_step..."
943 #print "Return from '%s'..." % name
945 if old_ss == 2 and self.single_step == 0:
946 self.single_step = old_ss
947 self.callback_on_return = old_cbor
949 o, exit = self.exec_point
950 if op:
951 #print "Resume op '%s' (%s)" % (op.program.name, op)
952 self.pop_stack()
953 self.set_oip(op)
954 return done(exit)
956 self.callback_on_return = cbor
958 if self.single_step == 2:
959 self.single_step = 0
961 if self.op_in_progress:
962 self.push_stack(self.op_in_progress)
963 self.set_oip(None)
964 self.play_block(prog.code)
965 self.sched()
966 self.status_changed()
967 raise InProgress
969 def start_block_iteration(self, block, continuing = None):
970 "True if we are going to run the block, False to exit the loop"
971 "Continuing is 'next' or 'fail' if we reached the end of the block."
972 #print "Start interation"
973 if not self.foreach_stack:
974 raise Done
975 stack_block, nodes_list, restore, old_mark = self.foreach_stack[-1]
976 if stack_block != block:
977 self.reset_foreach_stack()
978 self.update_stack()
979 raise Exception("Reached the end of a block we never entered")
981 if continuing:
982 if block.enter:
983 self.leave()
984 if block.foreach:
985 restore.extend(self.current_nodes)
986 if continuing == 'fail':
987 print "Error in block; exiting early in program", block.get_program()
988 if old_mark:
989 [self.model.unlock(x) for x in old_mark]
990 self.foreach_stack.pop()
991 self.update_stack()
992 return 0
993 while nodes_list and nodes_list[0].parentNode == None:
994 print "Skipping deleted node", nodes_list[0]
995 del nodes_list[0]
997 if not nodes_list:
998 self.foreach_stack.pop()
999 self.update_stack()
1000 if block.foreach:
1001 nodes = filter(lambda x: self.has_ancestor(x, self.root), restore)
1002 self.move_to(nodes)
1003 if old_mark is not None:
1004 self.set_marked(old_mark)
1005 [self.model.unlock(x) for x in old_mark]
1006 return 0 # Nothing left to do
1007 nodes = nodes_list[0]
1008 del nodes_list[0]
1009 self.move_to(nodes)
1011 if nodes_list:
1012 print "[ %d after this ]" % len(nodes_list),
1013 sys.stdout.flush()
1015 if block.enter:
1016 self.enter()
1017 self.set_exec((block.start, 'next'))
1018 return 1
1020 def play_block(self, block):
1021 assert isinstance(block, Block)
1022 #print "Enter Block!"
1023 if block.foreach:
1024 list = self.current_nodes[:]
1025 else:
1026 list = [self.current_nodes[:]] # List of one item, containing everything
1028 if block.restore:
1029 marks = self.marked.copy()
1030 [self.model.lock(x) for x in marks]
1031 else:
1032 marks = None
1033 self.foreach_stack.append((block, list, [], marks))
1035 self.update_stack()
1036 if not self.start_block_iteration(block):
1037 # No nodes selected...
1038 if not block.is_toplevel():
1039 self.set_exec((block, 'next'))
1040 else:
1041 self.set_oip(None)
1042 self.set_exec((block.start, 'next'))
1043 raise Done
1045 def Block(self):
1046 assert self.op_in_progress
1047 oip = self.op_in_progress
1048 self.set_oip(None)
1049 self.play_block(oip)
1050 if not self.single_step:
1051 self.sched()
1052 raise InProgress
1054 def sched(self):
1055 if self.op_in_progress:
1056 raise Exception("Operation in progress")
1057 if self.idle_cb:
1058 raise Exception("Already playing!")
1059 self.idle_cb = self.idle_add(self.play_callback)
1061 def play_callback(self):
1062 self.idle_remove(self.idle_cb)
1063 self.idle_cb = 0
1064 try:
1065 self.in_callback = 1
1066 try:
1067 self.do_one_step()
1068 finally:
1069 self.in_callback = 0
1070 except Done:
1071 (op, exit) = self.exec_point
1072 if exit == 'fail' and self.innermost_failure:
1073 self.jump_to_innermost_failure()
1074 print "Done, at " + time.ctime(time.time())
1075 self.run_new()
1076 return 0
1077 except InProgress:
1078 #print "InProgress"
1079 return 0
1080 except:
1081 type, val, tb = sys.exc_info()
1082 list = traceback.extract_tb(tb)
1083 stack = traceback.format_list(list[-2:])
1084 ex = traceback.format_exception_only(type, val) + ['\n\n'] + stack
1085 traceback.print_exception(type, val, tb)
1086 print "Error in do_one_step(): stopping playback"
1087 node = self.op_in_progress
1088 self.set_oip(None)
1089 if node:
1090 self.set_exec((node, 'fail'))
1091 self.status_changed()
1092 return 0
1093 if self.op_in_progress or self.single_step:
1094 self.status_changed()
1095 return 0
1096 self.sched()
1097 return 0
1099 def status_changed(self):
1100 for display in self.displays:
1101 if hasattr(display, 'update_state'):
1102 display.update_state()
1104 def map(self, name):
1105 print "Map", name
1107 nodes = self.current_nodes[:]
1108 if not nodes:
1109 print "map of nothing: skipping..."
1110 return
1111 inp = [nodes, None] # Nodes, next
1112 def next(exit = exit, self = self, name = name, inp = inp):
1113 "This is called while in do_one_step() - normal rules apply."
1114 nodes, next = inp
1115 print "[ %d to go ]" % len(nodes),
1116 sys.stdout.flush()
1117 if exit == 'fail':
1118 print "Map: nodes remaining, but an error occurred..."
1119 return self.default_done(exit)
1120 while nodes and nodes[0].parentNode == None:
1121 print "Skipping deleted node", nodes[0]
1122 del nodes[0]
1123 if not nodes:
1124 return self.default_done(exit)
1125 self.move_to(nodes[0])
1126 del nodes[0]
1127 if not nodes:
1128 next = None
1129 #print "Map: calling play (%d after this)" % len(nodes)
1130 self.play(name, done = next) # Should raise InProgress
1131 if nodes is self.current_nodes:
1132 raise Exception("Slice failed!")
1133 inp[1] = next
1134 next('next')
1136 def name_to_prog(self, name):
1137 comps = string.split(name, '/')
1138 prog = self.model.root_program
1139 if prog.name != comps[0]:
1140 raise Exception("No such program as '%s'!" % name)
1141 del comps[0]
1142 while comps:
1143 prog = prog.subprograms[comps[0]]
1144 del comps[0]
1145 return prog
1147 def change_node(self, new_data):
1148 nodes = self.current_nodes
1149 if not nodes:
1150 return
1151 self.move_to([])
1152 if nodes[0].nodeType == Node.ELEMENT_NODE:
1153 # Slow, so do this here, even if vaguely incorrect...
1154 assert ' ' not in new_data
1155 if ':' in new_data:
1156 (prefix, localName) = string.split(new_data, ':', 1)
1157 else:
1158 (prefix, localName) = (None, new_data)
1159 namespaceURI = self.model.prefix_to_namespace(nodes[0], prefix)
1160 out = []
1161 for node in nodes:
1162 if node is self.root:
1163 self.model.unlock(self.root)
1164 new = self.model.set_name(node, namespaceURI, new_data)
1165 self.model.lock(new)
1166 self.root = new
1167 else:
1168 new = self.model.set_name(node, namespaceURI, new_data)
1169 out.append(new)
1170 self.move_to(out)
1171 else:
1172 for node in nodes:
1173 self.model.set_data(node, new_data)
1174 self.move_to(nodes)
1176 def add_node(self, where, data):
1177 cur = self.get_current()
1178 if where[1] == 'e':
1179 if ':' in data:
1180 (prefix, localName) = string.split(data, ':', 1)
1181 else:
1182 (prefix, localName) = (None, data)
1183 namespaceURI = self.model.prefix_to_namespace(self.get_current(), prefix)
1184 new = self.model.doc.createElementNS(namespaceURI, data)
1185 else:
1186 new = self.model.doc.createTextNode(data)
1188 try:
1189 if where[0] == 'i':
1190 self.model.insert_before(cur, new)
1191 elif where[0] == 'a':
1192 self.model.insert_after(cur, new)
1193 elif where[0] == 'e':
1194 self.model.insert_before(None, new, parent = cur)
1195 else:
1196 self.model.insert(cur, new)
1197 except:
1198 raise Beep
1200 self.move_to(new)
1202 def request_from_node(self, node, attrib):
1203 """Return a urllib2.Request object. If attrib is set then the URI is
1204 taken from that, otherwise search for a good attribute."""
1205 uri = None
1206 if node.nodeType == Node.TEXT_NODE:
1207 uri = node.nodeValue
1208 else:
1209 if attrib:
1210 uri = attrib.value
1211 elif node.hasAttributeNS(None, 'uri'):
1212 uri = node.getAttributeNS(None, 'uri')
1213 else:
1214 for attr in node.attributes.keys():
1215 uri = node.attributes[attr].value
1216 if uri.find('//') != -1 or uri.find('.htm') != -1:
1217 break
1218 if not uri:
1219 print "Can't suck", node, "(no uri attribute found)"
1220 raise Beep
1221 if uri.find('//') == -1:
1222 base = self.model.get_base_uri(node)
1223 #print "Relative URI..."
1224 if base:
1225 #print "Base URI is:", base, "add", uri
1226 uri = urlparse.urljoin(base, uri)
1227 else:
1228 pass
1229 #print "Warning: Can't find 'uri' attribute!"
1230 request = urllib2.Request(uri)
1232 return request
1234 def http_post(self):
1235 node = self.get_current()
1236 attrs = node.attributes
1237 post = []
1238 request = self.request_from_node(node, self.current_attrib)
1239 for (ns,name) in attrs.keys():
1240 if ns is not None: continue
1241 value = str(attrs[(ns, name)].value)
1242 if name.startswith('header-'):
1243 request.add_header(str(name)[7:], value)
1244 else:
1245 post.append((str(name), value))
1247 request.add_data(urllib.urlencode(post))
1248 node = self.suck_node(node, request)
1249 if node:
1250 self.move_to(node)
1252 def suck(self, md5_only = 0):
1253 nodes = self.current_nodes[:]
1254 attrib = self.current_attrib
1255 self.move_to([])
1256 final = []
1257 for x in nodes:
1258 request = self.request_from_node(x, attrib)
1259 try:
1260 new = self.suck_node(x, request, md5_only = md5_only)
1261 finally:
1262 self.move_to(x)
1263 final.append(new)
1264 self.move_to(final)
1266 def suck_md5(self):
1267 self.suck(md5_only = 1)
1269 def suck_node(self, node, request, md5_only = 0):
1270 """Load the resource specified by request and replace 'node' with the
1271 sucked data."""
1272 uri = request.get_full_url()
1273 if uri.startswith('file:///'):
1274 print "Loading", uri
1276 assert not request.has_data()
1277 stream = open(uri[7:])
1278 # (could read the mod time here...)
1279 last_mod = None
1280 else:
1281 print "Sucking", uri
1283 if request.has_data():
1284 print "POSTING", request.get_data()
1285 stream = urllib2.urlopen(request)
1286 headers = stream.info().headers
1287 last_mod = None
1288 for x in headers:
1289 if x.lower().startswith('last-modified:'):
1290 last_mod = x[14:].strip()
1291 break
1293 current_last_mod = node.getAttributeNS(None, 'last-modified')
1294 if current_last_mod and last_mod:
1295 if current_last_mod == last_mod:
1296 self.model.set_attrib(node, 'modified', None)
1297 print "not modified => not sucking!\n"
1298 return
1300 print "Fetching page contents..."
1301 data = stream.read()
1302 print "got data... tidying..."
1304 if data.startswith('<?xml'):
1305 pass
1306 else:
1307 data = to_html(data)
1309 old_md5 = node.getAttributeNS(None, 'md5_sum')
1311 import md5
1312 new_md5 = md5.new(data).hexdigest()
1314 if old_md5 and new_md5 == old_md5:
1315 self.model.set_attrib(node, 'modified', None)
1316 print "MD5 sums match => not parsing!"
1317 return node
1319 if md5_only:
1320 # This is a nasty hack left in for backwards compat.
1321 self.model.set_attrib(node, 'md5_sum', new_md5)
1322 return node
1324 print "parsing...",
1326 from Ft.Xml.InputSource import InputSourceFactory
1327 from Ft.Xml.cDomlette import nonvalParse
1328 isrc = InputSourceFactory()
1330 try:
1331 root = nonvalParse(isrc.fromString(data, uri))
1332 #ext.StripHtml(root)
1333 except:
1334 type, val, tb = sys.exc_info()
1335 traceback.print_exception(type, val, tb)
1336 print "parsing failed!"
1337 print "Data was:"
1338 print data
1339 #rox.report_exception()
1340 raise Beep
1341 else:
1342 print "parse OK...",
1344 new = node.ownerDocument.importNode(root.documentElement, 1)
1345 new.setAttributeNS(None, 'uri', uri)
1347 if last_mod:
1348 new.setAttributeNS(None, 'last-modified', last_mod)
1349 new.setAttributeNS(None, 'modified', 'yes')
1350 new.setAttributeNS(None, 'md5_sum', new_md5)
1352 self.move_to([])
1353 if node == self.root:
1354 self.model.unlock(self.root)
1355 self.model.replace_node(self.root, new)
1356 self.model.strip_space(new)
1357 self.model.lock(new)
1358 self.root = new
1359 else:
1360 self.model.replace_node(node, new)
1361 self.model.strip_space(new)
1363 print "Loaded."
1364 return new
1366 def put_before(self):
1367 node = self.get_current()
1368 if self.clipboard == None:
1369 raise Beep
1370 new = self.clipboard.cloneNode(1)
1371 try:
1372 self.model.insert_before(node, new)
1373 except:
1374 raise Beep
1376 def put_after(self):
1377 node = self.get_current()
1378 if self.clipboard == None:
1379 raise Beep
1380 new = self.clipboard.cloneNode(1)
1381 self.model.insert_after(node, new)
1383 def put_replace(self):
1384 node = self.get_current()
1385 if self.clipboard == None:
1386 print "No clipboard!"
1387 raise Beep
1388 if self.current_attrib:
1389 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1390 value = self.clipboard.childNodes[0].data
1391 else:
1392 value = self.clipboard.data
1393 a = self.current_attrib
1394 value = value.replace('\n', ' ')
1395 a = self.model.set_attrib(node, a.name, value)
1396 self.move_to(node, a)
1397 return
1398 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1399 if len(self.clipboard.childNodes) != 1:
1400 print "Multiple nodes in clipboard!"
1401 raise Beep
1402 new = self.clipboard.childNodes[0].cloneNode(1)
1403 else:
1404 new = self.clipboard.cloneNode(1)
1405 if new.nodeType != Node.ELEMENT_NODE:
1406 raise Beep
1407 self.move_to([])
1408 try:
1409 if node == self.root:
1410 self.model.unlock(self.root)
1411 try:
1412 self.model.replace_node(self.root, new)
1413 self.root = new
1414 finally:
1415 self.model.lock(self.root)
1416 else:
1417 self.model.replace_node(node, new)
1418 self.move_to(new)
1419 except:
1420 type, val, tb = sys.exc_info()
1421 traceback.print_exception(type, val, tb)
1422 print "Replace failed!"
1423 raise Beep
1425 def put_as_child_end(self):
1426 self.put_as_child(end = 1)
1428 def put_as_child(self, end = 0):
1429 node = self.get_current()
1430 if self.clipboard == None:
1431 raise Beep
1432 new = self.clipboard.cloneNode(1)
1433 if new.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1434 to = []
1435 for n in new.childNodes:
1436 to.append(n)
1437 else:
1438 to = new
1439 try:
1440 if end:
1441 self.model.insert_before(None, new, parent = node)
1442 else:
1443 self.model.insert(node, new, index = 0)
1444 except:
1445 raise Beep
1447 self.move_to(to)
1449 def yank_value(self):
1450 if not self.current_attrib:
1451 raise Beep
1452 value = self.current_attrib.value
1453 self.clipboard = self.model.doc.createTextNode(value)
1454 #print "Clip now", self.clipboard
1456 def yank_attribs(self, name = None):
1457 if name:
1458 print "yank_attribs: DEPRECATED -- use Yank instead!"
1459 self.clipboard = self.model.doc.createDocumentFragment()
1460 if name:
1461 if not self.get_current().hasAttributeNS(None, name):
1462 raise Beep
1463 attribs = [self.get_current().getAttributeNodeNS(None, name)]
1464 else:
1465 attribs = []
1466 dict = self.get_current().attributes
1467 for a in dict.keys():
1468 attribs.append(dict[a])
1470 # Make sure the attributes always come out in the same order
1471 # (helps with macros).
1472 def by_name(a, b):
1473 diff = cmp(a.name, b.name)
1474 if diff == 0:
1475 diff = cmp(a.namespaceURI, b.namespaceURI)
1476 return diff
1478 attribs.sort(by_name)
1479 for a in attribs:
1480 n = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
1481 n.appendChild(self.model.doc.createTextNode(a.value))
1482 self.clipboard.appendChild(n)
1483 #print "Clip now", self.clipboard
1485 def paste_attribs(self):
1486 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1487 attribs = self.clipboard.childNodes
1488 else:
1489 attribs = [self.clipboard]
1490 new = []
1491 for a in attribs:
1492 try:
1493 new.append((a.nodeName, a.childNodes[0].data))
1494 except:
1495 raise Beep
1496 for node in self.current_nodes:
1497 # XXX: Set NS attribs first...
1498 for (name, value) in new:
1499 self.model.set_attrib(node, name, value)
1501 def compare(self):
1502 "Ensure that all selected nodes have the same value."
1503 if len(self.current_nodes) < 2:
1504 raise Beep # Not enough nodes!
1505 base = self.current_nodes[0]
1506 for n in self.current_nodes[1:]:
1507 if not same(base, n):
1508 raise Beep(may_record = 1)
1510 def fail(self):
1511 raise Beep(may_record = 1)
1513 def do_pass(self):
1514 pass
1516 def fail_if(self, xpath):
1517 """Evaluate xpath as a boolean, and fail if true."""
1518 src = self.get_current()
1519 ns = GetAllNs(src)
1520 ns['ext'] = FT_EXT_NAMESPACE
1521 c = Context.Context(src.parentNode, [src.parentNode], processorNss = ns)
1523 rt = XPath.Evaluate(xpath, context = c)
1524 #print "Got", rt
1525 if src in rt:
1526 raise Beep(may_record = 1)
1528 def attribute(self, namespace = None, attrib = ''):
1529 node = self.get_current()
1531 if attrib == '':
1532 self.move_to(node)
1533 return
1535 if attrib == 'xmlns':
1536 attrib = None
1537 #print "(ns, attrib)", `namespace`, attrib
1539 a = node.attributes.get((namespace, attrib), None)
1541 if a:
1542 self.move_to(node, a)
1543 else:
1544 print "No such attribute"
1545 raise Beep()
1547 def set_attrib(self, value):
1548 a = self.current_attrib
1549 if not a:
1550 raise Beep()
1551 node = self.get_current()
1552 a = self.model.set_attrib(node, a.name, value)
1553 self.move_to(node, a)
1555 def add_attrib(self, UNUSED, name, value = ''):
1556 node = self.get_current()
1557 a = self.model.set_attrib(node, name, value)
1558 self.move_to(node, a)
1560 def parse_data(self, data, path):
1561 """Convert and XML document into a DOM Document."""
1562 from Ft.Xml.InputSource import InputSourceFactory
1563 from Ft.Xml.cDomlette import nonvalParse
1564 isrc = InputSourceFactory()
1566 try:
1567 doc = nonvalParse(isrc.fromString(data, path))
1568 except:
1569 type, val, tb = sys.exc_info()
1570 traceback.print_exception(type, val, tb)
1571 print "parsing failed!"
1572 print "Data was:"
1573 print data
1574 #rox.report_exception()
1575 raise Beep
1576 else:
1577 print "parse OK...",
1578 return doc
1580 def set_root_from_doc(self, doc):
1581 new = self.root.ownerDocument.importNode(doc.documentElement, 1)
1583 if self.root:
1584 self.model.unlock(self.root)
1585 self.move_to([])
1586 self.model.replace_node(self.root, new)
1587 self.model.lock(new)
1588 self.root = new
1589 self.move_to(self.root)
1591 def load_html(self, path):
1592 "Replace root with contents of this HTML file."
1593 print "Reading HTML..."
1594 data = file(path).read()
1595 data = to_html(data)
1596 doc = self.parse_data(data, path)
1597 #doc = ext.StripHtml(doc)
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 = self.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.hasAttributeNS(None, 'hidden'):
1676 new = None
1677 else:
1678 new = 'yes'
1679 self.model.set_attrib(node, 'hidden', new, with_update = 0)
1680 self.model.update_all(self.root)
1681 self.move_to(nodes)
1683 def soap_send(self):
1684 copy = node_to_xml(self.get_current())
1685 env = copy.documentElement
1687 if env.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/':
1688 alert("Not a SOAP-ENV:Envelope (bad namespace)")
1689 raise Done()
1690 if env.localName != 'Envelope':
1691 alert("Not a SOAP-ENV:Envelope (bad local name)")
1692 raise Done()
1694 if len(env.childNodes) != 2:
1695 alert("SOAP-ENV:Envelope must have one header and one body")
1696 raise Done()
1698 kids = elements(env)
1699 head = kids[0]
1700 body = kids[1]
1702 if head.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1703 head.localName != 'Head':
1704 alert("First child must be a SOAP-ENV:Head element")
1705 raise Done()
1707 if body.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1708 body.localName != 'Body':
1709 alert("Second child must be a SOAP-ENV:Body element")
1710 raise Done()
1712 sft = None
1713 for header in elements(head):
1714 if header.namespaceURI == DOME_NS and header.localName == 'soap-forward-to':
1715 sft = header
1716 break
1717 print header.namespaceURI
1718 print header.localName
1720 if not sft:
1721 alert("Head must contain a dome:soap-forward-to element")
1722 raise Done()
1724 dest = sft.childNodes[0].data
1725 parent = sft.parentNode
1726 if len(elements(parent)) == 1:
1727 sft = parent
1728 parent = sft.parentNode # Delete the whole header
1729 parent.removeChild(sft)
1731 import httplib, urlparse
1733 (scheme, addr, path, p, q, f) = urlparse.urlparse(dest, allow_fragments = 0)
1734 if scheme != 'http':
1735 alert("SOAP is only supported for 'http:' -- sorry!")
1736 raise Done()
1738 stream = StrGrab()
1739 PrettyPrint(copy, stream = stream)
1740 message = stream.data
1742 conn = httplib.HTTP(addr)
1743 conn.putrequest("POST", path)
1744 conn.putheader('Content-Type', 'text/xml; charset="utf-8"')
1745 conn.putheader('Content-Length', str(len(message)))
1746 conn.putheader('SOAPAction', '')
1747 conn.endheaders()
1748 conn.send(message)
1749 (code, r_mess, r_headers) = conn.getreply()
1751 reply = conn.getfile().read()
1752 print "Got:\n", reply
1754 reader = PyExpat.Reader() # XXX
1755 new_doc = reader.fromString(reply)
1756 print new_doc
1758 new = self.model.doc.importNode(new_doc.documentElement, 1)
1760 self.model.strip_space(new)
1762 old = self.get_current()
1763 self.move_to([])
1764 self.model.replace_node(old, new)
1765 self.move_to(new)
1767 def program_changed(self, changed_op):
1768 print "Check points..."
1769 if self.rec_point:
1770 (op, exit) = self.rec_point
1771 if not op.parent:
1772 print "Lost rec_point"
1773 self.rec_point = None
1774 if self.exec_point:
1775 (op, exit) = self.exec_point
1776 if not op.parent:
1777 print "Lost exec_point"
1778 self.exec_point = None
1779 for l in self.lists:
1780 l.update_points()
1781 self.status_changed()
1783 def prog_tree_changed(self):
1784 pass
1786 def export_all(self):
1787 doc = implementation.createDocument(DOME_NS, 'dome', None)
1788 node = self.model.root_program.to_xml(doc)
1789 doc.documentElement.appendChild(node)
1790 node = doc.createElementNS(DOME_NS, 'dome-data')
1791 doc.documentElement.appendChild(node)
1793 if self.chroots:
1794 print "*** WARNING: Saving from a chroot!"
1795 model = self.model
1796 data = doc.importNode(model.doc.documentElement, 1)
1797 node.appendChild(data)
1799 return doc
1801 def blank_all(self):
1802 doc = implementation.createDocument(None, 'root', None)
1803 self.move_home()
1804 self.clipboard = self.model.doc.createElementNS(None, 'root')
1805 self.put_replace()
1807 def mark_switch(self):
1808 new = self.marked.keys()
1809 self.set_marked(self.current_nodes)
1810 self.move_to(new)
1812 def set_marked(self, new):
1813 update = self.marked
1814 for x in self.marked.keys():
1815 self.model.unlock(x)
1816 self.marked = {}
1817 for x in new:
1818 self.model.lock(x)
1819 self.marked[x] = None
1820 update[x] = None
1821 update = update.keys()
1822 for display in self.displays:
1823 display.marked_changed(update)
1825 def mark_selection(self):
1826 self.set_marked(self.current_nodes)
1828 def clear_mark(self):
1829 self.set_marked([])
1831 def normalise(self):
1832 self.model.normalise(self.get_current())
1834 def remove_ns(self):
1835 nodes = self.current_nodes[:]
1836 self.move_to([])
1837 nodes = map(self.model.remove_ns, nodes)
1838 self.move_to(nodes)
1840 def convert_to_text(self):
1841 nodes = self.current_nodes[:]
1842 self.move_to([])
1843 nodes = map(self.model.convert_to_text, nodes)
1844 self.move_to(nodes)
1846 class StrGrab:
1847 data = ''
1849 def write(self, str):
1850 self.data += str