1 from __future__
import nested_scopes
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
14 from StringIO
import StringIO
16 from Program
import Op
, Block
20 import urllib
, urllib2
23 from constants
import *
26 entrefpattern
= re
.compile('&(\D\S+);')
30 for x
in node
.childNodes
:
31 if x
.nodeType
== Node
.ELEMENT_NODE
:
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?
45 data
= data
.replace('<o:p></o:p>', '')
46 data
= re
.sub('<!\[[^]]*\]>', '', data
)
52 #data = data.replace(' ', ' ')
53 #data = data.replace('©', '(c)')
54 #data = data.replace('ä', '(auml)')
55 #data = data.replace('ö', '(ouml)')
56 fixed
= fix_broken_html(data
)
64 tin
= os
.popen('tidy --force-output yes -q -utf8 -asxml 2>/dev/null', 'w')
66 tin
= os
.popen('tidy --force-output yes -q -asxml 2>/dev/null', 'w')
67 tin
.write(fixed
or data
)
73 data
= os
.fdopen(r
).read()
79 # - A ref to a DOM document
80 # - A set of current nodes
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 '.'
97 "delete_node_no_clipboard",
128 "Recursivly compare two nodes."
129 if a
.nodeType
!= b
.nodeType
or a
.nodeName
!= b
.nodeName
:
131 if a
.nodeValue
!= b
.nodeValue
:
135 if len(aks
) != len(bks
):
137 for (ak
, bk
) in map(None, aks
, bks
):
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"
148 def __init__(self
, model
, callback_handlers
= None):
149 """callback_handlers is an (idle_add, idle_remove) tuple"""
153 self
.single_step
= 1 # 0 = Play 1 = Step-into 2 = Step-over
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
162 if not callback_handlers
:
164 self
.idle_add
, self
.idle_remove
= g
.idle_add
, g
.idle_remove
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
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
191 self
.model
.unlock(self
.root
)
194 self
.model
.remove_view(self
)
195 self
.model
.root_program
.watchers
.remove(self
)
197 self
.model
.root_program
.watchers
.append(self
)
199 self
.set_display_root(self
.model
.get_root())
200 self
.move_to(self
.root
)
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."
209 self
.idle_remove(self
.idle_cb
)
212 self
.innermost_failure
= None
213 self
.call_on_done
= callback
214 self
.callback_on_return
= None
215 while self
.exec_stack
:
217 self
.reset_foreach_stack()
218 self
.status_changed()
221 def reset_foreach_stack(self
):
222 for block
, nodes
, restore
, mark
in self
.foreach_stack
:
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
)
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."
243 def set_exec(self
, pos
):
244 if self
.op_in_progress
:
245 raise Exception("Operation in progress...")
247 assert isinstance(pos
[0], Op
)
248 assert pos
[1] in ['next', 'fail']
249 self
.exec_point
= pos
251 #print "set_exec: %s:%s" % pos
255 def set_rec(self
, pos
):
259 self
.status_changed()
261 def record_at_point(self
):
262 if not self
.exec_point
:
263 alert("No current point!")
265 self
.set_rec(self
.exec_point
)
268 def stop_recording(self
):
270 self
.set_exec(self
.rec_point
)
273 alert("Not recording!")
275 def may_record(self
, action
):
276 "Perform and, possibly, record this action"
280 print "RECORD:", rec
, action
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()
289 op
.link_to(new_op
, old_exit
)
294 if isinstance(new_op
, Block
):
295 self
.set_rec((new_op
.start
, 'next'))
297 self
.set_rec((new_op
, 'next'))
299 play_op
, exit
= self
.exec_point
300 # (do_one_step may have stopped recording)
302 self
.set_rec((new_op
, exit
))
308 self
.do_action(action
)
314 (type, val
, tb
) = sys
.exc_info()
315 #if not val.may_record:
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
:
334 def update_replace(self
, old
, new
):
337 if old
in self
.current_nodes
:
339 self
.model
.unlock(old
)
340 self
.current_nodes
.remove(old
)
341 self
.current_nodes
.append(new
)
342 self
.update_all(new
.parentNode
)
344 self
.update_all(new
.parentNode
)
346 def has_ancestor(self
, node
, ancestor
):
347 while node
!= ancestor
:
348 node
= node
.parentNode
353 def update_all(self
, node
):
354 for display
in self
.displays
:
355 display
.update_all(node
)
358 #print "View deleted"
359 self
.model
.root_program
.watchers
.remove(self
)
363 self
.model
.unlock(self
.root
)
365 self
.model
.remove_view(self
)
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
:
375 if attrib
and attrib
.nodeType
!= Node
.ATTRIBUTE_NODE
:
376 raise Exception('attrib not of type ATTRIBUTE_NODE!')
378 if type(nodes
) != types
.ListType
:
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
:
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
:
415 self
.move_to(self
.get_current().nextSibling
)
419 for n
in self
.current_nodes
:
427 def move_right(self
):
429 for n
in self
.current_nodes
:
438 self
.move_to(self
.root
)
441 if not self
.get_current().childNodes
:
443 node
= self
.get_current().childNodes
[0]
444 while node
.nextSibling
:
445 node
= node
.nextSibling
448 def set_display_root(self
, root
):
449 self
.model
.lock(root
)
451 self
.model
.unlock(self
.root
)
453 self
.update_all(root
)
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!')
466 new_model
= self
.model
.lock_and_copy(node
)
467 self
.chroots
.append((self
.model
, node
, self
.marked
))
468 self
.set_model(new_model
)
472 """Undo the effect of the last chroot()."""
480 (old_model
, old_node
, old_marked
) = self
.chroots
.pop()
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
)
488 self
.set_marked(old_marked
.keys())
491 model
.undo_stack
= None
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])
507 #print "DO:", action[0]
510 new
= apply(fn
, action
[1:])
514 if not self
.op_in_progress
:
519 if not self
.op_in_progress
:
521 traceback
.print_exc()
525 if self
.op_in_progress
:
526 op
= self
.op_in_progress
528 self
.set_exec((op
, exit
))
532 def breakpoint(self
):
533 if self
.breakpoints
.has_key(self
.exec_point
):
535 op
= self
.exec_point
[0]
536 if op
.parent
.start
== op
and op
.next
== None:
537 return 1 # Empty program
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.")
549 if not self
.exec_point
:
550 alert("No current playback point.")
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())
560 l
.show_prog(op
.get_program())
563 next
= getattr(op
, exit
)
567 self
.do_action(next
.action
) # May raise InProgress
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
):
578 if not op
.parent
.is_toplevel():
579 self
.set_exec((op
.parent
, exit
))
582 print "(skipped a whole program!)"
583 if self
.callback_on_return
:
584 cb
= self
.callback_on_return
585 self
.callback_on_return
= None
590 def set_oip(self
, op
):
591 #print "set_oip:", self.exec_point
594 self
.op_in_progress
= op
598 def fast_global(self
, name
):
599 "Search for nodes with this name anywhere under the root (//name)"
600 #print "Fast global", name
602 (prefix
, localName
) = string
.split(name
, ':', 1)
604 (prefix
, localName
) = (None, name
)
605 if self
.current_nodes
:
606 src
= self
.current_nodes
[-1]
609 namespaceURI
= self
.model
.prefix_to_namespace(src
, prefix
)
612 if node
.nodeType
!= Node
.ELEMENT_NODE
:
614 if node
.localName
== localName
and node
.namespaceURI
== namespaceURI
:
616 map(add
, node
.childNodes
)
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:])
630 assert not self
.op_in_progress
or (self
.op_in_progress
.action
[1] == pattern
)
632 code
= self
.op_in_progress
.cached_code
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
641 ns
= GetAllNs(self
.current_nodes
[0])
642 ns
['ext'] = FT_EXT_NAMESPACE
644 c
= Context
.Context(self
.get_current(), processorNss
= ns
)
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
657 def select_children(self
):
659 for n
in self
.current_nodes
:
660 new
.extend(n
.childNodes
)
663 def select_region(self
, path
, ns
= None):
664 if len(self
.current_nodes
) == 0:
666 src
= self
.current_nodes
[-1]
669 ns
['ext'] = FT_EXT_NAMESPACE
670 c
= Context
.Context(src
, [src
], processorNss
= ns
)
671 rt
= XPath
.Evaluate(path
, context
= c
)
674 if not self
.has_ancestor(x
, self
.root
):
675 print "[ skipping search result above root ]"
680 print "*** Search for '%s' in select_region failed" % path
681 print " (namespaces were '%s')" % ns
683 if node
.parentNode
!= src
.parentNode
:
684 print "Nodes must have same parent!"
688 for n
in src
.parentNode
.childNodes
:
690 if n
is src
or n
is node
:
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:
700 node
= self
.get_current()
701 if node
.nodeType
== Node
.TEXT_NODE
:
704 if self
.current_attrib
:
705 current
= self
.current_attrib
.value
707 current
= node
.nodeName
708 pattern
= pattern
.replace('@CURRENT@', current
)
709 #print "Searching for", pattern
712 def do_search(self
, pattern
, ns
= None, toggle
= FALSE
):
713 if len(self
.current_nodes
) == 0:
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)
721 code
= self
.op_in_progress
.cached_code
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
730 ns
['ext'] = FT_EXT_NAMESPACE
731 c
= Context
.Context(src
, [src
], processorNss
= ns
)
733 rt
= code
.evaluate(c
)
736 if not self
.has_ancestor(x
, self
.root
):
737 print "[ skipping search result above root ]"
741 #if self.node_to_line[x] > self.current_line:
745 #print "*** Search for '%s' failed" % pattern
746 #print " (namespaces were '%s')" % ns
749 new
= self
.current_nodes
[:]
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
768 new
, num
= re
.subn(replace
, with
, a
.value
)
771 a
= self
.model
.set_attrib(nodes
[0], a
.name
, new
)
772 self
.move_to(nodes
[0], a
)
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
:
783 self
.model
.set_data(n
, new
)
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
:
791 new_ns
, x
= self
.model
.split_qname(n
, new
)
792 final
.append(self
.model
.set_name(n
, new_ns
, new
))
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:
804 src
= self
.current_nodes
[-1]
807 code
= self
.op_in_progress
.cached_code
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
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()
831 self
.model
.replace_node(node
, new
)
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
841 self
.set_exec((op
, exit
))
842 if not self
.single_step
:
844 self
.status_changed()
846 print "(nothing to resume)"
849 def ask_cb(result
, self
= self
):
853 self
.clipboard
= self
.model
.doc
.createTextNode(result
)
856 from GetArg
import GetArg
857 box
= GetArg('Input:', ask_cb
, [q
], destroy_return
= 1)
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
)
866 list.appendChild(self
.python_to_node(x
))
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
))
877 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
878 for n
in self
.current_nodes
:
879 c
= n
.cloneNode(deep
)
881 self
.clipboard
.appendChild(c
)
883 #print "Clip now", self.clipboard
885 def shallow_yank(self
):
888 def delete_shallow(self
):
889 nodes
= self
.current_nodes
[:]
892 if self
.root
in nodes
:
897 self
.model
.delete_shallow(n
)
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
[:]
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)
914 if self
.root
in nodes
:
917 new
= [x
.parentNode
for x
in nodes
]
919 self
.model
.delete_nodes(nodes
)
922 nodes
= self
.current_nodes
[:]
924 self
.model
.unlock(self
.root
)
928 self
.model
.lock(self
.root
)
929 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
932 nodes
= self
.current_nodes
[:]
934 self
.model
.unlock(self
.root
)
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
948 self
.set_exec((op
, exit
))
950 print "No operation to return to!"
951 c
= self
.call_on_done
953 self
.call_on_done
= None
956 self
.jump_to_innermost_failure()
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'))
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
977 done
= self
.default_done
979 def cbor(self
= self
, op
= self
.op_in_progress
, done
= done
,
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
993 #print "Resume op '%s' (%s)" % (op.program.name, op)
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
)
1006 self
.play_block(prog
.code
)
1008 self
.status_changed()
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
:
1017 stack_block
, nodes_list
, restore
, old_mark
= self
.foreach_stack
[-1]
1018 if stack_block
!= block
:
1019 self
.reset_foreach_stack()
1021 raise Exception("Reached the end of a block we never entered")
1027 restore
.extend(self
.current_nodes
)
1028 if continuing
== 'fail':
1029 print "Error in block; exiting early in program", block
.get_program()
1031 [self
.model
.unlock(x
) for x
in old_mark
]
1032 self
.foreach_stack
.pop()
1035 while nodes_list
and nodes_list
[0].parentNode
== None:
1036 print "Skipping deleted node", nodes_list
[0]
1040 self
.foreach_stack
.pop()
1043 nodes
= filter(lambda x
: self
.has_ancestor(x
, self
.root
), restore
)
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]
1054 print "[ %d after this ]" % len(nodes_list
),
1059 self
.set_exec((block
.start
, 'next'))
1062 def play_block(self
, block
):
1063 assert isinstance(block
, Block
)
1064 #print "Enter Block!"
1066 list = self
.current_nodes
[:]
1068 list = [self
.current_nodes
[:]] # List of one item, containing everything
1071 marks
= self
.marked
.copy()
1072 [self
.model
.lock(x
) for x
in marks
]
1075 self
.foreach_stack
.append((block
, list, [], marks
))
1078 if not self
.start_block_iteration(block
):
1079 # No nodes selected...
1080 if not block
.is_toplevel():
1081 self
.set_exec((block
, 'next'))
1084 self
.set_exec((block
.start
, 'next'))
1088 assert self
.op_in_progress
1089 oip
= self
.op_in_progress
1091 self
.play_block(oip
)
1092 if not self
.single_step
:
1097 if self
.op_in_progress
:
1098 raise Exception("Operation in progress")
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
)
1107 self
.in_callback
= 1
1111 self
.in_callback
= 0
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())
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
1132 self
.set_exec((node
, 'fail'))
1133 self
.status_changed()
1135 if self
.op_in_progress
or self
.single_step
:
1136 self
.status_changed()
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
):
1149 nodes
= self
.current_nodes
[:]
1151 print "map of nothing: skipping..."
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."
1157 print "[ %d to go ]" % len(nodes
),
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]
1166 return self
.default_done(exit
)
1167 self
.move_to(nodes
[0])
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!")
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
)
1185 prog
= prog
.subprograms
[comps
[0]]
1189 def change_node(self
, new_data
):
1190 nodes
= self
.current_nodes
1194 if nodes
[0].nodeType
== Node
.ELEMENT_NODE
:
1195 # Slow, so do this here, even if vaguely incorrect...
1196 assert ' ' not in new_data
1198 (prefix
, localName
) = string
.split(new_data
, ':', 1)
1200 (prefix
, localName
) = (None, new_data
)
1201 namespaceURI
= self
.model
.prefix_to_namespace(nodes
[0], prefix
)
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
)
1210 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1215 self
.model
.set_data(node
, new_data
)
1218 def add_node(self
, where
, data
):
1219 cur
= self
.get_current()
1222 (prefix
, localName
) = string
.split(data
, ':', 1)
1224 (prefix
, localName
) = (None, data
)
1225 namespaceURI
= self
.model
.prefix_to_namespace(self
.get_current(), prefix
)
1226 new
= self
.model
.doc
.createElementNS(namespaceURI
, data
)
1228 new
= self
.model
.doc
.createTextNode(data
)
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
)
1238 self
.model
.insert(cur
, 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."""
1248 if node
.nodeType
== Node
.TEXT_NODE
:
1249 uri
= node
.nodeValue
1253 elif node
.hasAttributeNS(None, 'uri'):
1254 uri
= node
.getAttributeNS(None, 'uri')
1256 for attr
in node
.attributes
.keys():
1257 a_node
= node
.attributes
[attr
]
1258 if a_node
.namespaceURI
== XMLNS_NAMESPACE
:
1261 if uri
.find('//') != -1 or uri
.find('.htm') != -1:
1264 print "Can't suck", node
, "(no uri attribute found)"
1266 if uri
.find('//') == -1:
1267 base
= self
.model
.get_base_uri(node
)
1268 print "Relative URI..."
1270 print "Base URI is:", base
, "add", uri
1271 if uri
.startswith('/'):
1272 uri
= urlparse
.urljoin(base
, uri
)
1274 uri
= base
+ '/' + uri
1275 print "Final URI is:", uri
1278 #print "Warning: Can't find 'uri' attribute!"
1279 request
= urllib2
.Request(uri
)
1283 def http_post(self
):
1284 node
= self
.get_current()
1285 attrs
= node
.attributes
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
)
1294 post
.append((str(name
), value
))
1296 request
.add_data(urllib
.urlencode(post
))
1297 node
= self
.suck_node(node
, request
)
1301 def suck(self
, md5_only
= 0):
1302 nodes
= self
.current_nodes
[:]
1303 attrib
= self
.current_attrib
1307 request
= self
.request_from_node(x
, attrib
)
1309 new
= self
.suck_node(x
, request
, md5_only
= md5_only
)
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
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...)
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
1338 if x
.lower().startswith('last-modified:'):
1339 last_mod
= x
[14:].strip()
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"
1349 print "Fetching page contents..."
1350 data
= stream
.read()
1351 print "got data... tidying..."
1353 if data
.startswith('<?xml'):
1356 data
= to_html(data
)
1357 #print "Converted to", data
1359 old_md5
= node
.getAttributeNS(None, 'md5_sum')
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!"
1370 # This is a nasty hack left in for backwards compat.
1371 self
.model
.set_attrib(node
, 'md5_sum', new_md5
)
1376 root
= self
.parse_data(data
, uri
)
1378 new
= node
.ownerDocument
.importNode(root
.documentElement
, 1)
1379 new
.setAttributeNS(None, 'uri', uri
)
1382 new
.setAttributeNS(None, 'last-modified', last_mod
)
1383 new
.setAttributeNS(None, 'modified', 'yes')
1384 new
.setAttributeNS(None, 'md5_sum', new_md5
)
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
)
1394 self
.model
.replace_node(node
, new
)
1395 self
.model
.strip_space(new
)
1400 def put_before(self
):
1401 node
= self
.get_current()
1402 if self
.clipboard
== None:
1404 new
= self
.clipboard
.cloneNode(1)
1406 self
.model
.insert_before(node
, new
)
1410 def put_after(self
):
1411 node
= self
.get_current()
1412 if self
.clipboard
== None:
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!"
1422 if self
.current_attrib
:
1423 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1424 value
= self
.clipboard
.childNodes
[0].data
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
)
1432 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1433 if len(self
.clipboard
.childNodes
) != 1:
1434 print "Multiple nodes in clipboard!"
1436 new
= self
.clipboard
.childNodes
[0].cloneNode(1)
1438 new
= self
.clipboard
.cloneNode(1)
1439 if new
.nodeType
!= Node
.ELEMENT_NODE
:
1443 if node
== self
.root
:
1444 self
.model
.unlock(self
.root
)
1446 self
.model
.replace_node(self
.root
, new
)
1449 self
.model
.lock(self
.root
)
1451 self
.model
.replace_node(node
, new
)
1454 type, val
, tb
= sys
.exc_info()
1455 traceback
.print_exception(type, val
, tb
)
1456 print "Replace failed!"
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:
1466 new
= self
.clipboard
.cloneNode(1)
1467 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1469 for n
in new
.childNodes
:
1475 self
.model
.insert_before(None, new
, parent
= node
)
1477 self
.model
.insert(node
, new
, index
= 0)
1483 def yank_value(self
):
1484 if not self
.current_attrib
:
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):
1492 print "yank_attribs: DEPRECATED -- use Yank instead!"
1493 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
1495 if not self
.get_current().hasAttributeNS(None, name
):
1497 attribs
= [self
.get_current().getAttributeNodeNS(None, name
)]
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).
1507 diff
= cmp(a
.name
, b
.name
)
1509 diff
= cmp(a
.namespaceURI
, b
.namespaceURI
)
1512 attribs
.sort(by_name
)
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
1523 attribs
= [self
.clipboard
]
1527 new
.append((a
.nodeName
, a
.childNodes
[0].data
))
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
)
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)
1545 raise Beep(may_record
= 1)
1550 def fail_if(self
, xpath
):
1551 """Evaluate xpath as a boolean, and fail if true."""
1552 src
= self
.get_current()
1554 ns
['ext'] = FT_EXT_NAMESPACE
1555 c
= Context
.Context(src
.parentNode
, [src
.parentNode
], processorNss
= ns
)
1557 rt
= XPath
.Evaluate(xpath
, context
= c
)
1560 raise Beep(may_record
= 1)
1562 def attribute(self
, namespace
= None, attrib
= ''):
1563 node
= self
.get_current()
1569 if attrib
== 'xmlns':
1571 #print "(ns, attrib)", `namespace`, attrib
1573 a
= node
.attributes
.get((namespace
, attrib
), None)
1576 self
.move_to(node
, a
)
1578 print "No such attribute"
1579 print "Looking for %s in %s" % ((namespace
, attrib
), node
.attributes
)
1582 def set_attrib(self
, value
):
1583 a
= self
.current_attrib
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
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()
1613 print "Parsing (with entities)..."
1614 doc
= nonvalParse(isrc
.fromString(data
, path
))
1616 print "Parse failed.. retry without entities..."
1617 data
= entrefpattern
.sub('&\\1;',data
)
1618 doc
= nonvalParse(isrc
.fromString(data
, path
))
1620 type, val
, tb
= sys
.exc_info()
1621 traceback
.print_exception(type, val
, tb
)
1622 print "parsing failed!"
1625 #rox.report_exception()
1628 print "parse OK...",
1631 def set_root_from_doc(self
, doc
):
1632 new
= self
.root
.ownerDocument
.importNode(doc
.documentElement
, 1)
1635 self
.model
.unlock(self
.root
)
1637 self
.model
.replace_node(self
.root
, new
)
1638 self
.model
.lock(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
)
1664 self
.model
.unlock(self
.root
)
1666 self
.model
.replace_node(self
.root
, new
)
1667 self
.model
.lock(new
)
1669 self
.move_to(self
.root
)
1671 def select_dups(self
):
1672 node
= self
.get_current()
1674 for n
in node
.parentNode
.childNodes
:
1679 self
.move_to(select
)
1681 def select_marked_region(self
, attr
= "unused"):
1683 if len(self
.marked
) != 1:
1684 print "Must be exactly one marked node!"
1686 if len(self
.current_nodes
) != 1:
1687 print "Must be exactly one selected node!"
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]:
1702 for x
in a
.parentNode
.childNodes
:
1709 self
.move_to(select
)
1711 print "One node is a parent of the other!"
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
[:]
1726 if node
.nodeType
!= Node
.ELEMENT_NODE
:
1728 if node
.hasAttributeNS(None, 'hidden'):
1732 self
.model
.set_attrib(node
, 'hidden', new
, with_update
= 0)
1733 self
.model
.update_all(self
.root
)
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)")
1743 if env
.localName
!= 'Envelope':
1744 alert("Not a SOAP-ENV:Envelope (bad local name)")
1747 if len(env
.childNodes
) != 2:
1748 alert("SOAP-ENV:Envelope must have one header and one body")
1751 kids
= elements(env
)
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")
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")
1766 for header
in elements(head
):
1767 if header
.namespaceURI
== DOME_NS
and header
.localName
== 'soap-forward-to':
1770 print header
.namespaceURI
1771 print header
.localName
1774 alert("Head must contain a dome:soap-forward-to element")
1777 dest
= sft
.childNodes
[0].data
1778 parent
= sft
.parentNode
1779 if len(elements(parent
)) == 1:
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!")
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', '')
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
)
1811 new
= self
.model
.doc
.importNode(new_doc
.documentElement
, 1)
1813 self
.model
.strip_space(new
)
1815 old
= self
.get_current()
1817 self
.model
.replace_node(old
, new
)
1820 def program_changed(self
, changed_op
):
1821 print "Check points..."
1823 (op
, exit
) = self
.rec_point
1825 print "Lost rec_point"
1826 self
.rec_point
= None
1828 (op
, exit
) = self
.exec_point
1830 print "Lost exec_point"
1831 self
.exec_point
= None
1832 for l
in self
.lists
:
1834 self
.status_changed()
1836 def prog_tree_changed(self
):
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
)
1847 print "*** WARNING: Saving from a chroot!"
1849 data
= doc
.importNode(model
.doc
.documentElement
, 1)
1850 node
.appendChild(data
)
1854 def blank_all(self
):
1855 doc
= implementation
.createDocument(None, 'root', None)
1857 self
.clipboard
= self
.model
.doc
.createElementNS(None, 'root')
1860 def mark_switch(self
):
1861 new
= self
.marked
.keys()
1862 self
.set_marked(self
.current_nodes
)
1865 def set_marked(self
, new
):
1866 update
= self
.marked
1867 for x
in self
.marked
.keys():
1868 self
.model
.unlock(x
)
1872 self
.marked
[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
):
1884 def normalise(self
):
1885 self
.model
.normalise(self
.get_current())
1887 def remove_ns(self
):
1888 nodes
= self
.current_nodes
[:]
1890 nodes
= map(self
.model
.remove_ns
, nodes
)
1893 def convert_to_text(self
):
1894 nodes
= self
.current_nodes
[:]
1896 nodes
= map(self
.model
.convert_to_text
, nodes
)
1902 def write(self
, str):