1 from __future__
import nested_scopes
5 from rox
import support
6 from xml
.dom
import Node
, ext
, XMLNS_NAMESPACE
7 from Ft
.Xml
import XPath
8 from Ft
.Xml
.XPath
import FT_EXT_NAMESPACE
, Context
9 from xml
.dom
.ext
.reader
import PyExpat
10 from Ft
.Xml
.cDomlette
import implementation
12 import os
, re
, string
, types
15 from StringIO
import StringIO
17 from Program
import Op
24 from constants
import *
28 for x
in node
.childNodes
:
29 if x
.nodeType
== Node
.ELEMENT_NODE
:
33 normal_chars
= string
.letters
+ string
.digits
+ "-"
35 fast_global
= re
.compile('//([-A-Za-z][-A-Za-z0-9]*:)?[-A-Za-z][-A-Za-z0-9]*$')
38 # - A ref to a DOM document
39 # - A set of current nodes
42 # It does not have any display code. It does contain code to perform actions
43 # (actions affect the document AND the view state).
45 # These actions can be repeated using '.'
54 "delete_node_no_clipboard",
82 "Recursivly compare two nodes."
83 if a
.nodeType
!= b
.nodeType
or a
.nodeName
!= b
.nodeName
:
85 if a
.nodeValue
!= b
.nodeValue
:
89 if len(aks
) != len(bks
):
91 for (ak
, bk
) in map(None, aks
, bks
):
96 class InProgress(Exception):
97 "Throw this if the operation will complete later..."
98 class Done(Exception):
99 "Thrown when the chain is completed successfully"
102 def __init__(self
, model
, callback_handlers
= None):
103 """callback_handlers is an (idle_add, idle_remove) tuple"""
107 self
.single_step
= 1 # 0 = Play 1 = Step-into 2 = Step-over
110 self
.current_nodes
= []
111 self
.clipboard
= None
112 self
.current_attrib
= None
114 if not callback_handlers
:
116 self
.idle_add
, self
.idle_remove
= gtk
.idle_add
, gtk
.idle_remove
118 self
.idle_add
, self
.idle_remove
= callback_handlers
120 self
.exec_point
= None # None, or (Op, Exit)
121 self
.rec_point
= None # None, or (Op, Exit)
122 self
.op_in_progress
= None
124 self
.callback_on_return
= None # Called when there are no more Ops...
125 self
.in_callback
= 0 # (not the above callback - this is the playback one)
126 self
.innermost_failure
= None
127 self
.call_on_done
= None # Called when there is nowhere to return to
128 self
.exec_stack
= [] # Ops we are inside (display use only)
130 self
.breakpoints
= {} # (op, exit) keys, values don't matter
131 self
.current_nodes
= []
132 self
.set_model(model
)
137 def get_current(self
):
138 if len(self
.current_nodes
) == 1:
139 return self
.current_nodes
[0]
140 raise Exception('This operation required exactly one selected node!')
142 def set_model(self
, model
):
145 self
.model
.unlock(self
.root
)
148 self
.model
.remove_view(self
)
149 self
.model
.root_program
.watchers
.remove(self
)
151 self
.model
.root_program
.watchers
.append(self
)
153 self
.set_display_root(self
.model
.get_root())
154 self
.move_to(self
.root
)
157 return self
.idle_cb
!= 0 or self
.in_callback
159 def run_new(self
, callback
= None):
160 "Reset the playback system (stack, step-mode and point)."
161 "Call callback(exit) when execution finishes."
163 self
.idle_remove(self
.idle_cb
)
166 self
.innermost_failure
= None
167 self
.call_on_done
= callback
168 self
.callback_on_return
= None
169 while self
.exec_stack
:
171 self
.status_changed()
173 def push_stack(self
, op
):
174 if not isinstance(op
, Op
):
175 raise Exception('push_stack: not an Op', op
)
176 self
.exec_stack
.append(op
)
181 op
= self
.exec_stack
.pop()
185 def set_exec(self
, pos
):
186 if self
.op_in_progress
:
187 raise Exception("Operation in progress...")
188 if pos
and not isinstance(pos
[0], Op
):
189 raise Exception("Not an (operation, exit) tuple: " + `pos`
)
190 self
.exec_point
= pos
192 #print "set_exec: %s:%s" % pos
196 def set_rec(self
, pos
):
200 self
.status_changed()
202 def record_at_point(self
):
203 if not self
.exec_point
:
204 support
.report_error("No current point!")
206 self
.set_rec(self
.exec_point
)
209 def stop_recording(self
):
211 self
.set_exec(self
.rec_point
)
214 support
.report_error("Not recording!")
216 def may_record(self
, action
):
217 "Perform and, possibly, record this action"
221 print "RECORD:", rec
, action
224 op
.link_to(new_op
, old_exit
)
229 self
.set_rec((new_op
, 'next'))
231 play_op
, exit
= self
.exec_point
232 # (do_one_step may have stopped recording)
234 self
.set_rec((new_op
, exit
))
240 self
.do_action(action
)
246 (type, val
, tb
) = sys
.exc_info()
247 #if not val.may_record:
251 support
.report_exception()
254 def add_display(self
, display
):
255 "Calls move_from(old_node) when we move and update_all() on updates."
256 self
.displays
.append(display
)
257 #print "Added:", self.displays
259 def remove_display(self
, display
):
260 self
.displays
.remove(display
)
261 #print "Removed, now:", self.displays
262 if not self
.displays
:
265 def update_replace(self
, old
, new
):
268 if old
in self
.current_nodes
:
270 self
.model
.unlock(old
)
271 self
.current_nodes
.remove(old
)
272 self
.current_nodes
.append(new
)
273 self
.update_all(new
.parentNode
)
275 self
.update_all(new
.parentNode
)
277 def has_ancestor(self
, node
, ancestor
):
278 while node
!= ancestor
:
279 node
= node
.parentNode
284 def update_all(self
, node
):
286 # Is the root node still around?
287 if not self.has_ancestor(self.root, self.model.get_root()):
288 # No - reset everything
289 print "[ lost root - using doc root ]"
290 self.root = self.model.doc.documentElement
292 raise Exception('Root locking error!')
294 # Is the current node still around?
295 for n in self.current_nodes[:]:
296 if not self.has_ancestor(n, self.root):
297 # No - locking error!
298 self.current_nodes.remove(n)
299 raise Exception('Internal locking error on %s!' % n)
301 if not (self.has_ancestor(node, self.root) or self.has_ancestor(self.root, node)):
302 #print "[ change to %s doesn't affect us (root %s) ]" % (node, self.root)
306 for display
in self
.displays
:
307 display
.update_all(node
)
310 #print "View deleted"
311 self
.model
.root_program
.watchers
.remove(self
)
315 self
.model
.unlock(self
.root
)
317 self
.model
.remove_view(self
)
320 # 'nodes' may be either a node or a list of nodes.
321 # If it's a single node, then an 'attrib' node may also be specified
322 def move_to(self
, nodes
, attrib
= None):
323 if self
.current_nodes
== nodes
:
326 if attrib
and attrib
.nodeType
!= Node
.ATTRIBUTE_NODE
:
327 raise Exception('attrib not of type ATTRIBUTE_NODE!')
329 if type(nodes
) != types
.ListType
:
332 old_nodes
= self
.current_nodes
333 self
.current_nodes
= nodes
335 for node
in self
.current_nodes
:
336 self
.model
.lock(node
)
337 for node
in old_nodes
:
338 self
.model
.unlock(node
)
340 self
.current_attrib
= attrib
342 for display
in self
.displays
:
343 display
.move_from(old_nodes
)
345 def move_prev_sib(self
):
346 if self
.get_current() == self
.root
or not self
.get_current().previousSibling
:
348 self
.move_to(self
.get_current().previousSibling
)
350 def move_next_sib(self
):
351 if self
.get_current() == self
.root
or not self
.get_current().nextSibling
:
353 self
.move_to(self
.get_current().nextSibling
)
357 for n
in self
.current_nodes
:
365 def move_right(self
):
367 for n
in self
.current_nodes
:
376 self
.move_to(self
.root
)
379 if not self
.get_current().childNodes
:
381 node
= self
.get_current().childNodes
[0]
382 while node
.nextSibling
:
383 node
= node
.nextSibling
386 def set_display_root(self
, root
):
387 self
.model
.lock(root
)
389 self
.model
.unlock(self
.root
)
391 self
.update_all(root
)
394 """Change the display root to a COPY of the selected node.
395 Call Leave to check changes back in."""
396 node
= self
.get_current()
397 if node
is self
.root
:
398 raise Beep
# Locking problems if this happens...
399 if self
.model
.doc
is not node
.ownerDocument
:
400 raise Exception('Current node not in view!')
401 self
.chroots
.append((self
.model
, node
))
402 self
.set_model(self
.model
.lock_and_copy(node
))
405 """Undo the effect of the last chroot()."""
409 (old_model
, old_node
) = self
.chroots
.pop()
411 copy
= old_model
.doc
.importNode(self
.model
.get_root(), 1)
412 self
.set_model(old_model
)
413 old_model
.unlock(old_node
)
414 old_model
.replace_node(old_node
, copy
)
417 def do_action(self
, action
):
418 "'action' is a tuple (function, arg1, arg2, ...)"
419 "Performs the action. Returns if action completes, or raises "
420 "InProgress if not (will call resume() later)."
421 if action
[0] in record_again
:
422 self
.last_action
= action
423 elif action
[0] == 'again':
424 action
= self
.last_action
425 fn
= getattr(self
, action
[0])
427 #print "DO:", action[0]
430 new
= apply(fn
, action
[1:])
434 if not self
.op_in_progress
:
439 if not self
.op_in_progress
:
441 traceback
.print_exc()
445 if self
.op_in_progress
:
446 op
= self
.op_in_progress
448 self
.set_exec((op
, exit
))
452 def breakpoint(self
):
453 if self
.breakpoints
.has_key(self
.exec_point
):
455 op
= self
.exec_point
[0]
456 if op
.program
.start
== op
and op
.next
== None:
457 return 1 # Empty program
460 def do_one_step(self
):
461 "Execute the next op after exec_point, then:"
462 "- position the point on one of the exits return."
463 "- if there is no op to perform, call callback_on_return() or raise Done."
464 "- if the operation is started but not complete, raise InProgress and "
465 " arrange to resume() later."
466 if self
.op_in_progress
:
467 support
.report_error("Already executing something.")
469 if not self
.exec_point
:
470 support
.report_error("No current playback point.")
472 (op
, exit
) = self
.exec_point
474 if self
.single_step
== 0 and self
.breakpoint():
475 print "Hit a breakpoint! At " + time
.ctime(time
.time())
480 l
.show_prog(op
.program
)
483 next
= getattr(op
, exit
)
486 self
.do_action(next
.action
) # May raise InProgress
489 if exit
== 'fail' and not self
.innermost_failure
:
490 #print "Setting innermost_failure on", op
491 self
.innermost_failure
= op
493 if self
.callback_on_return
:
494 cb
= self
.callback_on_return
495 self
.callback_on_return
= None
500 def set_oip(self
, op
):
503 self
.op_in_progress
= op
507 def fast_global(self
, name
):
508 "Search for nodes with this name anywhere under the root (//name)"
509 #print "Fast global", name
511 (prefix
, localName
) = string
.split(name
, ':', 1)
513 (prefix
, localName
) = (None, name
)
514 if self
.current_nodes
:
515 src
= self
.current_nodes
[-1]
518 namespaceURI
= self
.model
.prefix_to_namespace(src
, prefix
)
521 if node
.nodeType
!= Node
.ELEMENT_NODE
:
523 if node
.localName
== localName
and node
.namespaceURI
== namespaceURI
:
525 map(add
, node
.childNodes
)
531 def do_global(self
, pattern
):
532 if len(self
.current_nodes
) != 1:
533 self
.move_to(self
.root
)
534 if pattern
[:2] == '//':
535 if fast_global
.match(pattern
):
536 self
.fast_global(pattern
[2:])
540 ns
= ext
.GetAllNs(self
.current_nodes
[0])
541 ns
['ext'] = FT_EXT_NAMESPACE
544 c
= Context
.Context(self
.get_current(), processorNss
= ns
)
545 from Ft
.Xml
.XPath
import XPathParser
546 code
= XPathParser
.new().parse(self
.macro_pattern(pattern
))
548 nodes
= code
.evaluate(c
)
549 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
550 #print "Found", nodes
553 def select_region(self
, path
, ns
= None):
554 if len(self
.current_nodes
) == 0:
556 src
= self
.current_nodes
[-1]
558 ns
= ext
.GetAllNs(src
)
559 ns
['ext'] = FT_EXT_NAMESPACE
560 c
= Context
.Context(src
, [src
], processorNss
= ns
)
561 rt
= XPath
.Evaluate(path
, context
= c
)
564 if not self
.has_ancestor(x
, self
.root
):
565 print "[ skipping search result above root ]"
570 print "*** Search for '%s' in select_region failed" % path
571 print " (namespaces were '%s')" % ns
573 if node
.parentNode
!= src
.parentNode
:
574 print "Nodes must have same parent!"
578 for n
in src
.parentNode
.childNodes
:
580 if n
is src
or n
is node
:
584 self
.move_to(selected
)
586 def macro_pattern(self
, pattern
):
587 """Do the @CURRENT@ substitution for an XPath"""
588 if len(self
.current_nodes
) != 1:
590 node
= self
.get_current()
591 if node
.nodeType
== Node
.TEXT_NODE
:
594 current
= node
.nodeName
595 pattern
= pattern
.replace('@CURRENT@', current
)
596 #print "Searching for", pattern
599 def do_search(self
, pattern
, ns
= None, toggle
= FALSE
):
600 if len(self
.current_nodes
) == 0:
603 src
= self
.current_nodes
[-1]
605 ns
= ext
.GetAllNs(src
)
606 ns
['ext'] = FT_EXT_NAMESPACE
607 c
= Context
.Context(src
, [src
], processorNss
= ns
)
609 rt
= XPath
.Evaluate(self
.macro_pattern(pattern
), context
= c
)
612 if not self
.has_ancestor(x
, self
.root
):
613 print "[ skipping search result above root ]"
617 #if self.node_to_line[x] > self.current_line:
621 #print "*** Search for '%s' failed" % pattern
622 #print " (namespaces were '%s')" % ns
625 new
= self
.current_nodes
[:]
634 def do_text_search(self
, pattern
):
635 pattern
= self
.macro_pattern(pattern
)
636 return self
.do_search("//text()[ext:match('%s')]" % pattern
)
638 def subst(self
, replace
, with
):
639 "re search and replace on the current node"
640 nodes
= self
.current_nodes
[:]
641 check
= len(nodes
) == 1
642 a
= self
.current_attrib
644 new
= re
.sub(replace
, with
, a
.value
)
645 a
= self
.model
.set_attrib(nodes
[0], a
.name
, new
)
646 self
.move_to(nodes
[0], a
)
651 if n
.nodeType
== Node
.TEXT_NODE
:
652 old
= str(n
.data
).replace('\n', ' ')
653 new
, num
= re
.subn(replace
, with
, old
)
654 if check
and not num
:
657 self
.model
.set_data(n
, new
)
659 elif n
.nodeType
== Node
.ELEMENT_NODE
:
660 old
= str(n
.nodeName
)
661 new
, num
= re
.subn(replace
, with
, old
)
662 if check
and not num
:
665 new_ns
, x
= self
.model
.split_qname(n
, new
)
666 final
.append(self
.model
.set_name(n
, new_ns
, new
))
672 def python(self
, expr
):
673 "Replace node with result of expr(old_value)"
674 if self
.get_current().nodeType
== Node
.TEXT_NODE
:
675 vars = {'x': self
.get_current().data
, 're': re
, 'sub': re
.sub
, 'string': string
}
676 result
= eval(expr
, vars)
677 new
= self
.python_to_node(result
)
678 node
= self
.get_current()
680 self
.model
.replace_node(node
, new
)
685 def resume(self
, exit
= 'next'):
686 "After raising InProgress, call this to start moving again."
687 if self
.op_in_progress
:
688 op
= self
.op_in_progress
690 self
.set_exec((op
, exit
))
691 if not self
.single_step
:
693 self
.status_changed()
695 print "(nothing to resume)"
698 def ask_cb(result
, self
= self
):
702 self
.clipboard
= self
.model
.doc
.createTextNode(result
)
705 from GetArg
import GetArg
706 box
= GetArg('Input:', ask_cb
, [q
], destroy_return
= 1)
709 def python_to_node(self
, data
):
710 "Convert a python data structure into a tree and return the root."
711 if type(data
) == types
.ListType
:
712 list = self
.model
.doc
.createElementNS(DOME_NS
, 'dome:list')
714 list.appendChild(self
.python_to_node(x
))
716 return self
.model
.doc
.createTextNode(str(data
))
718 def yank(self
, deep
= 1):
719 if self
.current_attrib
:
720 a
= self
.current_attrib
722 self
.clipboard
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
723 self
.clipboard
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
725 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
726 for n
in self
.current_nodes
:
727 c
= n
.cloneNode(deep
)
729 self
.clipboard
.appendChild(c
)
731 #print "Clip now", self.clipboard
733 def shallow_yank(self
):
736 def delete_shallow(self
):
737 nodes
= self
.current_nodes
[:]
740 if self
.root
in nodes
:
745 self
.model
.delete_shallow(n
)
748 def delete_node_no_clipboard(self
):
749 self
.delete_node(yank
= 0)
751 def delete_node(self
, yank
= 1):
752 nodes
= self
.current_nodes
[:]
757 if self
.current_attrib
:
758 ca
= self
.current_attrib
759 self
.current_attrib
= None
760 self
.model
.set_attrib(self
.get_current(), ca
.name
, None)
762 if self
.root
in nodes
:
764 self
.move_to([]) # Makes things go *much* faster!
768 #print "Delete %s, parent %s" % (x, p)
772 self
.model
.delete_nodes(nodes
)
775 nodes
= self
.current_nodes
[:]
777 self
.model
.unlock(self
.root
)
781 self
.model
.lock(self
.root
)
782 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
785 nodes
= self
.current_nodes
[:]
787 self
.model
.unlock(self
.root
)
791 self
.model
.lock(self
.root
)
792 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
794 def default_done(self
, exit
):
795 "Called when execution of a program returns. op_in_progress has been "
796 "restored - move to the exit."
797 #print "default_done(%s)" % exit
798 if self
.op_in_progress
:
799 op
= self
.op_in_progress
801 self
.set_exec((op
, exit
))
803 print "No operation to return to!"
804 c
= self
.call_on_done
806 self
.call_on_done
= None
809 self
.jump_to_innermost_failure()
812 def jump_to_innermost_failure(self
):
813 assert self
.innermost_failure
!= None
815 print "Returning to innermost failure:", self
.innermost_failure
816 self
.set_exec((self
.innermost_failure
, 'fail'))
818 if hasattr(l
, 'set_innermost_failure'):
819 l
.set_innermost_failure(self
.innermost_failure
)
821 def play(self
, name
, done
= None):
822 "Play this macro. When it returns, restore the current op_in_progress (if any)"
823 "and call done(exit). Default for done() moves exec_point."
824 "done() is called from do_one_step() - usual rules apply."
826 prog
= self
.name_to_prog(name
)
827 self
.innermost_failure
= None
830 done
= self
.default_done
832 def cbor(self
= self
, op
= self
.op_in_progress
, done
= done
,
834 old_cbor
= self
.callback_on_return
,
835 old_ss
= self
.single_step
):
836 "We're in do_one_step..."
838 #print "Return from '%s'..." % name
840 if old_ss
== 2 and self
.single_step
== 0:
841 self
.single_step
= old_ss
842 self
.callback_on_return
= old_cbor
844 o
, exit
= self
.exec_point
846 #print "Resume op '%s' (%s)" % (op.program.name, op)
851 self
.callback_on_return
= cbor
853 if self
.single_step
== 2:
856 if self
.op_in_progress
:
857 self
.push_stack(self
.op_in_progress
)
859 self
.set_exec((prog
.start
, 'next'))
861 self
.status_changed()
865 if self
.op_in_progress
:
866 raise Exception("Operation in progress")
868 raise Exception("Already playing!")
869 self
.idle_cb
= self
.idle_add(self
.play_callback
)
871 def play_callback(self
):
872 self
.idle_remove(self
.idle_cb
)
881 (op
, exit
) = self
.exec_point
882 if exit
== 'fail' and self
.innermost_failure
:
883 self
.jump_to_innermost_failure()
884 print "Done, at " + time
.ctime(time
.time())
891 type, val
, tb
= sys
.exc_info()
892 list = traceback
.extract_tb(tb
)
893 stack
= traceback
.format_list(list[-2:])
894 ex
= traceback
.format_exception_only(type, val
) + ['\n\n'] + stack
895 traceback
.print_exception(type, val
, tb
)
896 print "Error in do_one_step(): stopping playback"
897 node
= self
.op_in_progress
899 self
.set_exec((node
, 'fail'))
900 self
.status_changed()
902 if self
.op_in_progress
or self
.single_step
:
903 self
.status_changed()
908 def status_changed(self
):
909 for display
in self
.displays
:
910 if hasattr(display
, 'update_state'):
911 display
.update_state()
916 nodes
= self
.current_nodes
[:]
918 print "map of nothing: skipping..."
920 inp
= [nodes
, None] # Nodes, next
921 def next(exit
= exit
, self
= self
, name
= name
, inp
= inp
):
922 "This is called while in do_one_step() - normal rules apply."
924 print "[ %d to go ]" % len(nodes
),
926 print "Map: nodes remaining, but an error occurred..."
927 return self
.default_done(exit
)
928 while nodes
and nodes
[0].parentNode
== None:
929 print "Skipping deleted node", nodes
[0]
932 return self
.default_done(exit
)
933 self
.move_to(nodes
[0])
937 #print "Map: calling play (%d after this)" % len(nodes)
938 self
.play(name
, done
= next
) # Should raise InProgress
939 if nodes
is self
.current_nodes
:
940 raise Exception("Slice failed!")
944 def name_to_prog(self
, name
):
945 comps
= string
.split(name
, '/')
946 prog
= self
.model
.root_program
947 if prog
.name
!= comps
[0]:
948 raise Exception("No such program as '%s'!" % name
)
951 prog
= prog
.subprograms
[comps
[0]]
955 def change_node(self
, new_data
):
956 nodes
= self
.current_nodes
960 if nodes
[0].nodeType
== Node
.ELEMENT_NODE
:
961 # Slow, so do this here, even if vaguely incorrect...
963 (prefix
, localName
) = string
.split(new_data
, ':', 1)
965 (prefix
, localName
) = (None, new_data
)
966 namespaceURI
= self
.model
.prefix_to_namespace(nodes
[0], prefix
)
969 if node
is self
.root
:
970 self
.model
.unlock(self
.root
)
971 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
975 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
980 self
.model
.set_data(node
, new_data
)
983 def add_node(self
, where
, data
):
984 cur
= self
.get_current()
987 (prefix
, localName
) = string
.split(data
, ':', 1)
989 (prefix
, localName
) = (None, data
)
990 namespaceURI
= self
.model
.prefix_to_namespace(self
.get_current(), prefix
)
991 new
= self
.model
.doc
.createElementNS(namespaceURI
, data
)
993 new
= self
.model
.doc
.createTextNode(data
)
997 self
.model
.insert_before(cur
, new
)
998 elif where
[0] == 'a':
999 self
.model
.insert_after(cur
, new
)
1001 self
.model
.insert(cur
, new
)
1007 def http_post(self
):
1008 node
= self
.get_current()
1009 attrs
= node
.attributes
1011 for (ns
,name
) in attrs
.keys():
1013 post
.append((str(name
),
1014 str(attrs
[(ns
, name
)].value
)))
1015 node
= self
.suck_node(node
, post_data
= urllib
.urlencode(post
))
1020 nodes
= self
.current_nodes
[:]
1025 new
= self
.suck_node(x
)
1031 def suck_node(self
, node
, post_data
= None):
1033 if node
.nodeType
== Node
.TEXT_NODE
:
1034 uri
= node
.nodeValue
1036 if self
.current_attrib
:
1037 uri
= self
.current_attrib
.value
1038 elif node
.hasAttributeNS(None, 'uri'):
1039 uri
= node
.getAttributeNS(None, 'uri')
1041 for attr
in node
.attributes
.keys():
1042 uri
= node
.attributes
[attr
].value
1043 if uri
.find('//') != -1 or uri
.find('.htm') != -1:
1046 print "Can't suck", node
1048 if uri
.find('//') == -1:
1049 base
= self
.model
.get_base_uri(node
)
1050 #print "Relative URI..."
1052 #print "Base URI is:", base, "add", uri
1053 uri
= urlparse
.urljoin(base
, uri
)
1056 #print "Warning: Can't find 'uri' attribute!"
1058 print "Sucking", uri
1060 if post_data
is not None:
1061 print "POSTING", post_data
1062 stream
= urllib
.urlopen(uri
, post_data
)
1063 headers
= stream
.info().headers
1066 if x
.lower().startswith('last-modified:'):
1067 last_mod
= x
[14:].strip()
1070 current_last_mod
= node
.getAttributeNS(None, 'last-modified')
1071 if current_last_mod
and last_mod
:
1072 if current_last_mod
== last_mod
:
1073 self
.model
.set_attrib(node
, 'modified', None)
1074 print "not modified => not sucking!\n"
1077 print "Fetching page contents..."
1078 data
= stream
.read()
1079 print "got data... tidying..."
1089 tin
= os
.popen('tidy -asxml 2>/dev/null', 'w')
1096 data
= os
.fdopen(r
).read()
1097 os
.waitpid(child
, 0)
1099 old_md5
= node
.getAttributeNS(None, 'md5_sum')
1102 new_md5
= md5
.new(data
).hexdigest()
1104 if old_md5
and new_md5
== old_md5
:
1105 self
.model
.set_attrib(node
, 'modified', None)
1106 print "MD5 sums match => not parsing!"
1109 reader
= PyExpat
.Reader()
1112 from Ft
.Xml
.InputSource
import InputSourceFactory
1113 from Ft
.Xml
.cDomlette
import nonvalParse
1114 isrc
= InputSourceFactory()
1117 root
= nonvalParse(isrc
.fromString(data
, uri
))
1120 print "parsing failed!"
1123 #support.report_exception()
1126 print "parse OK...",
1128 new
= node
.ownerDocument
.importNode(root
.documentElement
, 1)
1129 new
.setAttributeNS(None, 'uri', uri
)
1132 new
.setAttributeNS(None, 'last-modified', last_mod
)
1133 new
.setAttributeNS(None, 'modified', 'yes')
1134 new
.setAttributeNS(None, 'md5_sum', new_md5
)
1137 if node
== self
.root
:
1138 self
.model
.unlock(self
.root
)
1139 self
.model
.replace_node(self
.root
, new
)
1140 #self.model.strip_space(new) (not sure we need this)
1141 self
.model
.lock(new
)
1144 self
.model
.replace_node(node
, new
)
1145 #self.model.strip_space(new)
1150 def dom_from_command(self
, command
, callback
= None, old_md5
= None):
1151 """Execute shell command 'command' in the background.
1152 Parse the output as XML. When done, call callback(doc_root, md5).
1153 If old_md5 is given, compare the MD5 of the document with it,
1154 and do callback(None, "Same") if they match.
1157 cout
= os
.popen(command
)
1160 def got_all(data
, cb
= callback
, m5
= old_md5
):
1162 new_md5
= md5
.new(data
).hexdigest()
1164 if m5
and new_md5
== m5
:
1168 reader
= PyExpat
.Reader()
1172 root
= reader
.fromStream(StringIO(data
))
1175 print "dom_from_command: parsing failed"
1176 support
.report_exception()
1180 # XXX: only for nogui...
1181 got_all(cout
.read())
1184 def got_html(src
, cond
, all
= all
, got_all
= got_all
):
1185 data
= src
.read(100)
1189 input_remove(all
[1])
1193 all
[1] = input_add(cout
, GDK
.INPUT_READ
, got_html
)
1195 def put_before(self
):
1196 node
= self
.get_current()
1197 if self
.clipboard
== None:
1199 new
= self
.clipboard
.cloneNode(1)
1201 self
.model
.insert_before(node
, new
)
1205 def put_after(self
):
1206 node
= self
.get_current()
1207 if self
.clipboard
== None:
1209 new
= self
.clipboard
.cloneNode(1)
1210 self
.model
.insert_after(node
, new
)
1212 def put_replace(self
):
1213 node
= self
.get_current()
1214 if self
.clipboard
== None:
1216 if self
.current_attrib
:
1217 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1218 value
= self
.clipboard
.childNodes
[0].data
1220 value
= self
.clipboard
.data
1221 a
= self
.current_attrib
1222 value
= value
.replace('\n', ' ')
1223 self
.model
.set_attrib(node
, a
.name
, value
)
1225 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1226 if len(self
.clipboard
.childNodes
) != 1:
1228 new
= self
.clipboard
.childNodes
[0].cloneNode(1)
1230 new
= self
.clipboard
.cloneNode(1)
1231 if new
.nodeType
!= Node
.ELEMENT_NODE
:
1235 if node
== self
.root
:
1236 self
.model
.unlock(self
.root
)
1238 self
.model
.replace_node(self
.root
, new
)
1241 self
.model
.lock(self
.root
)
1243 self
.model
.replace_node(node
, new
)
1248 def put_as_child(self
):
1249 node
= self
.get_current()
1250 if self
.clipboard
== None:
1252 new
= self
.clipboard
.cloneNode(1)
1253 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1255 for n
in new
.childNodes
:
1260 self
.model
.insert(node
, new
, index
= 0)
1266 def yank_value(self
):
1267 if not self
.current_attrib
:
1269 value
= self
.current_attrib
.value
1270 self
.clipboard
= self
.model
.doc
.createTextNode(value
)
1271 #print "Clip now", self.clipboard
1273 def yank_attribs(self
, name
):
1274 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
1276 if not self
.get_current().hasAttribute(name
):
1278 attribs
= [self
.get_current().getAttributeNode(name
)]
1281 dict = self
.get_current().attributes
1282 for a
in dict.keys():
1283 attribs
.append(dict[a
])
1285 # Make sure the attributes always come out in the same order
1286 # (helps with macros).
1288 diff
= cmp(a
.name
, b
.name
)
1290 diff
= cmp(a
.namespaceURI
, b
.namespaceURI
)
1293 attribs
.sort(by_name
)
1295 n
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
1296 n
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
1297 self
.clipboard
.appendChild(n
)
1298 #print "Clip now", self.clipboard
1300 def paste_attribs(self
):
1301 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1302 attribs
= self
.clipboard
.childNodes
1304 attribs
= [self
.clipboard
]
1308 new
.append((a
.nodeName
, a
.childNodes
[0].data
))
1311 for node
in self
.current_nodes
:
1312 # XXX: Set NS attribs first...
1313 for (name
, value
) in new
:
1314 self
.model
.set_attrib(node
, name
, value
)
1317 "Ensure that all selected nodes have the same value."
1318 if len(self
.current_nodes
) < 2:
1319 raise Beep
# Not enough nodes!
1320 base
= self
.current_nodes
[0]
1321 for n
in self
.current_nodes
[1:]:
1322 if not same(base
, n
):
1323 raise Beep(may_record
= 1)
1326 raise Beep(may_record
= 1)
1328 def attribute(self
, namespace
= None, attrib
= ''):
1329 node
= self
.get_current()
1335 if attrib
== 'xmlns':
1337 #print "(ns, attrib)", `namespace`, attrib
1339 a
= node
.attributes
.get((namespace
, attrib
), None)
1342 self
.move_to(node
, a
)
1344 print "No such attribute"
1347 def set_attrib(self
, value
):
1348 a
= self
.current_attrib
1351 node
= self
.get_current()
1352 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1353 self
.move_to(node
, a
)
1355 def add_attrib(self
, UNUSED
, name
, value
= ''):
1356 if name
.startswith('xmlns:'):
1357 print "*** SET NS ATTRIB ***", self
.op_in_progress
.program
1359 node
= self
.get_current()
1360 a
= self
.model
.set_attrib(node
, name
, value
)
1361 self
.move_to(node
, a
)
1363 def load_html(self
, path
):
1364 "Replace root with contents of this HTML file."
1365 print "Reading HTML..."
1366 command
= "tidy -asxml '%s' 2>/dev/null" % path
1368 def done(root
, md5
, self
= self
):
1370 new
= self
.root
.ownerDocument
.importNode(root
.documentElement
, 1)
1373 self
.model
.unlock(self
.root
)
1375 self
.model
.replace_node(self
.root
, new
)
1376 self
.model
.lock(new
)
1378 self
.move_to(self
.root
)
1380 self
.dom_from_command(command
, done
)
1382 def load_xml(self
, path
):
1383 "Replace root with contents of this XML (or Dome) file."
1384 reader
= PyExpat
.Reader()
1385 new_doc
= reader
.fromUri(path
)
1386 self
.load_node(new_doc
.documentElement
)
1388 def load_node(self
, root
):
1389 new
= self
.model
.doc
.importNode(root
, 1)
1391 self
.model
.strip_space(new
)
1394 self
.model
.unlock(self
.root
)
1396 self
.model
.replace_node(self
.root
, new
)
1397 self
.model
.lock(new
)
1399 self
.move_to(self
.root
)
1401 def select_dups(self
):
1402 node
= self
.get_current()
1404 for n
in node
.parentNode
.childNodes
:
1409 self
.move_to(select
)
1411 def show_html(self
):
1412 from HTML
import HTML
1413 HTML(self
.model
, self
.get_current()).show()
1415 def show_canvas(self
):
1416 from Canvas
import Canvas
1417 Canvas(self
, self
.get_current()).show()
1419 def toggle_hidden(self
):
1420 nodes
= self
.current_nodes
[:]
1423 if node
.hasAttributeNS(None, 'hidden'):
1427 self
.model
.set_attrib(node
, 'hidden', new
, with_update
= 0)
1428 self
.model
.update_all(self
.root
)
1431 def soap_send(self
):
1432 copy
= node_to_xml(self
.get_current())
1433 env
= copy
.documentElement
1435 if env
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/':
1436 support
.report_error("Not a SOAP-ENV:Envelope (bad namespace)")
1438 if env
.localName
!= 'Envelope':
1439 support
.report_error("Not a SOAP-ENV:Envelope (bad local name)")
1442 if len(env
.childNodes
) != 2:
1443 support
.report_error("SOAP-ENV:Envelope must have one header and one body")
1446 kids
= elements(env
)
1450 if head
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1451 head
.localName
!= 'Head':
1452 support
.report_error("First child must be a SOAP-ENV:Head element")
1455 if body
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1456 body
.localName
!= 'Body':
1457 support
.report_error("Second child must be a SOAP-ENV:Body element")
1461 for header
in elements(head
):
1462 if header
.namespaceURI
== DOME_NS
and header
.localName
== 'soap-forward-to':
1465 print header
.namespaceURI
1466 print header
.localName
1469 support
.report_error("Head must contain a dome:soap-forward-to element")
1472 dest
= sft
.childNodes
[0].data
1473 parent
= sft
.parentNode
1474 if len(elements(parent
)) == 1:
1476 parent
= sft
.parentNode
# Delete the whole header
1477 parent
.removeChild(sft
)
1479 import httplib
, urlparse
1481 (scheme
, addr
, path
, p
, q
, f
) = urlparse
.urlparse(dest
, allow_fragments
= 0)
1482 if scheme
!= 'http':
1483 support
.report_error("SOAP is only supported for 'http:' -- sorry!")
1487 ext
.PrettyPrint(copy
, stream
= stream
)
1488 message
= stream
.data
1490 conn
= httplib
.HTTP(addr
)
1491 conn
.putrequest("POST", path
)
1492 conn
.putheader('Content-Type', 'text/xml; charset="utf-8"')
1493 conn
.putheader('Content-Length', str(len(message
)))
1494 conn
.putheader('SOAPAction', '')
1497 (code
, r_mess
, r_headers
) = conn
.getreply()
1499 reply
= conn
.getfile().read()
1500 print "Got:\n", reply
1502 reader
= PyExpat
.Reader()
1503 new_doc
= reader
.fromString(reply
)
1506 new
= self
.model
.doc
.importNode(new_doc
.documentElement
, 1)
1508 self
.model
.strip_space(new
)
1510 old
= self
.get_current()
1512 self
.model
.replace_node(old
, new
)
1515 def program_changed(self
, changed_op
):
1516 print "Check points..."
1518 (op
, exit
) = self
.rec_point
1520 print "Lost rec_point"
1521 self
.rec_point
= None
1523 (op
, exit
) = self
.exec_point
1525 print "Lost exec_point"
1526 self
.exec_point
= None
1527 for l
in self
.lists
:
1529 self
.status_changed()
1531 def prog_tree_changed(self
):
1534 def export_all(self
):
1535 doc
= implementation
.createDocument(DOME_NS
, 'dome', None)
1536 node
= self
.model
.root_program
.to_xml(doc
)
1537 doc
.documentElement
.appendChild(node
)
1538 node
= doc
.createElementNS(DOME_NS
, 'dome-data')
1539 doc
.documentElement
.appendChild(node
)
1542 print "*** WARNING: Saving from a chroot!"
1544 data
= doc
.importNode(model
.doc
.documentElement
, 1)
1545 node
.appendChild(data
)
1549 def blank_all(self
):
1550 doc
= implementation
.createDocument(None, 'root', None)
1552 self
.clipboard
= self
.model
.doc
.createElementNS(None, 'root')
1558 def write(self
, str):