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