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
, sys
15 from StringIO
import StringIO
17 from Program
import Op
, Block
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]*$')
37 def fix_broken_html(data
):
38 """Pre-parse the data before sending to tidy to fix really really broken
39 stuff (eg, MS Word output). Returns None if data is OK"""
40 if data
.find('<o:p>') == -1:
41 return # Doesn't need fixing?
43 data
= data
.replace('<o:p></o:p>', '')
44 data
= re
.sub('<!\[[^]]*\]>', '', data
)
50 fixed
= fix_broken_html(data
)
58 tin
= os
.popen('tidy --force-output yes -q -utf8 -asxml 2>/dev/null', 'w')
60 tin
= os
.popen('tidy --force-output yes -q -asxml 2>/dev/null', 'w')
61 tin
.write(fixed
or data
)
67 data
= os
.fdopen(r
).read()
73 # - A ref to a DOM document
74 # - A set of current nodes
77 # It does not have any display code. It does contain code to perform actions
78 # (actions affect the document AND the view state).
80 # These actions can be repeated using '.'
89 "delete_node_no_clipboard",
118 "Recursivly compare two nodes."
119 if a
.nodeType
!= b
.nodeType
or a
.nodeName
!= b
.nodeName
:
121 if a
.nodeValue
!= b
.nodeValue
:
125 if len(aks
) != len(bks
):
127 for (ak
, bk
) in map(None, aks
, bks
):
132 class InProgress(Exception):
133 "Throw this if the operation will complete later..."
134 class Done(Exception):
135 "Thrown when the chain is completed successfully"
138 def __init__(self
, model
, callback_handlers
= None):
139 """callback_handlers is an (idle_add, idle_remove) tuple"""
143 self
.single_step
= 1 # 0 = Play 1 = Step-into 2 = Step-over
145 self
.chroots
= [] # (model, node, marked)
146 self
.foreach_stack
= [] # (block, [nodes], restore-nodes)
147 self
.current_nodes
= []
148 self
.clipboard
= None
149 self
.current_attrib
= None
152 if not callback_handlers
:
154 self
.idle_add
, self
.idle_remove
= gtk
.idle_add
, gtk
.idle_remove
156 self
.idle_add
, self
.idle_remove
= callback_handlers
158 self
.exec_point
= None # None, or (Op, Exit)
159 self
.rec_point
= None # None, or (Op, Exit)
160 self
.op_in_progress
= None
162 self
.callback_on_return
= None # Called when there are no more Ops...
163 self
.in_callback
= 0 # (not the above callback - this is the playback one)
164 self
.innermost_failure
= None
165 self
.call_on_done
= None # Called when there is nowhere to return to
166 self
.exec_stack
= [] # Ops we are inside (display use only)
168 self
.breakpoints
= {} # (op, exit) keys, values don't matter
169 self
.current_nodes
= []
170 self
.set_model(model
)
172 def get_current(self
):
173 if len(self
.current_nodes
) == 1:
174 return self
.current_nodes
[0]
175 raise Exception('This operation required exactly one selected node!')
177 def set_model(self
, model
):
178 assert not self
.marked
181 self
.model
.unlock(self
.root
)
184 self
.model
.remove_view(self
)
185 self
.model
.root_program
.watchers
.remove(self
)
187 self
.model
.root_program
.watchers
.append(self
)
189 self
.set_display_root(self
.model
.get_root())
190 self
.move_to(self
.root
)
193 return self
.idle_cb
!= 0 or self
.in_callback
195 def run_new(self
, callback
= None):
196 "Reset the playback system (stack, step-mode and point)."
197 "Call callback(exit) when execution finishes."
199 self
.idle_remove(self
.idle_cb
)
202 self
.innermost_failure
= None
203 self
.call_on_done
= callback
204 self
.callback_on_return
= None
205 while self
.exec_stack
:
207 self
.status_changed()
209 def push_stack(self
, op
):
210 if not isinstance(op
, Op
):
211 raise Exception('push_stack: not an Op', op
)
212 self
.exec_stack
.append(op
)
217 op
= self
.exec_stack
.pop()
221 def set_exec(self
, pos
):
222 if self
.op_in_progress
:
223 raise Exception("Operation in progress...")
225 assert isinstance(pos
[0], Op
)
226 assert pos
[1] in ['next', 'fail']
227 self
.exec_point
= pos
229 #print "set_exec: %s:%s" % pos
233 def set_rec(self
, pos
):
237 self
.status_changed()
239 def record_at_point(self
):
240 if not self
.exec_point
:
241 support
.report_error("No current point!")
243 self
.set_rec(self
.exec_point
)
246 def stop_recording(self
):
248 self
.set_exec(self
.rec_point
)
251 support
.report_error("Not recording!")
253 def may_record(self
, action
):
254 "Perform and, possibly, record this action"
258 print "RECORD:", rec
, action
260 if action
== ['enter']:
261 new_op
= Block(op
.parent
)
262 new_op
.toggle_enter()
265 op
.link_to(new_op
, old_exit
)
270 if isinstance(new_op
, Block
):
271 self
.set_rec((new_op
.start
, 'next'))
273 self
.set_rec((new_op
, 'next'))
275 play_op
, exit
= self
.exec_point
276 # (do_one_step may have stopped recording)
278 self
.set_rec((new_op
, exit
))
284 self
.do_action(action
)
290 (type, val
, tb
) = sys
.exc_info()
291 #if not val.may_record:
295 support
.report_exception()
298 def add_display(self
, display
):
299 "Calls move_from(old_node) when we move and update_all() on updates."
300 self
.displays
.append(display
)
301 #print "Added:", self.displays
303 def remove_display(self
, display
):
304 self
.displays
.remove(display
)
305 #print "Removed, now:", self.displays
306 if not self
.displays
:
309 def update_replace(self
, old
, new
):
312 if old
in self
.current_nodes
:
314 self
.model
.unlock(old
)
315 self
.current_nodes
.remove(old
)
316 self
.current_nodes
.append(new
)
317 self
.update_all(new
.parentNode
)
319 self
.update_all(new
.parentNode
)
321 def has_ancestor(self
, node
, ancestor
):
322 while node
!= ancestor
:
323 node
= node
.parentNode
328 def update_all(self
, node
):
329 for display
in self
.displays
:
330 display
.update_all(node
)
333 #print "View deleted"
334 self
.model
.root_program
.watchers
.remove(self
)
338 self
.model
.unlock(self
.root
)
340 self
.model
.remove_view(self
)
343 # 'nodes' may be either a node or a list of nodes.
344 # (duplicates will be removed)
345 # If it's a single node, then an 'attrib' node may also be specified
346 def move_to(self
, nodes
, attrib
= None):
347 if self
.current_nodes
== nodes
:
350 if attrib
and attrib
.nodeType
!= Node
.ATTRIBUTE_NODE
:
351 raise Exception('attrib not of type ATTRIBUTE_NODE!')
353 if type(nodes
) != types
.ListType
:
366 if len(old
) != len(nodes
):
367 print "(move_to: attempt to set duplicate nodes)"
369 old_nodes
= self
.current_nodes
370 self
.current_nodes
= nodes
372 for node
in self
.current_nodes
:
373 self
.model
.lock(node
)
374 for node
in old_nodes
:
375 self
.model
.unlock(node
)
377 self
.current_attrib
= attrib
379 for display
in self
.displays
:
380 display
.move_from(old_nodes
)
382 def move_prev_sib(self
):
383 if self
.get_current() == self
.root
or not self
.get_current().previousSibling
:
385 self
.move_to(self
.get_current().previousSibling
)
387 def move_next_sib(self
):
388 if self
.get_current() == self
.root
or not self
.get_current().nextSibling
:
390 self
.move_to(self
.get_current().nextSibling
)
394 for n
in self
.current_nodes
:
402 def move_right(self
):
404 for n
in self
.current_nodes
:
413 self
.move_to(self
.root
)
416 if not self
.get_current().childNodes
:
418 node
= self
.get_current().childNodes
[0]
419 while node
.nextSibling
:
420 node
= node
.nextSibling
423 def set_display_root(self
, root
):
424 self
.model
.lock(root
)
426 self
.model
.unlock(self
.root
)
428 self
.update_all(root
)
431 """Change the display root to a COPY of the selected node.
432 Call Leave to check changes back in."""
433 node
= self
.get_current()
434 if node
is self
.root
:
435 raise Beep
# Locking problems if this happens...
436 if self
.model
.doc
is not node
.ownerDocument
:
437 raise Exception('Current node not in view!')
441 new_model
= self
.model
.lock_and_copy(node
)
442 self
.chroots
.append((self
.model
, node
, self
.marked
))
443 self
.set_model(new_model
)
446 """Undo the effect of the last chroot()."""
454 (old_model
, old_node
, old_marked
) = self
.chroots
.pop()
456 copy
= old_model
.doc
.importNode(self
.model
.get_root(), 1)
457 old_model
.unlock(old_node
)
458 old_model
.replace_node(old_node
, copy
)
459 self
.set_model(old_model
)
461 self
.set_marked(old_marked
.keys())
464 model
.undo_stack
= None
470 def do_action(self
, action
):
471 "'action' is a tuple (function, arg1, arg2, ...)"
472 "Performs the action. Returns if action completes, or raises "
473 "InProgress if not (will call resume() later)."
474 if action
[0] in record_again
:
475 self
.last_action
= action
476 elif action
[0] == 'again':
477 action
= self
.last_action
478 fn
= getattr(self
, action
[0])
480 #print "DO:", action[0]
483 new
= apply(fn
, action
[1:])
487 if not self
.op_in_progress
:
492 if not self
.op_in_progress
:
494 traceback
.print_exc()
498 if self
.op_in_progress
:
499 op
= self
.op_in_progress
501 self
.set_exec((op
, exit
))
505 def breakpoint(self
):
506 if self
.breakpoints
.has_key(self
.exec_point
):
508 op
= self
.exec_point
[0]
509 if op
.parent
.start
== op
and op
.next
== None:
510 return 1 # Empty program
513 def do_one_step(self
):
514 "Execute the next op after exec_point, then:"
515 "- position the point on one of the exits return."
516 "- if there is no op to perform, call callback_on_return() or raise Done."
517 "- if the operation is started but not complete, raise InProgress and "
518 " arrange to resume() later."
519 if self
.op_in_progress
:
520 support
.report_error("Already executing something.")
522 if not self
.exec_point
:
523 support
.report_error("No current playback point.")
525 (op
, exit
) = self
.exec_point
527 if self
.single_step
== 0 and self
.breakpoint():
528 print "Hit a breakpoint! At " + time
.ctime(time
.time())
533 l
.show_prog(op
.get_program())
536 next
= getattr(op
, exit
)
539 self
.do_action(next
.action
) # May raise InProgress
542 if exit
== 'fail' and not self
.innermost_failure
:
543 #print "Setting innermost_failure on", op
544 self
.innermost_failure
= op
546 # If we're in a block, try exiting from it...
547 if isinstance(op
.parent
, Block
):
548 if self
.start_block_iteration(op
.parent
, continuing
= exit
):
550 if not op
.parent
.is_toplevel():
551 self
.set_exec((op
.parent
, exit
))
554 if self
.callback_on_return
:
555 cb
= self
.callback_on_return
556 self
.callback_on_return
= None
561 def set_oip(self
, op
):
562 #print "set_oip:", self.exec_point
565 self
.op_in_progress
= op
569 def fast_global(self
, name
):
570 "Search for nodes with this name anywhere under the root (//name)"
571 #print "Fast global", name
573 (prefix
, localName
) = string
.split(name
, ':', 1)
575 (prefix
, localName
) = (None, name
)
576 if self
.current_nodes
:
577 src
= self
.current_nodes
[-1]
580 namespaceURI
= self
.model
.prefix_to_namespace(src
, prefix
)
583 if node
.nodeType
!= Node
.ELEMENT_NODE
:
585 if node
.localName
== localName
and node
.namespaceURI
== namespaceURI
:
587 map(add
, node
.childNodes
)
593 def do_global(self
, pattern
):
594 if len(self
.current_nodes
) != 1:
595 self
.move_to(self
.root
)
596 if pattern
[:2] == '//':
597 if fast_global
.match(pattern
):
598 self
.fast_global(pattern
[2:])
602 ns
= ext
.GetAllNs(self
.current_nodes
[0])
603 ns
['ext'] = FT_EXT_NAMESPACE
609 c
= Context
.Context(self
.get_current(), processorNss
= ns
)
610 from Ft
.Xml
.XPath
import XPathParser
611 code
= XPathParser
.new().parse(self
.macro_pattern(pattern
))
613 nodes
= code
.evaluate(c
)
614 #nodes = XPath.Evaluate(self.macro_pattern(pattern), contextNode = self.get_current())
615 #print "Found", nodes
618 def select_region(self
, path
, ns
= None):
619 if len(self
.current_nodes
) == 0:
621 src
= self
.current_nodes
[-1]
623 ns
= ext
.GetAllNs(src
)
624 ns
['ext'] = FT_EXT_NAMESPACE
625 c
= Context
.Context(src
, [src
], processorNss
= ns
)
626 rt
= XPath
.Evaluate(path
, context
= c
)
629 if not self
.has_ancestor(x
, self
.root
):
630 print "[ skipping search result above root ]"
635 print "*** Search for '%s' in select_region failed" % path
636 print " (namespaces were '%s')" % ns
638 if node
.parentNode
!= src
.parentNode
:
639 print "Nodes must have same parent!"
643 for n
in src
.parentNode
.childNodes
:
645 if n
is src
or n
is node
:
649 self
.move_to(selected
)
651 def macro_pattern(self
, pattern
):
652 """Do the @CURRENT@ substitution for an XPath"""
653 if len(self
.current_nodes
) != 1:
655 node
= self
.get_current()
656 if node
.nodeType
== Node
.TEXT_NODE
:
659 current
= node
.nodeName
660 pattern
= pattern
.replace('@CURRENT@', current
)
661 #print "Searching for", pattern
664 def do_search(self
, pattern
, ns
= None, toggle
= FALSE
):
665 if len(self
.current_nodes
) == 0:
668 src
= self
.current_nodes
[-1]
670 ns
= ext
.GetAllNs(src
)
671 ns
['ext'] = FT_EXT_NAMESPACE
672 c
= Context
.Context(src
, [src
], processorNss
= ns
)
674 rt
= XPath
.Evaluate(self
.macro_pattern(pattern
), context
= c
)
677 if not self
.has_ancestor(x
, self
.root
):
678 print "[ skipping search result above root ]"
682 #if self.node_to_line[x] > self.current_line:
686 #print "*** Search for '%s' failed" % pattern
687 #print " (namespaces were '%s')" % ns
690 new
= self
.current_nodes
[:]
699 def do_text_search(self
, pattern
):
700 pattern
= self
.macro_pattern(pattern
)
701 return self
.do_search("//text()[ext:match('%s')]" % pattern
)
703 def subst(self
, replace
, with
):
704 "re search and replace on the current node"
705 nodes
= self
.current_nodes
[:]
706 check
= len(nodes
) == 1
707 a
= self
.current_attrib
709 new
= re
.sub(replace
, with
, a
.value
)
710 a
= self
.model
.set_attrib(nodes
[0], a
.name
, new
)
711 self
.move_to(nodes
[0], a
)
716 if n
.nodeType
== Node
.TEXT_NODE
:
717 old
= n
.data
.replace('\n', ' ')
718 new
, num
= re
.subn(replace
, with
, old
)
719 if check
and not num
:
722 self
.model
.set_data(n
, new
)
724 elif n
.nodeType
== Node
.ELEMENT_NODE
:
725 old
= str(n
.nodeName
)
726 new
, num
= re
.subn(replace
, with
, old
)
727 if check
and not num
:
730 new_ns
, x
= self
.model
.split_qname(n
, new
)
731 final
.append(self
.model
.set_name(n
, new_ns
, new
))
737 def python(self
, expr
):
738 "Replace node with result of expr(old_value)"
739 if self
.get_current().nodeType
== Node
.TEXT_NODE
:
740 vars = {'x': self
.get_current().data
, 're': re
, 'sub': re
.sub
, 'string': string
}
741 result
= eval(expr
, vars)
742 new
= self
.python_to_node(result
)
743 node
= self
.get_current()
745 self
.model
.replace_node(node
, new
)
750 def resume(self
, exit
= 'next'):
751 "After raising InProgress, call this to start moving again."
752 if self
.op_in_progress
:
753 op
= self
.op_in_progress
755 self
.set_exec((op
, exit
))
756 if not self
.single_step
:
758 self
.status_changed()
760 print "(nothing to resume)"
763 def ask_cb(result
, self
= self
):
767 self
.clipboard
= self
.model
.doc
.createTextNode(result
)
770 from GetArg
import GetArg
771 box
= GetArg('Input:', ask_cb
, [q
], destroy_return
= 1)
774 def python_to_node(self
, data
):
775 "Convert a python data structure into a tree and return the root."
776 if type(data
) == types
.ListType
:
777 list = self
.model
.doc
.createElementNS(DOME_NS
, 'dome:list')
778 list.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:dome', DOME_NS
)
780 list.appendChild(self
.python_to_node(x
))
782 return self
.model
.doc
.createTextNode(str(data
))
784 def yank(self
, deep
= 1):
785 if self
.current_attrib
:
786 a
= self
.current_attrib
788 self
.clipboard
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
789 self
.clipboard
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
791 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
792 for n
in self
.current_nodes
:
793 c
= n
.cloneNode(deep
)
795 self
.clipboard
.appendChild(c
)
797 #print "Clip now", self.clipboard
799 def shallow_yank(self
):
802 def delete_shallow(self
):
803 nodes
= self
.current_nodes
[:]
806 if self
.root
in nodes
:
811 self
.model
.delete_shallow(n
)
814 def delete_node_no_clipboard(self
):
815 self
.delete_node(yank
= 0)
817 def delete_node(self
, yank
= 1):
818 nodes
= self
.current_nodes
[:]
823 if self
.current_attrib
:
824 ca
= self
.current_attrib
825 self
.current_attrib
= None
826 self
.model
.set_attrib(self
.get_current(), ca
.name
, None)
828 if self
.root
in nodes
:
831 new
= [x
.parentNode
for x
in nodes
]
833 self
.model
.delete_nodes(nodes
)
836 nodes
= self
.current_nodes
[:]
838 self
.model
.unlock(self
.root
)
842 self
.model
.lock(self
.root
)
843 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
846 nodes
= self
.current_nodes
[:]
848 self
.model
.unlock(self
.root
)
852 self
.model
.lock(self
.root
)
853 self
.move_to(filter(lambda x
: self
.has_ancestor(x
, self
.root
), nodes
))
855 def default_done(self
, exit
):
856 "Called when execution of a program returns. op_in_progress has been "
857 "restored - move to the exit."
858 #print "default_done(%s)" % exit
859 if self
.op_in_progress
:
860 op
= self
.op_in_progress
862 self
.set_exec((op
, exit
))
864 print "No operation to return to!"
865 c
= self
.call_on_done
867 self
.call_on_done
= None
870 self
.jump_to_innermost_failure()
873 def jump_to_innermost_failure(self
):
874 assert self
.innermost_failure
!= None
876 print "Returning to innermost failure:", self
.innermost_failure
877 self
.set_exec((self
.innermost_failure
, 'fail'))
879 if hasattr(l
, 'set_innermost_failure'):
880 l
.set_innermost_failure(self
.innermost_failure
)
882 def play(self
, name
, done
= None):
883 "Play this macro. When it returns, restore the current op_in_progress (if any)"
884 "and call done(exit). Default for done() moves exec_point."
885 "done() is called from do_one_step() - usual rules apply."
887 prog
= self
.name_to_prog(name
)
888 self
.innermost_failure
= None
891 done
= self
.default_done
893 def cbor(self
= self
, op
= self
.op_in_progress
, done
= done
,
895 old_cbor
= self
.callback_on_return
,
896 old_ss
= self
.single_step
):
897 "We're in do_one_step..."
899 #print "Return from '%s'..." % name
901 if old_ss
== 2 and self
.single_step
== 0:
902 self
.single_step
= old_ss
903 self
.callback_on_return
= old_cbor
905 o
, exit
= self
.exec_point
907 #print "Resume op '%s' (%s)" % (op.program.name, op)
912 self
.callback_on_return
= cbor
914 if self
.single_step
== 2:
917 if self
.op_in_progress
:
918 self
.push_stack(self
.op_in_progress
)
920 self
.play_block(prog
.code
)
922 self
.status_changed()
925 def start_block_iteration(self
, block
, continuing
= None):
926 "True if we are going to run the block, False to exit the loop"
927 "Continuing is 'next' or 'fail' if we reached the end of the block."
928 #print "Start interation"
929 if not self
.foreach_stack
:
930 raise Exception("Reached the end of a block we never entered")
931 stack_block
, nodes_list
, restore
= self
.foreach_stack
[-1]
932 if stack_block
!= block
:
933 self
.foreach_stack
= []
934 raise Exception("Reached the end of a block we never entered")
940 restore
.extend(self
.current_nodes
)
941 if continuing
== 'fail':
942 print "Error in block; exiting early in program", block
.get_program()
943 self
.foreach_stack
.pop()
945 while nodes_list
and nodes_list
[0].parentNode
== None:
946 print "Skipping deleted node", nodes_list
[0]
950 self
.foreach_stack
.pop()
952 nodes
= filter(lambda x
: self
.has_ancestor(x
, self
.root
), restore
)
954 return 0 # Nothing left to do
955 nodes
= nodes_list
[0]
960 print "[ %d after this ]" % len(nodes_list
),
965 self
.set_exec((block
.start
, 'next'))
968 def play_block(self
, block
):
969 assert isinstance(block
, Block
)
970 #print "Enter Block!"
972 list = self
.current_nodes
[:]
974 list = [self
.current_nodes
[:]] # List of one item, containing everything
976 self
.foreach_stack
.append((block
, list, []))
977 if not self
.start_block_iteration(block
):
978 # No nodes selected...
979 if not block
.is_toplevel():
980 self
.set_exec((block
, 'next'))
983 assert self
.op_in_progress
984 oip
= self
.op_in_progress
987 if not self
.single_step
:
992 if self
.op_in_progress
:
993 raise Exception("Operation in progress")
995 raise Exception("Already playing!")
996 self
.idle_cb
= self
.idle_add(self
.play_callback
)
998 def play_callback(self
):
999 self
.idle_remove(self
.idle_cb
)
1002 self
.in_callback
= 1
1006 self
.in_callback
= 0
1008 (op
, exit
) = self
.exec_point
1009 if exit
== 'fail' and self
.innermost_failure
:
1010 self
.jump_to_innermost_failure()
1011 print "Done, at " + time
.ctime(time
.time())
1018 type, val
, tb
= sys
.exc_info()
1019 list = traceback
.extract_tb(tb
)
1020 stack
= traceback
.format_list(list[-2:])
1021 ex
= traceback
.format_exception_only(type, val
) + ['\n\n'] + stack
1022 traceback
.print_exception(type, val
, tb
)
1023 print "Error in do_one_step(): stopping playback"
1024 node
= self
.op_in_progress
1027 self
.set_exec((node
, 'fail'))
1028 self
.status_changed()
1030 if self
.op_in_progress
or self
.single_step
:
1031 self
.status_changed()
1036 def status_changed(self
):
1037 for display
in self
.displays
:
1038 if hasattr(display
, 'update_state'):
1039 display
.update_state()
1041 def map(self
, name
):
1044 nodes
= self
.current_nodes
[:]
1046 print "map of nothing: skipping..."
1048 inp
= [nodes
, None] # Nodes, next
1049 def next(exit
= exit
, self
= self
, name
= name
, inp
= inp
):
1050 "This is called while in do_one_step() - normal rules apply."
1052 print "[ %d to go ]" % len(nodes
),
1055 print "Map: nodes remaining, but an error occurred..."
1056 return self
.default_done(exit
)
1057 while nodes
and nodes
[0].parentNode
== None:
1058 print "Skipping deleted node", nodes
[0]
1061 return self
.default_done(exit
)
1062 self
.move_to(nodes
[0])
1066 #print "Map: calling play (%d after this)" % len(nodes)
1067 self
.play(name
, done
= next
) # Should raise InProgress
1068 if nodes
is self
.current_nodes
:
1069 raise Exception("Slice failed!")
1073 def name_to_prog(self
, name
):
1074 comps
= string
.split(name
, '/')
1075 prog
= self
.model
.root_program
1076 if prog
.name
!= comps
[0]:
1077 raise Exception("No such program as '%s'!" % name
)
1080 prog
= prog
.subprograms
[comps
[0]]
1084 def change_node(self
, new_data
):
1085 nodes
= self
.current_nodes
1089 if nodes
[0].nodeType
== Node
.ELEMENT_NODE
:
1090 # Slow, so do this here, even if vaguely incorrect...
1091 assert ' ' not in new_data
1093 (prefix
, localName
) = string
.split(new_data
, ':', 1)
1095 (prefix
, localName
) = (None, new_data
)
1096 namespaceURI
= self
.model
.prefix_to_namespace(nodes
[0], prefix
)
1099 if node
is self
.root
:
1100 self
.model
.unlock(self
.root
)
1101 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1102 self
.model
.lock(new
)
1105 new
= self
.model
.set_name(node
, namespaceURI
, new_data
)
1110 self
.model
.set_data(node
, new_data
)
1113 def add_node(self
, where
, data
):
1114 cur
= self
.get_current()
1117 (prefix
, localName
) = string
.split(data
, ':', 1)
1119 (prefix
, localName
) = (None, data
)
1120 namespaceURI
= self
.model
.prefix_to_namespace(self
.get_current(), prefix
)
1121 new
= self
.model
.doc
.createElementNS(namespaceURI
, data
)
1123 new
= self
.model
.doc
.createTextNode(data
)
1127 self
.model
.insert_before(cur
, new
)
1128 elif where
[0] == 'a':
1129 self
.model
.insert_after(cur
, new
)
1130 elif where
[0] == 'e':
1131 self
.model
.insert_before(None, new
, parent
= cur
)
1133 self
.model
.insert(cur
, new
)
1139 def http_post(self
):
1140 node
= self
.get_current()
1141 attrs
= node
.attributes
1143 for (ns
,name
) in attrs
.keys():
1145 post
.append((str(name
),
1146 str(attrs
[(ns
, name
)].value
)))
1147 node
= self
.suck_node(node
, post_data
= urllib
.urlencode(post
))
1152 nodes
= self
.current_nodes
[:]
1153 attrib
= self
.current_attrib
1158 new
= self
.suck_node(x
, attrib
= attrib
)
1164 def suck_node(self
, node
, post_data
= None, attrib
= None):
1166 if node
.nodeType
== Node
.TEXT_NODE
:
1167 uri
= node
.nodeValue
1171 elif node
.hasAttributeNS(None, 'uri'):
1172 uri
= node
.getAttributeNS(None, 'uri')
1174 for attr
in node
.attributes
.keys():
1175 uri
= node
.attributes
[attr
].value
1176 if uri
.find('//') != -1 or uri
.find('.htm') != -1:
1179 print "Can't suck", node
1181 if uri
.find('//') == -1:
1182 base
= self
.model
.get_base_uri(node
)
1183 #print "Relative URI..."
1185 #print "Base URI is:", base, "add", uri
1186 uri
= urlparse
.urljoin(base
, uri
)
1189 #print "Warning: Can't find 'uri' attribute!"
1191 if uri
.startswith('file:///'):
1192 print "Loading", uri
1194 assert not post_data
1195 stream
= open(uri
[7:])
1196 # (could read the mod time here...)
1199 print "Sucking", uri
1201 if post_data
is not None:
1202 print "POSTING", post_data
1203 stream
= urllib
.urlopen(uri
, post_data
)
1204 headers
= stream
.info().headers
1207 if x
.lower().startswith('last-modified:'):
1208 last_mod
= x
[14:].strip()
1211 current_last_mod
= node
.getAttributeNS(None, 'last-modified')
1212 if current_last_mod
and last_mod
:
1213 if current_last_mod
== last_mod
:
1214 self
.model
.set_attrib(node
, 'modified', None)
1215 print "not modified => not sucking!\n"
1218 print "Fetching page contents..."
1219 data
= stream
.read()
1220 print "got data... tidying..."
1222 if data
.startswith('<?xml'):
1225 data
= to_html(data
)
1227 old_md5
= node
.getAttributeNS(None, 'md5_sum')
1230 new_md5
= md5
.new(data
).hexdigest()
1232 if old_md5
and new_md5
== old_md5
:
1233 self
.model
.set_attrib(node
, 'modified', None)
1234 print "MD5 sums match => not parsing!"
1237 reader
= PyExpat
.Reader()
1240 from Ft
.Xml
.InputSource
import InputSourceFactory
1241 from Ft
.Xml
.cDomlette
import nonvalParse
1242 isrc
= InputSourceFactory()
1245 root
= nonvalParse(isrc
.fromString(data
, uri
))
1248 type, val
, tb
= sys
.exc_info()
1249 traceback
.print_exception(type, val
, tb
)
1250 print "parsing failed!"
1253 #support.report_exception()
1256 print "parse OK...",
1258 new
= node
.ownerDocument
.importNode(root
.documentElement
, 1)
1259 new
.setAttributeNS(None, 'uri', uri
)
1262 new
.setAttributeNS(None, 'last-modified', last_mod
)
1263 new
.setAttributeNS(None, 'modified', 'yes')
1264 new
.setAttributeNS(None, 'md5_sum', new_md5
)
1267 if node
== self
.root
:
1268 self
.model
.unlock(self
.root
)
1269 self
.model
.replace_node(self
.root
, new
)
1270 #self.model.strip_space(new) (not sure we need this)
1271 self
.model
.lock(new
)
1274 self
.model
.replace_node(node
, new
)
1275 #self.model.strip_space(new)
1280 def dom_from_command(self
, command
, callback
= None, old_md5
= None):
1281 """Execute shell command 'command' in the background.
1282 Parse the output as XML. When done, call callback(doc_root, md5).
1283 If old_md5 is given, compare the MD5 of the document with it,
1284 and do callback(None, "Same") if they match.
1287 cout
= os
.popen(command
)
1290 def got_all(data
, cb
= callback
, m5
= old_md5
):
1292 new_md5
= md5
.new(data
).hexdigest()
1294 if m5
and new_md5
== m5
:
1298 reader
= PyExpat
.Reader()
1302 root
= reader
.fromStream(StringIO(data
))
1305 print "dom_from_command: parsing failed"
1306 support
.report_exception()
1310 # XXX: only for nogui...
1311 got_all(cout
.read())
1314 def got_html(src
, cond
, all
= all
, got_all
= got_all
):
1315 data
= src
.read(100)
1319 input_remove(all
[1])
1323 all
[1] = input_add(cout
, GDK
.INPUT_READ
, got_html
)
1325 def put_before(self
):
1326 node
= self
.get_current()
1327 if self
.clipboard
== None:
1329 new
= self
.clipboard
.cloneNode(1)
1331 self
.model
.insert_before(node
, new
)
1335 def put_after(self
):
1336 node
= self
.get_current()
1337 if self
.clipboard
== None:
1339 new
= self
.clipboard
.cloneNode(1)
1340 self
.model
.insert_after(node
, new
)
1342 def put_replace(self
):
1343 node
= self
.get_current()
1344 if self
.clipboard
== None:
1345 print "No clipboard!"
1347 if self
.current_attrib
:
1348 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1349 value
= self
.clipboard
.childNodes
[0].data
1351 value
= self
.clipboard
.data
1352 a
= self
.current_attrib
1353 value
= value
.replace('\n', ' ')
1354 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1355 self
.move_to(node
, a
)
1357 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1358 if len(self
.clipboard
.childNodes
) != 1:
1359 print "Multiple nodes in clipboard!"
1361 new
= self
.clipboard
.childNodes
[0].cloneNode(1)
1363 new
= self
.clipboard
.cloneNode(1)
1364 if new
.nodeType
!= Node
.ELEMENT_NODE
:
1368 if node
== self
.root
:
1369 self
.model
.unlock(self
.root
)
1371 self
.model
.replace_node(self
.root
, new
)
1374 self
.model
.lock(self
.root
)
1376 self
.model
.replace_node(node
, new
)
1379 type, val
, tb
= sys
.exc_info()
1380 traceback
.print_exception(type, val
, tb
)
1381 print "Replace failed!"
1384 def put_as_child(self
):
1385 node
= self
.get_current()
1386 if self
.clipboard
== None:
1388 new
= self
.clipboard
.cloneNode(1)
1389 if new
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1391 for n
in new
.childNodes
:
1396 self
.model
.insert(node
, new
, index
= 0)
1402 def yank_value(self
):
1403 if not self
.current_attrib
:
1405 value
= self
.current_attrib
.value
1406 self
.clipboard
= self
.model
.doc
.createTextNode(value
)
1407 #print "Clip now", self.clipboard
1409 def yank_attribs(self
, name
):
1410 self
.clipboard
= self
.model
.doc
.createDocumentFragment()
1412 if not self
.get_current().hasAttribute(name
):
1414 attribs
= [self
.get_current().getAttributeNode(name
)]
1417 dict = self
.get_current().attributes
1418 for a
in dict.keys():
1419 attribs
.append(dict[a
])
1421 # Make sure the attributes always come out in the same order
1422 # (helps with macros).
1424 diff
= cmp(a
.name
, b
.name
)
1426 diff
= cmp(a
.namespaceURI
, b
.namespaceURI
)
1429 attribs
.sort(by_name
)
1431 n
= self
.model
.doc
.createElementNS(a
.namespaceURI
, a
.nodeName
)
1432 n
.appendChild(self
.model
.doc
.createTextNode(a
.value
))
1433 self
.clipboard
.appendChild(n
)
1434 #print "Clip now", self.clipboard
1436 def paste_attribs(self
):
1437 if self
.clipboard
.nodeType
== Node
.DOCUMENT_FRAGMENT_NODE
:
1438 attribs
= self
.clipboard
.childNodes
1440 attribs
= [self
.clipboard
]
1444 new
.append((a
.nodeName
, a
.childNodes
[0].data
))
1447 for node
in self
.current_nodes
:
1448 # XXX: Set NS attribs first...
1449 for (name
, value
) in new
:
1450 self
.model
.set_attrib(node
, name
, value
)
1453 "Ensure that all selected nodes have the same value."
1454 if len(self
.current_nodes
) < 2:
1455 raise Beep
# Not enough nodes!
1456 base
= self
.current_nodes
[0]
1457 for n
in self
.current_nodes
[1:]:
1458 if not same(base
, n
):
1459 raise Beep(may_record
= 1)
1462 raise Beep(may_record
= 1)
1467 def fail_if(self
, xpath
):
1468 """Evaluate xpath as a boolean, and fail if true."""
1469 src
= self
.get_current()
1470 ns
= ext
.GetAllNs(src
)
1471 ns
['ext'] = FT_EXT_NAMESPACE
1472 c
= Context
.Context(src
.parentNode
, [src
.parentNode
], processorNss
= ns
)
1474 rt
= XPath
.Evaluate(xpath
, context
= c
)
1477 raise Beep(may_record
= 1)
1479 def attribute(self
, namespace
= None, attrib
= ''):
1480 node
= self
.get_current()
1486 if attrib
== 'xmlns':
1488 #print "(ns, attrib)", `namespace`, attrib
1490 a
= node
.attributes
.get((namespace
, attrib
), None)
1493 self
.move_to(node
, a
)
1495 print "No such attribute"
1498 def set_attrib(self
, value
):
1499 a
= self
.current_attrib
1502 node
= self
.get_current()
1503 a
= self
.model
.set_attrib(node
, a
.name
, value
)
1504 self
.move_to(node
, a
)
1506 def add_attrib(self
, UNUSED
, name
, value
= ''):
1507 node
= self
.get_current()
1508 a
= self
.model
.set_attrib(node
, name
, value
)
1509 self
.move_to(node
, a
)
1511 def load_html(self
, path
):
1512 "Replace root with contents of this HTML file."
1513 print "Reading HTML..."
1514 command
= "tidy -asxml '%s' 2>/dev/null" % path
1516 def done(root
, md5
, self
= self
):
1518 new
= self
.root
.ownerDocument
.importNode(root
.documentElement
, 1)
1521 self
.model
.unlock(self
.root
)
1523 self
.model
.replace_node(self
.root
, new
)
1524 self
.model
.lock(new
)
1526 self
.move_to(self
.root
)
1528 self
.dom_from_command(command
, done
)
1530 def load_xml(self
, path
):
1531 "Replace root with contents of this XML (or Dome) file."
1532 reader
= PyExpat
.Reader()
1533 new_doc
= reader
.fromUri(path
)
1534 self
.load_node(new_doc
.documentElement
)
1536 def load_node(self
, root
):
1537 new
= self
.model
.doc
.importNode(root
, 1)
1539 self
.model
.strip_space(new
)
1542 self
.model
.unlock(self
.root
)
1544 self
.model
.replace_node(self
.root
, new
)
1545 self
.model
.lock(new
)
1547 self
.move_to(self
.root
)
1549 def select_dups(self
):
1550 node
= self
.get_current()
1552 for n
in node
.parentNode
.childNodes
:
1557 self
.move_to(select
)
1559 def select_marked_region(self
, attr
= "unused"):
1561 if len(self
.marked
) != 1:
1562 print "Must be exactly one marked node!"
1564 if len(self
.current_nodes
) != 1:
1565 print "Must be exactly one selected node!"
1568 a
= Path
.path_to(self
.get_current())
1569 b
= Path
.path_to(self
.marked
.keys()[0])
1571 while a
and b
and a
[0] == b
[0]:
1580 for x
in a
.parentNode
.childNodes
:
1587 self
.move_to(select
)
1589 print "One node is a parent of the other!"
1592 def show_html(self
):
1593 from HTML
import HTML
1594 HTML(self
.model
, self
.get_current()).show()
1596 def show_canvas(self
):
1597 from Canvas
import Canvas
1598 Canvas(self
, self
.get_current()).show()
1600 def toggle_hidden(self
):
1601 nodes
= self
.current_nodes
[:]
1604 if node
.hasAttributeNS(None, 'hidden'):
1608 self
.model
.set_attrib(node
, 'hidden', new
, with_update
= 0)
1609 self
.model
.update_all(self
.root
)
1612 def soap_send(self
):
1613 copy
= node_to_xml(self
.get_current())
1614 env
= copy
.documentElement
1616 if env
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/':
1617 support
.report_error("Not a SOAP-ENV:Envelope (bad namespace)")
1619 if env
.localName
!= 'Envelope':
1620 support
.report_error("Not a SOAP-ENV:Envelope (bad local name)")
1623 if len(env
.childNodes
) != 2:
1624 support
.report_error("SOAP-ENV:Envelope must have one header and one body")
1627 kids
= elements(env
)
1631 if head
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1632 head
.localName
!= 'Head':
1633 support
.report_error("First child must be a SOAP-ENV:Head element")
1636 if body
.namespaceURI
!= 'http://schemas.xmlsoap.org/soap/envelope/' or \
1637 body
.localName
!= 'Body':
1638 support
.report_error("Second child must be a SOAP-ENV:Body element")
1642 for header
in elements(head
):
1643 if header
.namespaceURI
== DOME_NS
and header
.localName
== 'soap-forward-to':
1646 print header
.namespaceURI
1647 print header
.localName
1650 support
.report_error("Head must contain a dome:soap-forward-to element")
1653 dest
= sft
.childNodes
[0].data
1654 parent
= sft
.parentNode
1655 if len(elements(parent
)) == 1:
1657 parent
= sft
.parentNode
# Delete the whole header
1658 parent
.removeChild(sft
)
1660 import httplib
, urlparse
1662 (scheme
, addr
, path
, p
, q
, f
) = urlparse
.urlparse(dest
, allow_fragments
= 0)
1663 if scheme
!= 'http':
1664 support
.report_error("SOAP is only supported for 'http:' -- sorry!")
1668 ext
.PrettyPrint(copy
, stream
= stream
)
1669 message
= stream
.data
1671 conn
= httplib
.HTTP(addr
)
1672 conn
.putrequest("POST", path
)
1673 conn
.putheader('Content-Type', 'text/xml; charset="utf-8"')
1674 conn
.putheader('Content-Length', str(len(message
)))
1675 conn
.putheader('SOAPAction', '')
1678 (code
, r_mess
, r_headers
) = conn
.getreply()
1680 reply
= conn
.getfile().read()
1681 print "Got:\n", reply
1683 reader
= PyExpat
.Reader()
1684 new_doc
= reader
.fromString(reply
)
1687 new
= self
.model
.doc
.importNode(new_doc
.documentElement
, 1)
1689 self
.model
.strip_space(new
)
1691 old
= self
.get_current()
1693 self
.model
.replace_node(old
, new
)
1696 def program_changed(self
, changed_op
):
1697 print "Check points..."
1699 (op
, exit
) = self
.rec_point
1701 print "Lost rec_point"
1702 self
.rec_point
= None
1704 (op
, exit
) = self
.exec_point
1706 print "Lost exec_point"
1707 self
.exec_point
= None
1708 for l
in self
.lists
:
1710 self
.status_changed()
1712 def prog_tree_changed(self
):
1715 def export_all(self
):
1716 doc
= implementation
.createDocument(DOME_NS
, 'dome', None)
1717 node
= self
.model
.root_program
.to_xml(doc
)
1718 doc
.documentElement
.appendChild(node
)
1719 node
= doc
.createElementNS(DOME_NS
, 'dome-data')
1720 doc
.documentElement
.appendChild(node
)
1723 print "*** WARNING: Saving from a chroot!"
1725 data
= doc
.importNode(model
.doc
.documentElement
, 1)
1726 node
.appendChild(data
)
1730 def blank_all(self
):
1731 doc
= implementation
.createDocument(None, 'root', None)
1733 self
.clipboard
= self
.model
.doc
.createElementNS(None, 'root')
1736 def mark_switch(self
):
1737 new
= self
.marked
.keys()
1738 self
.set_marked(self
.current_nodes
)
1741 def set_marked(self
, new
):
1742 update
= self
.marked
1743 for x
in self
.marked
.keys():
1744 self
.model
.unlock(x
)
1748 self
.marked
[x
] = None
1750 update
= update
.keys()
1751 for display
in self
.displays
:
1752 display
.marked_changed(update
)
1754 def mark_selection(self
):
1755 self
.set_marked(self
.current_nodes
)
1757 def clear_mark(self
):
1760 def normalise(self
):
1761 self
.model
.normalise(self
.get_current())
1763 def remove_ns(self
):
1764 node
= self
.get_current()
1766 self
.move_to(self
.model
.remove_ns(node
))
1771 def write(self
, str):