The use of signals and slots in the previous section was an example of using signals and slots in GUI building. Of course, you can use signals and slots to link GUI widgets with each other, and most of your slot implementations will be in subclasses of QWidget — but the mechanism works well under other circumstances. A GUI is not necessary.
In this section, I will show how signals and slots make a natural extension to the event driven nature of XML parsers. As you probably know, XML is a fairly simple mark-up language that can be used to represent hierarchical data. There are basically two ways to look at XML data. One is to convert the data in one fell swoop into some hierarchical representation (for example, dictionaries containing dictionaries). This method is the DOM (data-object-model) representation. Alternatively, you can parse the data character by character, generating an event every time a certain chunk has been completed; this is the SAX parser model.
Python contains support for both XML handling models in its standard libraries. The currently appreciated module is xml.sax, which can make use of the fast expat parser. However, expat is not part of standard Python. There is an older, deprecated module, xmllib, which uses regular expressions for parsing. While deprecated, this module is still the most convenient introduction to XML handling with Python. It's also far more ‘Pythonic' in feel than the Sax module, which is based on the way Java does things.
We'll create a special module that will use xmllib to parse an XML document and generate PyQt signals for all elements of that document. It is easy to connect these signals to another object (for instance, a PyQt QListView which can show the XML document in a treeview). But it would be just as easy to create a formatter object that would present the data as HTML. A slightly more complicated task would be to create a formatter object that would apply XSLT transformations to the XML document — that is, it would format the XML using stylesheets. Using signals and slots, you can connect more than one transformation to the same run of the parser. A good example would be a combination of a GUI interface, a validator, and a statistics calculator.
The next example is very simple. It is easy to extend, though, with special nodes for comments, a warning message box for errors, and more columns for attributes.
Example 7-9. An XML parser with signals and slots
# # qtparser.py — a simple parser that, using xmllib, # generates a signal for every parsed XML document. # import sys import xmllibfrom qt import * TRUE=1
FALSE=0
class Parser(xmllib.XMLParser):def __init__(self, qObject, *args):
xmllib.XMLParser.__init__(self) self.qObject=qObject def start(self, document):
xmllib.XMLParser.feed(self, document) xmllib.XMLParser.close(self)
An example for a Designer ui file could contain the following definition:
self.elements={'widget' : (self.start_widget, self.end_widget) ,'class' : (self.start_class, self.end_class) ,'property': (self.start_property, self.end_property) ,name' : (self.start_name, self.end_name)}
The keys to this dictionary are the actual tag strings. The tuple that follows the key consists of the functions that should be called for the opening and the ending tag. If you don't want a function to be called, enter None. Of course, you must implement these functions yourself, in the derived parser class.
# # Data handling functions# def handle_xml(self, encoding, standalone):
self.qObject.emit(PYSIGNAL("sigXML"), (encoding, standalone))
def handle_doctype(self, tag, pubid, syslit, data): self.qObject.emit(PYSIGNAL("sigDocType"), (tag, pubid, syslit, data,))
def handle_data(self, data): self.qObject.emit(PYSIGNAL("sigData"),(data,))
def handle_charref(self, ref): self.qObject.emit(PYSIGNAL("sigCharref"),(ref,))
def handle_comment(self, comment): self.qObject.emit(PYSIGNAL("sigComment"),(comment,))
def handle_cdata(self, data): self.qObject.emit(PYSIGNAL("sigCData"),(data,))
def handle_proc(self, data): self.qObject.emit(PYSIGNAL("sigProcessingInstruction"),
(data,)) def handle_special(self, data):
self.qObject.emit(PYSIGNAL("sigSpecial"), (data,)) def syntax_error(self, message): (11) self.qObject.emit(PYSIGNAL("sigError"),(message,)) def unknown_starttag(self, tag, attributes): (12) self.qObject.emit(PYSIGNAL("sigStartTag"), (tag,attributes)) (13) def unknown_endtag(self, tag): self.qObject.emit(PYSIGNAL("sigEndTag"),(tag,)) (14) def unknown_charref(self, ref): self.qObject.emit(PYSIGNAL("sigCharRef"),(ref,)) def unknown_entityref(self, ref): self.qObject.emit(PYSIGNAL("sigEntityRef"),(ref,))
<!DOCTYPE book PUBLIC "-//Norman Walsh//DTD DocBk XML V3.1.4//EN" "http://nwalsh.com/docbook/xml/3.1.4/db3xml.dtd">
and points to a DTD — a description of what's allowed in this particular kind of XML document.
<![CDATA[surely you will be allowed to starve to death in one of the royal parks.]]>
will present the quote ‘surely you will be allowed to starve to death in one of the royal parks.' to any slot that is connected to sigCData.
The TreeView class will show the contents of the XML file.
class TreeView(QListView):def __init__(self, *args): apply(QListView.__init__,(self, ) + args) self.stack=[]
self.setRootIsDecorated(TRUE)
self.addColumn("Element")
def startDocument(self, tag, pubid, syslit, data):
i=QListViewItem(self) if tag == None: tag = "None" i.setText(0, tag) self.stack.append(i) def startElement(self, tag, attributes):
if tag == None: tag = "None" i=QListViewItem(self.stack[-1]) i.setText(0, tag) self.stack.append(i) def endElement(self, tag):
del(self.stack[-1])
def main(args): if (len(args) == 2): app = QApplication(sys.argv) QObject.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) w = TreeView() app.setMainWidget(w) o=QObject()p=Parser(o)
QObject.connect(o, PYSIGNAL("sigDocType"),
w.startDocument) QObject.connect(o, PYSIGNAL("sigStartTag"), w.startElement) QObject.connect(o, PYSIGNAL("sigEndTag"), w.endElement) s=open(args[1]).read()
p.start(s) w.show() app.exec_loop() else: print "Usage: python qtparser.py FILE.xml" if __name__=="__main__": main(sys.argv)
This is a very simple and convenient way of working with XML files and PyQt gui's — but it's generally useful, too. The standard way of working with XML files and parsers allows for only one function to be called for each tag. Using signals and slots, you can have as many slots connected to each signal as you want. For instance, you can have not only a gui, but also an analyzer that produces statistics listening in on the same parsing run.
The result of parsing a Designer .ui file.
On a final note, there is one bug in this code... See if you can find it, or consult the Section called QListView and QListViewItem in Chapter 10 for an explanation.