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