# Copyright (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """XmlTree module""" __revision__ = "$Id: XmlTree.py,v 1.15 2004/08/06 12:50:41 syt Exp $" from gtk import * from xml.dom.Event import EventListener, MutationEvent from xml.dom.ext.Printer import utf8_to_code from xml.dom.ext import PrettyPrint try: from xml.xpath import Evaluate except: from Ft.Xml.XPath import Evaluate MULTIPLY = gdk.keyval_from_name('multiply') DIVIDE = gdk.keyval_from_name('divide') # On Windows, gtk requires UTF-8 strings import sys if sys.platform=='win32': def utf8_to_gtk(string): if string is not None: return string.encode('utf-8') else : return '' else: def utf8_to_gtk (string): if string: try: return utf8_to_code(string,'ISO-8859-1') except: print 'error converting string',string return string else: return '' _interestingEvents = [ "DOMNodeInserted", "DOMNodeRemoved", "DOMAttrModified", "DOMCharacterDataModified" ] class InfoGenerator: """Infogenerator""" def __init__(self,patterns=None): self.patterns= patterns or [] self.pattern_change_listener=[] def append_patterns(self,patterns): self.patterns = self.patterns + patterns def generate_info_for_element(self,element_node): for (p,i) in self.patterns: if Evaluate(p,element_node): return Evaluate(i,element_node) return '' class XmlTree(CTree,EventListener) : """XmlTree""" def __init__(self): CTree.__init__(self,3,1,['info','node name','node value']) self.set_expander_style(CTREE_EXPANDER_CIRCULAR) self.set_line_style(CTREE_LINES_DOTTED) self.set_column_auto_resize(0,1) self.set_column_auto_resize(1,1) self.connect('button-press-event',self.clicked) self.connect('key-press-event',self.key_pressed) self.connect('destroy',self.cleanup) self.filter = '.' self.document = None self.info_generator = None self.filtered_nodes = [] self.expand_mode = 0 def set_document(self,document): """sets the document displayed by the widget""" if self.document == document: return if self.document: for type in _interestingEvents: self.document.removeEventListener(type,self,0) self.document=document self.set_filter(self.filter) if document: for type in _interestingEvents: self.document.addEventListener(type,self,0) def set_info_generator(self,generator): self.info_generator = generator def set_filter(self,xpath): """sets the XPath that will serve as a filter on the document""" if self.document: f = Evaluate(xpath,self.document) self.freeze() for n in self.filtered_nodes[:]: if n not in f: self.removeTree(n) self.filtered_nodes.remove(n) for n in f: if n not in self.filtered_nodes[:]: try: next_node = f[f.index(n)+1] for s in self.root_nodes: if self.node_get_row_data(s)==next_node: sibling = s break except: sibling = None self.addTree(n,gtktree_sibling_node=sibling, expand = self.expand_mode) self.thaw() self.filtered_nodes = f self.filter = xpath def set_expand_mode(self,mode): self.expand_mode = mode # # Event Listener implementation # def handleEvent(self,evt): if evt.type == 'DOMNodeRemoved': self.removeTree(evt.target) elif evt.type == 'DOMNodeInserted': found = 0 for b in self.base_nodes(): tree_parent = self.find_by_row_data(b,evt.relatedNode) if tree_parent: if evt.target.nextSibling: tree_sibling = self.find_by_row_data(tree_parent, evt.target.nextSibling) else : tree_sibling = None self.addTree(evt.target, tree_parent, tree_sibling, expand = self.expand_mode) found = 1 break if not found: self.set_filter(self.filter) elif evt.type == 'DOMAttrModified' : if evt.attrChange == evt.MODIFICATION: self.updateTree(evt.relatedNode) elif evt.attrChange == evt.REMOVAL: self.removeTree(evt.relatedNode) elif evt.attrChange == evt.ADDITION: for b in self.base_nodes(): tree_parent = self.find_by_row_data(b,evt.target) if tree_parent: if tree_parent.children: tree_sibling = tree_parent.children[0] else: tree_sibling = None self.addTree(evt.relatedNode, tree_parent,tree_sibling, expand = self.expand_mode) break elif evt.type == 'DOMCharacterDataModified': for b in self.base_nodes(): treenode = self.find_by_row_data(b,evt.target) if treenode: self.node_set_text(treenode,2,evt.target.nodeValue) # # Miscelaneous internal methods # def cleanup(self,*args): self.set_document(None) def key_pressed(self,tree,event): if event.keyval==MULTIPLY: for sel in self.selection: self.expand_recursive(sel) self.emit_stop_by_name('key-press-event') elif event.keyval==DIVIDE: for sel in self.selection: self.collapse_recursive(sel) self.emit_stop_by_name('key-press-event') def clicked(self,tree,event): if event.button == 1 and \ event.type == gdk._2BUTTON_PRESS and \ self.get_selection_info(int(event.x), int(event.y)): row, col = self.get_selection_info(int(event.x), int(event.y)) text = self.get_text(row,2) if text: dlg = Dialog() gtktext = TextView() buf = gtktext.get_buffer() startiter, enditer = buf.get_bounds() buf.insert(startiter, text) #gtktext.insert_text(text) #scroll = ScrolledWindow() #scroll.set_policy(POLICY_AUTOMATIC,POLICY_AUTOMATIC) #scroll.add(gtktext) dlg.vbox.set_spacing(10) dlg.vbox.pack_start(gtktext) butt = Button("Dismiss") butt.set_flags(CAN_DEFAULT|HAS_DEFAULT) butt.connect_object('clicked',lambda d:d.destroy(),dlg) bbox=HButtonBox() bbox.set_layout(BUTTONBOX_SPREAD) dlg.action_area.add(bbox) bbox.add(butt) dlg.set_position(WIN_POS_MOUSE) dlg.show_all() self.emit_stop_by_name('button-press-event') def removeTree(self,domtree): for b in self.base_nodes(): treenode=self.find_by_row_data(b,domtree) if treenode: self.remove_node(treenode) break def addTree(self, node, gtktree_parent_node=None,gtktree_sibling_node=None,expand=0) : name='' if node.nodeType==node.ELEMENT_NODE and self.info_generator: name = self.info_generator.generate_info_for_element(node) elif node.nodeType == node.COMMENT_NODE : name = 'Comment' label=[utf8_to_gtk(name),utf8_to_gtk(node.nodeName),utf8_to_gtk(node.nodeValue)] item = self.insert_node(gtktree_parent_node,gtktree_sibling_node,label, is_leaf=0) self.node_set_row_data(item,node) if node.nodeType!=node.ATTRIBUTE_NODE: if node.attributes or node.childNodes : for att in node.attributes or []: self.addTree(att,item) for child in node.childNodes or []: self.addTree(child,item) if expand: self.expand(item) def updateTree(self,domtree): for b in self.base_nodes(): treenode=self.find_by_row_data(b,domtree) if treenode: parent = treenode.parent if parent: expanded = parent.expanded else: expanded = treenode.expanded sibling = treenode.sibling self.freeze() self.remove_node(treenode) self.addTree(domtree,parent,sibling,expanded) self.thaw() return self.set_filter(self.filter) # test ######################################################################### if __name__=='__main__': from xml.dom.ext.reader import Sax2 window=Window(WINDOW_TOPLEVEL) window.set_title('test XmlTree') window.set_usize(300,300) window.connect('delete_event',lambda x,e:mainquit()) domtree=Sax2.FromXml('textnodeanother textnode') ig = InfoGenerator([('@id','string(@id)'),('@name','string(@name)')]) tree=XmlTree() tree.set_info_generator(ig) window.add(tree) tree.set_document(domtree) window.show_all() mainloop()