5.13. Putting it all together

We’ve covered a lot of ground. Let’s step back and see how all the pieces fit together.

To start with, this is a script that takes its arguments on the command line, using the getopt module.

def main(argv):                         
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
    except getopt.GetoptError:          
    for opt, arg in opts:               

We create a new instance of the KantGenerator class, and pass it the grammar file and source that may or may not have been specified on the command line.

    k = KantGenerator(grammar, source)

The KantGenerator instance automatically loads the grammar, which is an XML file. We use our custom openAnything function to open the file (which could be stored in a local file or a remote web server), then use the built-in minidom parsing functions to parse the XML into a tree of Python objects.

    def _load(self, source):
        sock = toolbox.openAnything(source)
        xmldoc = minidom.parse(sock).documentElement

Oh, and along the way, we take advantage of our knowledge of the structure of the XML document to set up a little cache of references, which are just elements in the XML document.

    def loadGrammar(self, grammar):                         
        for ref in self.grammar.getElementsByTagName("ref"):
            self.refs[ref.attributes["id"].value] = ref     

If we specified some source material on the command line, we use that; otherwise we rip through the grammar looking for the "top-level" reference (that isn’t referenced by anything else) and use that as a starting point.

    def getDefaultSource(self):
        xrefs = {}
        for xref in self.grammar.getElementsByTagName("xref"):
            xrefs[xref.attributes["id"].value] = 1
        xrefs = xrefs.keys()
        standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs]
        return '<xref id="%s"/>' % random.choice(standaloneXrefs)

Now we rip through our source material. The source material is also XML, and we parse it one node at a time. To keep our code separated and more maintainable, we use separate handlers for each node type.

    def parse_Element(self, node): 
        handlerMethod = getattr(self, "do_%s" % node.tagName)

We bounce through the grammar, parsing all the children of each p element,

    def do_p(self, node):
        if doit:
            for child in node.childNodes: self.parse(child)

replacing choice elements with a random child,

    def do_choice(self, node):

and replacing xref elements with a random child of the corresponding ref element, which we previously cached.

    def do_xref(self, node):
        id = node.attributes["id"].value

Eventually, we parse our way down to plain text,

    def parse_Text(self, node):    
        text = node.data

which we print out.

def main(argv):                         
    k = KantGenerator(grammar, source)
    print k.output()