home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / doc / diveintopython / examples / kgp / kgp.py < prev    next >
Encoding:
Python Source  |  2004-05-05  |  8.5 KB  |  252 lines

  1. #!/usr/bin/env python2
  2. """Kant Generator for Python
  3.  
  4. Generates mock philosophy based on a context-free grammar
  5.  
  6. Usage: python kgp.py [options] [source]
  7.  
  8. Options:
  9.   -g ..., --grammar=...   use specified grammar file or URL
  10.   -h, --help              show this help
  11.   -d                      show debugging information while parsing
  12.  
  13. Examples:
  14.   kgp.py                  generates several paragraphs of Kantian philosophy
  15.   kgp.py -g husserl.xml   generates several paragraphs of Husserl
  16.   kpg.py "<xref id='paragraph'/>"  generates a paragraph of Kant
  17.   kgp.py template.xml     reads from template.xml to decide what to generate
  18.  
  19. This program is part of "Dive Into Python", a free Python book for
  20. experienced programmers.  Visit http://diveintopython.org/ for the
  21. latest version.
  22. """
  23.  
  24. __author__ = "Mark Pilgrim (mark@diveintopython.org)"
  25. __version__ = "$Revision: 1.4 $"
  26. __date__ = "$Date: 2004/05/05 21:57:19 $"
  27. __copyright__ = "Copyright (c) 2001 Mark Pilgrim"
  28. __license__ = "Python"
  29.  
  30. from xml.dom import minidom
  31. import random
  32. import toolbox
  33. import sys
  34. import getopt
  35.  
  36. _debug = 0
  37.  
  38. class NoSourceError(Exception): pass
  39.  
  40. class KantGenerator:
  41.     """generates mock philosophy based on a context-free grammar"""
  42.     
  43.     def __init__(self, grammar, source=None):
  44.         self.loadGrammar(grammar)
  45.         self.loadSource(source and source or self.getDefaultSource())
  46.         self.refresh()
  47.  
  48.     def _load(self, source):
  49.         """load XML input source, return parsed XML document
  50.  
  51.         - a URL of a remote XML file ("http://diveintopython.org/kant.xml")
  52.         - a filename of a local XML file ("~/diveintopython/common/py/kant.xml")
  53.         - standard input ("-")
  54.         - the actual XML document, as a string
  55.         """
  56.         sock = toolbox.openAnything(source)
  57.         xmldoc = minidom.parse(sock).documentElement
  58.         sock.close()
  59.         return xmldoc
  60.  
  61.     def loadGrammar(self, grammar):
  62.         """load context-free grammar"""
  63.         self.grammar = self._load(grammar)
  64.         self.refs = {}
  65.         for ref in self.grammar.getElementsByTagName("ref"):
  66.             self.refs[ref.attributes["id"].value] = ref
  67.         
  68.     def loadSource(self, source):
  69.         """load source"""
  70.         self.source = self._load(source)
  71.  
  72.     def getDefaultSource(self):
  73.         """guess default source of the current grammar
  74.         
  75.         The default source will be one of the <ref>s that is not
  76.         cross-referenced.  This sounds complicated but it's not.
  77.         Example: The default source for kant.xml is
  78.         "<xref id='section'/>", because 'section' is the one <ref>
  79.         that is not <xref>'d anywhere in the grammar.
  80.         In most grammars, the default source will produce the
  81.         longest (and most interesting) output.
  82.         """
  83.         xrefs = {}
  84.         for xref in self.grammar.getElementsByTagName("xref"):
  85.             xrefs[xref.attributes["id"].value] = 1
  86.         xrefs = xrefs.keys()
  87.         standaloneXrefs = [e for e in self.refs.keys() if e not in xrefs]
  88.         if not standaloneXrefs:
  89.             raise NoSourceError, "can't guess source, and no source specified"
  90.         return '<xref id="%s"/>' % random.choice(standaloneXrefs)
  91.         
  92.     def reset(self):
  93.         """reset parser"""
  94.         self.pieces = []
  95.         self.capitalizeNextWord = 0
  96.  
  97.     def refresh(self):
  98.         """reset output buffer, re-parse entire source file, and return output
  99.         
  100.         Since parsing involves a good deal of randomness, this is an
  101.         easy way to get new output without having to reload a grammar file
  102.         each time.
  103.         """
  104.         self.reset()
  105.         self.parse(self.source)
  106.         return self.output()
  107.  
  108.     def output(self):
  109.         """output generated text"""
  110.         return "".join(self.pieces)
  111.  
  112.     def randomChildElement(self, node):
  113.         """choose a random child element of a node
  114.         
  115.         This is a utility method used by do_xref and do_choice.
  116.         """
  117.         choices = [e for e in node.childNodes
  118.                    if e.nodeType == e.ELEMENT_NODE]
  119.         chosen = random.choice(choices)
  120.         if _debug:
  121.             sys.stderr.write('%s available choices: %s\n' % \
  122.                 (len(choices), [e.toxml() for e in choices]))
  123.             sys.stderr.write('Chosen: %s\n' % chosen.toxml())
  124.         return chosen
  125.  
  126.     def parse(self, node):
  127.         """parse a single XML node
  128.         
  129.         A parsed XML document (from minidom.parse) is a tree of nodes
  130.         of various types.  Each node is represented by an instance of the
  131.         corresponding Python class (Element for a tag, Text for
  132.         text data, Document for the top-level document).  The following
  133.         statement constructs the name of a class method based on the type
  134.         of node we're parsing ("parse_Element" for an Element node,
  135.         "parse_Text" for a Text node, etc.) and then calls the method.
  136.         """
  137.         parseMethod = getattr(self, "parse_%s" % node.__class__.__name__)
  138.         parseMethod(node)
  139.  
  140.     def parse_Document(self, node):
  141.         """parse the document node
  142.         
  143.         The document node by itself isn't interesting (to us), but
  144.         its only child, node.documentElement, is: it's the root node
  145.         of the grammar.
  146.         """
  147.         self.parse(node.documentElement)
  148.  
  149.     def parse_Text(self, node):
  150.         """parse a text node
  151.         
  152.         The text of a text node is usually added to the output buffer
  153.         verbatim.  The one exception is that <p class='sentence'> sets
  154.         a flag to capitalize the first letter of the next word.  If
  155.         that flag is set, we capitalize the text and reset the flag.
  156.         """
  157.         text = node.data
  158.         if self.capitalizeNextWord:
  159.             self.pieces.append(text[0].upper())
  160.             self.pieces.append(text[1:])
  161.             self.capitalizeNextWord = 0
  162.         else:
  163.             self.pieces.append(text)
  164.  
  165.     def parse_Element(self, node):
  166.         """parse an element
  167.         
  168.         An XML element corresponds to an actual tag in the source:
  169.         <xref id='...'>, <p chance='...'>, <choice>, etc.
  170.         Each element type is handled in its own method.  Like we did in
  171.         parse(), we construct a method name based on the name of the
  172.         element ("do_xref" for an <xref> tag, etc.) and
  173.         call the method.
  174.         """
  175.         handlerMethod = getattr(self, "do_%s" % node.tagName)
  176.         handlerMethod(node)
  177.  
  178.     def parse_Comment(self, node):
  179.         """parse a comment
  180.         
  181.         The grammar can contain XML comments, but we ignore them
  182.         """
  183.         pass
  184.     
  185.     def do_xref(self, node):
  186.         """handle <xref id='...'> tag
  187.         
  188.         An <xref id='...'> tag is a cross-reference to a <ref id='...'>
  189.         tag.  <xref id='sentence'/> evaluates to a randomly chosen child of
  190.         <ref id='sentence'>.
  191.         """
  192.         id = node.attributes["id"].value
  193.         self.parse(self.randomChildElement(self.refs[id]))
  194.  
  195.     def do_p(self, node):
  196.         """handle <p> tag
  197.         
  198.         The <p> tag is the core of the grammar.  It can contain almost
  199.         anything: freeform text, <choice> tags, <xref> tags, even other
  200.         <p> tags.  If a "class='sentence'" attribute is found, a flag
  201.         is set and the next word will be capitalized.  If a "chance='X'"
  202.         attribute is found, there is an X% chance that the tag will be
  203.         evaluated (and therefore a (100-X)% chance that it will be
  204.         completely ignored)
  205.         """
  206.         keys = node.attributes.keys()
  207.         if "class" in keys:
  208.             if node.attributes["class"].value == "sentence":
  209.                 self.capitalizeNextWord = 1
  210.         if "chance" in keys:
  211.             chance = int(node.attributes["chance"].value)
  212.             doit = (chance > random.randrange(100))
  213.         else:
  214.             doit = 1
  215.         if doit:
  216.             for child in node.childNodes: self.parse(child)
  217.  
  218.     def do_choice(self, node):
  219.         """handle <choice> tag
  220.         
  221.         A <choice> tag contains one or more <p> tags.  One <p> tag
  222.         is chosen at random and evaluated; the rest are ignored.
  223.         """
  224.         self.parse(self.randomChildElement(node))
  225.  
  226. def usage():
  227.     print __doc__
  228.  
  229. def main(argv):
  230.     grammar = "kant.xml"
  231.     try:
  232.         opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="])
  233.     except getopt.GetoptError:
  234.         usage()
  235.         sys.exit(2)
  236.     for opt, arg in opts:
  237.         if opt in ("-h", "--help"):
  238.             usage()
  239.             sys.exit()
  240.         elif opt == '-d':
  241.             global _debug
  242.             _debug = 1
  243.         elif opt in ("-g", "--grammar"):
  244.             grammar = arg
  245.     
  246.     source = "".join(args)
  247.     k = KantGenerator(grammar, source)
  248.     print k.output()
  249.  
  250. if __name__ == "__main__":
  251.     main(sys.argv[1:])
  252.