#!/usr/bin/python # Buzz Lite # 06/04/02 by Charles Nofsinger # This code is governed by the Gnu Public License # and is provided without any warranty of any kind # by using this code you accept full responsibility for any damage # of any kind import string,sys,os from qt import * from xml.sax import make_parser,handler OUTPUT_ENCODING = 'ISO-8859-1' class ErrorHandler: global BigFatal BigFatal="SGML Syntax Error" global isopml def error(self, exception): 'Handle a recoverable error.' print ('Error: %s\n' % exception) def fatalError(self, exception): 'Handle a non-recoverable error.' print ('Fatal error: %s\n' % exception) raise BigFatal def warning(self, exception): 'Handle a warning.' print ('Warning: %s\n' % exception) class ElementWrapper: def __init__(self, EltType): self.EltType = EltType def __elt__(self): return self.EltType def startsave(self,outfile,newvalue): global bracket_open global lastend if self.EltType == '__Root': return lastend=self.EltType if self.EltType != '__data': if self.EltType == '__attr': attrname=self.xml_escape(newvalue[:newvalue.find('=')]) attrval=self.xml_escape(newvalue[len(attrname)+1:]) outfile.write(' %s=\'%s\' '%(attrname,attrval)) elif self.EltType != '__outline': if bracket_open: outfile.write('>') outfile.write('<%s'%newvalue) bracket_open = 1 else: if bracket_open: outfile.write('>') outfile.write('') bracket_open=0 newvalue = string.replace(newvalue, '&nl;','\n') outfile.write('%s' % self.xml_escape(newvalue,1)) def endsave(self,outfile,newvalue): global lastend global bracket_open if self.EltType == '__Root' or self.EltType == '__attr': return if self.EltType != '__data': if bracket_open: outfile.write('/>') bracket_open=0 lastend='__data' return if lastend != '__data': outfile.write('\n') if self.EltType != '__outline': outfile.write(''%(newvalue)) else: outfile.write('') lastend=self.EltType else: lastend='__data' def xml_escape(self,str,isdata=0): if str: if 1: str = string.replace(str, '&', '&') str = string.replace(str, '<', '<') str = string.replace(str, '>', '>') str = string.replace(str, '"', '"') if not isdata: str = string.replace(str, "'", ''') #str = string.replace(str, '\n', '&nl;') try: u=unicode(str,'latin1') str=u.encode('latin1','replace') except: news='' for i in range(len(str)): try: news=news+str[i].encode('replace') except: news=news+'?' str=news return str else: return '' class GetElements(handler.ContentHandler): def setTree(self, tree,nodeStack): self.tree = tree self.nodeStack=nodeStack self.lasttype="root" def setDocumentLocator(self, locator): 'Receive an mbject for locating the origin of SAX document events.' self.locator = locator def startElement(self, name, attrs): self.lasttype=name if (name == 'outline' or name == 'Outline') and attrs.has_key('text'): title=attrs['text'] title=title.encode('iso8859-1') newnode=title name='__outline' else: name = name.encode('iso8859-1') newnode=name id = TreeViewItem(self.nodeStack[-1],newnode,ElementWrapper(name)) if self.nodeStack[-1]==self.tree: self.tree.root=id self.nodeStack.append(id) for attr in attrs.keys(): if attr != 'text': name= attr+"="+attrs[attr] name=name.encode('iso8859-1') attid = TreeViewItem(id,name,ElementWrapper('__attr')) def endElement(self, name ): self.nodeStack = self.nodeStack[:-1] self.lasttype="__end" def characters(self, data ): global logger istext=0 if not isopml and (self.lasttype=="__data" or self.lasttype=="xsl:text" or self.lasttype=="fo:block"): data=string.replace(data,'\n','&nl;') istext=1 self.lasttype="__data" if istext or string.strip(data): if len(data) > 200: print("Warning character data exceeds display capacity: You can set wrap fields in XML and reopen") data = data.encode('iso8859-1') id=TreeViewItem(self.nodeStack[-1], data,ElementWrapper('__data')) class TreeViewItem(QListViewItem): def __init__(self, parent,label,data=None,prev=-1): global is3 if prev==-1: prev=parent.lastchild() QListViewItem.__init__(self, parent,prev) self.setText(0, label) if is3: self.setRenameEnabled(0,1) self.data = data def setData(self, data): self.data=data def lastchild(self): nodecount=self.childCount() if not nodecount: return None lastchild=self.firstChild() for i in range(nodecount-1): lastchild=lastchild.nextSibling() return lastchild def prev(self): if not self.parent(): return None prevchild=None childcount=self.parent().childCount() if childcount: kid=self.parent().firstChild() for i in range(childcount-1): if kid==self: return prevchild prevchild=kid kid=kid.nextSibling() return None def prevprev(self): if not self.parent(): return None prevchild=None prevprevchild=None childcount=self.parent().childCount() if childcount: kid=self.parent().firstChild() for i in range(childcount-1): if kid==self: return prevprevchild prevprevchild=prevchild prevchild=kid kid=kid.nextSibling() return None def move(self,newparent,newsib): self.parent().takeItem(self) newparent.insertItem(self) newparent.setOpen(1) if newsib: self.moveItem(newsib) self.setSelected(1) class XMLTreeCtrl(QListView): def __init__(self,filename): QListView.__init__(self,None,"") self.setTreeStepSize(10) self.setSorting(-1) self.setRootIsDecorated(1) self.addColumn(filename) self.casesensitive=0 self.lastdelete="" self.lastfile="" self.modified=0 self.lastdeletewrapper="" self.lastfoundroot="" self.currentfindstr="" self.currentfind=0 self.findcount=1 self.filename=filename (self.cwd,foo)=os.path.split(filename) self.root=self self.nodeStack = [self.root] def lastchild(self): return None def LoadTree(self, filename): global isopml from xml.sax import make_parser if not os.path.exists(filename): print("File %s doesn't exist"%filename) return "" filesplit=filename.split('.') filetype=filesplit[len(filesplit)-1] isopml=0 if (filetype.lower() == "opml"): isopml=1 Parser = make_parser("expat") DocHandler=GetElements() DocHandler.setTree(self,self.nodeStack) Parser.setErrorHandler(ErrorHandler()) Parser.setContentHandler(DocHandler) ParserStatus = Parser.parse(filename) del Parser return filetype def keyPressEvent(self,event): global homefile global window global bracket_open global is3 selection=self.selectedItem() if event.key()==Qt.Key_T or event.key()==Qt.Key_Space: if selection.isOpen(): selection.setOpen(0) else: selection.setOpen(1) elif event.key()==Qt.Key_E or event.key()==Qt.Key_Return: if selection: if is3: selection.startRename(0) else: dlg = SimpleTextEntry('Edit', 'Edit', str(selection.text(0)), self, None, 1) if dlg.exec_loop() == QDialog.Accepted: text = dlg.text selection.setText(0,str(text)) elif event.key()==Qt.Key_N or event.key() == Qt.Key_F6: if selection: selection = TreeViewItem(selection.parent(),'',ElementWrapper("__outline"),selection) selection.setSelected(1) if is3: selection.startRename(0) else: dlg = SimpleTextEntry('Edit', 'Edit', str(selection.text(0)), self, None, 1) if dlg.exec_loop() == QDialog.Accepted: text = dlg.text selection.setText(0,str(text)) elif event.key()==Qt.Key_C or event.key()==Qt.Key_F7: if selection: parent=selection selection = TreeViewItem(selection,'',ElementWrapper('__outline')) parent.setOpen(1) selection.setSelected(1) if is3: selection.startRename(0) else: dlg = SimpleTextEntry('Edit', 'Edit', str(selection.text(0)), self, None, 1) if dlg.exec_loop() == QDialog.Accepted: text = dlg.text selection.setText(0,str(text)) elif event.key()==Qt.Key_P: if selection and self.lastdelete: selection = TreeViewItem(selection,self.lastdelete,self.lastdeletewrapper) selection.setSelected(1) elif event.key()==Qt.Key_U: if selection: self.modified=1 selection = selection.moveItem(selection.prevprev()) elif event.key()==Qt.Key_D: if selection: selection.moveItem(selection.nextSibling()) self.modified=1 elif event.key()==Qt.Key_L: if selection and selection.parent() and selection.parent().parent(): selection.move(selection.parent().parent(),selection.parent()) self.modified=1 elif event.key()==Qt.Key_R: if selection: childprev=selection.prev() if childprev: selection.move(childprev,None) self.modified=1 elif event.key()==Qt.Key_I: self.repeat_find() elif event.key()==Qt.Key_X: if selection: self.lastdelete=str(selection.text(0)) self.lastdeletewrapper=selection.data selection.delete() elif event.key()==Qt.Key_S: bracket_open=0 outfile=open(self.filename,'w') outfile.write("") self.SaveModel(self.root,outfile,0) self.modified=0 self.message("Saved!") elif event.key()==Qt.Key_A: self.save_as() elif event.key()==Qt.Key_F: self.finding=self.root dlg = SimpleTextEntry('Find', 'Enter key words', self.currentfindstr, self, None, 1) if dlg.exec_loop() == QDialog.Accepted: text = dlg.text if text: findstr=text if not self.casesensitive: findstr=findstr.lower() if self.currentfindstr==findstr and self.lastfoundroot: findroot=self.lastfoundroot self.findcount=self.findcount+1 else: findroot=self.finding self.findcount=1 self.currentfindstr=findstr self.currentfind=0 founditem=self.FindInModel(findroot,self.currentfindstr,0,self.casesensitive) if founditem: self.ensureItemVisible(founditem) founditem.setSelected(1) self.lastfoundroot=findroot else: self.message("Not Found!") self.lastfoundroot=None self.findcount=1 elif event.key()==Qt.Key_M: self.modified=0 self.message("Modified status Cleared!") elif event.key()==Qt.Key_H: if self.modified: self.message("File was Modified!") return self.open_file(homefile) elif event.key()==Qt.Key_B: if self.modified: self.message("File was Modified!") return if self.lastfile and os.path.isfile(self.lastfile): self.open_file(self.lastfile) elif event.key()==Qt.Key_O: if self.modified: self.message("File was Modified") return url=str(selection.text(0)) if url[0:4] != "url=" and selection.childCount(): kid=selection.firstChild() for i in range(selection.childCount()): url=str(kid.text(0)) if url[0:4] == "url=": selection=kid break kid=kid.nextSibling() urloffset=url.find('=')+1 if urloffset: url=url[urloffset:] (foo,extension)=os.path.splitext(url) (self.cwd,foo)=os.path.split(self.filename) if (extension == ".opml" or extension == ".OPML"): filename=os.path.join(self.cwd,url) self.open_file(filename) return else: if (url.find(":") == -1) and not os.path.exists(url): url="FILE:"+os.path.join(self.cwd,url) return os.system("dillo "+url+' &') else: self.file_open() else: QListView.keyPressEvent(self, event) def message(self,message): QMessageBox.warning(self, 'Buzz',message) def open_file(self,filename): self.lastfile=self.filename self.filename=filename self.clear() self.root=self self.nodeStack = [self.root] self.LoadTree(filename) self.setColumnText(0,self.filename) self.expand_body() return def file_open(self): filename = str(QFileDialog.getOpenFileName(None, 'OPML Files (*.opml);;All Files (*)', self)) if filename: if os.path.isfile(filename): self.open_file(filename) else: self.message("Not a file!") return def save_as(self): global bracket_open bracket_open=0 filename = str(QFileDialog.getSaveFileName(None, 'OPML Files (*.opml *.*);;All Files (*)', self)) if filename: outfile=open(filename,'w') if outfile: try: outfile.write("") self.SaveModel(self.root,outfile,0) self.modified=0 self.message("Saved!") except: self.message("%s NOT Saved"%filename) else: self.message("Could not write") def repeat_find(self): if self.currentfindstr and self.lastfoundroot: findroot=self.lastfoundroot self.findcount=self.findcount+1 self.currentfind=0 founditem=self.FindInModel(findroot,self.currentfindstr,0,self.casesensitive) if founditem: self.ensureItemVisible(founditem) founditem.setSelected(1) self.lastfoundroot=findroot else: self.message("Not Found!") self.lastfouldroot=None self.findcount=1 def SaveModel(self,item,outfile,depth): global bracket_open data_node=item.data newvalue=str(item.text(0)) data_node.startsave(outfile,newvalue) cookie=depth childcount=item.childCount() if childcount: kid=item.firstChild() for i in range(childcount): self.SaveModel(kid,outfile,depth+1) kid=kid.nextSibling() data_node.endsave(outfile,newvalue) def expand_to_root(self,item): item.setOpen(1) if item.parent: self.expand_to_root(item.parent) def expand_body(self): self.root.setOpen(1) if self.root.childCount(): self.root.lastchild().setOpen(1) def FindInModel(self,item,needle,depth,casesensitive=0): newvalue=str(item.text(0)) cookie=depth if not casesensitive: found=newvalue.lower().find(needle) else: found=newvalue.find(needle) if found != - 1: self.currentfind=self.currentfind+1 if self.currentfind==self.findcount: return(item) if item.childCount(): kid=item.firstChild() for i in range(item.childCount()): found=self.FindInModel(kid,needle,cookie,casesensitive) if found: return(found) kid=kid.nextSibling() return(None) class SimpleTextEntry(QDialog): "Dialog for find string text entry" def __init__(self, caption, label, dfltText='', parent=None, name=None, modal=0, flags=0): QDialog.__init__(self, parent, name, modal, flags) self.text = '' if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) label = QLabel(label, self) topLayout.addWidget(label) self.entry = QLineEdit(dfltText, self) topLayout.addWidget(self.entry) self.entry.setFocus() self.connect(self.entry, SIGNAL('returnPressed()'), self, SLOT('accept()')) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton('OK', self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton('Cancel', self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) def accept(self): "Set text string before closing" self.text = unicode(self.entry.text()).strip() return QDialog.accept(self) app = QApplication(sys.argv) global is3 is3=0 if qVersion()[0] >= '3': is3 =1 if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): xml=XMLTreeCtrl(sys.argv[1]) app.setMainWidget(xml) homefile=sys.argv[1] xml.LoadTree(sys.argv[1]) xml.expand_body() xml.show() app.exec_loop() else: print "Could not open"