1 from __future__
import nested_scopes
11 from xml
.dom
import Node
, XMLNS_NAMESPACE
12 from Ft
.Xml
import XPath
13 from Ft
.Xml
.XPath
import FT_EXT_NAMESPACE
, Context
14 from Ft
.Xml
.cDomlette
import implementation
15 from Ft
.Xml
.Domlette
import PrettyPrint
17 import re
, string
, sys
19 from StringIO
import StringIO
21 from Program
import Op
, Block
25 import urllib
, urllib2
28 from constants
import *
34 for x
in node
.childNodes
:
35 if x
.nodeType
== Node
.ELEMENT_NODE
:
39 normal_chars
= string
.letters
+ string
.digits
+ "-"
41 fast_global
= re
.compile('//([-A-Za-z][-A-Za-z0-9]*:)?[-A-Za-z][-A-Za-z0-9]*$')
44 # - A ref to a DOM document
45 # - A set of current nodes
48 # It does not have any display code. It does contain code to perform actions
49 # (actions affect the document AND the view state).
51 # These actions can be repeated using '.'
62 "delete_node_no_clipboard",
93 "Recursivly compare two nodes."
94 if a
.nodeType
!= b
.nodeType
or a
.nodeName
!= b
.nodeName
:
96 if a
.nodeValue
!= b
.nodeValue
:
100 if len(aks
) != len(bks
):
102 for (ak
, bk
) in map(None, aks
, bks
):
107 class InProgress(Exception):
108 "Throw this if the operation will complete later..."
109 class Done(Exception):
110 "Thrown when the chain is completed successfully"
113 def __init__(self
, model
, callback_handlers
= None):
114 """callback_handlers is an (idle_add, idle_remove) tuple"""
118 self
.single_step
= 1 # 0 = Play 1 = Step-into 2 = Step-over
120 self
.chroots
= [] # (model, node, marked)
121 self
.foreach_stack
= [] # (block, [nodes], restore-nodes, restore-marks)
122 self
.current_nodes
= []
123 self
.clipboard
= None
124 self
.current_attrib
= None
127 if not callback_handlers
:
129 self
.idle_add
, self
.idle_remove
= g
.idle_add
, g
.idle_remove
131 self
.idle_add
, self
.idle_remove
= callback_handlers
133 self
.exec_point
= None # None, or (Op, Exit)
134 self
.rec_point
= None # None, or (Op, Exit)
135 self
.op_in_progress
= None
137 self
.callback_on_return
= None # Called when there are no more Ops...
138 self
.in_callback
= 0 # (not the above callback - this is the playback one)
139 self
.innermost_failure
= None
140 self
.call_on_done
= None # Called when there is nowhere to return to
141 self
.exec_stack
= [] # Ops we are inside (display use only)
143 self
.breakpoints
= {} # (op, exit) keys, values don't matter
144 self
.current_nodes
= []
145 self
.set_model(model
)
147 def get_current(self
):
148 if len(self
.current_nodes
) == 1:
149 return self
.current_nodes
[0]
150 raise Exception('This operation required exactly one selected node!')
152 def set_model(self
, model
):
153 assert not self
.marked
156 self
.model
.unlock(self
.root
)
159 self
.model
.remove_view(self
)
160 self
.model
.root_program
.watchers
.remove(self
)
162 self
.model
.root_program
.watchers
.append(self
)
164 self
.set_display_root(self
.model
.get_root())
165 self
.move_to(self
.root
)
168 return self
.idle_cb
!= 0 or self
.in_callback
170 def run_new(self
, callback
= None):
171 "Reset the playback system (stack, step-mode and point)."
172 "Call callback(exit) when execution finishes."
174 self
.idle_remove(self
.idle_cb
)
177 self
.innermost_failure
= None
178 self
.call_on_done
= callback
179 self
.callback_on_return
= None
180 while self
.exec_stack
:
182 self
.reset_foreach_stack()
183 self
.status_changed()
186 def reset_foreach_stack(self
):
187 for block
, nodes
, restore
, mark
in self
.foreach_stack
:
189 print "reset_foreach_stack: unlocking %d nodes" % len(mark
)
190 [self
.model
.unlock(x
) for x
in mark
]
191 self
.foreach_stack
= []
193 def push_stack(self
, op
):
194 if not isinstance(op
, Op
):
195 raise Exception('push_stack: not an Op', op
)
196 self
.exec_stack
.append(op
)
197 self
.update_stack(op
)
200 op
= self
.exec_stack
.pop()
201 self
.update_stack(op
)
203 def update_stack(self
, op
= None):
204 "Called when exec_stack or foreach_stack changes."
208 def set_exec(self
, pos
):
209 if self
.op_in_progress
:
210 raise Exception("Operation in progress...")
212 assert isinstance(pos
[0], Op
)
213 assert pos
[1] in ['next', 'fail']
214 self
.exec_point
= pos
216 #print "set_exec: %s:%s" % pos
220 def set_rec(self
, pos
):
224 self
.status_changed()
226 def record_at_point(self
):
227 if not self
.exec_point
:
228 alert("No current point!")
230 self
.set_rec(self
.exec_point
)
233 def stop_recording(self
):
235 self
.set_exec(self
.rec_point
)
238 alert("Not recording!")
240 def may_record(self
, action
):
241 "Perform and, possibly, record this action"
245 print "RECORD:", rec
, action
247 if action
== ['enter']:
248 new_op
= Block(op
.parent
)
249 new_op
.toggle_enter()
250 if len(self
.current_nodes
) > 1:
251 new_op
.toggle_foreach()
254 op
.link_to(new_op
, old_exit
)
259 if isinstance(new_op
, Block
):
260 self
.set_rec((new_op
.start
, 'next'))
262 self
.set_rec((new_op
, 'next'))
264 play_op
, exit
= self
.exec_point
265 # (do_one_step may have stopped recording)
267 self
.set_rec((new_op
, exit
))
273 self
.do_action(action
)
279 (type, val
, tb
) = sys
.exc_info()
280 #if not val.may_record:
286 rox
.report_exception()
288 def add_display(self
, display
):
289 "Calls move_from(old_node) when we move and update_all() on updates."
290 self
.displays
.append(display
)
291 #print "Added:", self.displays
293 def remove_display(self
, display
):
294 self
.displays
.remove(display
)
295 #print "Removed, now:", self.displays
296 if not self
.displays
:
299 def update_replace(self
, old
, new
):
302 if old
in self
.current_nodes
:
304 self
.model
.unlock(old
)
305 self
.current_nodes
.remove(old
)
306 self
.current_nodes
.append(new
)
307 self
.update_all(new
.parentNode
)
309 self
.update_all(new
.parentNode
)
311 def has_ancestor(self
, node
, ancestor
):
312 while node
!= ancestor
:
313 node
= node
.parentNode
318 def update_all(self
, node
):
319 for display
in self
.displays
:
320 display
.update_all(node
)
323 #print "View deleted"
324 self
.model
.root_program
.watchers
.remove(self
)
328 self
.model
.unlock(self
.root
)
330 self
.model
.remove_view(self
)
333 # 'nodes' may be either a node or a list of nodes.
334 # (duplicates will be removed)
335 # If it's a single node, then an 'attrib' node may also be specified
336 def move_to(self
, nodes
, attrib
= None):
337 if self
.current_nodes
== nodes
:
340 if attrib
and attrib
.nodeType
!= Node
.ATTRIBUTE_NODE
:
341 raise Exception('attrib not of type ATTRIBUTE_NODE!')
343 if type(nodes
) != list:
356 #if len(old) != len(nodes):
357 # print "(move_to: attempt to set duplicate nodes)"
359 old_nodes
= self
.current_nodes
360 self
.current_nodes
= nodes
362 for node
in self
.current_nodes
:
363 self
.model
.lock(node
)
364 for node
in old_nodes
:
365 self
.model
.unlock(node
)
367 self
.current_attrib
= attrib
369 for display
in self
.displays
:
370 display
.move_from(old_nodes
)
372 def move_prev_sib(self
):
374 new
= [n
.previousSibling
or 1/0 for n
in self
.current_nodes
]
379 def move_next_sib(self
):
381 new
= [n
.nextSibling
or 1/0 for n
in self
.current_nodes
]
388 for n
in self
.current_nodes
:
396 def move_right(self
):
398 for n
in self
.current_nodes
:
407 self
.move_to(self
.root
)
410 if not self
.get_current().childNodes
:
412 node
= self
.get_current().childNodes
[0]
413 while node
.nextSibling
:
414 node
= node
.nextSibling
417 def set_display_root(self
, root
):
418 self
.model
.lock(root
)
420 self
.model
.unlock(self
.root
)
422 self
.update_all(root
)
425 """Change the display root to a COPY of the selected node.
426 Call Leave to check changes back in."""
427 node
= self
.get_current()
428 if node
is self
.root
:
429 raise Beep
# Locking problems if this happens...
430 if self
.model
.doc
is not node
.ownerDocument
:
431 raise Exception('Current node not in view!')
435 new_model
= self
.model
.lock_and_copy(node
)
436 self
.chroots
.append((self
.model
, node
, self
.marked
))
437 self
.set_model(new_model
)
441 """Undo the effect of the last chroot()."""
449 (old_model
, old_node
, old_marked
) = self
.chroots
.pop()
452 copy
= old_model
.doc
.importNode(self
.model
.get_root(), 1)
453 old_model
.unlock(old_node
)
454 old_model
.replace_node(old_node
, copy
)
455 self
.set_model(old_model
)
457 self
.set_marked(old_marked
.keys())
460 model
.undo_stack
= None
466 def do_action(self
, action
):
467 "'action' is a tuple (function, arg1, arg2, ...)"
468 "Performs the action. Returns if action completes, or raises "
469 "InProgress if not (will call resume() later)."
470 if action
[0] in record_again
:
471 self
.last_action
= action
472 elif action
[0] == 'again':
473 action
= self
.last_action
474 fn
= getattr(self
, action
[0])
476 #print "DO:", action[0]
479 new
= apply(fn
, action
[1:])
483 if not self
.op_in_progress
:
488 if not self
.op_in_progress
:
490 traceback
.print_exc()
494 if self
.op_in_progress
:
495 op
= self
.op_in_progress
497 self
.set_exec((op
, exit
))
501 def breakpoint(self
):
502 if self
.breakpoints
.has_key(self
.exec_point
):
504 op
= self
.exec_point
[0]
505 if op
.parent
.start
== op
and op
.next
== None:
506 return 1 # Empty program
509 def do_one_step(self
):
510 "Execute the next op after exec_point, then:"
511 "- position the point on one of the exits return."
512 "- if there is no op to perform, call callback_on_return() or raise Done."
513 "- if the operation is started but not complete, raise InProgress and "
514 " arrange to resume() later."
515 if self
.op_in_progress
:
516 alert("Already executing something.")
518 if not self
.exec_point
:
519 alert("No current playback point.")
521 (op
, exit
) = self
.exec_point
523 if self
.single_step
== 0 and self
.breakpoint():
524 print "Hit a breakpoint! At " + time
.ctime(time
.time())
529 l
.show_prog(op
.get_program())
532 next
= getattr(op
, exit
)
536 self
.do_action(next
.action
) # May raise InProgress
539 if exit
== 'fail' and not self
.innermost_failure
:
540 #print "Setting innermost_failure on", op
541 self
.innermost_failure
= op
543 # If we're in a block, try exiting from it...
544 if isinstance(op
.parent
, Block
):
545 if self
.start_block_iteration(op
.parent
, continuing
= exit
):
547 if not op
.parent
.is_toplevel():
548 self
.set_exec((op
.parent
, exit
))
551 print "(skipped a whole program!)"
552 if self
.callback_on_return
:
553 cb
= self
.callback_on_return
554 self
.callback_on_return
= None
559 def set_oip(self
, op
):
560 #print "set_oip:", self.exec_point
563 self
.op_in_progress
= op
567 def fast_global(self
, name
):
568 "Search for nodes with this name anywhere under the root (//name)"
569 #print "Fast global", name
571 (prefix
, localName
) = string
.split(name
, ':', 1)
573 (prefix
, localName
) = (None, name
)
574 if self
.current_nodes
:
575 src
= self
.current_nodes
[-1]
578 namespaceURI
= self
.model
.prefix_to_namespace(src
, prefix
)
581 if node
.nodeType
!= Node
.ELEMENT_NODE
:
583 if node
.localName
== localName
and node
.namespaceURI
== namespaceURI
:
585 map(add
, node
.childNodes
)
591 def do_global(self
, pattern
):
592 if len(self
.current_nodes
) != 1:
593 self
.move_to(self
.root
)
594 if pattern
[:2] == '//':
595 if fast_global
.match(pattern
):
596 self
.fast_global(pattern
[2:])
599 assert not self
.op_in_progress
or (self
.op_in_progress
.action
[1] == pattern
)
601 code
= self
.op_in_progress
.cached_code
603 from Ft
.Xml
.XPath
import XPathParser
604 code
= XPathParser
.new().parse(self
.macro_pattern(pattern
))
605 if self
.op_in_progress
and pattern
.find('@CURRENT@') == -1:
606 self
.op_in_progress
.cached_code
= code
610 ns
= GetAllNs(self
.current_nodes
[0])
611 ns
['ext'] = FT_EXT_NAMESPACE
613 c
= Context
.Context(self
.get_current(), processorNss
= ns
)
615 nodes
= code
.evaluate(c
)
616 assert type(nodes
) == list
618 #don't select the document itself!
619 #Also, don't select attributes (needed for XSLT stuff)
620 nodes
= [n
for n
in nodes
if n
.parentNode
]
622 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
623 #print "Found", nodes
626 def select_children(self
):
628 for n
in self
.current_nodes
:
629 new
.extend(n
.childNodes
)
632 def select_region(self
, path
, ns
= None):
633 if len(self
.current_nodes
) == 0:
635 src
= self
.current_nodes
[-1]
638 ns
['ext'] = FT_EXT_NAMESPACE
639 c
= Context
.Context(src
, [src
], processorNss
= ns
)
640 rt
= XPath
.Evaluate(path
, context
= c
)
643 if not self
.has_ancestor(x
, self
.root
):
644 print "[ skipping search result above root ]"
649 print "*** Search for '%s' in select_region failed" % path
650 print " (namespaces were '%s')" % ns
652 if node
.parentNode
!= src
.parentNode
:
653 print "Nodes must have same parent!"
657 for n
in src
.parentNode
.childNodes
:
659 if n
is src
or n
is node
:
663 self
.move_to(selected
)
665 def macro_pattern(self
, pattern
):
666 """Do the @CURRENT@ substitution for an XPath"""
667 if len(self
.current_nodes
) != 1:
669 node
= self
.get_current()
670 if node
.nodeType
== Node
.TEXT_NODE
:
673 if self
.current_attrib
:
674 current
= self
.current_attrib
.value
676 current
= node
.nodeName
677 pattern
= pattern
.replace('@CURRENT@', current
)
678 #print "Searching for", pattern
681 def do_search(self
, pattern
, ns
= None, toggle
= FALSE
):
682 if len(self
.current_nodes
) == 0:
685 src
= self
.current_nodes
[-1]
687 # May be from a text_search...
688 #assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
690 code
= self
.op_in_progress
.cached_code
692 from Ft
.Xml
.XPath
import XPathParser
693 code
= XPathParser
.new().parse(self
.macro_pattern(pattern
))
694 if self
.op_in_progress
and pattern
.find('@CURRENT@') == -1:
695 self
.op_in_progress
.cached_code
= code
699 ns
['ext'] = FT_EXT_NAMESPACE
700 c
= Context
.Context(src
, [src
], processorNss
= ns
)
702 rt
= code
.evaluate(c
)
705 if not self
.has_ancestor(x
, self
.root
):
706 print "[ skipping search result above root ]"
710 #if self.node_to_line[x] > self.current_line:
714 #print "*** Search for '%s' failed" % pattern
715 #print " (namespaces were '%s')" % ns
718 new
= self
.current_nodes
[:]
727 def do_text_search(self
, pattern
):
728 pattern
= self
.macro_pattern(pattern
)
729 return self
.do_search("//text()[ext:match('%s')]" % pattern
)
731 def subst(self
, replace
, with
):
732 "re search and replace on the current node"
733 nodes
= self
.current_nodes
[:]
734 check
= len(nodes
) == 1
735 a
= self
.current_attrib
737 new
, num
= re
.subn(replace
, with
, a
.value
)
740 a
= self
.model
.set_attrib(nodes
[0], a
.name
, new
)
741 self
.move_to(nodes
[0], a
)
746 if n
.nodeType
== Node
.TEXT_NODE
:
747 old
= n
.data
.replace('\n', ' ')
748 new
, num
= re
.subn(replace
, with
, old
)
749 if check
and not num
:
752 self
.model
.set_data(n
, new
)
754 elif n
.nodeType
== Node
.ELEMENT_NODE
:
755 old
= str(n
.nodeName
)
756 new
, num
= re
.subn(replace
, with
, old
)
757 if check
and not num
:
760 new_ns
, x
= self
.model
.split_qname(n
, new
)
761 final
.append(self
.model
.set_name(n
, new_ns
, new
))
767 def xpath(self
, expr
):
768 "Put the result of 'expr' on the clipboard."
769 expr
= 'string(%s)' % expr
770 if len(self
.current_nodes
) == 0:
773 src
= self
.current_nodes
[-1]
776 code
= self
.op_in_progress
.cached_code
778 from Ft
.Xml
.XPath
import XPathParser
779 code
= XPathParser
.new().parse(self
.macro_pattern(expr
))
780 if self
.op_in_progress
and expr
.find('@CURRENT@') == -1:
781 self
.op_in_progress
.cached_code
= code
784 ns
['ext'] = FT_EXT_NAMESPACE
785 c
= Context
.Context(src
, [src
], processorNss
= ns
)
787 rt
= code
.evaluate(c
)
789 self
.clipboard
= self
.model
.doc
.createTextNode(rt
)
790 print "Result is", self
.clipboard
792 def python(self
, expr
):
793 "Replace node with result of expr(old_value)"
794 if self
.get_current().nodeType
== Node
.TEXT_NODE
:
795 vars = {'x': self
.get_current().data
, 're': re
, 'sub': re
.sub
, 'string': string
}
796 result
= eval(expr
, vars)
797 new
= self
.python_to_node(result
)
798 node
= self
.get_current()
800 self
.model
.replace_node(node
, new
)
805 def resume(self
, exit
= 'next'):
806 "After raising InProgress, call this to start moving again."
807 if self
.op_in_progress
:
808 op
= self
.op_in_progress
810 self
.set_exec((op
, exit
))
811 if not self
.single_step
:
813 self
.status_changed()
815 print "(nothing to resume)"
818 def ask_cb(result
, self
= self
):
822 self
.clipboard
= self
.model
.doc
.createTextNode(result
)
825 from GetArg
import GetArg
826 box
= GetArg('Input:', ask_cb
, [q
], destroy_return
= 1)
829 def python_to_node(self
, data
):
830 "Convert a python data structure into a tree and return the root."
831 if type(data
) == list:
832 nlist
= self
.model
.doc
.createElementNS(DOME_NS
, 'dome:list')
833 nlist
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:dome', DOME_NS
)
835 nlist
.appendChild(self
.python_to_node(x
))
837 return self
.model
.doc
.createTextNode(str(data
))
839 def yank(self
, deep
= 1):
840 if self
.current_attrib
:
841 a
= self
.current_attrib
843 self
.clipboard
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
844 self
.clipboard
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
846 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
847 for n
in self
.current_nodes
:
848 c
= n
.cloneNode(deep
)
850 self
.clipboard
.appendChild(c
)
852 #print "Clip now", self.clipboard
854 def shallow_yank(self
):
857 def delete_shallow(self
):
858 nodes
= self
.current_nodes
[:]
861 if self
.root
in nodes
:
866 self
.model
.delete_shallow(n
)
869 def delete_node_no_clipboard(self
):
870 self
.delete_node(yank
= 0)
872 def delete_node(self
, yank
= 1):
873 nodes
= self
.current_nodes
[:]
878 if self
.current_attrib
:
879 ca
= self
.current_attrib
880 self
.current_attrib
= None
881 self
.model
.set_attrib(self
.get_current(), ca
.name
, None)
883 if self
.root
in nodes
:
886 new
= [x
.parentNode
for x
in nodes
]
888 self
.model
.delete_nodes(nodes
)
891 nodes
= self
.current_nodes
[:]
893 self
.model
.unlock(self
.root
)
897 self
.model
.lock(self
.root
)
898 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
901 nodes
= self
.current_nodes
[:]
903 self
.model
.unlock(self
.root
)
907 self
.model
.lock(self
.root
)
908 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
910 def default_done(self
, exit
):
911 "Called when execution of a program returns. op_in_progress has been "
912 "restored - move to the exit."
913 #print "default_done(%s)" % exit
914 if self
.op_in_progress
:
915 op
= self
.op_in_progress
917 self
.set_exec((op
, exit
))
919 print "No operation to return to!"
920 c
= self
.call_on_done
922 self
.call_on_done
= None
925 self
.jump_to_innermost_failure()
928 def jump_to_innermost_failure(self
):
929 assert self
.innermost_failure
!= None
931 print "Returning to innermost failure:", self
.innermost_failure
932 self
.set_exec((self
.innermost_failure
, 'fail'))
934 if hasattr(l
, 'set_innermost_failure'):
935 l
.set_innermost_failure(self
.innermost_failure
)
937 def play(self
, name
, done
= None):
938 "Play this macro. When it returns, restore the current op_in_progress (if any)"
939 "and call done(exit). Default for done() moves exec_point."
940 "done() is called from do_one_step() - usual rules apply."
942 prog
= self
.name_to_prog(name
)
943 self
.innermost_failure
= None
946 done
= self
.default_done
948 def cbor(self
= self
, op
= self
.op_in_progress
, done
= done
,
950 old_cbor
= self
.callback_on_return
,
951 old_ss
= self
.single_step
):
952 "We're in do_one_step..."
954 #print "Return from '%s'..." % name
956 if old_ss
== 2 and self
.single_step
== 0:
957 self
.single_step
= old_ss
958 self
.callback_on_return
= old_cbor
960 o
, exit
= self
.exec_point
962 #print "Resume op '%s' (%s)" % (op.program.name, op)
967 self
.callback_on_return
= cbor
969 if self
.single_step
== 2:
972 if self
.op_in_progress
:
973 self
.push_stack(self
.op_in_progress
)
975 self
.play_block(prog
.code
)
977 self
.status_changed()
980 def start_block_iteration(self
, block
, continuing
= None):
981 "True if we are going to run the block, False to exit the loop"
982 "Continuing is 'next' or 'fail' if we reached the end of the block."
983 #print "Start interation"
984 if not self
.foreach_stack
:
986 stack_block
, nodes_list
, restore
, old_mark
= self
.foreach_stack
[-1]
987 if stack_block
!= block
:
988 self
.reset_foreach_stack()
990 raise Exception("Reached the end of a block we never entered")
996 restore
.extend(self
.current_nodes
)
997 if continuing
== 'fail':
998 print "Error in block; exiting early in program", block
.get_program()
1000 [self
.model
.unlock(x
) for x
in old_mark
]
1001 self
.foreach_stack
.pop()
1004 while nodes_list
and nodes_list
[0].parentNode
== None:
1005 print "Skipping deleted node", nodes_list
[0]
1009 self
.foreach_stack
.pop()
1012 nodes
= filter(lambda x
: self
.has_ancestor(x
, self
.root
), restore
)
1014 if old_mark
is not None:
1015 self
.set_marked(old_mark
)
1016 [self
.model
.unlock(x
) for x
in old_mark
]
1017 return 0 # Nothing left to do
1018 nodes
= nodes_list
[0]
1023 print "[ %d after this ]" % len(nodes_list
),
1028 self
.set_exec((block
.start
, 'next'))
1031 def play_block(self
, block
):
1032 assert isinstance(block
, Block
)
1033 #print "Enter Block!"
1035 list = self
.current_nodes
[:]
1037 list = [self
.current_nodes
[:]] # List of one item, containing everything
1040 marks
= self
.marked
.copy()
1041 [self
.model
.lock(x
) for x
in marks
]
1044 self
.foreach_stack
.append((block
, list, [], marks
))
1047 if not self
.start_block_iteration(block
):
1048 # No nodes selected...
1049 if not block
.is_toplevel():
1050 self
.set_exec((block
, 'next'))
1053 self
.set_exec((block
.start
, 'next'))
1057 assert self
.op_in_progress
1058 oip
= self
.op_in_progress
1060 self
.play_block(oip
)
1061 if not self
.single_step
:
1066 if self
.op_in_progress
:
1067 raise Exception("Operation in progress")
1069 raise Exception("Already playing!")
1070 self
.idle_cb
= self
.idle_add(self
.play_callback
)
1072 def play_callback(self
):
1073 self
.idle_remove(self
.idle_cb
)
1076 self
.in_callback
= 1
1080 self
.in_callback
= 0
1082 (op
, exit
) = self
.exec_point
1083 if exit
== 'fail' and self
.innermost_failure
:
1084 self
.jump_to_innermost_failure()
1085 print "Done, at " + time
.ctime(time
.time())
1092 type, val
, tb
= sys
.exc_info()
1093 list = traceback
.extract_tb(tb
)
1094 stack
= traceback
.format_list(list[-2:])
1095 ex
= traceback
.format_exception_only(type, val
) + ['\n\n'] + stack
1096 traceback
.print_exception(type, val
, tb
)
1097 print "Error in do_one_step(): stopping playback"
1098 node
= self
.op_in_progress
1101 self
.set_exec((node
, 'fail'))
1102 self
.status_changed()
1104 if self
.op_in_progress
or self
.single_step
:
1105 self
.status_changed()
1110 def status_changed(self
):
1111 for display
in self
.displays
:
1112 if hasattr(display
, 'update_state'):
1113 display
.update_state()
1115 def map(self
, name
):
1118 nodes
= self
.current_nodes
[:]
1120 print "map of nothing: skipping..."
1122 inp
= [nodes
, None] # Nodes, next
1123 def next(exit
= exit
, self
= self
, name
= name
, inp
= inp
):
1124 "This is called while in do_one_step() - normal rules apply."
1126 print "[ %d to go ]" % len(nodes
),
1129 print "Map: nodes remaining, but an error occurred..."
1130 return self
.default_done(exit
)
1131 while nodes
and nodes
[0].parentNode
== None:
1132 print "Skipping deleted node", nodes
[0]
1135 return self
.default_done(exit
)
1136 self
.move_to(nodes
[0])
1140 #print "Map: calling play (%d after this)" % len(nodes)
1141 self
.play(name
, done
= next
) # Should raise InProgress
1142 if nodes
is self
.current_nodes
:
1143 raise Exception("Slice failed!")
1147 def name_to_prog(self
, name
):
1148 comps
= string
.split(name
, '/')
1149 prog
= self
.model
.root_program
1150 if prog
.name
!= comps
[0]:
1151 raise Exception("No such program as '%s'!" % name
)
1154 prog
= prog
.subprograms
[comps
[0]]
1158 def change_node(self
, new_data
):
1159 nodes
= self
.current_nodes
1163 if nodes
[0].nodeType
== Node
.ELEMENT_NODE
:
1164 # Slow, so do this here, even if vaguely incorrect...
1165 assert ' ' not in new_data
1167 (prefix
, localName
) = string
.split(new_data
, ':', 1)
1169 (prefix
, localName
) = (None, new_data
)
1170 namespaceURI
= self
.model
.prefix_to_namespace(nodes
[0], prefix
)
1173 if node
is self
.root
:
1174 self
.model
.unlock(self
.root
)
1175 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1176 self
.model
.lock(new
)
1179 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1184 self
.model
.set_data(node
, new_data
)
1187 def add_node(self
, where
, data
):
1188 cur
= self
.get_current()
1191 (prefix
, localName
) = string
.split(data
, ':', 1)
1193 (prefix
, localName
) = (None, data
)
1194 namespaceURI
= self
.model
.prefix_to_namespace(self
.get_current(), prefix
)
1195 new
= self
.model
.doc
.createElementNS(namespaceURI
, data
)
1196 elif where
[1] == 'a':
1197 self
.add_attrib(None, data
)
1200 new
= self
.model
.doc
.createTextNode(data
)
1204 self
.model
.insert_before(cur
, new
)
1205 elif where
[0] == 'a':
1206 self
.model
.insert_after(cur
, new
)
1207 elif where
[0] == 'e':
1208 self
.model
.insert_before(None, new
, parent
= cur
)
1210 self
.model
.insert(cur
, new
)
1216 def request_from_node(self
, node
, attrib
):
1217 """Return a urllib2.Request object. If attrib is set then the URI is
1218 taken from that, otherwise search for a good attribute."""
1220 if node
.nodeType
== Node
.TEXT_NODE
:
1221 uri
= node
.nodeValue
1225 elif node
.hasAttributeNS(None, 'uri'):
1226 uri
= node
.getAttributeNS(None, 'uri')
1228 for attr
in node
.attributes
.keys():
1229 a_node
= node
.attributes
[attr
]
1230 if a_node
.namespaceURI
== XMLNS_NAMESPACE
:
1233 if uri
.find('//') != -1 or uri
.find('.htm') != -1:
1236 print "Can't suck", node
, "(no uri attribute found)"
1238 if uri
.find('//') == -1:
1239 base
= self
.model
.get_base_uri(node
)
1241 base
= os
.path
.dirname(base
)
1242 print "Relative URI..."
1244 print "Base URI is:", base
, "add", uri
1245 if uri
.startswith('/'):
1246 uri
= urlparse
.urljoin(base
, uri
)
1248 uri
= base
+ '/' + uri
1249 print "Final URI is:", uri
1252 #print "Warning: Can't find 'uri' attribute!"
1254 uri
= 'file://' + uri
1255 request
= urllib2
.Request(uri
)
1259 def http_post(self
):
1260 node
= self
.get_current()
1261 attrs
= node
.attributes
1263 request
= self
.request_from_node(node
, self
.current_attrib
)
1264 for (ns
,name
) in attrs
.keys():
1265 if ns
is not None: continue
1266 value
= str(attrs
[(ns
, name
)].value
)
1267 if name
.startswith('header-'):
1268 request
.add_header(str(name
)[7:], value
)
1270 post
.append((str(name
), value
))
1272 request
.add_data(urllib
.urlencode(post
))
1273 node
= self
.suck_node(node
, request
)
1277 def suck(self
, md5_only
= 0):
1278 nodes
= self
.current_nodes
[:]
1279 attrib
= self
.current_attrib
1283 request
= self
.request_from_node(x
, attrib
)
1285 new
= self
.suck_node(x
, request
, md5_only
= md5_only
)
1292 self
.suck(md5_only
= 1)
1294 def suck_node(self
, node
, request
, md5_only
= 0):
1295 """Load the resource specified by request and replace 'node' with the
1297 uri
= request
.get_full_url()
1298 if uri
.startswith('file:///'):
1299 print "Loading", uri
1301 assert not request
.has_data()
1302 stream
= open(uri
[7:])
1303 # (could read the mod time here...)
1306 print "Sucking", uri
1308 if request
.has_data():
1309 print "POSTING", request
.get_data()
1310 stream
= urllib2
.urlopen(request
)
1311 headers
= stream
.info().headers
1314 if x
.lower().startswith('last-modified:'):
1315 last_mod
= x
[14:].strip()
1318 current_last_mod
= node
.getAttributeNS(None, 'last-modified')
1319 if current_last_mod
and last_mod
:
1320 if current_last_mod
== last_mod
:
1321 self
.model
.set_attrib(node
, 'modified', None)
1322 print "not modified => not sucking!\n"
1325 print "Fetching page contents..."
1326 data
= stream
.read()
1327 print "got data... tidying..."
1329 if data
.startswith('<?xml'):
1332 data
= support
.to_html_doc(data
)
1333 #print "Converted to", data
1335 old_md5
= node
.getAttributeNS(None, 'md5_sum')
1338 new_md5
= md5
.new(data
).hexdigest()
1340 if old_md5
and new_md5
== old_md5
:
1341 self
.model
.set_attrib(node
, 'modified', None)
1342 print "MD5 sums match => not parsing!"
1346 # This is a nasty hack left in for backwards compat.
1347 self
.model
.set_attrib(node
, 'md5_sum', new_md5
)
1353 root
= support
.parse_data(data
, uri
)
1357 new
= node
.ownerDocument
.importNode(root
.documentElement
, 1)
1358 new
.setAttributeNS(None, 'uri', uri
)
1361 new
.setAttributeNS(None, 'last-modified', last_mod
)
1362 new
.setAttributeNS(None, 'modified', 'yes')
1363 new
.setAttributeNS(None, 'md5_sum', new_md5
)
1366 if node
== self
.root
:
1367 self
.model
.unlock(self
.root
)
1368 self
.model
.replace_node(self
.root
, new
)
1369 self
.model
.strip_space(new
)
1370 self
.model
.lock(new
)
1373 self
.model
.replace_node(node
, new
)
1374 self
.model
.strip_space(new
)
1379 def put_before(self
):
1380 node
= self
.get_current()
1381 if self
.clipboard
== None:
1383 new
= self
.clipboard
.cloneNode(1)
1385 self
.model
.insert_before(node
, new
)
1389 def put_after(self
):
1390 node
= self
.get_current()
1391 if self
.clipboard
== None:
1393 new
= self
.clipboard
.cloneNode(1)
1394 self
.model
.insert_after(node
, new
)
1396 def put_replace(self
):
1397 node
= self
.get_current()
1398 if self
.clipboard
== None:
1399 print "No clipboard!"
1401 if self
.current_attrib
:
1402 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1403 value
= self
.clipboard
.childNodes
[0].data
1405 value
= self
.clipboard
.data
1406 a
= self
.current_attrib
1407 value
= value
.replace('\n', ' ')
1408 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1409 self
.move_to(node
, a
)
1411 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1412 if len(self
.clipboard
.childNodes
) != 1:
1413 print "Multiple nodes in clipboard!"
1415 new
= self
.clipboard
.childNodes
[0].cloneNode(1)
1417 new
= self
.clipboard
.cloneNode(1)
1418 if new
.nodeType
!= Node
.ELEMENT_NODE
:
1422 if node
== self
.root
:
1423 self
.model
.unlock(self
.root
)
1425 self
.model
.replace_node(self
.root
, new
)
1428 self
.model
.lock(self
.root
)
1430 self
.model
.replace_node(node
, new
)
1433 type, val
, tb
= sys
.exc_info()
1434 traceback
.print_exception(type, val
, tb
)
1435 print "Replace failed!"
1438 def put_as_child_end(self
):
1439 self
.put_as_child(end
= 1)
1441 def put_as_child(self
, end
= 0):
1442 node
= self
.get_current()
1443 if self
.clipboard
== None:
1445 new
= self
.clipboard
.cloneNode(1)
1446 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1448 for n
in new
.childNodes
:
1454 self
.model
.insert_before(None, new
, parent
= node
)
1456 self
.model
.insert(node
, new
, index
= 0)
1462 def yank_value(self
):
1463 if not self
.current_attrib
:
1465 value
= self
.current_attrib
.value
1466 self
.clipboard
= self
.model
.doc
.createTextNode(value
)
1467 #print "Clip now", self.clipboard
1469 def yank_attribs(self
, name
= None):
1471 print "yank_attribs: DEPRECATED -- use Yank instead!"
1472 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
1474 if not self
.get_current().hasAttributeNS(None, name
):
1476 attribs
= [self
.get_current().getAttributeNodeNS(None, name
)]
1479 dict = self
.get_current().attributes
1480 for a
in dict.keys():
1481 attribs
.append(dict[a
])
1483 # Make sure the attributes always come out in the same order
1484 # (helps with macros).
1486 diff
= cmp(a
.name
, b
.name
)
1488 diff
= cmp(a
.namespaceURI
, b
.namespaceURI
)
1491 attribs
.sort(by_name
)
1493 n
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
1494 n
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
1495 self
.clipboard
.appendChild(n
)
1496 #print "Clip now", self.clipboard
1498 def paste_attribs(self
):
1499 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1500 attribs
= self
.clipboard
.childNodes
1502 attribs
= [self
.clipboard
]
1506 new
.append((a
.nodeName
, a
.childNodes
[0].data
))
1509 for node
in self
.current_nodes
:
1510 # XXX: Set NS attribs first...
1511 for (name
, value
) in new
:
1512 self
.model
.set_attrib(node
, name
, value
)
1515 "Ensure that all selected nodes have the same value."
1516 if len(self
.current_nodes
) < 2:
1517 raise Beep
# Not enough nodes!
1518 base
= self
.current_nodes
[0]
1519 for n
in self
.current_nodes
[1:]:
1520 if not same(base
, n
):
1521 raise Beep(may_record
= 1)
1524 raise Beep(may_record
= 1)
1529 def fail_if(self
, xpath
):
1530 """Evaluate xpath as a boolean, and fail if true."""
1531 src
= self
.get_current()
1533 ns
['ext'] = FT_EXT_NAMESPACE
1534 c
= Context
.Context(src
.parentNode
, [src
.parentNode
], processorNss
= ns
)
1536 rt
= XPath
.Evaluate(xpath
, context
= c
)
1539 raise Beep(may_record
= 1)
1541 def attribute(self
, namespace
= None, attrib
= ''):
1542 node
= self
.get_current()
1548 if attrib
== 'xmlns':
1550 #print "(ns, attrib)", `namespace`, attrib
1552 a
= node
.attributes
.get((namespace
, attrib
), None)
1555 self
.move_to(node
, a
)
1557 print "No such attribute"
1558 print "Looking for %s in %s" % ((namespace
, attrib
), node
.attributes
)
1561 def set_attrib(self
, value
):
1562 a
= self
.current_attrib
1565 node
= self
.get_current()
1566 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1567 self
.move_to(node
, a
)
1569 def rename_attrib(self
, new
):
1570 a
= self
.current_attrib
1573 node
= self
.get_current()
1574 new_attr
= self
.model
.set_attrib(node
, new
, a
.value
)
1575 self
.model
.set_attrib(node
, a
.name
, None)
1576 self
.move_to(node
, new_attr
)
1578 def add_attrib(self
, UNUSED
, name
, value
= ''):
1579 node
= self
.get_current()
1580 a
= self
.model
.set_attrib(node
, name
, value
)
1581 self
.move_to(node
, a
)
1583 def set_root_from_doc(self
, doc
):
1584 new
= self
.root
.ownerDocument
.importNode(doc
.documentElement
, 1)
1587 self
.model
.unlock(self
.root
)
1589 self
.model
.replace_node(self
.root
, new
)
1590 self
.model
.lock(new
)
1592 self
.move_to(self
.root
)
1594 def load_html(self
, path
):
1595 "Replace root with contents of this HTML file."
1596 print "Reading HTML..."
1597 doc
= self
.model
.load_html(path
)
1598 self
.set_root_from_doc(doc
)
1600 def load_xml(self
, path
):
1601 "Replace root with contents of this XML (or Dome) file."
1602 print "Reading XML..."
1603 data
= file(path
).read()
1604 doc
= support
.parse_data(data
, path
)
1605 self
.set_root_from_doc(doc
)
1607 def load_node(self
, root
):
1608 new
= self
.model
.doc
.importNode(root
, 1)
1610 self
.model
.strip_space(new
)
1613 self
.model
.unlock(self
.root
)
1615 self
.model
.replace_node(self
.root
, new
)
1616 self
.model
.lock(new
)
1618 self
.move_to(self
.root
)
1620 def select_dups(self
):
1621 node
= self
.get_current()
1623 for n
in node
.parentNode
.childNodes
:
1628 self
.move_to(select
)
1630 def select_marked_region(self
, attr
= "unused"):
1632 if len(self
.marked
) != 1:
1633 print "Must be exactly one marked node!"
1635 if len(self
.current_nodes
) != 1:
1636 print "Must be exactly one selected node!"
1639 a
= Path
.path_to(self
.get_current())
1640 b
= Path
.path_to(self
.marked
.keys()[0])
1642 while a
and b
and a
[0] == b
[0]:
1651 for x
in a
.parentNode
.childNodes
:
1658 self
.move_to(select
)
1660 print "One node is a parent of the other!"
1663 def show_html(self
):
1664 from HTML
import HTML
1665 HTML(self
.model
, self
.get_current()).show()
1667 def show_canvas(self
):
1668 from Canvas
import Canvas
1669 Canvas(self
, self
.get_current()).show()
1671 def toggle_hidden(self
):
1672 nodes
= self
.current_nodes
[:]
1675 if node
.nodeType
!= Node
.ELEMENT_NODE
:
1677 if node
.hasAttributeNS(None, 'hidden'):
1681 self
.model
.set_attrib(node
, 'hidden', new
, with_update
= 0)
1682 self
.model
.update_all(self
.root
)
1685 def soap_send(self
):
1686 copy
= node_to_xml(self
.get_current())
1687 env
= copy
.documentElement
1689 if env
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/':
1690 alert("Not a SOAP-ENV:Envelope (bad namespace)")
1692 if env
.localName
!= 'Envelope':
1693 alert("Not a SOAP-ENV:Envelope (bad local name)")
1696 if len(env
.childNodes
) != 2:
1697 alert("SOAP-ENV:Envelope must have one header and one body")
1700 kids
= elements(env
)
1704 if head
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1705 head
.localName
!= 'Head':
1706 alert("First child must be a SOAP-ENV:Head element")
1709 if body
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1710 body
.localName
!= 'Body':
1711 alert("Second child must be a SOAP-ENV:Body element")
1715 for header
in elements(head
):
1716 if header
.namespaceURI
== DOME_NS
and header
.localName
== 'soap-forward-to':
1719 print header
.namespaceURI
1720 print header
.localName
1723 alert("Head must contain a dome:soap-forward-to element")
1726 dest
= sft
.childNodes
[0].data
1727 parent
= sft
.parentNode
1728 if len(elements(parent
)) == 1:
1730 parent
= sft
.parentNode
# Delete the whole header
1731 parent
.removeChild(sft
)
1733 import httplib
, urlparse
1735 (scheme
, addr
, path
, p
, q
, f
) = urlparse
.urlparse(dest
, allow_fragments
= 0)
1736 if scheme
!= 'http':
1737 alert("SOAP is only supported for 'http:' -- sorry!")
1741 PrettyPrint(copy
, stream
= stream
)
1742 message
= stream
.data
1744 conn
= httplib
.HTTP(addr
)
1745 conn
.putrequest("POST", path
)
1746 conn
.putheader('Content-Type', 'text/xml; charset="utf-8"')
1747 conn
.putheader('Content-Length', str(len(message
)))
1748 conn
.putheader('SOAPAction', '')
1751 (code
, r_mess
, r_headers
) = conn
.getreply()
1753 reply
= conn
.getfile().read()
1754 print "Got:\n", reply
1756 reader
= PyExpat
.Reader() # XXX
1757 new_doc
= reader
.fromString(reply
)
1760 new
= self
.model
.doc
.importNode(new_doc
.documentElement
, 1)
1762 self
.model
.strip_space(new
)
1764 old
= self
.get_current()
1766 self
.model
.replace_node(old
, new
)
1769 def program_changed(self
, changed_op
):
1770 print "Check points..."
1772 (op
, exit
) = self
.rec_point
1774 print "Lost rec_point"
1775 self
.rec_point
= None
1777 (op
, exit
) = self
.exec_point
1779 print "Lost exec_point"
1780 self
.exec_point
= None
1781 for l
in self
.lists
:
1783 self
.status_changed()
1785 def prog_tree_changed(self
):
1788 def export_all(self
):
1789 doc
= implementation
.createDocument(DOME_NS
, 'dome', None)
1790 node
= self
.model
.root_program
.to_xml(doc
)
1791 doc
.documentElement
.appendChild(node
)
1792 node
= doc
.createElementNS(DOME_NS
, 'dome-data')
1793 doc
.documentElement
.appendChild(node
)
1796 print "*** WARNING: Saving from a chroot!"
1798 data
= doc
.importNode(model
.doc
.documentElement
, 1)
1799 node
.appendChild(data
)
1803 def blank_all(self
):
1804 doc
= implementation
.createDocument(None, 'root', None)
1806 self
.clipboard
= self
.model
.doc
.createElementNS(None, 'root')
1809 def mark_switch(self
):
1810 new
= self
.marked
.keys()
1811 self
.set_marked(self
.current_nodes
)
1814 def set_marked(self
, new
):
1815 update
= self
.marked
1816 for x
in self
.marked
.keys():
1817 self
.model
.unlock(x
)
1821 self
.marked
[x
] = None
1823 update
= update
.keys()
1824 for display
in self
.displays
:
1825 display
.marked_changed(update
)
1827 def mark_selection(self
):
1828 self
.set_marked(self
.current_nodes
)
1830 def clear_mark(self
):
1833 def normalise(self
):
1834 self
.model
.normalise(self
.get_current())
1836 def remove_ns(self
):
1837 nodes
= self
.current_nodes
[:]
1839 nodes
= map(self
.model
.remove_ns
, nodes
)
1842 def convert_to(self
, fn
):
1843 nodes
= self
.current_nodes
[:]
1845 nodes
= map(fn
, nodes
)
1847 def convert_to_text(self
): self
.convert_to(self
.model
.convert_to_text
)
1848 def convert_to_comment(self
): self
.convert_to(self
.model
.convert_to_comment
)
1853 def write(self
, str):