Commits

Robert Brewer  committed f2d51ea

Initial import from 2.1.0a2 in CVS, with some re-org to push py code down a level

  • Participants

Comments (0)

Files changed (118)

+include MANIFEST.in
+include license.txt
+include setup.py
+recursive-include . *.py
+recursive-include stt *
+recursive-include doc *.html
+recursive-include doc *.css
+prune examples/html.py
+prune examples/py*
+prune examples/rtf*
+global-exclude *CVS*
+global-exclude *Cvs*
+global-exclude *CVS*
+global-exclude *cvs*
+global-exclude *.scc
+global-exclude *.pyc
+global-exclude *.pyo
+global-exclude *.gz
+global-exclude *.zip
+global-exclude *.bat
+global-exclude *.exe
+global-exclude *.sxw
+global-exclude *.so
+global-exclude *.pyd

File doc/common_problems.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<html>
+<head>
+      	               
+  <meta http-equiv="CONTENT-TYPE"
+ content="text/html; charset=windows-1252">
+  <title>Common Problems with SimpleParse 2.0</title>
+                     	 	               
+  <meta name="GENERATOR" content="OpenOffice.org 641  (Win32)">
+      	               
+  <meta name="AUTHOR" content="Mike Fletcher">
+      	               
+  <meta name="CREATED" content="20020705;15224300">
+      	               
+  <meta name="CHANGEDBY" content="Mike Fletcher">
+      	               
+  <meta name="CHANGED" content="20020706;20211800">
+      	               
+  <link rel="stylesheet" type="text/css" href="sitestyle.css">
+                   
+  <meta name="author" content="Mike C. Fletcher">
+</head>
+  <body lang="en-US">
+           
+<h1>Common Problems</h1>
+           
+<p>Describes common errors, anti-patterns and known bugs with the SimpleParse 
+  2.0 engine.</p>
+           
+<h2>Repetition-as-Recursion</h2>
+           
+<p>Is <b>extremely inefficient</b>, it generates 4 new Python objects and 
+  a number of new object pointers for every match (figure &gt; 100 bytes for
+  each match), on top of the engine overhead in tracking the recursion, so
+ if you have a 1-million character match that's �matching� for every character, 
+  you'll have hundreds of megabytes of memory used.</p>
+           
+<p>In addition, if you are not using the non-recursive rewrite of mx.TextTools, 
+  you can actually blow up the C stack with the recursive calls to tag().
+�Symptoms of this are a memory access error when attempting to parse.</p>
+           
+<pre><font color="#ff0000"><font size="2" style="font-size: 11pt;"><span
+ style="background: transparent none repeat scroll 50% 0%;">a := 'b', a? # bad!</span></font></font><br><font
+ color="#0000ff"><span
+ style="background: transparent none repeat scroll 50% 0%;">a := 'b'+ # good!</span></font></pre>
+         
+<h2> Null-match Children of Repeating Groups</h2>
+           
+<p>At present, there's no way for the engine to know whether a child has been
+satisfied (matched) because they are optional (or all of their children are
+optional), or because they actually matched.  The problem with the obvious 
+  solution of just checking whether we've moved forward in the text is that 
+  many classes of match object may match depending on external (non-text-based)
+   conditions, so if we do the check, all of those mechanisms suddenly fail. 
+   For now, make sure:</p>
+           
+<ul>
+      	<li>                         
+    <p><b>No child</b> of a 	<b>repeating FirstOfGroup</b> (x/y/z)+ or (x/y/z)* 
+  can <b>match a 	Null-string</b></p>
+      	</li>
+       <li>                         
+    <p><b>At least one child </b>	of a <b>repeating SequentialGroup </b>(x,y,z)+ 
+  or (x,y,z)* <b>must not 	match</b> the Null-string</p>
+      </li>
+         
+</ul>
+           
+<p>You can recognize this situation by the process going into an endless loop
+with little or no memory being consumed. �To fix this one, I'd likely need
+to add another return value type to the mxTextTools engine.</p>
+           
+<h2>No Backtracking</h2>
+           
+<p>The TextTools engine does not support backtracking as seen in RE engines 
+  and many parsers, so productions like this can never match:</p>
+           
+<pre><font color="#ff0000">a := (b/c)*, c</font></pre>
+         
+<p> Because the 'c' productions will all have been consumed by the FirstOfGroup, 
+  so the last 'c' can never match.  This is a fundamental limit of the current 
+  back-end, so unless a new back-end is created, the problem will not go away.
+   You will need to design your grammars accordingly.</p>
+           
+<h2>First-Of, not Longest-Of (Meaning of / )</h2>
+           
+<p>The production c := (a/b) produces a FirstOfGroup, that is, a group which 
+  <b>matches the first child to match</b>.  Many parsers and regex engines 
+ use an algorithm that matches all children and chooses the longest successful 
+  match.  It would be possible to define a new TextTools tagging command to
+  support  the longest-of semantics for Table/SubTable matches, but I haven't
+  felt the need to do so.  If such a command is created, it will likely be
+ spelled '|' rather than '/' in the SimpleParse grammar.<br>
+</p>
+<h2>Grouping Rules</h2>
+<p>Although not particularly likely, users of SimpleParse 1.0 may have relied
+on the (extremely non-intuitive) grouping mechanism for element tokens in
+their grammars. �With that mechanism, the group:<br>
+</p>
+<pre>a,b,c/d,e<br></pre>
+<p>was interpreted as:<br>
+</p>
+<pre>a,b,(c/(d,e))<br></pre>
+<p>The new rule is simply that alternation binds closer than sequences, so
+the same grammar becomes:<br>
+</p>
+<pre>a,b,(c/d),e<br></pre>
+<p>which, though no more (or less) intuitive than:<br>
+</p>
+<pre>(a,b,c)/(d,e) ### it doesn't work this way!!!<br></pre>
+<p>is certainly better than the original mechanism.<br>
+</p>
+ 
+<h2>mxTextTools Versions</h2>
+ 
+<p>You will, if possible, want to use the non-recursive rewrite of the 2.1.0
+mxTextTools engine (2.1.0nr). �At the time of writing, the mainline 2.1.0b3
+has some errors (which I'm told are fixed for 2.1.0final), while the non-recursive 
+rewrite passes all tests. �The bugs in the (recursive) engine(s) that are
+known (and not likely to be fixed in the case of 2.1.0 final) are:<br>
+ </p>
+ 
+<ul>
+   <li>Recursive data structure returns from reported subtable matches (not 
+a problem for SimpleParse, we never report a subtable match. [2.0.3 only]</li>
+   <li>Failure to truncate/report position for a failed table correctly (may 
+confuse your code, as the results from failed matches may show up in your 
+tag-lists�[2.0.3 only]</li>
+   <li>AppendToTag only supports list objects with�[2.0.3 only]</li>
+   <li>C Stack overflow with recursive calls [2.0.3 AND 2.1.0 (recursive)]<br>
+   </li>
+ 
+</ul>
+ <a href="index.html">Up to index...</a><br>
+         
+<p align="center">A <a href="http://sourceforge.net"> <img
+ src="http://sourceforge.net/sflogo.php?group_id=55673&amp;type=5"
+ width="210" height="62" border="0" alt="SourceForge Logo">
+   </a><br>
+   Open Source <a href="http://simpleparse.sourceforge.net/">project</a><br>
+ </p>
+ <br>
+</body>
+</html>

File doc/index.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <meta content="en-us" http-equiv="Content-Language">
+  <meta content="text/html; charset=windows-1252"
+ http-equiv="Content-Type">
+  <meta content="Microsoft FrontPage 4.0" name="GENERATOR">
+  <meta content="FrontPage.Editor.Document" name="ProgId">
+  <title>SimpleParse 2.1</title>
+  <link href="sitestyle.css" type="text/css" rel="stylesheet">
+  <meta content="Mike C. Fletcher" name="author">
+</head>
+<body>
+<h1>SimpleParse <font size="-2">A Parser Generator for mxTextTools
+v2.1.0</font></h1>
+<p>SimpleParse is a <a href="#License">BSD-licensed</a> Python package
+providing a simple and fast parser generator using a modified version
+of the <a href="http://www.lemburg.com/files/python/mxTextTools.html">mxTextTools</a>
+text-tagging engine. SimpleParse allows you to generate parsers
+directly from your
+EBNF grammar.<br>
+</p>
+<p>Unlike most parser generators, SimpleParse generates single-pass
+parsers (there is no distinct tokenization stage), an
+approach taken from the predecessor project (mcf.pars) which
+attempted to create "autonomously parsing regex objects". The resulting
+parsers are not as generalized as those created by,
+for instance, the Earley algorithm, but they do tend to be useful for
+the parsing of computer file formats and the like (as distinct from
+natural language and similar "hard" parsing problems).</p>
+<p>As of version 2.1.0 the SimpleParse project includes a patched copy
+of the mxTextTools tagging library with the non-recursive rewrite of
+the core parsing loop.&nbsp; This means that you will need to build the
+extension module to use SimpleParse, but the effect is to provide a
+uniform parsing platform where all of the features of a give
+SimpleParse version are always available.<br>
+</p>
+<p>For those interested in working on the project, I'm actively
+interested in welcoming and supporting both new developers
+and new users. Feel free to <a href="http://www.vrplumber.com/">contact
+me</a>.</p>
+<h2>Documentation</h2>
+<ul>
+  <li><a href="scanning_with_simpleparse.html">Scanning with SimpleParse</a>
+-- describes the process of creating a Parser object with your EBNF
+grammar, and using that parser to scan input texts</li>
+  <li><a href="simpleparse_grammars.html">SimpleParse Grammars</a> --
+reference to the various features of the default SimpleParse EBNF
+grammar variant</li>
+  <li><a href="processing_result_trees.html">Processing Result Trees</a>
+-- brief description of the results of the tagging/scanning process and
+the features available for processing (and altering) those results</li>
+  <li><a href="common_problems.html">Common Problems</a> -- description
+of a number of common bugs, errors, pitfalls and anti-patterns when
+using the engine.</li>
+  <li><a
+ href="http://www.ibm.com/developerworks/linux/library/l-simple.html">IBM
+DeveloperWorks Article</a> by Dr. David Mertz -- discusses (and teaches
+the use of) SimpleParse 1.0, contrasting the EBNF-based parser with
+tools such as regexen for text-processing tasks. &nbsp;Watch also
+for Dr. Mertz' upcoming book Text Processing with Python</li>
+  <li><a href="http://www.lemburg.com/files/python/mxTextTools.html">mxTextTools</a>
+documentation -- documents the underlying mxTextTools engine.
+&nbsp;Hopefully most users of SimpleParse who aren't actually
+creating custom/prebuilt parsing elements shouldn't need this link.<br>
+  </li>
+  <li><a href="pydoc/simpleparse.html">PyDoc references</a> --
+automatically generated documentation on the various elements within
+the package. Of particular interest are the library of
+reusable structures (<a href="pydoc/simpleparse.common.html">simpleparse.common</a>)
+and the <a href="pydoc/simpleparse.parser.html">Parser class</a>,
+which is the primary interface for the parsing system.<br>
+  </li>
+</ul>
+<h2>Acquisition and Installation</h2>
+<p> You will need a copy of Python with <a
+ href="http://www.python.org/sigs/distutils-sig/download.html">distutils</a>
+support (Python versions 2.0 and above include this). You'll also need
+a C
+compiler compatible with your Python build and understood by distutils.</p>
+<p>To install the base SimpleParse engine, <a
+ href="http://sourceforge.net/project/showfiles.php?group_id=55673">download
+the latest version</a> in your preferred format. If you are using the
+Win32 installer, simply run the executable. If you are using one of the
+source distributions, unpack the distribution into a
+temporary directory (maintaining the directory structure)
+then run: </p>
+<pre>setup.py install</pre>
+<p> in the top directory created by the expansion process.&nbsp; This
+will cause the patched mxTextTools library to be built as a sub-package
+of the simpleparse package and will then install the whole package to
+your system.<br>
+</p>
+<h2>Features/Changelog</h2>
+<p>New in 2.1.0a1:</p>
+<ul>
+  <li>Includes (patched) mxTextTools extension as part of SimpleParse,
+no longer uses stand-alone mxTextTools installations<br>
+  </li>
+  <li>Retooled setup environment to build and distribute directly from
+the CVS checkout</li>
+  <li>Bug-fixes in c_comment and c_nest_comment common productions
+(thanks to Stephen Waterbury), basic tests for the comment productions
+added<br>
+  </li>
+</ul>
+<p>New in 2.0.1:<br>
+</p>
+<ul>
+  <li>Bug fix in ISO Date Time parser test cases, was assuming Canadian
+EST timezone.<br>
+  </li>
+  <li>Bug fix for error-on-fail SyntaxError's when used with optional
+string message (2.0.1.a3)</li>
+</ul>
+<blockquote>diff -w -r1.4 error.py<br>
+32c32<br>
+&lt; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return
+'%s: %s'%( self.__class__.__name__, self.messageFormat(message) )<br>
+---<br>
+&gt; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return
+'%s: %s'%( self.__class__.__name__, self.messageFormat(self.message) )</blockquote>
+<ul>
+  <li>Case-insensitive literal values declared with c"literal" in the
+default grammar (new in 2.0.1a2)<br>
+  </li>
+  <li>Significant optimisations to the generated parse tables, can
+result in huge speedups for very formal grammars<br>
+  </li>
+</ul>
+<p>New in 2.0:</p>
+<ul>
+  <li>New, refactored and simplified API. Most of the time you only
+need to deal with a single class for all your interactions with the
+parser system (<a href="pydoc/simpleparse.parser.html">simpleparse.parser</a>.Parser),
+and one module if you decide to use the provided post-processing
+mechanism (<a href="pydoc/simpleparse.dispatchprocessor.html">simpleparse.dispatchprocessor</a>).</li>
+  <li>"Expanded Productions" -- allow you to define productions whose
+children are reported as if the enclosing production did not exist
+(allows you to use productions for organisational as well as
+reporting purposes)</li>
+  <li>Exposure of <a
+ href="processing_result_trees.html#nonstandardresulttrees">callout
+mechanism</a> in mxTextTools</li>
+  <li>Exposure of "LookAhead" mechanism in mxTextTools (allows you to
+spell "is followed by", "is not followed by", or "matches x but
+doesn't match y" in SimpleParse EBNF grammars). &nbsp;Specified with
+the prefix ?, as in ?-"this" which matches iff "this" is not the next
+item, but on matching doesn't move the read-head forward (more
+precisely, it causes the engine to continue processing at the previous
+position).</li>
+  <li>"Error on fail" error-reporting facility, allows you to raise
+Parser Syntax Errors when particular productions (or element tokens)
+fail to match. &nbsp;Allow for fairly flexible error reporting.
+&nbsp;To specify, just add a '!' character after the element token
+that must match, or include it as a stand-alone token in a sequential
+group to specify that all subsequent items must succeed. &nbsp;You can
+specify an error message format by using a string literal after the !
+character.</li>
+  <li>Library of common constructs (<a
+ href="pydoc/simpleparse.common.html">simpleparse.common</a> package)
+which are easily included in your grammars<br>
+  </li>
+  <li>Hexidecimal escapes for string and character ranges</li>
+  <li>Rewritten generators -- the generator interface has been
+seperated from the parser interfaces, this makes it possible to
+write grammars directly using generator objects if desired, and
+allows defining the EBNF grammar using the same tools as generate
+derived parsers</li>
+  <li>An XML-Parser (including DTD parsing) based on the XML
+specification's EBNF (this is not a production parser, merely an
+example for parsing a complex file format, and is not yet Unicode
+capable)</li>
+  <li>Example VRML97 and LISP parsers<br>
+  </li>
+  <li>Compatability API for SimpleParse 1.0 applications<br>
+  </li>
+  <li>With the non-recursive mxTextTools, can process (albeit
+inefficiently) recursion-as-repetition grammars </li>
+  <li>Non-recursive rewrite of mxTextTools now ~95% of the speed of the
+recursive version </li>
+</ul>
+<p> General</p>
+<ul>
+  <li>Simple-to-use interface, define an EBNF and start parsing</li>
+  <li>Fast for small files -- this is primarily a feature of the
+underlying TextTools engine, rather than a particular feature of the
+parser generator.</li>
+  <li>Allows pre-built and external parsing functions, which allows you
+to define Python methods to handle tricky parsing tasks</li>
+</ul>
+<h3>"Class" of Parsers Generated</h3>
+<p>Our (current) parsers are top-down, in that they work from the top
+of the parsing graph (the root production). They are not, however,
+tokenising parsers, so there is no appropriate LL(x) designation as far
+as I can see, and there is an arbitrary lookahead mechanism that could
+theoretically parse the entire rest of the file just to see if a
+particular character matches). &nbsp;I would hazard a guess that they
+are theoretically closest to a deterministic recursive-descent parser.<br>
+</p>
+<p>There are no backtracking facilities, so any ambiguity is handled by
+choosing the first successful match of a grammar (not the longest, as
+in most top-down parsers, mostly because without tokenisation, it would
+be expensive to do checks for each possible match's length). &nbsp;As a
+result of this, the parsers are entirely deterministic.<br>
+</p>
+<p>The time/memory characteristics are such that, in general, the time
+to parse an input text varies with the amount of text to parse. There
+are two major factors, the time to do the actual parsing (which, for
+simple deterministic grammars should be close to linear with the length
+of the text, though a pathalogical grammar might have radically
+different operating characteristics) and the time to build the results
+tree (which depends on the memory architecture of the machine, the
+currently free memory, and the phase of the moon). &nbsp;As a rule,
+SimpleParse parsers will be faster (for suitably limited grammars) than
+anything you can code directly in Python. &nbsp;They will not generally
+outperform grammar-specific parsers written in C.<br>
+</p>
+<h2>Missing Features<br>
+</h2>
+<ul>
+  <li>SimpleParse does not current use an Earley or similar highly
+generalised parser, instead, it uses a simple deterministic parsing
+algorithm which, though fast for certain classes of
+problems, is incapable of dealing with ambiguity, backtracking or
+cross-references</li>
+  <li>The library of common patterns is extremely sparse</li>
+  <li>Unicode support</li>
+  <li>There is no analysis and only minimal reduction done on the
+grammar. &nbsp;Having now read most of <a
+ href="http://www.cs.vu.nl/%7Edick/PTAPG.html">Parsing Techniques - A
+Practical Guide</a>, I can see how some fairly significant changes will
+be required to support such operations (and thereby the more common
+parsing techniques).<br>
+  </li>
+</ul>
+<h2>Possible Future Directions</h2>
+<ul>
+  <li>Alternative parsing back-ends -- the new objectgenerator module
+is fairly well isolated from the rest of the system, and
+encompasses most of the dependencies on the mxTextTools engine. Adding
+an optional Earley or similar back-end should be
+possible with minimal upset to the project. &nbsp;A backend using re
+objects is another possibility (my precursor mcf.pars engine was
+written to use regexen for parsing, and was an acceptable (though
+not stellar) performer).</li>
+  <li>Alternative EBNF grammars -- SimpleParse's EBNF, though fairly
+readily understood, is not by any means the only EBNF variant,
+providing support for a number of EBNF variants would ease the job
+of porting grammars to the system.</li>
+  <li>More common/library code -- common data formats, HTML and/or
+SGML parsers</li>
+</ul>
+<p> mxTextTools Rewrite Enhancements</p>
+<ul>
+  <li>Case-insensitive matching commands? </li>
+  <li>Backtracking support?</li>
+</ul>
+<p>Alternate C Back-end?<br>
+</p>
+<ul>
+  <li>Given the amount of effort poured into the mxTextTools engine,
+this may seem silly, but it would be nice to implement a more advanced
+parsing algorithm directly in C, without going through the
+assembly-like
+interface of mxTextTools. &nbsp;Given that Marc-Andr&eacute; isn't
+interested
+in adopting the non-recursive codebase, there's not much point
+retaining compatability with mxTextTools, so moving to a more
+parser-friendly engine might be the best approach.</li>
+</ul>
+<h3>mxBase/mxTextTools Installation<br>
+</h3>
+<p><span style="font-weight: bold;">NOTE:</span> This section only
+applies to SimpleParse versions before 2.1.0, SimpleParse 2.1.0 and
+above include a patched version of mxTextTools already!</p>
+<p>You will want an mxBase
+2.1.0 distribution to run SimpleParse, preferably with the
+non-recursive rewrite. If you want to use
+the non-recursive implementation, you will need to get the source
+archive for mxTextTools. &nbsp;It is possible to use mxBase 2.0.3 with
+SimpleParse,
+but not to use it for building the non-recursive TextTools engine
+(2.0.3 also lacks a lot of features and bug-fixes found in the 2.1.0
+versions).</p>
+<p>Note: without the <span style="font-weight: bold;">non-recursive
+rewrite</span> of 2.1.0 (i.e. with the recursive version), the test
+suite will not pass all tests.&nbsp;
+I'm not sure why they fail with the recursive version, but it
+does
+argue for using the non-recursive rewrite.</p>
+<p>To build the non-recursive TextTools engine, you'll need to
+get the source distribution for the non-recursive implementation from
+the <a
+ href="http://sourceforge.net/project/showfiles.php?group_id=55673">SimpleParse
+file repository</a>.&nbsp; Note,
+there are incompatabilities in the mxBase 2.1 versions that make it
+necessary to use the versions specified below to build the
+non-recursive versions.<br>
+</p>
+<ul>
+  <li>Python 2.2.x, <a
+ href="http://lists.egenix.com/mailman-archives/egenix-users/2002-August/000078.html">mxBase
+2.1b5</a>, non-recursive <a
+ href="https://sourceforge.net/project/showfiles.php?group_id=55673&amp;package_id=53017&amp;release_id=108636">1.0.0b4</a><br>
+  </li>
+  <li>Python 2.3.x, <a
+ href="http://lists.egenix.com/mailman-archives/egenix-users/2003-August/000262.html">mxBase
+2.1</a> August 2003 Shapshot, non-recursive <a
+ href="https://sourceforge.net/project/showfiles.php?group_id=55673&amp;package_id=53017">1.0.0b5+</a></li>
+</ul>
+<p>This archive is intended to be expanded over the
+mxBase source archive from the top-level directory, replacing one file
+and
+adding four others.</p>
+<p>
+</p>
+<pre>cd egenix-mx-base-2.1.0<br>gunzip non-recursive-1.0.0b1.tar.gz<br>tar -xvf non-recursive-1.0.0b1.tar<br></pre>
+<p>(Or use WinZip on Windows). When you have completed that, run:</p>
+<pre>setup.py build --force install<br></pre>
+<p> in the top directory of the eGenix-mx-base source tree. </p>
+<h2><a name="License"></a>Copyright, License &amp; Disclaimer</h2>
+<p>The 2.1.0 and greater releases include the eGenix mxTextTools
+extension:</p>
+<p>Licensed under
+the eGenix.com Public License see the <a href="mxLicense.html">mxLicense.html</a>
+file for details on
+licensing terms for the original library, the eGenix extensions are:<br>
+<br>
+&nbsp;&nbsp;&nbsp; Copyright (c) 1997-2000, Marc-Andre Lemburg<br>
+&nbsp;&nbsp;&nbsp; Copyright (c) 2000-2001, eGenix.com Software GmbH</p>
+<p>Extensions to the eGenix extensions (most significantly the rewrite
+of the core loop) are copyright Mike Fletcher and released under the
+SimpleParse License below:</p>
+<p>&nbsp;&nbsp;&nbsp; Copyright &copy; 2003-2006, Mike Fletcher</p>
+<p>SimpleParse License:</p>
+<p style="margin-left: 80px;">Copyright &copy; 1998-2006, Copyright by
+Mike C. Fletcher; All Rights Reserved.<br>
+mailto: <a href="mailto:mcfletch@users.sourceforge.net">mcfletch@users.sourceforge.net</a>
+</p>
+<p style="margin-left: 80px;">Permission to use, copy, modify, and
+distribute this software and
+its documentation for any purpose and without fee or royalty is
+hereby granted, provided that the above copyright notice
+appear in all copies and that both the copyright notice and
+this permission notice appear in supporting documentation or
+portions thereof, including modifications, that you make. </p>
+<p style="margin-left: 80px;">THE AUTHOR MIKE C. FLETCHER DISCLAIMS ALL
+WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHOR
+BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE!</p>
+<p align="center">A <a href="http://sourceforge.net"> <img
+ alt="SourceForge Logo"
+ src="http://sourceforge.net/sflogo.php?group_id=55673&amp;type=5"
+ border="0" height="62" width="210"></a><br>
+Open Source <a href="http://simpleparse.sourceforge.net/">project</a><br>
+</p>
+</body>
+</html>

File doc/mxLicense.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+   <TITLE>mx Extension Series - License Information</TITLE>
+   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+</HEAD>
+
+  <BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#0000EE" VLINK="#551A8B" ALINK="#FF0000">
+
+    <HR NOSHADE WIDTH="100%">
+    <H2>mx Extension Series - License Information</H2>
+
+    <HR SIZE=1 NOSHADE WIDTH="100%">
+    <TABLE WIDTH="100%">
+      <TR>
+	<TD>
+	  <SMALL>
+	    <A HREF="#Public">Public License</A> :
+	    <A HREF="#Commercial">Commercial License</A> :
+	    <A HREF="" TARGET="_top">Home</A>
+	</SMALL>
+	</TD>
+	<TD ALIGN=RIGHT>
+	  <SMALL>
+	    <FONT COLOR="#FF0000">Version 1.0.0</FONT>
+	  </SMALL>
+	</TD>
+    </TABLE>
+    <HR SIZE=1 NOSHADE WIDTH="100%">
+
+    <H3>Introduction</H3>
+
+    <UL>
+
+	<P>
+	  The mx Extensions Series packages are brought to you by the
+	  eGenix.com Software, Skills and Services GmbH, Langenfeld,
+	  Germany.  We are licensing our products under the following
+	  two different licenses:
+	<UL>
+	  <LI><A HREF="#Public">eGenix.com Public License</A>
+	  <LI><A HREF="#Commercial">eGenix.com Commercial License</A>.
+	</UL>
+
+	<P>
+	  The <I>Public License</I> is very similar to the Python 2.0
+	  license and covers the open source software made available
+	  by eGenix.com which is free of charge even for commercial
+	  use.
+
+	<P>
+	  The <I>Commercial License</I> is intended for covering
+	  commercial eGenix.com software, notably the mxODBC
+	  package. Only private and non-commercial use is free of
+	  charge.
+
+	<P>
+	  If you have questions regarding these licenses, please
+	  contact <A
+	  HREF="mailto:licenses@eGenix.com">Licenses@eGenix.com</A>.
+	  If you would like to bundle the software with your
+	  commercial product, please write to <A
+	  HREF="mailto:sales@eGenix.com">Sales@eGenix.com</A>
+	  for more information about the redistribution conditions and
+	  terms.
+
+    </UL>
+
+    <A NAME="Public"></A>
+    
+    <H3>eGenix.com Public License</H3>
+
+    <UL>
+
+	<P>
+	  The eGenix.com Public License is similar to the Python 2.0
+	  and considered an Open Source license (in the sense defined
+	  by the <A HREF="http://www.opensource.org">Open Source
+	  Intiative (OSI)</A>) by eGenix.com.
+
+	<P>
+	  The license should also be compatible to the <A
+	  HREF="http://www.gnu.org">GNU Public License</A> in case
+	  that matters. The only part which is known to have caused
+	  some problems with Richard Stallmann in the past is the
+	  choice of law clause.
+
+	<P>
+	<TABLE BORDER=1 CELLPADDING=15 BGCOLOR="#F3F3F3">
+
+	  <TR>
+	    <TD>
+
+<H3> EGENIX.COM PUBLIC LICENSE AGREEMENT             VERSION 1.0.0 </H3>
+<P>
+<H4> 1. Introduction </H4>
+<P>
+This "License Agreement" is between eGenix.com Software, Skills and
+Services GmbH ("eGenix.com"), having an office at Pastor-Loeh-Str. 48,
+D-40764 Langenfeld, Germany, and the Individual or Organization
+("Licensee") accessing and otherwise using this software in source or
+binary form and its associated documentation ("the Software").
+<P>
+<H4> 2. License  </H4>
+<P>
+Subject to the terms and conditions of this eGenix.com Public License
+Agreement, eGenix.com hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the eGenix.com Public License Agreement is
+retained in the Software, or in any derivative version of the Software
+prepared by Licensee.
+<P>
+<H4> 3. NO WARRANTY </H4>
+<P>
+eGenix.com is making the Software available to Licensee on an "AS IS"
+basis.  SUBJECT TO ANY STATUTORY WARRANTIES WHICH CAN NOT BE EXCLUDED,
+EGENIX.COM MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
+BY WAY OF EXAMPLE, BUT NOT LIMITATION, EGENIX.COM MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+<P>
+<H4> 4. LIMITATION OF LIABILITY </H4>
+<P>
+EGENIX.COM SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+(INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
+BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER
+PECUNIARY LOSS) AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE
+SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE
+POSSIBILITY THEREOF.
+<P>
+SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF
+INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE EXCLUSION OR
+LIMITATION MAY NOT APPLY TO LICENSEE.
+<P>
+<H4> 5. Termination </H4>
+<P>
+This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+<P>
+<H4> 6. General </H4>
+<P>
+Nothing in this License Agreement affects any statutory rights of
+consumers that cannot be waived or limited by contract.
+<P>
+Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between
+eGenix.com and Licensee.
+<P>
+If any provision of this License Agreement shall be unlawful, void, or
+for any reason unenforceable, such provision shall be modified to the
+extent necessary to render it enforceable without losing its intent,
+or, if no such modification is possible, be severed from this License
+Agreement and shall not affect the validity and enforceability of the
+remaining provisions of this License Agreement.
+<P>
+This License Agreement shall be governed by and interpreted in all
+respects by the law of Germany, excluding conflict of law
+provisions. It shall not be governed by the United Nations Convention
+on Contracts for International Sale of Goods.
+<P>
+This License Agreement does not grant permission to use eGenix.com
+trademarks or trade names in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+<P>
+The controlling language of this License Agreement is English. If
+Licensee has received a translation into another language, it has been
+provided for Licensee's convenience only.
+<P>
+<H4> 14. Agreement </H4>
+<P>
+By downloading, copying, installing or otherwise using the Software,
+Licensee agrees to be bound by the terms and conditions of this
+License Agreement.
+<P>
+
+	    </TD>
+
+	</TABLE>
+
+    </UL>
+
+    <A NAME="Commercial"></A>
+    
+    <H3>eGenix.com Commercial License</H3>
+
+    <UL>
+
+	<P>
+	  The eGenix.com Commercial License is covers commercial
+	  eGenix.com software, notably the mxODBC package. Only
+	  private and non-commercial use is free of charge. Usage of
+	  the software in commercial settings such as for implementing
+	  in-house applications in/for companies or consulting work
+	  where the software is used as tool requires a "Proof of
+	  Authorization" which can be bought from eGenix.com.
+
+	<P>
+	<TABLE BORDER=1 CELLPADDING=15 BGCOLOR="#F3F3F3">
+
+	  <TR>
+	    <TD>
+
+<H3> EGENIX.COM COMMERCIAL LICENSE AGREEMENT         VERSION 1.0.0 </H3>
+<P>
+<H4> 1. Introduction </H4>
+<P>
+This "License Agreement" is between eGenix.com Software, Skills and
+Services GmbH ("eGenix.com"), having an office at Pastor-Loeh-Str. 48,
+D-40764 Langenfeld, Germany, and the Individual or Organization
+("Licensee") accessing and otherwise using this software in source or
+binary form and its associated documentation ("the Software").
+<P>
+<H4> 2. Terms and Definitions </H4>
+<P>
+The "Software" covered under this License Agreement includes without
+limitation, all object code, source code, help files, publications,
+documentation and other programs, products or tools that are included
+in the official "Software Distribution" available from eGenix.com.
+<P>
+The "Proof of Authorization" for the Software is a written and signed
+notice from eGenix.com providing evidence of the extent of
+authorizations the Licensee has acquired to use the Software and of
+Licensee's eligibility for future upgrade program prices (if
+announced) and potential special or promotional opportunities. As
+such, the Proof of Authorization becomes part of this License Agreement.
+<P>
+Installation of the Software ("Installation") refers to the process of
+unpacking or copying the files included in the Software Distribution
+to an Installation Target.
+<P>
+"Installation Target" refers to the target of an installation
+operation.  Targets are defined as follows:
+<P>
+    1) "CPU" refers to a central processing unit which is able to
+    store and/or execute the Software (a server, personal computer, or
+    other computer-like device) using at most two (2) processors,
+<P>
+    2) "Site" refers to at most one hundred fifty (150) CPUs installed
+    at a single site of a company,
+<P>
+    3) "Corporate" refers to at most one thousand (1000) CPUs
+    installed at an unlimited number of sites of the company,
+<P>
+    4) "Developer CPU" refers to a single CPU used by at most one (1)
+    developer.
+<P>
+When installing the Software on a server CPU for use by other CPUs in
+a network, Licensee must obtain a License for the server CPU and for
+all client CPUs attached to the network which will make use of the
+Software by copying the Software in binary or source form from the
+server into their CPU memory. If a CPU makes use of more than two (2)
+processors, Licensee must obtain additional CPU licenses to cover the
+total number of installed processors. Likewise, if a Developer CPU is
+used by more than one developer, Licensee must obtain additional
+Developer CPU licenses to cover the total number of developers using
+the CPU.
+<P>
+"Commercial Environment" refers to any application environment which
+is aimed at producing profit. This includes, without limitation,
+for-profit organizations, work as independent contractor, consultant
+and other profit generating relationships with organizations or
+individuals.  
+<P>
+"Non-Commercial Environments" are all those application environments
+which do not directly or indirectly generate profit.  Educational and
+other officially acknowledged non-profit organizations are regarded as
+being a Non-Commercial Environment in the above sense.
+<P>
+<H4> 3. License Grant </H4>
+<P>
+Subject to the terms and conditions of this License Agreement,
+eGenix.com hereby grants Licensee a non-exclusive, world-wide license
+to
+<P>
+    1) use the Software to the extent of authorizations Licensee has
+    acquired and
+<P>
+    2) distribute, make and install copies to support the level of use
+    authorized, providing Licensee reproduces this License Agreement
+    and any other legends of ownership on each copy, or partial copy,
+    of the Software.
+<P>
+If Licensee acquires this Software as a program upgrade, Licensee's
+authorization to use the Software from which Licensee upgraded is
+terminated.
+<P>
+Licensee will ensure that anyone who uses the Software does so only in
+compliance with the terms of this License Agreement.
+<P>
+Licensee may not 
+<P>
+    1) use, copy, install, compile, modify, or distribute the Software
+    except as provided in this License Agreement;
+<P>
+    2) reverse assemble, reverse engineer, reverse compile, or
+    otherwise translate the Software except as specifically permitted
+    by law without the possibility of contractual waiver; or
+<P>
+    3) rent, sublicense or lease the Software.
+<P>
+<H4> 4. Authorizations </H4>
+<P>
+The extent of authorization depends on the ownership of a Proof of
+Authorization for the Software.  
+<P>
+Usage of the Software for any other purpose not explicitly covered by
+this License Agreement or granted by the Proof of Authorization is not
+permitted and requires the written prior permission from eGenix.com.
+<P>
+<H4> 4.1. Non-Commercial Environments </H4>
+<P>
+This section applies to all uses of the Software without a Proof of
+Authorization for the Software in a Non-Commercial Environment.
+<P>
+Licensee may copy, install, compile, modify and use the Software under
+the terms of this License Agreement FOR NON-COMMERCIAL PURPOSES ONLY.
+<P>
+Use of the Software in a Commercial Environment or for any other
+purpose, such as redistribution, IS NOT PERMITTED BY THIS LICENSE and
+requires a Proof of Authorization from eGenix.com.
+<P>
+<H4> 4.2. Evaluation Period for Commercial Environments </H4>
+<P>
+This section applies to all uses of the Software without a Proof of
+Authorization for the Software in a Commercial Environment.
+<P>
+Licensee may copy, install, compile, modify and use the Software under
+the terms of this License Agreement FOR EVALUATION AND TESTING
+PURPOSES and DURING A LIMITED EVALUATION PERIOD OF AT MOST THIRTY (30)
+DAYS AFTER INITIAL INSTALLATION ONLY. 
+<P>
+For use of the Software after the evaluation period or for any other
+purpose, such as redistribution, Licensee must obtain a Proof of
+Authorization from eGenix.com.
+<P>
+If Licensee decides not to obtain a Proof of Authorization after the
+evaluation period, Licensee agrees to cease using and to remove all
+installed copies of the Software.
+<P>
+<H4> 4.3. Usage under Proof of Authorization </H4>
+<P>
+This section applies to all uses of the Software provided that
+Licensee owns a Proof of Authorization for the Software.
+<P>
+Licensee may copy, install, compile, modify, use and distribute the
+Software to the extent of authorization acquired by the Proof of
+Authorization and under the terms an conditions of this License
+Agreement.
+<P>
+<H4> 5. Transfer of Rights and Obligations </H4>
+<P>
+Licensee may transfer all license rights and obligations under a Proof
+of Authorization for the Software to another party by transferring the
+Proof of Authorization and a copy of this License Agreement and all
+documentation.
+<P>
+The transfer of Licensee's license rights and obligations terminates
+Licensee's authorization to use the Software under the Proof of
+Authorization.
+<P>
+<H4> 6. Modifications </H4>
+<P>
+Software modifications may only be distributed in form of patches to
+the original files contained in the Software Distribution.
+<P>
+The patches must be accompanied by a legend of origin and ownership
+and a visible message stating that the patches are not original
+Software delivered by eGenix.com, nor that eGenix.com can be held
+liable for possible damages related directly or indirectly to the
+patches if they are applied to the Software.
+<P>
+<H4> 7. Experimental Code or Features </H4>
+<P>
+The Software may include components containing experimental code or
+features which may be modified substantially before becoming generally
+available.
+<P>
+These experimental components or features may not be at the level of
+performance or compatibility of generally available eGenix.com
+products. eGenix.com does not guarantee that any of the experimental
+components or features contained in the eGenix.com will ever be made
+generally available.
+<P>
+<H4> 8. Expiration and License Control Devices </H4>
+<P>
+Components of the Software may contain disabling or license control
+devices that will prevent them from being used after the expiration of
+a period of time or on Installation Targets for which no license was
+obtained.
+<P>
+Licensee will not tamper with these disabling devices or the
+components. Licensee will take precautions to avoid any loss of data
+that might result when the components can no longer be used.
+<P>
+<H4> 9. NO WARRANTY </H4>
+<P>
+eGenix.com is making the Software available to Licensee on an "AS IS"
+basis. SUBJECT TO ANY STATUTORY WARRANTIES WHICH CAN NOT BE EXCLUDED,
+EGENIX.COM MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, EGENIX.COM MAKES NO
+AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR
+FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE
+WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
+<P>
+<H4> 10. LIMITATION OF LIABILITY </H4>
+<P>
+TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL
+EGENIX.COM BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE
+FOR (I) ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+(INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
+BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER
+PECUNIARY LOSS) AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE
+SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE
+POSSIBILITY THEREOF; OR (II) ANY AMOUNTS IN EXCESS OF THE AGGREGATE
+AMOUNTS PAID TO EGENIX.COM UNDER THIS LICENSE AGREEMENT DURING THE
+TWELVE (12) MONTH PERIOD PRECEEDING THE DATE THE CAUSE OF ACTION
+AROSE.
+<P>
+SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF
+INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE EXCLUSION OR
+LIMITATION MAY NOT APPLY TO LICENSEE.
+<P>
+<H4> 11. Termination </H4>
+<P>
+This License Agreement will automatically terminate upon a material
+breach of its terms and conditions if not cured within thirty (30)
+days of written notice by eGenix.com. Upon termination, Licensee shall
+discontinue use and remove all installed copies of the Software.
+<P>
+<H4> 12. Indemnification  </H4>
+<P>
+Licensee hereby agrees to indemnify eGenix.com against and hold
+harmless eGenix.com from any claims, lawsuits or other losses that
+arise out of Licensee's breach of any provision of this License
+Agreement.
+<P>
+<H4> 13. Third Party Rights  </H4>
+<P>
+Any software or documentation in source or binary form provided along
+with the Software that is associated with a separate license agreement
+is licensed to Licensee under the terms of that license
+agreement. This License Agreement does not apply to those portions of
+the Software. Copies of the third party licenses are included in the
+Software Distribution.
+<P>
+<H4> 14. High Risk Activities  </H4>
+<P>
+The Software is not fault-tolerant and is not designed, manufactured
+or intended for use or resale as on-line control equipment in
+hazardous environments requiring fail-safe performance, such as in the
+operation of nuclear facilities, aircraft navigation or communication
+systems, air traffic control, direct life support machines, or weapons
+systems, in which the failure of the Software, or any software, tool,
+process, or service that was developed using the Software, could lead
+directly to death, personal injury, or severe physical or
+environmental damage ("High Risk Activities").
+<P>
+Accordingly, eGenix.com specifically disclaims any express or implied
+warranty of fitness for High Risk Activities.
+<P>
+Licensee agree that eGenix.com will not be liable for any claims or
+damages arising from the use of the Software, or any software, tool,
+process, or service that was developed using the Software, in such
+applications.
+<P>
+<H4> 15. General </H4>
+<P>
+Nothing in this License Agreement affects any statutory rights of
+consumers that cannot be waived or limited by contract.
+<P>
+Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between
+eGenix.com and Licensee.
+<P>
+If any provision of this License Agreement shall be unlawful, void, or
+for any reason unenforceable, such provision shall be modified to the
+extent necessary to render it enforceable without losing its intent,
+or, if no such modification is possible, be severed from this License
+Agreement and shall not affect the validity and enforceability of the
+remaining provisions of this License Agreement.
+<P>
+This License Agreement shall be governed by and interpreted in all
+respects by the law of Germany, excluding conflict of law
+provisions. It shall not be governed by the United Nations Convention
+on Contracts for International Sale of Goods.
+<P>
+This License Agreement does not grant permission to use eGenix.com
+trademarks or trade names in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+<P>
+The controlling language of this License Agreement is English. If
+Licensee has received a translation into another language, it has been
+provided for Licensee's convenience only.
+<P>
+<H4> 16. Agreement </H4>
+<P>
+By downloading, copying, installing or otherwise using the Software,
+Licensee agrees to be bound by the terms and conditions of this
+License Agreement.
+<P>
+<P>
+For question regarding this license agreement, please write to:
+<PRE>
+	  eGenix.com Software, Skills and Services GmbH
+	  Pastor-Loeh-Str. 48
+	  D-40764 Langenfeld
+	  Germany
+</PRE>
+
+	    </TD>
+
+	</TABLE>
+
+	<P>
+	  The following two sections give examples of the "Proof of
+	  Authorization" for a commercial use license of product under
+	  this license.
+
+	<P>
+	  When you buy such a license, you will receive a signed
+	  "Proof of Authorization" by postal mail within a week or
+	  two.  We will also send you the Proof of Authorization Key
+	  by e-mail to acknowledge acceptance of the payment.
+	  
+	<P>
+	<TABLE BORDER=1 CELLPADDING=15 BGCOLOR="#F3F3F3">
+
+	  <TR>
+	    <TD>
+
+<H3> EGENIX.COM PROOF OF AUTHORIZATION     (Example: CPU License) </H3>
+<P>
+<H4> 1. License Grant </H4>
+<P>
+eGenix.com Software, Skills and Services GmbH ("eGenix.com"), having
+an office at Pastor-Loeh-Str. 48, D-40764 Langenfeld, Germany, hereby
+grants the Individual or Organization ("Licensee") a non-exclusive,
+world-wide license to use the software listed below in source or
+binary form and its associated documentation ("the Software") under
+the terms and conditions of the eGenix.com Commercial License
+Agreement Version 1.0.0 and to the extent authorized by this Proof of
+Authorization.
+<P>
+<H4> 2. Covered Software </H4>
+<PRE>
+   Software Name:		   mxODBC Python ODBC Interface
+   Software Version:		   Version 2.0.0
+   Software Distribution:	   mxODBC-2.0.0.zip
+   Software Distribution MD5 Hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+   Operating System:		   any compatible operating system
+</PRE>
+<H4> 3. Authorizations </H4>
+<P>
+eGenix.com hereby authorizes Licensee to copy, install, compile,
+modify and use the Software on the following Installation Targets.
+<PRE>
+   Installation Targets:	   one (1) CPU
+</PRE>
+Redistribution of the Software is not allowed under this Proof of
+Authorization.
+<P>
+<H4> 4. Proof </H4>
+<P>
+This Proof of Authorization was issued by
+<P>
+<PRE>
+	      __________________________________
+
+
+	      Langenfeld, ______________________
+
+              Proof of Authorization Key:
+              xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
+</PRE>
+<P>
+
+	    </TD>
+
+	</TABLE>
+
+	<P>
+	  The next section gives an example of a "Developer CPU
+	  Licenses" which allows you to redistribute software built
+	  around the Software or integrating it. Please contact <A
+	  HREF="mailto:sales@eGenix.com">sales@eGenix.com</A> for
+	  questions about the redistribution conditions.
+
+	<P>
+	<TABLE BORDER=1 CELLPADDING=15 BGCOLOR="#F3F3F3">
+
+	  <TR>
+	    <TD>
+
+<H3> EGENIX.COM PROOF OF AUTHORIZATION  (Example: Developer License) </H3>
+<P>
+<H4> 1. License Grant </H4>
+<P>
+eGenix.com Software, Skills and Services GmbH ("eGenix.com"), having
+an office at Pastor-Loeh-Str. 48, D-40764 Langenfeld, Germany, hereby
+grants the Individual or Organization ("Licensee") a non-exclusive,
+world-wide license to use and distribute the software listed below in
+source or binary form and its associated documentation ("the
+Software") under the terms and conditions of the eGenix.com Commercial
+License Agreement Version 1.0.0 and to the extent authorized by this
+Proof of Authorization.
+<P>
+<H4> 2. Covered Software </H4>
+<PRE>
+   Software Name:		   mxODBC Python ODBC Interface
+   Software Version:		   Version 2.0.0
+   Software Distribution:	   mxODBC-2.0.0.zip
+   Software Distribution MD5 Hash: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+   Operating System:		   any compatible operating system
+</PRE>
+<H4> 3. Authorizations </H4>
+<P>
+<H4> 3.1. Application Development </H4>
+<P>
+eGenix.com hereby authorizes Licensee to copy, install, compile,
+modify and use the Software on the following Developer Installation
+Targets for the purpose of developing products using the Software as
+integral part.
+<PRE>
+   Developer Installation Targets: one (1) CPU
+</PRE>
+<H4> 3.2. Redistribution </H4>
+<P>
+eGenix.com hereby authorizes Licensee to redistribute the Software
+bundled with a products developed by Licensee on the Developer
+Installation Targets ("the Product") subject to the terms and
+conditions of the eGenix.com Commercial License Agreement for
+installation and use in combination with the Product on the following
+Redistribution Installation Targets, provided that:
+<P>
+    1) Licensee shall not and shall not permit or assist any third
+    party to sell or distribute the Software as a separate product;
+<P>
+    2) Licensee shall not and shall not permit any third party to
+<P>
+       (i) market, sell or distribute the Software to any end user
+       except subject to the eGenix Commercial License Agreement,
+<P>
+       (ii) rent, sell, lease or otherwise transfer the Software or
+       any part thereof or use it for the benefit of any third party,
+<P>
+       (iii) use the Software outside the Product or for any other
+       purpose not expressly licensed hereunder;
+<P>
+    3) the Product does not provide functions or capabilities similar
+    to those of the Software itself, i.e. the Product does not
+    introduce commercial competition for the Software as sold by
+    eGenix.com.
+<P>
+<PRE>
+   Redistribution Installation Targets:	any number of CPUs capable of
+					running the Product and the
+					Software
+</PRE>
+<H4> 4. Proof </H4>
+<P>
+This Proof of Authorization was issued by
+<P>
+<PRE>
+	      __________________________________
+
+
+	      Langenfeld, ______________________
+
+              Proof of Authorization Key:
+              xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
+</PRE>
+<P>
+
+	    </TD>
+
+	</TABLE>
+
+    </UL>
+
+    <HR WIDTH="100%">
+    <CENTER><FONT SIZE=-1>&copy; 2000, Copyright by eGenix.com
+    Software GmbH, Langengeld, Germany; All Rights Reserved. mailto:
+    <A HREF="mailto:info@egenix.com">info@egenix.com</A>
+    </FONT></CENTER>
+
+  </BODY>
+</HTML>

File doc/processing_result_trees.html

+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<html>
+<head>
+             	                                    
+  <meta http-equiv="CONTENT-TYPE"
+ content="text/html; charset=windows-1252">
+  <title>Processing Result Trees</title>
+                                                 	 	                    
+               
+  <meta name="GENERATOR" content="OpenOffice.org 641  (Win32)">
+             	                                    
+  <meta name="AUTHOR" content="Mike Fletcher">
+             	                                    
+  <meta name="CREATED" content="20020705;13585200">
+             	                                    
+  <meta name="CHANGEDBY" content="Mike Fletcher">
+             	                                    
+  <meta name="CHANGED" content="20020706;20023400">
+             	                                    
+  <link rel="stylesheet" type="text/css" href="sitestyle.css">
+</head>
+  <body lang="en-US">
+                         
+<h1>Processing Result Trees</h1>
+                         
+<p>SimpleParse parsers generate tree structures describing the structure
+of your parsed content. This document briefly describes the structures, a
+simple mechanism for processing the structures, and ways to alter the structures
+  as they are generated to accomplish specific goals.</p>
+                         
+<p>Prerequisites:</p>
+                         
+<ul>
+              <li>     Python 2.x programming   </li>
+              <li>     Familiarity with creating SimpleParse 2.0 Parsers
+(see:        <a href="scanning_with_simpleparse.html">Scanning with SimpleParse</a>)
+     </li>
+                       
+</ul>
+                          
+<h2>Standard Result Trees</h2>
+                         
+<p>SimpleParse uses the same result format as is used for the underlying
+mx.TextTools engine.  The engine returns a three-item tuple from the parsing
+of the top-level (root) production like so:</p>
+                         
+<pre>success, resultTrees, nextCharacter = myParser.parse( someText, processor=None)</pre>
+                       
+<p> Success is a Boolean value indicating whether the production (by default
+      the root production) matched (was satisfied) at all.  If success is
+true, nextCharacter is  an   integer value indicating the next character
+to be parsed in the text  (i.e.   someText[ startCharacter:nextCharacter
+] was parsed).<br>
+   </p>
+     
+<p><i>[New in 2.0.0b2]</i> Note: If success is false, then nextCharacter is
+set to the (very ill-defined) "error position", which is the position reached
+by the last TextTools command in the top-level production before the entire
+table failed. This is a lower-level value than is usefully predictable within
+SimpleParse (for instance, negative results which cause a failure will actually
+report the position after the positive version of the element token succeeds).
+�You might, I suppose, use it as a hint to your users of where the error
+occured, but using error-on-fail SyntaxErrors is <b>by far</b> the prefered
+method. �Basically, if success is false, consider nextCharacter to contain
+garbage data.<br>
+    </p>
+       
+<p>When the processor argument to parse is false (or a non-callable object),
+  the system does not attempt     to use the default processing mechanism,
+ and returns the result trees directly.     The standard format for result-tree
+  nodes is as follows:</p>
+                         
+<pre>(production_name, start, stop, children_trees)</pre>
+                       
+<p> Where start and stop represent indexes in the source text such that sourcetext
+      [ start: stop] is the text which matched this production. The <b>list
+  of children   </b> is the list of a list of the result-trees for the child
+  productions within    the production, or <b>None</b> (Note: that last is
+ important, you can't automatically do a "for" over the children_trees).<br>
+    </p>
+       
+<p>Expanded productions, as well as unreported productions (and the children
+      of unreported productions), will not appear in the result trees, neither
+     will the root production. See <a href="simpleparse_grammars.html">Understanding
+  SimpleParse Grammars</a> for details. However, LookAhead productions where
+ the non-lookahead value would normally return results, will return their
+results in the position where the LookAhead is included in the grammar.</p>
+       
+<p>If the processor argument to parse is true and callable, the processor
+  object will be called with (success, resultTrees, nextCharacter) on completion
+  of parsing. �The processor can then take whatever processing steps desired,
+  the return value from calling the processor with the results is returned
+ directly to the caller of parse.<br>
+     </p>
+                                           
+<h2>DispatchProcessor</h2>
+                         
+<p>SimpleParse 2.0 provides a simple mechanism for processing result trees,
+  a recursive series of calls to attributes of  a  �Processor� object with
+ functions to automate the call-by-name dispatching. �This processor implementation
+ is available for examination in the simpleparse.dispatchprocessor module.
+ �The main functions are:</p>
+                         
+<pre>def dispatch( source, tag, buffer ):<br>	"""Dispatch on source for tag with buffer<br><br>	Find the attribute or key "tag-object" (tag[0]) of source,<br>	then call it with (tag, buffer)<br>	"""<br>def dispatchList( source, taglist, buffer ):<br>	"""Dispatch on source for each tag in taglist with buffer"""<br><br>def multiMap( taglist, source=None, buffer=None ):<br>	"""Convert a taglist to a mapping from tag-object:[list-of-tags]<br>	<br>	For instance, if you have items of 3 different types, in any order,<br>	you can retrieve them all sorted by type with multimap( childlist)<br>	then access them by tagobject key.<br><br>	If source and buffer are specified, call dispatch on all items.<br>	"""<br><br>def singleMap( taglist, source=None, buffer=None ):<br>	"""Convert a taglist to a mapping from tag-object:tag, <br>	overwritting early with late tags.  If source and buffer<br>	are specified, call dispatch on all items."""<br><br>def getString( (tag, left, right, sublist), buffer):<br>	"""Return the string value of the tag passed"""<br><br>def lines( start=None, end=None, buffer=None ):<br>	"""Return number of lines in buffer[start:end]"""<br></pre>
+                       
+<p>With a class <b>DispatchProcessor</b>, which provides a __call__ implementation
+  to trigger dispatching for both "called as root processor" and "called
+to   process an individual result element" cases.<br>
+    </p>
+       
+<p>You define a DispatchProcessor sub-class with methods named for each production
+  that will be processed by the processor, with signatures of:<br>
+    </p>
+       
+<pre>from simpleparse.dispatchprocessor import *<br>class MyProcessorClass( DispatchProcessor ):<br>	def production_name( self, (tag,start,stop,subtags), buffer ):<br>		"""Process the given production and it's children"""<br></pre>
+       
+<p>Within those production-handling methods, you can call the dispatch functions
+  to process the sub-tags of the current production (keep in mind that the
+ sub-tags "list" may be a None object). �You can see examples of this processing
+ methodology in simpleparse.simpleparsegrammar, simpleparse.common.iso_date
+ and simpleparse.common.strings (among others).<br>
+    </p>
+       
+<p>For real-world Parsers, where you normally use the same processing class
+  for all runs of the parser, you can define a default Processor class like
+  so:<br>
+    </p>
+       
+<p></p>
+       
+<pre>class MyParser( Parser ):<br>	def buildProcessor( self ):<br>		return MyProcessorClass()</pre>
+                       
+<p>so that if no processor is explicitly specified in the parse call, your
+  "MyProcessorClass" instance will be used for processing the results.<br>
+    </p>
+                                          
+<h2><a name="nonstandardresulttrees"></a>Non-standard Result Trees (AppendMatch,
+AppendToTagobj, AppendTagobj,       CallTag)</h2>
+                         
+<p>SimpleParse 2.0 introduced features which expose certain of the mx.TextTool
+      library's features for producing non-standard result trees.  Although
+  not    generally recommended for use in �normal� parsers, these features
+ are useful    for certain types of text processing, and their exposure was
+ requested.  Each  flag has a different effect on the result tree, the particular
+ effects  are  discussed below.</p>
+                         
+<p>The exposure is through the Processor (or more precisely, a super-class
+      of Processor called �MethodSource�) object. To specify the use of one
+  of the flags, you set an attribute    in your MethodSource object (your
+Processor  object) with the    name _m_productionname (for the �method� to
+use, which  is either an actual    callable object for use with CallTag,
+or one of the  other mx.TextTools  flag   constants above).  In the case
+of  AppendTagobj  , you will likely want to   specify a particular tagobj
+object to be appended,  you do that by setting   an attribute named _o_productionname
+in your MethodSource.   For AppendToTagobj,   you <b>must</b><span
+ style=""> specify an _o_productionname  object with an �append� method.<br>
+    </span></p>
+       
+<p><span style="">Note: you can use MethodSource as your direct ancestor
+if you want to define a non-standard result tree, but don't want to do any
+processing of the results (this is the reason for having seperate classes).
+�MethodSource does not define a __call__ method.<br>
+    </span></p>
+                         
+<h3>CallTag</h3>
+                         
+<pre>_m_productionname = callableObject<code>(</code><br><code>    taglist,</code><br><code>    text,</code><br><code>    left,</code><br><code>    right,</code><br><code>    subtags</code><br><code>)</code></pre>
+                       
+<p> The given object/method is called on a successful match with the values
+      shown.  The text argument is the entire text buffer being parsed, the
+  rest    of the values are what you're accustomed to seeing in result tuples.</p>
+                         
+<p>Notes:</p>
+                         
+<ul>
+             	<li>               Nothing is (necessarily) added to the results 
+list when 	CallTag    is specified! �If you want something added, call taglist.append( 
+item ).   	    	</li>
+              <li>              Raising an error in the CallTag method will 
+ 	halt    parsing.    	</li>
+              <li>                    The  callableObject  is accessed from 
+ the   	MethodSource object using   standard getattr, so if you are using 
+a 	function,   it will need to define   a �self� parameter for 	the first 
+position.       </li>
+                       
+</ul>
+                         
+<h3>AppendToTagobj</h3>
+                         
+<pre>_m_productionname = AppendToTagobj<br>_o_productionname = objectWithAppendMethod</pre>
+                       
+<p> On a successful match, the system will call _o_productionname.append((None,l,r,subtags))
+      method.  For some processing tasks, it's conceivable you might want
+to   use   this method to pull out all instances of a production from a larger
+   (already-written)   grammar where going through the whole results tree
+to   find the deeply nested   productions is considered too involved.</p>
+                         
+<p>Notes:</p>
+                         
+<ul>
+             	<li>          Nothing is added to the results list when 	AppendToTagobj
+     is specified!    	</li>
+              <li>      Raising an error in the AppendToTagobj method 	will 
+ halt   parsing.    </li>
+                       
+</ul>
+                         
+<h3>AppendMatch</h3>
+                         
+<pre>_m_productionname = AppendMatch</pre>
+                       
+<p> On a successful match, the system will append the matched text to the 
+      result tree, rather than a tuple of results.  In situations where you 
+  just   want to extract the text, this can be useful.  The downside is that 
+  your  results tree has a non-standard format that you need to explicitly 
+ watch out for while processing the results.</p>
+                         
+<h3>AppendTagobj</h3>
+                         
+<pre>_m_productionname = AppendTagobj<br>_o_productionname = any object<br># object is optional, if omitted, the production name string is used</pre>
+                       
+<p> On a successful match, the system will append the tagobject to the result
+      tree, rather than a tuple of results.  In situations where you just
+want     notification that the production has matched (and it doesn't matter
+what    it matched), this can be useful.  The downside, again, is that your
+results    tree has a non-standard format that you need to explicitly watch
+out for   while processing the results.</p>
+             <a href="index.html">Up to index...</a><br>
+             
+<p align="center">A <a href="http://sourceforge.net"> <img
+ src="http://sourceforge.net/sflogo.php?group_id=55673&amp;type=5"
+ width="210" height="62" border="0" alt="SourceForge Logo">
+         </a><br>
+          Open Source <a href="http://simpleparse.sourceforge.net/">project</a><br>
+    </p>
+    <br>
+   <br>
+  <br>
+ <br>
+</body>
+</html>

File doc/pydoc/.cvsignore

+*.pyc
+*.so
+*.html

File doc/pydoc/builddocs.py

+"""Script to automatically generate PyTable documentation"""
+import pydoc2
+
+if __name__ == "__main__":
+	excludes = [
+		"Numeric",
+		"_tkinter",
+		"Tkinter",
+		"math",
+		"string",
+		"twisted",
+	]
+	stops = [
+	]
+
+	modules = [
+		'simpleparse',
+		'simpleparse.common',
+		'simpleparse.stt',
+		'simpleparse.stt.TextTools',
+		'simpleparse.stt.TextTools.mxTextTools',
+		'simpleparse.examples',
+		'simpleparse.tests',
+		'simpleparse.xml',
+		'__builtin__',
+	]	
+	pydoc2.PackageDocumentationGenerator(
+		baseModules = modules,
+		destinationDirectory = ".",
+		exclusions = excludes,
+		recursionStops = stops,
+	).process ()
+	

File doc/pydoc/pydoc2.py

+"""Pydoc sub-class for generating documentation for entire packages"""
+import pydoc, inspect, os, string
+import sys, imp, os, stat, re, types, inspect
+from repr import Repr
+from string import expandtabs, find, join, lower, split, strip, rfind, rstrip
+
+def classify_class_attrs(cls):
+	"""Return list of attribute-descriptor tuples.
+
+	For each name in dir(cls), the return list contains a 4-tuple
+	with these elements:
+
+		0. The name (a string).
+
+		1. The kind of attribute this is, one of these strings:
+			   'class method'    created via classmethod()
+			   'static method'   created via staticmethod()
+			   'property'        created via property()
+			   'method'          any other flavor of method
+			   'data'            not a method
+
+		2. The class which defined this attribute (a class).
+
+		3. The object as obtained directly from the defining class's
+		   __dict__, not via getattr.  This is especially important for
+		   data attributes:  C.data is just a data object, but
+		   C.__dict__['data'] may be a data descriptor with additional
+		   info, like a __doc__ string.
+	
+	Note: This version is patched to work with Zope Interface-bearing objects
+	"""
+
+	mro = inspect.getmro(cls)
+	names = dir(cls)
+	result = []
+	for name in names:
+		# Get the object associated with the name.
+		# Getting an obj from the __dict__ sometimes reveals more than
+		# using getattr.  Static and class methods are dramatic examples.
+		if name in cls.__dict__:
+			obj = cls.__dict__[name]
+		else:
+			try:
+				obj = getattr(cls, name)
+			except AttributeError, err:
+				continue
+
+		# Figure out where it was defined.
+		homecls = getattr(obj, "__objclass__", None)
+		if homecls is None:
+			# search the dicts.
+			for base in mro:
+				if name in base.__dict__:
+					homecls = base
+					break
+
+		# Get the object again, in order to get it from the defining
+		# __dict__ instead of via getattr (if possible).
+		if homecls is not None and name in homecls.__dict__:
+			obj = homecls.__dict__[name]
+
+		# Also get the object via getattr.
+		obj_via_getattr = getattr(cls, name)
+
+		# Classify the object.
+		if isinstance(obj, staticmethod):
+			kind = "static method"
+		elif isinstance(obj, classmethod):
+			kind = "class method"
+		elif isinstance(obj, property):
+			kind = "property"
+		elif (inspect.ismethod(obj_via_getattr) or
+			  inspect.ismethoddescriptor(obj_via_getattr)):
+			kind = "method"
+		else:
+			kind = "data"
+
+		result.append((name, kind, homecls, obj))
+
+	return result
+inspect.classify_class_attrs = classify_class_attrs
+
+
+class DefaultFormatter(pydoc.HTMLDoc):
+	def docmodule(self, object, name=None, mod=None, packageContext = None, *ignored):
+		"""Produce HTML documentation for a module object."""
+		name = object.__name__ # ignore the passed-in name
+		parts = split(name, '.')
+		links = []
+		for i in range(len(parts)-1):
+			links.append(
+				'<a href="%s.html"><font color="#ffffff">%s</font></a>' %
+				(join(parts[:i+1], '.'), parts[i]))
+		linkedname = join(links + parts[-1:], '.')
+		head = '<big><big><strong>%s</strong></big></big>' % linkedname
+		try:
+			path = inspect.getabsfile(object)
+			url = path
+			if sys.platform == 'win32':
+				import nturl2path
+				url = nturl2path.pathname2url(path)
+			filelink = '<a href="file:%s">%s</a>' % (url, path)
+		except TypeError:
+			filelink = '(built-in)'
+		info = []
+		if hasattr(object, '__version__'):
+			version = str(object.__version__)
+			if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
+				version = strip(version[11:-1])
+			info.append('version %s' % self.escape(version))
+		if hasattr(object, '__date__'):
+			info.append(self.escape(str(object.__date__)))
+		if info:
+			head = head + ' (%s)' % join(info, ', ')
+		result = self.heading(
+			head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
+
+		modules = inspect.getmembers(object, inspect.ismodule)
+
+		classes, cdict = [], {}
+		for key, value in inspect.getmembers(object, inspect.isclass):
+			if (inspect.getmodule(value) or object) is object:
+				classes.append((key, value))
+				cdict[key] = cdict[value] = '#' + key
+		for key, value in classes:
+			for base in value.__bases__:
+				key, modname = base.__name__, base.__module__
+				module = sys.modules.get(modname)
+				if modname != name and module and hasattr(module, key):
+					if getattr(module, key) is base:
+						if not cdict.has_key(key):
+							cdict[key] = cdict[base] = modname + '.html#' + key
+		funcs, fdict = [], {}
+		for key, value in inspect.getmembers(object, inspect.isroutine):
+			if inspect.isbuiltin(value) or inspect.getmodule(value) is object:
+				funcs.append((key, value))
+				fdict[key] = '#-' + key
+				if inspect.isfunction(value): fdict[value] = fdict[key]
+		data = []
+		for key, value in inspect.getmembers(object, pydoc.isdata):
+			if key not in ['__builtins__', '__doc__']:
+				data.append((key, value))
+
+		doc = self.markup(pydoc.getdoc(object), self.preformat, fdict, cdict)
+		doc = doc and '<tt>%s</tt>' % doc
+		result = result + '<p>%s</p>\n' % doc
+
+		packageContext.clean ( classes, object )
+		packageContext.clean ( funcs, object )
+		packageContext.clean ( data, object )
+		
+		if hasattr(object, '__path__'):
+			modpkgs = []
+			modnames = []
+			for file in os.listdir(object.__path__[0]):
+				path = os.path.join(object.__path__[0], file)
+				modname = inspect.getmodulename(file)
+				if modname and modname not in modnames:
+					modpkgs.append((modname, name, 0, 0))
+					modnames.append(modname)
+				elif pydoc.ispackage(path):
+					modpkgs.append((file, name, 1, 0))
+			modpkgs.sort()
+			contents = self.multicolumn(modpkgs, self.modpkglink)
+##			result = result + self.bigsection(
+##				'Package Contents', '#ffffff', '#aa55cc', contents)
+			result = result + self.moduleSection( object, packageContext)
+		elif modules:
+			contents = self.multicolumn(
+				modules, lambda (key, value), s=self: s.modulelink(value))
+			result = result + self.bigsection(
+				'Modules', '#fffff', '#aa55cc', contents)
+
+		
+		if classes:
+##			print classes
+##			import pdb
+##			pdb.set_trace()
+			classlist = map(lambda (key, value): value, classes)
+			contents = [
+				self.formattree(inspect.getclasstree(classlist, 1), name)]
+			for key, value in classes:
+				contents.append(self.document(value, key, name, fdict, cdict))
+			result = result + self.bigsection(
+				'Classes', '#ffffff', '#ee77aa', join(contents))
+		if funcs:
+			contents = []
+			for key, value in funcs:
+				contents.append(self.document(value, key, name, fdict, cdict))
+			result = result + self.bigsection(
+				'Functions', '#ffffff', '#eeaa77', join(contents))
+		if data:
+			contents = []
+			for key, value in data:
+				try:
+					contents.append(self.document(value, key))
+				except Exception, err:
+					pass
+			result = result + self.bigsection(
+				'Data', '#ffffff', '#55aa55', join(contents, '<br>\n'))
+		if hasattr(object, '__author__'):
+			contents = self.markup(str(object.__author__), self.preformat)
+			result = result + self.bigsection(
+				'Author', '#ffffff', '#7799ee', contents)
+		if hasattr(object, '__credits__'):
+			contents = self.markup(str(object.__credits__), self.preformat)
+			result = result + self.bigsection(
+				'Credits', '#ffffff', '#7799ee', contents)
+
+		return result
+
+	def classlink(self, object, modname):
+		"""Make a link for a class."""
+		name, module = object.__name__, sys.modules.get(object.__module__)
+		if hasattr(module, name) and getattr(module, name) is object:
+			return '<a href="%s.html#%s">%s</a>' % (
+				module.__name__, name, name
+			)
+		return pydoc.classname(object, modname)
+	
+	def moduleSection( self, object, packageContext ):
+		"""Create a module-links section for the given object (module)"""
+		modules = inspect.getmembers(object, inspect.ismodule)
+		packageContext.clean ( modules, object )
+		packageContext.recurseScan( modules )
+
+		if hasattr(object, '__path__'):
+			modpkgs = []
+			modnames = []
+			for file in os.listdir(object.__path__[0]):
+				path = os.path.join(object.__path__[0], file)
+				modname = inspect.getmodulename(file)
+				if modname and modname not in modnames:
+					modpkgs.append((modname, object.__name__, 0, 0))
+					modnames.append(modname)
+				elif pydoc.ispackage(path):
+					modpkgs.append((file, object.__name__, 1, 0))
+			modpkgs.sort()
+			# do more recursion here...
+			for (modname, name, ya,yo) in modpkgs:
+				packageContext.addInteresting( join( (object.__name__, modname), '.'))
+			items = []
+			for (modname, name, ispackage,isshadowed) in modpkgs:
+				try:
+					# get the actual module object...
+##					if modname == "events":
+##						import pdb
+##						pdb.set_trace()
+					module = pydoc.safeimport( "%s.%s"%(name,modname) )
+					description, documentation = pydoc.splitdoc( inspect.getdoc( module ))
+					if description:
+						items.append(
+							"""%s -- %s"""% (
+								self.modpkglink( (modname, name, ispackage, isshadowed) ),
+								description,
+							)
+						)
+					else:
+						items.append(
+							self.modpkglink( (modname, name, ispackage, isshadowed) )
+						)
+				except:
+					items.append(
+						self.modpkglink( (modname, name, ispackage, isshadowed) )
+					)
+			contents = string.join( items, '<br>')
+			result = self.bigsection(
+				'Package Contents', '#ffffff', '#aa55cc', contents)
+		elif modules:
+			contents = self.multicolumn(
+				modules, lambda (key, value), s=self: s.modulelink(value))
+			result = self.bigsection(
+				'Modules', '#fffff', '#aa55cc', contents)
+		else:
+			result = ""
+		return result
+	
+	
+class AlreadyDone(Exception):
+	pass
+	
+
+
+class PackageDocumentationGenerator:
+	"""A package document generator creates documentation
+	for an entire package using pydoc's machinery.
+
+	baseModules -- modules which will be included
+		and whose included and children modules will be
+		considered fair game for documentation
+	destinationDirectory -- the directory into which
+		the HTML documentation will be written
+	recursion -- whether to add modules which are
+		referenced by and/or children of base modules
+	exclusions -- a list of modules whose contents will
+		not be shown in any other module, commonly
+		such modules as OpenGL.GL, wxPython.wx etc.
+	recursionStops -- a list of modules which will
+		explicitly stop recursion (i.e. they will never
+		be included), even if they are children of base
+		modules.
+	formatter -- allows for passing in a custom formatter
+		see DefaultFormatter for sample implementation.
+	"""
+	def __init__ (
+		self, baseModules, destinationDirectory = ".",
+		recursion = 1, exclusions = (),
+		recursionStops = (),
+		formatter = None
+	):
+		self.destinationDirectory = os.path.abspath( destinationDirectory)
+		self.exclusions = {}
+		self.warnings = []
+		self.baseSpecifiers = {}
+		self.completed = {}
+		self.recursionStops = {}
+		self.recursion = recursion
+		for stop in recursionStops:
+			self.recursionStops[ stop ] = 1
+		self.pending = []
+		for exclusion in exclusions:
+			try:
+				self.exclusions[ exclusion ]= pydoc.locate ( exclusion)
+			except pydoc.ErrorDuringImport, value:
+				self.warn( """Unable to import the module %s which was specified as an exclusion module"""% (repr(exclusion)))
+		self.formatter = formatter or DefaultFormatter()
+		for base in baseModules:
+			self.addBase( base )
+	def warn( self, message ):
+		"""Warnings are used for recoverable, but not necessarily ignorable conditions"""
+		self.warnings.append (message)
+	def info (self, message):
+		"""Information/status report"""
+		print message
+	def addBase(self, specifier):
+		"""Set the base of the documentation set, only children of these modules will be documented"""
+		try:
+			self.baseSpecifiers [specifier] = pydoc.locate ( specifier)
+			self.pending.append (specifier)
+		except pydoc.ErrorDuringImport, value:
+			self.warn( """Unable to import the module %s which was specified as a base module"""% (repr(specifier)))
+	def addInteresting( self, specifier):
+		"""Add a module to the list of interesting modules"""
+		if self.checkScope( specifier):
+##			print "addInteresting", specifier
+			self.pending.append (specifier)
+		else:
+			self.completed[ specifier] = 1
+	def checkScope (self, specifier):
+		"""Check that the specifier is "in scope" for the recursion"""
+		if not self.recursion:
+			return 0
+		items = string.split (specifier, ".")
+		stopCheck = items [:]
+		while stopCheck:
+			name = string.join(items, ".")
+			if self.recursionStops.get( name):
+				return 0
+			elif self.completed.get (name):
+				return 0
+			del stopCheck[-1]
+		while items:
+			if self.baseSpecifiers.get( string.join(items, ".")):
+				return 1
+			del items[-1]
+		# was not within any given scope
+		return 0
+
+	def process( self ):
+		"""Having added all of the base and/or interesting modules,
+		proceed to generate the appropriate documentation for each
+		module in the appropriate directory, doing the recursion
+		as we go."""
+		try:
+			while self.pending:
+				try:
+					if self.completed.has_key( self.pending[0] ):
+						raise AlreadyDone( self.pending[0] )
+					self.info( """Start %s"""% (repr(self.pending[0])))
+					object = pydoc.locate ( self.pending[0] )
+					self.info( """   ... found %s"""% (repr(object.__name__)))
+				except AlreadyDone:
+					pass
+				except pydoc.ErrorDuringImport, value:
+					self.info( """   ... FAILED %s"""% (repr( value)))
+					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
+				except (SystemError, SystemExit), value:
+					self.info( """   ... FAILED %s"""% (repr( value)))
+					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
+				except Exception, value:
+					self.info( """   ... FAILED %s"""% (repr( value)))
+					self.warn( """Unable to import the module %s"""% (repr(self.pending[0])))
+				else:
+					page = self.formatter.page(
+						pydoc.describe(object),
+						self.formatter.docmodule(
+							object,
+							object.__name__,
+							packageContext = self,
+						)
+					)
+					file = open (
+						os.path.join(
+							self.destinationDirectory,
+							self.pending[0] + ".html",
+						),
+						'w',
+					)
+					file.write(page)
+					file.close()
+					self.completed[ self.pending[0]] = object
+				del self.pending[0]
+		finally:
+			for item in self.warnings:
+				print item
+			
+	def clean (self, objectList, object):
+		"""callback from the formatter object asking us to remove
+		those items in the key, value pairs where the object is
+		imported from one of the excluded modules"""
+		for key, value in objectList[:]:
+			for excludeObject in self.exclusions.values():
+				if hasattr( excludeObject, key ) and excludeObject is not object:
+					if (
+						getattr( excludeObject, key) is value or
+						(hasattr( excludeObject, '__name__') and
+						 excludeObject.__name__ == "Numeric"
+						 )
+					):
+						objectList[:] = [ (k,o) for k,o in objectList if k != key ]
+	def recurseScan(self, objectList):
+		"""Process the list of modules trying to add each to the
+		list of interesting modules"""
+		for key, value in objectList:
+			self.addInteresting( value.__name__ )
+
+
+