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 *
32 #http://www.w3.org/2001/12/soap-envelope'
33 SOAPENV_NS
= 'http://schemas.xmlsoap.org/soap/envelope/'
37 for x
in node
.childNodes
:
38 if x
.nodeType
== Node
.ELEMENT_NODE
:
42 normal_chars
= string
.letters
+ string
.digits
+ "-"
44 fast_global
= re
.compile('//([-A-Za-z][-A-Za-z0-9]*:)?[-A-Za-z][-A-Za-z0-9]*$')
47 # - A ref to a DOM document
48 # - A set of current nodes
51 # It does not have any display code. It does contain code to perform actions
52 # (actions affect the document AND the view state).
54 # These actions can be repeated using '.'
66 "delete_node_no_clipboard",
97 "Recursivly compare two nodes."
98 if a
.nodeType
!= b
.nodeType
or a
.nodeName
!= b
.nodeName
:
100 if a
.nodeValue
!= b
.nodeValue
:
104 if len(aks
) != len(bks
):
106 for (ak
, bk
) in map(None, aks
, bks
):
111 class InProgress(Exception):
112 "Throw this if the operation will complete later..."
113 class Done(Exception):
114 "Thrown when the chain is completed successfully"
117 def __init__(self
, model
, callback_handlers
= None):
118 """callback_handlers is an (idle_add, idle_remove) tuple"""
122 self
.single_step
= 1 # 0 = Play 1 = Step-into 2 = Step-over
124 self
.chroots
= [] # (model, node, marked)
125 self
.foreach_stack
= [] # (block, [nodes], restore-nodes, restore-marks)
126 self
.current_nodes
= []
127 self
.clipboard
= None
128 self
.current_attrib
= None
131 if not callback_handlers
:
133 self
.idle_add
, self
.idle_remove
= g
.idle_add
, g
.idle_remove
135 self
.idle_add
, self
.idle_remove
= callback_handlers
137 self
.exec_point
= None # None, or (Op, Exit)
138 self
.rec_point
= None # None, or (Op, Exit)
139 self
.op_in_progress
= None
141 self
.callback_on_return
= None # Called when there are no more Ops...
142 self
.in_callback
= 0 # (not the above callback - this is the playback one)
143 self
.innermost_failure
= None
144 self
.call_on_done
= None # Called when there is nowhere to return to
145 self
.exec_stack
= [] # Ops we are inside (display use only)
147 self
.breakpoints
= {} # (op, exit) keys, values don't matter
148 self
.current_nodes
= []
149 self
.set_model(model
)
151 def get_current(self
):
152 if len(self
.current_nodes
) == 1:
153 return self
.current_nodes
[0]
154 raise Exception('This operation required exactly one selected node!')
156 def set_model(self
, model
):
157 assert not self
.marked
160 self
.model
.unlock(self
.root
)
163 self
.model
.remove_view(self
)
164 self
.model
.root_program
.watchers
.remove(self
)
166 self
.model
.root_program
.watchers
.append(self
)
168 self
.set_display_root(self
.model
.get_root())
169 self
.move_to(self
.root
)
172 return self
.idle_cb
!= 0 or self
.in_callback
174 def run_new(self
, callback
= None):
175 "Reset the playback system (stack, step-mode and point)."
176 "Call callback(exit) when execution finishes."
178 self
.idle_remove(self
.idle_cb
)
181 self
.innermost_failure
= None
182 self
.call_on_done
= callback
183 self
.callback_on_return
= None
184 while self
.exec_stack
:
186 self
.reset_foreach_stack()
187 self
.status_changed()
189 self
.set_status(None)
191 def reset_foreach_stack(self
):
192 for block
, nodes
, restore
, mark
in self
.foreach_stack
:
194 print "reset_foreach_stack: unlocking %d nodes" % len(mark
)
195 [self
.model
.unlock(x
) for x
in mark
]
196 self
.foreach_stack
= []
198 def push_stack(self
, op
):
199 if not isinstance(op
, Op
):
200 raise Exception('push_stack: not an Op', op
)
201 self
.exec_stack
.append(op
)
202 self
.update_stack(op
)
205 op
= self
.exec_stack
.pop()
206 self
.update_stack(op
)
208 def update_stack(self
, op
= None):
209 "Called when exec_stack or foreach_stack changes."
213 def set_exec(self
, pos
):
214 if self
.op_in_progress
:
215 raise Exception("Operation in progress...")
217 assert isinstance(pos
[0], Op
)
218 assert pos
[1] in ['next', 'fail']
219 self
.exec_point
= pos
221 #print "set_exec: %s:%s" % pos
225 def set_rec(self
, pos
):
229 self
.status_changed()
231 def record_at_point(self
):
232 if not self
.exec_point
:
233 alert("No current point!")
235 self
.set_rec(self
.exec_point
)
238 def stop_recording(self
):
240 self
.set_exec(self
.rec_point
)
243 alert("Not recording!")
245 def may_record(self
, action
):
246 "Perform and, possibly, record this action"
250 print "RECORD:", rec
, action
252 if action
== ['enter']:
253 new_op
= Block(op
.parent
)
254 new_op
.toggle_enter()
255 if len(self
.current_nodes
) > 1:
256 new_op
.toggle_foreach()
259 op
.link_to(new_op
, old_exit
)
264 if isinstance(new_op
, Block
):
265 self
.set_rec((new_op
.start
, 'next'))
267 self
.set_rec((new_op
, 'next'))
269 play_op
, exit
= self
.exec_point
270 # (do_one_step may have stopped recording)
272 self
.set_rec((new_op
, exit
))
278 self
.do_action(action
)
284 (type, val
, tb
) = sys
.exc_info()
285 #if not val.may_record:
291 rox
.report_exception()
293 def add_display(self
, display
):
294 "Calls move_from(old_node) when we move and update_all() on updates."
295 self
.displays
.append(display
)
296 #print "Added:", self.displays
298 def remove_display(self
, display
):
299 self
.displays
.remove(display
)
300 #print "Removed, now:", self.displays
301 if not self
.displays
:
304 def update_replace(self
, old
, new
):
307 if old
in self
.current_nodes
:
309 self
.model
.unlock(old
)
310 self
.current_nodes
.remove(old
)
311 self
.current_nodes
.append(new
)
312 self
.update_all(new
.parentNode
)
314 self
.update_all(new
.parentNode
)
316 def has_ancestor(self
, node
, ancestor
):
317 while node
!= ancestor
:
318 node
= node
.parentNode
323 def update_all(self
, node
):
324 for display
in self
.displays
:
325 display
.update_all(node
)
328 #print "View deleted"
329 self
.model
.root_program
.watchers
.remove(self
)
333 self
.model
.unlock(self
.root
)
335 self
.model
.remove_view(self
)
338 # 'nodes' may be either a node or a list of nodes.
339 # (duplicates will be removed)
340 # If it's a single node, then an 'attrib' node may also be specified
341 def move_to(self
, nodes
, attrib
= None):
342 if self
.current_nodes
== nodes
:
345 if attrib
and attrib
.nodeType
!= Node
.ATTRIBUTE_NODE
:
346 raise Exception('attrib not of type ATTRIBUTE_NODE!')
348 if type(nodes
) != list:
352 for n
in nodes
: assert n
.nodeType
363 #if len(old) != len(nodes):
364 # print "(move_to: attempt to set duplicate nodes)"
366 old_nodes
= self
.current_nodes
367 self
.current_nodes
= nodes
369 for node
in self
.current_nodes
:
370 self
.model
.lock(node
)
371 for node
in old_nodes
:
372 self
.model
.unlock(node
)
374 self
.current_attrib
= attrib
376 for display
in self
.displays
:
377 display
.move_from(old_nodes
)
379 def move_prev_sib(self
):
381 new
= [n
.previousSibling
or 1/0 for n
in self
.current_nodes
]
386 def move_next_sib(self
):
388 new
= [n
.nextSibling
or 1/0 for n
in self
.current_nodes
]
395 for n
in self
.current_nodes
:
403 def move_right(self
):
405 for n
in self
.current_nodes
:
414 self
.move_to(self
.root
)
417 if not self
.get_current().childNodes
:
419 node
= self
.get_current().childNodes
[0]
420 while node
.nextSibling
:
421 node
= node
.nextSibling
424 def set_display_root(self
, root
):
425 self
.model
.lock(root
)
427 self
.model
.unlock(self
.root
)
429 self
.update_all(root
)
432 """Change the display root to a COPY of the selected node.
433 Call Leave to check changes back in."""
434 node
= self
.get_current()
435 if node
is self
.root
:
436 raise Beep
# Locking problems if this happens...
437 if node
.nodeType
!= Node
.ELEMENT_NODE
:
438 raise Exception('Can only enter an element!')
439 if self
.model
.doc
is not node
.ownerDocument
:
440 raise Exception('Current node not in view!')
444 new_model
= self
.model
.lock_and_copy(node
)
445 self
.chroots
.append((self
.model
, node
, self
.marked
))
446 self
.set_model(new_model
)
450 """Undo the effect of the last chroot()."""
458 (old_model
, old_node
, old_marked
) = self
.chroots
.pop()
461 copy
= old_model
.import_with_ns(self
.model
.get_root())
462 old_model
.unlock(old_node
)
463 old_model
.replace_node(old_node
, copy
)
464 self
.set_model(old_model
)
466 self
.set_marked(old_marked
.keys())
469 model
.undo_stack
= None
475 def do_action(self
, action
):
476 "'action' is a tuple (function, arg1, arg2, ...)"
477 "Performs the action. Returns if action completes, or raises "
478 "InProgress if not (will call resume() later)."
479 if action
[0] in record_again
:
480 self
.last_action
= action
481 elif action
[0] == 'again':
482 action
= self
.last_action
483 fn
= getattr(self
, action
[0])
485 #print "DO:", action[0]
488 new
= apply(fn
, action
[1:])
492 if not self
.op_in_progress
:
497 if not self
.op_in_progress
:
499 traceback
.print_exc()
503 if self
.op_in_progress
:
504 op
= self
.op_in_progress
506 self
.set_exec((op
, exit
))
510 def breakpoint(self
):
511 if self
.breakpoints
.has_key(self
.exec_point
):
513 op
= self
.exec_point
[0]
514 if op
.parent
.start
== op
and op
.next
== None:
515 return 1 # Empty program
518 def do_one_step(self
):
519 "Execute the next op after exec_point, then:"
520 "- position the point on one of the exits return."
521 "- if there is no op to perform, call callback_on_return() or raise Done."
522 "- if the operation is started but not complete, raise InProgress and "
523 " arrange to resume() later."
524 if self
.op_in_progress
:
525 alert("Already executing something.")
527 if not self
.exec_point
:
528 alert("No current playback point.")
530 (op
, exit
) = self
.exec_point
532 if self
.single_step
== 0 and self
.breakpoint():
533 print "Hit a breakpoint! At " + time
.ctime(time
.time())
538 l
.show_prog(op
.get_program())
541 next
= getattr(op
, exit
)
545 self
.do_action(next
.action
) # May raise InProgress
548 if exit
== 'fail' and not self
.innermost_failure
:
549 #print "Setting innermost_failure on", op
550 self
.innermost_failure
= op
552 # If we're in a block, try exiting from it...
553 if isinstance(op
.parent
, Block
):
554 if self
.start_block_iteration(op
.parent
, continuing
= exit
):
556 if not op
.parent
.is_toplevel():
557 self
.set_exec((op
.parent
, exit
))
560 print "(skipped a whole program!)"
561 if self
.callback_on_return
:
562 cb
= self
.callback_on_return
563 self
.callback_on_return
= None
568 def set_oip(self
, op
):
569 #print "set_oip:", self.exec_point
572 self
.op_in_progress
= op
576 def fast_global(self
, name
):
577 "Search for nodes with this name anywhere under the root (//name)"
578 #print "Fast global", name
580 (prefix
, localName
) = string
.split(name
, ':', 1)
582 (prefix
, localName
) = (None, name
)
583 if self
.current_nodes
:
584 src
= self
.current_nodes
[-1]
587 namespaceURI
= self
.model
.prefix_to_namespace(src
, prefix
)
590 if node
.nodeType
!= Node
.ELEMENT_NODE
:
592 if node
.localName
== localName
and node
.namespaceURI
== namespaceURI
:
594 map(add
, node
.childNodes
)
600 def do_global(self
, pattern
):
601 if len(self
.current_nodes
) != 1:
602 self
.move_to(self
.root
)
603 if pattern
[:2] == '//':
604 if fast_global
.match(pattern
):
605 self
.fast_global(pattern
[2:])
608 assert not self
.op_in_progress
or (self
.op_in_progress
.action
[1] == pattern
)
609 code
= self
.cached_code(pattern
)
611 ns
= self
.model
.namespaces
.uri
612 c
= Context
.Context(self
.get_current(), processorNss
= ns
)
614 nodes
= code
.evaluate(c
)
615 assert type(nodes
) == list
617 #don't select the document itself!
618 #Also, don't select attributes (needed for XSLT stuff)
619 nodes
= [n
for n
in nodes
if n
.parentNode
]
621 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
622 #print "Found", nodes
625 def select_children(self
):
627 for n
in self
.current_nodes
:
628 new
.extend(n
.childNodes
)
631 def select_region(self
, path
, ns
= None):
632 if len(self
.current_nodes
) == 0:
634 src
= self
.current_nodes
[-1]
636 ns
= self
.model
.namespaces
.uri
637 c
= Context
.Context(src
, [src
], processorNss
= ns
)
638 rt
= XPath
.Evaluate(path
, context
= c
)
641 if not self
.has_ancestor(x
, self
.root
):
642 print "[ skipping search result above root ]"
647 print "*** Search for '%s' in select_region failed" % path
648 print " (namespaces were '%s')" % ns
650 if node
.parentNode
!= src
.parentNode
:
651 print "Nodes must have same parent!"
655 for n
in src
.parentNode
.childNodes
:
657 if n
is src
or n
is node
:
661 self
.move_to(selected
)
663 def macro_pattern(self
, pattern
):
664 """Do the @CURRENT@ substitution for an XPath"""
665 if len(self
.current_nodes
) != 1:
667 node
= self
.get_current()
668 if node
.nodeType
== Node
.TEXT_NODE
:
671 if self
.current_attrib
:
672 current
= self
.current_attrib
.value
674 current
= node
.nodeName
675 pattern
= pattern
.replace('@CURRENT@', current
)
676 #print "Searching for", pattern
679 def do_search(self
, pattern
, ns
= None, toggle
= FALSE
):
680 if len(self
.current_nodes
) == 0:
683 src
= self
.current_nodes
[-1]
685 # May be from a text_search...
686 #assert not self.op_in_progress or (self.op_in_progress.action[1] == pattern)
688 code
= self
.op_in_progress
.cached_code
690 from Ft
.Xml
.XPath
import XPathParser
691 code
= XPathParser
.new().parse(self
.macro_pattern(pattern
))
692 if self
.op_in_progress
and pattern
.find('@CURRENT@') == -1:
693 self
.op_in_progress
.cached_code
= code
695 ns
= self
.model
.namespaces
.uri
696 c
= Context
.Context(src
, [src
], processorNss
= ns
)
698 rt
= code
.evaluate(c
)
701 if not self
.has_ancestor(x
, self
.root
):
702 print "[ skipping search result above root ]"
706 #if self.node_to_line[x] > self.current_line:
710 #print "*** Search for '%s' failed" % pattern
711 #print " (namespaces were '%s')" % ns
714 new
= self
.current_nodes
[:]
723 def do_text_search(self
, pattern
):
724 pattern
= self
.macro_pattern(pattern
)
725 return self
.do_search("//text()[ext:match('%s')]" % pattern
)
727 def subst(self
, replace
, with
):
728 "re search and replace on the current node"
729 nodes
= self
.current_nodes
[:]
730 check
= len(nodes
) == 1
731 a
= self
.current_attrib
733 new
, num
= re
.subn(replace
, with
, a
.value
)
736 a
= self
.model
.set_attrib(nodes
[0], a
.name
, new
)
737 self
.move_to(nodes
[0], a
)
742 if n
.nodeType
== Node
.TEXT_NODE
:
743 old
= n
.data
.replace('\n', ' ')
744 new
, num
= re
.subn(replace
, with
, old
)
745 if check
and not num
:
748 self
.model
.set_data(n
, new
)
750 elif n
.nodeType
== Node
.ELEMENT_NODE
:
751 old
= str(n
.nodeName
)
752 new
, num
= re
.subn(replace
, with
, old
)
753 if check
and not num
:
756 new_ns
, x
= self
.model
.split_qname(n
, new
)
757 final
.append(self
.model
.set_name(n
, new_ns
, new
))
763 def xpath(self
, expr
):
764 "Put the result of 'expr' on the clipboard."
765 expr
= 'string(%s)' % expr
766 if len(self
.current_nodes
) == 0:
769 src
= self
.current_nodes
[-1]
772 code
= self
.op_in_progress
.cached_code
774 from Ft
.Xml
.XPath
import XPathParser
775 code
= XPathParser
.new().parse(self
.macro_pattern(expr
))
776 if self
.op_in_progress
and expr
.find('@CURRENT@') == -1:
777 self
.op_in_progress
.cached_code
= code
779 ns
= self
.model
.namespaces
.uri
780 c
= Context
.Context(src
, [src
], processorNss
= ns
)
782 rt
= code
.evaluate(c
)
784 self
.clipboard
= self
.model
.doc
.createTextNode(rt
)
785 print "Result is", self
.clipboard
787 def python(self
, expr
):
788 "Replace node with result of expr(old_value)"
789 if self
.get_current().nodeType
== Node
.TEXT_NODE
:
790 vars = {'x': self
.get_current().data
, 're': re
, 'sub': re
.sub
, 'string': string
}
791 result
= eval(expr
, vars)
792 new
= self
.python_to_node(result
)
793 node
= self
.get_current()
795 self
.model
.replace_node(node
, new
)
800 def resume(self
, exit
= 'next'):
801 "After raising InProgress, call this to start moving again."
802 if self
.op_in_progress
:
803 op
= self
.op_in_progress
805 self
.set_exec((op
, exit
))
806 if not self
.single_step
:
808 self
.status_changed()
810 print "(nothing to resume)"
813 def ask_cb(result
, self
= self
):
817 self
.clipboard
= self
.model
.doc
.createTextNode(result
)
820 from GetArg
import GetArg
821 box
= GetArg('Input:', ask_cb
, [q
], destroy_return
= 1)
824 def python_to_node(self
, data
):
825 "Convert a python data structure into a tree and return the root."
826 if type(data
) == list:
827 nlist
= self
.model
.doc
.createElementNS(DOME_NS
, 'dome:list')
828 #nlist.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:dome', DOME_NS)
830 li
= self
.model
.doc
.createElementNS(DOME_NS
, 'dome:li')
831 nlist
.appendChild(li
)
832 li
.appendChild(self
.python_to_node(x
))
834 return self
.model
.doc
.createTextNode(str(data
))
836 def yank(self
, deep
= 1):
837 if self
.current_attrib
:
838 a
= self
.current_attrib
840 self
.clipboard
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
841 self
.clipboard
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
843 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
844 for n
in self
.current_nodes
:
845 c
= n
.cloneNode(deep
)
847 self
.clipboard
.appendChild(c
)
849 #print "Clip now", self.clipboard
851 def shallow_yank(self
):
854 def delete_shallow(self
):
855 nodes
= self
.current_nodes
[:]
858 if self
.root
in nodes
:
862 new
= [x
.parentNode
for x
in nodes
]
864 self
.model
.delete_shallow(n
)
867 def delete_node_no_clipboard(self
):
868 self
.delete_node(yank
= 0)
870 def delete_node(self
, yank
= 1):
871 nodes
= self
.current_nodes
[:]
876 if self
.current_attrib
:
877 ca
= self
.current_attrib
878 self
.current_attrib
= None
879 self
.model
.set_attrib(self
.get_current(), ca
.name
, None)
881 if self
.root
in nodes
:
884 new
= [x
.parentNode
for x
in nodes
]
886 self
.model
.delete_nodes(nodes
)
889 nodes
= self
.current_nodes
[:]
891 self
.model
.unlock(self
.root
)
895 self
.model
.lock(self
.root
)
896 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
899 nodes
= self
.current_nodes
[:]
901 self
.model
.unlock(self
.root
)
905 self
.model
.lock(self
.root
)
906 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
908 def default_done(self
, exit
):
909 "Called when execution of a program returns. op_in_progress has been "
910 "restored - move to the exit."
911 #print "default_done(%s)" % exit
912 if self
.op_in_progress
:
913 op
= self
.op_in_progress
915 self
.set_exec((op
, exit
))
917 print "No operation to return to!"
918 c
= self
.call_on_done
920 self
.call_on_done
= None
923 self
.jump_to_innermost_failure()
926 def jump_to_innermost_failure(self
):
927 assert self
.innermost_failure
!= None
929 print "Returning to innermost failure:", self
.innermost_failure
930 self
.set_exec((self
.innermost_failure
, 'fail'))
932 if hasattr(l
, 'set_innermost_failure'):
933 l
.set_innermost_failure(self
.innermost_failure
)
935 def play(self
, name
, done
= None):
936 "Play this macro. When it returns, restore the current op_in_progress (if any)"
937 "and call done(exit). Default for done() moves exec_point."
938 "done() is called from do_one_step() - usual rules apply."
940 prog
= self
.name_to_prog(name
)
941 self
.innermost_failure
= None
944 done
= self
.default_done
946 def cbor(self
= self
, op
= self
.op_in_progress
, done
= done
,
948 old_cbor
= self
.callback_on_return
,
949 old_ss
= self
.single_step
):
950 "We're in do_one_step..."
952 #print "Return from '%s'..." % name
954 if old_ss
== 2 and self
.single_step
== 0:
955 self
.single_step
= old_ss
956 self
.callback_on_return
= old_cbor
958 o
, exit
= self
.exec_point
960 #print "Resume op '%s' (%s)" % (op.program.name, op)
965 self
.callback_on_return
= cbor
967 if self
.single_step
== 2:
970 if self
.op_in_progress
:
971 self
.push_stack(self
.op_in_progress
)
973 self
.play_block(prog
.code
)
975 self
.status_changed()
978 def start_block_iteration(self
, block
, continuing
= None):
979 "True if we are going to run the block, False to exit the loop"
980 "Continuing is 'next' or 'fail' if we reached the end of the block."
981 #print "Start interation"
982 if not self
.foreach_stack
:
984 stack_block
, nodes_list
, restore
, old_mark
= self
.foreach_stack
[-1]
985 if stack_block
!= block
:
986 self
.reset_foreach_stack()
988 raise Exception("Reached the end of a block we never entered")
994 restore
.extend(self
.current_nodes
)
995 if continuing
== 'fail':
996 print "Error in block; exiting early in program", block
.get_program()
998 [self
.model
.unlock(x
) for x
in old_mark
]
999 self
.foreach_stack
.pop()
1002 while nodes_list
and nodes_list
[0].parentNode
== None:
1003 print "Skipping deleted node", nodes_list
[0]
1007 self
.foreach_stack
.pop()
1010 nodes
= filter(lambda x
: self
.has_ancestor(x
, self
.root
), restore
)
1012 if old_mark
is not None:
1013 self
.set_marked(old_mark
)
1014 [self
.model
.unlock(x
) for x
in old_mark
]
1015 return 0 # Nothing left to do
1016 nodes
= nodes_list
[0]
1021 print "[ %d after this ]" % len(nodes_list
),
1026 self
.set_exec((block
.start
, 'next'))
1029 def play_block(self
, block
):
1030 assert isinstance(block
, Block
)
1031 #print "Enter Block!"
1033 list = self
.current_nodes
[:]
1035 list = [self
.current_nodes
[:]] # List of one item, containing everything
1038 marks
= self
.marked
.copy()
1039 [self
.model
.lock(x
) for x
in marks
]
1042 self
.foreach_stack
.append((block
, list, [], marks
))
1045 if not self
.start_block_iteration(block
):
1046 # No nodes selected...
1047 if not block
.is_toplevel():
1048 self
.set_exec((block
, 'next'))
1051 self
.set_exec((block
.start
, 'next'))
1055 assert self
.op_in_progress
1056 oip
= self
.op_in_progress
1058 self
.play_block(oip
)
1059 if not self
.single_step
:
1064 if self
.op_in_progress
:
1065 raise Exception("Operation in progress")
1067 raise Exception("Already playing!")
1068 self
.idle_cb
= self
.idle_add(self
.play_callback
)
1070 def play_callback(self
):
1071 self
.idle_remove(self
.idle_cb
)
1074 self
.in_callback
= 1
1078 self
.in_callback
= 0
1080 (op
, exit
) = self
.exec_point
1081 if exit
== 'fail' and self
.innermost_failure
:
1082 self
.jump_to_innermost_failure()
1083 print "Done, at " + time
.ctime(time
.time())
1090 type, val
, tb
= sys
.exc_info()
1091 list = traceback
.extract_tb(tb
)
1092 stack
= traceback
.format_list(list[-2:])
1093 ex
= traceback
.format_exception_only(type, val
) + ['\n\n'] + stack
1094 traceback
.print_exception(type, val
, tb
)
1095 print "Error in do_one_step(): stopping playback"
1096 node
= self
.op_in_progress
1099 self
.set_exec((node
, 'fail'))
1100 self
.status_changed()
1102 if self
.op_in_progress
or self
.single_step
:
1103 self
.status_changed()
1108 def set_status(self
, message
= None):
1109 "Set the status bar message."
1110 for d
in self
.displays
:
1111 if hasattr(d
, 'set_status'): d
.set_status(message
)
1113 def status_changed(self
):
1114 for display
in self
.displays
:
1115 if hasattr(display
, 'update_state'):
1116 display
.update_state()
1118 def map(self
, name
):
1121 nodes
= self
.current_nodes
[:]
1123 print "map of nothing: skipping..."
1125 inp
= [nodes
, None] # Nodes, next
1126 def next(exit
= exit
, self
= self
, name
= name
, inp
= inp
):
1127 "This is called while in do_one_step() - normal rules apply."
1129 print "[ %d to go ]" % len(nodes
),
1132 print "Map: nodes remaining, but an error occurred..."
1133 return self
.default_done(exit
)
1134 while nodes
and nodes
[0].parentNode
== None:
1135 print "Skipping deleted node", nodes
[0]
1138 return self
.default_done(exit
)
1139 self
.move_to(nodes
[0])
1143 #print "Map: calling play (%d after this)" % len(nodes)
1144 self
.play(name
, done
= next
) # Should raise InProgress
1145 if nodes
is self
.current_nodes
:
1146 raise Exception("Slice failed!")
1150 def name_to_prog(self
, name
):
1151 comps
= string
.split(name
, '/')
1152 prog
= self
.model
.root_program
1153 if prog
.name
!= comps
[0]:
1154 raise Exception("No such program as '%s'!" % name
)
1157 prog
= prog
.subprograms
[comps
[0]]
1161 def change_node(self
, new_data
):
1162 nodes
= self
.current_nodes
1166 if nodes
[0].nodeType
== Node
.ELEMENT_NODE
:
1167 # Slow, so do this here, even if vaguely incorrect...
1168 assert ' ' not in new_data
1170 (prefix
, localName
) = string
.split(new_data
, ':', 1)
1172 (prefix
, localName
) = (None, new_data
)
1173 namespaceURI
= self
.model
.prefix_to_namespace(nodes
[0], prefix
)
1176 if node
is self
.root
:
1177 self
.model
.unlock(self
.root
)
1178 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1179 self
.model
.lock(new
)
1182 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1187 self
.model
.set_data(node
, new_data
)
1190 def add_node(self
, where
, data
):
1191 cur
= self
.get_current()
1194 (prefix
, localName
) = string
.split(data
, ':', 1)
1196 (prefix
, localName
) = (None, data
)
1197 namespaceURI
= self
.model
.prefix_to_namespace(self
.get_current(), prefix
)
1198 new
= self
.model
.doc
.createElementNS(namespaceURI
, data
)
1199 elif where
[1] == 'a':
1200 self
.add_attrib(None, data
)
1203 new
= self
.model
.doc
.createTextNode(data
)
1207 self
.model
.insert_before(cur
, new
)
1208 elif where
[0] == 'a':
1209 self
.model
.insert_after(cur
, new
)
1210 elif where
[0] == 'e':
1211 self
.model
.insert_before(None, new
, parent
= cur
)
1213 self
.model
.insert(cur
, new
)
1219 def request_from_node(self
, node
, attrib
):
1220 """Return a urllib2.Request object. If attrib is set then the URI is
1221 taken from that, otherwise search for a good attribute."""
1223 if node
.nodeType
== Node
.TEXT_NODE
:
1224 uri
= node
.nodeValue
1228 elif node
.hasAttributeNS(None, 'uri'):
1229 uri
= node
.getAttributeNS(None, 'uri')
1231 for attr
in node
.attributes
.keys():
1232 a_node
= node
.attributes
[attr
]
1233 if a_node
.namespaceURI
== XMLNS_NAMESPACE
:
1236 if uri
.find('//') != -1 or uri
.find('.htm') != -1:
1239 print "Can't suck", node
, "(no uri attribute found)"
1241 if uri
.find('//') == -1:
1242 base
= self
.model
.get_base_uri(node
)
1244 base
= os
.path
.dirname(base
)
1245 print "Relative URI..."
1247 print "Base URI is:", base
, "add", uri
1248 if uri
.startswith('/'):
1249 uri
= urlparse
.urljoin(base
, uri
)
1251 uri
= base
+ '/' + uri
1252 print "Final URI is:", uri
1255 #print "Warning: Can't find 'uri' attribute!"
1257 uri
= 'file://' + uri
1258 request
= urllib2
.Request(uri
)
1262 def http_post(self
):
1263 node
= self
.get_current()
1264 attrs
= node
.attributes
1266 request
= self
.request_from_node(node
, self
.current_attrib
)
1267 for (ns
,name
) in attrs
.keys():
1268 if ns
is not None: continue
1269 value
= str(attrs
[(ns
, name
)].value
)
1270 if name
.startswith('header-'):
1271 request
.add_header(str(name
)[7:], value
)
1273 post
.append((str(name
), value
))
1275 request
.add_data(urllib
.urlencode(post
))
1276 node
= self
.suck_node(node
, request
)
1280 def suck(self
, md5_only
= 0):
1281 nodes
= self
.current_nodes
[:]
1282 attrib
= self
.current_attrib
1286 request
= self
.request_from_node(x
, attrib
)
1288 new
= self
.suck_node(x
, request
, md5_only
= md5_only
)
1297 self
.suck(md5_only
= 1)
1299 def suck_node(self
, node
, request
, md5_only
= 0):
1300 """Load the resource specified by request and replace 'node' with the
1302 uri
= request
.get_full_url()
1303 self
.set_status("Fetching %s (connecting)..." % uri
)
1306 if uri
.startswith('file:///'):
1307 assert not request
.has_data()
1308 stream
= open(uri
[7:])
1310 if request
.has_data(): print "POSTING", request
.get_data()
1311 stream
= urllib2
.urlopen(request
)
1312 headers
= stream
.info().headers
1314 self
.set_status("Fetching %s (downloading)..." % uri
)
1315 data
= stream
.read()
1316 self
.set_status("Fetching %s (parsing)..." % uri
)
1318 if not data
.startswith('<?xml'): data
= support
.to_html_doc(data
)
1321 root
= support
.parse_data(data
, uri
)
1325 new
= self
.model
.import_with_ns(root
.documentElement
)
1326 new
.setAttributeNS(None, 'uri', uri
)
1329 if node
== self
.root
:
1330 self
.model
.unlock(self
.root
)
1331 self
.model
.replace_node(self
.root
, new
)
1332 self
.model
.strip_space(new
)
1333 self
.model
.lock(new
)
1336 self
.model
.replace_node(node
, new
)
1337 self
.model
.strip_space(new
)
1343 def put_before(self
):
1344 node
= self
.get_current()
1345 if self
.clipboard
== None:
1347 new
= self
.clipboard
.cloneNode(1)
1349 self
.model
.insert_before(node
, new
)
1353 def put_after(self
):
1354 node
= self
.get_current()
1355 if self
.clipboard
== None:
1357 new
= self
.clipboard
.cloneNode(1)
1358 self
.model
.insert_after(node
, new
)
1360 def put_replace(self
):
1361 node
= self
.get_current()
1362 if self
.clipboard
== None:
1363 print "No clipboard!"
1365 if self
.current_attrib
:
1366 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1367 value
= self
.clipboard
.childNodes
[0].data
1369 value
= self
.clipboard
.data
1370 a
= self
.current_attrib
1371 value
= value
.replace('\n', ' ')
1372 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1373 self
.move_to(node
, a
)
1375 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1376 if len(self
.clipboard
.childNodes
) != 1:
1377 print "Multiple nodes in clipboard!"
1379 new
= self
.clipboard
.childNodes
[0].cloneNode(1)
1381 new
= self
.clipboard
.cloneNode(1)
1382 if new
.nodeType
!= Node
.ELEMENT_NODE
:
1386 if node
== self
.root
:
1387 self
.model
.unlock(self
.root
)
1389 self
.model
.replace_node(self
.root
, new
)
1392 self
.model
.lock(self
.root
)
1394 self
.model
.replace_node(node
, new
)
1397 type, val
, tb
= sys
.exc_info()
1398 traceback
.print_exception(type, val
, tb
)
1399 print "Replace failed!"
1402 def put_as_child_end(self
):
1403 self
.put_as_child(end
= 1)
1405 def put_as_child(self
, end
= 0):
1406 node
= self
.get_current()
1407 if self
.clipboard
== None:
1409 new
= self
.clipboard
.cloneNode(1)
1410 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1412 for n
in new
.childNodes
:
1418 self
.model
.insert_before(None, new
, parent
= node
)
1420 self
.model
.insert(node
, new
, index
= 0)
1426 def yank_value(self
):
1427 if not self
.current_attrib
:
1429 value
= self
.current_attrib
.value
1430 self
.clipboard
= self
.model
.doc
.createTextNode(value
)
1431 #print "Clip now", self.clipboard
1433 def yank_attribs(self
, name
= None):
1435 print "yank_attribs: DEPRECATED -- use Yank instead!"
1436 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
1438 if not self
.get_current().hasAttributeNS(None, name
):
1440 attribs
= [self
.get_current().getAttributeNodeNS(None, name
)]
1443 dict = self
.get_current().attributes
1444 for a
in dict.keys():
1445 attribs
.append(dict[a
])
1447 # Make sure the attributes always come out in the same order
1448 # (helps with macros).
1450 diff
= cmp(a
.name
, b
.name
)
1452 diff
= cmp(a
.namespaceURI
, b
.namespaceURI
)
1455 attribs
.sort(by_name
)
1457 n
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
1458 n
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
1459 self
.clipboard
.appendChild(n
)
1460 #print "Clip now", self.clipboard
1462 def paste_attribs(self
):
1463 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1464 attribs
= self
.clipboard
.childNodes
1466 attribs
= [self
.clipboard
]
1470 new
.append((a
.nodeName
, a
.childNodes
[0].data
))
1473 for node
in self
.current_nodes
:
1474 # XXX: Set NS attribs first...
1475 for (name
, value
) in new
:
1476 self
.model
.set_attrib(node
, name
, value
)
1479 "Ensure that all selected nodes have the same value."
1480 if len(self
.current_nodes
) < 2:
1481 raise Beep
# Not enough nodes!
1482 base
= self
.current_nodes
[0]
1483 for n
in self
.current_nodes
[1:]:
1484 if not same(base
, n
):
1485 raise Beep(may_record
= 1)
1488 raise Beep(may_record
= 1)
1493 def fail_if(self
, xpath
):
1494 """Evaluate xpath as a boolean, and fail if true."""
1495 src
= self
.get_current()
1496 ns
= self
.model
.namespaces
.uri
1497 c
= Context
.Context(src
.parentNode
, [src
.parentNode
], processorNss
= ns
)
1499 rt
= XPath
.Evaluate(xpath
, context
= c
)
1502 raise Beep(may_record
= 1)
1504 def attribute(self
, namespace
= None, attrib
= ''):
1505 node
= self
.get_current()
1511 if attrib
== 'xmlns':
1513 #print "(ns, attrib)", `namespace`, attrib
1515 a
= node
.attributes
.get((namespace
, attrib
), None)
1518 self
.move_to(node
, a
)
1520 print "No such attribute"
1521 print "Looking for %s in %s" % ((namespace
, attrib
), node
.attributes
)
1524 def set_attrib(self
, value
):
1525 a
= self
.current_attrib
1528 node
= self
.get_current()
1529 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1530 self
.move_to(node
, a
)
1532 def rename_attrib(self
, new
):
1533 a
= self
.current_attrib
1536 node
= self
.get_current()
1537 new_attr
= self
.model
.set_attrib(node
, new
, a
.value
)
1538 self
.model
.set_attrib(node
, a
.name
, None)
1539 self
.move_to(node
, new_attr
)
1541 def add_attrib(self
, UNUSED
, name
, value
= ''):
1542 node
= self
.get_current()
1543 a
= self
.model
.set_attrib(node
, name
, value
)
1544 self
.move_to(node
, a
)
1546 def set_root_from_doc(self
, doc
):
1547 new
= self
.model
.import_with_ns(doc
.documentElement
)
1550 self
.model
.unlock(self
.root
)
1552 self
.model
.replace_node(self
.root
, new
)
1553 self
.model
.lock(new
)
1555 self
.move_to(self
.root
)
1557 def load_html(self
, path
):
1558 "Replace root with contents of this HTML file."
1559 print "Reading HTML..."
1560 doc
= self
.model
.load_html(path
)
1561 self
.set_root_from_doc(doc
)
1563 def load_xml(self
, path
):
1564 "Replace root with contents of this XML (or Dome) file."
1565 print "Reading XML..."
1566 data
= file(path
).read()
1567 doc
= support
.parse_data(data
, path
)
1568 self
.set_root_from_doc(doc
)
1570 def select_dups(self
):
1571 node
= self
.get_current()
1573 for n
in node
.parentNode
.childNodes
:
1578 self
.move_to(select
)
1580 def select_marked_region(self
, attr
= "unused"):
1582 if len(self
.marked
) != 1:
1583 print "Must be exactly one marked node!"
1585 if len(self
.current_nodes
) != 1:
1586 print "Must be exactly one selected node!"
1589 a
= Path
.path_to(self
.get_current())
1590 b
= Path
.path_to(self
.marked
.keys()[0])
1592 while a
and b
and a
[0] == b
[0]:
1601 for x
in a
.parentNode
.childNodes
:
1608 self
.move_to(select
)
1610 print "One node is a parent of the other!"
1613 def show_html(self
):
1614 from HTML
import HTML
1615 HTML(self
.model
, self
.get_current()).show()
1617 def show_canvas(self
):
1618 from Canvas
import Canvas
1619 Canvas(self
, self
.get_current()).show()
1621 def toggle_hidden(self
, message
= None):
1622 """'message' is a XPath to calculate the message to display.
1623 If None, nodes are toggled between hidden and not hidden."""
1625 from Ft
.Xml
.XPath
import XPathParser
1626 code
= XPathParser
.new().parse('string(%s)' % message
)
1630 nodes
= self
.current_nodes
[:]
1632 hidden
= self
.model
.hidden
1633 hidden_code
= self
.model
.hidden_code
1635 if node
.nodeType
!= Node
.ELEMENT_NODE
:
1639 hidden_code
[node
] = code
1643 if node
in hidden_code
:
1644 ns
= self
.model
.namespaces
.uri
1645 c
= Context
.Context(node
, [node
], processorNss
= ns
)
1646 hidden
[node
] = hidden_code
[node
].evaluate(c
).strip()
1648 hidden
[node
] = 'hidden'
1649 self
.model
.update_all(self
.root
)
1652 def soap_send(self
):
1653 copy
= node_to_xml(self
.get_current())
1654 env
= copy
.documentElement
1655 from Ft
.Xml
.Lib
.Nss
import GetAllNs
1657 for p
, u
in self
.model
.namespaces
.uri
.iteritems():
1660 elif p
not in ('xml', 'xmlns'):
1661 env
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:%s' % p
, u
)
1663 if env
.namespaceURI
!= SOAPENV_NS
:
1664 alert("Not a SOAP-ENV:Envelope (bad namespace)")
1666 if env
.localName
!= 'Envelope':
1667 alert("Not a SOAP-ENV:Envelope (bad local name)")
1670 if len(env
.childNodes
) != 2:
1671 alert("SOAP-ENV:Envelope must have one header and one body")
1674 kids
= elements(env
)
1678 if head
.namespaceURI
!= SOAPENV_NS
or \
1679 head
.localName
!= 'Head':
1680 alert("First child must be a SOAP-ENV:Head element")
1683 if body
.namespaceURI
!= SOAPENV_NS
or \
1684 body
.localName
!= 'Body':
1685 alert("Second child must be a SOAP-ENV:Body element")
1689 for header
in elements(head
):
1690 if header
.namespaceURI
== DOME_NS
and header
.localName
== 'soap-forward-to':
1693 print header
.namespaceURI
1694 print header
.localName
1697 alert("Head must contain a dome:soap-forward-to element")
1700 dest
= sft
.childNodes
[0].data
1701 parent
= sft
.parentNode
1702 if len(elements(parent
)) == 1:
1704 parent
= sft
.parentNode
# Delete the whole header
1705 parent
.removeChild(sft
)
1707 import httplib
, urlparse
1709 (scheme
, addr
, path
, p
, q
, f
) = urlparse
.urlparse(dest
, allow_fragments
= 0)
1710 if scheme
!= 'http':
1711 alert("SOAP is only supported for 'http:' -- sorry!")
1715 PrettyPrint(copy
, stream
= stream
)
1716 message
= stream
.data
1719 conn
= httplib
.HTTP(addr
)
1720 conn
.putrequest("POST", path
)
1721 conn
.putheader('Content-Type', 'text/xml; charset="utf-8"')
1722 conn
.putheader('Content-Length', str(len(message
)))
1723 conn
.putheader('SOAPAction', '')
1726 (code
, r_mess
, r_headers
) = conn
.getreply()
1728 reply
= conn
.getfile().read()
1729 print "Got:\n", reply
1731 new_doc
= support
.parse_data(reply
, None)
1733 new
= self
.model
.doc
.importNode(new_doc
.documentElement
, 1)
1735 self
.model
.strip_space(new
)
1737 old
= self
.get_current()
1739 self
.model
.replace_node(old
, new
)
1742 def program_changed(self
, changed_op
):
1743 print "Check points..."
1745 (op
, exit
) = self
.rec_point
1747 print "Lost rec_point"
1748 self
.rec_point
= None
1750 (op
, exit
) = self
.exec_point
1752 print "Lost exec_point"
1753 self
.exec_point
= None
1754 for l
in self
.lists
:
1756 self
.status_changed()
1758 def prog_tree_changed(self
):
1761 def export_all(self
):
1762 doc
= implementation
.createDocument(DOME_NS
, 'dome:dome', None)
1764 doc
.documentElement
.appendChild(self
.model
.namespaces
.to_xml(doc
))
1766 node
= self
.model
.root_program
.to_xml(doc
)
1767 doc
.documentElement
.appendChild(node
)
1768 node
= doc
.createElementNS(DOME_NS
, 'dome:dome-data')
1769 doc
.documentElement
.appendChild(node
)
1772 print "*** WARNING: Saving from a chroot!"
1774 data
= doc
.importNode(model
.doc
.documentElement
, 1)
1775 node
.appendChild(data
)
1779 def blank_all(self
):
1780 doc
= implementation
.createDocument(None, 'root', None)
1782 self
.clipboard
= self
.model
.doc
.createElementNS(None, 'root')
1785 def mark_switch(self
):
1786 new
= self
.marked
.keys()
1787 self
.set_marked(self
.current_nodes
)
1790 def set_marked(self
, new
):
1791 update
= self
.marked
1792 for x
in self
.marked
.keys():
1793 self
.model
.unlock(x
)
1797 self
.marked
[x
] = None
1799 update
= update
.keys()
1800 for display
in self
.displays
:
1801 display
.marked_changed(update
)
1803 def mark_selection(self
):
1804 self
.set_marked(self
.current_nodes
)
1806 def cached_code(self
, expr
):
1807 """If self.op_in_progress has cached code, return that. Otherwise, compile and cache code."""
1809 return self
.op_in_progress
.cached_code
1811 from Ft
.Xml
.XPath
import XPathParser
1812 code
= XPathParser
.new().parse(self
.macro_pattern(expr
))
1813 if self
.op_in_progress
and expr
.find('@CURRENT@') == -1:
1814 self
.op_in_progress
.cached_code
= code
1817 def move_selection(self
, xpath
):
1818 "Move selected node to parent identified by xpath."
1819 src
= self
.get_current()
1820 code
= self
.cached_code(xpath
)
1822 c
= Context
.Context(src
, [src
], processorNss
= self
.model
.namespaces
.uri
)
1823 rt
= code
.evaluate(c
)
1828 print "Multiple matches!", rt
1833 self
.model
.delete_nodes([src
])
1834 self
.model
.insert(dst
, src
)
1836 def move_marked(self
):
1837 to
= self
.get_current()
1838 if to
.nodeType
!= Node
.ELEMENT_NODE
:
1842 self
.model
.delete_nodes(tmp
)
1844 self
.model
.insert(to
, n
)
1845 self
.set_marked(tmp
)
1847 def clear_mark(self
):
1850 def normalise(self
):
1851 self
.model
.normalise(self
.get_current())
1853 def remove_ns(self
):
1854 print "remove_ns: Disabled"
1856 nodes
= self
.current_nodes
[:]
1858 nodes
= map(self
.model
.remove_ns
, nodes
)
1861 def convert_to(self
, fn
):
1862 nodes
= self
.current_nodes
[:]
1864 nodes
= map(fn
, nodes
)
1866 def convert_to_element(self
): self
.convert_to(self
.model
.convert_to_element
)
1867 def convert_to_text(self
): self
.convert_to(self
.model
.convert_to_text
)
1868 def convert_to_comment(self
): self
.convert_to(self
.model
.convert_to_comment
)
1873 def write(self
, str):