Trying to record an Enter creates an Enter/Leave block instead.
[dom-editor.git] / Dome / View.py
blob1a81d9bcc9c85b50624979b2be115a856b908d29
1 from __future__ import nested_scopes
3 import GDK
4 from support import *
5 from rox import support
6 from xml.dom import Node, ext, XMLNS_NAMESPACE
7 from Ft.Xml import XPath
8 from Ft.Xml.XPath import FT_EXT_NAMESPACE, Context
9 from xml.dom.ext.reader import PyExpat
10 from Ft.Xml.cDomlette import implementation
12 import os, re, string, types, sys
13 import urlparse
14 import Html
15 from StringIO import StringIO
17 from Program import Op, Block
18 from Beep import Beep
20 import time
21 import urllib
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)
147 self.current_nodes = []
148 self.clipboard = None
149 self.current_attrib = None
150 self.marked = {}
152 if not callback_handlers:
153 import gtk
154 self.idle_add, self.idle_remove = gtk.idle_add, gtk.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.status_changed()
209 def push_stack(self, op):
210 if not isinstance(op, Op):
211 raise Exception('push_stack: not an Op', op)
212 self.exec_stack.append(op)
213 for l in self.lists:
214 l.update_stack(op)
216 def pop_stack(self):
217 op = self.exec_stack.pop()
218 for l in self.lists:
219 l.update_stack(op)
221 def set_exec(self, pos):
222 if self.op_in_progress:
223 raise Exception("Operation in progress...")
224 if pos is not None:
225 assert isinstance(pos[0], Op)
226 assert pos[1] in ['next', 'fail']
227 self.exec_point = pos
228 #if pos:
229 #print "set_exec: %s:%s" % pos
230 for l in self.lists:
231 l.update_points()
233 def set_rec(self, pos):
234 self.rec_point = pos
235 for l in self.lists:
236 l.update_points()
237 self.status_changed()
239 def record_at_point(self):
240 if not self.exec_point:
241 support.report_error("No current point!")
242 return
243 self.set_rec(self.exec_point)
244 self.set_exec(None)
246 def stop_recording(self):
247 if self.rec_point:
248 self.set_exec(self.rec_point)
249 self.set_rec(None)
250 else:
251 support.report_error("Not recording!")
253 def may_record(self, action):
254 "Perform and, possibly, record this action"
255 rec = self.rec_point
257 if rec:
258 print "RECORD:", rec, action
259 (op, old_exit) = rec
260 if action == ['enter']:
261 new_op = Block(op.parent)
262 new_op.toggle_enter()
263 else:
264 new_op = Op(action)
265 op.link_to(new_op, old_exit)
266 self.set_exec(rec)
267 try:
268 self.do_one_step()
269 except InProgress:
270 if isinstance(new_op, Block):
271 self.set_rec((new_op.start, 'next'))
272 else:
273 self.set_rec((new_op, 'next'))
274 return
275 play_op, exit = self.exec_point
276 # (do_one_step may have stopped recording)
277 if self.rec_point:
278 self.set_rec((new_op, exit))
279 self.set_exec(None)
280 return
282 exit = 'next'
283 try:
284 self.do_action(action)
285 except InProgress:
286 pass
287 except Beep:
288 import gtk
289 gtk.gdk_beep()
290 (type, val, tb) = sys.exc_info()
291 #if not val.may_record:
292 # return 0
293 exit = 'fail'
294 except:
295 support.report_exception()
296 raise
298 def add_display(self, display):
299 "Calls move_from(old_node) when we move and update_all() on updates."
300 self.displays.append(display)
301 #print "Added:", self.displays
303 def remove_display(self, display):
304 self.displays.remove(display)
305 #print "Removed, now:", self.displays
306 if not self.displays:
307 self.delete()
309 def update_replace(self, old, new):
310 if old == self.root:
311 self.root = new
312 if old in self.current_nodes:
313 self.model.lock(new)
314 self.model.unlock(old)
315 self.current_nodes.remove(old)
316 self.current_nodes.append(new)
317 self.update_all(new.parentNode)
318 else:
319 self.update_all(new.parentNode)
321 def has_ancestor(self, node, ancestor):
322 while node != ancestor:
323 node = node.parentNode
324 if not node:
325 return FALSE
326 return TRUE
328 def update_all(self, node):
329 for display in self.displays:
330 display.update_all(node)
332 def delete(self):
333 #print "View deleted"
334 self.model.root_program.watchers.remove(self)
335 self.move_to([])
336 for l in self.lists:
337 l.destroy()
338 self.model.unlock(self.root)
339 self.root = None
340 self.model.remove_view(self)
341 self.model = None
343 # 'nodes' may be either a node or a list of nodes.
344 # (duplicates will be removed)
345 # If it's a single node, then an 'attrib' node may also be specified
346 def move_to(self, nodes, attrib = None):
347 if self.current_nodes == nodes:
348 return
350 if attrib and attrib.nodeType != Node.ATTRIBUTE_NODE:
351 raise Exception('attrib not of type ATTRIBUTE_NODE!')
353 if type(nodes) != types.ListType:
354 assert nodes
355 nodes = [nodes]
357 if len(nodes) > 1:
358 # Remove duplicates
359 map = {}
360 old = nodes
361 nodes = []
362 for n in old:
363 if n not in map:
364 map[n] = None
365 nodes.append(n)
366 if len(old) != len(nodes):
367 print "(move_to: attempt to set duplicate nodes)"
369 old_nodes = self.current_nodes
370 self.current_nodes = nodes
372 for node in self.current_nodes:
373 self.model.lock(node)
374 for node in old_nodes:
375 self.model.unlock(node)
377 self.current_attrib = attrib
379 for display in self.displays:
380 display.move_from(old_nodes)
382 def move_prev_sib(self):
383 if self.get_current() == self.root or not self.get_current().previousSibling:
384 raise Beep
385 self.move_to(self.get_current().previousSibling)
387 def move_next_sib(self):
388 if self.get_current() == self.root or not self.get_current().nextSibling:
389 raise Beep
390 self.move_to(self.get_current().nextSibling)
392 def move_left(self):
393 new = []
394 for n in self.current_nodes:
395 if n == self.root:
396 raise Beep
397 p = n.parentNode
398 if p not in new:
399 new.append(p)
400 self.move_to(new)
402 def move_right(self):
403 new = []
404 for n in self.current_nodes:
405 kids = n.childNodes
406 if kids:
407 new.append(kids[0])
408 else:
409 raise Beep
410 self.move_to(new)
412 def move_home(self):
413 self.move_to(self.root)
415 def move_end(self):
416 if not self.get_current().childNodes:
417 raise Beep
418 node = self.get_current().childNodes[0]
419 while node.nextSibling:
420 node = node.nextSibling
421 self.move_to(node)
423 def set_display_root(self, root):
424 self.model.lock(root)
425 if self.root:
426 self.model.unlock(self.root)
427 self.root = root
428 self.update_all(root)
430 def enter(self):
431 """Change the display root to a COPY of the selected node.
432 Call Leave to check changes back in."""
433 node = self.get_current()
434 if node is self.root:
435 raise Beep # Locking problems if this happens...
436 if self.model.doc is not node.ownerDocument:
437 raise Exception('Current node not in view!')
438 self.move_to([])
439 self.set_marked([])
441 new_model = self.model.lock_and_copy(node)
442 self.chroots.append((self.model, node, self.marked))
443 self.set_model(new_model)
445 def leave(self):
446 """Undo the effect of the last chroot()."""
447 if not self.chroots:
448 raise Beep
450 self.set_marked([])
451 self.move_to([])
452 model = self.model
454 (old_model, old_node, old_marked) = self.chroots.pop()
456 copy = old_model.doc.importNode(self.model.get_root(), 1)
457 old_model.unlock(old_node)
458 old_model.replace_node(old_node, copy)
459 self.set_model(old_model)
460 self.move_to([copy])
461 self.set_marked(old_marked.keys())
463 if not model.views:
464 model.undo_stack = None
465 model.__dict__ = {}
466 del model
467 import gc
468 gc.collect()
470 def do_action(self, action):
471 "'action' is a tuple (function, arg1, arg2, ...)"
472 "Performs the action. Returns if action completes, or raises "
473 "InProgress if not (will call resume() later)."
474 if action[0] in record_again:
475 self.last_action = action
476 elif action[0] == 'again':
477 action = self.last_action
478 fn = getattr(self, action[0])
479 exit = 'next'
480 #print "DO:", action[0]
481 self.model.mark()
482 try:
483 new = apply(fn, action[1:])
484 except InProgress:
485 raise
486 except Beep:
487 if not self.op_in_progress:
488 raise
489 exit = 'fail'
490 new = None
491 except:
492 if not self.op_in_progress:
493 raise
494 traceback.print_exc()
495 exit = 'fail'
496 new = None
498 if self.op_in_progress:
499 op = self.op_in_progress
500 self.set_oip(None)
501 self.set_exec((op, exit))
502 if new:
503 self.move_to(new)
505 def breakpoint(self):
506 if self.breakpoints.has_key(self.exec_point):
507 return 1
508 op = self.exec_point[0]
509 if op.parent.start == op and op.next == None:
510 return 1 # Empty program
511 return 0
513 def do_one_step(self):
514 "Execute the next op after exec_point, then:"
515 "- position the point on one of the exits return."
516 "- if there is no op to perform, call callback_on_return() or raise Done."
517 "- if the operation is started but not complete, raise InProgress and "
518 " arrange to resume() later."
519 if self.op_in_progress:
520 support.report_error("Already executing something.")
521 raise Done()
522 if not self.exec_point:
523 support.report_error("No current playback point.")
524 raise Done()
525 (op, exit) = self.exec_point
527 if self.single_step == 0 and self.breakpoint():
528 print "Hit a breakpoint! At " + time.ctime(time.time())
529 if self.rec_point:
530 self.set_rec(None)
531 self.single_step = 1
532 for l in self.lists:
533 l.show_prog(op.get_program())
534 return
536 next = getattr(op, exit)
537 if next:
538 self.set_oip(next)
539 self.do_action(next.action) # May raise InProgress
540 return
542 if exit == 'fail' and not self.innermost_failure:
543 #print "Setting innermost_failure on", op
544 self.innermost_failure = op
546 # If we're in a block, try exiting from it...
547 if isinstance(op.parent, Block):
548 if self.start_block_iteration(op.parent, continuing = exit):
549 return # Looping...
550 if not op.parent.is_toplevel():
551 self.set_exec((op.parent, exit))
552 return
554 if self.callback_on_return:
555 cb = self.callback_on_return
556 self.callback_on_return = None
557 cb()
558 else:
559 raise Done()
561 def set_oip(self, op):
562 #print "set_oip:", self.exec_point
563 if op:
564 self.set_exec(None)
565 self.op_in_progress = op
566 for l in self.lists:
567 l.update_points()
569 def fast_global(self, name):
570 "Search for nodes with this name anywhere under the root (//name)"
571 #print "Fast global", name
572 if ':' in name:
573 (prefix, localName) = string.split(name, ':', 1)
574 else:
575 (prefix, localName) = (None, name)
576 if self.current_nodes:
577 src = self.current_nodes[-1]
578 else:
579 src = self.root
580 namespaceURI = self.model.prefix_to_namespace(src, prefix)
581 select = []
582 def add(node):
583 if node.nodeType != Node.ELEMENT_NODE:
584 return
585 if node.localName == localName and node.namespaceURI == namespaceURI:
586 select.append(node)
587 map(add, node.childNodes)
588 add(self.root)
589 self.move_to(select)
591 # Actions...
593 def do_global(self, pattern):
594 if len(self.current_nodes) != 1:
595 self.move_to(self.root)
596 if pattern[:2] == '//':
597 if fast_global.match(pattern):
598 self.fast_global(pattern[2:])
599 return
600 ns = {}
601 if not ns:
602 ns = ext.GetAllNs(self.current_nodes[0])
603 ns['ext'] = FT_EXT_NAMESPACE
604 try:
605 ns['_'] = ns[None]
606 except KeyError:
607 pass
608 #print "ns is", ns
609 c = Context.Context(self.get_current(), processorNss = ns)
610 from Ft.Xml.XPath import XPathParser
611 code = XPathParser.new().parse(self.macro_pattern(pattern))
612 #print code
613 nodes = code.evaluate(c)
614 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
615 #print "Found", nodes
616 self.move_to(nodes)
618 def select_region(self, path, ns = None):
619 if len(self.current_nodes) == 0:
620 raise Beep
621 src = self.current_nodes[-1]
622 if not ns:
623 ns = ext.GetAllNs(src)
624 ns['ext'] = FT_EXT_NAMESPACE
625 c = Context.Context(src, [src], processorNss = ns)
626 rt = XPath.Evaluate(path, context = c)
627 node = None
628 for x in rt:
629 if not self.has_ancestor(x, self.root):
630 print "[ skipping search result above root ]"
631 continue
632 if not node:
633 node = x
634 if not node:
635 print "*** Search for '%s' in select_region failed" % path
636 print " (namespaces were '%s')" % ns
637 raise Beep
638 if node.parentNode != src.parentNode:
639 print "Nodes must have same parent!"
640 raise Beep
641 on = 0
642 selected = []
643 for n in src.parentNode.childNodes:
644 was_on = on
645 if n is src or n is node:
646 on = not was_on
647 if on or was_on:
648 selected.append(n)
649 self.move_to(selected)
651 def macro_pattern(self, pattern):
652 """Do the @CURRENT@ substitution for an XPath"""
653 if len(self.current_nodes) != 1:
654 return pattern
655 node = self.get_current()
656 if node.nodeType == Node.TEXT_NODE:
657 current = node.data
658 else:
659 current = node.nodeName
660 pattern = pattern.replace('@CURRENT@', current)
661 #print "Searching for", pattern
662 return pattern
664 def do_search(self, pattern, ns = None, toggle = FALSE):
665 if len(self.current_nodes) == 0:
666 src = self.root
667 else:
668 src = self.current_nodes[-1]
669 if not ns:
670 ns = ext.GetAllNs(src)
671 ns['ext'] = FT_EXT_NAMESPACE
672 c = Context.Context(src, [src], processorNss = ns)
674 rt = XPath.Evaluate(self.macro_pattern(pattern), context = c)
675 node = None
676 for x in rt:
677 if not self.has_ancestor(x, self.root):
678 print "[ skipping search result above root ]"
679 continue
680 if not node:
681 node = x
682 #if self.node_to_line[x] > self.current_line:
683 #node = x
684 #break
685 if not node:
686 #print "*** Search for '%s' failed" % pattern
687 #print " (namespaces were '%s')" % ns
688 raise Beep
689 if toggle:
690 new = self.current_nodes[:]
691 if node in new:
692 new.remove(node)
693 else:
694 new.append(node)
695 self.move_to(new)
696 else:
697 self.move_to(node)
699 def do_text_search(self, pattern):
700 pattern = self.macro_pattern(pattern)
701 return self.do_search("//text()[ext:match('%s')]" % pattern)
703 def subst(self, replace, with):
704 "re search and replace on the current node"
705 nodes = self.current_nodes[:]
706 check = len(nodes) == 1
707 a = self.current_attrib
708 if a:
709 new = re.sub(replace, with, a.value)
710 a = self.model.set_attrib(nodes[0], a.name, new)
711 self.move_to(nodes[0], a)
712 else:
713 self.move_to([])
714 final = []
715 for n in nodes:
716 if n.nodeType == Node.TEXT_NODE:
717 old = n.data.replace('\n', ' ')
718 new, num = re.subn(replace, with, old)
719 if check and not num:
720 self.move_to(n)
721 raise Beep
722 self.model.set_data(n, new)
723 final.append(n)
724 elif n.nodeType == Node.ELEMENT_NODE:
725 old = str(n.nodeName)
726 new, num = re.subn(replace, with, old)
727 if check and not num:
728 self.move_to(n)
729 raise Beep
730 new_ns, x = self.model.split_qname(n, new)
731 final.append(self.model.set_name(n, new_ns, new))
732 else:
733 self.move_to(n)
734 raise Beep
735 self.move_to(final)
737 def python(self, expr):
738 "Replace node with result of expr(old_value)"
739 if self.get_current().nodeType == Node.TEXT_NODE:
740 vars = {'x': self.get_current().data, 're': re, 'sub': re.sub, 'string': string}
741 result = eval(expr, vars)
742 new = self.python_to_node(result)
743 node = self.get_current()
744 self.move_to([])
745 self.model.replace_node(node, new)
746 self.move_to(new)
747 else:
748 raise Beep
750 def resume(self, exit = 'next'):
751 "After raising InProgress, call this to start moving again."
752 if self.op_in_progress:
753 op = self.op_in_progress
754 self.set_oip(None)
755 self.set_exec((op, exit))
756 if not self.single_step:
757 self.sched()
758 self.status_changed()
759 else:
760 print "(nothing to resume)"
762 def ask(self, q):
763 def ask_cb(result, self = self):
764 if result is None:
765 exit = 'fail'
766 else:
767 self.clipboard = self.model.doc.createTextNode(result)
768 exit = 'next'
769 self.resume(exit)
770 from GetArg import GetArg
771 box = GetArg('Input:', ask_cb, [q], destroy_return = 1)
772 raise InProgress
774 def python_to_node(self, data):
775 "Convert a python data structure into a tree and return the root."
776 if type(data) == types.ListType:
777 list = self.model.doc.createElementNS(DOME_NS, 'dome:list')
778 list.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:dome', DOME_NS)
779 for x in data:
780 list.appendChild(self.python_to_node(x))
781 return list
782 return self.model.doc.createTextNode(str(data))
784 def yank(self, deep = 1):
785 if self.current_attrib:
786 a = self.current_attrib
788 self.clipboard = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
789 self.clipboard.appendChild(self.model.doc.createTextNode(a.value))
790 else:
791 self.clipboard = self.model.doc.createDocumentFragment()
792 for n in self.current_nodes:
793 c = n.cloneNode(deep)
794 #print n, "->", c
795 self.clipboard.appendChild(c)
797 #print "Clip now", self.clipboard
799 def shallow_yank(self):
800 self.yank(0)
802 def delete_shallow(self):
803 nodes = self.current_nodes[:]
804 if not nodes:
805 return
806 if self.root in nodes:
807 raise Beep
808 self.shallow_yank()
809 self.move_to([])
810 for n in nodes:
811 self.model.delete_shallow(n)
812 self.move_home()
814 def delete_node_no_clipboard(self):
815 self.delete_node(yank = 0)
817 def delete_node(self, yank = 1):
818 nodes = self.current_nodes[:]
819 if not nodes:
820 return
821 if yank:
822 self.yank()
823 if self.current_attrib:
824 ca = self.current_attrib
825 self.current_attrib = None
826 self.model.set_attrib(self.get_current(), ca.name, None)
827 return
828 if self.root in nodes:
829 raise Beep
830 self.move_to([])
831 new = [x.parentNode for x in nodes]
832 self.move_to(new)
833 self.model.delete_nodes(nodes)
835 def undo(self):
836 nodes = self.current_nodes[:]
837 self.move_to([])
838 self.model.unlock(self.root)
839 try:
840 self.model.undo()
841 finally:
842 self.model.lock(self.root)
843 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
845 def redo(self):
846 nodes = self.current_nodes[:]
847 self.move_to([])
848 self.model.unlock(self.root)
849 try:
850 self.model.redo()
851 finally:
852 self.model.lock(self.root)
853 self.move_to(filter(lambda x: self.has_ancestor(x, self.root), nodes))
855 def default_done(self, exit):
856 "Called when execution of a program returns. op_in_progress has been "
857 "restored - move to the exit."
858 #print "default_done(%s)" % exit
859 if self.op_in_progress:
860 op = self.op_in_progress
861 self.set_oip(None)
862 self.set_exec((op, exit))
863 else:
864 print "No operation to return to!"
865 c = self.call_on_done
866 if c:
867 self.call_on_done = None
868 c(exit)
869 elif exit == 'fail':
870 self.jump_to_innermost_failure()
871 raise Done()
873 def jump_to_innermost_failure(self):
874 assert self.innermost_failure != None
876 print "Returning to innermost failure:", self.innermost_failure
877 self.set_exec((self.innermost_failure, 'fail'))
878 for l in self.lists:
879 if hasattr(l, 'set_innermost_failure'):
880 l.set_innermost_failure(self.innermost_failure)
882 def play(self, name, done = None):
883 "Play this macro. When it returns, restore the current op_in_progress (if any)"
884 "and call done(exit). Default for done() moves exec_point."
885 "done() is called from do_one_step() - usual rules apply."
887 prog = self.name_to_prog(name)
888 self.innermost_failure = None
890 if not done:
891 done = self.default_done
893 def cbor(self = self, op = self.op_in_progress, done = done,
894 name = name,
895 old_cbor = self.callback_on_return,
896 old_ss = self.single_step):
897 "We're in do_one_step..."
899 #print "Return from '%s'..." % name
901 if old_ss == 2 and self.single_step == 0:
902 self.single_step = old_ss
903 self.callback_on_return = old_cbor
905 o, exit = self.exec_point
906 if op:
907 #print "Resume op '%s' (%s)" % (op.program.name, op)
908 self.pop_stack()
909 self.set_oip(op)
910 return done(exit)
912 self.callback_on_return = cbor
914 if self.single_step == 2:
915 self.single_step = 0
917 if self.op_in_progress:
918 self.push_stack(self.op_in_progress)
919 self.set_oip(None)
920 self.play_block(prog.code)
921 self.sched()
922 self.status_changed()
923 raise InProgress
925 def start_block_iteration(self, block, continuing = None):
926 "True if we are going to run the block, False to exit the loop"
927 "Continuing is 'next' or 'fail' if we reached the end of the block."
928 #print "Start interation"
929 if not self.foreach_stack:
930 raise Exception("Reached the end of a block we never entered")
931 stack_block, nodes_list, restore = self.foreach_stack[-1]
932 if stack_block != block:
933 self.foreach_stack = []
934 raise Exception("Reached the end of a block we never entered")
936 if continuing:
937 if block.enter:
938 self.leave()
939 if block.foreach:
940 restore.extend(self.current_nodes)
941 if continuing == 'fail':
942 print "Error in block; exiting early in program", block.get_program()
943 self.foreach_stack.pop()
944 return 0
945 while nodes_list and nodes_list[0].parentNode == None:
946 print "Skipping deleted node", nodes_list[0]
947 del nodes_list[0]
949 if not nodes_list:
950 self.foreach_stack.pop()
951 if block.foreach:
952 nodes = filter(lambda x: self.has_ancestor(x, self.root), restore)
953 self.move_to(nodes)
954 return 0 # Nothing left to do
955 nodes = nodes_list[0]
956 del nodes_list[0]
957 self.move_to(nodes)
959 if nodes_list:
960 print "[ %d after this ]" % len(nodes_list),
961 sys.stdout.flush()
963 if block.enter:
964 self.enter()
965 self.set_exec((block.start, 'next'))
966 return 1
968 def play_block(self, block):
969 assert isinstance(block, Block)
970 #print "Enter Block!"
971 if block.foreach:
972 list = self.current_nodes[:]
973 else:
974 list = [self.current_nodes[:]] # List of one item, containing everything
976 self.foreach_stack.append((block, list, []))
977 if not self.start_block_iteration(block):
978 # No nodes selected...
979 if not block.is_toplevel():
980 self.set_exec((block, 'next'))
982 def Block(self):
983 assert self.op_in_progress
984 oip = self.op_in_progress
985 self.set_oip(None)
986 self.play_block(oip)
987 if not self.single_step:
988 self.sched()
989 raise InProgress
991 def sched(self):
992 if self.op_in_progress:
993 raise Exception("Operation in progress")
994 if self.idle_cb:
995 raise Exception("Already playing!")
996 self.idle_cb = self.idle_add(self.play_callback)
998 def play_callback(self):
999 self.idle_remove(self.idle_cb)
1000 self.idle_cb = 0
1001 try:
1002 self.in_callback = 1
1003 try:
1004 self.do_one_step()
1005 finally:
1006 self.in_callback = 0
1007 except Done:
1008 (op, exit) = self.exec_point
1009 if exit == 'fail' and self.innermost_failure:
1010 self.jump_to_innermost_failure()
1011 print "Done, at " + time.ctime(time.time())
1012 self.run_new()
1013 return 0
1014 except InProgress:
1015 #print "InProgress"
1016 return 0
1017 except:
1018 type, val, tb = sys.exc_info()
1019 list = traceback.extract_tb(tb)
1020 stack = traceback.format_list(list[-2:])
1021 ex = traceback.format_exception_only(type, val) + ['\n\n'] + stack
1022 traceback.print_exception(type, val, tb)
1023 print "Error in do_one_step(): stopping playback"
1024 node = self.op_in_progress
1025 self.set_oip(None)
1026 if node:
1027 self.set_exec((node, 'fail'))
1028 self.status_changed()
1029 return 0
1030 if self.op_in_progress or self.single_step:
1031 self.status_changed()
1032 return 0
1033 self.sched()
1034 return 0
1036 def status_changed(self):
1037 for display in self.displays:
1038 if hasattr(display, 'update_state'):
1039 display.update_state()
1041 def map(self, name):
1042 print "Map", name
1044 nodes = self.current_nodes[:]
1045 if not nodes:
1046 print "map of nothing: skipping..."
1047 return
1048 inp = [nodes, None] # Nodes, next
1049 def next(exit = exit, self = self, name = name, inp = inp):
1050 "This is called while in do_one_step() - normal rules apply."
1051 nodes, next = inp
1052 print "[ %d to go ]" % len(nodes),
1053 sys.stdout.flush()
1054 if exit == 'fail':
1055 print "Map: nodes remaining, but an error occurred..."
1056 return self.default_done(exit)
1057 while nodes and nodes[0].parentNode == None:
1058 print "Skipping deleted node", nodes[0]
1059 del nodes[0]
1060 if not nodes:
1061 return self.default_done(exit)
1062 self.move_to(nodes[0])
1063 del nodes[0]
1064 if not nodes:
1065 next = None
1066 #print "Map: calling play (%d after this)" % len(nodes)
1067 self.play(name, done = next) # Should raise InProgress
1068 if nodes is self.current_nodes:
1069 raise Exception("Slice failed!")
1070 inp[1] = next
1071 next('next')
1073 def name_to_prog(self, name):
1074 comps = string.split(name, '/')
1075 prog = self.model.root_program
1076 if prog.name != comps[0]:
1077 raise Exception("No such program as '%s'!" % name)
1078 del comps[0]
1079 while comps:
1080 prog = prog.subprograms[comps[0]]
1081 del comps[0]
1082 return prog
1084 def change_node(self, new_data):
1085 nodes = self.current_nodes
1086 if not nodes:
1087 return
1088 self.move_to([])
1089 if nodes[0].nodeType == Node.ELEMENT_NODE:
1090 # Slow, so do this here, even if vaguely incorrect...
1091 assert ' ' not in new_data
1092 if ':' in new_data:
1093 (prefix, localName) = string.split(new_data, ':', 1)
1094 else:
1095 (prefix, localName) = (None, new_data)
1096 namespaceURI = self.model.prefix_to_namespace(nodes[0], prefix)
1097 out = []
1098 for node in nodes:
1099 if node is self.root:
1100 self.model.unlock(self.root)
1101 new = self.model.set_name(node, namespaceURI, new_data)
1102 self.model.lock(new)
1103 self.root = new
1104 else:
1105 new = self.model.set_name(node, namespaceURI, new_data)
1106 out.append(new)
1107 self.move_to(out)
1108 else:
1109 for node in nodes:
1110 self.model.set_data(node, new_data)
1111 self.move_to(nodes)
1113 def add_node(self, where, data):
1114 cur = self.get_current()
1115 if where[1] == 'e':
1116 if ':' in data:
1117 (prefix, localName) = string.split(data, ':', 1)
1118 else:
1119 (prefix, localName) = (None, data)
1120 namespaceURI = self.model.prefix_to_namespace(self.get_current(), prefix)
1121 new = self.model.doc.createElementNS(namespaceURI, data)
1122 else:
1123 new = self.model.doc.createTextNode(data)
1125 try:
1126 if where[0] == 'i':
1127 self.model.insert_before(cur, new)
1128 elif where[0] == 'a':
1129 self.model.insert_after(cur, new)
1130 elif where[0] == 'e':
1131 self.model.insert_before(None, new, parent = cur)
1132 else:
1133 self.model.insert(cur, new)
1134 except:
1135 raise Beep
1137 self.move_to(new)
1139 def http_post(self):
1140 node = self.get_current()
1141 attrs = node.attributes
1142 post = []
1143 for (ns,name) in attrs.keys():
1144 if ns is None:
1145 post.append((str(name),
1146 str(attrs[(ns, name)].value)))
1147 node = self.suck_node(node, post_data = urllib.urlencode(post))
1148 if node:
1149 self.move_to(node)
1151 def suck(self):
1152 nodes = self.current_nodes[:]
1153 attrib = self.current_attrib
1154 self.move_to([])
1155 final = []
1156 for x in nodes:
1157 try:
1158 new = self.suck_node(x, attrib = attrib)
1159 finally:
1160 self.move_to(x)
1161 final.append(new)
1162 self.move_to(final)
1164 def suck_node(self, node, post_data = None, attrib = None):
1165 uri = None
1166 if node.nodeType == Node.TEXT_NODE:
1167 uri = node.nodeValue
1168 else:
1169 if attrib:
1170 uri = attrib.value
1171 elif node.hasAttributeNS(None, 'uri'):
1172 uri = node.getAttributeNS(None, 'uri')
1173 else:
1174 for attr in node.attributes.keys():
1175 uri = node.attributes[attr].value
1176 if uri.find('//') != -1 or uri.find('.htm') != -1:
1177 break
1178 if not uri:
1179 print "Can't suck", node
1180 raise Beep
1181 if uri.find('//') == -1:
1182 base = self.model.get_base_uri(node)
1183 #print "Relative URI..."
1184 if base:
1185 #print "Base URI is:", base, "add", uri
1186 uri = urlparse.urljoin(base, uri)
1187 else:
1188 pass
1189 #print "Warning: Can't find 'uri' attribute!"
1191 if uri.startswith('file:///'):
1192 print "Loading", uri
1194 assert not post_data
1195 stream = open(uri[7:])
1196 # (could read the mod time here...)
1197 last_mod = None
1198 else:
1199 print "Sucking", uri
1201 if post_data is not None:
1202 print "POSTING", post_data
1203 stream = urllib.urlopen(uri, post_data)
1204 headers = stream.info().headers
1205 last_mod = None
1206 for x in headers:
1207 if x.lower().startswith('last-modified:'):
1208 last_mod = x[14:].strip()
1209 break
1211 current_last_mod = node.getAttributeNS(None, 'last-modified')
1212 if current_last_mod and last_mod:
1213 if current_last_mod == last_mod:
1214 self.model.set_attrib(node, 'modified', None)
1215 print "not modified => not sucking!\n"
1216 return
1218 print "Fetching page contents..."
1219 data = stream.read()
1220 print "got data... tidying..."
1222 if data.startswith('<?xml'):
1223 pass
1224 else:
1225 data = to_html(data)
1227 old_md5 = node.getAttributeNS(None, 'md5_sum')
1229 import md5
1230 new_md5 = md5.new(data).hexdigest()
1232 if old_md5 and new_md5 == old_md5:
1233 self.model.set_attrib(node, 'modified', None)
1234 print "MD5 sums match => not parsing!"
1235 return node
1237 reader = PyExpat.Reader()
1238 print "parsing...",
1240 from Ft.Xml.InputSource import InputSourceFactory
1241 from Ft.Xml.cDomlette import nonvalParse
1242 isrc = InputSourceFactory()
1244 try:
1245 root = nonvalParse(isrc.fromString(data, uri))
1246 ext.StripHtml(root)
1247 except:
1248 type, val, tb = sys.exc_info()
1249 traceback.print_exception(type, val, tb)
1250 print "parsing failed!"
1251 print "Data was:"
1252 print data
1253 #support.report_exception()
1254 raise Beep
1255 else:
1256 print "parse OK...",
1258 new = node.ownerDocument.importNode(root.documentElement, 1)
1259 new.setAttributeNS(None, 'uri', uri)
1261 if last_mod:
1262 new.setAttributeNS(None, 'last-modified', last_mod)
1263 new.setAttributeNS(None, 'modified', 'yes')
1264 new.setAttributeNS(None, 'md5_sum', new_md5)
1266 self.move_to([])
1267 if node == self.root:
1268 self.model.unlock(self.root)
1269 self.model.replace_node(self.root, new)
1270 #self.model.strip_space(new) (not sure we need this)
1271 self.model.lock(new)
1272 self.root = new
1273 else:
1274 self.model.replace_node(node, new)
1275 #self.model.strip_space(new)
1277 print "Loaded."
1278 return new
1280 def dom_from_command(self, command, callback = None, old_md5 = None):
1281 """Execute shell command 'command' in the background.
1282 Parse the output as XML. When done, call callback(doc_root, md5).
1283 If old_md5 is given, compare the MD5 of the document with it,
1284 and do callback(None, "Same") if they match.
1286 #print command
1287 cout = os.popen(command)
1289 all = ["", None]
1290 def got_all(data, cb = callback, m5 = old_md5):
1291 import md5
1292 new_md5 = md5.new(data).hexdigest()
1294 if m5 and new_md5 == m5:
1295 cb(None, "Same")
1296 return
1298 reader = PyExpat.Reader()
1299 print "Parsing..."
1301 try:
1302 root = reader.fromStream(StringIO(data))
1303 ext.StripHtml(root)
1304 except:
1305 print "dom_from_command: parsing failed"
1306 support.report_exception()
1307 root = None
1308 cb(root, new_md5)
1310 # XXX: only for nogui...
1311 got_all(cout.read())
1312 return
1314 def got_html(src, cond, all = all, got_all = got_all):
1315 data = src.read(100)
1316 if data:
1317 all[0] += data
1318 return
1319 input_remove(all[1])
1320 src.close()
1321 got_all(all[0])
1323 all[1] = input_add(cout, GDK.INPUT_READ, got_html)
1325 def put_before(self):
1326 node = self.get_current()
1327 if self.clipboard == None:
1328 raise Beep
1329 new = self.clipboard.cloneNode(1)
1330 try:
1331 self.model.insert_before(node, new)
1332 except:
1333 raise Beep
1335 def put_after(self):
1336 node = self.get_current()
1337 if self.clipboard == None:
1338 raise Beep
1339 new = self.clipboard.cloneNode(1)
1340 self.model.insert_after(node, new)
1342 def put_replace(self):
1343 node = self.get_current()
1344 if self.clipboard == None:
1345 print "No clipboard!"
1346 raise Beep
1347 if self.current_attrib:
1348 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1349 value = self.clipboard.childNodes[0].data
1350 else:
1351 value = self.clipboard.data
1352 a = self.current_attrib
1353 value = value.replace('\n', ' ')
1354 a = self.model.set_attrib(node, a.name, value)
1355 self.move_to(node, a)
1356 return
1357 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1358 if len(self.clipboard.childNodes) != 1:
1359 print "Multiple nodes in clipboard!"
1360 raise Beep
1361 new = self.clipboard.childNodes[0].cloneNode(1)
1362 else:
1363 new = self.clipboard.cloneNode(1)
1364 if new.nodeType != Node.ELEMENT_NODE:
1365 raise Beep
1366 self.move_to([])
1367 try:
1368 if node == self.root:
1369 self.model.unlock(self.root)
1370 try:
1371 self.model.replace_node(self.root, new)
1372 self.root = new
1373 finally:
1374 self.model.lock(self.root)
1375 else:
1376 self.model.replace_node(node, new)
1377 self.move_to(new)
1378 except:
1379 type, val, tb = sys.exc_info()
1380 traceback.print_exception(type, val, tb)
1381 print "Replace failed!"
1382 raise Beep
1384 def put_as_child(self):
1385 node = self.get_current()
1386 if self.clipboard == None:
1387 raise Beep
1388 new = self.clipboard.cloneNode(1)
1389 if new.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1390 to = []
1391 for n in new.childNodes:
1392 to.append(n)
1393 else:
1394 to = new
1395 try:
1396 self.model.insert(node, new, index = 0)
1397 except:
1398 raise Beep
1400 self.move_to(to)
1402 def yank_value(self):
1403 if not self.current_attrib:
1404 raise Beep
1405 value = self.current_attrib.value
1406 self.clipboard = self.model.doc.createTextNode(value)
1407 #print "Clip now", self.clipboard
1409 def yank_attribs(self, name):
1410 self.clipboard = self.model.doc.createDocumentFragment()
1411 if name:
1412 if not self.get_current().hasAttribute(name):
1413 raise Beep
1414 attribs = [self.get_current().getAttributeNode(name)]
1415 else:
1416 attribs = []
1417 dict = self.get_current().attributes
1418 for a in dict.keys():
1419 attribs.append(dict[a])
1421 # Make sure the attributes always come out in the same order
1422 # (helps with macros).
1423 def by_name(a, b):
1424 diff = cmp(a.name, b.name)
1425 if diff == 0:
1426 diff = cmp(a.namespaceURI, b.namespaceURI)
1427 return diff
1429 attribs.sort(by_name)
1430 for a in attribs:
1431 n = self.model.doc.createElementNS(a.namespaceURI, a.nodeName)
1432 n.appendChild(self.model.doc.createTextNode(a.value))
1433 self.clipboard.appendChild(n)
1434 #print "Clip now", self.clipboard
1436 def paste_attribs(self):
1437 if self.clipboard.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
1438 attribs = self.clipboard.childNodes
1439 else:
1440 attribs = [self.clipboard]
1441 new = []
1442 for a in attribs:
1443 try:
1444 new.append((a.nodeName, a.childNodes[0].data))
1445 except:
1446 raise Beep
1447 for node in self.current_nodes:
1448 # XXX: Set NS attribs first...
1449 for (name, value) in new:
1450 self.model.set_attrib(node, name, value)
1452 def compare(self):
1453 "Ensure that all selected nodes have the same value."
1454 if len(self.current_nodes) < 2:
1455 raise Beep # Not enough nodes!
1456 base = self.current_nodes[0]
1457 for n in self.current_nodes[1:]:
1458 if not same(base, n):
1459 raise Beep(may_record = 1)
1461 def fail(self):
1462 raise Beep(may_record = 1)
1464 def do_pass(self):
1465 pass
1467 def fail_if(self, xpath):
1468 """Evaluate xpath as a boolean, and fail if true."""
1469 src = self.get_current()
1470 ns = ext.GetAllNs(src)
1471 ns['ext'] = FT_EXT_NAMESPACE
1472 c = Context.Context(src.parentNode, [src.parentNode], processorNss = ns)
1474 rt = XPath.Evaluate(xpath, context = c)
1475 print "Got", rt
1476 if src in rt:
1477 raise Beep(may_record = 1)
1479 def attribute(self, namespace = None, attrib = ''):
1480 node = self.get_current()
1482 if attrib == '':
1483 self.move_to(node)
1484 return
1486 if attrib == 'xmlns':
1487 attrib = None
1488 #print "(ns, attrib)", `namespace`, attrib
1490 a = node.attributes.get((namespace, attrib), None)
1492 if a:
1493 self.move_to(node, a)
1494 else:
1495 print "No such attribute"
1496 raise Beep()
1498 def set_attrib(self, value):
1499 a = self.current_attrib
1500 if not a:
1501 raise Beep()
1502 node = self.get_current()
1503 a = self.model.set_attrib(node, a.name, value)
1504 self.move_to(node, a)
1506 def add_attrib(self, UNUSED, name, value = ''):
1507 node = self.get_current()
1508 a = self.model.set_attrib(node, name, value)
1509 self.move_to(node, a)
1511 def load_html(self, path):
1512 "Replace root with contents of this HTML file."
1513 print "Reading HTML..."
1514 command = "tidy -asxml '%s' 2>/dev/null" % path
1516 def done(root, md5, self = self):
1517 print "Loaded!"
1518 new = self.root.ownerDocument.importNode(root.documentElement, 1)
1520 if self.root:
1521 self.model.unlock(self.root)
1522 self.move_to([])
1523 self.model.replace_node(self.root, new)
1524 self.model.lock(new)
1525 self.root = new
1526 self.move_to(self.root)
1528 self.dom_from_command(command, done)
1530 def load_xml(self, path):
1531 "Replace root with contents of this XML (or Dome) file."
1532 reader = PyExpat.Reader()
1533 new_doc = reader.fromUri(path)
1534 self.load_node(new_doc.documentElement)
1536 def load_node(self, root):
1537 new = self.model.doc.importNode(root, 1)
1539 self.model.strip_space(new)
1541 if self.root:
1542 self.model.unlock(self.root)
1543 self.move_to([])
1544 self.model.replace_node(self.root, new)
1545 self.model.lock(new)
1546 self.root = new
1547 self.move_to(self.root)
1549 def select_dups(self):
1550 node = self.get_current()
1551 select = []
1552 for n in node.parentNode.childNodes:
1553 if n is node:
1554 continue
1555 if same(node, n):
1556 select.append(n)
1557 self.move_to(select)
1559 def select_marked_region(self, attr = "unused"):
1560 select = []
1561 if len(self.marked) != 1:
1562 print "Must be exactly one marked node!"
1563 raise Beep()
1564 if len(self.current_nodes) != 1:
1565 print "Must be exactly one selected node!"
1566 raise Beep()
1567 import Path
1568 a = Path.path_to(self.get_current())
1569 b = Path.path_to(self.marked.keys()[0])
1571 while a and b and a[0] == b[0]:
1572 del a[0]
1573 del b[0]
1575 if a and b:
1576 select = []
1577 s = 0
1578 a = a[0]
1579 b = b[0]
1580 for x in a.parentNode.childNodes:
1581 if x == a:
1582 s = not s
1583 elif x == b:
1584 s = not s
1585 if s:
1586 select.append(x)
1587 self.move_to(select)
1588 else:
1589 print "One node is a parent of the other!"
1590 raise Beep()
1592 def show_html(self):
1593 from HTML import HTML
1594 HTML(self.model, self.get_current()).show()
1596 def show_canvas(self):
1597 from Canvas import Canvas
1598 Canvas(self, self.get_current()).show()
1600 def toggle_hidden(self):
1601 nodes = self.current_nodes[:]
1602 self.move_to([])
1603 for node in nodes:
1604 if node.hasAttributeNS(None, 'hidden'):
1605 new = None
1606 else:
1607 new = 'yes'
1608 self.model.set_attrib(node, 'hidden', new, with_update = 0)
1609 self.model.update_all(self.root)
1610 self.move_to(nodes)
1612 def soap_send(self):
1613 copy = node_to_xml(self.get_current())
1614 env = copy.documentElement
1616 if env.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/':
1617 support.report_error("Not a SOAP-ENV:Envelope (bad namespace)")
1618 raise Done()
1619 if env.localName != 'Envelope':
1620 support.report_error("Not a SOAP-ENV:Envelope (bad local name)")
1621 raise Done()
1623 if len(env.childNodes) != 2:
1624 support.report_error("SOAP-ENV:Envelope must have one header and one body")
1625 raise Done()
1627 kids = elements(env)
1628 head = kids[0]
1629 body = kids[1]
1631 if head.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1632 head.localName != 'Head':
1633 support.report_error("First child must be a SOAP-ENV:Head element")
1634 raise Done()
1636 if body.namespaceURI != 'http://schemas.xmlsoap.org/soap/envelope/' or \
1637 body.localName != 'Body':
1638 support.report_error("Second child must be a SOAP-ENV:Body element")
1639 raise Done()
1641 sft = None
1642 for header in elements(head):
1643 if header.namespaceURI == DOME_NS and header.localName == 'soap-forward-to':
1644 sft = header
1645 break
1646 print header.namespaceURI
1647 print header.localName
1649 if not sft:
1650 support.report_error("Head must contain a dome:soap-forward-to element")
1651 raise Done()
1653 dest = sft.childNodes[0].data
1654 parent = sft.parentNode
1655 if len(elements(parent)) == 1:
1656 sft = parent
1657 parent = sft.parentNode # Delete the whole header
1658 parent.removeChild(sft)
1660 import httplib, urlparse
1662 (scheme, addr, path, p, q, f) = urlparse.urlparse(dest, allow_fragments = 0)
1663 if scheme != 'http':
1664 support.report_error("SOAP is only supported for 'http:' -- sorry!")
1665 raise Done()
1667 stream = StrGrab()
1668 ext.PrettyPrint(copy, stream = stream)
1669 message = stream.data
1671 conn = httplib.HTTP(addr)
1672 conn.putrequest("POST", path)
1673 conn.putheader('Content-Type', 'text/xml; charset="utf-8"')
1674 conn.putheader('Content-Length', str(len(message)))
1675 conn.putheader('SOAPAction', '')
1676 conn.endheaders()
1677 conn.send(message)
1678 (code, r_mess, r_headers) = conn.getreply()
1680 reply = conn.getfile().read()
1681 print "Got:\n", reply
1683 reader = PyExpat.Reader()
1684 new_doc = reader.fromString(reply)
1685 print new_doc
1687 new = self.model.doc.importNode(new_doc.documentElement, 1)
1689 self.model.strip_space(new)
1691 old = self.get_current()
1692 self.move_to([])
1693 self.model.replace_node(old, new)
1694 self.move_to(new)
1696 def program_changed(self, changed_op):
1697 print "Check points..."
1698 if self.rec_point:
1699 (op, exit) = self.rec_point
1700 if not op.parent:
1701 print "Lost rec_point"
1702 self.rec_point = None
1703 if self.exec_point:
1704 (op, exit) = self.exec_point
1705 if not op.parent:
1706 print "Lost exec_point"
1707 self.exec_point = None
1708 for l in self.lists:
1709 l.update_points()
1710 self.status_changed()
1712 def prog_tree_changed(self):
1713 pass
1715 def export_all(self):
1716 doc = implementation.createDocument(DOME_NS, 'dome', None)
1717 node = self.model.root_program.to_xml(doc)
1718 doc.documentElement.appendChild(node)
1719 node = doc.createElementNS(DOME_NS, 'dome-data')
1720 doc.documentElement.appendChild(node)
1722 if self.chroots:
1723 print "*** WARNING: Saving from a chroot!"
1724 model = self.model
1725 data = doc.importNode(model.doc.documentElement, 1)
1726 node.appendChild(data)
1728 return doc
1730 def blank_all(self):
1731 doc = implementation.createDocument(None, 'root', None)
1732 self.move_home()
1733 self.clipboard = self.model.doc.createElementNS(None, 'root')
1734 self.put_replace()
1736 def mark_switch(self):
1737 new = self.marked.keys()
1738 self.set_marked(self.current_nodes)
1739 self.move_to(new)
1741 def set_marked(self, new):
1742 update = self.marked
1743 for x in self.marked.keys():
1744 self.model.unlock(x)
1745 self.marked = {}
1746 for x in new:
1747 self.model.lock(x)
1748 self.marked[x] = None
1749 update[x] = None
1750 update = update.keys()
1751 for display in self.displays:
1752 display.marked_changed(update)
1754 def mark_selection(self):
1755 self.set_marked(self.current_nodes)
1757 def clear_mark(self):
1758 self.set_marked([])
1760 def normalise(self):
1761 self.model.normalise(self.get_current())
1763 def remove_ns(self):
1764 node = self.get_current()
1765 self.move_to([])
1766 self.move_to(self.model.remove_ns(node))
1768 class StrGrab:
1769 data = ''
1771 def write(self, str):
1772 self.data += str